diff --git a/.gitattributes b/.gitattributes index d1d55f96e5..b71e748ea7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,3 +1,9 @@ -.gitattributes export-ignore -.gitignore export-ignore -*.texy linguist-language=Text +.gitattributes export-ignore +.github/ export-ignore +.gitignore export-ignore +AGENTS.md export-ignore +docs/ export-ignore +tests/ export-ignore + +*.php* diff=php +*.sh text eol=lf diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000000..803c0972ef --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,347 @@ +# To My Agents! + +It is my fervent wish that this file guide every AI coding agent working with code in this repository. + +## Project Overview + +This is the official documentation repository for the Nette PHP framework ecosystem (`github.com/nette/docs`). It holds the user manual for 33 packages, written in Texy markup and published at nette.org, doc.nette.org, latte.nette.org, tester.nette.org and tracy.nette.org. + +Pages are authored in English and Czech. From those two sources they are translated into eight further languages: `de`, `es`, `fr`, `it`, `ja`, `pl`, `ru`, `tr`. + +## CRITICAL: Language Version Rules + +**All edits MUST go only into the `/cs/` and `/en/` versions, and always into both at once.** + +Everything below follows from a single mechanism: the translation system maps content between languages **line by line**. Break that correspondence and the mapping silently produces wrong translations in every other mutation. + +1. **Edit only `/cs/` and `/en/`** — never touch `de`, `es`, `fr`, `it`, `ja`, `pl`, `ru`, `tr`. Those are generated, and edits there would be overwritten. +2. **Edit both versions in the same change** — a change that lands in only one of the pair is an incomplete change. +3. **Keep perfect line alignment** — both files must end up with the same number of lines, and the same information must sit on the same line number. Line 14 of `application/en/presenters.texy` corresponds to line 14 of `application/cs/presenters.texy`. +4. **Keep the same file set** — `/cs/` and `/en/` must contain exactly the same `.texy` files. Adding a page means creating it in both. +5. **Verify before committing** — `wc -l package/en/file.texy package/cs/file.texy` must report identical counts. + +Adding or removing a line therefore always means adding or removing it in both files simultaneously, blank lines included. If a sentence needs one extra line in Czech, the English version needs a matching line as well. + +## Documentation Structure + +Documentation is organized by package, language and article: + +``` +/ +├── en/ # English version +├── cs/ # Czech version +├── de/, es/, ... # Generated language variants — do not edit +├── files/ # Shared images and assets +└── meta.json # Package metadata (documented version, repo, composer name) +``` + +The `version` field in `meta.json` says which version of the package the pages describe. It is the baseline for `.{data-version}` markers, see [below](#texy-documentation-modifiers). + +### Package Inventory (33 packages) + +**Nette Framework** (https://doc.nette.org): +- `application/` - MVC framework, presenters, routing, components +- `assets/` - Asset management and Vite integration +- `bootstrap/` - Application initialization and configuration +- `caching/` - Caching mechanisms +- `command-line/` - Building CLI applications: switches, options, ANSI output +- `component-model/` - Component system and lifecycle +- `database/` - Database access layer, Explorer, transactions +- `dependency-injection/` - DI container, autowiring, services +- `forms/` - Form creation, validation, rendering +- `http/` - HTTP request/response, sessions, URLs +- `mail/` - Email sending +- `neon/` - NEON format parser +- `nette/` - Main documentation hub, glossary, installation +- `php-generator/` - PHP code generation +- `robot-loader/` - Automatic class loader +- `safe-stream/` - Safe stream handling +- `schema/` - Schema validation and generation +- `security/` - Authentication, authorization, passwords +- `tokenizer/` - String tokenization (library no longer developed) +- `utils/` - Arrays, strings, filesystem, validation, datetime + +**Key projects from the Nette ecosystem:** +- `ai/` - Nette AI (subdomain https://ai.nette.org) +- `ai-access/` - AI Access, one PHP interface for OpenAI, Claude, Gemini, DeepSeek and Grok +- `latte/` - Templating engine v3.0+ with a `/cookbook/` subdirectory, the only package with a nested structure (subdomain https://latte.nette.org) +- `tester/` - Testing framework with assertions and TestCase (subdomain https://tester.nette.org) +- `tracy/` - Debugger, dumper, extensions (subdomain https://tracy.nette.org) + +**Supporting** (subdomain https://doc.nette.org): +- `best-practices/` - Development patterns and recipes +- `contributing/` - Contribution guidelines and documentation standards +- `migrations/` - Version upgrades +- `quickstart/` - Getting started tutorial +- `tools/` - Developer tooling: Code Checker, Coding Standard, PHPStan rules, IDE support, X-ray +- `www/` - Website content, logo, license, packages (subdomain https://nette.org) + +**Others** (not part of Nette, only hosted on a Nette subdomain): +- `texy/` - Texy markup language processor (https://texy.nette.org) +- `dibi/` - Dibi database abstraction layer (https://dibi.nette.org) + +**Structure notes:** +- Every package has `@home.texy` (entry point). Most also have `@meta.texy` (metadata) and `@left-menu.texy` or `@menu.texy` (navigation). + +**Branch structure:** +- `master` - Current documentation (latest versions) +- `doc-3.x` - Version 3.x documentation +- `doc-2.x` - Legacy 2.x documentation + +## CRITICAL: Always Write Plain ASCII Quotes + +**In `.texy` files, always type the plain ASCII `"` and `'`. Never type typographic +quotes directly**, in any language — not `„…"`, not `«…»`, not `“…”`. + +Texy converts them to the correct typographic pair for the page's language during +rendering, driven by `TypographyModule::$locales` (17 languages: bg, cs, de, el, en, +es, fr, hu, it, ja, pl, pt, ro, ru, sl, tr, uk). The same ASCII source therefore +renders as `„text"` in German, `«text»` in Russian and `「text」` in Japanese. + +```texy +Mit "Initialisierung der Umgebung" meinen wir … ← correct, Texy handles it +Mit „Initialisierung der Umgebung" meinen wir … ← wrong, and broken on top +``` + +Why this is a rule and not a preference: + +- **Typing them by hand produces broken pairs.** The closing characters (U+201C, + U+2018) are unreliable to produce, so what usually comes out is an opening `„` + followed by an ASCII `"` — a mismatched pair that no tool flags. This really + happened in `application/de/how-it-works.texy` and had to be repaired by script. +- **Hardcoded quotes ignore the language.** A `„…"` typed into the Russian mutation + stays German-looking; Texy would have produced `«…»`. +- **It keeps the source diffable and greppable.** One byte per quote, no invisible + code points to normalize later. + +The same applies to code samples: keep whatever the English source uses, since inside +a code block Texy performs no typographic substitution at all. + +**The one exception** is the Texy documentation itself, where typographic quotes are the +subject matter and must appear literally: `texy/{cs,en}/configuration.texy` (the list of +locales), `texy/{cs,en}/syntax.texy` and `texy/cs/@home.texy`. Do not ASCII-ize those +lines. When touching them, take the pairs from `TypographyModule::$locales` — writing +them by hand produces broken pairs like `„text"`, which is exactly what those lines +suffered from before. Note that plain ASCII is equally wrong there: Texy would re-typeset +it to the *page's* locale, so a sample meant to show English quotes would come out with +Czech ones on the Czech page. Everywhere else the ASCII rule holds without exception. + +## Texy Documentation Modifiers + +Use these modifiers when documenting API elements in `.texy` files: + +**Methods** - use `[method]` modifier: +```texy +static fromBlank(int $width, int $height, ?ImageColor $color=null): Image .[method] +----------------------------------------------------------------------------------- +Creates a new true color image of the given dimensions. The default color is black. +``` + +**Prefer expressive array types over bare `array`.** In signatures, describe what an +array holds using phpDoc-style shapes that mirror the code's `@return`/`@param`: +- `getComponents(): IComponent[]` instead of `getComponents(): array` +- `getComponentTree(): list` (a numerically indexed list) instead of `: array` +- `array` for keyed maps + +Bare `array` is the least informative part of a signature. These phpDoc shapes are not +native PHP types, which is fine here: they only appear in documentation. + +**Latte filters** - use `[filter]` modifier: +```texy +batch(int $length, mixed $item): array .[filter] +------------------------------------------------ +Filter that simplifies outputting linear data in table form. +``` + +**New features** - use `{data-version:X.Y}` modifier with version number: +```texy +New great feature .{data-version:3.1.0} +--------------------------------------- +Description of the feature. +``` + +**Only mark versions NEWER than the documented baseline.** The baseline is the version +in the package's `meta.json`. A `.{data-version}` marker is only meaningful for a feature +added *after* the baseline, so the reader knows they need a newer patch/minor. + +**The baseline is the FLOOR of the documented version range, not the latest release.** +This is critical when `meta.json` is a whole series like `3.x`: +- `meta.json: 3.x` means the page serves readers on **any** 3.y — including old 3.0. + So a reader on 3.0 genuinely needs to know a feature arrived in 3.3.1. Therefore + **keep** every `.{data-version:3.y.z}` where `3.y.z > 3.0` — the floor is `3.0`, and + markers above the floor are meaningful. (E.g. `Using with PSR-16 .{data-version:3.3.1}` + on a `3.x` page is CORRECT and must stay.) +- `meta.json: 4.0` (a concrete version) has floor `4.0`. Then `.{data-version:4.0.0}` and + any `.{data-version:3.x.x}` are redundant (`<= 4.0`) and should be removed; keep only + markers for later releases (`4.0.6`, `4.1`, …). + +Rules of thumb: +- Do **not** add `.{data-version:X.0}` on a page whose baseline floor is `X.0`. +- Do **not** keep markers `<=` the baseline floor (e.g. `3.1.0` on a page documenting `4.0`). +- When bumping `meta.json` to a new major (e.g. `3.x` → `4.0`), the floor jumps from `3.0` + to `4.0`: **strip** all markers `<= 4.0`, keep only later releases. + +**Deprecated items** - use `[deprecated]` modifier (without version number): +```texy +static rgb(int $red, int $green, int $blue, int $transparency=0): array .[method][deprecated] +--------------------------------------------------------------------------------------------- +This function has been replaced by the `ImageColor` class. +``` + +**Combined modifiers** (e.g., new filter): +```texy +accept .[filter]{data-version:3.1.0} +------------------------------------ +Filter used during migration from Latte 3.0 to confirm behavior change acceptance. +``` + +**Chaining multiple modifier groups** — append additional `{...}` blocks without a leading dot (only the first one has `.`): +```texy +Národní prostředí .{data-version:3.0.18}{toc: Locale} +----------------------------------------------------- +``` + +**`.{data-version:X.Y.Z}` placements that work:** +- After a heading text (before the underline) — marks the whole section. +- On a list item, after an inline element: `` - `ClassName` .{data-version:3.2.9} - description `` — marks the item. +- On its own line before a paragraph as a block modifier — marks that paragraph. +- At the end of a paragraph, after the sentence period — Texy renders it correctly as a + `data-version` attribute of the paragraph (verified; there are existing precedents, e.g. + `dependency-injection/en/factory.texy`, `forms/en/in-presenter.texy`, `templates.texy`). + +## Headings and Anchors + +All headings automatically generate URL anchors based on their text. This allows linking directly to any section. + +**Automatic anchors:** +- Heading "Installing Claude Code" → anchor `#installing-claude-code` +- Heading "What's Next" → anchor `#what-s-next` + +**Linking to sections:** +```texy +See [installation guide |getting-started#installing-claude-code] for details. +``` + +**Custom anchors** - use `.{#anchor-name}` modifier when you need a specific anchor: +```texy +Installing Claude Code .{#Installing} +===================================== +``` + +**API signature headings** — a heading that documents a method or filter with a full +signature plus a `.[method]` or `.[filter]` modifier anchors to just the **member name**, +not the whole signature. The parameter list, return type, the modifier itself, and any +trailing `.{data-version:…}` are all stripped. Link to it with `|#name`: +```texy +map(callable $transformer): iterable .[filter]{data-version:3.1.6} → [… |#map] +beforeRender(Latte\Runtime\Template $template): void .[method] → [… |#beforeRender] +``` +The name keeps its original case (`#localDate`, `#beforeRender`), but links resolve +case-insensitively, so `|#localdate` works too. This special rule needs both the +parentheses and the `.[method]`/`.[filter]` modifier; a parenless heading like +`first .[filter]` falls back to the generic rule (strip from the first `.[` / `.{`), +which still yields `#first`. + +**Czech / diacritic anchors** — Texy slugifies Czech headings by stripping diacritics and lowercasing (e.g. "Automatické opakování" → `#automaticke-opakovani`). When linking, you may also write the original heading text after `#` (including diacritics) and Texy resolves it correctly — both forms work: +```texy +[see |transactions#automaticke-opakovani] +[viz |transactions#Automatické opakování] +``` + +Custom anchors are useful when: +- The automatic anchor would be too long or unclear +- You want a stable anchor that won't change if you rename the heading +- You need to match an existing link from another page + +## Documentation Guidelines + +Follow Nette's documentation standards: +- Start with simple concepts, progress to advanced topics +- Test all code examples for accuracy +- Use clear, concise language +- Minimal use of highlighting and special formatting +- Adhere to Nette's coding standard in code examples + +English is the primary language. Use DeepL Translator for translations, which will be reviewed by contributors. + +## Czech Terminology Conventions + +When writing Czech documentation, keep these technical terms in English (they are part of common Czech programming slang): +- `marker interface` (not "značkovací rozhraní") +- `deadlock`, `idle timeout`, `callback` + +Translate domain terms that have established Czech equivalents: +- "serialization failure" → "serializační konflikt" +- "outermost transaction" → "vnější transakce" (not "nejvyšší") +- "transient error" → "přechodná chyba" + +## Writing Style + +Nette documentation is known for its **friendly, approachable language** that remains **technically precise**. This style is a core part of the Nette brand and must be maintained across all documentation. + +### Key Principles + +1. **Friendly and approachable** – Write as if explaining to a colleague, not writing a technical manual +2. **Completely understandable** – No assumed knowledge; explain every concept when first introduced +3. **Technically accurate** – Use precise terminology and correct examples +4. **Only brief where clarity allows** – Never sacrifice understanding for brevity + +### Good vs Bad Examples + +**Good example:** +> MCP Inspector allows AI to look directly at your application – to see what services you have registered, what tables are in your database, and what routes lead where. Without this, the AI would have to guess based on patterns it learned during training. + +**Bad example:** +> MCP Inspector provides runtime introspection via DI container, database schema, and router inspection tools. + +The good example explains what the tool does and why it matters. The bad example is technically correct but assumes the reader already understands the concepts. + +### Tone Guidelines + +- Use "you" and "your" to address the reader directly +- Use "we" when walking through steps together ("Let's start by...") +- Explain the "why" not just the "what" +- Use concrete examples instead of abstract descriptions +- Anticipate questions and answer them proactively +- Avoid jargon; when technical terms are necessary, explain them + +### Structure Guidelines + +- Start each page with a `.[perex]` or `
` (for multiple paragraphs) summary that explains what the reader will learn +- Use clear, descriptive headings that tell the reader what each section contains +- Break complex topics into digestible sections +- Use code examples liberally – they're often clearer than prose +- End sections with "What's Next" links when appropriate + +## Mandatory Self-Review After Every Change + +**Every time you add or rewrite text in the documentation, you MUST afterwards critically +review that text and act on your own critique.** Writing the text is only the first step; +the change is not finished until you have reviewed it and incorporated the resulting +improvements. + +This applies to **both original writing and translation**: + +1. **Original text (new or rewritten)** – After writing, critically evaluate it against the + Writing Style and Documentation Guidelines above: Is it clear and completely + understandable? Technically accurate? Friendly but precise? Free of jargon and + redundancy? Does it follow the ASCII quotes rule? Then rewrite to fix every weakness + you found. +2. **Translations** – After translating a text, critically evaluate the **quality of the + translation**: Does it faithfully convey the meaning of the source? Does it read naturally + in the target language (not like a machine translation)? Is terminology consistent with the + conventions above (e.g. Czech Terminology Conventions)? Then incorporate your findings and + correct the translation. + +Do not present a change as complete, and do not stop, until this review-and-fix pass has been +done. When the change is non-trivial, briefly summarize what the self-review found and what +you improved. + +## Before Committing + +1. Verify the `/cs/` and `/en/` pair has identical line counts. +2. Run the Nette Code Checker, which validates whitespace, encoding and BOM across the repository. It also runs in CI on every push and pull request: + ```sh + composer create-project nette/code-checker code-checker + code-checker/code-checker + ``` diff --git a/ai/cs/@home.texy b/ai/cs/@home.texy new file mode 100644 index 0000000000..ab88aeae6f --- /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 +====================== + +**[Pluginy pro Claude Code a Codex |agent-plugins]** dodají 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 pluginy 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..ca4b062c10 --- /dev/null +++ b/ai/cs/@left-menu.texy @@ -0,0 +1,5 @@ +- [Úvod |@home] +- [Začínáme |getting-started] +- [Pluginy pro agenty |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..fac5320a42 --- /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 nebo Codex |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..b2e980152d --- /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 **[plugins for Claude Code and Codex |agent-plugins]** supply 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 plugins |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..fe43cedd74 --- /dev/null +++ b/ai/en/@left-menu.texy @@ -0,0 +1,5 @@ +- [Overview |@home] +- [Getting Started |getting-started] +- [Agent Plugins |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..335cf0e899 --- /dev/null +++ b/ai/en/agent-plugins.texy @@ -0,0 +1,444 @@ +Nette Plugins for Claude Code and Codex +*************************************** + +
+ +The "Nette plugins":https://github.com/nette/agent-plugins give Claude Code and Codex 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. + +The same plugins work in both tools and give you: +- 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 +============ + +The plugins come as a family, so you install only what you need: + +| 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 | + + +Claude Code +----------- + +If you haven't installed Claude Code yet, see the [complete setup guide |getting-started]. Once Claude Code is running, add the Nette marketplace and install the main 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 +``` + + +Codex +----- + +In Codex, add the same marketplace from the command line and install the main plugin: + +```shell +codex plugin marketplace add nette/agent-plugins +codex plugin add nette@nette +``` + +The other plugins are installed the same way, for example `codex plugin add nette-lint@nette`. Then start a new session so Codex picks them up. + +There is one extra step for the plugins that check or fix your files. Codex doesn't run a plugin's hooks just because you installed it; you have to approve them first. Open `/hooks` in Codex, review the hook definitions of the installed plugins and trust them. When a plugin update changes its hooks, you have to review them again. + + +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. The skills are identical in both clients, so whenever the examples below say Claude, the same applies to Codex. + + +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, the agent knows about it immediately and can suggest a fix. No more discovering errors in the browser. + +The checks run after the agent edits a file with its own editing tools. When it changes files through shell commands instead, no hook runs, so it's worth asking it to run your project's checks afterwards. + +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 the agent is running. So even if you keep several applications in one repository and start the agent 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 # Claude Code +codex plugin add nette-dev@nette # Codex +``` + +| 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. In Claude Code: + +```shell +/plugin install php-fixer@nette +/install-php-fixer +``` + +The second command installs `nette/coding-standard` globally. In Codex, install the plugin with `codex plugin add php-fixer@nette` and then pick the `install-php-fixer` skill, which does the same. 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 plugins work even better with [MCP Inspector |mcp-inspector] - a tool that lets the agent 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. Codex isn't registered by `init`, so add the server to its configuration yourself as described in [Other AI Tools |mcp-inspector#other-ai-tools]. 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. + +{{repo: nette/agent-plugins}} diff --git a/ai/en/getting-started.texy b/ai/en/getting-started.texy new file mode 100644 index 0000000000..b265d09e84 --- /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 claude-code +``` + +After installation, skip to [Starting Claude Code |#starting-claude-code]. + + +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 [Nette plugins |agent-plugins] provide, 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 +- [Agent Plugins |agent-plugins] - Explore all the skills (Claude Code and Codex 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..c75ee4aae7 --- /dev/null +++ b/ai/en/mcp-inspector.texy @@ -0,0 +1,326 @@ +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}} +{{repo: nette/mcp-inspector}} 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.}} 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..1c7788484b --- /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 [Nette plugins for Claude Code or Codex |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 @@ +{} diff --git a/application/cs/@home.texy b/application/cs/@home.texy index 310ddab4d8..88228c1487 100644 --- a/application/cs/@home.texy +++ b/application/cs/@home.texy @@ -52,13 +52,13 @@ Nette bylo vždy průkopníkem v oblasti webových technologií. Hlavní výhody ------------- -- **Bezpečnost**: Automatická obrana proti [zranitelnostem|nette:vulnerability-protection] jako XSS, CSRF, atd. +- **Bezpečnost**: Automatická obrana proti [zranitelnostem|nette:vulnerability-protection] jako XSS, CSRF atd. - **Produktivita**: Méně psaní, více funkcí díky chytrému návrhu - **Debugging**: [Tracy debugger|tracy:] s routovacím panelem - **Výkon**: Chytrá cache, lazy loading komponent - **Flexibilita**: Snadná úprava URL i po dokončení aplikace - **Komponenty**: Unikátní systém znovupoužitelných UI prvků -- **Moderní**: Plná podpora PHP 8.4+ a typového systému +- **Moderní**: Plná podpora PHP 8.3+ a typového systému Začínáme @@ -71,13 +71,13 @@ Začínáme 5. [Interaktivní komponenty |components] - Využití komponentového systému -Kompatbility s PHP ------------------- +Kompatibilita s PHP +------------------- | verze | kompatibilní s PHP |-----------|------------------- -| Nette Application 4.0 | PHP 8.1 - 8.4 -| Nette Application 3.2 | PHP 8.1 - 8.4 +| Nette Application 3.3 | PHP 8.3 - 8.5 +| Nette Application 3.2 | PHP 8.1 - 8.5 | Nette Application 3.1 | PHP 7.2 - 8.3 | Nette Application 3.0 | PHP 7.1 - 8.0 | Nette Application 2.4 | PHP 5.6 - 8.0 diff --git a/application/cs/@left-menu.texy b/application/cs/@left-menu.texy index 68d0549dc9..8848df94ca 100644 --- a/application/cs/@left-menu.texy +++ b/application/cs/@left-menu.texy @@ -1,5 +1,6 @@ Nette Application ***************** +- [Úvod |@home] - [Jak fungují aplikace? |how-it-works] - [Bootstrapping] - [Presentery |presenters] @@ -11,6 +12,7 @@ Nette Application - [AJAX & snippety |ajax] - [Multiplier |multiplier] - [Konfigurace |configuration] +- [Upgrade |upgrading] Další četba diff --git a/application/cs/ajax.texy b/application/cs/ajax.texy index fe7772083f..1e5f8bd9e5 100644 --- a/application/cs/ajax.texy +++ b/application/cs/ajax.texy @@ -14,7 +14,7 @@ V éře moderních webových aplikací, kde se často rozkládá funkcionalita m AJAXový požadavek ================= -AJAXový požadavek se v zásadě neliší od klasického HTTP požadavku. Zavolá se presenter s určitými parametry. A je na presenteru, jakým způsobem bude na požadavek reagovat - může vrátit data ve formátu JSON, odeslat část HTML kódu, XML dokument, atd. +AJAXový požadavek se v zásadě neliší od klasického HTTP požadavku. Zavolá se presenter s určitými parametry. A je na presenteru, jakým způsobem bude na požadavek reagovat - může vrátit data ve formátu JSON, odeslat část HTML kódu, XML dokument atd. Na straně prohlížeče inicializujeme AJAXový požadavek pomocí funkce `fetch()`: @@ -35,7 +35,7 @@ Chcete-li odeslat data ve formátu JSON, použijte metodu [`sendJson()` |present ```php public function actionExport(): void { - $this->sendJson($this->model->getData); + $this->sendJson($this->model->getData()); } ``` @@ -55,17 +55,17 @@ public function handleClick($param): void Snippety ======== -Nejsilnější prostředek, který nabízí Nette pro propojení serveru s klientem, představují snippety. Díky nim můžete z obyčejné aplikace udělat AJAXovou jen s minimálním úsilím a několika řádky kódu. Jak to celé funguje demonstruje příklad Fifteen, jehož kód najdete na [GitHubu |https://github.com/nette-examples/fifteen]. +Nejsilnější prostředek, který nabízí Nette pro propojení serveru s klientem, představují snippety. Díky nim můžete z obyčejné aplikace udělat AJAXovou jen s minimálním úsilím a několika řádky kódu. Jak to celé funguje, demonstruje příklad Fifteen, jehož kód najdete na [GitHubu |https://github.com/nette-examples/fifteen]. -Snippety, nebo-li výstřižky, umožnují aktualizovat jen části stránky, místo toho, aby se celá stránka znovunačítala. Jednak je to rychlejší a efektivnější, ale poskytuje to také komfortnější uživatelský zážitek. Snippety vám mohou připomínat Hotwire pro Ruby on Rails nebo Symfony UX Turbo. Zajímavé je, že Nette představilo snippety již o 14 let dříve. +Snippety, neboli výstřižky, umožňují aktualizovat jen části stránky, místo toho, aby se celá stránka znovunačítala. Jednak je to rychlejší a efektivnější, ale poskytuje to také komfortnější uživatelský zážitek. Snippety vám mohou připomínat Hotwire pro Ruby on Rails nebo Symfony UX Turbo. Zajímavé je, že Nette představilo snippety již o 14 let dříve. -Jak snippety fungují? Při prvním načtení stránky (ne-AJAXovém požadavku) se načte celá stránka včetně všech snippetů. Když uživatel interaguje se stránkou (např. klikne na tlačítko, odešle formulář, atd.), místo načtení celé stránky se vyvolá AJAXový požadavek. Kód v presenteru provede akci a rozhodne, které snippety je třeba aktualizovat. Nette tyto snippety vykreslí a odešle ve formě pole ve formátu JSON. Obslužný kód v prohlížeči získané snippety vloží zpět do stránky. Přenáší se tedy jen kód změněných snippetů, což šetří šířku pásma a zrychluje načítání oproti přenášení obsahu celé stránky. +Jak snippety fungují? Při prvním načtení stránky (ne-AJAXovém požadavku) se načte celá stránka včetně všech snippetů. Když uživatel interaguje se stránkou (např. klikne na tlačítko, odešle formulář, atd.), místo načtení celé stránky se vyvolá AJAXový požadavek. Kód v presenteru provede akci a rozhodne, které snippety je třeba aktualizovat. Nette tyto snippety vykreslí a odešle jako JSON payload s polem snippetů. Obslužný kód v prohlížeči získané snippety vloží zpět do stránky. Přenáší se tedy jen kód změněných snippetů, což šetří šířku pásma a zrychluje načítání oproti přenášení obsahu celé stránky. Pokud se pomocí `redrawControl()` žádný snippet neinvaliduje, Nette i na AJAXový požadavek vrátí celou stránku - snippety se posílají jen při invalidaci. Naja ---- -K obsluze snippetů na straně prohlížeče slouží [knihovna Naja |https://naja.js.org]. Tu [nainstalujte |https://naja.js.org/#/guide/01-install-setup-naja] jako node.js balíček (pro použití s aplikacemi Webpack, Rollup, Vite, Parcel a dalšími): +K obsluze snippetů na straně prohlížeče slouží [knihovna Naja |https://naja.js.org]. Tu [nainstalujte |https://naja.js.org/#/guide/01-install-setup-naja] jako Node.js balíček (pro použití s bundlery jako Webpack, Rollup, Vite, Parcel a dalšími): ```shell npm install naja @@ -74,7 +74,7 @@ npm install naja …nebo přímo vložte do šablony stránky: ```latte - + ``` Nejprve je potřeba knihovnu [inicializovat |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: @@ -121,6 +121,8 @@ Nette umožňuje ještě jemnější kontrolu toho, co se má překreslit. Uvede $this->redrawControl('header'); ``` +Případnou invalidaci lze zrušit druhým parametrem `$redraw`: voláním `$this->redrawControl('header', redraw: false)` označíte snippet jako nevyžadující překreslení. Úplná signatura je `redrawControl(?string $snippet = null, bool $redraw = true)`. + Snippety v Latte ---------------- @@ -133,7 +135,7 @@ Používání snippetů v Latte je nesmírně snadné. Chcete-li definovat čás {/snippet} ``` -Snippet vytvoří v HTML stránce element `
` se speciálním vygenerovaným `id`. Při překreslení snippetu se pak aktulizuje obsah tohoto elementu. Proto je nutné, aby při prvotním vykreslení stránky se vykreslily také všechny snippety, byť mohou být třeba na začátku prázdné. +Snippet vytvoří v HTML stránce element `
` se speciálním vygenerovaným `id`. Při překreslení snippetu se pak aktualizuje obsah tohoto elementu. Proto je nutné, aby se při prvotním vykreslení stránky vykreslily také všechny snippety, byť mohou být třeba na začátku prázdné. Můžete vytvořit i snippet s jiným elementem než `
` pomocí n:attributu: @@ -155,7 +157,9 @@ Názvy snippetů mohou být také výrazy: {/foreach} ``` -Takto nám vznikne několik snippetů `item-0`, `item-1` atd. Pokud bychom přímo invalidovali dynamický snippet (například `item-1`), nepřekreslilo by se nic. Důvod je ten, že snippety opravdu fungují jako výstřižky a vykreslují se jen přímo ony samotné. Jenže v šabloně fakticky žádný snippet pojmenovaný `item-1` není. Ten vznikne až vykonáváním kódu v okolí snippetu, tedy cyklu foreach. Označíme proto část šablony, která se má vykonat pomocí značky `{snippetArea}`: +Samo o sobě jde o nefunkční mezikrok: dynamický snippet vykreslený mimo statický `{snippet}` nebo `{snippetArea}` vyvolá `E_USER_WARNING` s hláškou *Dynamic snippets are allowed only inside static snippet/snippetArea.* Napravíme to níže. + +Takto nám vznikne několik snippetů `item-0`, `item-1` atd. Pokud bychom přímo invalidovali dynamický snippet (například `item-1`), nepřekreslilo by se nic. Důvod je ten, že snippety opravdu fungují jako výstřižky a vykreslují se jen přímo ony samotné. Jenže v šabloně fakticky žádný snippet pojmenovaný `item-1` není. Ten vznikne až vykonáváním kódu v okolí snippetu, tedy cyklu foreach. Označíme proto část šablony, která se má vykonat, pomocí značky `{snippetArea}`: ```latte
    @@ -198,7 +202,7 @@ $this->redrawControl('item'); Snippety v komponentách ----------------------- -Snippety můžete vytvářet i v [komponentách|components] a Nette je bude automaticky překreslovat. Ale platí tu určité omezení: pro překreslení snippetů volá metodu `render()` bez parametrů. Tedy nebude fungovat předávání parametrů v šabloně: +Snippety můžete vytvářet i v [komponentách|components] a Nette je bude automaticky překreslovat. Ale platí tu určité omezení: při překreslování snippetů Nette volá metodu `render()` bez parametrů. Tedy nebude fungovat předávání parametrů v šabloně: ```latte OK @@ -226,6 +230,12 @@ public function actionDelete(int $id): void ``` +Přesměrování +------------ + +Během AJAXového požadavku metody `redirect()` a `redirectUrl()` neposílají HTTP přesměrování. Místo toho zapíšou cílovou URL do payloadu (datový objekt odesílaný v odpovědi na AJAX), konkrétně do vlastnosti `payload.redirect`, a odešlou jej; samotné přesměrování pak provede klientská knihovna (Naja). + + Předávání parametrů =================== diff --git a/application/cs/bootstrapping.texy b/application/cs/bootstrapping.texy index 2b4a2dec58..a3996a816f 100644 --- a/application/cs/bootstrapping.texy +++ b/application/cs/bootstrapping.texy @@ -16,6 +16,9 @@ Bootstrapping je proces inicializace prostředí aplikace, vytvoření kontejner Aplikace, ať už jde o ty webové nebo skripty spouštěné z příkazové řádky, začínají svůj běh nějakou formou inicializace prostředí. V dávných dobách to míval na starosti soubor s názvem třeba `include.inc.php`, který prvotní soubor inkludoval. V moderních Nette aplikacích jej nahradila třída `Bootstrap`, kterou jakožto součást aplikace najdete v souboru `app/Bootstrap.php`. Může vypadat kupříkladu takto: ```php +namespace App; + +use Nette; use Nette\Bootstrap\Configurator; class Bootstrap @@ -78,6 +81,9 @@ $application = $container->getByType(Nette\Application\Application::class); $application->run(); ``` +.[note] +Objekt `$application` během zpracování požadavku vyvolává [události |nette:glossary#události] - `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown` a `onError` (při nezachycené výjimce). Můžete k nim připojit handlery, což se hodí pro logování nebo monitoring napříč aplikací. + Jak vidno, s nastavením prostředí a vytvořením dependency injection (DI) kontejneru pomáhá třída [api:Nette\Bootstrap\Configurator], kterou si nyní blíže představíme. @@ -93,7 +99,7 @@ Nette se chová různě podle toho, zda běží na vývojářském nebo produkč 🚀 Produkční režim (Production): - - Nezobrazuje žádné ladící informace, všechny chyby zapisuje do logu + - Nezobrazuje žádné ladicí informace, všechny chyby zapisuje do logu - Při chybě zobrazí ErrorPresenter nebo obecnou stránku "Server Error" - Cache se nikdy automaticky neobnovuje! - Optimalizovaný pro rychlost a bezpečnost @@ -101,7 +107,7 @@ Nette se chová různě podle toho, zda běží na vývojářském nebo produkč Volba režimu se provádí autodetekcí, takže obvykle není potřeba nic konfigurovat nebo ručně přepínat: -- vývojářský režim: na localhostu (IP adresa `127.0.0.1` nebo `::1`) pokud není přítomná proxy (tj. její HTTP hlavička) +- vývojářský režim: na localhostu (IP adresa `127.0.0.1` nebo `::1`), pokud není přítomná proxy (tj. její HTTP hlavička) - produkční režim: všude jinde Pokud chceme vývojářský režim povolit i v dalších případech, například programátorům přistupujícím z konkrétní IP adresy, použijeme `setDebugMode()`: @@ -124,6 +130,12 @@ $this->configurator->setDebugMode(false); Pozor, hodnota `true` zapne vývojářský režim natvrdo, což se nikdy nesmí stát na produkčním serveru. +Autodetekci uvnitř zajišťuje statická metoda `Configurator::detectDebugMode()`, kterou můžete zavolat i sami, například pro detekci vývojářského režimu mimo konfigurátor. Přijímá volitelný whitelist IP adres nebo názvů počítačů a vrací, zda má aktuální požadavek běžet ve vývojářském režimu: + +```php +$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200'); +``` + Debugovací nástroj Tracy ======================== @@ -181,6 +193,8 @@ Konfigurační soubory se obvykle zapisují ve formátu [NEON |neon:format]. V s .[tip] Ve vývojářském režimu se kontejner automaticky aktualizuje při každé změně kódu nebo konfiguračních souborů. V produkčním režimu se vygeneruje jen jednou a změny se kvůli maximalizaci výkonu nekontrolují. +Zatímco `createContainer()` kontejner vytvoří a vrátí jeho instanci, metoda `loadContainer()` vrátí jen název vygenerované třídy kontejneru, kterou si pak můžete vytvořit sami. To se hodí v pokročilých scénářích. + Konfigurační soubory načteme pomocí `addConfig()`: ```php @@ -208,7 +222,7 @@ Pokud se v konfiguračních souborech objeví prvky se stejnými klíči, budou Statické parametry ------------------ -Parametry používané v konfiguračních souborech můžeme definovat [v sekci `parameters` |dependency-injection:configuration#Parametry] a také je předávat (či přepisovat) metodou `addStaticParameters()` (má alias `addParameters()`). Důležité je, že různé hodnoty parametrů způsobí vygenerování dalších DI kontejnerů, tedy dalších tříd. +Parametry používané v konfiguračních souborech můžeme definovat [v sekci `parameters` |dependency-injection:configuration#Parametry] a také je předávat (či přepisovat) metodou `addStaticParameters()` (má starší, dnes zavržený (deprecated) alias `addParameters()`). Důležité je, že různé hodnoty parametrů způsobí vygenerování dalších DI kontejnerů, tedy dalších tříd. ```php $this->configurator->addStaticParameters([ @@ -222,7 +236,7 @@ Na parametr `projectId` se lze v konfiguraci odkázat obvyklým zápisem `%proje Dynamické parametry ------------------- -Do kontejneru můžeme přidat i dynamické parametry, jejichž různé hodnoty na rozdíl od statických parameterů nezpůsobí generování nových DI kontejnerů. +Do kontejneru můžeme přidat i dynamické parametry, jejichž různé hodnoty na rozdíl od statických parametrů nezpůsobí generování nových DI kontejnerů. ```php $this->configurator->addDynamicParameters([ @@ -242,14 +256,14 @@ $this->configurator->addDynamicParameters([ Výchozí parametry ----------------- -V konfiguračních souborech můžete využít tyto statické parametry: +V konfiguračních souborech můžete využít tyto parametry: - `%appDir%` je absolutní cesta k adresáři se souborem `Bootstrap.php` - `%wwwDir%` je absolutní cesta k adresáři se vstupním souborem `index.php` - `%tempDir%` je absolutní cesta k adresáři pro dočasné soubory - `%vendorDir%` je absolutní cesta k adresáři, kam Composer instaluje knihovny - `%rootDir%` je absolutní cesta ke kořenovému adresáři projektu -- `%baseUrl%` je absolutní URL ke kořenovému adresáři +- `%baseUrl%` je absolutní URL ke kořenovému adresáři (dynamický parametr vyhodnocovaný za běhu) - `%debugMode%` udává, zda je aplikace v debugovacím režimu - `%consoleMode%` udává, zda request přišel přes příkazovou řádku @@ -257,7 +271,7 @@ V konfiguračních souborech můžete využít tyto statické parametry: Importované služby ------------------ -Nyní už jdeme hlouběji. Ačkoliv je smyslem DI kontejneru objekty vyrábet, výjimečně může vzniknout potřeba do kontejneru existující objekt vložit. Uděláme to tak, že službu definujeme s příznakem `imported: true`. +Nyní už jdeme hlouběji. Ačkoliv je smyslem DI kontejneru objekty vyrábět, výjimečně může vzniknout potřeba do kontejneru existující objekt vložit. Uděláme to tak, že službu definujeme s příznakem `imported: true`. ```neon services: diff --git a/application/cs/components.texy b/application/cs/components.texy index f2ebe76722..08f8f8df35 100644 --- a/application/cs/components.texy +++ b/application/cs/components.texy @@ -23,7 +23,7 @@ Tovární metody Jak se do presenteru komponenty vkládají a následně používají? Obvykle pomocí továrních metod. -Továrna na komponenty představuje elegantní způsob, jak komponenty vytvářet teprve ve chvíli, kdy jsou skutečně potřeba (lazy / on demand). Celé kouzlo spočívá v implementaci metody s názvem `createComponent()`, kde `` je název vytvářené komponenty, a která komponentu vytvoří a vrátí. +Továrna na komponenty představuje elegantní způsob, jak komponenty vytvářet teprve ve chvíli, kdy jsou skutečně potřeba (lazy / on demand). Celé kouzlo spočívá v implementaci metody s názvem `createComponent()`, kde `` je název vytvářené komponenty; tato metoda komponentu vytvoří a vrátí. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -31,7 +31,7 @@ class DefaultPresenter extends Nette\Application\UI\Presenter protected function createComponentPoll(): PollControl { $poll = new PollControl; - $poll->items = $this->item; + $poll->items = $this->items; return $poll; } } @@ -46,7 +46,7 @@ Továrny nikdy nevoláme přímo, zavolají se samy ve chvíli, kdy komponentu p ```php .{file:DefaultPresenter.php} // přistoupíme ke komponentě a pokud to bylo poprvé, -// zavolá se createComponentPoll() která ji vytvoří +// zavolá se createComponentPoll(), která ji vytvoří $poll = $this->getComponent('poll'); // alternativní syntax: $poll = $this['poll']; ``` @@ -59,13 +59,18 @@ V šabloně je možné vykreslit komponentu pomocí značky [{control} |#Vykresl {control poll} ``` +.[tip] +Pro dynamické vytváření proměnného počtu komponent použijte [Multiplier |multiplier]. + +Tovární metody `createComponent()` nefungují jen v presenterech. Stejným způsobem můžete komponentu vložit i do jiné komponenty a skládat je tak do sebe - hodí se to například pro samostatně vykreslovaný formulář uvnitř komponenty. + Hollywood style =============== Komponenty běžně používají jednu svěží techniku, které rádi říkáme Hollywood style. Určitě znáte okřídlenou větu, kterou tak často slyší účastníci filmových konkurzů: "Nevolejte nám, my vám zavoláme". A právě o tu jde. -V Nette totiž místo toho, abyste se museli neustále na něco ptát ("byl formulář odeslaný?", "bylo to validní?" nebo "stiskl uživatel tohle tlačítko?"), řeknete frameworku "až se to stane, zavolej tuhle metodu" a necháte další práci na něm. Pokud programujete v JavaScriptu, tento styl programování důvěrně znáte. Píšete funkce které se volají, až nastane určitá událost. A jazyk jim předá příslušné parametry. +V Nette totiž místo toho, abyste se museli neustále na něco ptát ("byl formulář odeslaný?", "bylo to validní?" nebo "stiskl uživatel tohle tlačítko?"), řeknete frameworku "až se to stane, zavolej tuhle metodu" a necháte další práci na něm. Pokud programujete v JavaScriptu, tento styl programování důvěrně znáte. Píšete funkce, které se volají, až nastane určitá událost. A jazyk jim předá příslušné parametry. Tohle zcela mění pohled na psaní aplikací. Čím víc úkolů můžete nechat na frameworku, tím méně máte práce vy. A tím méně toho můžete třeba opomenout. @@ -87,7 +92,7 @@ class PollControl extends Control Vykreslení ========== -Už víme, že k vykreslení komponenty se používá značka `{control componentName}`. Ta vlastně zavolá metodu `render()` komponenty, ve které se postáráme o vykreslení. K dispozici máme, úplně stejně jako v presenteru, [Latte šablonu|templates] v proměnné `$this->template`, do které předáme parametry. Na rozdíl od presenteru musíme uvést soubor se šablonou a nechat ji vykreslit: +Už víme, že k vykreslení komponenty se používá značka `{control componentName}`. Ta vlastně zavolá metodu `render()` komponenty, ve které se postaráme o vykreslení. K dispozici máme, úplně stejně jako v presenteru, [Latte šablonu|templates] v proměnné `$this->template`, do které předáme parametry. Na rozdíl od presenteru musíme uvést soubor se šablonou a nechat ji vykreslit: ```php .{file:PollControl.php} public function render(): void @@ -141,10 +146,10 @@ $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -Metoda `getComponent()` vrací komponentu `poll` a nad touto komponentou volá metodu `render()`, resp. `renderPaginator()` pokud je jiný způsob renderování uveden ve značce za dvojtečkou. +Metoda `getComponent()` vrací komponentu `poll` a nad touto komponentou volá metodu `render()`, resp. `renderPaginator()`, pokud je ve značce za dvojtečkou uveden jiný způsob renderování. .[caution] -Pozor, pokud se kdekoliv v parametrech objeví **`=>`**, všechny parametry budou zabaleny do pole a předány jako první argument: +Pozor, pokud se v parametrech objeví **`=>`** mimo hranaté závorky, všechny parametry budou zabaleny do pole a předány jako první argument: ```latte {control poll, id: 123, message: 'hello'} @@ -156,7 +161,7 @@ se přeloží jako: $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Vykreslení sub-komponety: +Vykreslení sub-komponenty: ```latte {control cartControl-someForm} @@ -175,7 +180,7 @@ Komponenty, stejně jako presentery, předávají do šablon několik užitečn - `$user` je objekt [reprezentující uživatele |security:authentication] - `$presenter` je aktuální presenter - `$control` je aktuální komponenta -- `$flashes` pole [zpráv |#Flash zprávy] zaslaných funkcí `flashMessage()` +- `$flashes` je pole [zpráv |#Flash zprávy] zaslaných funkcí `flashMessage()` Signál @@ -188,7 +193,7 @@ Tomuto druhu požadavků se říká signály. A podobně jako akce vyvolávají ```php public function handleClick(int $x, int $y): void { - // ... processing of signal ... + // ... zpracování signálu ... } ``` @@ -198,7 +203,7 @@ Odkaz, který zavolá signál, vytvoříme obvyklým způsobem, tedy v šabloně click here ``` -Signál se vždy volá na aktuálním presenteru a action, není možné jej vyvolat na jiném presenteru nebo jiné action. +Signál se vždy volá na aktuálním presenteru a akci, není možné jej vyvolat na jiném presenteru nebo jiné akci. Signál tedy způsobí znovunačtení stránky úplně stejně jako při původním požadavku, jen navíc zavolá obslužnou metodu signálu s příslušnými parametry. Pokud metoda neexistuje, vyhodí se výjimka [api:Nette\Application\UI\BadSignalException], která se uživateli zobrazí jako chybová stránka 403 Forbidden. @@ -214,7 +219,7 @@ Flash zprávy Komponenta má své vlastní úložiště flash zpráv nezávislé na presenteru. Jde o zprávy, které např. informují o výsledku operace. Důležitým rysem flash zpráv je to, že jsou v šabloně k dispozici i po přesměrování. I po zobrazení zůstanou živé ještě dalších 30 sekund - například pro případ, že by z důvodu chybného přenosu uživatel dal stránku obnovit - zpráva mu tedy hned nezmizí. -Zasílání obstarává metoda [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Prvním parametrem je text zprávy nebo objekt `stdClass` reprezentující zprávu. Nepovinným druhým parametrem její typ (error, warning, info apod.). Metoda `flashMessage()` vrací instanci flash zprávy jako objekt `stdClass`, které je možné přidávat další informace. +Zasílání obstarává metoda [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Prvním parametrem je text zprávy (`string`, `Stringable`) nebo objekt `stdClass` reprezentující zprávu. Nepovinným druhým parametrem je její typ (error, warning, info apod.). Metoda `flashMessage()` vrací instanci flash zprávy jako objekt `stdClass`, ke kterému je možné přidávat další informace. ```php $this->flashMessage('Položka byla smazána.'); @@ -230,19 +235,19 @@ $this->redirect(/* ... */); // a přesměrujeme ``` -Přesměrování po signálu -======================= +Přesměrování po zpracování signálu +================================== Po zpracování signálu komponenty často následuje přesměrování. Je to podobná situace jako u formulářů - po jejich odeslání také přesměrováváme, aby při obnovení stránky v prohlížeči nedošlo k opětovnému odeslání dat. ```php -$this->redirect('this') // přesměruje na aktuální presenter a action +$this->redirect('this'); // přesměruje na aktuální presenter a akci ``` Protože komponenta je znovupoužitelný prvek a obvykle by neměla mít přímou vazbu na konkrétní presentery, metody `redirect()` a `link()` automaticky interpretují parametr jako signál komponenty: ```php -$this->redirect('click') // přesměruje na signál 'click' téže komponenty +$this->redirect('click'); // přesměruje na signál 'click' téže komponenty ``` Pokud potřebujete přesměrovat na jiný presenter či akci, můžete to udělat prostřednictvím presenteru: @@ -259,7 +264,7 @@ Persistentní parametry slouží k udržování stavu v komponentách mezi různ Máte např. komponentu pro stránkování obsahu. Takových komponent může být na stránce několik. A přejeme si, aby po kliknutí na odkaz zůstaly všechny komponenty na své aktuální stránce. Proto z čísla stránky (`page`) uděláme persistentní parametr. -Vytvoření persistentního parametru je v Nette nesmírně jednoduché. Stačí vytvořit veřejnou property a označit ji atributem: (dříve se používalo `/** @persistent */`) +Vytvoření persistentního parametru je v Nette nesmírně jednoduché. Stačí vytvořit veřejnou property a označit ji atributem (dříve se používalo `/** @persistent */`): ```php use Nette\Application\Attributes\Persistent; // tento řádek je důležitý @@ -289,25 +294,25 @@ Nebo jej lze *vyresetovat*, tj. odstranit z URL. Pak bude nabývat svou výchoz Persistentní komponenty ======================= -Nejen parametry, ale také komponenty mohou být persistentní. U takové komponenty se její persistentní parametry přenáší i mezi různými akcemi presenteru nebo mezi více presentery. Persistentní komponenty značíme anotací u třídy presenteru. Třeba takto označíme komponenty `calendar` a `poll`: +Nejen parametry, ale také komponenty mohou být persistentní. Jejich persistentní parametry se pak přenáší i mezi různými akcemi presenteru nebo mezi více presentery. Persistentní komponenty značíme atributem u třídy presenteru. Třeba takto označíme komponenty `calendar` a `poll`: ```php -/** - * @persistent(calendar, poll) - */ +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] class DefaultPresenter extends Nette\Application\UI\Presenter { } ``` -Podkomponenty uvnitř těchto komponent není třeba značit, stanou se persistentní taky. +Podkomponenty uvnitř těchto komponent není třeba značit, stanou se persistentní také. -V PHP 8 můžete pro označení persistentních komponent použít také atributy: +Starší anotace `@persistent` stále funguje, ale je zastaralá a vyhazuje varování: ```php -use Nette\Application\Attributes\Persistent; - -#[Persistent('calendar', 'poll')] +/** + * @persistent(calendar, poll) + */ class DefaultPresenter extends Nette\Application\UI\Presenter { } @@ -325,7 +330,7 @@ Vezměme si jako příklad komponentu, která má závislost na službě `PollFa class PollControl extends Control { public function __construct( - private int $id, // Id ankety pro kterou vytváříme komponentu + private int $id, // ID ankety, pro kterou vytváříme komponentu private PollFacade $facade, ) { } @@ -407,7 +412,7 @@ Komponenty v Nette Application představují znovupoužitelné součásti webov 4) má schopnost reagovat na uživatelské akce (signály) 5) vytváří hierarchickou strukturu (kde kořenem je presenter) -Každou z těchto funkcí obstarává některá z tříd dědičné linie. Vykreslování (1 + 2) má na starosti [api:Nette\Application\UI\Control], začlenění do [životního cyklu |presenters#Životní cyklus presenteru] (3, 4) třída [api:Nette\Application\UI\Component] a vytváření hierachické struktury (5) třídy [Container a Component |component-model:]. +Každou z těchto funkcí obstarává některá z tříd dědičné linie. Vykreslování (1 + 2) má na starosti [api:Nette\Application\UI\Control], začlenění do [životního cyklu |presenters#Životní cyklus presenteru] (3, 4) třída [api:Nette\Application\UI\Component] a vytváření hierarchické struktury (5) třídy [Container a Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -431,7 +436,7 @@ Nette\ComponentModel\Component { IComponent } Validace persistentních parametrů --------------------------------- -Hodnoty [persistentních parametrů |#Persistentní parametry] přijatých z URL zapisuje do properties metoda `loadState()`. Ta také kontroluje, zda odpovídá datový typ uvedený u property, jinak odpoví chybou 404 a stránka se nezobrazí. +Hodnoty [persistentních parametrů |#Persistentní parametry] přijatých z URL zapisuje do properties metoda `loadState()`. Ta také kontroluje, zda hodnota odpovídá datovému typu uvedenému u property; jinak odpoví chybou 404 a stránka se nezobrazí. Nikdy slepě nevěřte persistentním parametrům, protože mohou být snadno uživatelem přepsány v URL. Takto například ověříme, zda je číslo stránky `$this->page` větší než 0. Vhodnou cestou je přepsat zmíněnou metodu `loadState()`: @@ -455,24 +460,38 @@ class PaginatingControl extends Control Opačný proces, tedy sesbírání hodnot z persistentních properties, má na starosti metoda `saveState()`. +Připojení k presenteru +---------------------- + +Ve chvíli, kdy se komponenta stane součástí hierarchie presenteru, zavolají se její callbacky uložené v poli `$onAnchor`. Od tohoto okamžiku má komponenta k dispozici presenter, může bezpečně vytvářet odkazy, číst persistentní parametry apod. + +```php +$control->onAnchor[] = function ($control): void { + // komponenta má nyní k dispozici presenter +}; +``` + + Signály do hloubky ------------------ -Signál způsobí znovunačtení stránky úplně stejně jako při původním požadavku (kromě případu, kdy je volán AJAXem) a vyvolá metodu `signalReceived($signal)`, jejíž výchozí implementace ve třídě `Nette\Application\UI\Component` se pokusí zavolat metodu složenou ze slov `handle{signal}`. Další zpracování je na daném objektu. Objekty, které dědí od `Component` (tzn. `Control` a `Presenter`) reagují tak, že se snaží zavolat metodu `handle{signal}` s příslušnými parametry. +Signál způsobí znovunačtení stránky úplně stejně jako při původním požadavku (kromě případu, kdy je volán AJAXem) a vyvolá metodu `signalReceived($signal)`, jejíž výchozí implementace ve třídě `Nette\Application\UI\Component` se pokusí zavolat metodu složenou ze slov `handle`. Další zpracování je na daném objektu. Objekty, které dědí od `Component` (tzn. `Control` a `Presenter`), reagují tak, že se snaží zavolat metodu `handle` s příslušnými parametry. + +Jinými slovy: vezme se definice funkce `handle` a všechny parametry, které přišly s požadavkem, a k argumentům se podle jména dosadí parametry z URL a pokusí se danou metodu zavolat. Např. jako parametr `$id` se předá hodnota z parametru `id` v URL, jako `$something` se předá `something` z URL, atd. A pokud metoda neexistuje, metoda `signalReceived` vyvolá [výjimku |api:Nette\Application\UI\BadSignalException]. -Jinými slovy: vezme se definice funkce `handle{signal}` a všechny parametry, které přišly s požadavkem, a k argumentům se podle jména dosadí parametry z URL a pokusí se danou metodu zavolat. Např. jako prametr `$id` se předá hodnota z parametru `id` v URL, jako `$something` se předá `something` z URL, atd. A pokud metoda neexistuje, metoda `signalReceived` vyvolá [výjimku |api:Nette\Application\UI\BadSignalException]. +Kromě parametrů z URL bere signál v potaz i parametry odeslané v **POST těle požadavku**. To se hodí, protože signály se často volají přes JavaScript, kde je přirozené posílat data metodou POST. Pokud ale parametr stejného jména přijde jak z URL, tak z POST těla, má přednost hodnota **z URL**. Vyhněte se proto tomu, aby POST pole mělo stejný název jako parametr z URL nebo z routy, jinak by ho hodnota z URL tiše přepsala. Parametry signálu sdílejí společný prostor s parametry action metod a persistentními parametry, viz [Společný prostor parametrů |presenters#Společný prostor parametrů]. Signál může přijímat jakákoliv komponenta, presenter nebo objekt, který implementuje rozhraní `SignalReceiver` a je připojený do stromu komponent. Mezi hlavní příjemce signálů budou patřit `Presentery` a vizuální komponenty dědící od `Control`. Signál má sloužit jako znamení pro objekt, že má něco udělat - anketa si má započítat hlas od uživatele, blok s novinkami se má rozbalit a zobrazit dvakrát tolik novinek, formulář byl odeslán a má zpracovat data a podobně. -URL pro signál vytváříme pomocí metody [Component::link() |api:Nette\Application\UI\Component::link()]. Jako parametr `$destination` předáme řetězec `{signal}!` a jako `$args` pole argumentů, které chceme signálu předat. Signál se vždy volá na aktuálním presenteru a action s aktuálními parametry, parametry signálu se jen přidají. Navíc se přidává hned na začátku **parametr `?do`, který určuje signál**. +URL pro signál vytváříme pomocí metody [Component::link() |api:Nette\Application\UI\Component::link()]. Jako parametr `$destination` předáme řetězec `{signal}!` a jako `$args` pole argumentů, které chceme signálu předat. Signál se vždy volá na aktuálním presenteru a akci s aktuálními parametry, parametry signálu se jen přidají. Navíc se přidává **parametr `?do`, který určuje signál**. Jeho formát je buď `{signal}`, nebo `{signalReceiver}-{signal}`. `{signalReceiver}` je název komponenty v presenteru. Proto nemůže být v názvu komponenty pomlčka - používá se k oddělení názvu komponenty a signálu, je ovšem možné takto zanořit několik komponent. -Metoda [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] ověří, zda je komponenta (první argument) příjemcem signálu (druhý argument). Druhý argument můžeme vynechat - pak zjišťuje, jestli je komponenta příjemcem jakéhokoliv signálu. Jako druhý parameter lze uvést `true` a tím ověřit, jestli je příjemcem nejen uvedená komponenta, ale také kterýkoliv její potomek. +Metoda [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] ověří, zda je komponenta (první argument) příjemcem signálu (druhý argument). Druhý argument můžeme vynechat - pak zjišťuje, jestli je komponenta příjemcem jakéhokoliv signálu. Jako druhý parametr lze uvést `true` a tím ověřit, jestli je příjemcem nejen uvedená komponenta, ale také kterýkoliv její potomek. -V kterékoliv fázi předcházející `handle{signal}` můžeme vykonat signál manuálně zavoláním metody [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], která si bere na starosti vyřízení signálu - vezme komponentu, která se určila jako příjemce signálu (pokud není určen příjemce signálu, je to presenter samotný) a pošle jí signál. +V kterékoliv fázi předcházející `handle` můžeme vykonat signál manuálně zavoláním metody [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], která má na starosti vyřízení signálu - vezme komponentu, která se určila jako příjemce signálu (pokud není určen příjemce signálu, je to presenter samotný) a pošle jí signál. Příklad: diff --git a/application/cs/configuration.texy b/application/cs/configuration.texy index b105f5154e..84d79b50a3 100644 --- a/application/cs/configuration.texy +++ b/application/cs/configuration.texy @@ -11,11 +11,11 @@ Application ```neon application: # zobrazit "Nette Application" panel v Tracy BlueScreen? - debugger: ... # (bool) výchozí je true + debugger: ... # (bool) zapne se, je-li k dispozici Tracy - # bude se při chybě volat error-presenter? - # má efekt pouze ve vývojářském režimu - catchExceptions: ... # (bool) výchozí je true + # v produkci error-presenter zpracovává výjimky vždy; + # tato volba jej zapne i ve vývojářském režimu + catchExceptions: ... # (bool) výchozí je false - tj. ve vývoji vypnuto, v produkci vždy zapnuto # název error-presenteru errorPresenter: Error # (string|array) výchozí je 'Nette:Error' @@ -40,7 +40,9 @@ application: 5xx: Error5xx # pro ostatní výjimky ``` -Volba `silentLinks` určuje, jak se Nette zachová ve vývojářském režimu, když selže generování odkazu (třeba proto, že neexistuje presenter, atd). Výchozí hodnota `false` znamená, že Nette vyhodí `E_USER_WARNING` chybu. Nastavením na `true` dojde k potlačení této chybové hlášky. V produkčním prostředí se `E_USER_WARNING` vyvolá vždy. Toto chování můžeme také ovlivnit nastavením proměnné presenteru [$invalidLinkMode |creating-links#Neplatné odkazy]. +Rozdělení se hodí proto, že obě situace jsou zásadně odlišné. Výjimka `BadRequestException` (kódy 4xx) znamená, že aplikace je v pořádku a jen návštěvník požádal o něco, co neexistuje. Můžete proto použít plnohodnotný presenter, který zobrazí přátelskou zprávu v layoutu vašeho webu. Naproti tomu chyba 5xx znamená, že se v aplikaci něco rozbilo a nevíte co. Presenter pro 5xx proto udržujte co nejjednodušší, aby při jeho vykreslování už nemohlo nic dalšího selhat - ideálně by neměl sahat na databázi, layout ani na přihlášeného uživatele. + +Volba `silentLinks` určuje, jak se Nette zachová ve vývojářském režimu, když selže generování odkazu (třeba proto, že neexistuje presenter, atd.). Výchozí hodnota `false` znamená, že Nette vyhodí chybu `E_USER_WARNING`. Nastavením na `true` dojde k potlačení této chybové hlášky. V produkčním prostředí se `E_USER_WARNING` vyvolá vždy. Toto chování můžeme také ovlivnit nastavením proměnné presenteru [$invalidLinkMode |creating-links#Neplatné odkazy]. [Aliasy zjednodušují odkazování |creating-links#Aliasy] na často používané presentery. @@ -50,7 +52,7 @@ Volba `silentLinks` určuje, jak se Nette zachová ve vývojářském režimu, k Automatická registrace presenterů --------------------------------- -Nette automaticky přidává presentery jako služby do DI kontejneru, což zásadně zrychlí jejich vytváření. Jak Nette presentery dohledává lze konfigurovat: +Nette automaticky přidává presentery jako služby do DI kontejneru, což zásadně zrychlí jejich vytváření. Jak Nette presentery dohledává, lze konfigurovat: ```neon application: @@ -73,7 +75,7 @@ application: - %vendorDir%/mymodule ``` -Skenování adresářů lze vypnout uvedením hodnoty false. Nedoporučujeme úplně potlačit automatické přidávání presenterů, protože jinak dojde ke snížení výkonu aplikace. +Skenování adresářů lze vypnout uvedením hodnoty `false`. Presentery pak nejsou registrované jako služby, takže je nelze upravovat v sekci [decorator |dependency-injection:configuration#Decorator] a jejich vytváření je pomalejší. Úplné potlačení automatické registrace proto nedoporučujeme, protože sníží výkon aplikace. Šablony Latte @@ -84,7 +86,7 @@ Tímto nastavením lze globálně ovlivnit chování Latte v komponentách a pre ```neon latte: # zobrazit Latte panel v Tracy Baru pro hlavní šablonu (true) nebo všechny komponenty (all)? - debugger: ... # (true|false|'all') výchozí je true + debugger: ... # (true|false|'all') zapne se s Tracy (jen v debug módu) # generuje šablony s hlavičkou declare(strict_types=1) strictTypes: ... # (bool) výchozí je false @@ -92,6 +94,12 @@ latte: # zapne režim [striktního parseru |latte:develop#striktní režim] strictParsing: ... # (bool) výchozí je false + # omezí platnost proměnných na tělo cyklu + scopedLoopVariables: ... # (bool) výchozí je false + + # odstraní odsazení vzniklé zanořením v párových značkách + dedent: ... # (bool) výchozí je false + # aktivuje [kontrolu vygenerovaného kódu |latte:develop#Kontrola vygenerovaného kódu] phpLinter: ... # (string) výchozí je null @@ -102,7 +110,7 @@ latte: templateClass: App\MyTemplateClass # výchozí je Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Pokud používáte Latte verze 3, můžete přidávat nové [rozšíření |latte:extending-latte#Latte Extension] pomocí: +Nová [rozšíření |latte:extending-latte#Latte Extension] můžete přidávat pomocí: ```neon latte: @@ -110,20 +118,6 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Pokud používáte Latte verze 2, můžete registrovat nové tagy (makra) buď uvedením jména třídy, nebo referencí na službu. Jako výchozí je zavolána metoda `install()`, ale to lze změnit tím, že uvedeme jméno jiné metody: - -```neon -latte: - # registrace uživatelských Latte značek - macros: - - App\MyLatteMacros::register # statická metoda, classname nebo callable - - @App\MyLatteMacrosFactory # služba s metodou install() - - @App\MyLatteMacrosFactory::register # služba s metodou register() - -services: - - App\MyLatteMacrosFactory -``` - Routování ========= @@ -133,7 +127,7 @@ Základní nastavení: ```neon routing: # zobrazit routovací panel v Tracy Bar? - debugger: ... # (bool) výchozí je true + debugger: ... # (bool) zapne se s Tracy (jen v debug módu) # serializuje router do DI kontejneru cache: ... # (bool) výchozí je false @@ -185,7 +179,8 @@ Tyto služby se přidávají do DI kontejneru: |---------------------------------------------------------- | `application.application` | [api:Nette\Application\Application] | [spouštěč celé aplikace |how-it-works#Nette Application] | `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] -| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | továrna na presentery +| `application.presenterFactory` | [api:Nette\Application\IPresenterFactory] | továrna na presentery | `application.###` | [api:Nette\Application\UI\Presenter] | jednotlivé presentery +| `routing.router` | [api:Nette\Routing\Router] | směrovač | `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | továrna objektu `Latte\Engine` | `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | továrna pro [`$this->template` |templates] diff --git a/application/cs/creating-links.texy b/application/cs/creating-links.texy index 011beea750..703a1206ea 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 @@ -40,11 +40,11 @@ Je možné předávat i pojmenované parametry. Následující odkaz předává Pokud metoda `ProductPresenter::renderShow()` nemá `$lang` ve své signatuře, může si hodnotu parametru zjistit pomocí `$lang = $this->getParameter('lang')` nebo z [property |presenters#Parametry požadavku]. -Pokud jsou parametry uložené v poli, lze je rozvinout operátorem `...` (v Latte 2.x operátorem `(expand)`): +Pokud jsou parametry uložené v poli, lze je rozvinout operátorem `...`: ```latte {var $args = [$product->id, lang => cs]} -detail produktu +detail produktu ``` V odkazech se také automaticky předávají tzv. [persistentní parametry |presenters#Persistentní parametry]. @@ -73,6 +73,14 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Odkazy lze vytvářet i bez presenteru, od toho je tu [#LinkGenerator] a jeho metoda `link()`. +Někdy potřebujete odkaz vytvořit hned, ale skutečnou URL sestavit až později. K tomu slouží metoda `lazyLink()`, která vrací objekt `Nette\Application\UI\Link`. Výhodou je, že tento objekt můžete předat dál, například do šablony, a než se vykreslí, můžete ještě upravit jeho parametry metodou `setParameter()`. Samotná URL se sestaví až ve chvíli, kdy se objekt převede na řetězec: + +```php +$link = $this->lazyLink('Product:show', $id); +// ... +echo $link; // URL se vygeneruje až zde +``` + Odkazy na presenter =================== @@ -122,6 +130,13 @@ Odkazovat můžeme na určitou část stránky přes tzv. fragment za znakem mř odkaz na Home:default a fragment #main ``` +.{data-version:3.3.0} +Fragment lze nastavit i dynamicky jako argument s klíčem `#`. Jeho hodnota se automaticky zakóduje a má přednost před fragmentem uvedeným v cíli: + +```php +$this->link('Home:default', ['#' => $fragment]); +``` + Absolutní cesty =============== @@ -142,9 +157,9 @@ Cíl `this` vytvoří odkaz na aktuální stránku: refresh ``` -Zároveň se přenáší i všechny parametry uvedené v signatuře metody `action()` nebo `render()`, pokud není `action()` definovaná. Takže pokud jsme na stránce `Product:show` a `id: 123`, odkaz na `this` předá i tento parameter. +Zároveň se přenáší i všechny parametry uvedené v signatuře metody `action()` nebo `render()`, pokud není `action()` definovaná. Takže pokud jsme na stránce `Product:show` a `id: 123`, odkaz na `this` předá i tento parametr. -Samozřejmě je možné parametry specifikovat přímo: +Samozřejmě je možné parametry uvést přímo: ```latte refresh @@ -181,8 +196,8 @@ Pro zjištění, zda jsme v určitém modulu nebo jeho submodulu, použijeme met ``` -Změna základu pro odkazy .{data-version:v3.2.7} -=============================================== +Změna základu pro odkazy .{data-version:3.2.7} +============================================== Ve výchozím stavu se relativní odkazy odvíjejí od aktuálního presenteru. To lze změnit pomocí `{linkBase}`: @@ -194,12 +209,13 @@ Ve výchozím stavu se relativní odkazy odvíjejí od aktuálního presenteru. Odkaz povede na `Admin:Dashboard:Product:show`. Ovlivněny jsou pouze relativní odkazy - absolutní odkazy začínající dvojtečkou a odkazy na aktuální presenter (`this`, `show`) zůstávají nezměněny. `{linkBase}` platí pro celou šablonu a je užitečné zejména v šablonách layoutu, kde zajistí konzistentní odkazy nezávisle na volajícím presenteru. +Značka musí být umístěna na začátku šablony, jinak vyhodí `CompileException`. Odkazy na signál ================ -Cílem odkazu nemusí být jen presenter a akce, ale také [signál |components#Signál] (volají metodu `handle()`). Pak je syntaxe následující: +Cílem odkazu nemusí být jen presenter a akce, ale také [signál |components#Signál] (volá metodu `handle()`). Pak je syntaxe následující: ``` [//] [sub-component:]signal! [#fragment] @@ -240,8 +256,8 @@ $this->getPresenter()->link('Home:default') ``` -Aliasy .{data-version:v3.2.2} -============================= +Aliasy .{data-version:3.2.3} +============================ Občas se může hodit přiřadit dvojici Presenter:akce snadno zapamatovatelný alias. Například úvodní stránku `Front:Home:default` pojmenovat jednoduše jako `home` nebo `Admin:Dashboard:default` jako `admin`. @@ -267,7 +283,7 @@ Podporované jsou i ve všech metodách pracujících s odkazy, jako je `redirec Neplatné odkazy =============== -Může se stát, že vytvoříme neplatný odkaz - buď proto, že vede na neexistující presenter, nebo proto, že předává víc parametrů, než které cílová metoda přijímá ve své signatuře, nebo když pro cílovou akci nelze vygenerovat URL. Jak naložit s neplatnými odkazy určuje statická proměnná `Presenter::$invalidLinkMode`. Ta může nabývat kombinaci těchto hodnot (konstant): +Může se stát, že vytvoříme neplatný odkaz - buď proto, že vede na neexistující presenter, nebo proto, že předává víc parametrů, než které cílová metoda přijímá ve své signatuře, nebo když pro cílovou akci nelze vygenerovat URL. Jak naložit s neplatnými odkazy, určuje property `$this->invalidLinkMode`, kterou nastavíte v presenteru. Ta může nabývat kombinace těchto hodnot (konstant): - `Presenter::InvalidLinkSilent` - tichý režim, jako URL se vrátí znak # - `Presenter::InvalidLinkWarning` - vyhodí se varování E_USER_WARNING, které bude v produkčním režimu zalogováno, ale nezpůsobí přerušení běhu skriptu @@ -283,7 +299,7 @@ a[href^="#error:"] { } ``` -Pokud nechceme, aby se ve vývojovém prostředí produkovala varování, můžeme nastavit tichý režim přímo v [konfiguraci|configuration]. +Pokud nechceme, aby se ve vývojovém prostředí varování vypisovala, můžeme je potlačit přímo v [konfiguraci|configuration]. ```neon application: @@ -294,9 +310,9 @@ application: LinkGenerator ============= -Jak vytvářet odkazy s podobným komfortem jako má metoda `link()`, ale bez přítomnosti presenteru? Od toho je tu [api:Nette\Application\LinkGenerator]. +Jak vytvářet odkazy s podobným komfortem, jako má metoda `link()`, ale bez přítomnosti presenteru? Od toho je tu [api:Nette\Application\LinkGenerator]. -LinkGenerátor je služba, kterou si můžete nechat předat přes konstruktor a poté vytvářet odkazy jeho metodou `link()`. +LinkGenerator je služba, kterou si můžete nechat předat přes konstruktor a poté vytvářet odkazy jeho metodou `link()`. Oproti presenterům je tu rozdíl. LinkGenerator vytváří všechny odkazy rovnou jako absolutní URL. A dále neexistuje žádný "aktuální presenter", takže nelze jako cíl uvést jen název akce `link('default')` nebo uvádět relativní cesty k modulům. diff --git a/application/cs/directory-structure.texy b/application/cs/directory-structure.texy index 3e3f48918a..c1620c48bf 100644 --- a/application/cs/directory-structure.texy +++ b/application/cs/directory-structure.texy @@ -39,7 +39,7 @@ Tuto strukturu můžete libovolně upravovat podle svých potřeb - složky pře Principy organizace kódu ======================== -Když poprví prozkoumáváte nový projekt, měli byste se v něm rychle zorientovat. Představte si, že rozkliknete adresář `app/Model/` a uvidíte tuto strukturu: +Když poprvé prozkoumáváte nový projekt, měli byste se v něm rychle zorientovat. Představte si, že rozkliknete adresář `app/Model/` a uvidíte tuto strukturu: /--pre app/Model/ @@ -89,13 +89,13 @@ Pro konzistenci doporučujeme používat: Veřejný adresář `www/` ====================== -Tento adresář je jediný přístupný z webu (tzv. document-root). Často se můžete setkat i s názvem `public/` místo `www/` - je to jen otázka konvence a na funkčnost rostlináře to nemá vliv. Adresář obsahuje: +Tento adresář je jediný přístupný z webu (tzv. document-root). Často se můžete setkat i s názvem `public/` místo `www/` - je to jen otázka konvence a na funkci rostlináře to nemá vliv. Adresář obsahuje: - [Vstupní bod |bootstrapping#index.php] aplikace `index.php` - Soubor `.htaccess` s pravidly pro mod_rewrite (u Apache) - Statické soubory (CSS, JavaScript, obrázky) - Uploadované soubory -Pro správné zabezpečení aplikace je zásadní mít správně [nakonfigurovaný document-root |nette:troubleshooting#Jak změnit či ostranit z URL adresář www]. +Pro správné zabezpečení aplikace je zásadní mít správně [nakonfigurovaný document-root |nette:troubleshooting#Jak změnit či odstranit z URL adresář www]. .[note] Nikdy neumisťujte do tohoto adresáře složku `node_modules/` - obsahuje tisíce souborů, které mohou být spustitelné a neměly by být veřejně dostupné. @@ -193,7 +193,7 @@ Jednou z velkých výhod této struktury je, jak elegantně se přizpůsobuje ro └── feed.latte ← šablona pro RSS feed \-- -Časem přibydou další typy feedů a potřebujeme pro ně více logiky... Žádný problém! Složka `Export/` se jednoduše stane modulem: +Časem přibudou další typy feedů a potřebujeme pro ně více logiky... Žádný problém! Složka `Export/` se jednoduše stane modulem: /--pre Export/ @@ -208,7 +208,7 @@ Jednou z velkých výhod této struktury je, jak elegantně se přizpůsobuje ro Tato transformace je naprosto plynulá - stačí vytvořit nové podsložky, rozdělit do nich kód a aktualizovat odkazy (např. z `Export:feed` na `Export:Feed:zbozi`). Díky tomu můžeme strukturu postupně rozšiřovat podle potřeby, úroveň zanoření není nijak omezena. -Pokud například v administraci máte mnoho presenterů týkajících se správy objednávek, jako jsou `OrderDetail`, `OrderEdit`, `OrderDispatch` atd., můžete pro lepší organizovanost v tomto místě vytvořit modul (složku) `Order`, ve kterém budou (složky pro) presentery `Detail`, `Edit`, `Dispatch` a další. +Pokud například v administraci máte mnoho presenterů týkajících se správy objednávek, jako jsou `OrderDetail`, `OrderEdit`, `OrderDispatch` atd., můžete pro lepší přehlednost v tomto místě vytvořit modul (složku) `Order`, ve kterém budou (složky pro) presentery `Detail`, `Edit`, `Dispatch` a další. Umístění šablon @@ -225,13 +225,13 @@ V předchozích ukázkách jsme viděli, že šablony jsou umístěny přímo ve Toto umístění se v praxi ukazuje jako nejpohodlnější - všechny související soubory máte hned po ruce. -Alternativně můžete šablony umístit do podsložky `templates/`. Nette podporuje obě varianty. Dokonce můžete šablony umístit i úplně mimo `Presentation/` složku. Vše o možnostech umístění šablon najdete v kapitole [Hledání šablon |templates#Hledání šablon]. +Alternativně můžete šablony umístit do podsložky `templates/`. Nette podporuje obě varianty. Dokonce můžete šablony umístit i úplně mimo složku `Presentation/`. Vše o možnostech umístění šablon najdete v kapitole [Hledání šablon |templates#Hledání šablon]. Pomocné třídy a komponenty -------------------------- -K prezenterům a šablonám často patří i další pomocné soubory. Umístíme je logicky podle jejich působnosti: +K presenterům a šablonám často patří i další pomocné soubory. Umístíme je logicky podle jejich působnosti: 1. **Přímo u presenteru** v případě specifických komponent pro daný presenter: @@ -313,7 +313,7 @@ class PricingService } ``` -**Repozitáře**: zajišťují veškerou komunikaci s datovým úložištěm, typicky databází. Jeho úkolem je načítání a ukládání entit a implementace metod pro jejich vyhledávání. Repozitář odstiňuje zbytek aplikace od implementačních detailů databáze a poskytuje objektově orientované rozhraní pro práci s daty. +**Repozitáře**: zajišťují veškerou komunikaci s datovým úložištěm, typicky databází. Jejich úkolem je načítání a ukládání entit a implementace metod pro jejich vyhledávání. Repozitář odstiňuje zbytek aplikace od implementačních detailů databáze a poskytuje objektově orientované rozhraní pro práci s daty. ```php class OrderRepository @@ -401,7 +401,7 @@ Příklad pro lepší pochopení: Příkazové skripty ================= -Aplikace často potřebují vykonávat činnosti mimo běžné HTTP požadavky - ať už jde o zpracování dat v pozadí, údržbu, nebo periodické úlohy. Pro spouštění slouží jednoduché skripty v adresáři `bin/`, samotnou implementační logiku pak umisťujeme do `app/Tasks/` (případně `app/Commands/`). +Aplikace často potřebují vykonávat činnosti mimo běžné HTTP požadavky - ať už jde o zpracování dat v pozadí, údržbu, nebo periodické úlohy. Pro spouštění slouží jednoduché skripty v adresáři `bin/`, samotnou implementační logiku pak umisťujeme do `app/Tasks/` (případně `app/Commands/`). Příklad: @@ -420,7 +420,7 @@ Příklad: Co patří do modelu a co do příkazových skriptů? Například logika pro odeslání jednoho e-mailu je součástí modelu, hromadná rozesílka tisíců e-mailů už patří do `Tasks/`. -Úlohy obvykle [spouštíme z příkazového řádku |https://blog.nette.org/cs/cli-skripty-v-nette-aplikaci] nebo přes cron. Lze je spouštět i přes HTTP požadavek, ale je nutné myslet na bezpečnost. Presenter, který úlohu spustí, je potřeba zabezpečit, například jen pro přihlášené uživatele nebo silným tokenem a přístupem z povolených IP adres. U dlouhých úloh je nutné zvýšit časový limit skriptu a použít `session_write_close()`, aby se nezamykala session. +Úlohy obvykle spouštíme z příkazového řádku nebo přes cron: skript v `bin/` vytvoří DI kontejner pomocí metody [bootConsoleApplication() |bootstrapping#Odlišné prostředí] a vytáhne si z něj potřebnou službu. Lze je spouštět i přes HTTP požadavek, ale je nutné myslet na bezpečnost. Presenter, který úlohu spustí, je potřeba zabezpečit, například jen pro přihlášené uživatele nebo silným tokenem a přístupem z povolených IP adres. U dlouhých úloh je nutné zvýšit časový limit skriptu a použít `session_write_close()`, aby se nezamykala session. Další možné adresáře @@ -470,7 +470,7 @@ Mapování presenterů Mapování definuje pravidla pro odvozování názvu třídy z názvu presenteru. Specifikujeme je v [konfiguraci|configuration] pod klíčem `application › mapping`. -Na této stránce jsme si ukázali, že presentery umísťujeme do složky `app/Presentation` (případně `app/UI`). Tuto konvenci musíme Nette sdělit v konfiguračním souboru. Stačí jeden řádek: +Na této stránce jsme si ukázali, že presentery umísťujeme do složky `app/Presentation` (případně `app/UI`). Od Nette Application 3.3 je toto výchozí konvence, kterou není nutné nijak konfigurovat. Pokud používáte jinou strukturu nebo chcete mapování uvést explicitně, výchozímu nastavení odpovídá tento řádek: ```neon application: @@ -486,7 +486,7 @@ application: Mapování funguje tak, že název presenteru `Home` nahradí hvězdičku v masce `App\Presentation\*Presenter`, čímž získáme výsledný název třídy `App\Presentation\HomePresenter`. Jednoduché! -Jak ale vidíte v ukázkách v této a dalších kapitolách, třídy presenterů umisťujeme do eponymních podadresářů, například presenter `Home` se mapuje na třídu `App\Presentation\Home\HomePresenter`. Toho dosáhneme zdvojením dvojtečky (vyžaduje Nette Application 3.2): +Jak ale vidíte v ukázkách v této a dalších kapitolách, třídy presenterů umisťujeme do eponymních podadresářů, například presenter `Home` se mapuje na třídu `App\Presentation\Home\HomePresenter`. Toho dosáhneme použitím zdvojené hvězdičky `**` (vyžaduje Nette Application 3.2.3): ```neon application: @@ -514,9 +514,9 @@ application: Api: App\Api\*Presenter ``` -Funguje to i pro hlouběji zanořené adresářové struktury, jako je například presenter `Admin:User:Edit`, se segment s hvězdičkou opakuje pro každou úroveň a výsledkem je třída `App\Presentation\Admin\User\Edit\EditPresenter`. +Funguje to i pro hlouběji zanořené adresářové struktury, jako je například presenter `Admin:User:Edit`, kdy se segment s hvězdičkou opakuje pro každou úroveň modulu a výsledkem je třída `App\Presentation\Admin\User\Edit\EditPresenter`. -Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů. Tento zápis je ekvivaletní s předchozím: +Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů. Pro výše uvedené příklady je tento zápis ekvivalentní s předchozím: ```neon application: diff --git a/application/cs/how-it-works.texy b/application/cs/how-it-works.texy index 2396423c45..63516d0459 100644 --- a/application/cs/how-it-works.texy +++ b/application/cs/how-it-works.texy @@ -51,7 +51,7 @@ Adresářovou strukturu můžete jakkoliv měnit, složky přejmenovat či přes U trošku větších aplikací můžeme složky s presentery a šablonami [rozčlenit do podadresářů |directory-structure#Presentery a šablony] a třídy do jmenných prostorů, kterým říkáme moduly. -Adresář `www/` představuje tzv. veřejný adresář neboli document-root projektu. Můžete jej přejmenovat bez nutnosti cokoliv dalšího nastavovat na straně aplikace. Jen je potřeba [nakonfigurovat hosting |nette:troubleshooting#Jak změnit či ostranit z URL adresář www] tak, aby document-root mířil do tohoto adresáře. +Adresář `www/` představuje tzv. veřejný adresář neboli document-root projektu. Můžete jej přejmenovat bez nutnosti cokoliv dalšího nastavovat na straně aplikace. Jen je potřeba [nakonfigurovat hosting |nette:troubleshooting#Jak změnit či odstranit z URL adresář www] tak, aby document-root mířil do tohoto adresáře. WebProject si můžete také rovnou stáhnout včetně Nette a to pomocí [Composeru |best-practices:composer]: @@ -93,9 +93,9 @@ Aplikace psané v Nette se člení do spousty tzv. presenterů (v jiných framew Application začne tím, že požádá tzv. router, aby rozhodl, kterému z presenterů předat aktuální požadavek k vyřízení. Router rozhodne, čí je to zodpovědnost. Podívá se na vstupní URL `https://example.com/product/123` a na základě toho, jak je nastavený, rozhodne, že tohle je práce např. pro **presenter** `Product`, po kterém bude chtít jako **akci** zobrazení (`show`) produktu s `id: 123`. Dvojici presenter + akce je dobrým zvykem zapisovat oddělené dvojtečkou jako `Product:show`. -Tedy router transformoval URL na dvojici `Presenter:action` + parametry, v našem případě `Product:show` + `id: 123`. Jak takový router vypadá se můžete podívat v souboru `app/Core/RouterFactory.php` a podrobně ho popisujeme v kapitole [Routing]. +Tedy router transformoval URL na dvojici `Presenter:action` + parametry, v našem případě `Product:show` + `id: 123`. Jak takový router vypadá, se můžete podívat v souboru `app/Core/RouterFactory.php` a podrobně ho popisujeme v kapitole [Routing]. -Pojďme dál. Application už zná jméno presenteru a může pokračovat dál. Tím že vyrobí objekt třídy `ProductPresenter`, což je kód presenteru `Product`. Přesněji řečeno, požádá DI kontejner, aby presenter vyrobil, protože od vyrábění je tu on. +Pojďme dál. Application už zná jméno presenteru a může pokračovat. Tím, že vyrobí objekt třídy `ProductPresenter`, což je kód presenteru `Product`. Přesněji řečeno, požádá DI kontejner, aby presenter vyrobil, protože od vyrábění je tu on. Presenter může vypadat třeba takto: @@ -119,11 +119,11 @@ Vyřizování požadavku přebírá presenter. A úkol zní jasně: proveď akci Presenter může obsluhovat více akcí, tedy mít více metod `render()`. Ale doporučujeme navrhovat presentery s jednou nebo co nejméně akcemi. -Takže, zavolala se metoda `renderShow(123)`, jejíž kód je sice smyšlený příklad, ale můžete na něm vidět, jak se předávají data do šablony, tedy zápisem do `$this->template`. +Takže se zavolala metoda `renderShow(123)`, jejíž kód je sice smyšlený příklad, ale můžete na něm vidět, jak se předávají data do šablony, tedy zápisem do `$this->template`. Následně presenter vrátí odpověď. Tou může být HTML stránka, obrázek, XML dokument, odeslání souboru z disku, JSON nebo třeba přesměrování na jinou stránku. Důležité je, že pokud explicitně neřekneme, jak má odpovědět (což je případ `ProductPresenter`), bude odpovědí vykreslení šablony s HTML stránkou. Proč? Protože v 99 % případů chceme vykreslit šablonu, tudíž presenter tohle chování bere jako výchozí a chce nám ulehčit práci. To je smyslem Nette. -Nemusíme ani uvádět, jakou šablonu vykreslit, cestu k ní si odvodí sám. V případě akce `show` jednodušše zkusí načíst šablonu `show.latte` v adresáři s třídou `ProductPresenter`. Taktéž se pokusí dohledat layout v souboru `@layout.latte` (podrobněji o [dohledávání šablon |templates#Hledání šablon]). +Nemusíme ani uvádět, jakou šablonu vykreslit, cestu k ní si odvodí sám. V případě akce `show` jednoduše zkusí načíst šablonu `show.latte` v adresáři s třídou `ProductPresenter`. Taktéž se pokusí dohledat layout v souboru `@layout.latte` (podrobněji o [dohledávání šablon |templates#Hledání šablon]). A následně šablony vykreslí. Tím je úkol presenteru i celé aplikace dokonán a dílo jest završeno. Pokud by šablona neexistovala, vrátí se stránka s chybou 404. Více se o presenterech dočtete na stránce [Presentery|presenters]. @@ -132,7 +132,7 @@ A následně šablony vykreslí. Tím je úkol presenteru i celé aplikace dokon Pro jistotu, zkusme si zrekapitulovat celý proces s trošku jinou URL: 1) URL bude `https://example.com` -2) bootujeme aplikaci, vytvoří se kontejner a spustí `Application::run()` +2) bootujeme aplikaci, vytvoří se kontejner a spustí se `Application::run()` 3) router URL dekóduje jako dvojici `Home:default` 4) vytvoří se objekt třídy `HomePresenter` 5) zavolá se metoda `renderDefault()` (pokud existuje) @@ -147,7 +147,7 @@ Možná jste se teď setkali s velkou spoustou nových pojmů, ale věříme, ž Když už přišla řeč na šablony, v Nette se používá šablonovací systém [Latte |latte:]. Proto taky ty koncovky `.latte` u šablon. Latte se používá jednak proto, že jde o nejlépe zabezpečený šablonovací systém pro PHP, a zároveň také systém nejintuitivnější. Nemusíte se učit mnoho nového, vystačíte si se znalostí PHP a několika značek. Všechno se dozvíte [v dokumentaci |templates]. -V šabloně se [vytvářejí odkazy |creating-links] na další presentery & akce takto: +V šabloně se [vytvářejí odkazy |creating-links] na další presentery a akce takto: ```latte detail produktu @@ -159,7 +159,7 @@ Prostě místo reálného URL napíšete známý pár `Presenter:action` a uvede detail produktu ``` -Generování URL má na starosti už dříve zmíněný router. Totiž routery v Nette jsou výjimečné tím, že dokáží provádět nejen transformace z URL na dvojici presenter:action, ale také obráceně, tedy z názvu presenteru + akce + parametrů vygenerovat URL. Díky tomu v Nette můžete úplně změnit tvary URL v celé hotové aplikaci, aniž byste změnili jediný znak v šabloně nebo presenteru. Jen tím, že upravíte router. Také díky tomu funguje tzv. kanonizace, což je další unikátní vlastnost Nette, která přispívá k lepšímu SEO (optimalizaci nalezitelnosti na internetu) tím, že automaticky zabraňuje existenci duplicitního obsahu na různých URL. Hodně programátorů to považuje za ohromující. +Generování URL má na starosti už dříve zmíněný router. Routery v Nette jsou totiž výjimečné tím, že dokáží provádět nejen transformace z URL na dvojici presenter:action, ale také obráceně, tedy z názvu presenteru + akce + parametrů vygenerovat URL. Díky tomu v Nette můžete úplně změnit tvary URL v celé hotové aplikaci, aniž byste změnili jediný znak v šabloně nebo presenteru. Jen tím, že upravíte router. Také díky tomu funguje tzv. kanonizace, což je další unikátní vlastnost Nette, která přispívá k lepšímu SEO (optimalizaci nalezitelnosti na internetu) tím, že automaticky zabraňuje existenci duplicitního obsahu na různých URL. Hodně programátorů to považuje za ohromující. Interaktivní komponenty @@ -169,7 +169,7 @@ O presenterech vám musíme prozradit ještě jednu věc: mají v sobě zabudova Komponenty jsou samostatné znovupoužitelné celky, které vkládáme do stránek (tedy presenterů). Mohou to být [formuláře |forms:in-presenter], [datagridy |https://componette.org/contributte/datagrid/], menu, hlasovací ankety, vlastně cokoliv, co má smysl používat opakovaně. Můžeme vytvářet vlastní komponenty nebo používat některé z [ohromné nabídky |https://componette.org] open source komponent. -Komponenty zásadním způsobem ovlivňují přístup k tvorbě aplikacím. Otevřou vám nové možnosti skládání stránek z předpřipravených jednotek. A navíc mají něco společného s [Hollywoodem |components#Hollywood style]. +Komponenty zásadním způsobem ovlivňují přístup k tvorbě aplikací. Otevřou vám nové možnosti skládání stránek z předpřipravených jednotek. A navíc mají něco společného s [Hollywoodem |components#Hollywood style]. DI kontejner a konfigurace @@ -177,13 +177,13 @@ DI kontejner a konfigurace DI kontejner neboli továrna na objekty je srdce celé aplikace. -Nemějte obavy, není to žádný magický black box, jak by se třeba mohlo z předchozích řádků zdát. Vlastně je to jedna docela nudná PHP třída, kterou vygeneruje Nette a uloží do adresáře s cache. Má spoustu metod pojmenovaných jako `createServiceAbcd()` a každá z nich umí vyrobit a vrátit nějaký objekt. Ano, je tam i metoda `createServiceApplication()`, která vyrobí `Nette\Application\Application`, který jsme potřebovali v souboru `index.php` pro spuštění aplikace. A jsou tam metody vyrábějící jednotlivé presentery. A tak dále. +Nemějte obavy, není to žádný magický black box, jak by se třeba mohlo z předchozích řádků zdát. Vlastně je to jedna docela nudná PHP třída, kterou vygeneruje Nette a uloží do adresáře s cache. Má spoustu metod pojmenovaných jako `createServiceAbcd()` a každá z nich umí vyrobit a vrátit nějaký objekt. Ano, je tam i metoda `createServiceApplication__application()`, která vyrobí `Nette\Application\Application`, který jsme potřebovali v souboru `index.php` pro spuštění aplikace. A jsou tam metody vyrábějící jednotlivé presentery. A tak dále. Objektům, které DI kontejner vytváří, se z nějakého důvodu říká služby. -Co je na této třídě opravdu speciálního, tak že ji neprogramujete vy, ale framework. On skutečně vygeneruje PHP kód a uloží ho na disk. Vy jen dáváte instrukce, jaké objekty má umět kontejner vyrábět a jak přesně. A tyhle instrukce jsou zapsané v [konfiguračních souborech |bootstrapping#Konfigurace DI kontejneru], pro které se používá formát [NEON|neon:format] a tedy mají i příponu `.neon`. +Na této třídě je opravdu speciální to, že ji neprogramujete vy, ale framework. On skutečně vygeneruje PHP kód a uloží ho na disk. Vy jen dáváte instrukce, jaké objekty má umět kontejner vyrábět a jak přesně. A tyhle instrukce jsou zapsané v [konfiguračních souborech |bootstrapping#Konfigurace DI kontejneru], pro které se používá formát [NEON|neon:format] a tedy mají i příponu `.neon`. -Konfigurační soubory slouží čistě k instruování DI kontejneru. Takže když například uvedu v sekci [session |http:configuration#Session] volbu `expiration: 14 days`, tak DI kontejner při vytváření objektu `Nette\Http\Session` reprezentujícího session zavolá jeho metodu `setExpiration('14 days')` a tím se konfigurace stane realitou. +Konfigurační soubory slouží čistě k instruování DI kontejneru. Takže když například v sekci [session |http:configuration#Session] uvedete volbu `expiration: 14 days`, tak DI kontejner při vytváření objektu `Nette\Http\Session` reprezentujícího session zavolá jeho metodu `setExpiration('14 days')` a tím se konfigurace stane realitou. Je tu pro vás připravená celá kapitola popisující, co vše lze [konfigurovat |nette:configuring] a jak [definovat vlastní služby |dependency-injection:services]. diff --git a/application/cs/multiplier.texy b/application/cs/multiplier.texy index 4bd4c80f76..dddd5f573a 100644 --- a/application/cs/multiplier.texy +++ b/application/cs/multiplier.texy @@ -2,7 +2,7 @@ Multiplier: dynamické komponenty ******************************** .[perex] -Nástroj na dynamickou tvorbu interaktivních komponent +Nástroj na dynamickou tvorbu interaktivních komponent. Vyjděme od typického příkladu: mějme seznam zboží v eshopu, přičemž u každého budeme chtít vypsat formulář pro přidání zboží do košíku. Jednou z možných variant je obalit celý výpis do jednoho formuláře. Mnohem pohodlnější způsob nám však nabízí [api:Nette\Application\UI\Multiplier]. @@ -37,7 +37,7 @@ Nyní můžeme v šabloně jednoduše u každého zboží nechat vykreslit formu {/foreach} ``` -Argument předaný v značce `{control}` je ve formátu, který říká: +Argument předaný ve značce `{control}` je ve formátu, který říká: 1. získej komponentu `shopForm` 2. a z ní získej potomka `$item->id` @@ -46,12 +46,12 @@ Při prvním volání bodu **1.** `shopForm` ještě neexistuje, takže se zavol V další iteraci foreache již metoda `createComponentShopForm` volána nebude (komponenta existuje), ale protože hledáme jejího jiného potomka (`$item->id` bude v každé iteraci jiné), znovu bude zavolána anonymní funkce a vrátí nám nový formulář. -Jediné, co zbývá, je zajistit, aby nám formulář do košíku přidal skutečně to zboží, které má - aktuálně je formulář u každého zboží úplně totožný. Pomůže nám vlastnost Multiplieru (a obecně každé továrny na komponentu v Nette Frameworku), a to ta, že každá továrna jako svůj první argument dostává název tvořené komponenty. V našem případě to bude `$item->id`, což je přesně ten údaj, který potřebujeme. Stačí tedy lehce upravit tvorbu formuláře: +Jediné, co zbývá, je zajistit, aby nám formulář do košíku přidal skutečně to zboží, které má - aktuálně je formulář u každého zboží úplně totožný. Pomůže nám vlastnost Multiplieru (a obecně každé továrny na komponentu v Nette Frameworku), a to ta, že každá továrna dostává jako svůj první argument název tvořené komponenty. Továrna Multiplieru navíc jako druhý argument dostává samotnou instanci Multiplieru. V našem případě bude prvním argumentem `$item->id`, což je přesně ten údaj, který potřebujeme. Stačí tedy lehce upravit tvorbu formuláře: ```php protected function createComponentShopForm(): Multiplier { - return new Multiplier(function ($itemId) { + return new Multiplier(function (string $itemId) { $form = new Nette\Application\UI\Form; $form->addInteger('count', 'Počet zboží:') ->setRequired(); diff --git a/application/cs/presenters.texy b/application/cs/presenters.texy index 9f3c2f2f8b..a867660fb6 100644 --- a/application/cs/presenters.texy +++ b/application/cs/presenters.texy @@ -50,16 +50,19 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Ihned po obdržení požadavku se zavolá metoda `startup()`. Můžete ji využít k inicializaci properties, ověření uživatelských oprávnění atd. Je vyžadováno, aby metoda vždy volala předka `parent::startup()`. +Ihned po obdržení požadavku se zavolá metoda `startup()`. Můžete ji využít k inicializaci properties, ověření uživatelských oprávnění atd. Metoda musí vždy volat předka `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Obdoba metody `render()`. Zatímco `render()` je určená k tomu, aby připravila data pro konkrétní šablonu, která se následně vykreslí, tak v `action()` se zpracovává požadavek bez návaznosti na vykreslování šablony. Například se zpracují data, přihlásí či odhlásí uživatel, a tak podobně, a poté [přesměruje jinam |#Přesměrování]. +Obdoba metody `render()`. Zatímco `render()` je určená k tomu, aby připravila data pro konkrétní šablonu, která se následně vykreslí, tak v `action()` se zpracovává požadavek bez návaznosti na vykreslování šablony. Například se zpracují data, přihlásí či odhlásí uživatel a podobně a poté se [přesměruje jinam |#Přesměrování]. Důležité je, že `action()` se volá dříve než `render()`, takže v ní můžeme případně změnit další běh dějin, tj. změnit šablonu, která se bude kreslit, a také metodu `render()`, která se bude volat. A to pomocí `setView('jineView')`. +.{data-version:3.2.3} +Můžete dokonce přepnout na zcela jinou akci metodou `switch('jináAkce')`. Přeruší běh aktuální metody a místo ní spustí metody `action()` a `render()` nové akce (a vypne automatickou [kanonizaci|#Kanonizace]). Samotný požadavek pokračuje dál, ukončí se jen právě běžící metoda. + Metodě se předávají parametry z požadavku. Je možné a doporučené uvést parametrům typy, např. `actionShow(int $id, ?string $slug = null)` - pokud bude parametr `id` chybět nebo pokud nebude integer, presenter vrátí [chybu 404 |#Chyba 404 a spol] a ukončí činnost. @@ -105,7 +108,27 @@ Metoda `afterRender`, jak název opět napovídá, se volá za každou metodou ` Volá se na konci životního cyklu presenteru. -**Dobrá rada, než půjdeme dál**. Presenter jak vidno může obsluhovat více akcí/view, tedy mít více metod `render()`. Ale doporučujeme navrhovat presentery s jednou nebo co nejméně akcemi. +Události +-------- + +Kromě metod `startup()`, `beforeRender()` a `shutdown()`, které se volají jako součást životního cyklu presenteru, lze definovat ještě další funkce, které se mají automaticky zavolat. Presenter definuje tzv. [události |nette:glossary#události], jejichž handlery přidáte do polí `$onStartup`, `$onRender` a `$onShutdown`. + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` + +Handlery v poli `$onStartup` se volají těsně před metodou `startup()`, dále `$onRender` mezi `beforeRender()` a `render()` a nakonec `$onShutdown` těsně před `shutdown()`. + + +**Dobrá rada, než půjdeme dál**. Presenter, jak vidno, může obsluhovat více akcí/view, tedy mít více metod `render()`. Ale doporučujeme navrhovat presentery s jednou nebo co nejméně akcemi. Odeslání odpovědi @@ -115,7 +138,7 @@ Odpovědí presenteru je zpravidla [vykreslení šablony s HTML stránkou|templa Kdykoliv během životního cyklu můžeme některou z následujících metod odeslat odpověď a zároveň tak ukončit presenter: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` a `forward()` [přesměruje |#Přesměrování] +- `redirect()`, `redirectPermanent()`, `redirectUrl()` a `forward()` [přesměrují |#Přesměrování] - `error()` ukončí presenter [kvůli chybě |#Chyba 404 a spol] - `sendJson($data)` presenter ukončí a [odešle data |#Odeslání JSON] ve formátu JSON - `sendTemplate()` presenter ukončí a ihned [vykreslí šablonu |templates] @@ -124,13 +147,13 @@ Kdykoliv během životního cyklu můžeme některou z následujících metod od Každá z těchto metod okamžitě ukončí činnost presenteru vyhozením tzv. tiché ukončovací výjimky `Nette\Application\AbortException`. -Pokud žádnou z těchto metod nezavoláte, presenter automaticky přistoupí k vykreslí šablony. Proč? Protože v 99 % případů chceme vykreslit šablonu, tudíž presenter tohle chování bere jako výchozí a chce nám ulehčit práci. +Pokud žádnou z těchto metod nezavoláte, presenter automaticky přistoupí k vykreslení šablony. Proč? Protože v 99 % případů chceme vykreslit šablonu, tudíž presenter tohle chování bere jako výchozí a chce nám ulehčit práci. Vytváření odkazů ================ -Presenter disponuje metodou `link()`, pomocí které lze vytvářet URL odkazy na další presentery. Prvním parametrem je cílový presenter & akce, následují předávané argumenty, které mohou být uvedeny jako pole: +Presenter disponuje metodou `link()`, pomocí které lze vytvářet URL odkazy na další presentery. Prvním parametrem je cílový presenter a akce, následují předávané argumenty, které mohou být uvedeny jako pole: ```php $url = $this->link('Product:show', $id); @@ -138,7 +161,7 @@ $url = $this->link('Product:show', $id); $url = $this->link('Product:show', [$id, 'lang' => 'cs']); ``` -V šabloně se vytvářejí odkazy na další presentery & akce tímto způsobem: +V šabloně se vytvářejí odkazy na další presentery a akce tímto způsobem: ```latte detail produktu @@ -152,7 +175,7 @@ Více informací najdete v kapitole [Vytváření odkazů URL|creating-links]. Přesměrování ============ -K přechodu na jiný presenter slouží metody `redirect()` a `forward()`, které mají velmi podobnou syntax jako metoda [link() |#Vytváření odkazů]. +K přechodu na jiný presenter slouží metody `redirect()` a `forward()`, které mají velmi podobnou syntaxi jako metoda [link() |#Vytváření odkazů]. Metoda `forward()` přejde na nový presenter okamžitě bez HTTP přesměrování: @@ -188,7 +211,7 @@ Flash zprávy Jde o zprávy obvykle informující o výsledku nějaké operace. Důležitým rysem flash zpráv je to, že jsou v šabloně k dispozici i po přesměrování. I po zobrazení zůstanou živé ještě dalších 30 sekund - například pro případ, že by z důvodu chybného přenosu uživatel dal stránku obnovit - zpráva mu tedy hned nezmizí. -Stačí zavolat metodu [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] a o předání do šablony se postará presenter. Prvním parametrem je text zprávy a nepovinným druhým parametrem její typ (error, warning, info apod.). Metoda `flashMessage()` vrací instanci flash zprávy, které je možné přidávat další informace. +Stačí zavolat metodu [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] a o předání do šablony se postará presenter. Prvním parametrem je text zprávy a nepovinným druhým parametrem její typ (error, warning, info apod.). Metoda `flashMessage()` vrací instanci flash zprávy, ke které je možné přidávat další informace. ```php $this->flashMessage('Položka byla smazána.'); @@ -207,7 +230,7 @@ $this->redirect(/* ... */); // a přesměrujeme Chyba 404 a spol. ================= -Pokud nelze splnit požadavek, třeba z důvodu, že článek který chceme zobrazit neexistuje v databázi, vyhodíme chybu 404 metodou `error(?string $message = null, int $httpCode = 404)`. +Pokud nelze splnit požadavek, třeba z důvodu, že článek, který chceme zobrazit, v databázi neexistuje, vyhodíme chybu 404 metodou `error(string $message = '', int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -220,13 +243,13 @@ public function renderShow(int $id): void } ``` -HTTP kód chyby lze předat jako druhý parametr, výchozí je 404. Metoda funguje tak, že vyhodí výjimku `Nette\Application\BadRequestException`, načež `Application` předá řízení error-presenteru. Což je presenter, jehož úkolem je zobrazit stránku informující o nastalé chybě. Nastavení error-preseteru se provádí v [konfiguraci application|configuration]. +HTTP kód chyby lze předat jako druhý parametr, výchozí je 404. Metoda funguje tak, že vyhodí výjimku `Nette\Application\BadRequestException`, načež `Application` předá řízení error-presenteru. Což je presenter, jehož úkolem je zobrazit stránku informující o nastalé chybě. Nastavení error-presenteru se provádí v [konfiguraci application|configuration]. Odeslání JSON ============= -Příklad action-metody, která odešle data ve formátu JSON a ukončí presenter: +Metoda `sendJson($data)` převede předaná data do formátu JSON, odešle je jako HTTP odpověď a ukončí běh presenteru. Příklad: ```php public function actionData(): void @@ -240,7 +263,7 @@ public function actionData(): void Parametry požadavku .{data-version:3.1.14} ========================================== -Presenter a také každá komponenta získává z HTTP požadavku své parametry. Jejich hodnotu zjistíte metodou `getParameter($name)` nebo `getParameters()`. Hodnoty jsou řetězce či pole řetězců, jde v podstatě o surové data získané přímo z URL. +Presenter a také každá komponenta získává z HTTP požadavku své parametry. Jejich hodnotu zjistíte metodou `getParameter($name)` nebo `getParameters()`. Hodnoty jsou řetězce či pole řetězců, jde v podstatě o surová data získaná přímo z URL. Pro větší pohodlí doporučujeme parametry zpřístupnit přes property. Stačí je označit atributem `#[Parameter]`: @@ -268,9 +291,9 @@ Persistentní parametry Persistentní parametry slouží k udržování stavu mezi různými požadavky. Jejich hodnota zůstává stejná i po kliknutí na odkaz. Na rozdíl od dat v session se přenášejí v URL. A to zcela automaticky, není tedy potřeba je explicitně uvádět v `link()` nebo `n:href`. -Příklad použití? Máte multijazyčnou aplikaci. Aktuální jazyk je parameter, který musí být neustále součástí URL. Ale bylo by neskutečně únavné ho v každém odkazu uvádět. Tak z něj uděláte persistentní parametr `lang` a bude se přenášet sám. Paráda! +Příklad použití? Máte multijazyčnou aplikaci. Aktuální jazyk je parametr, který musí být neustále součástí URL. Ale bylo by neskutečně únavné ho v každém odkazu uvádět. Tak z něj uděláte persistentní parametr `lang` a bude se přenášet sám. Paráda! -Vytvoření persistentního parametru je v Nette nesmírně jednoduché. Stačí vytvořit veřejnou property a označit ji atributem: (dříve se používalo `/** @persistent */`) +Vytvoření persistentního parametru je v Nette nesmírně jednoduché. Stačí vytvořit veřejnou property a označit ji atributem (dříve se používalo `/** @persistent */`): ```php use Nette\Application\Attributes\Persistent; // tento řádek je důležitý @@ -282,7 +305,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Pokud bude `$this->lang` mít hodnotu například `'en'`, tak i odkazy vytvořené pomocí `link()` nebo `n:href` budou obsahovat parameter `lang=en`. A po kliknutí na odkaz bude opět `$this->lang = 'en'`. +Pokud bude `$this->lang` mít hodnotu například `'en'`, tak i odkazy vytvořené pomocí `link()` nebo `n:href` budou obsahovat parametr `lang=en`. A po kliknutí na odkaz bude opět `$this->lang = 'en'`. U property doporučujeme uvádět i datový typ (např. `string`) a můžete uvést i výchozí hodnotu. Hodnoty parametrů lze [validovat |#Validace parametrů]. @@ -317,6 +340,26 @@ Nebo jej lze *vyresetovat*, tj. odstranit z URL. Pak bude nabývat svou výchoz ``` +Společný prostor parametrů +========================== + +Parametry požadavku, [persistentní parametry |#Persistentní parametry] i parametry metod `action`, `render` a `handle` (signálů) sdílejí jeden společný prostor, kde je každý z nich identifikován svým jménem. Pokud se stejné jméno objeví ve více z nich, odkazují na jednu a tutéž hodnotu. + +Toho se často využívá. Například persistentní parametr `lang` a argument `$lang` action nebo signálové metody jsou jedno a totéž - aktuální hodnotu persistentního parametru si přečtete prostě tím, že ho uvedete v signatuře metody: + +```php +#[Persistent] +public string $lang; + +public function handleSearch(string $query, string $lang): void +{ + // $lang obsahuje aktuální hodnotu persistentního parametru lang +} +``` + +Protože je tento prostor sdílený, dbejte na to, aby názvy parametrů byly jedinečné, pokud nechcete, aby cíleně sdílely hodnotu. To platí i pro signály, které navíc čtou parametry z POST těla požadavku, viz [Signály do hloubky |components#Signály do hloubky]. + + Interaktivní komponenty ======================= @@ -324,7 +367,7 @@ Presentery v sobě mají zabudovaný komponentový systém. Komponenty jsou samo Jak se do presenteru komponenty vkládají a následně používají? To se dozvíte v kapitole [Komponenty |components]. Dokonce zjistíte, co mají společného s Hollywoodem. -A kde mohu získat komponenty? Na stránce [Componette |https://componette.org/search/component] najdete open-source komponenty a také řadu dalších doplňku pro Nette, které sem umístili dobrovolníci z komunity okolo frameworku. +A kde mohu získat komponenty? Na stránce [Componette |https://componette.org/search/component] najdete open-source komponenty a také řadu dalších doplňků pro Nette, které sem umístili dobrovolníci z komunity okolo frameworku. Jdeme do hloubky @@ -337,7 +380,7 @@ S tím, co jsme si dosud v této kapitole ukázali, si nejspíš úplně vystač Validace parametrů ------------------ -Hodnoty [parametrů požadavku |#Parametry požadavku] a [persistentních parametrů |#Persistentní parametry] přijatých z URL zapisuje do properties metoda `loadState()`. Ta také kontroluje, zda odpovídá datový typ uvedený u property, jinak odpoví chybou 404 a stránka se nezobrazí. +Hodnoty [parametrů požadavku |#Parametry požadavku] a [persistentních parametrů |#Persistentní parametry] přijatých z URL zapisuje do properties metoda `loadState()`. Ta také kontroluje, zda hodnota odpovídá datovému typu uvedenému u property; jinak odpoví chybou 404 a stránka se nezobrazí. Nikdy slepě nevěřte parametrům, protože mohou být snadno uživatelem přepsány v URL. Takto například ověříme, zda je jazyk `$this->lang` mezi podporovanými. Vhodnou cestou je přepsat zmíněnou metodu `loadState()`: @@ -364,9 +407,9 @@ Uložení a obnovení požadavku Požadavek, který vyřizuje presenter, je objekt [api:Nette\Application\Request] a vrací ho metoda presenteru `getRequest()`. -Aktuální požadavek lze uložit do session nebo naopak z ní obnovit a nechat jej presenter znovu vykonat. To se hodí například v situaci, když uživatel vyplňuje formulář a vyprší mu přihlášení. Aby o data nepřišel, před přesměrováním na přihlašovací stránku aktuální požadavek uložíme do session pomocí `$reqId = $this->storeRequest()`, které vrátí jeho identifikátor v podobě krátkého řetězce a ten předáme jako parameter přihlašovacímu presenteru. +Aktuální požadavek lze uložit do session nebo naopak z ní obnovit a nechat jej presenter znovu vykonat. To se hodí například v situaci, když uživatel vyplňuje formulář a vyprší mu přihlášení. Aby o data nepřišel, před přesměrováním na přihlašovací stránku aktuální požadavek uložíme do session pomocí `$reqId = $this->storeRequest()`, které vrátí jeho identifikátor v podobě krátkého řetězce a ten předáme jako parametr přihlašovacímu presenteru. -Po přihlášení zavoláme metodu `$this->restoreRequest($reqId)`, která požadavek vyzvedne ze session a forwarduje na něj. Metoda přitom ověří, že požadavek vytvořil stejný uživatel, jako se nyní přihlásil. Pokud by se přihlásil jiný uživatel nebo klíč byl neplatný, neudělá nic a program pokračuje dál. +Po přihlášení zavoláme metodu `$this->restoreRequest($reqId)`, která požadavek vyzvedne ze session. POST požadavky na něj forwarduje, ostatní (GET) přesměruje na URL požadavku. Metoda přitom ověří, že požadavek vytvořil stejný uživatel, jako se nyní přihlásil. Pokud by se přihlásil jiný uživatel nebo klíč byl neplatný, neudělá nic a program pokračuje dál. Podívejte se na návod [Jak se vrátit k dřívější stránce |best-practices:restore-request]. @@ -376,7 +419,7 @@ Kanonizace Presentery mají jednu opravdu skvělou vlastnost, která přispívá k lepšímu SEO (optimalizaci nalezitelnosti na internetu). Automaticky zabraňují existenci duplicitního obsahu na různých URL. Pokud k určitému cíli vede více URL adres, např. `/index` a `/index?page=1`, framework určí jednu z nich za primární (kanonickou) a ostatní na ni přesměruje pomocí HTTP kódu 301. Díky tomu vám vyhledávače stránky neindexují dvakrát a nerozmělní jejich page rank. -Tomuto procesu se říká kanonizace. Kanonickou URL je ta, kterou vygeneruje [router|routing], zpravidla tedy první odpovídající routa v kolekci. +Tomuto procesu se říká kanonizace. Kanonická URL je ta, kterou vygeneruje [router|routing], zpravidla tedy první odpovídající routa v kolekci. Kanonizace je ve výchozím nastavení zapnutá a lze ji vypnout přes `$this->autoCanonicalize = false`. @@ -388,30 +431,12 @@ Kanonizaci můžete vyvolat i manuálně pomocí metody `canonicalize()`, které public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // přesměruje, pokud $slug se liší od $realSlug + // přesměruje, pokud se $slug liší od $realSlug $this->canonicalize('Product:show', [$id, $realSlug]); } ``` - -Události --------- - -Kromě metod `startup()`, `beforeRender()` a `shutdown()`, které se volají jako součást životního cyklu presenteru, lze definovat ještě další funkce, které se mají automaticky zavolat. Presenter definuje tzv. [událost |nette:glossary#události], jejichž handlery přidáte do polí `$onStartup`, `$onRender` a `$onShutdown`. - -```php -class ArticlePresenter extends Nette\Application\UI\Presenter -{ - public function __construct() - { - $this->onStartup[] = function () { - // ... - }; - } -} -``` - -Handlery v poli `$onStartup` se volají těsně před metodou `startup()`, dále `$onRender` mezi `beforeRender()` a `render()` a nakonec `$onShutdown` těsně před `shutdown()`. +Kompletní vzor, který kombinuje routovací filtry s `canonicalize()` pro generování SEO-friendly URL, najdete v návodu [Hezké URL se slugem |best-practices:pretty-urls]. Odpovědi @@ -447,19 +472,79 @@ $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $ht $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +Můžete si napsat i vlastní odpověď. Stačí implementovat rozhraní `Nette\Application\Response`, které má jedinou metodu `send()` přijímající HTTP požadavek a odpověď. To se hodí například při streamování dat, která nechcete držet v paměti: + +```php +class CsvResponse implements Nette\Application\Response +{ + public function __construct( + private string $fileName, + private iterable $rows, + ) { + } + + public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void + { + $response->setContentType('text/csv', 'utf-8'); + $response->sendAsFile($this->fileName); + + $handle = fopen('php://output', 'w'); + foreach ($this->rows as $row) { + fputcsv($handle, $row); + } + + fclose($handle); + } +} +``` + +V presenteru ji pak odešlete obvyklým způsobem: `$this->sendResponse(new CsvResponse('export.csv', $rows));` + -Omezení přístupu pomocí `#[Requires]` .{data-version:3.2.2} +HTTP kešování +------------- + +Metoda `lastModified()` umožňuje snadno využít HTTP kešování. Předáte jí datum a čas poslední změny obsahu (jako timestamp, řetězec nebo objekt `DateTimeInterface`) a volitelně ETag validátor (krátký řetězec identifikující aktuální verzi obsahu, například jeho hash) a čas expirace. Pokud už prohlížeč aktuální verzi má, presenter odešle odpověď `304 Not Modified` a ukončí činnost, takže se stránka zbytečně negeneruje ani nepřenáší: + +```php +public function renderArticle(int $id): void +{ + $article = $this->articles->getById($id); + $this->lastModified($article->updatedAt); + // ... +} +``` + + +Doplnění šablony .{data-version:3.3.0} +-------------------------------------- + +Když presenter vykresluje šablonu, metoda `sendTemplate()` těsně před vykreslením zavolá `completeTemplate()`. Ta doplní proměnné označené atributem `#[TemplateVariable]` a dohledá soubor šablony (výchozí proměnné nastaví už `TemplateFactory` při vytvoření šablony). Tuto chráněnou metodu můžete přepsat a přidat do šablony proměnné společné pro všechny pohledy nebo nastavit jiný soubor: + +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My App'; +} +``` + + +Omezení přístupu pomocí `#[Requires]` .{data-version:3.2.3} ----------------------------------------------------------- -Atribut `#[Requires]` poskytuje pokročilé možnosti pro omezení přístupu k presenterům a jejich metodám. Lze jej použít pro specifikaci HTTP metod, vyžadování AJAXového požadavku, omezení na stejný původ (same origin), a přístup pouze přes forwardování. Atribut lze aplikovat jak na třídy presenterů, tak na jednotlivé metody `action()`, `render()`, `handle()` a `createComponent()`. +Atribut `#[Requires]` poskytuje pokročilé možnosti pro omezení přístupu k presenterům a jejich metodám. Lze jej použít k určení HTTP metod, vyžadování AJAXového požadavku, omezení na stejný původ (same origin) i k povolení přístupu pouze přes forwardování. Atribut lze aplikovat jak na třídy presenterů, tak na jednotlivé metody `action()`, `render()`, `handle()` a `createComponent()`. -Můžete určit tyto omezení: +Můžete určit tato omezení: - na HTTP metody: `#[Requires(methods: ['GET', 'POST'])]` - vyžadování AJAXového požadavku: `#[Requires(ajax: true)]` - přístup pouze ze stejného původu: `#[Requires(sameOrigin: true)]` - přístup pouze přes forward: `#[Requires(forward: true)]` - omezení na konkrétní akce: `#[Requires(actions: 'default')]` +.[note] +Shoda stejného původu (same origin) se od verze 3.3 ověřuje pomocí prohlížečové hlavičky `Sec-Fetch-Site` (dříve pomocí SameSite cookie), což je spolehlivější a kontroluje přesnou shodu schématu, domény i portu. + Podrobnosti najdete v návodu [Jak používat atribut Requires |best-practices:attribute-requires]. @@ -468,7 +553,7 @@ Kontrola HTTP metody Presentery v Nette automaticky ověřují HTTP metodu každého příchozího požadavku. Důvodem pro tuto kontrolu je především bezpečnost. Standardně jsou povoleny metody `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. -Chcete-li povolit navíc například metodu `OPTIONS`, použijte k tomu atribut `#[Requires]` (od Nette Application v3.2): +Chcete-li povolit navíc například metodu `OPTIONS`, použijte k tomu atribut `#[Requires]` (od Nette Application v3.2.3): ```php #[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] @@ -477,20 +562,20 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Ve verzi 3.1 se ověření provádí v `checkHttpMethod()`, která zjišťuje, zda je metoda specifikovaná v požadavku obsažena v poli `$presenter->allowedMethods`. Přidání metody udělejte takto: +Od verze 3.1.13 se ověření provádí v `checkHttpMethod()`, která zjišťuje, zda je metoda specifikovaná v požadavku obsažena v poli `$presenter->allowedMethods`. Od verze 3.2.3 je však tento postup zastaralý ve prospěch `#[Requires]`. Metodu můžete přepsat takto: ```php class MyPresenter extends Nette\Application\UI\Presenter { - protected function checkHttpMethod(): void - { - $this->allowedMethods[] = 'OPTIONS'; - parent::checkHttpMethod(); - } + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } } ``` -Je důležité zdůraznit, že pokud povolíte metodu `OPTIONS`, musíte ji následně také patřičně obsloužit v rámci svého presenteru. Metoda je často používána jako tzv. preflight request, který prohlížeč automaticky odesílá před skutečným požadavkem, když je potřeba zjistit, zda je požadavek povolený z hlediska CORS (Cross-Origin Resource Sharing) politiky. Pokud metodu povolíte, ale neimplementujete správnou odpověď, může to vést k nekonzistencím a potenciálním bezpečnostním problémům. +Je důležité zdůraznit, že pokud povolíte metodu `OPTIONS`, musíte ji následně také patřičně obsloužit v rámci svého presenteru. Metoda je často používána jako tzv. preflight request, který prohlížeč automaticky odesílá před skutečným požadavkem, když je potřeba zjistit, zda je požadavek povolený podle pravidel CORS (Cross-Origin Resource Sharing). Pokud metodu povolíte, ale neimplementujete správnou odpověď, může to vést k nekonzistencím a potenciálním bezpečnostním problémům. Označení zastaralých akcí .{data-version:3.2.3} diff --git a/application/cs/routing.texy b/application/cs/routing.texy index 3a2e753731..e7f69b6cb7 100644 --- a/application/cs/routing.texy +++ b/application/cs/routing.texy @@ -3,7 +3,7 @@ Routování
-Router má na starosti vše okolo URL adres, aby vy už jste nad nimi nemuseli přemýšlet. Ukážeme si: +Router má na starosti vše okolo URL adres, abyste už nad nimi nemuseli přemýšlet. Ukážeme si: - jak nastavit router, aby URL byly podle představ - povíme si o SEO a přesměrování @@ -14,15 +14,15 @@ Router má na starosti vše okolo URL adres, aby vy už jste nad nimi nemuseli p Lidštější URL (nebo taky cool či pretty URL) jsou použitelnější, zapamatovatelnější a pozitivně přispívají k SEO. Nette na to myslí a vychází vývojářům plně vstříc. Můžete si pro svou aplikaci navrhnout přesně takovou strukturu URL adres, jakou budete chtít. Můžete ji navrhnout dokonce až ve chvíli, když už je aplikace hotová, protože se to obejde bez zásahů do kódu či šablon. Definuje se totiž elegantním způsobem na jednom [jediném místě |#Začlenění do aplikace], v routeru, a není tak roztroušen ve formě anotací ve všech presenterech. -Router v Nette je mimořádný tím, že je **obousměrný.** Umí jak dekódovat URL v HTTP požadavku, tak i odkazy vytvářet. Hraje tedy zásadní roli v [Nette Application |how-it-works#Nette Application], protože jednak rozhoduje o tom, který presenter a action bude vykonávat aktuální požadavek, ale také se využívá pro [generování URL |creating-links] v šabloně atd. +Router v Nette je mimořádný tím, že je **obousměrný.** Umí jak dekódovat URL v HTTP požadavku, tak i odkazy vytvářet. Hraje tedy zásadní roli v [Nette Application |how-it-works#Nette Application], protože jednak rozhoduje o tom, který presenter a akce budou vykonávat aktuální požadavek, ale také se využívá pro [generování URL |creating-links] v šabloně atd. -Ovšem router není limitován jen pro tohle využití, můžete jej použít v aplikacích, kde se vůbec presentery nepoužívají, pro REST API, atd. Více v části [#samostatné použití]. +Ovšem router není omezen jen na tohle využití, můžete jej použít v aplikacích, kde se vůbec presentery nepoužívají, pro REST API, atd. Více v části [#samostatné použití]. Kolekce rout ============ -Nejpříjemnější způsob, jak definovat podobu URL adres v aplikaci, nabízí třída [api:Nette\Application\Routers\RouteList]. Definice je tvořena seznamem tzv. rout, tedy masek URL adres a k nim přidružených presenterů a akcí pomocí jednoduchého API. Routy nemusíme nijak pojmenovávat. +Nejpříjemnější způsob, jak definovat podobu URL adres v aplikaci, nabízí třída [api:Nette\Application\Routers\RouteList]. Definici tvoří pomocí jednoduchého API seznam tzv. rout, tedy masek URL adres a k nim přidružených presenterů a akcí. Routy nemusíme nijak pojmenovávat. ```php $router = new Nette\Application\Routers\RouteList; @@ -97,7 +97,7 @@ $router->addRoute('/', 'Home:default'); Uvedená routa akceptuje např. URL ve tvaru `/article/edit` nebo také `/catalog/list` a chápe je jako presentery a akce `Article:edit` a `Catalog:list`. -Zaroveň dává parametrům `presenter` a `action` výchozí hodnoty `Home` a `default` a jsou tedy také volitelné. Takže routa akceptuje i URL ve tvaru `/article` a chápe ji jako `Article:default`. Nebo obráceně, odkaz na `Product:default` vygeneruje cestu `/product`, odkaz na výchozí `Home:default` cestu `/`. +Zároveň dává parametrům `presenter` a `action` výchozí hodnoty `Home` a `default` a jsou tedy také volitelné. Takže routa akceptuje i URL ve tvaru `/article` a chápe ji jako `Article:default`. Nebo obráceně, odkaz na `Product:default` vygeneruje cestu `/product`, odkaz na výchozí `Home:default` cestu `/`. Maska může popisovat nejen relativní cestu od kořenového adresáře webu, ale také absolutní cestu, pokud začíná lomítkem, nebo dokonce celé absolutní URL, začíná-li dvěma lomítky: @@ -146,7 +146,7 @@ $router->addRoute('[/]', /* ... */); // /download => lang => null, name => download ``` -Když je parametr součásti volitelné sekvence, stává se pochopitelně také volitelným. Pokud nemá uvedenou výchozí hodnotu, tak bude null. +Když je parametr součástí volitelné sekvence, stává se pochopitelně také volitelným. Pokud nemá uvedenou výchozí hodnotu, tak bude null. Volitelné části mohou být i v doméně: @@ -198,7 +198,7 @@ $router->addRoute('[[/[/]]]', /* ... */); Zástupné znaky -------------- -V masce absolutní cesty můžeme použít následující zástupné znaky a vyhnout se tak např. nutnosti zapisovat do masky doménu, která se může lišit ve vývojovém a produkčním prostředí: +V masce absolutního URL můžeme použít následující zástupné znaky a vyhnout se tak např. nutnosti zapisovat do masky doménu, která se může lišit ve vývojovém a produkčním prostředí: - `%tld%` = top level domain, např. `com` nebo `org` - `%sld%` = second level domain, např. `example` @@ -208,14 +208,14 @@ V masce absolutní cesty můžeme použít následující zástupné znaky a vyh ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); -$router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('//www.%sld%.%tld%/%basePath%//', /* ... */); ``` Rozšířený zápis --------------- -Cíl routy obvykle zapisovaný ve tvaru `Presenter:action` může být také zapsát pomocí pole, které definuje jednotlivé parametry a jejich výchozí hodnoty: +Cíl routy obvykle zapisovaný ve tvaru `Presenter:action` může být také zapsán pomocí pole, které definuje jednotlivé parametry a jejich výchozí hodnoty: ```php $router->addRoute('/[/]', [ @@ -244,6 +244,16 @@ $router->addRoute('/[/]', [ Je důležité poznamenat, že pokud parametry definované v poli nejsou uvedeny v masce cesty, jejich hodnoty nelze změnit, ani pomocí query parametrů uvedených za otazníkem v URL. +Toho se využívá pro **pevné parametry** - když chceme dát konkrétní stránce krátkou, zapamatovatelnou URL. Například aby `/obchodni-podminky` vždy otevřelo `Article:view` s `id: 123`: + +```php +$router->addRoute('obchodni-podminky', [ + 'presenter' => 'Article', + 'action' => 'view', + 'id' => 123, +]); +``` + Filtry a překlady ----------------- @@ -280,7 +290,7 @@ $router->addRoute('/', [ Více klíčů překladového slovníku může vést na tentýž presenter. Tím se k němu vytvoří různé aliasy. Za kanonickou variantu (tedy tu, která bude ve vygenerovaném URL) se považuje poslední klíč. -Překladovou tabulku lze tímto způsobem použít na jakýkoliv parametr. Přičemž pokud překlad neexistuje, bere se původní hodnota. Tohle chování můžeme změnit doplněním `Route::FilterStrict => true` a routa pak odmítne URL, pokud hodnota není ve slovníku. +Překladovou tabulku lze tímto způsobem použít na jakýkoliv parametr, přičemž pokud překlad neexistuje, bere se původní hodnota. Tohle chování můžeme změnit doplněním `Route::FilterStrict => true` a routa pak odmítne URL, pokud hodnota není ve slovníku. Kromě překladového slovníku v podobě pole lze nasadit i vlastní překladové funkce. @@ -300,13 +310,13 @@ $router->addRoute('//', [ Funkce `Route::FilterIn` převádí mezi parametrem v URL a řetězcem, který se poté předá do presenteru, funkce `FilterOut` zajišťuje převod opačným směrem. -Parametry `presenter`, `action` a `module` už mají předdefinované filtry, které převádějí mezi stylem PascalCase resp. camelCase a kebab-case používaným v URL. Výchozí hodnota parametrů se zapisuje už v transformované podobě, takže třeba v případě presenteru píšeme ``, nikoliv ``. +Parametry `presenter`, `action` a `module` už mají předdefinované filtry, které převádějí mezi stylem PascalCase, resp. camelCase, a kebab-case používaným v URL. Výchozí hodnota parametrů se zapisuje v té podobě, v jaké se předává do aplikace (PascalCase u presenteru a modulu, camelCase u akce), takže třeba v případě presenteru píšeme ``, nikoliv ``. Obecné filtry ------------- -Vedle filtrů určených pro konkrétní parametry můžeme definovat též obecné filtry, které obdrží asociativní pole všech parametrů, které mohou jakkoliv modifikovat a poté je vrátí. Obecné filtry definujeme pod prázdným klíčem. +Vedle filtrů určených pro konkrétní parametry můžeme definovat též obecné filtry, které obdrží asociativní pole všech parametrů, mohou je jakkoliv upravit a poté vrátí. Obecné filtry definujeme pod prázdným klíčem. ```php use Nette\Routing\Route; @@ -323,7 +333,9 @@ $router->addRoute('/', [ Obecné filtry dávají možnost upravit chování routy naprosto jakýmkoliv způsobem. Můžeme je použít třeba pro modifikaci parametrů na základě jiných parametrů. Například přeložení `` a `` na základě aktuální hodnoty parametru ``. -Pokud má parametr definovaný vlastní filtr a současně existuje obecný filtr, provede se vlastní `FilterIn` před obecným a naopak obecný `FilterOut` před vlastním. Tedy uvnitř obecného filtru jsou hodnoty parametrů `presenter` resp. `action` zapsané ve stylu PascalCase resp. camelCase. +Pokud má parametr definovaný vlastní filtr a současně existuje obecný filtr, provede se vlastní `FilterIn` před obecným a naopak obecný `FilterOut` před vlastním. Tedy uvnitř obecného filtru jsou hodnoty parametrů `presenter`, resp. `action`, zapsané ve stylu PascalCase, resp. camelCase. + +Praktické využití těchto filtrů, tedy generování SEO-friendly URL typu `/clanek/123-jak-upect-chleba` bez zásahu do šablon, najdete v návodu [Hezké URL se slugem |best-practices:pretty-urls]. Jednosměrky OneWay @@ -333,7 +345,7 @@ Jednosměrné routy se používají pro zachování funkčnosti starých URL, kt ```php // staré URL /product-info?id=123 -$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); +$router->addRoute('product-info', 'Product:detail', oneWay: true); // nové URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` @@ -363,11 +375,19 @@ $router->addRoute('', function (string $lang) { }); ``` +Kromě parametrů z masky může callback přijmout také služby z DI kontejneru. Ty se předají podle typu parametru. Navíc parametr `$presenter` dostane instanci [MicroPresenteru |api:NetteModule\MicroPresenter], který routu zpracovává: + +```php +$router->addRoute('', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) { + // ... +}); +``` + Moduly ------ -Pokud máme více rout, které spadají do společného [modulu |directory-structure#Presentery a šablony], využijeme `withModule()`: +Pokud máme více rout, které spadají do společného [modulu |directory-structure#Presentery a šablony], využijeme `withModule()`. Uvedený modul se automaticky předřadí presenteru u každé routy ve skupině a z URL úplně zmizí: ```php $router = new RouteList; @@ -379,7 +399,7 @@ $router->withModule('Forum') // následující routy jsou součástí modulu For ->addRoute('sign:in', 'Sign:in'); ``` -Alternativou je použití parametru `module`: +Alternativou je parametr `module`, který rovněž nastaví pevný modul a drží ho mimo URL: ```php // URL manage/dashboard/default se mapuje na presenter Admin:Dashboard @@ -388,6 +408,12 @@ $router->addRoute('manage//', [ ]); ``` +Název presenteru je úplný teprve i se svým modulem, např. `Front:Admin:ProductList`. Kdykoli se takový celý název ocitne v parametru URL, router jej zakóduje dvěma jednoduchými pravidly: každá dvojtečka `:` (oddělovač modulů) se stane **tečkou** a každý předěl mezi slovy v PascalCase názvu se stane **pomlčkou**. Takže `Front:Admin:ProductList` se v URL zapíše jako `front.admin.product-list` a stejně se i dekóduje zpět. Právě proto modulární aplikace bez některého z výše uvedených nástrojů tvoří URL plné teček. + +`withModule()` i parametr `module` se tomu vyhnou právě tím, že známý prefix modulu z názvu presenteru odříznou dřív, než se dostane do URL: modul je konstanta, takže ho není potřeba nijak kódovat. + +Někdy chceme, aby se modul sám měnil a objevoval se v URL, a tak sáhneme po `` přímo v masce. Pozor ale na jednu zásadní věc: **`` pohltí celou modulovou cestu** - vše až po poslední dvojtečku v názvu presenteru. U presenteru `Shop:Admin:Product` je to modul `Shop:Admin` a presenter `Product`, a protože se dvojtečky mění na tečky, dostaneme: + Subdomény --------- @@ -501,7 +527,7 @@ services: - App\Core\RouterFactory::createRouter ``` -Jakékoliv závislosti, třeba na databázi atd, se předají tovární metodě jako její parametry pomocí [autowiringu|dependency-injection:autowiring]: +Jakékoliv závislosti, třeba na databázi atd., se předají tovární metodě jako její parametry pomocí [autowiringu|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -522,7 +548,7 @@ Generuje adresy zhruba v tomto tvaru: http://example.com/?presenter=Product&action=detail&id=123 ``` -Parametrem konstruktoru SimpleRouteru je výchozí presenter & akce, na který se má směřovat, pokud otevřeme stránku bez parametrů, např. `http://example.com/`. +Parametrem konstruktoru SimpleRouteru je výchozí presenter a akce, na které se má směřovat, pokud otevřeme stránku bez parametrů, např. `http://example.com/`. ```php // výchozím presenterem bude 'Home' a akce 'default' @@ -542,7 +568,7 @@ SEO a kanonizace Framework přispívá k SEO (optimalizaci nalezitelnosti na internetu) tím, že zabraňuje duplicitě obsahu na různých URL. Pokud k určitému cíli vede více adres, např. `/index` a `/index.html`, framework první z nich určí za primární (kanonickou) a ostatní na ni přesměruje pomocí HTTP kódu 301. Díky tomu vám vyhledávače stránky nezaindexují dvakrát a nerozmělní jejich page rank. -Tomuto procesu se říká kanonizace. Kanonickou URL je ta, kterou vygeneruje router, tj. první vyhovující routa v kolekci bez příznaku OneWay. Proto v kolekci uvádíme **primární routy jako první**. +Tomuto procesu se říká kanonizace. Kanonická URL je ta, kterou vygeneruje router, tj. první vyhovující routa v kolekci bez příznaku OneWay. Proto v kolekci uvádíme **primární routy jako první**. Kanonizaci provádí presenter, více v kapitole [kanonizace |presenters#Kanonizace]. @@ -564,7 +590,7 @@ Přesměrování celého webu na HTTPS je nutné nastavit na úrovni serveru, na ``` -Router generuje URL se stejným protokolem, s jakým byla stránka načtena, takže nic víc není pořeba nastavovat. +Router generuje URL se stejným protokolem, s jakým byla stránka načtena, takže nic víc není potřeba nastavovat. Pokud ale výjimečně potřebujeme, aby různé routy běžely pod různými protokoly, uvedeme ho v masce routy: @@ -582,14 +608,14 @@ Ladění routeru Routovací panel zobrazující se v [Tracy Baru |tracy:] je užitečným pomocníkem, který zobrazuje seznam rout a také parametrů, které router získal z URL. -Zelený pruh se symbolem ✓ představuje routu, která zpracovala aktuální URL, modrou barvou a symbolem ≈ jsou označené routy, které by také URL zpracovaly, kdyby je zelená nepředběhla. Dále vidíme aktuální presenter & akci. +Zelený pruh se symbolem ✓ představuje routu, která zpracovala aktuální URL, modrou barvou a symbolem ≈ jsou označené routy, které by také URL zpracovaly, kdyby je zelená nepředběhla. Dále vidíme aktuální presenter a akci. [* routing-debugger.webp *] Zároveň pokud dojde k neočekávanému přesměrování kvůli [kanonizaci |#SEO a kanonizace], je užitečné se podívat do panelu v liště *redirect*, kde zjistíte, jak router URL původně pochopil a proč přesměroval. .[note] -Při ladění routeru doporučujeme otevřít v prohlížeči Developer Tools (Ctrl+Shift+I nebo Cmd+Option+I) a v panelu Network vypnout cache, aby se do ní neukládaly přesměrování. +Při ladění routeru doporučujeme otevřít v prohlížeči Developer Tools (Ctrl+Shift+I nebo Cmd+Option+I) a v panelu Network vypnout cache, aby se do ní neukládala přesměrování. Výkonnost @@ -628,7 +654,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Metoda `match` zpracuje aktuální požadavek [$httpRequest |http:request], ze kterého lze získat nejen URL, ale i hlavičky atd., do pole obsahující název presenteru a jeho parametry. Pokud požadavek zpracovat neumí, vrátí null. Při zpracování požadavku musíme vrátit minimálně presenter a akci. Název presenteru je úplný a obsahuje i případné moduly: +Metoda `match` zpracuje aktuální požadavek [$httpRequest |http:request], ze kterého lze získat nejen URL, ale i hlavičky atd., do pole obsahujícího název presenteru a jeho parametry. Pokud požadavek zpracovat neumí, vrátí null. Při zpracování požadavku musíme vrátit minimálně presenter; akce je volitelná a pokud ji neuvedeme, nastaví se na `default`. Název presenteru je úplný a obsahuje i případné moduly: ```php [ @@ -682,7 +708,7 @@ class RouterFactory } ``` -Pokud používáte DI kontejner, což doporučujeme, opět metodu přidáme do konfigurace a poté router společne s HTTP požadavkem získáme z kontejneru: +Pokud používáte DI kontejner, což doporučujeme, opět metodu přidáme do konfigurace a poté router společně s HTTP požadavkem získáme z kontejneru: ```php $router = $container->getByType(Nette\Routing\Router::class); @@ -718,4 +744,6 @@ $url = $router->constructUrl($params, $httpRequest->getUrl()); ``` -{{composer: nette/router}} +{{composer: nette/routing}} +{{repo: nette/routing}} +{{api: https://api.nette.org/routing/}} diff --git a/application/cs/templates.texy b/application/cs/templates.texy index 568b40837e..b5ae830d67 100644 --- a/application/cs/templates.texy +++ b/application/cs/templates.texy @@ -31,7 +31,7 @@ A tohle bude šablona akce: {/block} ``` -Ta definuje blok `content`, který se vloží na místo `{include content}` v layoutu, a také re-definuje blok `title`, kterým přepíše `{block title}` v layoutu. Zkuste si představit výsledek. +Ta definuje blok `content`, který se vloží na místo `{include content}` v layoutu, a také předefinuje blok `title`, kterým přepíše `{block title}` v layoutu. Zkuste si představit výsledek. Hledání šablon @@ -39,7 +39,7 @@ Hledání šablon Nemusíte v presenterech uvádět, jaká šablona se má vykreslit, framework cestu odvodí sám a ušetří vám psaní. -Pokud používáte adresářovou strukturu, kde každý presenter má vlastní adresář, jednodušše umístěte šablonu do tohoto adresáře pod jménem akce (resp. view), tj. pro akci `default` použijte šablonu `default.latte`: +Pokud používáte adresářovou strukturu, kde každý presenter má vlastní adresář, jednoduše umístěte šablonu do tohoto adresáře pod jménem akce (resp. view), tj. pro akci `default` použijte šablonu `default.latte`: /--pre app/ @@ -49,16 +49,16 @@ app/ └── default.latte \-- -Pokud používáte strukturu, kde jsou společně presentery v jednom adresáři a šablony ve složce `templates`, uložte ji buď do souboru `..latte` nebo `/.latte`: +Pokud používáte strukturu, kde jsou společně presentery v jednom adresáři a šablony ve složce `templates`, uložte ji buď do souboru `..latte`, nebo `/.latte`: /--pre app/ └── Presenters/ ├── HomePresenter.php └── templates/ - ├── Home.default.latte ← 1. varianta - └── Home/ - └── default.latte ← 2. varianta + ├── Home/ + │ └── default.latte ← 1. varianta + └── Home.default.latte ← 2. varianta \-- Adresář `templates` může být umístěn také o úroveň výš, tj. na stejné úrovni, jako je adresář s třídami presenterů. @@ -96,9 +96,9 @@ app/ ├── HomePresenter.php └── templates/ ├── @layout.latte ← společný layout - ├── Home.@layout.latte ← jen pro Home, 1. varianta - └── Home/ - └── @layout.latte ← jen pro Home, 2. varianta + ├── Home/ + │ └── @layout.latte ← jen pro Home, 1. varianta + └── Home.@layout.latte ← jen pro Home, 2. varianta \-- Pokud se presenter nachází v modulu, bude se dohledávat i o další adresářové úrovně výš, podle zanoření modulu. @@ -120,7 +120,7 @@ Proměnné do šablony předáváme zápisem do `$this->template`. V šabloně j $this->template->article = $this->articles->getById($id); ``` -Pokud chcete, aby se hodnota určité property automaticky předala do šablony jako proměnná, označte ji atributem `#[TemplateVariable]` a viditelností public nebo protected: .{data-version:3.2.9} +Pokud chcete, aby se hodnota určité property automaticky předala do šablony jako proměnná, označte ji atributem `#[TemplateVariable]` a viditelností public: .{data-version:3.2.9} ```php use Nette\Application\Attributes\TemplateVariable; @@ -145,7 +145,7 @@ Presentery a komponenty předávají do šablon několik užitečných proměnn - `$user` je objekt [reprezentující uživatele |security:authentication] - `$presenter` je aktuální presenter - `$control` je aktuální komponenta nebo presenter -- `$flashes` pole [zpráv |presenters#Flash zprávy] zaslaných funkcí `flashMessage()` +- `$flashes` je pole [zpráv |presenters#Flash zprávy] zaslaných funkcí `flashMessage()` Pokud používáte vlastní třídu šablony, tyto proměnné se předají, pokud pro ně vytvoříte property. @@ -176,11 +176,13 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template Objekt `$this->template` v presenteru bude nyní instancí třídy `ArticleTemplate`. PHP tak bude při zápisu kontrolovat deklarované typy. -Anotace `@property-read` slouží pro IDE a statickou analýzu, díky ní bude fungovat našeptávání, viz "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Nette volí třídu šablony automaticky. Nejprve hledá třídu s názvem `Template`, např. `ArticleEditTemplate` pro akci `edit`, a teprve pokud neexistuje, použije `Template`. + +Anotace `@property-read` slouží IDE a statické analýze, díky ní bude fungovat našeptávání, viz "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Našeptávání můžete využít i přímo v šablonách. Stačí do PhpStorm nainstalovat plugin pro Latte a uvést na začátek šablony název třídy parametrů šablony, více v článku "Latte: jak na typový systém":https://blog.nette.org/cs/latte-jak-na-typovy-system: +Našeptávání můžete využít i přímo v šablonách. Stačí do PhpStorm nainstalovat plugin pro Latte a uvést na začátek šablony název třídy parametrů šablony, více v kapitole [Latte: typový systém |latte:type-system]: ```latte {templateType App\Presentation\Article\ArticleTemplate} @@ -201,11 +203,22 @@ public function renderDefault(): void } ``` +.{data-version:3.3.0} +Pokud potřebujete ovlivnit, jak se šablona před vykreslením dokončuje - například doplnit proměnné společné všem akcím - můžete v presenteru přepsat metodu `completeTemplate()`. Volá se těsně před vykreslením šablony: + +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'Můj blog'; +} +``` + Vytváření odkazů ---------------- -V šabloně se vytvářejí odkazy na další presentery & akce tímto způsobem: +V šabloně se vytvářejí odkazy na další presentery a akce tímto způsobem: ```latte detail produktu @@ -233,7 +246,7 @@ Nejrychlejší způsob je přidat filtr nebo funkci přímo v kódu presenteru protected function beforeRender(): void { // přidání filtru - $this->template->addFilter('money', fn($val) => round($val) . ' Kč'); + $this->template->addFilter('money', fn($val) => number_format($val, 2) . ' Kč'); // přidání funkce $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); @@ -268,18 +281,18 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template #[Latte\Attributes\TemplateFilter] public function money(float $val): string { - return round($val) . ' Kč'; + return number_format($val, 2) . ' Kč'; } #[Latte\Attributes\TemplateFunction] public function isWeekend(DateTimeInterface $date): bool { - return $date->format('N') >= 6 + return $date->format('N') >= 6; } } ``` -Latte automaticky rozpozná a zaregistruje metody označené těmito atributy. Název filtru nebo funkce v šabloně odpovídá názvu metody. Tyto metody nesmí být privátní. +Latte automaticky rozpozná a zaregistruje metody označené těmito atributy. Název filtru nebo funkce v šabloně odpovídá názvu metody. Tyto metody musí být public. **Globálně pomocí Extension** @@ -332,13 +345,25 @@ latte: - App\Presentation\Accessory\LatteExtension ``` -Výhodou extension je, že lze využít dependency injection, mít přístup k modelové vrstvě aplikace a všechna rozšíření mít přehledně na jednom místě. Extension umožnuje definovat i vlastní značky, providery, průchody pro Latte kompilátor a další. +Výhodou extension je, že lze využít dependency injection, mít přístup k modelové vrstvě aplikace a všechna rozšíření mít přehledně na jednom místě. Extension umožňuje definovat i vlastní značky, providery, průchody pro Latte kompilátor a další. + + +Nastavení všech šablon +---------------------- + +Služba `TemplateFactory`, která vytváří všechny šablony, nabízí veřejné pole callbacků `$onCreate`. Ty se zavolají při vytvoření každé šablony, takže filtry, funkce nebo proměnné pro všechny šablony v aplikaci můžete nastavit z jednoho místa. Každý callback dostane nově vytvořenou šablonu. Službu `TemplateFactory` si [necháte předat |dependency-injection:passing-dependencies] a její callbacky zaregistrujete např. při startu aplikace: + +```php +$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void { + $template->addFilter('money', fn($val) => number_format($val, 2) . ' Kč'); +}; +``` Překládání ---------- -Pokud programujete vícejazyčnou aplikaci, budete nejspíš potřebovat některé texty v šabloně vypsat v různých jazycích. Nette Framework k tomuto účelu definuje rozhraní pro překlad [api:Nette\Localization\Translator], které má jedinou metodu `translate()`. Ta přijímá zprávu `$message`, což zpravidla bývá řetězec, a libovolné další parametry. Úkolem je vrátit přeložený řetězec. V Nette není žádná výchozí implementace, můžete si vybrat podle svých potřeb z několika hotových řešeních, které najdete na [Componette |https://componette.org/search/localization]. V jejich dokumentaci se dozvíte, jak translator konfigurovat. +Pokud programujete vícejazyčnou aplikaci, budete nejspíš potřebovat některé texty v šabloně vypsat v různých jazycích. Nette Framework k tomuto účelu definuje rozhraní pro překlad [api:Nette\Localization\Translator], které má jedinou metodu `translate()`. Ta přijímá zprávu `$message`, což zpravidla bývá řetězec, a libovolné další parametry. Úkolem je vrátit přeložený řetězec. V Nette není žádná výchozí implementace, můžete si vybrat podle svých potřeb z několika hotových řešení, která najdete na [Componette |https://componette.org/search/localization]. V jejich dokumentaci se dozvíte, jak translator konfigurovat. Šablonám lze nastavit překladač, který si [necháme předat |dependency-injection:passing-dependencies], metodou `setTranslator()`: diff --git a/application/cs/upgrading.texy b/application/cs/upgrading.texy new file mode 100644 index 0000000000..d4b144c738 --- /dev/null +++ b/application/cs/upgrading.texy @@ -0,0 +1,47 @@ +Upgrade +******* + + +Upgrade na verzi 3.0 +==================== + +Nette 3.0 doplňuje typehinty k parametrům a návratovým hodnotám metod. Pokud takovou metodu přepisujete ve třídě zděděné z Nette (například v presenteru nebo komponentě), musíte doplnit stejné typehinty, jinak PHP vyhodí chybu "Declaration must be compatible". + +Rozhraní `Nette\Application\IRouter` bylo změněno. Metoda `match()` nyní vrací a `constructUrl()` přijímá pole parametrů namísto objektu `Nette\Application\Request`. + +Nette nyní kontroluje, zda je každý signál odeslán ze stejného originu (tj. ze stejné domény a subdomény). Tato same-origin policy je kritický bezpečnostní mechanismus, který pomáhá omezit možné vektory útoku. Pokud chcete povolit další původy, přidejte k metodě obsluhující signál anotaci `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXy(): void +{ +} +``` + +To platí také pro odesílání formulářů. Pokud chcete povolit odeslání z jiného původu, postupujte takto: + +```php +$form = new Nette\Application\UI\Form; +$form->allowCrossOrigin(); +``` + +Konstruktor `Nette\ComponentModel\Component` nebyl roky používán a byl odstraněn ve verzi 3.0. Je to BC break: pokud voláte rodičovský konstruktor v komponentě nebo presenteru zděděném od `Nette\Application\UI\Presenter`, musíte volání odstranit. + + +Upgrade na verzi 2.4 +==================== + +- `Route` i `SimpleRouter` nyní generují v URL stejné HTTP/HTTPS schéma, s jakým se přistupuje ke stránce. Routu, která vyžaduje určitý protokol, lze zapsat se schématem, např. `Route('http://domain.cz/')`. +- U parametrů typu bool použitých v render/action metodách (tj. s výchozí hodnotou true nebo false) a u persistentních parametrů se nyní rozlišuje mezi false a null. Pokud parametr v URL není uveden, jeho hodnota je nyní `null` (dříve `false`). +- Třída, kterou vrací `Presenter::getReflection()`, již není potomkem `Nette\Reflection\ClassType` a rovněž `getReflection()->getMethod()` není potomkem `Nette\Reflection\Method`. +- Flag `SECURED` a `Route::$defaultFlags` jsou zastaralé. + + +Upgrade na verzi 2.3 +==================== + +- routy a názvy presenterů jsou **case-sensitive**. Nette vás upozorní, pokud v názvu presenteru použijete špatnou velikost písmen; masku routy z výkonnostních důvodů nekontroluje, ověřte si ji tedy ručně. +- `Route::addStyle()` a `Route::setStyleProperty()` jsou zastaralé a nyní vyvolají `E_USER_DEPRECATED`. +- přípona šablony `.phtml` a stará syntaxe odkazů se již nepodporují. diff --git a/application/de/@home.texy b/application/de/@home.texy index a151863801..65ca4bdcc0 100644 --- a/application/de/@home.texy +++ b/application/de/@home.texy @@ -2,13 +2,13 @@ Nette Application ***************** .[perex] -Nette Application ist der Kern des Nette Frameworks und bietet leistungsstarke Werkzeuge zur Erstellung moderner Webanwendungen. Es bietet eine Reihe außergewöhnlicher Eigenschaften, die die Entwicklung erheblich vereinfachen und die Sicherheit sowie die Wartbarkeit des Codes verbessern. +Nette Application ist der Kern des Nette Frameworks und bietet leistungsstarke Werkzeuge für die Erstellung moderner Webanwendungen. Es bietet eine Reihe außergewöhnlicher Eigenschaften, die die Entwicklung erheblich vereinfachen und die Sicherheit sowie die Wartbarkeit des Codes verbessern. Installation ------------ -Laden Sie die Bibliothek herunter und installieren Sie sie mit [Composer|best-practices:composer]: +Laden Sie die Bibliothek mit [Composer|best-practices:composer] herunter und installieren Sie sie: ```shell composer require nette/application @@ -18,68 +18,68 @@ composer require nette/application Warum Nette Application wählen? ------------------------------- -Nette war schon immer ein Pionier im Bereich der Webtechnologien. +Nette war schon immer ein Vorreiter bei Webtechnologien. -**Bidirektionaler Router:** Nette verfügt über ein fortschrittliches Routing-System, das durch seine Bidirektionalität einzigartig ist – es übersetzt nicht nur URLs in Anwendungsaktionen, sondern kann auch rückwirkend URL-Adressen generieren. Das bedeutet: -- Sie können jederzeit die URL-Struktur der gesamten Anwendung ändern, ohne die Templates anpassen zu müssen -- URLs werden automatisch kanonisiert, was die SEO verbessert -- Das Routing wird an einer Stelle definiert, nicht verstreut in Annotationen +**Bidirektionaler Router:** Nette verfügt über ein fortschrittliches Routing-System, das in seiner Bidirektionalität einzigartig ist - es übersetzt nicht nur URLs in Aktionen der Anwendung, sondern kann URLs auch umgekehrt erzeugen. Das bedeutet: +- Sie können die URL-Struktur der gesamten Anwendung jederzeit ändern, ohne die Templates anpassen zu müssen +- URLs werden automatisch kanonisiert, was das SEO verbessert +- Das Routing ist an einer einzigen Stelle definiert, nicht über Annotationen verstreut -**Komponenten und Signale:** Das integrierte Komponentensystem, inspiriert von Delphi und React.js, ist unter PHP-Frameworks völlig einzigartig: -- Ermöglicht die Erstellung wiederverwendbarer UI-Elemente -- Unterstützt die hierarchische Zusammensetzung von Komponenten -- Bietet eine elegante Verarbeitung von AJAX-Anfragen mittels Signalen +**Komponenten und Signale:** Das eingebaute Komponentensystem, inspiriert von Delphi und React.js, ist unter den PHP-Frameworks einzigartig: +- Es ermöglicht, wiederverwendbare UI-Elemente zu erstellen +- Es unterstützt die hierarchische Zusammensetzung von Komponenten +- Es bietet eine elegante Behandlung von AJAX-Requests mithilfe von Signalen - Umfangreiche Bibliothek fertiger Komponenten auf [Componette](https://componette.org) -**AJAX und Snippets:** Nette führte bereits 2009 eine revolutionäre Methode zur Arbeit mit AJAX ein, lange vor ähnlichen Lösungen wie Hotwire für Ruby on Rails oder Symfony UX Turbo: -- Snippets ermöglichen die Aktualisierung nur von Teilen der Seite, ohne JavaScript schreiben zu müssen +**AJAX und Snippets:** Nette führte 2009 eine revolutionäre Art der Arbeit mit AJAX ein, lange vor ähnlichen Lösungen wie Hotwire für Ruby on Rails oder Symfony UX Turbo: +- Snippets erlauben es, nur Teile der Seite zu aktualisieren, ohne JavaScript schreiben zu müssen - Automatische Integration mit dem Komponentensystem -- Intelligente Invalidierung von Seitenteilen -- Minimale Menge an übertragenen Daten +- Intelligente Invalidierung von Seitenabschnitten +- Minimale Datenübertragung -**Intuitive Templates [Latte|latte:]:** Das sicherste Template-System für PHP mit erweiterten Funktionen: -- Automatischer Schutz vor XSS durch kontextsensitives Escaping -- Erweiterbarkeit durch eigene Filter, Funktionen und Tags +**Intuitive [Latte|latte:]-Templates:** Das sicherste Templating-System für PHP mit fortschrittlichen Funktionen: +- Automatischer XSS-Schutz durch kontextsensitives Escaping +- Erweiterbar um eigene Filter, Funktionen und Tags - Template-Vererbung und Snippets für AJAX -- Hervorragende Unterstützung für PHP 8.x mit Typsystem +- Ausgezeichnete Unterstützung für PHP 8.x samt Typsystem -**Dependency Injection:** Nette nutzt Dependency Injection vollständig aus: +**Dependency Injection:** Nette nutzt Dependency Injection voll aus: - Automatische Übergabe von Abhängigkeiten (Autowiring) -- Konfiguration mittels übersichtlichem NEON-Format +- Konfiguration im übersichtlichen NEON-Format - Unterstützung für Komponenten-Factories Hauptvorteile ------------- -- **Sicherheit**: Automatischer Schutz vor [Schwachstellen|nette:vulnerability-protection] wie XSS, CSRF, etc. -- **Produktivität**: Weniger schreiben, mehr Funktionen dank cleverem Design +- **Sicherheit**: Automatischer Schutz vor [Sicherheitslücken|nette:vulnerability-protection] wie XSS, CSRF usw. +- **Produktivität**: Weniger Schreibarbeit, mehr Funktionen dank durchdachtem Design - **Debugging**: [Tracy Debugger|tracy:] mit Routing-Panel - **Leistung**: Intelligenter Cache, Lazy Loading von Komponenten -- **Flexibilität**: Einfache Anpassung von URLs auch nach Fertigstellung der Anwendung +- **Flexibilität**: Einfache Änderung der URLs auch nach Fertigstellung der Anwendung - **Komponenten**: Einzigartiges System wiederverwendbarer UI-Elemente -- **Modern**: Volle Unterstützung für PHP 8.4+ und Typsystem +- **Modern**: Volle Unterstützung für PHP 8.3+ und das Typsystem Erste Schritte -------------- -1. [Wie Anwendungen funktionieren |how-it-works] - Verständnis der grundlegenden Architektur +1. [Wie funktionieren Anwendungen? |how-it-works] - Verständnis der grundlegenden Architektur 2. [Presenter |presenters] - Arbeit mit Presentern und Aktionen -3. [Templates |templates] - Erstellung von Templates in Latte -4. [Routing |routing] - Konfiguration von URL-Adressen -5. [Interaktive Komponenten |components] - Nutzung des Komponentensystems +3. [Templates |templates] - Templates in Latte erstellen +4. [Routing |routing] - URL-Adressen konfigurieren +5. [Interaktive Komponenten |components] - Das Komponentensystem verwenden PHP-Kompatibilität ------------------ -| Version | kompatibel mit PHP -|-------------------|------------------- -| Nette Application 4.0 | PHP 8.1 – 8.4 -| Nette Application 3.2 | PHP 8.1 – 8.4 -| Nette Application 3.1 | PHP 7.2 – 8.3 -| Nette Application 3.0 | PHP 7.1 – 8.0 -| Nette Application 2.4 | PHP 5.6 – 8.0 +| Version | kompatibel mit PHP +|-----------------------|------------------- +| Nette Application 3.3 | PHP 8.3 - 8.5 +| Nette Application 3.2 | PHP 8.1 - 8.5 +| Nette Application 3.1 | PHP 7.2 - 8.3 +| Nette Application 3.0 | PHP 7.1 - 8.0 +| Nette Application 2.4 | PHP 5.6 - 8.0 -Gilt für die letzte Patch-Version. +Gilt für die jeweils letzten Patch-Versionen. diff --git a/application/de/@left-menu.texy b/application/de/@left-menu.texy index 2a16b701bb..c9bd5699cf 100644 --- a/application/de/@left-menu.texy +++ b/application/de/@left-menu.texy @@ -1,22 +1,24 @@ Nette Application ***************** -- [Wie Anwendungen funktionieren |how-it-works] -- [Bootstrapping] -- [Presenter |presenters] -- [Templates |templates] +- [Übersicht |@home] +- [Wie funktionieren Anwendungen? |how-it-works] +- [Bootstrapping|bootstrapping] +- [Presenter|presenters] +- [Templates|templates] - [Verzeichnisstruktur |directory-structure] -- [Routing |routing] -- [Erstellen von URL-Links |creating-links] +- [Routing|routing] +- [URL-Links erstellen |creating-links] - [Interaktive Komponenten |components] - [AJAX & Snippets |ajax] -- [Multiplier |Multiplier] -- [Konfiguration |configuration] +- [Multiplier |multiplier] +- [Konfiguration|configuration] +- [Upgrade|upgrading] -Weitere Lektüre -*************** -- [Warum Nette verwenden? |www:10-reasons-why-nette] +Weiterführende Lektüre +********************** +- [Warum Nette verwenden?|www:10-reasons-why-nette] - [Installation |nette:installation] -- [Schreiben wir die erste Anwendung! |quickstart:] -- [Anleitungen und Verfahren |best-practices:] +- [Erstellen Sie Ihre erste Anwendung! |quickstart:] +- [Best Practices |best-practices:] - [Fehlerbehebung |nette:troubleshooting] diff --git a/application/de/ajax.texy b/application/de/ajax.texy index 87844ef98b..5fa29383e4 100644 --- a/application/de/ajax.texy +++ b/application/de/ajax.texy @@ -3,20 +3,20 @@ AJAX & Snippets
-In der Ära moderner Webanwendungen, in der die Funktionalität oft zwischen Server und Browser aufgeteilt ist, ist AJAX ein unverzichtbares Bindeglied. Welche Möglichkeiten bietet uns das Nette Framework in diesem Bereich? +Im Zeitalter moderner Webanwendungen, in dem die Funktionalität oft zwischen Server und Browser aufgeteilt ist, ist AJAX das unverzichtbare Bindeglied. Welche Möglichkeiten bietet uns das Nette Framework in diesem Bereich? - Senden von Teilen des Templates, sogenannten Snippets - Übergabe von Variablen zwischen PHP und JavaScript -- Tools zum Debuggen von AJAX-Anfragen +- Werkzeuge zum Debuggen von AJAX-Requests
-AJAX-Anfrage +AJAX-Request ============ -Eine AJAX-Anfrage unterscheidet sich im Grunde nicht von einer klassischen HTTP-Anfrage. Ein Presenter wird mit bestimmten Parametern aufgerufen. Und es liegt am Presenter, wie er auf die Anfrage reagiert – er kann Daten im JSON-Format zurückgeben, einen Teil des HTML-Codes senden, ein XML-Dokument usw. +Ein AJAX-Request unterscheidet sich grundsätzlich nicht von einem klassischen HTTP-Request. Es wird ein Presenter mit bestimmten Parametern aufgerufen. Es liegt am Presenter, wie er auf den Request antwortet - er kann Daten im JSON-Format zurückgeben, ein Stück HTML-Code senden, ein XML-Dokument usw. -Auf der Browserseite initialisieren wir die AJAX-Anfrage mit der Funktion `fetch()`: +Auf der Browserseite starten wir einen AJAX-Request mit der Funktion `fetch()`: ```js fetch(url, { @@ -24,22 +24,22 @@ fetch(url, { }) .then(response => response.json()) .then(payload => { - // Verarbeitung der Antwort + // die Antwort verarbeiten }); ``` -Auf der Serverseite erkennen wir eine AJAX-Anfrage mit der Methode `$httpRequest->isAjax()` des [die HTTP-Anfrage kapselnden |http:request] Dienstes. Zur Erkennung verwendet sie den HTTP-Header `X-Requested-With`, daher ist es wichtig, diesen mitzusenden. Innerhalb des Presenters kann die Methode `$this->isAjax()` verwendet werden. +Auf der Serverseite wird ein AJAX-Request an der Methode `$httpRequest->isAjax()` des Services erkannt, der [den HTTP-Request kapselt |http:request]. Zur Erkennung dient der HTTP-Header `X-Requested-With`, es ist daher wesentlich, ihn mitzusenden. Innerhalb des Presenters können Sie die Methode `$this->isAjax()` verwenden. -Wenn Sie Daten im JSON-Format senden möchten, verwenden Sie die Methode [`sendJson()` |presenters#Senden der Antwort]. Die Methode beendet auch die Aktivität des Presenters. +Wenn Sie Daten im JSON-Format senden wollen, verwenden Sie die Methode [`sendJson()` |presenters#Eine Antwort senden]. Die Methode beendet zugleich die Tätigkeit des Presenters. ```php public function actionExport(): void { - $this->sendJson($this->model->getData); + $this->sendJson($this->model->getData()); } ``` -Wenn Sie planen, mit einem speziellen Template für AJAX zu antworten, können Sie dies wie folgt tun: +Wenn Sie mit einem speziellen, für AJAX gedachten Template antworten wollen, geht das so: ```php public function handleClick($param): void @@ -55,38 +55,38 @@ public function handleClick($param): void Snippets ======== -Das stärkste Mittel, das Nette zur Verbindung von Server und Client bietet, sind Snippets. Dank ihnen können Sie eine gewöhnliche Anwendung mit minimalem Aufwand und wenigen Codezeilen in eine AJAX-Anwendung verwandeln. Wie das Ganze funktioniert, demonstriert das Beispiel Fifteen, dessen Code Sie auf [GitHub |https://github.com/nette-examples/fifteen] finden. +Das mächtigste Werkzeug, das Nette für die Verbindung von Server und Client bietet, sind Snippets. Mit ihnen verwandeln Sie eine gewöhnliche Anwendung mit minimalem Aufwand und wenigen Zeilen Code in eine AJAX-Anwendung. Wie das alles funktioniert, zeigt das Beispiel Fifteen, dessen Code Sie auf [GitHub |https://github.com/nette-examples/fifteen] finden. -Snippets, oder Ausschnitte, ermöglichen es, nur Teile der Seite zu aktualisieren, anstatt die gesamte Seite neu zu laden. Dies ist nicht nur schneller und effizienter, sondern bietet auch eine komfortablere Benutzererfahrung. Snippets erinnern vielleicht an Hotwire für Ruby on Rails oder Symfony UX Turbo. Interessanterweise hat Nette Snippets bereits 14 Jahre früher eingeführt. +Snippets erlauben es, nur Teile der Seite zu aktualisieren, statt die ganze Seite neu zu laden. Das ist nicht nur schneller und effizienter, sondern bietet auch ein angenehmeres Benutzererlebnis. Snippets erinnern Sie vielleicht an Hotwire für Ruby on Rails oder Symfony UX Turbo. Interessanterweise hat Nette die Snippets 14 Jahre früher eingeführt. -Wie funktionieren Snippets? Beim ersten Laden der Seite (nicht-AJAX-Anfrage) wird die gesamte Seite einschließlich aller Snippets geladen. Wenn der Benutzer mit der Seite interagiert (z. B. auf einen Button klickt, ein Formular absendet usw.), wird anstelle des Ladens der gesamten Seite eine AJAX-Anfrage ausgelöst. Der Code im Presenter führt die Aktion aus und entscheidet, welche Snippets aktualisiert werden müssen. Nette rendert diese Snippets und sendet sie in Form eines Arrays im JSON-Format. Der Verarbeitungscode im Browser fügt die empfangenen Snippets wieder in die Seite ein. Es wird also nur der Code der geänderten Snippets übertragen, was Bandbreite spart und das Laden im Vergleich zur Übertragung des gesamten Seiteninhalts beschleunigt. +Wie funktionieren Snippets? Beim ersten Laden der Seite (einem Nicht-AJAX-Request) wird die gesamte Seite samt allen Snippets geladen. Wenn der Benutzer mit der Seite interagiert (z. B. auf eine Schaltfläche klickt, ein Formular absendet usw.), wird statt des Neuladens der ganzen Seite ein AJAX-Request ausgelöst. Der Code im Presenter führt die Aktion aus und entscheidet, welche Snippets aktualisiert werden müssen. Nette rendert diese Snippets und sendet sie als JSON-Payload, das ein Array mit den Snippets enthält. Der Code im Browser fügt die empfangenen Snippets anschließend wieder in die Seite ein. Es wird also nur der Code der geänderten Snippets übertragen, was Bandbreite spart und das Laden gegenüber der Übertragung des gesamten Seiteninhalts beschleunigt. Wird kein Snippet mit `redrawControl()` invalidiert, gibt Nette auch bei einem AJAX-Request die gesamte Seite zurück - Snippets werden nur dann gesendet, wenn etwas invalidiert wurde. Naja ---- -Zur Verarbeitung von Snippets auf der Browserseite dient die [Bibliothek Naja |https://naja.js.org]. Diese [installieren Sie |https://naja.js.org/#/guide/01-install-setup-naja] als node.js-Paket (zur Verwendung mit Anwendungen wie Webpack, Rollup, Vite, Parcel und anderen): +Für die Arbeit mit Snippets auf der Browserseite dient die [Bibliothek Naja |https://naja.js.org]. [Installieren Sie sie |https://naja.js.org/#/guide/01-install-setup-naja] als Node.js-Paket (für die Verwendung mit Bundlern wie Webpack, Rollup, Vite, Parcel und anderen): ```shell npm install naja ``` -…oder fügen Sie sie direkt in das Seiten-Template ein: +…oder binden Sie sie direkt in das Template der Seite ein: ```latte - + ``` -Zuerst muss die Bibliothek [initialisiert |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] werden: +Zuerst müssen Sie die Bibliothek [initialisieren |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: ```js naja.initialize(); ``` -Um aus einem gewöhnlichen Link (Signal) oder dem Absenden eines Formulars eine AJAX-Anfrage zu machen, genügt es, den entsprechenden Link, das Formular oder den Button mit der Klasse `ajax` zu kennzeichnen: +Um aus einem gewöhnlichen Link (Signal) oder dem Absenden eines Formulars einen AJAX-Request zu machen, genügt es, den betreffenden Link, das Formular oder die Schaltfläche mit der Klasse `ajax` zu kennzeichnen: ```latte -Go +Los
@@ -100,32 +100,34 @@ oder ``` -Neuzeichnen von Snippets ------------------------- +Snippets neu zeichnen +--------------------- -Jedes Objekt der Klasse [Control |components] (einschließlich des Presenters selbst) verfolgt, ob Änderungen aufgetreten sind, die sein Neuzeichnen erfordern. Dazu dient die Methode `redrawControl()`: +Jedes Objekt der Klasse [Control |components] (einschließlich des Presenters selbst) merkt sich, ob Änderungen eingetreten sind, die ein Neuzeichnen erfordern. Dazu dient die Methode `redrawControl()`: ```php public function handleLogin(string $user): void { - // nach dem Login muss der relevante Teil neu gezeichnet werden + // nach dem Login muss der betreffende Teil neu gezeichnet werden $this->redrawControl(); // ... } ``` -Nette ermöglicht eine noch feinere Kontrolle darüber, was neu gezeichnet werden soll. Die genannte Methode kann nämlich als Argument den Namen des Snippets entgegennehmen. Man kann also auf der Ebene von Template-Teilen invalidieren (sprich: Neuzeichnen erzwingen). Wenn die gesamte Komponente invalidiert wird, wird auch jedes ihrer Snippets neu gezeichnet: +Nette erlaubt eine noch feinere Steuerung dessen, was neu gezeichnet werden soll. Die Methode kann als Argument den Namen des Snippets entgegennehmen. Es lässt sich also auf der Ebene von Template-Teilen invalidieren (das heißt: ein Neuzeichnen erzwingen). Wird die gesamte Komponente invalidiert, wird auch jedes Snippet darin neu gezeichnet: ```php // invalidiert das Snippet 'header' $this->redrawControl('header'); ``` +Eine anstehende Invalidierung lässt sich über den zweiten Parameter `$redraw` auch aufheben: Der Aufruf `$this->redrawControl('header', redraw: false)` markiert das Snippet als nicht neu zu zeichnen. Die vollständige Signatur lautet `redrawControl(?string $snippet = null, bool $redraw = true)`. + Snippets in Latte ----------------- -Die Verwendung von Snippets in Latte ist extrem einfach. Um einen Teil des Templates als Snippet zu definieren, umschließen Sie ihn einfach mit den Tags `{snippet}` und `{/snippet}`: +Die Verwendung von Snippets in Latte ist ausgesprochen einfach. Um einen Teil des Templates als Snippet zu definieren, umschließen Sie ihn einfach mit den Tags `{snippet}` und `{/snippet}`: ```latte {snippet header} @@ -133,9 +135,9 @@ Die Verwendung von Snippets in Latte ist extrem einfach. Um einen Teil des Templ {/snippet} ``` -Das Snippet erstellt in der HTML-Seite ein `
`-Element mit einer speziellen generierten `id`. Beim Neuzeichnen des Snippets wird dann der Inhalt dieses Elements aktualisiert. Daher ist es notwendig, dass beim erstmaligen Rendern der Seite auch alle Snippets gerendert werden, auch wenn sie anfangs leer sein mögen. +Das Snippet erzeugt in der HTML-Seite ein Element `
` mit einer speziell generierten `id`. Beim Neuzeichnen des Snippets wird der Inhalt dieses Elements aktualisiert. Deshalb ist es nötig, dass beim ersten Rendern der Seite auch alle Snippets gerendert werden, selbst wenn sie anfangs leer sein sollten. -Sie können auch ein Snippet mit einem anderen Element als `
` erstellen, indem Sie ein n:Attribut verwenden: +Ein Snippet lässt sich mit einem n:Attribut auch mit einem anderen Element als `
` erzeugen: ```latte
@@ -147,7 +149,7 @@ Sie können auch ein Snippet mit einem anderen Element als `
` erstellen, in Snippet-Bereiche ---------------- -Snippet-Namen können auch Ausdrücke sein: +Namen von Snippets können auch Ausdrücke sein: ```latte {foreach $items as $id => $item} @@ -155,7 +157,9 @@ Snippet-Namen können auch Ausdrücke sein: {/foreach} ``` -So entstehen mehrere Snippets `item-0`, `item-1` usw. Wenn wir ein dynamisches Snippet direkt invalidieren würden (zum Beispiel `item-1`), würde nichts neu gezeichnet werden. Der Grund dafür ist, dass Snippets wirklich wie Ausschnitte funktionieren und nur sie selbst direkt gerendert werden. Im Template gibt es jedoch faktisch kein Snippet namens `item-1`. Dieses entsteht erst durch die Ausführung des Codes um das Snippet herum, also der foreach-Schleife. Wir kennzeichnen daher den Teil des Templates, der ausgeführt werden soll, mit dem Tag `{snippetArea}`: +Für sich genommen ist das ein nicht funktionsfähiger Zwischenschritt: Wird ein dynamisches Snippet außerhalb eines statischen `{snippet}` oder `{snippetArea}` gerendert, löst es ein `E_USER_WARNING` mit der Meldung *Dynamic snippets are allowed only inside static snippet/snippetArea.* aus. Das beheben wir gleich. + +So entstehen mehrere Snippets wie `item-0`, `item-1` usw. Würden wir ein dynamisches Snippet (z. B. `item-1`) direkt invalidieren, würde nichts neu gezeichnet. Der Grund ist, dass Snippets wirklich als Ausschnitte funktionieren und nur sie selbst direkt gerendert werden. Im Template gibt es jedoch technisch gesehen gar kein Snippet namens `item-1`. Es entsteht erst, wenn der Code rund um das Snippet, also die foreach-Schleife, ausgeführt wird. Deshalb kennzeichnen wir den Teil des Templates, der ausgeführt werden muss, mit dem Tag `{snippetArea}`: ```latte
    @@ -165,16 +169,16 @@ So entstehen mehrere Snippets `item-0`, `item-1` usw. Wenn wir ein dynamisches S
``` -Und lassen sowohl das Snippet selbst als auch den gesamten übergeordneten Bereich neu zeichnen: +Und wir fordern das Neuzeichnen sowohl des einzelnen Snippets als auch des gesamten übergeordneten Bereichs an: ```php $this->redrawControl('itemsContainer'); $this->redrawControl('item-1'); ``` -Gleichzeitig ist es ratsam sicherzustellen, dass das Array `$items` nur die Elemente enthält, die neu gezeichnet werden sollen. +Zugleich ist es ratsam, dafür zu sorgen, dass das Array `$items` nur die Einträge enthält, die neu gezeichnet werden sollen. -Wenn wir mittels des `{include}`-Tags ein anderes Template einfügen, das Snippets enthält, muss das Einfügen des Templates ebenfalls in eine `snippetArea` eingeschlossen und diese zusammen mit dem Snippet invalidiert werden: +Binden wir mit dem Tag `{include}` ein weiteres Template mit Snippets in das Haupttemplate ein, ist es nötig, das eingebundene Template erneut in eine `snippetArea` zu hüllen und diese zusammen mit dem Snippet zu invalidieren: ```latte {snippetArea include} @@ -198,22 +202,22 @@ $this->redrawControl('item'); Snippets in Komponenten ----------------------- -Sie können Snippets auch in [Komponenten|components] erstellen und Nette wird sie automatisch neu zeichnen. Es gibt jedoch eine gewisse Einschränkung: Für das Neuzeichnen von Snippets ruft Nette die Methode `render()` ohne Parameter auf. Das bedeutet, dass die Übergabe von Parametern im Template nicht funktioniert: +Snippets können Sie auch in [Komponenten|components] erstellen, und Nette zeichnet sie automatisch neu. Es gibt jedoch eine Einschränkung: Zum Neuzeichnen von Snippets ruft Nette die Methode `render()` ohne Parameter auf. Die Übergabe von Parametern im Template funktioniert daher nicht: ```latte OK {control productGrid} -wird nicht funktionieren: +funktioniert nicht: {control productGrid $arg, $arg} {control productGrid:paginator} ``` -Senden von Benutzerdaten ------------------------- +Eigene Daten senden +------------------- -Zusammen mit den Snippets können Sie beliebige zusätzliche Daten an den Client senden. Schreiben Sie diese einfach in das `payload`-Objekt: +Zusammen mit den Snippets können Sie dem Client beliebige weitere Daten senden. Schreiben Sie sie einfach in das Objekt `payload`: ```php public function actionDelete(int $id): void @@ -226,10 +230,16 @@ public function actionDelete(int $id): void ``` -Parameterübergabe -================= +Weiterleitung +------------- + +Während eines AJAX-Requests senden die Methoden `redirect()` und `redirectUrl()` keine HTTP-Weiterleitung. Stattdessen schreiben sie die Ziel-URL in das Payload (das im AJAX-Response gesendete Datenobjekt), konkret in dessen Eigenschaft `payload.redirect`, und senden es; die eigentliche Weiterleitung führt dann die clientseitige Bibliothek (Naja) aus. + -Wenn wir einer Komponente über eine AJAX-Anfrage Parameter senden, seien es Signalparameter oder persistente Parameter, müssen wir bei der Anfrage deren globalen Namen angeben, der auch den Namen der Komponente enthält. Den vollständigen Namen des Parameters gibt die Methode `getParameterId()` zurück. +Parameter übergeben +=================== + +Wenn wir einer Komponente über einen AJAX-Request Parameter senden, seien es Signal- oder persistente Parameter, müssen wir im Request ihren globalen Namen angeben, der auch den Namen der Komponente enthält. Den vollständigen Parameternamen liefert die Methode `getParameterId()`. ```js let url = new URL({link //foo!}); @@ -247,3 +257,9 @@ public function handleFoo(int $bar): void { } ``` + + +Weiterführende Lektüre +====================== + +- [Dynamische Snippets |best-practices:dynamic-snippets] diff --git a/application/de/bootstrapping.texy b/application/de/bootstrapping.texy index 6a61ac820c..dabfea3a5b 100644 --- a/application/de/bootstrapping.texy +++ b/application/de/bootstrapping.texy @@ -3,19 +3,22 @@ Bootstrapping
-Bootstrapping ist der Prozess der Initialisierung der Anwendungsumgebung, der Erstellung eines Dependency Injection (DI) Containers und des Startens der Anwendung. Wir werden besprechen: +Bootstrapping ist der Prozess der Initialisierung der Anwendungsumgebung, der Erzeugung des Dependency-Injection-Containers (DI) und des Startens der Anwendung. Wir besprechen: -- wie die Bootstrap-Klasse die Umgebung initialisiert +- wie die Klasse Bootstrap die Umgebung initialisiert - wie Anwendungen mit NEON-Dateien konfiguriert werden -- wie zwischen Produktions- und Entwicklungsmodus unterschieden wird -- wie der DI-Container erstellt und konfiguriert wird +- wie man Produktions- und Entwicklungsmodus unterscheidet +- wie man den DI-Container erzeugt und konfiguriert
-Anwendungen, egal ob Webanwendungen oder von der Kommandozeile gestartete Skripte, beginnen ihre Ausführung mit einer Form der Initialisierung der Umgebung. Früher war dafür eine Datei wie `include.inc.php` verantwortlich, die von der initialen Datei eingebunden wurde. In modernen Nette-Anwendungen wurde dies durch die Klasse `Bootstrap` ersetzt, die Sie als Teil der Anwendung in der Datei `app/Bootstrap.php` finden. Sie könnte zum Beispiel so aussehen: +Anwendungen, seien es Web-Anwendungen oder von der Kommandozeile ausgeführte Skripte, beginnen ihre Ausführung mit einer Form der Initialisierung der Umgebung. Früher kümmerte sich darum eine Datei mit einem Namen wie `include.inc.php`, die von der Startdatei eingebunden wurde. In modernen Nette-Anwendungen wurde sie durch die Klasse `Bootstrap` ersetzt, die als Teil der Anwendung in der Datei `app/Bootstrap.php` zu finden ist. Sie kann zum Beispiel so aussehen: ```php +namespace App; + +use Nette; use Nette\Bootstrap\Configurator; class Bootstrap @@ -26,9 +29,9 @@ class Bootstrap public function __construct() { $this->rootDir = dirname(__DIR__); - // Der Configurator ist für die Einstellung der Anwendungsumgebung und der Dienste verantwortlich. + // Der Configurator ist für das Einrichten der Anwendungsumgebung und der Services zuständig. $this->configurator = new Configurator; - // Legt das Verzeichnis für temporäre Dateien fest, die von Nette generiert werden (z. B. kompilierte Templates) + // Verzeichnis für von Nette erzeugte temporäre Dateien festlegen (z. B. kompilierte Templates) $this->configurator->setTempDirectory($this->rootDir . '/temp'); } @@ -41,14 +44,14 @@ class Bootstrap private function initializeEnvironment(): void { - // Nette ist schlau und der Entwicklungsmodus wird automatisch aktiviert, - // oder Sie können ihn für eine bestimmte IP-Adresse aktivieren, indem Sie die folgende Zeile auskommentieren: + // Nette ist schlau, und der Entwicklungsmodus schaltet sich automatisch ein, + // oder Sie aktivieren ihn für eine bestimmte IP-Adresse, indem Sie die folgende Zeile einkommentieren: // $this->configurator->setDebugMode('secret@23.75.345.200'); - // Aktiviert Tracy: das ultimative "Schweizer Taschenmesser" für das Debugging. + // Aktiviert Tracy: das ultimative "Schweizer Taschenmesser" zum Debuggen. $this->configurator->enableTracy($this->rootDir . '/log'); - // RobotLoader: lädt automatisch alle Klassen im ausgewählten Verzeichnis + // RobotLoader: lädt automatisch alle Klassen im gewählten Verzeichnis $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); @@ -56,7 +59,7 @@ class Bootstrap private function setupContainer(): void { - // Lädt Konfigurationsdateien + // Konfigurationsdateien laden $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } @@ -66,69 +69,78 @@ class Bootstrap index.php ========= -Die initiale Datei für Webanwendungen ist `index.php`, die sich im [öffentlichen Verzeichnis |directory-structure#Öffentliches Verzeichnis www] `www/` befindet. Diese lässt die Umgebung von der Bootstrap-Klasse initialisieren und den DI-Container erstellen. Danach holt sie sich den Dienst `Application` daraus, der die Webanwendung startet: +Bei Webanwendungen ist die Startdatei `index.php`, die im [öffentlichen Verzeichnis |directory-structure#Öffentliches Verzeichnis www/] `www/` liegt. Sie lässt die Klasse Bootstrap die Umgebung initialisieren und den DI-Container erzeugen. Anschließend holt sie sich aus dem Container den Service `Application`, der die Webanwendung startet: ```php $bootstrap = new App\Bootstrap; -// Initialisierung der Umgebung + Erstellung des DI-Containers +// Umgebung initialisieren + DI-Container erzeugen $container = $bootstrap->bootWebApplication(); -// Der DI-Container erstellt das Objekt Nette\Application\Application +// Der DI-Container erzeugt ein Objekt Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// Start der Nette-Anwendung und Verarbeitung der eingehenden Anfrage +// Die Nette-Anwendung starten und den eingehenden Request verarbeiten $application->run(); ``` -Wie man sieht, hilft die Klasse [api:Nette\Bootstrap\Configurator] bei der Einstellung der Umgebung und der Erstellung des Dependency Injection (DI) Containers, die wir uns nun genauer ansehen werden. +.[note] +Das Objekt `$application` löst während der Verarbeitung des Requests [Events |nette:glossary#Events] aus - `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown` und `onError` (bei einer unbehandelten Exception). Sie können daran Handler hängen, was sich für Logging oder anwendungsweites Monitoring anbietet. + +Wie Sie sehen, hilft die Klasse [api:Nette\Bootstrap\Configurator] beim Einrichten der Umgebung und beim Erzeugen des Dependency-Injection-Containers (DI). Stellen wir sie nun genauer vor. Entwicklungs- vs. Produktionsmodus ================================== -Nette verhält sich unterschiedlich, je nachdem, ob es auf einem Entwicklungs- oder Produktionsserver läuft: +Nette verhält sich unterschiedlich, je nachdem, ob es auf einem Entwicklungs- oder einem Produktionsserver läuft: -🛠️ Entwicklungsmodus (Development): - - Zeigt die Tracy Debugbar mit nützlichen Informationen an (SQL-Abfragen, Ausführungszeit, verwendeter Speicher) - - Zeigt im Fehlerfall eine detaillierte Fehlerseite mit Funktionsaufrufen und Variableninhalten an - - Erneuert automatisch den Cache bei Änderungen an Latte-Templates, Konfigurationsdateien usw. +🛠️ Entwicklungsmodus: + - Zeigt die Tracy Debug Bar mit nützlichen Informationen (SQL-Queries, Ausführungszeit, verbrauchter Speicher) + - Zeigt bei einem Fehler eine detaillierte Fehlerseite mit Funktionsaufrufen und Variableninhalten + - Aktualisiert den Cache automatisch, wenn Latte-Templates, Konfigurationsdateien usw. geändert werden -🚀 Produktionsmodus (Production): - - Zeigt keine Debugging-Informationen an, alle Fehler werden im Log protokolliert - - Zeigt im Fehlerfall den ErrorPresenter oder eine allgemeine "Server Error"-Seite an - - Der Cache wird niemals automatisch erneuert! - - Optimiert für Geschwindigkeit und Sicherheit +🚀 Produktionsmodus: + - Zeigt keinerlei Debugging-Informationen an, alle Fehler werden ins Log geschrieben + - Zeigt bei einem Fehler den ErrorPresenter oder eine allgemeine Seite "Server Error" + - Der Cache wird niemals automatisch aktualisiert! + - Optimiert auf Geschwindigkeit und Sicherheit -Die Moduswahl erfolgt durch Auto-Detektion, sodass normalerweise nichts konfiguriert oder manuell umgeschaltet werden muss: +Die Wahl des Modus erfolgt per Autodetection, üblicherweise ist also nichts zu konfigurieren und der Modus nicht manuell umzuschalten: -- Entwicklungsmodus: auf Localhost (IP-Adresse `127.0.0.1` oder `::1`), wenn kein Proxy vorhanden ist (d. h. dessen HTTP-Header) +- Entwicklungsmodus: auf localhost (IP-Adresse `127.0.0.1` oder `::1`), sofern kein Proxy vorhanden ist (also dessen HTTP-Header nicht erkannt wird) - Produktionsmodus: überall sonst -Wenn wir den Entwicklungsmodus auch in anderen Fällen aktivieren möchten, z. B. für Programmierer, die von einer bestimmten IP-Adresse zugreifen, verwenden wir `setDebugMode()`: +Wollen wir den Entwicklungsmodus auch in anderen Fällen einschalten, etwa für Programmierer, die von einer bestimmten IP-Adresse zugreifen, verwenden wir `setDebugMode()`: ```php -$this->configurator->setDebugMode('23.75.345.200'); // es kann auch ein Array von IP-Adressen angegeben werden +$this->configurator->setDebugMode('23.75.345.200'); // es lässt sich auch ein Array von IP-Adressen angeben ``` -Wir empfehlen dringend, die IP-Adresse mit einem Cookie zu kombinieren. Wir speichern einen geheimen Token, z. B. `secret1234`, im Cookie `nette-debug` und aktivieren auf diese Weise den Entwicklungsmodus für Programmierer, die von einer bestimmten IP-Adresse zugreifen und gleichzeitig den erwähnten Token im Cookie haben: +Wir empfehlen dringend, die IP-Adresse mit einem Cookie zu kombinieren. Legen Sie im Cookie `nette-debug` ein geheimes Token ab, z. B. `secret1234`, und aktivieren Sie so den Entwicklungsmodus für Programmierer, die von einer bestimmten IP-Adresse zugreifen und zugleich das genannte Token im Cookie haben: ```php $this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Wir können den Entwicklungsmodus auch vollständig deaktivieren, sogar für Localhost: +Den Entwicklungsmodus können wir auch vollständig abschalten, sogar für localhost: ```php $this->configurator->setDebugMode(false); ``` -Achtung, der Wert `true` schaltet den Entwicklungsmodus fest ein, was auf einem Produktionsserver niemals passieren darf. +Beachten Sie, dass der Wert `true` den Entwicklungsmodus erzwingt, was auf einem Produktionsserver **niemals** passieren darf. + +Die Autodetection erledigt intern die statische Methode `Configurator::detectDebugMode()`, die Sie auch selbst aufrufen können, etwa um den Entwicklungsmodus außerhalb des Configurators zu erkennen. Sie nimmt optional eine Whitelist von IP-Adressen oder Rechnernamen entgegen und gibt zurück, ob der aktuelle Request im Entwicklungsmodus laufen soll: + +```php +$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200'); +``` Debugging-Tool Tracy ==================== -Für einfaches Debugging aktivieren wir noch das großartige Werkzeug [Tracy |tracy:]. Im Entwicklungsmodus visualisiert es Fehler und im Produktionsmodus protokolliert es Fehler in das angegebene Verzeichnis: +Für bequemes Debuggen aktivieren wir das ausgezeichnete Werkzeug [Tracy |tracy:]. Im Entwicklungsmodus visualisiert es Fehler, im Produktionsmodus protokolliert es sie in das angegebene Verzeichnis: ```php $this->configurator->enableTracy($this->rootDir . '/log'); @@ -138,19 +150,19 @@ $this->configurator->enableTracy($this->rootDir . '/log'); Temporäre Dateien ================= -Nette verwendet einen Cache für den DI-Container, RobotLoader, Templates usw. Daher ist es notwendig, den Pfad zum Verzeichnis festzulegen, in dem der Cache gespeichert wird: +Nette verwendet Cache für den DI-Container, RobotLoader, Templates usw. Deshalb muss der Pfad zu dem Verzeichnis gesetzt werden, in dem der Cache abgelegt wird: ```php $this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` -Unter Linux oder macOS setzen Sie für die Verzeichnisse `log/` und `temp/` [Schreibrechte |nette:troubleshooting#Einstellung der Verzeichnisberechtigungen]. +Setzen Sie unter Linux oder macOS für die Verzeichnisse `log/` und `temp/` [Schreibrechte |nette:troubleshooting#Einstellung der Verzeichnisberechtigungen]. RobotLoader =========== -In der Regel möchten wir Klassen automatisch mit dem [RobotLoader |robot-loader:] laden, also müssen wir ihn starten und ihn Klassen aus dem Verzeichnis laden lassen, in dem sich `Bootstrap.php` befindet (d. h. `__DIR__`), sowie aus allen Unterverzeichnissen: +Üblicherweise wollen wir Klassen automatisch mit [RobotLoader |robot-loader:] laden. Wir müssen ihn also starten und Klassen aus dem Verzeichnis laden lassen, in dem `Bootstrap.php` liegt (also `__DIR__`), samt allen Unterverzeichnissen: ```php $this->configurator->createRobotLoader() @@ -158,13 +170,13 @@ $this->configurator->createRobotLoader() ->register(); ``` -Ein alternativer Ansatz besteht darin, Klassen nur über [Composer |best-practices:composer] unter Einhaltung von PSR-4 laden zu lassen. +Ein alternativer Weg ist, Klassen ausschließlich über [Composer |best-practices:composer] nach PSR-4 zu laden. Zeitzone ======== -Über den Konfigurator können Sie die Standard-Zeitzone einstellen. +Über den Configurator lässt sich die Standard-Zeitzone einstellen. ```php $this->configurator->setTimeZone('Europe/Prague'); @@ -174,20 +186,22 @@ $this->configurator->setTimeZone('Europe/Prague'); Konfiguration des DI-Containers =============================== -Ein Teil des Boot-Prozesses ist die Erstellung des DI-Containers, auch Objektfabrik genannt, der das Herz der gesamten Anwendung ist. Es handelt sich tatsächlich um eine PHP-Klasse, die von Nette generiert und im Cache-Verzeichnis gespeichert wird. Die Fabrik erstellt die Schlüsselobjekte der Anwendung, und mithilfe von Konfigurationsdateien weisen wir sie an, wie sie diese erstellen und einstellen soll, wodurch wir das Verhalten der gesamten Anwendung beeinflussen. +Teil des Startvorgangs ist die Erzeugung des DI-Containers, also der Objekt-Factory, die das Herz der gesamten Anwendung ist. Es handelt sich tatsächlich um eine von Nette erzeugte und im Cache-Verzeichnis abgelegte PHP-Klasse. Die Factory stellt die Schlüsselobjekte der Anwendung her, und mit Konfigurationsdateien weisen wir sie an, wie sie diese erzeugen und einrichten soll - und beeinflussen damit das Verhalten der gesamten Anwendung. -Konfigurationsdateien werden normalerweise im [NEON |neon:format]-Format geschrieben. In einem separaten Kapitel erfahren Sie, [was alles konfiguriert werden kann |nette:configuring]. +Konfigurationsdateien werden üblicherweise im [NEON-Format |neon:format] geschrieben. In einem eigenen Kapitel lesen Sie, [was sich alles konfigurieren lässt |nette:configuring]. .[tip] -Im Entwicklungsmodus wird der Container bei jeder Änderung des Codes oder der Konfigurationsdateien automatisch aktualisiert. Im Produktionsmodus wird er nur einmal generiert, und Änderungen werden zur Maximierung der Leistung nicht überprüft. +Im Entwicklungsmodus wird der Container bei jeder Änderung des Codes oder der Konfigurationsdateien automatisch aktualisiert. Im Produktionsmodus wird er nur einmal erzeugt, und Änderungen werden nicht geprüft, um die Leistung zu maximieren. + +Während `createContainer()` den Container baut und seine Instanz zurückgibt, liefert die Methode `loadContainer()` nur den Namen der generierten Container-Klasse, die Sie dann selbst instanziieren können. Das ist in fortgeschrittenen Szenarien nützlich. -Konfigurationsdateien laden wir mit `addConfig()`: +Konfigurationsdateien werden mit `addConfig()` geladen: ```php $this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` -Wenn wir mehrere Konfigurationsdateien hinzufügen möchten, können wir die Funktion `addConfig()` mehrmals aufrufen. +Wollen wir weitere Konfigurationsdateien hinzufügen, können wir die Funktion `addConfig()` mehrfach aufrufen. ```php $configDir = $this->rootDir . '/config'; @@ -198,17 +212,17 @@ if (PHP_SAPI === 'cli') { } ``` -Der Name `cli.php` ist kein Tippfehler; die Konfiguration kann auch in einer PHP-Datei geschrieben sein, die sie als Array zurückgibt. +Der Name `cli.php` ist kein Tippfehler; die Konfiguration lässt sich auch in einer PHP-Datei schreiben, die sie als Array zurückgibt. -Wir können auch weitere Konfigurationsdateien im [Abschnitt `includes` |dependency-injection:configuration#Dateien einbinden] hinzufügen. +Weitere Konfigurationsdateien können wir auch [im Abschnitt `includes` |dependency-injection:configuration#Dateien einbinden] hinzufügen. -Wenn in den Konfigurationsdateien Elemente mit denselben Schlüsseln erscheinen, werden sie überschrieben oder im Falle von [Arrays zusammengeführt |dependency-injection:configuration#Zusammenführen]. Die später eingebundene Datei hat eine höhere Priorität als die vorherige. Die Datei, in der der Abschnitt `includes` aufgeführt ist, hat eine höhere Priorität als die darin eingebundenen Dateien. +Erscheinen in den Konfigurationsdateien Einträge mit denselben Schlüsseln, werden sie überschrieben, im Fall von [Arrays zusammengeführt |dependency-injection:configuration#Zusammenführen]. Eine später eingebundene Datei hat höhere Priorität als die vorherige. Die Datei, in der der Abschnitt `includes` steht, hat höhere Priorität als die darin eingebundenen Dateien. Statische Parameter ------------------- -Parameter, die in Konfigurationsdateien verwendet werden, können [im Abschnitt `parameters` |dependency-injection:configuration#Parameter] definiert und auch über die Methode `addStaticParameters()` (hat den Alias `addParameters()`) übergeben (oder überschrieben) werden. Wichtig ist, dass unterschiedliche Werte der Parameter die Generierung zusätzlicher DI-Container, also zusätzlicher Klassen, verursachen. +Parameter, die in Konfigurationsdateien verwendet werden, lassen sich [im Abschnitt `parameters` |dependency-injection:configuration#Parameter] definieren und außerdem mit der Methode `addStaticParameters()` übergeben (oder überschreiben), deren älterer, inzwischen veralteter Alias `addParameters()` lautet. Wichtig ist, dass unterschiedliche Parameterwerte die Erzeugung weiterer DI-Container, also weiterer Klassen, bewirken. ```php $this->configurator->addStaticParameters([ @@ -216,13 +230,13 @@ $this->configurator->addStaticParameters([ ]); ``` -Auf den Parameter `projectId` kann in der Konfiguration mit der üblichen Schreibweise `%projectId%` verwiesen werden. +Auf den Parameter `projectId` lässt sich in der Konfiguration mit der üblichen Schreibweise `%projectId%` verweisen. Dynamische Parameter -------------------- -Wir können dem Container auch dynamische Parameter hinzufügen, deren unterschiedliche Werte im Gegensatz zu statischen Parametern nicht die Generierung neuer DI-Container verursachen. +Dem Container können wir auch dynamische Parameter hinzufügen, deren unterschiedliche Werte im Gegensatz zu statischen Parametern keine Erzeugung neuer DI-Container bewirken. ```php $this->configurator->addDynamicParameters([ @@ -230,7 +244,7 @@ $this->configurator->addDynamicParameters([ ]); ``` -So können wir einfach z. B. Umgebungsvariablen hinzufügen, auf die dann in der Konfiguration mit der Schreibweise `%env.variable%` verwiesen werden kann. +So lassen sich zum Beispiel bequem Umgebungsvariablen ergänzen, auf die man dann in der Konfiguration mit der Schreibweise `%env.variable%` verweisen kann. ```php $this->configurator->addDynamicParameters([ @@ -242,21 +256,22 @@ $this->configurator->addDynamicParameters([ Standardparameter ----------------- -In Konfigurationsdateien können Sie diese statischen Parameter verwenden: +In den Konfigurationsdateien können Sie diese Parameter verwenden: - `%appDir%` ist der absolute Pfad zum Verzeichnis mit der Datei `Bootstrap.php` -- `%wwwDir%` ist der absolute Pfad zum Verzeichnis mit der Eingabedatei `index.php` +- `%wwwDir%` ist der absolute Pfad zum Verzeichnis mit der Startdatei `index.php` - `%tempDir%` ist der absolute Pfad zum Verzeichnis für temporäre Dateien -- `%vendorDir%` ist der absolute Pfad zum Verzeichnis, in dem Composer Bibliotheken installiert -- `%rootDir%` ist der absolute Pfad zum Stammverzeichnis des Projekts -- `%debugMode%` gibt an, ob sich die Anwendung im Debugging-Modus befindet -- `%consoleMode%` gibt an, ob die Anfrage über die Kommandozeile kam +- `%vendorDir%` ist der absolute Pfad zum Verzeichnis, in das Composer die Bibliotheken installiert +- `%rootDir%` ist der absolute Pfad zum Wurzelverzeichnis des Projekts +- `%baseUrl%` ist die absolute URL zum Wurzelverzeichnis (ein dynamischer Parameter, der zur Laufzeit aufgelöst wird) +- `%debugMode%` gibt an, ob die Anwendung im Debug-Modus läuft +- `%consoleMode%` gibt an, ob der Request über die Kommandozeile kam -Importierte Dienste -------------------- +Importierte Services +-------------------- -Jetzt gehen wir tiefer. Obwohl der Zweck des DI-Containers darin besteht, Objekte zu erstellen, kann es ausnahmsweise notwendig sein, ein vorhandenes Objekt in den Container einzufügen. Dies tun wir, indem wir den Dienst mit dem Flag `imported: true` definieren. +Jetzt gehen wir tiefer. Obwohl der Zweck des DI-Containers darin besteht, Objekte zu erzeugen, kann gelegentlich der Bedarf entstehen, ein bereits existierendes Objekt in den Container einzufügen. Wir tun das, indem wir den Service mit dem Flag `imported: true` definieren. ```neon services: @@ -274,10 +289,10 @@ $this->configurator->addServices([ ``` -Unterschiedliche Umgebungen -=========================== +Verschiedene Umgebungen +======================= -Scheuen Sie sich nicht, die Bootstrap-Klasse an Ihre Bedürfnisse anzupassen. Sie können der Methode `bootWebApplication()` Parameter hinzufügen, um Webprojekte zu unterscheiden. Oder wir können weitere Methoden hinzufügen, zum Beispiel `bootTestEnvironment()`, die die Umgebung für Unit-Tests initialisiert, `bootConsoleApplication()` für Skripte, die von der Kommandozeile aufgerufen werden, usw. +Passen Sie die Klasse `Bootstrap` ruhig Ihren Bedürfnissen an. Sie können der Methode `bootWebApplication()` Parameter hinzufügen, um zwischen Webprojekten zu unterscheiden. Oder wir ergänzen weitere Methoden, etwa `bootTestEnvironment()`, die die Umgebung für Unit-Tests initialisiert, `bootConsoleApplication()` für von der Kommandozeile aufgerufene Skripte usw. ```php public function bootTestEnvironment(): Nette\DI\Container diff --git a/application/de/components.texy b/application/de/components.texy index b39a8e5abd..b9b3713eee 100644 --- a/application/de/components.texy +++ b/application/de/components.texy @@ -3,27 +3,27 @@ Interaktive Komponenten
-Komponenten sind eigenständige, wiederverwendbare Objekte, die wir in Seiten einfügen. Das können Formulare, Datengrids, Umfragen sein, eigentlich alles, was sinnvoll wiederverwendet werden kann. Wir zeigen Ihnen: +Komponenten sind eigenständige, wiederverwendbare Objekte, die wir in Seiten einbetten. Das können Formulare, Datagrids, Umfragen sein - kurz alles, was sich sinnvoll wiederholt verwenden lässt. Wir zeigen: -- Wie man Komponenten verwendet? -- Wie man sie schreibt? -- Was sind Signale? +- wie man Komponenten verwendet? +- wie man sie schreibt? +- was Signale sind?
-Nette verfügt über ein eingebautes Komponentensystem. Etwas Ähnliches kennen Kenner vielleicht noch aus Delphi oder ASP.NET Web Forms, und React oder Vue.js bauen auf etwas entfernt Ähnlichem auf. In der Welt der PHP-Frameworks ist dies jedoch eine einzigartige Angelegenheit. +Nette hat ein eingebautes Komponentensystem. Etwas Ähnliches kennen Veteranen vielleicht aus Delphi oder ASP.NET Web Forms; React oder Vue.js bauen auf entfernt Ähnlichem auf. In der Welt der PHP-Frameworks ist das jedoch eine einzigartige Eigenschaft. -Dabei beeinflussen Komponenten den Ansatz zur Anwendungsentwicklung grundlegend. Sie können Seiten aus vorgefertigten Einheiten zusammenstellen. Benötigen Sie ein Datagrid in der Administration? Sie finden es auf [Componette |https://componette.org/search/component], einem Repository für Open-Source-Add-ons (also nicht nur Komponenten) für Nette, und fügen es einfach in den Presenter ein. +Zugleich beeinflussen Komponenten die Herangehensweise an die Anwendungsentwicklung grundlegend. Sie können Seiten aus vorbereiteten Einheiten zusammensetzen. Brauchen Sie in Ihrer Administration ein Datagrid? Finden Sie es auf [Componette |https://componette.org/search/component], einem Repository von Open-Source-Erweiterungen (nicht nur Komponenten) für Nette, und fügen Sie es einfach in den Presenter ein. -Sie können beliebig viele Komponenten in einen Presenter integrieren. Und in einige Komponenten können Sie weitere Komponenten einfügen. So entsteht ein Komponentenbaum, dessen Wurzel der Presenter ist. +Sie können beliebig viele Komponenten in den Presenter einbinden. Und in manche Komponenten können Sie weitere Komponenten einbetten. So entsteht ein Komponentenbaum, dessen Wurzel der Presenter ist. Factory-Methoden ================ -Wie werden Komponenten in den Presenter eingefügt und anschließend verwendet? Normalerweise über Factory-Methoden. +Wie werden Komponenten in den Presenter eingefügt und anschließend verwendet? Üblicherweise über Factory-Methoden. -Eine Komponenten-Factory stellt eine elegante Möglichkeit dar, Komponenten erst dann zu erstellen, wenn sie tatsächlich benötigt werden (lazy / on demand). Der ganze Zauber besteht darin, eine Methode mit dem Namen `createComponent()` zu implementieren, wobei `` der Name der zu erstellenden Komponente ist, und die die Komponente erstellt und zurückgibt. +Eine Factory für Komponenten ist ein eleganter Weg, Komponenten erst dann zu erzeugen, wenn sie tatsächlich gebraucht werden (lazy / on demand). Die ganze Magie steckt in der Implementierung einer Methode namens `createComponent()`, wobei `` der Name der erzeugten Komponente ist und die die Komponente erzeugt und zurückgibt. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -31,49 +31,54 @@ class DefaultPresenter extends Nette\Application\UI\Presenter protected function createComponentPoll(): PollControl { $poll = new PollControl; - $poll->items = $this->item; + $poll->items = $this->items; return $poll; } } ``` -Dadurch, dass alle Komponenten in separaten Methoden erstellt werden, gewinnt der Code an Übersichtlichkeit. +Weil alle Komponenten in eigenen Methoden erzeugt werden, wird der Code übersichtlicher. .[note] -Komponentennamen beginnen immer mit einem Kleinbuchstaben, obwohl sie im Methodennamen großgeschrieben werden. +Namen von Komponenten beginnen immer mit einem Kleinbuchstaben, obwohl sie im Methodennamen großgeschrieben werden. -Factories werden niemals direkt aufgerufen, sie rufen sich selbst auf, wenn die Komponente zum ersten Mal verwendet wird. Dadurch wird die Komponente zum richtigen Zeitpunkt erstellt und nur dann, wenn sie tatsächlich benötigt wird. Wenn wir die Komponente nicht verwenden (z. B. bei einer AJAX-Anfrage, bei der nur ein Teil der Seite übertragen wird, oder beim Caching des Templates), wird sie überhaupt nicht erstellt und wir sparen Serverleistung. +Wir rufen Factories nie direkt auf; sie werden automatisch beim ersten Verwenden der Komponente aufgerufen. Dadurch wird die Komponente im richtigen Moment erzeugt und nur dann, wenn sie tatsächlich gebraucht wird. Verwenden wir die Komponente nicht (z. B. bei einem AJAX-Request, bei dem nur ein Teil der Seite übertragen wird, oder beim Cachen des Templates), wird sie überhaupt nicht erzeugt, was Serverleistung spart. ```php .{file:DefaultPresenter.php} -// Wir greifen auf die Komponente zu, und wenn es das erste Mal war, -// wird createComponentPoll() aufgerufen, die sie erstellt +// wir greifen auf die Komponente zu, und wenn es das erste Mal war, +// wird createComponentPoll() aufgerufen, die sie erzeugt $poll = $this->getComponent('poll'); -// alternative Syntax: $poll = $this['poll']; +// alternative Schreibweise: $poll = $this['poll']; ``` -Im Template kann eine Komponente mit dem Tag [{control} |#Rendern] gerendert werden. Es ist daher nicht notwendig, Komponenten manuell an das Template zu übergeben. +Im Template lässt sich eine Komponente mit dem Tag [{control} |#Rendering] rendern. Komponenten müssen daher nicht von Hand an das Template übergeben werden. ```latte -

Stimmen Sie ab

+

Bitte stimmen Sie ab

{control poll} ``` +.[tip] +Um dynamisch eine variable Anzahl von Komponenten zu erzeugen, verwenden Sie den [Multiplier |multiplier]. + +Die Factory-Methoden `createComponent()` funktionieren nicht nur in Presentern. Auf dieselbe Weise können Sie eine Komponente in eine andere Komponente verschachteln und sie zu einem Baum zusammensetzen - praktisch zum Beispiel für ein separat gerendertes Formular innerhalb einer Komponente. + Hollywood Style =============== -Komponenten verwenden üblicherweise eine frische Technik, die wir gerne Hollywood Style nennen. Sie kennen sicher den geflügelten Satz, den Teilnehmer von Filmcastings so oft hören: „Rufen Sie uns nicht an, wir rufen Sie an“. Und genau darum geht es. +Komponenten nutzen gewöhnlich eine erfrischende Technik, die wir gerne Hollywood Style nennen. Sie kennen sicher das Klischee, das Teilnehmer von Filmcastings oft hören: "Rufen Sie uns nicht an, wir rufen Sie an." Und genau darum geht es. -In Nette sagen Sie dem Framework nämlich, anstatt ständig nachfragen zu müssen („wurde das Formular abgeschickt?“, „war es gültig?“ oder „hat der Benutzer diesen Button gedrückt?“), „wenn das passiert, ruf diese Methode auf“ und überlassen die weitere Arbeit ihm. Wenn Sie in JavaScript programmieren, kennen Sie diesen Programmierstil genau. Sie schreiben Funktionen, die aufgerufen werden, wenn ein bestimmtes Ereignis eintritt. Und die Sprache übergibt ihnen die entsprechenden Parameter. +In Nette müssen Sie nicht ständig Fragen stellen ("wurde das Formular abgeschickt?", "war es gültig?", oder "hat der Benutzer diese Schaltfläche gedrückt?"), sondern Sie sagen dem Framework "wenn das passiert, rufe diese Methode auf" und überlassen ihm die weitere Arbeit. Wenn Sie in JavaScript programmieren, ist Ihnen dieser Programmierstil bestens vertraut. Sie schreiben Funktionen, die aufgerufen werden, wenn ein bestimmtes Ereignis eintritt. Und die Sprache übergibt ihnen die passenden Parameter. -Dies verändert die Sichtweise auf das Schreiben von Anwendungen grundlegend. Je mehr Aufgaben Sie dem Framework überlassen können, desto weniger Arbeit haben Sie. Und desto weniger können Sie vielleicht übersehen. +Das ändert die Sicht auf das Schreiben von Anwendungen vollständig. Je mehr Aufgaben Sie dem Framework überlassen können, desto weniger Arbeit haben Sie. Und desto weniger können Sie übersehen. -Wir schreiben eine Komponente -============================= +Eine Komponente schreiben +========================= -Unter dem Begriff Komponente verstehen wir normalerweise einen Nachfahren der Klasse [api:Nette\Application\UI\Control]. (Genauer wäre es also, den Begriff „Controls“ zu verwenden, aber „Kontrollen“ hat im Deutschen eine ganz andere Bedeutung, und eher haben sich „Komponenten“ durchgesetzt.) Der Presenter [api:Nette\Application\UI\Presenter] selbst ist übrigens auch ein Nachfahre der Klasse `Control`. +Mit dem Begriff Komponente meinen wir üblicherweise einen Nachfahren der Klasse [api:Nette\Application\UI\Control]. (Genauer wäre der Begriff "Controls", aber der hat in manchen Sprachen eine andere Bedeutung, und "Komponenten" hat sich stärker eingebürgert.) Auch der Presenter [api:Nette\Application\UI\Presenter] selbst ist ein Nachfahre der Klasse `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +89,22 @@ class PollControl extends Control ``` -Rendern -======= +Rendering +========= -Wir wissen bereits, dass zum Rendern einer Komponente der Tag `{control componentName}` verwendet wird. Dieser ruft eigentlich die Methode `render()` der Komponente auf, in der wir uns um das Rendern kümmern. Uns steht, genau wie im Presenter, eine [Latte-Vorlage|templates] in der Variablen `$this->template` zur Verfügung, an die wir Parameter übergeben. Im Gegensatz zum Presenter müssen wir die Template-Datei angeben und sie rendern lassen: +Wir wissen bereits, dass zum Rendern einer Komponente der Tag `{control componentName}` dient. Er ruft in Wirklichkeit die Methode `render()` der Komponente auf, in der wir uns um das Rendering kümmern. Wir haben, genau wie im Presenter, in der Variablen `$this->template` ein [Latte-Template|templates] zur Verfügung, dem wir Parameter übergeben. Anders als im Presenter müssen wir die Template-Datei angeben und sie rendern lassen: ```php .{file:PollControl.php} public function render(): void { - // Wir fügen einige Parameter in das Template ein + // einige Parameter in das Template einfügen $this->template->param = $value; - // und rendern es + // und es rendern $this->template->render(__DIR__ . '/poll.latte'); } ``` -Der `{control}`-Tag ermöglicht es, Parameter an die `render()`-Methode zu übergeben: +Der Tag `{control}` erlaubt es, der Methode `render()` Parameter zu übergeben: ```latte {control poll $id, $message} @@ -112,7 +117,7 @@ public function render(int $id, string $message): void } ``` -Manchmal kann eine Komponente aus mehreren Teilen bestehen, die wir getrennt rendern möchten. Für jeden davon erstellen wir eine eigene Rendering-Methode, hier im Beispiel etwa `renderPaginator()`: +Manchmal kann eine Komponente aus mehreren Teilen bestehen, die wir getrennt rendern wollen. Für jeden davon legen wir eine eigene Rendering-Methode an, im Beispiel hier `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +126,69 @@ public function renderPaginator(): void } ``` -Und im Template rufen wir sie dann auf mit: +Und im Template rufen wir sie dann so auf: ```latte {control poll:paginator} ``` -Zum besseren Verständnis ist es gut zu wissen, wie dieser Tag in PHP übersetzt wird. +Zum besseren Verständnis ist es gut zu wissen, wie sich dieser Tag in PHP-Code übersetzt. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -wird übersetzt als: +übersetzt sich zu: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -Die Methode `getComponent()` gibt die Komponente `poll` zurück und ruft für diese Komponente die Methode `render()` bzw. `renderPaginator()` auf, wenn im Tag nach dem Doppelpunkt eine andere Rendering-Art angegeben ist. +Die Methode `getComponent()` gibt die Komponente `poll` zurück, und auf dieser Komponente wird die Methode `render()` aufgerufen, bzw. `renderPaginator()`, wenn im Tag nach dem Doppelpunkt eine andere Rendering-Methode angegeben ist. .[caution] -Achtung, wenn irgendwo in den Parametern **`=>`** vorkommt, werden alle Parameter in ein Array verpackt und als erstes Argument übergeben: +Achtung: Erscheint in den Parametern außerhalb eckiger Klammern ein **`=>`**, werden alle Parameter in ein Array verpackt und als erstes Argument übergeben: ```latte {control poll, id: 123, message: 'hello'} ``` -wird übersetzt als: +übersetzt sich zu: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Rendern einer Sub-Komponente: +Rendering einer Unterkomponente: ```latte {control cartControl-someForm} ``` -wird übersetzt als: +übersetzt sich zu: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Komponenten, ebenso wie Presenter, übergeben automatisch einige nützliche Variablen an die Templates: +Komponenten übergeben Templates, ebenso wie Presenter, automatisch mehrere nützliche Variablen: - `$basePath` ist der absolute URL-Pfad zum Wurzelverzeichnis (z. B. `/eshop`) - `$baseUrl` ist die absolute URL zum Wurzelverzeichnis (z. B. `http://localhost/eshop`) -- `$user` ist das Objekt, das [den Benutzer repräsentiert |security:authentication] +- `$user` ist ein Objekt, das [den Benutzer repräsentiert |security:authentication] - `$presenter` ist der aktuelle Presenter - `$control` ist die aktuelle Komponente -- `$flashes` ist ein Array von [Nachrichten |#Flash-Nachrichten], die mit der Funktion `flashMessage()` gesendet wurden +- `$flashes` ist ein Array von [Meldungen |#Flash-Meldungen], die mit der Funktion `flashMessage()` gesendet wurden Signal ====== -Wir wissen bereits, dass die Navigation in einer Nette-Anwendung im Verlinken oder Weiterleiten auf Paare von `Presenter:action` besteht. Aber was ist, wenn wir nur eine Aktion auf der **aktuellen Seite** durchführen wollen? Zum Beispiel die Sortierreihenfolge von Spalten in einer Tabelle ändern; einen Eintrag löschen; den Hell-/Dunkelmodus umschalten; ein Formular absenden; in einer Umfrage abstimmen; usw. +Wir wissen bereits, dass die Navigation in einer Nette-Anwendung im Verlinken oder Weiterleiten auf Paare `Presenter:action` besteht. Was aber, wenn wir nur eine Aktion auf der **aktuellen Seite** ausführen wollen? Zum Beispiel die Sortierung von Spalten einer Tabelle ändern; ein Element löschen; zwischen hellem und dunklem Modus umschalten; ein Formular absenden; in einer Umfrage abstimmen usw. -Diese Art von Anfragen wird als Signale bezeichnet. Und ähnlich wie Aktionen die Methoden `action()` oder `render()` aufrufen, rufen Signale die Methoden `handle()` auf. Während der Begriff Aktion (oder View) rein mit Presentern zusammenhängt, betreffen Signale alle Komponenten. Und somit auch Presenter, da `UI\Presenter` ein Nachfahre von `UI\Control` ist. +Diese Art von Request nennt man Signal. Und so, wie Aktionen die Methoden `action()` oder `render()` aufrufen, rufen Signale die Methoden `handle()` auf. Während sich der Begriff Aktion (oder View) rein auf Presenter bezieht, betreffen Signale alle Komponenten. Und damit auch Presenter, denn `UI\Presenter` ist ein Nachfahre von `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +197,36 @@ public function handleClick(int $x, int $y): void } ``` -Einen Link, der ein Signal aufruft, erstellen wir auf die übliche Weise, d. h. im Template mit dem Attribut `n:href` oder dem Tag `{link}`, im Code mit der Methode `link()`. Mehr dazu im Kapitel [Erstellen von URL-Links |creating-links#Links zu Signalen]. +Einen Link, der ein Signal aufruft, erstellt man auf die übliche Weise, also im Template mit dem Attribut `n:href` oder dem Tag `{link}`, im Code mit der Methode `link()`. Mehr im Kapitel [Erstellen von URL-Links |creating-links#Links auf Signale]. ```latte -Hier klicken +hier klicken ``` -Ein Signal wird immer im aktuellen Presenter und der aktuellen Action aufgerufen, es kann nicht in einem anderen Presenter oder einer anderen Action ausgelöst werden. +Ein Signal wird immer auf dem aktuellen Presenter und der aktuellen Aktion aufgerufen; es lässt sich nicht auf einem anderen Presenter oder einer anderen Aktion auslösen. -Ein Signal bewirkt also das Neuladen der Seite genau wie bei der ursprünglichen Anfrage, ruft aber zusätzlich die Signal-Handler-Methode mit den entsprechenden Parametern auf. Wenn die Methode nicht existiert, wird eine Ausnahme [api:Nette\Application\UI\BadSignalException] ausgelöst, die dem Benutzer als Fehlerseite 403 Forbidden angezeigt wird. +Ein Signal bewirkt also, dass die Seite genau wie beim ursprünglichen Request neu geladen wird, ruft zusätzlich aber die Methode zur Verarbeitung des Signals mit den passenden Parametern auf. Existiert die Methode nicht, wird eine Exception [api:Nette\Application\UI\BadSignalException] geworfen, die dem Benutzer als Fehlerseite 403 Forbidden angezeigt wird. Snippets und AJAX ================= -Signale erinnern Sie vielleicht ein wenig an AJAX: Handler, die auf der aktuellen Seite aufgerufen werden. Und Sie haben Recht, Signale werden tatsächlich oft mittels AJAX aufgerufen, und anschließend übertragen wir nur die geänderten Teile der Seite an den Browser. Also sogenannte Snippets. Weitere Informationen finden Sie auf der [AJAX gewidmeten Seite |ajax]. +Signale erinnern Sie vielleicht ein wenig an AJAX: Handler, die auf der aktuellen Seite aufgerufen werden. Und Sie haben recht, Signale werden tatsächlich oft mit AJAX aufgerufen, und anschließend werden nur die geänderten Teile der Seite in den Browser übertragen. Diese nennt man Snippets. Mehr dazu finden Sie auf der [Seite über AJAX |ajax]. -Flash-Nachrichten -================= +Flash-Meldungen +=============== -Eine Komponente hat ihren eigenen Speicher für Flash-Nachrichten, unabhängig vom Presenter. Dies sind Nachrichten, die z. B. über das Ergebnis einer Operation informieren. Ein wichtiges Merkmal von Flash-Nachrichten ist, dass sie auch nach einer Weiterleitung im Template verfügbar sind. Auch nach der Anzeige bleiben sie weitere 30 Sekunden aktiv – zum Beispiel für den Fall, dass der Benutzer aufgrund einer fehlerhaften Übertragung die Seite neu lädt – die Nachricht verschwindet also nicht sofort. +Eine Komponente hat einen eigenen, vom Presenter unabhängigen Speicher für Flash-Meldungen. Das sind Meldungen, die zum Beispiel über das Ergebnis einer Operation informieren. Eine wichtige Eigenschaft von Flash-Meldungen ist, dass sie im Template auch nach einer Weiterleitung verfügbar sind. Auch nach der Anzeige bleiben sie noch 30 Sekunden aktiv - zum Beispiel für den Fall, dass der Benutzer die Seite wegen eines Übertragungsfehlers neu lädt, verschwindet die Meldung nicht sofort. -Das Senden übernimmt die Methode [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Der erste Parameter ist der Nachrichtentext oder ein `stdClass`-Objekt, das die Nachricht repräsentiert. Der optionale zweite Parameter ist ihr Typ (error, warning, info usw.). Die Methode `flashMessage()` gibt eine Instanz der Flash-Nachricht als `stdClass`-Objekt zurück, dem weitere Informationen hinzugefügt werden können. +Das Senden übernimmt die Methode [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Der erste Parameter ist der Text der Meldung (`string`, `Stringable`) oder ein Objekt `stdClass`, das die Meldung repräsentiert. Der optionale zweite Parameter ist ihr Typ (error, warning, info usw.). Die Methode `flashMessage()` gibt eine Instanz der Flash-Meldung als Objekt `stdClass` zurück, dem sich weitere Informationen hinzufügen lassen. ```php -$this->flashMessage('Der Eintrag wurde gelöscht.'); -$this->redirect(/* ... */); // und wir leiten weiter +$this->flashMessage('Das Element wurde gelöscht.'); +$this->redirect(/* ... */); // und weiterleiten ``` -Dem Template stehen diese Nachrichten in der Variablen `$flashes` als `stdClass`-Objekte zur Verfügung, die die Eigenschaften `message` (Nachrichtentext), `type` (Nachrichtentyp) enthalten und die bereits erwähnten Benutzerinformationen enthalten können. Wir rendern sie zum Beispiel so: +Diese Meldungen stehen dem Template in der Variablen `$flashes` als Objekte `stdClass` zur Verfügung, die die Properties `message` (Text der Meldung) und `type` (Typ der Meldung) enthalten und die erwähnten eigenen Informationen enthalten können. Wir rendern sie zum Beispiel so: ```latte {foreach $flashes as $flash} @@ -230,36 +235,36 @@ Dem Template stehen diese Nachrichten in der Variablen `$flashes` als `stdClass` ``` -Weiterleitung nach Signal -========================= +Weiterleitung nach Verarbeitung eines Signals +============================================= -Nach der Verarbeitung eines Komponentensignals folgt oft eine Weiterleitung. Dies ist eine ähnliche Situation wie bei Formularen – nach dem Absenden leiten wir ebenfalls weiter, damit beim Neuladen der Seite im Browser die Daten nicht erneut gesendet werden. +Auf die Verarbeitung eines Signals einer Komponente folgt oft eine Weiterleitung. Das ist ähnlich wie bei Formularen - nach dem Absenden leiten wir ebenfalls weiter, damit die Daten beim Neuladen der Seite im Browser nicht erneut abgeschickt werden. ```php -$this->redirect('this') // leitet zum aktuellen Presenter und zur aktuellen Action weiter +$this->redirect('this'); // leitet auf den aktuellen Presenter und die aktuelle Aktion weiter ``` -Da eine Komponente ein wiederverwendbares Element ist und normalerweise keine direkte Bindung an bestimmte Presenter haben sollte, interpretieren die Methoden `redirect()` und `link()` den Parameter automatisch als Signal der Komponente: +Da eine Komponente ein wiederverwendbares Element ist und üblicherweise keine direkte Bindung an konkrete Presenter haben sollte, interpretieren die Methoden `redirect()` und `link()` den Parameter automatisch als Signal der Komponente: ```php -$this->redirect('click') // leitet zum Signal 'click' derselben Komponente weiter +$this->redirect('click'); // leitet auf das Signal 'click' derselben Komponente weiter ``` -Wenn Sie zu einem anderen Presenter oder einer anderen Aktion weiterleiten müssen, können Sie dies über den Presenter tun: +Müssen Sie auf einen anderen Presenter oder eine andere Aktion weiterleiten, geht das über den Presenter: ```php -$this->getPresenter()->redirect('Product:show'); // leitet zu einem anderen Presenter/Action weiter +$this->getPresenter()->redirect('Product:show'); // leitet auf einen anderen Presenter/eine andere Aktion weiter ``` Persistente Parameter ===================== -Persistente Parameter dienen dazu, den Zustand in Komponenten über verschiedene Anfragen hinweg zu erhalten. Ihr Wert bleibt auch nach dem Klick auf einen Link gleich. Im Gegensatz zu Daten in der Session werden sie in der URL übertragen. Und das vollautomatisch, einschließlich Links, die in anderen Komponenten auf derselben Seite erstellt wurden. +Persistente Parameter dienen dazu, den Zustand von Komponenten über verschiedene Requests hinweg zu erhalten. Ihr Wert bleibt auch nach dem Klick auf einen Link derselbe. Anders als Daten in der Session werden sie in der URL übertragen. Und das geschieht vollautomatisch, einschließlich der Links, die in anderen Komponenten auf derselben Seite erstellt werden. -Sie haben z. B. eine Komponente für die Paginierung von Inhalten. Solche Komponenten können auf einer Seite mehrmals vorkommen. Und wir möchten, dass nach dem Klick auf einen Link alle Komponenten auf ihrer aktuellen Seite bleiben. Deshalb machen wir die Seitenzahl (`page`) zu einem persistenten Parameter. +Sie haben zum Beispiel eine Komponente zur Paginierung von Inhalten. Auf einer Seite können mehrere solcher Komponenten sein. Und wir wollen, dass alle Komponenten nach dem Klick auf einen Link auf ihrer aktuellen Seite bleiben. Deshalb machen wir die Seitennummer (`page`) zu einem persistenten Parameter. -Die Erstellung eines persistenten Parameters ist in Nette äußerst einfach. Es genügt, eine öffentliche Eigenschaft zu erstellen und sie mit einem Attribut zu kennzeichnen: (früher wurde `/** @persistent */` verwendet) +Einen persistenten Parameter in Nette zu erstellen ist ausgesprochen einfach. Legen Sie einfach eine public Property an und kennzeichnen Sie sie mit dem Attribut: (früher wurde `/** @persistent */` verwendet) ```php use Nette\Application\Attributes\Persistent; // diese Zeile ist wichtig @@ -271,15 +276,15 @@ class PaginatingControl extends Control } ``` -Für die Eigenschaft empfehlen wir, auch den Datentyp anzugeben (z. B. `int`), und Sie können auch einen Standardwert angeben. Die Werte der Parameter können [validiert |#Validierung persistenter Parameter] werden. +Wir empfehlen, für die Property den Datentyp anzugeben (z. B. `int`), und Sie können auch einen Standardwert angeben. Die Werte der Parameter lassen sich [validieren |#Validierung persistenter Parameter]. -Beim Erstellen eines Links kann der Wert des persistenten Parameters geändert werden: +Beim Erstellen eines Links lässt sich der Wert eines persistenten Parameters ändern: ```latte weiter ``` -Oder er kann *zurückgesetzt* werden, d. h. aus der URL entfernt werden. Dann nimmt er seinen Standardwert an: +Oder er lässt sich *zurücksetzen*, also aus der URL entfernen. Er nimmt dann seinen Standardwert an: ```latte zurücksetzen @@ -289,25 +294,25 @@ Oder er kann *zurückgesetzt* werden, d. h. aus der URL entfernt werden. Dann ni Persistente Komponenten ======================= -Nicht nur Parameter, sondern auch Komponenten können persistent sein. Bei einer solchen Komponente werden ihre persistenten Parameter auch zwischen verschiedenen Aktionen des Presenters oder zwischen mehreren Presentern übertragen. Persistente Komponenten kennzeichnen wir mit einer Annotation in der Presenter-Klasse. So kennzeichnen wir beispielsweise die Komponenten `calendar` und `poll`: +Nicht nur Parameter, sondern auch Komponenten können persistent sein. Ihre persistenten Parameter werden dann auch zwischen verschiedenen Aktionen des Presenters oder zwischen mehreren Presentern übertragen. Persistente Komponenten kennzeichnen wir mit einem Attribut an der Klasse des Presenters. So kennzeichnen wir zum Beispiel die Komponenten `calendar` und `poll`: ```php -/** - * @persistent(calendar, poll) - */ +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] class DefaultPresenter extends Nette\Application\UI\Presenter { } ``` -Sub-Komponenten innerhalb dieser Komponenten müssen nicht gekennzeichnet werden, sie werden ebenfalls persistent. +Unterkomponenten innerhalb dieser Komponenten müssen nicht gekennzeichnet werden; auch sie werden persistent. -In PHP 8 können Sie zur Kennzeichnung persistenter Komponenten auch Attribute verwenden: +Die ältere Annotation `@persistent` funktioniert weiterhin, ist aber veraltet und löst eine Warnung aus: ```php -use Nette\Application\Attributes\Persistent; - -#[Persistent('calendar', 'poll')] +/** + * @persistent(calendar, poll) + */ class DefaultPresenter extends Nette\Application\UI\Presenter { } @@ -317,9 +322,9 @@ class DefaultPresenter extends Nette\Application\UI\Presenter Komponenten mit Abhängigkeiten ============================== -Wie erstellt man Komponenten mit Abhängigkeiten, ohne die Presenter, die sie verwenden, zu „verschmutzen“? Dank der intelligenten Eigenschaften des DI-Containers in Nette kann man, wie bei der Verwendung klassischer Dienste, den größten Teil der Arbeit dem Framework überlassen. +Wie erstellt man Komponenten mit Abhängigkeiten, ohne die Presenter, die sie verwenden, zu "verstopfen"? Dank der klugen Fähigkeiten des DI-Containers in Nette lässt sich, ähnlich wie bei klassischen Services, der Großteil der Arbeit dem Framework überlassen. -Nehmen wir als Beispiel eine Komponente, die eine Abhängigkeit vom Dienst `PollFacade` hat: +Nehmen wir als Beispiel eine Komponente, die eine Abhängigkeit vom Service `PollFacade` hat: ```php class PollControl extends Control @@ -332,17 +337,17 @@ class PollControl extends Control public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -Wenn wir einen klassischen Dienst schreiben würden, gäbe es nichts zu lösen. Der DI-Container würde sich unsichtbar um die Übergabe aller Abhängigkeiten kümmern. Aber mit Komponenten gehen wir normalerweise so um, dass wir ihre neue Instanz direkt im Presenter in den [#Factory-Methoden] `createComponent…()` erstellen. Aber alle Abhängigkeiten aller Komponenten an den Presenter zu übergeben, nur um sie dann an die Komponenten weiterzugeben, ist umständlich. Und der viele geschriebene Code… +Würden wir einen klassischen Service schreiben, gäbe es nichts zu besprechen. Der DI-Container würde die Übergabe aller Abhängigkeiten unsichtbar erledigen. Bei Komponenten lösen wir das jedoch üblicherweise so, dass wir in den [#Factory-Methoden] `createComponent…()` direkt im Presenter eine neue Instanz erzeugen. Aber alle Abhängigkeiten aller Komponenten in den Presenter zu übergeben, nur um sie an die Komponenten weiterzureichen, ist umständlich. Und die Menge des geschriebenen Codes … -Die logische Frage ist, warum registrieren wir die Komponente nicht einfach als klassischen Dienst, übergeben sie an den Presenter und geben sie dann in der Methode `createComponent…()` zurück? Dieser Ansatz ist jedoch ungeeignet, da wir die Komponente möglicherweise mehrmals erstellen möchten. +Die logische Frage lautet: Warum registrieren wir die Komponente nicht einfach als klassischen Service, übergeben sie dem Presenter und geben sie dann in der Methode `createComponent…()` zurück? Dieser Ansatz ist jedoch ungeeignet, weil wir die Möglichkeit haben wollen, die Komponente bei Bedarf mehrfach zu erzeugen. -Die richtige Lösung ist, eine Factory für die Komponente zu schreiben, also eine Klasse, die uns die Komponente erstellt: +Die richtige Lösung ist, für die Komponente eine Factory zu schreiben, also eine Klasse, die die Komponente für uns erzeugt: ```php class PollControlFactory @@ -359,7 +364,7 @@ class PollControlFactory } ``` -Diese Factory registrieren wir in unserem Container in der Konfiguration: +Diese Factory registrieren wir in der Konfiguration in unserem Container: ```neon services: @@ -384,7 +389,7 @@ class PollPresenter extends Nette\Application\UI\Presenter } ``` -Das Tolle ist, dass Nette DI solche einfachen Factories [generieren |dependency-injection:factory] kann, sodass statt des gesamten Codes nur ihr Interface geschrieben werden muss: +Das Großartige daran ist, dass Nette DI solche einfachen Factories [generieren |dependency-injection:factory] kann, sodass Sie statt des gesamten Codes nur ihr Interface schreiben müssen: ```php interface PollControlFactory @@ -393,21 +398,21 @@ interface PollControlFactory } ``` -Und das ist alles. Nette implementiert dieses Interface intern und übergibt es an den Presenter, wo wir es bereits verwenden können. Es fügt magischerweise auch den Parameter `$id` und eine Instanz der Klasse `PollFacade` zu unserer Komponente hinzu. +Und das ist alles. Nette implementiert dieses Interface intern und injiziert es in den Presenter, wo wir es verwenden können. Es ergänzt unsere Komponente auf magische Weise um den Parameter `$id` und eine Instanz der Klasse `PollFacade`. Komponenten im Detail ===================== -Komponenten in Nette Application stellen wiederverwendbare Teile einer Webanwendung dar, die wir in Seiten einfügen und denen dieses ganze Kapitel gewidmet ist. Welche Fähigkeiten hat eine solche Komponente genau? +Komponenten stellen in Nette Application wiederverwendbare Teile einer Webanwendung dar, die wir in Seiten einbetten und denen dieses ganze Kapitel gewidmet ist. Was genau kann eine solche Komponente? -1) Sie ist im Template renderbar. -2) Sie weiß, [welchen Teil |ajax#Snippets] sie bei einer AJAX-Anfrage rendern soll (Snippets). -3) Sie hat die Fähigkeit, ihren Zustand in der URL zu speichern (persistente Parameter). -4) Sie hat die Fähigkeit, auf Benutzeraktionen zu reagieren (Signale). -5) Sie bildet eine hierarchische Struktur (deren Wurzel der Presenter ist). +1) Sie ist in einem Template renderbar +2) Sie weiß, [welchen Teil von sich |ajax#Snippets] sie bei einem AJAX-Request rendern soll (Snippets) +3) Sie kann ihren Zustand in der URL speichern (persistente Parameter) +4) Sie kann auf Benutzeraktionen reagieren (Signale) +5) Sie bildet eine hierarchische Struktur (deren Wurzel der Presenter ist) -Jede dieser Funktionen wird von einer der Klassen der Vererbungslinie übernommen. Das Rendern (1 + 2) übernimmt [api:Nette\Application\UI\Control], die Einbindung in den [Lebenszyklus |presenters#Lebenszyklus des Presenters] (3, 4) die Klasse [api:Nette\Application\UI\Component] und die Erstellung der hierarchischen Struktur (5) die Klassen [Container und Component |component-model:]. +Jede dieser Funktionen übernimmt eine der Klassen in der Vererbungslinie. Um das Rendering (1 + 2) kümmert sich [api:Nette\Application\UI\Control], um die Einbindung in den [Lebenszyklus |presenters#Lebenszyklus des Presenters] (3, 4) die Klasse [api:Nette\Application\UI\Component], und um den Aufbau der hierarchischen Struktur (5) die Klassen [Container und Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -431,9 +436,9 @@ Lebenszyklus einer Komponente Validierung persistenter Parameter ---------------------------------- -Die Werte der [persistenten Parameter |#Persistente Parameter], die aus der URL empfangen wurden, schreibt die Methode `loadState()` in die Eigenschaften. Sie prüft auch, ob der bei der Eigenschaft angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt. +Die aus der URL empfangenen Werte der [#Persistente Parameter] werden von der Methode `loadState()` in die Properties geschrieben. Sie prüft außerdem, ob der für die Property angegebene Datentyp passt; andernfalls antwortet sie mit dem Fehler 404 und die Seite wird nicht angezeigt. -Vertrauen Sie niemals blind persistenten Parametern, da sie vom Benutzer leicht in der URL überschrieben werden können. So überprüfen wir beispielsweise, ob die Seitenzahl `$this->page` größer als 0 ist. Ein geeigneter Weg ist, die erwähnte Methode `loadState()` zu überschreiben: +Vertrauen Sie persistenten Parametern niemals blind, denn sie lassen sich vom Benutzer in der URL leicht überschreiben. So prüfen wir zum Beispiel, ob die Seitennummer `$this->page` größer als 0 ist. Ein geeigneter Weg ist, die erwähnte Methode `loadState()` zu überschreiben: ```php class PaginatingControl extends Control @@ -452,27 +457,41 @@ class PaginatingControl extends Control } ``` -Der umgekehrte Prozess, also das Sammeln von Werten aus persistenten Eigenschaften, wird von der Methode `saveState()` übernommen. +Den umgekehrten Vorgang, also das Einsammeln der Werte aus den persistenten Properties, übernimmt die Methode `saveState()`. + + +Verbindung mit dem Presenter +---------------------------- + +In dem Moment, in dem eine Komponente Teil der Hierarchie des Presenters wird, werden ihre im Array `$onAnchor` gespeicherten Callbacks aufgerufen. Ab diesem Zeitpunkt hat die Komponente den Presenter zur Verfügung, kann gefahrlos Links erstellen, persistente Parameter lesen und so weiter. + +```php +$control->onAnchor[] = function ($control): void { + // die Komponente hat nun den Presenter zur Verfügung +}; +``` Signale im Detail ----------------- -Ein Signal bewirkt das Neuladen der Seite genau wie bei der ursprünglichen Anfrage (außer wenn es per AJAX aufgerufen wird) und ruft die Methode `signalReceived($signal)` auf, deren Standardimplementierung in der Klasse `Nette\Application\UI\Component` versucht, eine Methode aufzurufen, die aus den Wörtern `handle{signal}` zusammengesetzt ist. Die weitere Verarbeitung liegt beim jeweiligen Objekt. Objekte, die von `Component` erben (d. h. `Control` und `Presenter`), reagieren, indem sie versuchen, die Methode `handle{signal}` mit den entsprechenden Parametern aufzurufen. +Ein Signal bewirkt, dass die Seite genau wie beim ursprünglichen Request neu geladen wird (außer beim Aufruf über AJAX), und ruft die Methode `signalReceived($signal)` auf, deren Standardimplementierung in der Klasse `Nette\Application\UI\Component` versucht, eine aus den Wörtern `handle` zusammengesetzte Methode aufzurufen. Die weitere Verarbeitung liegt beim jeweiligen Objekt. Objekte, die von `Component` erben (also `Control` und `Presenter`), reagieren so, dass sie versuchen, die Methode `handle` mit den passenden Parametern aufzurufen. + +Mit anderen Worten: Es wird die Definition der Funktion `handle` genommen, samt allen Parametern, die mit dem Request kamen, und die Parameter aus der URL werden den Argumenten anhand des Namens zugeordnet, dann wird versucht, die Methode aufzurufen. Zum Beispiel wird der Wert des Parameters `id` aus der URL als Argument `$id` übergeben, `something` aus der URL als `$something` usw. Und existiert die Methode nicht, wirft die Methode `signalReceived` eine [Exception |api:Nette\Application\UI\BadSignalException]. -Mit anderen Worten: Es wird die Definition der Funktion `handle{signal}` und alle mit der Anfrage übermittelten Parameter genommen, und den Argumenten werden anhand des Namens Parameter aus der URL zugewiesen, und es wird versucht, die betreffende Methode aufzurufen. Z. B. wird als Parameter `$id` der Wert des Parameters `id` aus der URL übergeben, als `$something` wird `something` aus der URL übergeben, usw. Und wenn die Methode nicht existiert, löst die Methode `signalReceived` eine [Ausnahme |api:Nette\Application\UI\BadSignalException] aus. +Neben den Parametern aus der URL liest ein Signal auch die im **POST-Body des Requests** gesendeten Parameter. Das ist praktisch, weil Signale oft über JavaScript aufgerufen werden, wo es natürlich ist, Daten mit der Methode POST zu senden. Kommt ein Parameter gleichen Namens jedoch sowohl aus der URL als auch aus dem POST-Body, hat der Wert **aus der URL Vorrang**. Vermeiden Sie es daher, einem POST-Feld denselben Namen wie einem URL- oder Route-Parameter zu geben, sonst würde der Wert aus der URL ihn stillschweigend überschreiben. Die Signalparameter teilen sich einen gemeinsamen Raum mit den Parametern der Aktion und den persistenten Parametern, siehe [Gemeinsamer Parameterraum |presenters#Gemeinsamer Parameterraum]. -Ein Signal kann von jeder Komponente, jedem Presenter oder jedem Objekt empfangen werden, das das Interface `SignalReceiver` implementiert und in den Komponentenbaum eingebunden ist. +Ein Signal kann jede Komponente, jeder Presenter oder jedes Objekt empfangen, das das Interface `SignalReceiver` implementiert und mit dem Komponentenbaum verbunden ist. -Die Hauptempfänger von Signalen sind `Presenter` und visuelle Komponenten, die von `Control` erben. Ein Signal soll als Zeichen für ein Objekt dienen, dass es etwas tun soll – die Umfrage soll eine Stimme vom Benutzer zählen, der Nachrichtenblock soll sich aufklappen und doppelt so viele Nachrichten anzeigen, das Formular wurde abgeschickt und soll die Daten verarbeiten und so weiter. +Die Hauptempfänger von Signalen werden `Presenter` und visuelle Komponenten sein, die von `Control` erben. Ein Signal soll einem Objekt als Zeichen dienen, dass es etwas tun soll - eine Umfrage soll eine Stimme des Benutzers zählen, ein Nachrichtenblock soll sich ausklappen und doppelt so viele Nachrichten anzeigen, ein Formular wurde abgeschickt und soll Daten verarbeiten und so weiter. -Die URL für ein Signal erstellen wir mit der Methode [Component::link() |api:Nette\Application\UI\Component::link()]. Als Parameter `$destination` übergeben wir den String `{signal}!` und als `$args` ein Array von Argumenten, die wir dem Signal übergeben möchten. Das Signal wird immer im aktuellen Presenter und der aktuellen Action mit den aktuellen Parametern aufgerufen, die Signalparameter werden nur hinzugefügt. Zusätzlich wird ganz am Anfang der **Parameter `?do`, der das Signal bestimmt**, hinzugefügt. +Die URL für ein Signal wird mit der Methode [Component::link() |api:Nette\Application\UI\Component::link()] erstellt. Als Parameter `$destination` übergeben wir den String `{signal}!` und als `$args` ein Array von Argumenten, die wir dem Signal übergeben wollen. Das Signal wird immer auf dem aktuellen Presenter und der aktuellen Aktion mit den aktuellen Parametern aufgerufen; die Parameter des Signals kommen lediglich hinzu. Zusätzlich wird der **Parameter `?do`, der das Signal angibt**, ergänzt. -Sein Format ist entweder `{signal}` oder `{signalReceiver}-{signal}`. `{signalReceiver}` ist der Name der Komponente im Presenter. Deshalb darf im Komponentennamen kein Bindestrich vorkommen – er wird zur Trennung von Komponentennamen und Signal verwendet, es ist jedoch möglich, mehrere Komponenten auf diese Weise zu verschachteln. +Sein Format lautet entweder `{signal}` oder `{signalReceiver}-{signal}`. `{signalReceiver}` ist der Name der Komponente im Presenter. Deshalb darf im Namen der Komponente kein Bindestrich vorkommen - er dient der Trennung von Komponentenname und Signal, wobei sich auf diese Weise mehrere Komponenten verschachteln lassen. -Die Methode [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] prüft, ob eine Komponente (erstes Argument) der Empfänger eines Signals ist (zweites Argument). Das zweite Argument kann weggelassen werden – dann wird geprüft, ob die Komponente Empfänger irgendeines Signals ist. Als zweites Parameter kann `true` angegeben werden, um zu prüfen, ob nicht nur die angegebene Komponente, sondern auch einer ihrer Nachfahren Empfänger ist. +Die Methode [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] prüft, ob die Komponente (erstes Argument) Empfänger des Signals (zweites Argument) ist. Das zweite Argument kann weggelassen werden - dann wird geprüft, ob die Komponente Empfänger irgendeines Signals ist. Wird der zweite Parameter auf `true` gesetzt, wird geprüft, ob die angegebene Komponente oder einer ihrer Nachfahren Empfänger ist. -In jeder Phase vor `handle{signal}` können wir das Signal manuell ausführen, indem wir die Methode [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] aufrufen, die die Bearbeitung des Signals übernimmt – sie nimmt die Komponente, die als Signalempfänger bestimmt wurde (wenn kein Signalempfänger bestimmt ist, ist es der Presenter selbst) und sendet ihr das Signal. +In jeder Phase vor `handle` können wir das Signal manuell ausführen, indem wir die Methode [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] aufrufen, die sich um die Verarbeitung des Signals kümmert - sie nimmt die als Signalempfänger bestimmte Komponente (ist kein Empfänger angegeben, ist es der Presenter selbst) und sendet ihr das Signal. Beispiel: @@ -482,4 +501,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Damit wird das Signal vorzeitig ausgeführt und nicht mehr erneut aufgerufen. +Dadurch wird das Signal vorzeitig ausgeführt und nicht erneut aufgerufen. diff --git a/application/de/configuration.texy b/application/de/configuration.texy index 638fba748b..bc2f529a4c 100644 --- a/application/de/configuration.texy +++ b/application/de/configuration.texy @@ -1,8 +1,8 @@ -Konfiguration von Anwendungen -***************************** +Konfiguration der Anwendung +*************************** .[perex] -Übersicht über die Konfigurationsoptionen für Nette-Anwendungen. +Übersicht der Konfigurationsoptionen für Nette Application. Application @@ -10,12 +10,12 @@ Application ```neon application: - # Das "Nette Application"-Panel im Tracy BlueScreen anzeigen? - debugger: ... # (bool) Standard ist true + # das Panel "Nette Application" im Tracy BlueScreen anzeigen? + debugger: ... # (bool) aktiv, wenn Tracy verfügbar ist - # Soll bei einem Fehler der Error-Presenter aufgerufen werden? - # wirkt sich nur im Entwicklungsmodus aus - catchExceptions: ... # (bool) Standard ist true + # in der Produktion werden Exceptions immer vom Error-Presenter behandelt; + # diese Option aktiviert dieses Verhalten zusätzlich im Entwicklungsmodus + catchExceptions: ... # (bool) Standard ist false - also aus im Dev-Modus, in der Produktion immer an # Name des Error-Presenters errorPresenter: Error # (string|array) Standard ist 'Nette:Error' @@ -23,49 +23,51 @@ application: # definiert Aliase für Presenter und Aktionen aliases: ... - # definiert Regeln für die Übersetzung des Presenter-Namens in eine Klasse + # definiert die Regeln für die Übersetzung des Presenter-Namens in eine Klasse mapping: ... - # Erzeugen fehlerhafte Links keine Warnungen? - # wirkt sich nur im Entwicklungsmodus aus + # Warnungen für ungültige Links unterdrücken? + # wirkt nur im Entwicklungsmodus silentLinks: ... # (bool) Standard ist false ``` -Seit `nette/application` Version 3.2 kann ein Paar von Error-Presentern definiert werden: +Seit `nette/application` Version 3.2 lässt sich ein Paar von Error-Presentern definieren: ```neon application: errorPresenter: - 4xx: Error4xx # für die Ausnahme Nette\Application\BadRequestException - 5xx: Error5xx # für andere Ausnahmen + 4xx: Error4xx # für Nette\Application\BadRequestException + 5xx: Error5xx # für übrige Exceptions ``` -Die Option `silentLinks` bestimmt, wie sich Nette im Entwicklungsmodus verhält, wenn die Linkgenerierung fehlschlägt (z. B. weil der Presenter nicht existiert usw.). Der Standardwert `false` bedeutet, dass Nette einen `E_USER_WARNING`-Fehler auslöst. Durch Setzen auf `true` wird diese Fehlermeldung unterdrückt. In der Produktionsumgebung wird `E_USER_WARNING` immer ausgelöst. Dieses Verhalten kann auch durch Setzen der Presenter-Variable [$invalidLinkMode |creating-links#Ungültige Links] beeinflusst werden. +Die Aufteilung ist sinnvoll, weil sich beide Situationen grundlegend unterscheiden. Eine `BadRequestException` (Codes 4xx) bedeutet, dass mit der Anwendung alles in Ordnung ist und der Besucher lediglich etwas angefordert hat, das es nicht gibt. Sie können daher einen vollwertigen Presenter verwenden, der eine freundliche Meldung im Layout Ihrer Website anzeigt. Ein Fehler 5xx bedeutet dagegen, dass in der Anwendung etwas kaputtgegangen ist und Sie nicht wissen, was. Halten Sie den Presenter für 5xx deshalb so einfach wie möglich, damit bei seinem Rendering nichts weiter fehlschlagen kann - idealerweise sollte er weder auf die Datenbank noch auf das Layout oder den angemeldeten Benutzer zugreifen. -[Aliase vereinfachen das Verlinken |creating-links#Aliase] zu häufig verwendeten Presentern. +Die Option `silentLinks` bestimmt, wie sich Nette im Entwicklungsmodus verhält, wenn die Erzeugung eines Links fehlschlägt (etwa weil der Presenter nicht existiert usw.). Der Standardwert `false` bedeutet, dass Nette einen Fehler `E_USER_WARNING` auslöst. Die Einstellung `true` unterdrückt diese Fehlermeldung. In der Produktionsumgebung wird `E_USER_WARNING` immer ausgelöst. Dieses Verhalten lässt sich auch über die Presenter-Variable [$invalidLinkMode |creating-links#Ungültige Links] beeinflussen. -[Mapping definiert Regeln |directory-structure#Presenter-Mapping], nach denen der Klassenname vom Presenter-Namen abgeleitet wird. +[Aliase vereinfachen das Referenzieren |creating-links#Aliase] häufig verwendeter Presenter. + +Das [Mapping definiert die Regeln |directory-structure#Presenter-Mapping], nach denen aus dem Presenter-Namen der Klassenname abgeleitet wird. Automatische Registrierung von Presentern ----------------------------------------- -Nette fügt Presenter automatisch als Dienste zum DI-Container hinzu, was deren Erstellung erheblich beschleunigt. Wie Nette Presenter findet, kann konfiguriert werden: +Nette fügt Presenter automatisch als Services in den DI-Container ein, was ihre Erzeugung deutlich beschleunigt. Wie Nette die Presenter findet, lässt sich konfigurieren: ```neon application: - # Presenter in der Composer Class Map suchen? + # Presenter in der Composer-Class-Map suchen? scanComposer: ... # (bool) Standard ist true # Maske, der Klassen- und Dateiname entsprechen müssen scanFilter: ... # (string) Standard ist '*Presenter' - # In welchen Verzeichnissen sollen Presenter gesucht werden? + # in welchen Verzeichnissen nach Presentern suchen? scanDirs: # (string[]|false) Standard ist '%appDir%' - %vendorDir%/mymodule ``` -Die in `scanDirs` angegebenen Verzeichnisse überschreiben nicht den Standardwert `%appDir%`, sondern ergänzen ihn, sodass `scanDirs` beide Pfade `%appDir%` und `%vendorDir%/mymodule` enthält. Wenn wir das Standardverzeichnis auslassen möchten, verwenden wir ein [Ausrufezeichen |dependency-injection:configuration#Zusammenführen], das den Wert überschreibt: +Die in `scanDirs` angegebenen Verzeichnisse überschreiben den Standardwert `%appDir%` nicht, sondern ergänzen ihn; `scanDirs` enthält also beide Pfade `%appDir%` und `%vendorDir%/mymodule`. Wollen wir das Standardverzeichnis weglassen, verwenden wir ein [Ausrufezeichen |dependency-injection:configuration#Zusammenführen]: ```neon application: @@ -73,36 +75,42 @@ application: - %vendorDir%/mymodule ``` -Das Scannen von Verzeichnissen kann durch Angabe des Wertes false deaktiviert werden. Wir empfehlen nicht, das automatische Hinzufügen von Presentern vollständig zu unterdrücken, da dies die Leistung der Anwendung beeinträchtigen würde. +Das Scannen der Verzeichnisse lässt sich mit dem Wert `false` abschalten. Presenter werden dann nicht mehr als Services registriert, lassen sich also nicht über den Abschnitt [decorator |dependency-injection:configuration#Decorator] anpassen, und ihre Erzeugung ist langsamer. Wir empfehlen daher nicht, die automatische Registrierung vollständig zu unterdrücken, da dies die Leistung der Anwendung senkt. Latte-Templates =============== -Mit dieser Einstellung kann das Verhalten von Latte in Komponenten und Presentern global beeinflusst werden. +Diese Einstellung beeinflusst global das Verhalten von Latte in Komponenten und Presentern. ```neon latte: - # Latte-Panel in der Tracy Bar für das Haupttemplate (true) oder alle Komponenten (all) anzeigen? - debugger: ... # (true|false|'all') Standard ist true + # das Latte-Panel in der Tracy Bar für das Haupttemplate (true) oder für alle Komponenten (all) anzeigen? + debugger: ... # (true|false|'all') aktiv, wenn Tracy verfügbar ist (nur im Debug-Modus) - # generiert Templates mit dem Header declare(strict_types=1) + # Templates mit dem Header declare(strict_types=1) erzeugen strictTypes: ... # (bool) Standard ist false - # schaltet den [strengen Parser-Modus |latte:develop#strict-mode] ein + # [strikten Parser-Modus |latte:develop#strict mode] aktivieren strictParsing: ... # (bool) Standard ist false - # aktiviert die [Überprüfung des generierten Codes |latte:develop#checking-generated-code] + # beschränkt die Gültigkeit von Variablen auf den Schleifenkörper + scopedLoopVariables: ... # (bool) Standard ist false + + # entfernt die durch Verschachtelung in Paar-Tags entstandene Einrückung + dedent: ... # (bool) Standard ist false + + # [Prüfung des generierten Codes |latte:develop#Checking Generated Code] aktivieren phpLinter: ... # (string) Standard ist null - # setzt die Locale - locale: cs_CZ # (string) Standard ist null + # setzt das Locale + locale: de_DE # (string) Standard ist null - # Klasse des $this->template-Objekts + # Klasse des Objekts $this->template templateClass: App\MyTemplateClass # Standard ist Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Wenn Sie Latte Version 3 verwenden, können Sie neue [Erweiterungen |latte:extending-latte#Latte Extension] hinzufügen mit: +Neue [Extensions |latte:extending-latte#Latte Extension] fügen Sie so hinzu: ```neon latte: @@ -110,20 +118,6 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Wenn Sie Latte Version 2 verwenden, können Sie neue Tags (Makros) registrieren, indem Sie entweder den Klassennamen oder eine Referenz auf einen Dienst angeben. Standardmäßig wird die Methode `install()` aufgerufen, dies kann jedoch geändert werden, indem ein anderer Methodenname angegeben wird: - -```neon -latte: - # Registrierung benutzerdefinierter Latte-Tags - macros: - - App\MyLatteMacros::register # statische Methode, Klassenname oder Callable - - @App\MyLatteMacrosFactory # Dienst mit install()-Methode - - @App\MyLatteMacrosFactory::register # Dienst mit register()-Methode - -services: - - App\MyLatteMacrosFactory -``` - Routing ======= @@ -132,14 +126,14 @@ Grundeinstellungen: ```neon routing: - # Routing-Panel in der Tracy Bar anzeigen? - debugger: ... # (bool) Standard ist true + # das Routing-Panel in der Tracy Bar anzeigen? + debugger: ... # (bool) aktiv, wenn Tracy verfügbar ist (nur im Debug-Modus) - # serialisiert den Router in den DI-Container + # den Router in den DI-Container serialisieren cache: ... # (bool) Standard ist false ``` -Das Routing wird normalerweise in der Klasse [RouterFactory |routing#Routen-Sammlung] definiert. Alternativ können Routen auch in der Konfiguration über Paare `Maske: Aktion` definiert werden, aber diese Methode bietet nicht so viel Flexibilität bei den Einstellungen: +Das Routing definieren wir üblicherweise in der Klasse [RouterFactory |routing#Routensammlung]. Alternativ lassen sich Routes auch in der Konfiguration über Paare `Maske: Aktion` definieren, diese Methode bietet jedoch nicht so viel Flexibilität: ```neon routing: @@ -152,23 +146,23 @@ routing: Konstanten ========== -Erstellen von PHP-Konstanten. +Erzeugung von PHP-Konstanten. ```neon constants: Foobar: 'baz' ``` -Nach dem Start der Anwendung wird die Konstante `Foobar` erstellt. +Die Konstante `Foobar` wird nach dem Start der Anwendung erzeugt. .[note] -Konstanten sollten nicht als global verfügbare Variablen dienen. Verwenden Sie [Dependency Injection |dependency-injection:passing-dependencies], um Werte an Objekte zu übergeben. +Konstanten sollten nicht als global verfügbare Variablen dienen. Verwenden Sie zur Übergabe von Werten an Objekte [Dependency Injection |dependency-injection:passing-dependencies]. PHP === -Einstellen von PHP-Direktiven. Eine Übersicht über alle Direktiven finden Sie auf [php.net |https://www.php.net/manual/en/ini.list.php]. +Einstellung von PHP-Direktiven. Eine Übersicht aller Direktiven finden Sie auf [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: @@ -176,16 +170,17 @@ php: ``` -DI-Dienste -========== - -Diese Dienste werden dem DI-Container hinzugefügt: +DI-Services +=========== -| Name | Typ | Beschreibung -|---------------------------------------------------------------------------------------------------------- -| `application.application` | [api:Nette\Application\Application] | [Starter der gesamten Anwendung |how-it-works#Nette Application] -| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] -| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | Factory für Presenter -| `application.###` | [api:Nette\Application\UI\Presenter] | einzelne Presenter -| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | Factory für das `Latte\Engine`-Objekt -| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | Factory für [`$this->template` |templates] +Diese Services werden in den DI-Container eingefügt: + +| Name | Typ | Beschreibung +|----------------------------|---------------------------------------------------|----------------------------------------- +| `application.application` | [api:Nette\Application\Application] | der [Anwendungsstarter |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\IPresenterFactory] | Presenter-Factory +| `application.###` | [api:Nette\Application\UI\Presenter] | einzelne Presenter +| `routing.router` | [api:Nette\Routing\Router] | Router +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | Factory für das Objekt `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | Factory für [`$this->template` |templates] diff --git a/application/de/creating-links.texy b/application/de/creating-links.texy index ff63ee2419..66821107bd 100644 --- a/application/de/creating-links.texy +++ b/application/de/creating-links.texy @@ -3,56 +3,56 @@ Erstellen von URL-Links
-Das Erstellen von Links in Nette ist so einfach wie mit dem Finger zu zeigen. Sie müssen nur zielen, und das Framework erledigt die ganze Arbeit für Sie. Wir zeigen Ihnen: +Links in Nette zu erstellen ist so einfach wie mit dem Finger zu zeigen. Sie müssen nur zielen, und das Framework erledigt die ganze Arbeit für Sie. Wir zeigen: - wie man Links in Templates und anderswo erstellt -- wie man einen Link zur aktuellen Seite unterscheidet -- was mit ungültigen Links zu tun ist +- wie man einen Link auf die aktuelle Seite erkennt +- was man mit ungültigen Links macht
-Dank [bidirektionalem Routing |routing] müssen Sie niemals fest codierte URL-Adressen Ihrer Anwendung in Templates oder Code schreiben, die sich später ändern könnten, oder sie kompliziert zusammensetzen. Im Link genügt es, den Presenter und die Aktion anzugeben, eventuelle Parameter zu übergeben, und das Framework generiert die URL selbst. Eigentlich ist es sehr ähnlich wie der Aufruf einer Funktion. Das wird Ihnen gefallen. +Dank des [bidirektionalen Routings |routing] müssen Sie URLs Ihrer Anwendung nie fest in Templates oder Code schreiben - URLs, die sich später ändern könnten oder kompliziert zusammenzusetzen wären. Im Link geben Sie einfach den Presenter und die Aktion an, übergeben eventuelle Parameter, und das Framework erzeugt die URL selbst. Es ist eigentlich sehr ähnlich wie ein Funktionsaufruf. Das wird Ihnen gefallen. -Im Presenter-Template -===================== +Im Template des Presenters +========================== -Am häufigsten erstellen wir Links in Templates, und ein großartiger Helfer ist das Attribut `n:href`: +Am häufigsten erstellen wir Links in Templates, und ein großartiger Helfer ist dabei das Attribut `n:href`: ```latte Detail ``` -Beachten Sie, dass wir anstelle des HTML-Attributs `href` das [n:Attribut |latte:syntax#n:Attribute] `n:href` verwendet haben. Sein Wert ist dann nicht eine URL, wie es beim `href`-Attribut der Fall wäre, sondern der Name des Presenters und der Aktion. +Beachten Sie, dass wir statt des HTML-Attributs `href` das [n:Attribut |latte:syntax#n:Attribute] `n:href` verwendet haben. Sein Wert ist keine URL, wie es beim Attribut `href` der Fall wäre, sondern der Name des Presenters und der Aktion. -Ein Klick auf den Link ist, vereinfacht gesagt, so etwas wie der Aufruf der Methode `ProductPresenter::renderShow()`. Und wenn diese Parameter in ihrer Signatur hat, können wir sie mit Argumenten aufrufen: +Auf einen Link zu klicken ist, vereinfacht gesagt, so etwas wie der Aufruf der Methode `ProductPresenter::renderShow()`. Und wenn diese in ihrer Signatur Parameter hat, können wir sie mit Argumenten aufrufen: ```latte Produktdetail ``` -Es ist auch möglich, benannte Parameter zu übergeben. Der folgende Link übergibt den Parameter `lang` mit dem Wert `cs`: +Es lassen sich auch benannte Parameter übergeben. Der folgende Link übergibt den Parameter `lang` mit dem Wert `en`: ```latte -Produktdetail +Produktdetail ``` -Wenn die Methode `ProductPresenter::renderShow()` `$lang` nicht in ihrer Signatur hat, kann sie den Wert des Parameters mit `$lang = $this->getParameter('lang')` oder aus der [Property |presenters#Anfrageparameter] ermitteln. +Hat die Methode `ProductPresenter::renderShow()` in ihrer Signatur kein `$lang`, kann sie den Wert des Parameters mit `$lang = $this->getParameter('lang')` oder aus einer [Property |presenters#Parameter des Requests] holen. -Wenn die Parameter in einem Array gespeichert sind, können sie mit dem Operator `...` (in Latte 2.x mit dem Operator `(expand)`) erweitert werden: +Sind die Parameter in einem Array gespeichert, lassen sie sich mit dem Operator `...` entpacken: ```latte -{var $args = [$product->id, lang => cs]} -Produktdetail +{var $args = [$product->id, lang => en]} +Produktdetail ``` -In Links werden auch automatisch sogenannte [persistente Parameter |presenters#Persistente Parameter] übergeben. +In Links werden automatisch auch die sogenannten [persistenten Parameter |presenters#Persistente Parameter] übergeben. -Das Attribut `n:href` ist sehr praktisch für HTML-Tags ``. Wenn wir einen Link an anderer Stelle ausgeben möchten, zum Beispiel im Text, verwenden wir `{link}`: +Das Attribut `n:href` ist für HTML-Tags `` sehr praktisch. Wollen wir den Link anderswo ausgeben, zum Beispiel im Text, verwenden wir `{link}`: ```latte -Die Adresse lautet: {link Home:default} +Die URL lautet: {link Home:default} ``` @@ -65,92 +65,109 @@ Zum Erstellen eines Links im Presenter dient die Methode `link()`: $url = $this->link('Product:show', $product->id); ``` -Parameter können auch über ein Array übergeben werden, wo auch benannte Parameter angegeben werden können: +Die Parameter lassen sich auch als Array übergeben, in dem sich ebenfalls benannte Parameter angeben lassen: ```php -$url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$product->id, 'lang' => 'en']); ``` -Links können auch ohne Presenter erstellt werden, dafür gibt es den [#LinkGenerator] und seine Methode `link()`. +Links lassen sich auch ohne Presenter erstellen, und zwar mit dem [#LinkGenerator] und seiner Methode `link()`. +Manchmal brauchen Sie einen Link jetzt, wollen die eigentliche URL aber erst später erzeugen. Dafür gibt es die Methode `lazyLink()`, die ein Objekt `Nette\Application\UI\Link` zurückgibt. Der Vorteil ist, dass Sie dieses Objekt weiterreichen können, zum Beispiel in ein Template, und seine Parameter vor dem Rendern noch mit der Methode `setParameter()` anpassen können. Die URL selbst wird erst zusammengesetzt, wenn das Objekt in einen String umgewandelt wird: -Links zu Presentern +```php +$link = $this->lazyLink('Product:show', $id); +// ... +echo $link; // erst hier wird die URL erzeugt +``` + + +Links auf Presenter =================== -Wenn das Ziel des Links ein Presenter und eine Aktion ist, hat er diese Syntax: +Ist das Ziel des Links ein Presenter und eine Aktion, hat er diese Syntax: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Das Format wird von allen Latte-Tags und allen Presenter-Methoden unterstützt, die mit Links arbeiten, also `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` und auch dem [#LinkGenerator]. Auch wenn in den Beispielen `n:href` verwendet wird, könnte dort jede dieser Funktionen stehen. +Dieses Format unterstützen alle Latte-Tags und alle Methoden des Presenters, die mit Links arbeiten, also `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` und auch der [#LinkGenerator]. Auch wenn in den Beispielen `n:href` verwendet wird, könnte dort also jede dieser Funktionen stehen. -Die Grundform ist also `Presenter:action`: +Die Grundform ist demnach `Presenter:action`: ```latte Startseite ``` -Wenn wir auf eine Aktion des aktuellen Presenters verlinken, können wir seinen Namen weglassen: +Verlinken wir auf eine Aktion des aktuellen Presenters, können wir dessen Namen weglassen: ```latte Startseite ``` -Wenn das Ziel die Aktion `default` ist, können wir sie weglassen, aber der Doppelpunkt muss bleiben: +Ist die Zielaktion `default`, können wir sie weglassen, der Doppelpunkt muss aber bleiben: ```latte Startseite ``` -Links können auch zu anderen [Modulen |directory-structure#Presenter und Templates] führen. Hier werden Links in relative zu verschachtelten Submodulen oder absolute unterschieden. Das Prinzip ist analog zu Pfaden auf der Festplatte, nur dass anstelle von Schrägstrichen Doppelpunkte verwendet werden. Angenommen, der aktuelle Presenter ist Teil des Moduls `Front`, dann schreiben wir: +Links können auch auf andere [Module |directory-structure#Presenter und Templates] zeigen. Dabei unterscheidet man Links, die relativ zu einem verschachtelten Untermodul sind, und absolute Links. Das Prinzip ist analog zu Pfaden auf der Festplatte, nur werden statt Schrägstrichen Doppelpunkte verwendet. Nehmen wir an, der aktuelle Presenter gehört zum Modul `Front`, dann schreiben wir: ```latte -Link zu Front:Shop:Product:show -Link zu Admin:Product:show +Link auf Front:Shop:Product:show +Link auf Admin:Product:show ``` -Ein Sonderfall ist der Link [auf sich selbst |#Link zur aktuellen Seite], bei dem wir als Ziel `this` angeben. +Ein Sonderfall ist ein Link [auf sich selbst |#Link auf die aktuelle Seite], bei dem wir als Ziel `this` angeben. ```latte aktualisieren ``` -Wir können auf einen bestimmten Teil der Seite über das sogenannte Fragment nach dem Rautezeichen `#` verlinken: +Auf einen bestimmten Teil der Seite können wir über ein sogenanntes Fragment nach dem Rautezeichen `#` verlinken: ```latte -Link zu Home:default und Fragment #main +Link auf Home:default und das Fragment #main +``` + +.{data-version:3.3.0} +Das Fragment lässt sich auch dynamisch als Argument mit dem Schlüssel `#` setzen. Sein Wert wird automatisch kodiert und hat Vorrang vor dem im Ziel angegebenen Fragment: + +```php +$this->link('Home:default', ['#' => $fragment]); ``` Absolute Pfade ============== -Links, die mit `link()` oder `n:href` generiert werden, sind immer absolute Pfade (d. h. sie beginnen mit einem `/`), aber keine absoluten URLs mit Protokoll und Domain wie `https://domain`. +Mit `link()` oder `n:href` erzeugte Links sind immer absolute Pfade (sie beginnen also mit `/`), aber keine absoluten URLs mit Protokoll und Domain wie `https://domain`. + +Um eine absolute URL zu erzeugen, ergänzen Sie am Anfang zwei Schrägstriche (z. B. `n:href="//Home:"`). Alternativ können Sie den Presenter mit `$this->absoluteUrls = true` so umschalten, dass er nur absolute Links erzeugt. -Um eine absolute URL zu generieren, fügen Sie am Anfang zwei Schrägstriche hinzu (z. B. `n:href="//Home:"`). Oder Sie können den Presenter so umschalten, dass er nur absolute Links generiert, indem Sie `$this->absoluteUrls = true` setzen. +Im Template lässt sich außerdem der Filter `|absoluteUrl` verwenden, der einen relativen Pfad in einen absoluten umwandelt. -Link zur aktuellen Seite -======================== +Link auf die aktuelle Seite +=========================== -Das Ziel `this` erstellt einen Link zur aktuellen Seite: +Das Ziel `this` erzeugt einen Link auf die aktuelle Seite: ```latte aktualisieren ``` -Gleichzeitig werden auch alle Parameter übertragen, die in der Signatur der Methode `action()` oder `render()` angegeben sind, falls `action()` nicht definiert ist. Wenn wir also auf der Seite `Product:show` mit `id: 123` sind, übergibt der Link auf `this` auch diesen Parameter. +Dabei werden zugleich alle Parameter übernommen, die in der Signatur der Methode `action()` oder `render()` angegeben sind (sofern `action()` nicht definiert ist). Sind wir also auf der Seite `Product:show` mit `id: 123`, übergibt der Link auf `this` diesen Parameter ebenfalls. -Natürlich ist es möglich, Parameter direkt anzugeben: +Natürlich lassen sich Parameter auch direkt angeben: ```latte aktualisieren ``` -Die Funktion `isLinkCurrent()` prüft, ob das Ziel des Links mit der aktuellen Seite übereinstimmt. Dies kann beispielsweise in einem Template verwendet werden, um Links zu unterscheiden usw. +Die Funktion `isLinkCurrent()` prüft, ob das Ziel des Links mit der aktuellen Seite identisch ist. Das lässt sich zum Beispiel im Template nutzen, um Links optisch zu unterscheiden usw. -Die Parameter sind die gleichen wie bei der Methode `link()`, zusätzlich ist es jedoch möglich, anstelle einer konkreten Aktion den Platzhalter `*` anzugeben, der jede Aktion des gegebenen Presenters bedeutet. +Die Parameter sind dieselben wie bei der Methode `link()`, zusätzlich lässt sich anstelle einer konkreten Aktion die Wildcard `*` verwenden, was jede beliebige Aktion des angegebenen Presenters bedeutet. ```latte {if !isLinkCurrent('Admin:login')} @@ -162,15 +179,15 @@ Die Parameter sind die gleichen wie bei der Methode `link()`, zusätzlich ist es ``` -In Kombination mit `n:href` in einem Element kann eine verkürzte Form verwendet werden: +In Kombination mit `n:href` im selben Element lässt sich eine Kurzform verwenden: ```latte ... ``` -Der Platzhalter `*` kann nur anstelle der Aktion verwendet werden, nicht für den Presenter. +Die Wildcard `*` lässt sich nur anstelle der Aktion verwenden, nicht anstelle des Presenters. -Um festzustellen, ob wir uns in einem bestimmten Modul oder dessen Submodul befinden, verwenden wir die Methode `isModuleCurrent(moduleName)`. +Um festzustellen, ob wir uns in einem bestimmten Modul oder dessen Untermodul befinden, dient die Methode `isModuleCurrent(moduleName)`. ```latte
  • @@ -179,10 +196,26 @@ Um festzustellen, ob wir uns in einem bestimmten Modul oder dessen Submodul befi ``` -Links zu Signalen +Link-Basis ändern .{data-version:3.2.7} +======================================= + +Standardmäßig werden relative Links vom aktuellen Presenter abgeleitet. Das lässt sich mit `{linkBase}` ändern: + +```latte +{linkBase Admin:Dashboard} +Produktdetail +``` + +Der Link führt dann auf `Admin:Dashboard:Product:show`. Betroffen sind nur relative Links - absolute Links, die mit einem Doppelpunkt beginnen, und Links auf den aktuellen Presenter (`this`, `show`) bleiben unverändert. + +`{linkBase}` gilt für das gesamte Template und ist besonders in Layout-Templates nützlich, wo es unabhängig vom aufrufenden Presenter für einheitliche Links sorgt. +Der Tag muss am Anfang des Templates stehen, sonst wirft er eine `CompileException`. + + +Links auf Signale ================= -Das Ziel eines Links muss nicht nur ein Presenter und eine Aktion sein, sondern auch ein [Signal |components#Signal] (sie rufen die Methode `handle()` auf). Dann lautet die Syntax wie folgt: +Das Ziel eines Links muss nicht nur ein Presenter und eine Aktion sein, sondern kann auch ein [Signal |components#Signal] sein (es ruft die Methode `handle()` auf). Dann sieht die Syntax so aus: ``` [//] [sub-component:]signal! [#fragment] @@ -194,7 +227,7 @@ Das Signal wird also durch ein Ausrufezeichen unterschieden: Signal ``` -Es kann auch ein Link zum Signal einer Subkomponente (oder Sub-Subkomponente) erstellt werden: +Sie können auch einen Link auf ein Signal einer Unterkomponente (oder Unter-Unterkomponente) erstellen: ```latte Signal @@ -204,13 +237,13 @@ Es kann auch ein Link zum Signal einer Subkomponente (oder Sub-Subkomponente) er Links in einer Komponente ========================= -Da [Komponenten |components] separate wiederverwendbare Einheiten sind, die keine Bindungen an umgebende Presenter haben sollten, funktionieren Links hier etwas anders. Das Latte-Attribut `n:href` und der Tag `{link}` sowie Komponentenmethoden wie `link()` und andere betrachten das Linkziel **immer als den Namen des Signals**. Daher ist es nicht notwendig, ein Ausrufezeichen anzugeben: +Da [Komponenten|components] eigenständige, wiederverwendbare Einheiten sind, die keinerlei Bindung an umgebende Presenter haben sollten, funktionieren Links hier etwas anders. Das Latte-Attribut `n:href` und der Tag `{link}` sowie Methoden der Komponente wie `link()` und weitere **betrachten das Ziel des Links immer als Namen eines Signals**. Deshalb muss nicht einmal ein Ausrufezeichen angegeben werden: ```latte -Signal, nicht Aktion +Signal, keine Aktion ``` -Wenn wir im Template einer Komponente auf Presenter verlinken möchten, verwenden wir dazu den Tag `{plink}`: +Wollen wir im Template der Komponente auf Presenter verlinken, verwenden wir den Tag `{plink}`: ```latte Startseite @@ -223,12 +256,12 @@ $this->getPresenter()->link('Home:default') ``` -Aliase .{data-version:v3.2.2} -============================= +Aliase .{data-version:3.2.3} +============================ -Manchmal kann es nützlich sein, dem Paar Presenter:Aktion einen leicht zu merkenden Alias zuzuordnen. Zum Beispiel die Startseite `Front:Home:default` einfach als `home` benennen oder `Admin:Dashboard:default` als `admin`. +Manchmal ist es praktisch, einem Paar Presenter:action einen leicht merkbaren Alias zuzuweisen. Zum Beispiel die Startseite `Front:Home:default` einfach `home` zu nennen oder `Admin:Dashboard:default` als `admin`. -Aliase werden in der [Konfiguration |configuration] unter dem Schlüssel `application › aliases` definiert: +Aliase werden in der [Konfiguration|configuration] unter dem Schlüssel `application › aliases` definiert: ```neon application: @@ -238,26 +271,26 @@ application: sign: Front:Sign:in ``` -In Links werden sie dann mit einem At-Zeichen geschrieben, zum Beispiel: +In Links schreibt man sie dann mit einem At-Zeichen, zum Beispiel: ```latte Administration ``` -Sie werden auch in allen Methoden unterstützt, die mit Links arbeiten, wie `redirect()` und ähnliche. +Unterstützt werden sie auch in allen Methoden, die mit Links arbeiten, etwa `redirect()` und ähnlichen. Ungültige Links =============== -Es kann vorkommen, dass wir einen ungültigen Link erstellen – entweder weil er auf einen nicht existierenden Presenter verweist, oder weil er mehr Parameter übergibt, als die Zielmethode in ihrer Signatur akzeptiert, oder wenn für die Zielaktion keine URL generiert werden kann. Wie mit ungültigen Links umgegangen wird, bestimmt die statische Variable `Presenter::$invalidLinkMode`. Diese kann eine Kombination der folgenden Werte (Konstanten) annehmen: +Es kann passieren, dass wir einen ungültigen Link erzeugen - entweder weil er auf einen nicht existierenden Presenter führt, oder weil er mehr Parameter übergibt, als die Zielmethode in ihrer Signatur entgegennimmt, oder wenn für die Zielaktion keine URL erzeugt werden kann. Wie mit ungültigen Links umgegangen wird, legt man im Presenter über `$this->invalidLinkMode` fest. Es kann eine Kombination dieser Werte (Konstanten) annehmen: -- `Presenter::InvalidLinkSilent` - stiller Modus, als URL wird das Zeichen # zurückgegeben -- `Presenter::InvalidLinkWarning` - es wird eine E_USER_WARNING-Warnung ausgegeben, die im Produktionsmodus protokolliert wird, aber die Skriptausführung nicht unterbricht -- `Presenter::InvalidLinkTextual` - visuelle Warnung, gibt den Fehler direkt im Link aus -- `Presenter::InvalidLinkException` - es wird eine InvalidLinkException-Ausnahme ausgelöst +- `Presenter::InvalidLinkSilent` - stiller Modus, gibt als URL das Zeichen # zurück +- `Presenter::InvalidLinkWarning` - es wird eine Warnung E_USER_WARNING ausgelöst, die im Produktionsmodus protokolliert wird, die Ausführung des Skripts aber nicht unterbricht +- `Presenter::InvalidLinkTextual` - visuelle Warnung, gibt den Fehler direkt in den Link aus +- `Presenter::InvalidLinkException` - wirft eine InvalidLinkException -Die Standardeinstellung ist `InvalidLinkWarning` im Produktionsmodus und `InvalidLinkWarning | InvalidLinkTextual` im Entwicklungsmodus. `InvalidLinkWarning` im Produktionsmodus führt nicht zur Unterbrechung des Skripts, aber die Warnung wird protokolliert. Im Entwicklungsmodus wird sie von [Tracy |tracy:] abgefangen und zeigt einen Bluescreen an. `InvalidLinkTextual` funktioniert so, dass als URL eine Fehlermeldung zurückgegeben wird, die mit den Zeichen `#error:` beginnt. Damit solche Links auf den ersten Blick erkennbar sind, ergänzen wir unser CSS: +Die Standardeinstellung ist `InvalidLinkWarning` im Produktionsmodus und `InvalidLinkWarning | InvalidLinkTextual` im Entwicklungsmodus. `InvalidLinkWarning` bewirkt in der Produktionsumgebung keinen Abbruch des Skripts, die Warnung wird jedoch protokolliert. In der Entwicklungsumgebung fängt [Tracy |tracy:] sie ab und zeigt einen Bluescreen. `InvalidLinkTextual` funktioniert so, dass es als URL eine Fehlermeldung zurückgibt, die mit den Zeichen `#error:` beginnt. Damit solche Links auf den ersten Blick auffallen, ergänzen Sie Ihr CSS um: ```css a[href^="#error:"] { @@ -266,7 +299,7 @@ a[href^="#error:"] { } ``` -Wenn wir nicht möchten, dass im Entwicklungsmodus Warnungen erzeugt werden, können wir den stillen Modus direkt in der [Konfiguration |configuration] einstellen. +Wollen wir, dass in der Entwicklungsumgebung keine Warnungen entstehen, können wir sie direkt in der [Konfiguration|configuration] unterdrücken. ```neon application: @@ -277,10 +310,10 @@ application: LinkGenerator ============= -Wie erstellt man Links mit ähnlichem Komfort wie die Methode `link()`, aber ohne die Anwesenheit eines Presenters? Dafür gibt es den [api:Nette\Application\LinkGenerator]. +Wie erstellt man Links mit ähnlichem Komfort wie mit der Methode `link()`, aber ohne die Anwesenheit eines Presenters? Dafür ist [api:Nette\Application\LinkGenerator] da. -Der LinkGenerator ist ein Dienst, den Sie sich über den Konstruktor übergeben lassen und dann Links mit seiner Methode `link()` erstellen können. +Der LinkGenerator ist ein Service, den Sie sich über den Konstruktor übergeben lassen können und mit dessen Methode `link()` Sie dann Links erstellen. -Im Vergleich zu Presentern gibt es hier einen Unterschied. Der LinkGenerator erstellt alle Links direkt als absolute URLs. Außerdem gibt es keinen "aktuellen Presenter", sodass man als Ziel nicht nur den Aktionsnamen `link('default')` angeben oder relative Pfade zu Modulen verwenden kann. +Gegenüber Presentern gibt es einen Unterschied. Der LinkGenerator erzeugt alle Links direkt als absolute URLs. Außerdem gibt es keinen "aktuellen Presenter", daher lässt sich als Ziel weder nur der Name der Aktion `link('default')` angeben noch lassen sich relative Pfade zu Modulen verwenden. -Ungültige Links lösen immer eine `Nette\Application\UI\InvalidLinkException` aus. +Ungültige Links werfen immer eine `Nette\Application\UI\InvalidLinkException`. diff --git a/application/de/directory-structure.texy b/application/de/directory-structure.texy index c7c34615b7..450885cdd4 100644 --- a/application/de/directory-structure.texy +++ b/application/de/directory-structure.texy @@ -5,41 +5,41 @@ Verzeichnisstruktur der Anwendung Wie entwirft man eine übersichtliche und skalierbare Verzeichnisstruktur für Projekte im Nette Framework? Wir zeigen Ihnen bewährte Praktiken, die Ihnen bei der Organisation Ihres Codes helfen. Sie erfahren: -- wie Sie die Anwendung **logisch in Verzeichnisse gliedern** -- wie Sie die Struktur so gestalten, dass sie mit dem Wachstum des Projekts **gut skaliert** -- welche **möglichen Alternativen** es gibt und welche Vor- oder Nachteile sie haben +- wie man die Anwendung **logisch** in Verzeichnisse gliedert +- wie man die Struktur so entwirft, dass sie mit wachsendem Projekt **gut skaliert** +- welche **möglichen Alternativen** es gibt und welche Vor- und Nachteile sie haben
  • -Es ist wichtig zu erwähnen, dass das Nette Framework selbst keine bestimmte Struktur vorschreibt. Es ist so konzipiert, dass es sich leicht an alle Bedürfnisse und Präferenzen anpassen lässt. +Wichtig ist zu erwähnen, dass das Nette Framework selbst keine bestimmte Struktur erzwingt. Es ist so gestaltet, dass es sich leicht an beliebige Bedürfnisse und Vorlieben anpassen lässt. Grundlegende Projektstruktur ============================ -Obwohl das Nette Framework keine feste Verzeichnisstruktur vorschreibt, gibt es eine bewährte Standardanordnung in Form des [Web Project|https://github.com/nette/web-project]: +Auch wenn das Nette Framework keine feste Verzeichnisstruktur vorschreibt, gibt es eine bewährte Standardaufteilung in Form des [Web Project|https://github.com/nette/web-project]: /--pre web-project/ -├── app/ ← Anwendungsverzeichnis -├── assets/ ← SCSS-, JS-Dateien, Bilder..., alternativ resources/ -├── bin/ ← Skripte für die Befehlszeile +├── app/ ← Verzeichnis der Anwendung +├── assets/ ← SCSS-, JS-Dateien, Bilder…, alternativ resources/ +├── bin/ ← Skripte für die Kommandozeile ├── config/ ← Konfiguration ├── log/ ← protokollierte Fehler ├── temp/ ← temporäre Dateien, Cache ├── tests/ ← Tests -├── vendor/ ← Bibliotheken, die mit Composer installiert wurden +├── vendor/ ← von Composer installierte Bibliotheken └── www/ ← öffentliches Verzeichnis (Document-Root) \-- -Sie können diese Struktur beliebig an Ihre Bedürfnisse anpassen – Ordner umbenennen oder verschieben. Anschließend müssen Sie nur die relativen Pfade zu den Verzeichnissen in der Datei `Bootstrap.php` und gegebenenfalls `composer.json` anpassen. Mehr ist nicht nötig, keine komplexe Neukonfiguration, keine Änderung von Konstanten. Nette verfügt über eine intelligente Autoerkennung und erkennt automatisch den Speicherort der Anwendung einschließlich ihrer URL-Basis. +Sie können diese Struktur nach Ihren Bedürfnissen frei anpassen - Ordner umbenennen oder verschieben. Danach müssen Sie lediglich die relativen Pfade zu den Verzeichnissen in `Bootstrap.php` und gegebenenfalls in `composer.json` anpassen. Mehr ist nicht nötig, keine komplizierte Neukonfiguration, keine Änderungen an Konstanten. Nette hat eine intelligente Autodetection und erkennt den Ort der Anwendung samt ihrer Basis-URL automatisch. Prinzipien der Code-Organisation ================================ -Wenn Sie ein neues Projekt zum ersten Mal untersuchen, sollten Sie sich schnell darin zurechtfinden. Stellen Sie sich vor, Sie klicken auf das Verzeichnis `app/Model/` und sehen diese Struktur: +Wenn Sie ein neues Projekt zum ersten Mal erkunden, sollten Sie sich schnell zurechtfinden. Stellen Sie sich vor, Sie klicken auf das Verzeichnis `app/Model/` und sehen diese Struktur: /--pre app/Model/ @@ -48,9 +48,9 @@ Wenn Sie ein neues Projekt zum ersten Mal untersuchen, sollten Sie sich schnell └── Entities/ \-- -Daraus können Sie nur entnehmen, dass das Projekt einige Dienste, Repositories und Entitäten verwendet. Über den tatsächlichen Zweck der Anwendung erfahren Sie überhaupt nichts. +Daraus erfahren Sie nur, dass das Projekt irgendwelche Services, Repositories und Entities verwendet. Über den eigentlichen Zweck der Anwendung erfahren Sie nichts. -Schauen wir uns einen anderen Ansatz an – die **Organisation nach Domänen**: +Schauen wir uns einen anderen Ansatz an - die **Organisation nach Domänen**: /--pre app/Model/ @@ -60,62 +60,62 @@ Schauen wir uns einen anderen Ansatz an – die **Organisation nach Domänen**: └── Product/ \-- -Hier ist es anders – auf den ersten Blick ist klar, dass es sich um einen E-Shop handelt. Schon die Verzeichnisnamen verraten, was die Anwendung kann – sie arbeitet mit Zahlungen, Bestellungen und Produkten. +Hier ist es anders - auf den ersten Blick ist klar, dass es sich um einen E-Shop handelt. Schon die Verzeichnisnamen verraten, was die Anwendung kann: Sie arbeitet mit Zahlungen, Bestellungen und Produkten. -Der erste Ansatz (Organisation nach Klassentypen) bringt in der Praxis eine Reihe von Problemen mit sich: Code, der logisch zusammenhängt, ist auf verschiedene Ordner verteilt, und Sie müssen zwischen ihnen hin- und herspringen. Deshalb werden wir nach Domänen organisieren. +Der erste Ansatz (Organisation nach Klassentyp) bringt in der Praxis mehrere Probleme mit sich: Logisch zusammengehöriger Code ist über verschiedene Ordner verstreut, und Sie müssen zwischen ihnen hin- und herspringen. Deshalb organisieren wir nach Domänen. Namespaces ---------- -Es ist üblich, dass die Verzeichnisstruktur mit den Namespaces in der Anwendung korrespondiert. Das bedeutet, dass der physische Speicherort der Dateien ihrem Namespace entspricht. Zum Beispiel sollte eine Klasse, die sich in `app/Model/Product/ProductRepository.php` befindet, den Namespace `App\Model\Product` haben. Dieses Prinzip hilft bei der Orientierung im Code und vereinfacht das Autoloading. +Es ist üblich, dass die Verzeichnisstruktur den Namespaces in der Anwendung entspricht. Das bedeutet, dass der physische Ort der Dateien ihrem Namespace entspricht. Eine Klasse in `app/Model/Product/ProductRepository.php` sollte zum Beispiel den Namespace `App\Model\Product` haben. Dieses Prinzip hilft beim Navigieren im Code und vereinfacht das Autoloading. Singular vs. Plural in Namen ---------------------------- -Beachten Sie, dass wir für die Hauptverzeichnisse der Anwendung den Singular verwenden: `app`, `config`, `log`, `temp`, `www`. Ebenso innerhalb der Anwendung: `Model`, `Core`, `Presentation`. Das liegt daran, dass jedes dieser Verzeichnisse ein zusammenhängendes Konzept darstellt. +Beachten Sie, dass wir für die Hauptverzeichnisse der Anwendung den Singular verwenden: `app`, `config`, `log`, `temp`, `www`. Dasselbe gilt innerhalb der Anwendung: `Model`, `Core`, `Presentation`. Der Grund ist, dass jedes davon ein einziges zusammenhängendes Konzept darstellt. -Ähnlich repräsentiert z.B. `app/Model/Product` alles rund um Produkte. Wir nennen es nicht `Products`, weil es sich nicht um einen Ordner voller Produkte handelt (dann wären dort Dateien wie `nokia.php`, `samsung.php`). Es ist ein Namespace, der Klassen für die Arbeit mit Produkten enthält – `ProductRepository.php`, `ProductService.php`. +Ebenso stellt `app/Model/Product` alles dar, was mit Produkten zu tun hat. Wir nennen es nicht `Products`, weil es kein Ordner voller Produkte ist (der würde Dateien wie `nokia.php`, `samsung.php` enthalten). Es ist ein Namespace mit Klassen für die Arbeit mit Produkten - `ProductRepository.php`, `ProductService.php`. -Der Ordner `app/Tasks` steht im Plural, weil er einen Satz eigenständiger ausführbarer Skripte enthält – `CleanupTask.php`, `ImportTask.php`. Jedes davon ist eine eigenständige Einheit. +Der Ordner `app/Tasks` steht im Plural, weil er eine Menge eigenständiger ausführbarer Skripte enthält - `CleanupTask.php`, `ImportTask.php`. Jedes davon ist eine unabhängige Einheit. -Zur Konsistenz empfehlen wir die Verwendung von: -- Singular für einen Namespace, der eine funktionale Einheit repräsentiert (auch wenn er mit mehreren Entitäten arbeitet) -- Plural für Sammlungen eigenständiger Einheiten -- Im Zweifelsfall oder wenn Sie nicht darüber nachdenken möchten, wählen Sie den Singular +Aus Gründen der Konsistenz empfehlen wir: +- Singular für Namespaces, die eine funktionale Einheit darstellen (auch wenn sie mit mehreren Entities arbeiten) +- Plural für Sammlungen unabhängiger Einheiten +- Im Zweifel, oder wenn Sie nicht darüber nachdenken wollen, wählen Sie den Singular Öffentliches Verzeichnis `www/` =============================== -Dieses Verzeichnis ist das einzige, das vom Web aus zugänglich ist (sog. Document-Root). Oft trifft man auch auf den Namen `public/` anstelle von `www/` – das ist nur eine Frage der Konvention und hat keinen Einfluss auf die Funktionalität des Frameworks. Das Verzeichnis enthält: -- Den [Einstiegspunkt |bootstrapping#index.php] der Anwendung `index.php` -- Die Datei `.htaccess` mit Regeln für mod_rewrite (bei Apache) -- Statische Dateien (CSS, JavaScript, Bilder) -- Hochgeladene Dateien +Dieses Verzeichnis ist das einzige, das aus dem Web erreichbar ist (der Document-Root). Häufig begegnet Ihnen statt `www/` der Name `public/` - das ist reine Konventionssache und hat keinen Einfluss auf die Funktion der Anwendung. Das Verzeichnis enthält: +- den [Einstiegspunkt |bootstrapping#index.php] der Anwendung `index.php` +- die Datei `.htaccess` mit den Regeln für mod_rewrite (für Apache) +- statische Dateien (CSS, JavaScript, Bilder) +- hochgeladene Dateien -Für die korrekte Sicherheit der Anwendung ist es entscheidend, den [konfigurierten Document-Root |nette:troubleshooting#Wie ändert oder entfernt man das Verzeichnis www aus der URL] richtig eingestellt zu haben. +Für die richtige Absicherung der Anwendung ist es entscheidend, den [Document-Root korrekt zu konfigurieren |nette:troubleshooting#Wie ändert oder entfernt man das Verzeichnis www aus der URL?]. .[note] -Platzieren Sie niemals den Ordner `node_modules/` in diesem Verzeichnis – er enthält Tausende von Dateien, die ausführbar sein könnten und nicht öffentlich zugänglich sein sollten. +Legen Sie den Ordner `node_modules/` niemals in dieses Verzeichnis - er enthält Tausende von Dateien, die ausführbar sein können und nicht öffentlich zugänglich sein sollten. -Anwendungsverzeichnis `app/` -============================ +Verzeichnis der Anwendung `app/` +================================ -Dies ist das Hauptverzeichnis mit dem Anwendungscode. Die Grundstruktur: +Das ist das Hauptverzeichnis mit dem Code der Anwendung. Grundstruktur: /--pre app/ -├── Core/ ← Infrastrukturangelegenheiten +├── Core/ ← infrastrukturelle Belange ├── Model/ ← Geschäftslogik ├── Presentation/ ← Presenter und Templates -├── Tasks/ ← Befehlszeilenskripte -└── Bootstrap.php ← Bootstrap-Klasse der Anwendung +├── Tasks/ ← Kommandoskripte +└── Bootstrap.php ← Startklasse der Anwendung \-- -`Bootstrap.php` ist die [Startklasse der Anwendung|bootstrapping], die die Umgebung initialisiert, die Konfiguration lädt und den DI-Container erstellt. +`Bootstrap.php` ist die [Startklasse der Anwendung|bootstrapping], die die Umgebung initialisiert, die Konfiguration lädt und den DI-Container erzeugt. Schauen wir uns nun die einzelnen Unterverzeichnisse genauer an. @@ -123,13 +123,13 @@ Schauen wir uns nun die einzelnen Unterverzeichnisse genauer an. Presenter und Templates ======================= -Der Präsentationsteil der Anwendung befindet sich im Verzeichnis `app/Presentation`. Eine Alternative ist das kurze `app/UI`. Dies ist der Ort für alle Presenter, ihre Templates und eventuelle Hilfsklassen. +Der Präsentationsteil der Anwendung liegt im Verzeichnis `app/Presentation`. Eine Alternative ist das kürzere `app/UI`. Das ist der Ort für alle Presenter, ihre Templates und eventuelle zugehörige Hilfsklassen. -Diese Schicht organisieren wir nach Domänen. In einem komplexen Projekt, das einen E-Shop, einen Blog und eine API kombiniert, würde die Struktur so aussehen: +Diese Schicht organisieren wir nach Domänen. In einem komplexen Projekt, das E-Shop, Blog und API vereint, sähe die Struktur so aus: /--pre app/Presentation/ -├── Shop/ ← E-Shop Frontend +├── Shop/ ← Frontend des E-Shops │ ├── Product/ │ ├── Cart/ │ └── Order/ @@ -143,7 +143,7 @@ Diese Schicht organisieren wir nach Domänen. In einem komplexen Projekt, das ei └── V1/ \-- -Bei einem einfachen Blog hingegen würden wir folgende Gliederung verwenden: +Für einen einfachen Blog würden wir dagegen folgende Struktur verwenden: /--pre app/Presentation/ @@ -154,12 +154,12 @@ Bei einem einfachen Blog hingegen würden wir folgende Gliederung verwenden: │ ├── Dashboard/ │ └── Posts/ ├── Error/ -└── Export/ ← RSS, Sitemaps etc. +└── Export/ ← RSS, Sitemaps usw. \-- -Ordner wie `Home/` oder `Dashboard/` enthalten Presenter und Templates. Ordner wie `Front/`, `Admin/` oder `Api/` nennen wir **Module**. Technisch gesehen sind dies normale Verzeichnisse, die zur logischen Gliederung der Anwendung dienen. +Ordner wie `Home/` oder `Dashboard/` enthalten Presenter und Templates. Ordner wie `Front/`, `Admin/` oder `Api/` nennt man **Module**. Technisch sind das gewöhnliche Verzeichnisse, die der logischen Aufteilung der Anwendung dienen. -Jeder Ordner mit einem Presenter enthält den gleichnamigen Presenter und seine Templates. Zum Beispiel enthält der Ordner `Dashboard/`: +Jeder Ordner mit einem Presenter enthält die Presenter-Datei selbst und ihre Templates. Der Ordner `Dashboard/` enthält zum Beispiel: /--pre Dashboard/ @@ -167,7 +167,7 @@ Jeder Ordner mit einem Presenter enthält den gleichnamigen Presenter und seine └── default.latte ← Template \-- -Diese Verzeichnisstruktur spiegelt sich in den Namespaces der Klassen wider. Zum Beispiel befindet sich `DashboardPresenter` im Namespace `App\Presentation\Admin\Dashboard` (siehe [#Presenter-Mapping]): +Diese Verzeichnisstruktur spiegelt sich in den Namespaces der Klassen wider. `DashboardPresenter` liegt zum Beispiel im Namespace `App\Presentation\Admin\Dashboard` (siehe [#Presenter-Mapping]): ```php namespace App\Presentation\Admin\Dashboard; @@ -178,13 +178,13 @@ class DashboardPresenter extends Nette\Application\UI\Presenter } ``` -Auf den Presenter `Dashboard` innerhalb des Moduls `Admin` verweisen wir in der Anwendung mittels Doppelpunktnotation als `Admin:Dashboard`. Auf seine Aktion `default` dann als `Admin:Dashboard:default`. Bei verschachtelten Modulen verwenden wir mehrere Doppelpunkte, zum Beispiel `Shop:Order:Detail:default`. +Auf den Presenter `Dashboard` im Modul `Admin` verweisen wir in der Anwendung mit der Doppelpunkt-Schreibweise als `Admin:Dashboard`. Seine Aktion `default` wird dann als `Admin:Dashboard:default` bezeichnet. Bei verschachtelten Modulen verwenden wir mehrere Doppelpunkte, zum Beispiel `Shop:Order:Detail:default`. -Flexible Strukturentwicklung ----------------------------- +Flexible Entwicklung der Struktur +--------------------------------- -Einer der großen Vorteile dieser Struktur ist, wie elegant sie sich an die wachsenden Anforderungen des Projekts anpasst. Nehmen wir als Beispiel den Teil, der XML-Feeds generiert. Am Anfang haben wir eine einfache Form: +Einer der großen Vorteile dieser Struktur ist, wie elegant sie sich an wachsende Anforderungen des Projekts anpasst. Nehmen wir als Beispiel den Teil, der XML-Feeds erzeugt. Am Anfang haben wir eine einfache Form: /--pre Export/ @@ -193,7 +193,7 @@ Einer der großen Vorteile dieser Struktur ist, wie elegant sie sich an die wach └── feed.latte ← Template für den RSS-Feed \-- -Mit der Zeit kommen weitere Feed-Typen hinzu und wir benötigen mehr Logik für sie... Kein Problem! Der Ordner `Export/` wird einfach zu einem Modul: +Mit der Zeit kommen weitere Feed-Typen hinzu, und wir brauchen mehr Logik dafür … Kein Problem! Der Ordner `Export/` wird einfach zu einem Modul: /--pre Export/ @@ -202,47 +202,47 @@ Mit der Zeit kommen weitere Feed-Typen hinzu und wir benötigen mehr Logik für │ └── sitemap.latte └── Feed/ ├── FeedPresenter.php - ├── zbozi.latte ← Feed für Zboží.cz - └── heureka.latte ← Feed für Heureka.cz + ├── amazon.latte ← Feed für Amazon + └── ebay.latte ← Feed für eBay \-- -Diese Transformation ist absolut nahtlos – es genügt, neue Unterordner zu erstellen, den Code darin aufzuteilen und die Links zu aktualisieren (z.B. von `Export:feed` zu `Export:Feed:zbozi`). Dadurch können wir die Struktur nach Bedarf schrittweise erweitern, die Verschachtelungsebene ist in keiner Weise begrenzt. +Diese Umwandlung verläuft völlig reibungslos - legen Sie einfach neue Unterordner an, verteilen Sie den Code darauf und aktualisieren Sie die Links (z. B. von `Export:feed` auf `Export:Feed:amazon`). Dadurch können wir die Struktur nach Bedarf schrittweise erweitern, die Verschachtelungstiefe ist in keiner Weise begrenzt. -Wenn Sie beispielsweise in der Administration viele Presenter haben, die sich auf die Verwaltung von Bestellungen beziehen, wie `OrderDetail`, `OrderEdit`, `OrderDispatch` usw., können Sie zur besseren Organisation an dieser Stelle ein Modul (Ordner) `Order` erstellen, in dem sich die (Ordner für die) Presenter `Detail`, `Edit`, `Dispatch` und weitere befinden. +Wenn Sie zum Beispiel in der Administration viele Presenter rund um die Verwaltung von Bestellungen haben, etwa `OrderDetail`, `OrderEdit`, `OrderDispatch` usw., können Sie zur besseren Organisation ein Modul (einen Ordner) namens `Order` anlegen, das (Ordner für) die Presenter `Detail`, `Edit`, `Dispatch` und weitere enthält. -Platzierung von Templates -------------------------- +Ort der Templates +----------------- -In den vorherigen Beispielen haben wir gesehen, dass die Templates direkt im Ordner mit dem Presenter platziert sind: +In den vorherigen Beispielen haben wir gesehen, dass die Templates direkt im Ordner mit dem Presenter liegen: /--pre Dashboard/ ├── DashboardPresenter.php ← Presenter -├── DashboardTemplate.php ← optionale Klasse für das Template +├── DashboardTemplate.php ← optionale Template-Klasse └── default.latte ← Template \-- -Diese Platzierung erweist sich in der Praxis als am bequemsten – Sie haben alle zusammengehörigen Dateien sofort zur Hand. +Dieser Ort erweist sich in der Praxis als der bequemste - Sie haben alle zusammengehörigen Dateien griffbereit. -Alternativ können Sie die Templates in einem Unterordner `templates/` platzieren. Nette unterstützt beide Varianten. Sie können Templates sogar ganz außerhalb des `Presentation/`-Ordners platzieren. Alles über die Möglichkeiten zur Platzierung von Templates finden Sie im Kapitel [Suche nach Templates |templates#Finden von Vorlagen]. +Alternativ können Sie die Templates in einen Unterordner `templates/` legen. Nette unterstützt beide Varianten. Sie können die Templates sogar vollständig außerhalb des Ordners `Presentation/` ablegen. Alles über die Möglichkeiten des Template-Orts finden Sie im Kapitel [Suche nach Templates |templates#Suche nach Templates]. Hilfsklassen und Komponenten ---------------------------- -Zu Presentern und Templates gehören oft auch weitere Hilfsdateien. Wir platzieren sie logisch nach ihrem Wirkungsbereich: +Zu Presentern und Templates kommen oft weitere Hilfsdateien. Wir platzieren sie logisch nach ihrem Geltungsbereich: -1. **Direkt beim Presenter** im Falle spezifischer Komponenten für den jeweiligen Presenter: +1. **Direkt beim Presenter** im Fall von Komponenten, die für diesen Presenter spezifisch sind: /--pre Product/ ├── ProductPresenter.php -├── ProductGrid.php ← Komponente zur Produktauflistung -└── FilterForm.php ← Formular zur Filterung +├── ProductGrid.php ← Komponente für die Produktauflistung +└── FilterForm.php ← Formular zum Filtern \-- -2. **Für das Modul** – wir empfehlen die Verwendung des Ordners `Accessory`, der übersichtlich gleich am Anfang des Alphabets platziert wird: +2. **Für das Modul** - wir empfehlen den Ordner `Accessory`, der alphabetisch praktischerweise ganz am Anfang steht: /--pre Front/ @@ -253,7 +253,7 @@ Zu Presentern und Templates gehören oft auch weitere Hilfsdateien. Wir platzier └── Cart/ \-- -3. **Für die gesamte Anwendung** – in `Presentation/Accessory/`: +3. **Für die gesamte Anwendung** - in `Presentation/Accessory/`: /--pre app/Presentation/ ├── Accessory/ @@ -263,20 +263,20 @@ Zu Presentern und Templates gehören oft auch weitere Hilfsdateien. Wir platzier └── Admin/ \-- -Oder Sie können Hilfsklassen wie `LatteExtension.php` oder `TemplateFilters.php` im Infrastrukturordner `app/Core/Latte/` platzieren. Und Komponenten in `app/Components`. Die Wahl hängt von den Gewohnheiten des Teams ab. +Alternativ können Sie Hilfsklassen wie `LatteExtension.php` oder `TemplateFilters.php` in den Infrastrukturordner `app/Core/Latte/` legen. Und Komponenten nach `app/Components`. Die Wahl hängt von den Konventionen im Team ab. -Model - Das Herz der Anwendung -============================== +Model - Herz der Anwendung +========================== -Das Model enthält die gesamte Geschäftslogik der Anwendung. Für seine Organisation gilt wieder die Regel – wir strukturieren nach Domänen: +Das Model enthält die gesamte Geschäftslogik der Anwendung. Die Regel für seine Organisation lautet wieder: Struktur nach Domänen: /--pre app/Model/ ├── Payment/ ← alles rund um Zahlungen -│ ├── PaymentFacade.php ← Hauptzugangspunkt +│ ├── PaymentFacade.php ← Haupteinstiegspunkt │ ├── PaymentRepository.php -│ ├── Payment.php ← Entität +│ ├── Payment.php ← Entity ├── Order/ ← alles rund um Bestellungen │ ├── OrderFacade.php │ ├── OrderRepository.php @@ -284,9 +284,9 @@ Das Model enthält die gesamte Geschäftslogik der Anwendung. Für seine Organis └── Shipping/ ← alles rund um den Versand \-- -Im Model treffen Sie typischerweise auf diese Klassentypen: +Im Model begegnen Ihnen typischerweise diese Arten von Klassen: -**Fassaden**: stellen den Hauptzugangspunkt zu einer bestimmten Domäne in der Anwendung dar. Sie fungieren als Orchestrator, der die Zusammenarbeit zwischen verschiedenen Diensten koordiniert, um vollständige Use Cases (wie "Bestellung erstellen" oder "Zahlung verarbeiten") zu implementieren. Unter ihrer Orchestrierungsschicht verbirgt die Fassade Implementierungsdetails vor dem Rest der Anwendung und bietet so eine saubere Schnittstelle für die Arbeit mit der jeweiligen Domäne. +**Facades**: Sie stellen den Haupteinstiegspunkt in eine bestimmte Domäne der Anwendung dar. Sie treten als Orchestrator auf und koordinieren die Zusammenarbeit verschiedener Services, um vollständige Use-Cases umzusetzen (etwa "Bestellung anlegen" oder "Zahlung verarbeiten"). Unterhalb ihrer Orchestrierungsschicht verbirgt die Facade Implementierungsdetails vor dem Rest der Anwendung und bietet damit eine saubere Schnittstelle für die Arbeit mit der jeweiligen Domäne. ```php class OrderFacade @@ -294,14 +294,14 @@ class OrderFacade public function createOrder(Cart $cart): Order { // Validierung - // Erstellung der Bestellung - // Senden der E-Mail - // Eintrag in die Statistiken + // Anlegen der Bestellung + // Versand der E-Mail + // Schreiben in die Statistik } } ``` -**Dienste**: konzentrieren sich auf eine spezifische Geschäftsoperation innerhalb der Domäne. Im Gegensatz zur Fassade, die ganze Use Cases orchestriert, implementiert ein Dienst spezifische Geschäftslogik (wie Preisberechnungen oder Zahlungsverarbeitung). Dienste sind typischerweise zustandslos und können entweder von Fassaden als Bausteine für komplexere Operationen oder direkt von anderen Teilen der Anwendung für einfachere Aufgaben verwendet werden. +**Services**: Sie konzentrieren sich auf konkrete Geschäftsoperationen innerhalb einer Domäne. Anders als Facades, die ganze Use-Cases orchestrieren, implementiert ein Service konkrete Geschäftslogik (etwa Preisberechnungen oder die Verarbeitung von Zahlungen). Services sind typischerweise zustandslos und lassen sich entweder von Facades als Bausteine für komplexere Operationen oder für einfachere Aufgaben direkt von anderen Teilen der Anwendung verwenden. ```php class PricingService @@ -313,7 +313,7 @@ class PricingService } ``` -**Repositories**: stellen die gesamte Kommunikation mit dem Datenspeicher sicher, typischerweise einer Datenbank. Ihre Aufgabe ist das Laden und Speichern von Entitäten und die Implementierung von Methoden zu deren Suche. Das Repository schirmt den Rest der Anwendung von den Implementierungsdetails der Datenbank ab und bietet eine objektorientierte Schnittstelle für die Arbeit mit Daten. +**Repositories**: Sie kümmern sich um die gesamte Kommunikation mit dem Datenspeicher, typischerweise einer Datenbank. Ihre Aufgabe ist es, Entities zu laden und zu speichern und Methoden zu ihrer Suche zu implementieren. Ein Repository schirmt den Rest der Anwendung von den Implementierungsdetails der Datenbank ab und bietet eine objektorientierte Schnittstelle für die Arbeit mit Daten. ```php class OrderRepository @@ -328,10 +328,10 @@ class OrderRepository } ``` -**Entitäten**: Objekte, die die Hauptgeschäftskonzepte in der Anwendung repräsentieren, ihre eigene Identität haben und sich im Laufe der Zeit ändern. Typischerweise handelt es sich um Klassen, die mittels ORM (wie Nette Database Explorer oder Doctrine) auf Datenbanktabellen abgebildet sind. Entitäten können Geschäftsregeln enthalten, die sich auf ihre Daten beziehen, sowie Validierungslogik. +**Entities**: Objekte, die die wichtigsten Geschäftskonzepte der Anwendung darstellen, eine eigene Identität haben und sich im Laufe der Zeit ändern. Typischerweise sind das Klassen, die per ORM (etwa Nette Database Explorer oder Doctrine) auf Datenbanktabellen abgebildet werden. Entities können Geschäftsregeln zu ihren Daten und Validierungslogik enthalten. ```php -// Entität, die auf die Datenbanktabelle orders abgebildet ist +// Entity, die auf die Datenbanktabelle 'orders' abgebildet ist class Order extends Nette\Database\Table\ActiveRow { public function addItem(Product $product, int $quantity): void @@ -345,17 +345,17 @@ class Order extends Nette\Database\Table\ActiveRow } ``` -**Value Objects**: unveränderliche Objekte, die Werte ohne eigene Identität repräsentieren – beispielsweise ein Geldbetrag oder eine E-Mail-Adresse. Zwei Instanzen eines Value Objects mit gleichen Werten werden als identisch betrachtet. +**Value Objects**: unveränderliche Objekte, die Werte ohne eigene Identität darstellen - zum Beispiel einen Geldbetrag oder eine E-Mail-Adresse. Zwei Instanzen eines Value Objects mit denselben Werten gelten als identisch. Infrastrukturcode ================= -Der Ordner `Core/` (oder auch `Infrastructure/`) ist die Heimat für die technische Grundlage der Anwendung. Infrastrukturcode umfasst typischerweise: +Der Ordner `Core/` (alternativ `Infrastructure/`) ist die Heimat der technischen Grundlage der Anwendung. Zum Infrastrukturcode gehören typischerweise: /--pre app/Core/ -├── Router/ ← Routing und URL-Management +├── Router/ ← Routing und URL-Verwaltung │ └── RouterFactory.php ├── Security/ ← Authentifizierung und Autorisierung │ ├── Authenticator.php @@ -365,12 +365,12 @@ Der Ordner `Core/` (oder auch `Infrastructure/`) ist die Heimat für die technis │ └── FileLogger.php ├── Cache/ ← Caching-Schicht │ └── FullPageCache.php -└── Integration/ ← Integration mit externen Diensten +└── Integration/ ← Integration externer Dienste ├── Slack/ └── Stripe/ \-- -Bei kleineren Projekten genügt natürlich eine flache Gliederung: +Für kleinere Projekte genügt natürlich eine flache Struktur: /--pre Core/ @@ -379,29 +379,29 @@ Bei kleineren Projekten genügt natürlich eine flache Gliederung: └── QueueMailer.php \-- -Es handelt sich um Code, der: +Das ist Code, der: -- Sich um die technische Infrastruktur kümmert (Routing, Logging, Caching) -- Externe Dienste integriert (Sentry, Elasticsearch, Redis) -- Basisdienste für die gesamte Anwendung bereitstellt (Mail, Datenbank) -- Meistens unabhängig von der spezifischen Domäne ist – Cache oder Logger funktionieren gleich für E-Shop oder Blog. +- sich um die technische Infrastruktur kümmert (Routing, Logging, Caching) +- externe Dienste integriert (Sentry, Elasticsearch, Redis) +- grundlegende Services für die gesamte Anwendung bereitstellt (Mail, Datenbank) +- meist unabhängig von einer konkreten Domäne ist - Cache oder Logger funktionieren für einen E-Shop genauso wie für einen Blog. -Sind Sie unsicher, ob eine bestimmte Klasse hierher oder ins Modell gehört? Der Hauptunterschied besteht darin, dass der Code in `Core/`: +Sie fragen sich, ob eine bestimmte Klasse hierher oder ins Model gehört? Der entscheidende Unterschied ist, dass Code in `Core/`: -- Nichts über die Domäne weiß (Produkte, Bestellungen, Artikel) -- Meistens in ein anderes Projekt übertragen werden kann -- Löst "wie es funktioniert" (wie man eine E-Mail sendet), nicht "was es tut" (welche E-Mail gesendet werden soll) +- nichts über die Domäne weiß (Produkte, Bestellungen, Artikel) +- sich meist in ein anderes Projekt übertragen lässt +- löst "wie es funktioniert" (wie man eine E-Mail versendet), nicht "was es tut" (welche E-Mail zu versenden ist) -Beispiel zum besseren Verständnis: +Ein Beispiel zum besseren Verständnis: -- `App\Core\MailerFactory` - erstellt Instanzen der Klasse zum Senden von E-Mails, kümmert sich um SMTP-Einstellungen -- `App\Model\OrderMailer` - verwendet `MailerFactory` zum Senden von E-Mails über Bestellungen, kennt deren Templates und weiß, wann sie gesendet werden sollen +- `App\Core\MailerFactory` - erzeugt Instanzen der Klasse zum Versenden von E-Mails, kümmert sich um die SMTP-Einstellungen +- `App\Model\OrderMailer` - nutzt `MailerFactory`, um E-Mails zu Bestellungen zu versenden, kennt ihre Templates und weiß, wann sie versendet werden sollen -Befehlszeilenskripte -==================== +Kommandoskripte +=============== -Anwendungen müssen oft Tätigkeiten außerhalb normaler HTTP-Anfragen ausführen – sei es Datenverarbeitung im Hintergrund, Wartung oder periodische Aufgaben. Zur Ausführung dienen einfache Skripte im Verzeichnis `bin/`, die eigentliche Implementierungslogik platzieren wir dann in `app/Tasks/` (oder `app/Commands/`). +Anwendungen müssen oft Tätigkeiten außerhalb der gewöhnlichen HTTP-Requests ausführen - sei es Datenverarbeitung im Hintergrund, Wartung oder periodische Aufgaben. Zur Ausführung dienen einfache Skripte im Verzeichnis `bin/`, während die eigentliche Implementierungslogik in `app/Tasks/` (oder `app/Commands/`) liegt. Beispiel: @@ -409,52 +409,52 @@ Beispiel: app/Tasks/ ├── Maintenance/ ← Wartungsskripte │ ├── CleanupCommand.php ← Löschen alter Daten -│ └── DbOptimizeCommand.php ← Datenbankoptimierung -├── Integration/ ← Integration mit externen Systemen -│ ├── ImportProducts.php ← Import aus dem Liefersystem -│ └── SyncOrders.php ← Synchronisation von Bestellungen +│ └── DbOptimizeCommand.php ← Optimierung der Datenbank +├── Integration/ ← Integration externer Systeme +│ ├── ImportProducts.php ← Import aus dem System des Lieferanten +│ └── SyncOrders.php ← Synchronisation der Bestellungen └── Scheduled/ ← regelmäßige Aufgaben ├── NewsletterCommand.php ← Versand von Newslettern └── ReminderCommand.php ← Benachrichtigungen an Kunden \-- -Was gehört ins Modell und was in die Befehlszeilenskripte? Zum Beispiel ist die Logik zum Senden einer einzelnen E-Mail Teil des Modells, der Massenversand von Tausenden von E-Mails gehört bereits zu `Tasks/`. +Was gehört ins Model und was in Kommandoskripte? Die Logik zum Versenden einer einzelnen E-Mail ist zum Beispiel Teil des Models, während der Massenversand Tausender E-Mails in `Tasks/` gehört. -Aufgaben werden normalerweise [über die Befehlszeile ausgeführt |https://blog.nette.org/en/cli-scripts-in-nette-application] oder über Cron. Sie können auch über eine HTTP-Anfrage gestartet werden, aber man muss an die Sicherheit denken. Der Presenter, der die Aufgabe startet, muss abgesichert werden, zum Beispiel nur für angemeldete Benutzer oder mit einem starken Token und Zugriff von erlaubten IP-Adressen. Bei langen Aufgaben muss das Zeitlimit des Skripts erhöht und `session_write_close()` verwendet werden, damit die Session nicht gesperrt wird. +Tasks werden üblicherweise von der Kommandozeile oder per Cron ausgeführt: Das Skript in `bin/` erzeugt mit der Methode [bootConsoleApplication() |bootstrapping#Verschiedene Umgebungen] den DI-Container und holt den benötigten Service aus ihm. Sie lassen sich auch über einen HTTP-Request ausführen, dabei muss aber an die Sicherheit gedacht werden. Der Presenter, der den Task ausführt, muss abgesichert werden, zum Beispiel nur für angemeldete Benutzer oder mit einem starken Token und Zugriff von erlaubten IP-Adressen. Bei lang laufenden Tasks muss das Zeitlimit des Skripts erhöht und `session_write_close()` verwendet werden, um die Session nicht zu blockieren. Weitere mögliche Verzeichnisse ============================== -Neben den genannten grundlegenden Verzeichnissen können Sie je nach Projektbedarf weitere spezialisierte Ordner hinzufügen. Schauen wir uns die häufigsten davon und ihre Verwendung an: +Neben den genannten Grundverzeichnissen können Sie je nach Bedarf des Projekts weitere spezialisierte Ordner ergänzen. Schauen wir uns die häufigsten und ihre Verwendung an: /--pre app/ ├── Api/ ← API-Logik unabhängig von der Präsentationsschicht ├── Database/ ← Migrationsskripte und Seeder für Testdaten -├── Components/ ← gemeinsam genutzte visuelle Komponenten über die gesamte Anwendung hinweg -├── Event/ ← nützlich, wenn Sie eine ereignisgesteuerte Architektur verwenden +├── Components/ ← gemeinsame visuelle Komponenten der gesamten Anwendung +├── Event/ ← nützlich bei einer ereignisgesteuerten Architektur ├── Mail/ ← E-Mail-Templates und zugehörige Logik └── Utils/ ← Hilfsklassen \-- -Für gemeinsam genutzte visuelle Komponenten, die in Presentern über die gesamte Anwendung hinweg verwendet werden, kann der Ordner `app/Components` oder `app/Controls` verwendet werden: +Für gemeinsame visuelle Komponenten, die in Presentern der gesamten Anwendung verwendet werden, können Sie den Ordner `app/Components` oder `app/Controls` nutzen: /--pre app/Components/ -├── Form/ ← gemeinsam genutzte Formularkomponenten +├── Form/ ← gemeinsame Formularkomponenten │ ├── SignInForm.php │ └── UserForm.php -├── Grid/ ← Komponenten für Datenlisten +├── Grid/ ← Komponenten für Datenauflistungen │ └── DataGrid.php └── Navigation/ ← Navigationselemente ├── Breadcrumbs.php └── Menu.php \-- -Hierher gehören Komponenten mit komplexerer Logik. Wenn Sie Komponenten zwischen mehreren Projekten teilen möchten, ist es ratsam, sie in ein separates Composer-Paket auszulagern. +Hierher gehören Komponenten mit komplexerer Logik. Wollen Sie Komponenten zwischen mehreren Projekten teilen, empfiehlt es sich, sie in ein eigenes Composer-Paket auszulagern. -Im Verzeichnis `app/Mail` können Sie die Verwaltung der E-Mail-Kommunikation platzieren: +Im Verzeichnis `app/Mail` können Sie die Verwaltung der E-Mail-Kommunikation unterbringen: /--pre app/Mail/ @@ -468,32 +468,32 @@ Im Verzeichnis `app/Mail` können Sie die Verwaltung der E-Mail-Kommunikation pl Presenter-Mapping ================= -Das Mapping definiert Regeln zur Ableitung des Klassennamens aus dem Presenter-Namen. Wir spezifizieren sie in der [Konfiguration|configuration] unter dem Schlüssel `application › mapping`. +Das Mapping definiert die Regeln, nach denen aus dem Namen des Presenters der Klassenname abgeleitet wird. Wir geben sie in der [Konfiguration|configuration] unter dem Schlüssel `application › mapping` an. -Auf dieser Seite haben wir gezeigt, dass wir Presenter im Ordner `app/Presentation` (oder `app/UI`) platzieren. Diese Konvention müssen wir Nette in der Konfigurationsdatei mitteilen. Eine Zeile genügt: +Auf dieser Seite haben wir gezeigt, dass wir Presenter im Ordner `app/Presentation` (oder `app/UI`) ablegen. Seit Nette Application 3.3 ist das die Standardkonvention, die nicht konfiguriert werden muss. Wenn Sie eine andere Struktur verwenden oder das Mapping ausdrücklich angeben wollen, entspricht die Standardeinstellung dieser Zeile: ```neon application: mapping: App\Presentation\*\**Presenter ``` -Wie funktioniert das Mapping? Zum besseren Verständnis stellen wir uns zunächst eine Anwendung ohne Module vor. Wir möchten, dass die Presenter-Klassen in den Namespace `App\Presentation` fallen, damit der Presenter `Home` auf die Klasse `App\Presentation\HomePresenter` abgebildet wird. Was wir mit dieser Konfiguration erreichen: +Wie funktioniert das Mapping? Zum besseren Verständnis stellen wir uns zunächst eine Anwendung ohne Module vor. Wir wollen, dass die Presenter-Klassen unter den Namespace `App\Presentation` fallen, sodass der Presenter `Home` auf die Klasse `App\Presentation\HomePresenter` abgebildet wird. Das erreichen wir mit dieser Konfiguration: ```neon application: mapping: App\Presentation\*Presenter ``` -Das Mapping funktioniert so, dass der Presenter-Name `Home` das Sternchen in der Maske `App\Presentation\*Presenter` ersetzt, wodurch wir den resultierenden Klassennamen `App\Presentation\HomePresenter` erhalten. Einfach! +Das Mapping funktioniert so, dass der Stern in der Maske `App\Presentation\*Presenter` durch den Namen des Presenters `Home` ersetzt wird, woraus der endgültige Klassenname `App\Presentation\HomePresenter` entsteht. Einfach! -Wie Sie jedoch in den Beispielen in diesem und anderen Kapiteln sehen, platzieren wir die Presenter-Klassen in gleichnamigen Unterverzeichnissen, zum Beispiel wird der Presenter `Home` auf die Klasse `App\Presentation\Home\HomePresenter` abgebildet. Dies erreichen wir mit der `***Presenter`-Maske (erfordert Nette Application 3.2): +Wie Sie jedoch in den Beispielen in diesem und anderen Kapiteln sehen, legen wir die Presenter-Klassen in gleichnamige Unterverzeichnisse, der Presenter `Home` wird also auf die Klasse `App\Presentation\Home\HomePresenter` abgebildet. Das erreichen wir mit dem doppelten Stern `**` (erfordert Nette Application 3.2.3): ```neon application: mapping: App\Presentation\**Presenter ``` -Nun kommen wir zum Mapping von Presentern in Module. Für jedes Modul können wir ein spezifisches Mapping definieren: +Nun gehen wir zum Mapping von Presentern in Module über. Für jedes Modul können wir ein eigenes Mapping definieren: ```neon application: @@ -503,9 +503,9 @@ application: Api: App\Api\*Presenter ``` -Gemäß dieser Konfiguration wird der Presenter `Front:Home` auf die Klasse `App\Presentation\Front\Home\HomePresenter` abgebildet, während der Presenter `Api:OAuth` auf die Klasse `App\Api\OAuthPresenter` abgebildet wird. +Nach dieser Konfiguration wird der Presenter `Front:Home` auf die Klasse `App\Presentation\Front\Home\HomePresenter` abgebildet, während der Presenter `Api:OAuth` auf die Klasse `App\Api\OAuthPresenter` abgebildet wird. -Da die Module `Front` und `Admin` eine ähnliche Mapping-Methode haben und es solche Module wahrscheinlich mehr geben wird, ist es möglich, eine allgemeine Regel zu erstellen, die sie ersetzt. Zur Klassenmaske kommt somit ein neues Sternchen für das Modul hinzu: +Da die Module `Front` und `Admin` ein ähnliches Mapping-Muster haben und es davon vermutlich noch mehr geben wird, lässt sich eine allgemeine Regel erstellen, die sie ersetzt. In die Klassenmaske kommt ein neuer Stern für das Modul hinzu: ```neon application: @@ -514,9 +514,9 @@ application: Api: App\Api\*Presenter ``` -Es funktioniert auch für tiefer verschachtelte Verzeichnisstrukturen, wie zum Beispiel den Presenter `Admin:User:Edit`, wobei sich das Segment mit dem Sternchen für jede Ebene wiederholt und das Ergebnis die Klasse `App\Presentation\Admin\User\Edit\EditPresenter` ist. +Es funktioniert auch für tiefer verschachtelte Verzeichnisstrukturen, etwa den Presenter `Admin:User:Edit`, bei dem sich das Segment mit dem Stern für jede Modulebene wiederholt, woraus die Klasse `App\Presentation\Admin\User\Edit\EditPresenter` entsteht. -Eine alternative Schreibweise ist die Verwendung eines Arrays anstelle einer Zeichenkette, bestehend aus drei Segmenten. Diese Schreibweise ist äquivalent zur vorherigen: +Eine alternative Schreibweise ist, statt eines Strings ein Array aus drei Segmenten zu verwenden. Für die oben gezeigten Beispiele ist diese Schreibweise gleichwertig zur vorherigen: ```neon application: diff --git a/application/de/how-it-works.texy b/application/de/how-it-works.texy index f58652fbde..0766651e3b 100644 --- a/application/de/how-it-works.texy +++ b/application/de/how-it-works.texy @@ -3,10 +3,10 @@ Wie funktionieren Anwendungen?
    -Sie lesen gerade das grundlegende Dokument der Nette-Dokumentation. Sie werden das gesamte Funktionsprinzip von Webanwendungen kennenlernen. Schön von A bis Z, von dem Moment der Entstehung bis zum letzten Atemzug des PHP-Skripts. Nach dem Lesen werden Sie wissen: +Sie lesen gerade das grundlegende Kapitel der Nette-Dokumentation. Sie erfahren das komplette Prinzip, wie Webanwendungen funktionieren - von A bis Z, vom Moment der Entstehung eines Requests bis zum Ende der Ausführung des PHP-Skripts. Nach der Lektüre werden Sie wissen: - wie das Ganze funktioniert -- was Bootstrap, Presenter und DI-Container sind +- was Bootstrap, Presenter und der DI-Container sind - wie die Verzeichnisstruktur aussieht
    @@ -15,89 +15,89 @@ Sie lesen gerade das grundlegende Dokument der Nette-Dokumentation. Sie werden d Verzeichnisstruktur =================== -Öffnen Sie das Beispiel-Skelett einer Webanwendung namens [WebProject|https://github.com/nette/web-project] und beim Lesen können Sie sich die Dateien ansehen, von denen die Rede ist. +Öffnen Sie das Beispielskelett einer Webanwendung namens [WebProject|https://github.com/nette/web-project]. Beim Lesen können Sie in die besprochenen Dateien hineinschauen. Die Verzeichnisstruktur sieht ungefähr so aus: /--pre web-project/ -├── app/ ← Verzeichnis mit der Anwendung -│ ├── Core/ ← grundlegende Klassen, die für den Betrieb notwendig sind +├── app/ ← Verzeichnis der Anwendung +│ ├── Core/ ← für den Betrieb notwendige Basisklassen │ │ └── RouterFactory.php ← Konfiguration der URL-Adressen │ ├── Presentation/ ← Presenter, Templates & Co. -│ │ ├── @layout.latte ← Layout-Template +│ │ ├── @layout.latte ← Template des Layouts │ │ └── Home/ ← Verzeichnis des Home-Presenters │ │ ├── HomePresenter.php ← Klasse des Home-Presenters -│ │ └── default.latte ← Template der default-Aktion +│ │ └── default.latte ← Template für die Aktion default │ └── Bootstrap.php ← Startklasse Bootstrap ├── assets/ ← Ressourcen (SCSS, TypeScript, Quellbilder) -├── bin/ ← Skripte, die von der Kommandozeile ausgeführt werden +├── bin/ ← von der Kommandozeile ausgeführte Skripte ├── config/ ← Konfigurationsdateien │ ├── common.neon │ └── services.neon ├── log/ ← protokollierte Fehler ├── temp/ ← temporäre Dateien, Cache, … -├── vendor/ ← Bibliotheken, die mit Composer installiert wurden +├── vendor/ ← von Composer installierte Bibliotheken │ ├── ... │ └── autoload.php ← Autoloading aller installierten Pakete -├── www/ ← öffentliches Verzeichnis oder Document-Root des Projekts +├── www/ ← öffentliches Verzeichnis, Document-Root des Projekts │ ├── assets/ ← kompilierte statische Dateien (CSS, JS, Bilder, ...) -│ ├── .htaccess ← mod_rewrite-Regeln -│ └── index.php ← initiale Datei, mit der die Anwendung gestartet wird +│ ├── .htaccess ← Regeln für mod_rewrite +│ └── index.php ← Startdatei, die die Anwendung startet └── .htaccess ← verbietet den Zugriff auf alle Verzeichnisse außer www \-- -Die Verzeichnisstruktur können Sie beliebig ändern, Ordner umbenennen oder verschieben, sie ist völlig flexibel. Nette verfügt zudem über eine intelligente Autodetektion und erkennt automatisch den Speicherort der Anwendung einschließlich ihrer URL-Basis. +Sie können die Verzeichnisstruktur beliebig ändern, Ordner umbenennen oder verschieben; sie ist völlig flexibel. Nette verfügt außerdem über eine intelligente Autodetection und erkennt den Ort der Anwendung samt ihrer URL-Basis automatisch. -Bei etwas größeren Anwendungen können wir die Ordner mit Presentern und Templates [in Unterverzeichnisse aufteilen |directory-structure#Presenter und Templates] und Klassen in Namespaces, die wir Module nennen. +Bei etwas größeren Anwendungen können wir die Ordner mit Presentern und Templates in [Unterverzeichnisse |directory-structure#Presenter und Templates] gliedern und die Klassen in Namespaces zusammenfassen, die wir Module nennen. -Das Verzeichnis `www/` stellt das sogenannte öffentliche Verzeichnis oder Document-Root des Projekts dar. Sie können es umbenennen, ohne etwas Weiteres auf Anwendungsseite einstellen zu müssen. Es ist nur notwendig, [das Hosting zu konfigurieren |nette:troubleshooting#Wie ändert oder entfernt man das Verzeichnis www aus der URL], damit der Document-Root auf dieses Verzeichnis zeigt. +Das Verzeichnis `www/` stellt das öffentliche Verzeichnis bzw. den Document-Root des Projekts dar. Sie können es umbenennen, ohne auf der Seite der Anwendung sonst etwas konfigurieren zu müssen. Sie müssen lediglich [das Hosting so einstellen |nette:troubleshooting#Wie ändert oder entfernt man das Verzeichnis www aus der URL?], dass der Document-Root auf dieses Verzeichnis zeigt. -WebProject können Sie sich auch direkt inklusive Nette herunterladen, und zwar mittels [Composer |best-practices:composer]: +WebProject können Sie samt Nette auch direkt mit [Composer |best-practices:composer] herunterladen: ```shell composer create-project nette/web-project ``` -Unter Linux oder macOS setzen Sie für die Verzeichnisse `log/` und `temp/` [Schreibrechte |nette:troubleshooting#Einstellung der Verzeichnisberechtigungen]. +Setzen Sie unter Linux oder macOS für die Verzeichnisse `log/` und `temp/` [Schreibrechte |nette:troubleshooting#Einstellung der Verzeichnisberechtigungen]. -Die Anwendung WebProject ist startbereit, es muss überhaupt nichts konfiguriert werden und Sie können sie direkt im Browser anzeigen, indem Sie auf den Ordner `www/` zugreifen. +Die Anwendung WebProject ist startbereit; es ist überhaupt nichts zu konfigurieren, und Sie können sie direkt im Browser betrachten, indem Sie den Ordner `www/` aufrufen. -HTTP-Anfrage +HTTP-Request ============ -Alles beginnt in dem Moment, in dem der Benutzer im Browser eine Seite öffnet. Also wenn der Browser beim Server mit einer HTTP-Anfrage anklopft. Die Anfrage zielt auf eine einzige PHP-Datei, die sich im öffentlichen Verzeichnis `www/` befindet, und das ist `index.php`. Nehmen wir an, es handelt sich um eine Anfrage an die Adresse `https://example.com/product/123`. Dank geeigneter [Serverkonfiguration |nette:troubleshooting#Wie konfiguriert man den Server für schöne URLs Pretty URLs] wird auch diese URL auf die Datei `index.php` abgebildet und diese wird ausgeführt. +Alles beginnt damit, dass ein Benutzer im Browser eine Seite öffnet. Der Browser sendet einen HTTP-Request an den Server. Dieser Request zielt auf eine einzige PHP-Datei im öffentlichen Verzeichnis `www/`, nämlich `index.php`. Nehmen wir an, der Request gilt der Adresse `https://example.com/product/123`. Dank einer passenden [Serverkonfiguration |nette:troubleshooting#Wie konfiguriert man den Server für schöne URLs?] wird auch diese URL auf die Datei `index.php` abgebildet, die dann ausgeführt wird. Ihre Aufgabe ist es: 1) die Umgebung zu initialisieren -2) die Factory zu erhalten -3) die Nette-Anwendung zu starten, die die Anfrage bearbeitet +2) die Factory zu beschaffen +3) die Nette-Anwendung zu starten, die den Request bearbeitet -Welche Factory denn? Wir stellen doch keine Traktoren her, sondern Webseiten! Bleiben Sie dran, das wird gleich erklärt. +Welche Factory? Wir stellen doch keine Traktoren her, wir bauen Websites! Nur Geduld, das wird gleich erklärt. -Mit „Initialisierung der Umgebung“ meinen wir zum Beispiel, dass [Tracy|tracy:] aktiviert wird, ein großartiges Werkzeug zur Protokollierung oder Visualisierung von Fehlern. Auf dem Produktionsserver protokolliert es Fehler, auf dem Entwicklungsserver zeigt es sie direkt an. Zur Initialisierung gehört also auch die Entscheidung, ob die Website im Produktions- oder Entwicklungsmodus läuft. Dazu verwendet Nette eine [intelligente Autodetektion |bootstrapping#Entwicklungs- vs. Produktionsmodus]: Wenn Sie die Website auf localhost starten, läuft sie im Entwicklungsmodus. Sie müssen also nichts konfigurieren und die Anwendung ist direkt bereit sowohl für die Entwicklung als auch für den Live-Einsatz. Diese Schritte werden durchgeführt und sind im Kapitel über die [Bootstrap-Klasse|bootstrapping] ausführlich beschrieben. +Mit "Initialisierung der Umgebung" meinen wir zum Beispiel die Aktivierung von [Tracy|tracy:], einem großartigen Werkzeug zum Protokollieren und Visualisieren von Fehlern. Auf dem Produktionsserver protokolliert es Fehler, in der Entwicklungsumgebung zeigt es sie direkt an. Zur Initialisierung gehört daher auch die Entscheidung, ob die Website im Produktions- oder im Entwicklungsmodus läuft. Nette nutzt dafür eine [intelligente Autodetection |bootstrapping#Entwicklungs- vs. Produktionsmodus]: Wenn Sie die Website auf localhost betreiben, läuft sie im Entwicklungsmodus. Sie müssen nichts konfigurieren, und die Anwendung ist sofort sowohl für die Entwicklung als auch für den Live-Betrieb bereit. Diese Schritte werden im Kapitel über die [Klasse Bootstrap|bootstrapping] ausgeführt und ausführlich beschrieben. -Der dritte Punkt (ja, den zweiten haben wir übersprungen, aber wir kommen darauf zurück) ist der Start der Anwendung. Die Bearbeitung von HTTP-Anfragen übernimmt in Nette die Klasse `Nette\Application\Application` (weiter `Application`), wenn wir also sagen, die Anwendung starten, meinen wir konkret den Aufruf der Methode mit dem treffenden Namen `run()` auf einem Objekt dieser Klasse. +Der dritte Punkt (ja, den zweiten haben wir übersprungen, aber wir kommen darauf zurück) ist der Start der Anwendung. Die Bearbeitung von HTTP-Requests ist in Nette Aufgabe der Klasse `Nette\Application\Application` (im Folgenden `Application`). Wenn wir also sagen "die Anwendung starten", meinen wir konkret den Aufruf der treffend benannten Methode `run()` auf einem Objekt dieser Klasse. -Nette ist ein Mentor, der Sie dazu anleitet, saubere Anwendungen nach bewährten Methoden zu schreiben. Und eine der absolut bewährtesten heißt **Dependency Injection**, abgekürzt DI. An dieser Stelle möchten wir Sie nicht mit der Erklärung von DI belasten, dafür gibt es ein [eigenes Kapitel|dependency-injection:introduction], wesentlich ist die Konsequenz, dass uns Schlüsselobjekte üblicherweise von einer Objekt-Factory erstellt werden, die **DI-Container** (abgekürzt DIC) genannt wird. Ja, das ist die Factory, von der vorhin die Rede war. Und sie stellt uns auch das `Application`-Objekt her, deshalb benötigen wir zuerst den Container. Wir erhalten ihn mittels der Klasse `Configurator` und lassen ihn das `Application`-Objekt erstellen, rufen darauf die Methode `run()` auf und damit startet die Nette-Anwendung. Genau das geschieht in der Datei [index.php |bootstrapping#index.php]. +Nette ist ein Mentor, der Sie dazu anleitet, saubere Anwendungen nach bewährten Methodiken zu schreiben. Eine der etabliertesten davon heißt **Dependency Injection**, kurz DI. Wir wollen Sie an dieser Stelle nicht mit der Erklärung von DI belasten, dafür gibt es ein [eigenes Kapitel|dependency-injection:introduction]. Die wesentliche Konsequenz ist, dass Schlüsselobjekte üblicherweise von einer Objekt-Factory erzeugt werden, die man **DI-Container** (kurz DIC) nennt. Ja, das ist die Factory, von der eben die Rede war. Sie erzeugt für uns auch das Objekt `Application`, weshalb wir zuerst den Container brauchen. Wir beschaffen ihn mit der Klasse `Configurator`, lassen ihn das Objekt `Application` erzeugen, rufen darauf die Methode `run()` auf, und damit startet die Nette-Anwendung. Genau das passiert in der Datei [index.php |bootstrapping#index.php]. Nette Application ================= -Die Klasse Application hat eine einzige Aufgabe: auf eine HTTP-Anfrage zu antworten. +Die Klasse `Application` hat eine einzige Aufgabe: auf den HTTP-Request zu antworten. -Anwendungen, die in Nette geschrieben sind, gliedern sich in viele sogenannte Presenter (in anderen Frameworks können Sie auf den Begriff Controller stoßen, es handelt sich um dasselbe), das sind Klassen, von denen jede eine bestimmte Webseite repräsentiert: z.B. die Homepage; ein Produkt im E-Shop; ein Anmeldeformular; ein Sitemap-Feed usw. Eine Anwendung kann von einem bis zu Tausenden von Presentern haben. +In Nette geschriebene Anwendungen sind in viele sogenannte Presenter unterteilt (in anderen Frameworks begegnet Ihnen vielleicht der Begriff "Controller", was im Wesentlichen dasselbe ist). Das sind Klassen, von denen jede eine konkrete Seite der Website repräsentiert: z. B. die Startseite, ein Produkt im E-Shop, ein Anmeldeformular, einen Sitemap-Feed usw. Eine Anwendung kann von einem bis zu Tausenden von Presentern haben. -Die Application beginnt damit, den sogenannten Router zu bitten, zu entscheiden, welchem der Presenter die aktuelle Anfrage zur Bearbeitung übergeben werden soll. Der Router entscheidet, wessen Verantwortung das ist. Er schaut sich die Eingabe-URL `https://example.com/product/123` an und entscheidet auf Basis seiner Konfiguration, dass dies die Arbeit z.B. für den **Presenter** `Product` ist, von dem er als **Aktion** die Anzeige (`show`) des Produkts mit `id: 123` verlangen wird. Das Paar Presenter + Aktion wird üblicherweise durch einen Doppelpunkt getrennt als `Product:show` geschrieben. +Die `Application` beginnt damit, dass sie den sogenannten Router fragt, welcher Presenter den aktuellen Request bearbeiten soll. Der Router entscheidet über die Zuständigkeit. Er schaut sich die Eingabe-URL `https://example.com/product/123` an und entscheidet anhand seiner Konfiguration, dass diese Aufgabe zum Beispiel dem **Presenter** `Product` gehört, der die **Aktion** `show` für das Produkt mit `id: 123` ausführen soll. Es ist eine gute Gewohnheit, das Paar Presenter + Aktion durch einen Doppelpunkt getrennt zu schreiben, also `Product:show`. -Also hat der Router die URL in das Paar `Presenter:action` + Parameter transformiert, in unserem Fall `Product:show` + `id: 123`. Wie ein solcher Router aussieht, können Sie sich in der Datei `app/Core/RouterFactory.php` ansehen und wir beschreiben ihn detailliert im Kapitel [Routing]. +Der Router hat die URL also in das Paar `Presenter:Aktion` + Parameter umgewandelt, in unserem Fall `Product:show` + `id: 123`. Wie ein solcher Router aussieht, sehen Sie in der Datei `app/Core/RouterFactory.php`, und wir beschreiben ihn ausführlich im Kapitel [Routing |Routing]. -Gehen wir weiter. Die Application kennt nun den Namen des Presenters und kann weitermachen. Indem sie ein Objekt der Klasse `ProductPresenter` erstellt, was der Code des Presenters `Product` ist. Genauer gesagt, bittet sie den DI-Container, den Presenter zu erstellen, denn für das Erstellen ist er da. +Gehen wir weiter. Die `Application` kennt nun den Namen des Presenters und kann fortfahren. Sie erzeugt dazu eine Instanz der Klasse `ProductPresenter`, die den Code des Presenters `Product` enthält. Genauer gesagt bittet sie den DI-Container, den Presenter zu erzeugen, denn das Erzeugen von Objekten ist dessen Aufgabe. -Der Presenter kann etwa so aussehen: +Der Presenter könnte so aussehen: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -109,92 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // Wir holen Daten aus dem Modell und übergeben sie an das Template + // Daten aus dem Model holen und an das Template übergeben $this->template->product = $this->repository->getProduct($id); } } ``` -Die Bearbeitung der Anfrage übernimmt der Presenter. Und die Aufgabe lautet klar: führe die Aktion `show` mit `id: 123` aus. Was in der Sprache der Presenter bedeutet, dass die Methode `renderShow()` aufgerufen wird und im Parameter `$id` den Wert `123` erhält. +Der Presenter übernimmt die Bearbeitung des Requests. Die Aufgabe ist klar: die Aktion `show` mit `id: 123` ausführen. In der Terminologie der Presenter bedeutet das, dass die Methode `renderShow()` aufgerufen wird, die im Parameter `$id` den Wert `123` erhält. -Ein Presenter kann mehrere Aktionen bedienen, also mehrere Methoden `render()` haben. Wir empfehlen jedoch, Presenter mit einer oder möglichst wenigen Aktionen zu entwerfen. +Ein Presenter kann mehrere Aktionen bearbeiten, also mehrere Methoden `render()` haben. Wir empfehlen jedoch, Presenter mit einer oder möglichst wenigen Aktionen zu entwerfen. -Also, die Methode `renderShow(123)` wurde aufgerufen, deren Code zwar ein fiktives Beispiel ist, aber Sie können daran sehen, wie Daten an das Template übergeben werden, nämlich durch Schreiben in `$this->template`. +Es wurde also die Methode `renderShow(123)` aufgerufen. Ihr Code ist ein erfundenes Beispiel, zeigt aber, wie Daten an das Template übergeben werden, nämlich durch Schreiben in `$this->template`. -Anschließend gibt der Presenter eine Antwort zurück. Das kann eine HTML-Seite, ein Bild, ein XML-Dokument, das Senden einer Datei von der Festplatte, JSON oder auch eine Weiterleitung auf eine andere Seite sein. Wichtig ist, dass wenn wir nicht explizit sagen, wie geantwortet werden soll (was der Fall bei `ProductPresenter` ist), die Antwort das Rendern eines Templates mit einer HTML-Seite sein wird. Warum? Weil wir in 99 % der Fälle ein Template rendern wollen, daher nimmt der Presenter dieses Verhalten als Standard an und möchte uns die Arbeit erleichtern. Das ist der Sinn von Nette. +Anschließend gibt der Presenter eine Antwort zurück. Das kann eine HTML-Seite sein, ein Bild, ein XML-Dokument, das Senden einer Datei von der Festplatte, JSON oder auch eine Weiterleitung auf eine andere Seite. Wichtig ist: Wenn wir nicht ausdrücklich sagen, wie geantwortet werden soll (was bei `ProductPresenter` der Fall ist), lautet die Antwort, ein Template in eine HTML-Seite zu rendern. Warum? Weil wir in 99 % der Fälle ein Template rendern wollen. Deshalb macht der Presenter dieses Verhalten zum Standardverhalten und erleichtert uns die Arbeit. Das ist das Wesen von Nette. -Wir müssen nicht einmal angeben, welches Template gerendert werden soll, den Pfad dazu leitet er selbst ab. Im Falle der Aktion `show` versucht er einfach, das Template `show.latte` im Verzeichnis der Klasse `ProductPresenter` zu laden. Ebenso versucht er, das Layout in der Datei `@layout.latte` zu finden (mehr dazu unter [Template-Suche |templates#Finden von Vorlagen]). +Wir müssen nicht einmal angeben, welches Template gerendert werden soll; den Pfad leitet das Framework selbst ab. Im Fall der Aktion `show` versucht es einfach, das Template `show.latte` in dem Verzeichnis zu laden, in dem auch die Klasse `ProductPresenter` liegt. Ebenso versucht es, das Layout in der Datei `@layout.latte` zu finden (mehr zur [Suche nach Templates |templates#Suche nach Templates]). -Und anschließend rendert er die Templates. Damit ist die Aufgabe des Presenters und der gesamten Anwendung erfüllt und das Werk vollendet. Wenn das Template nicht existiert, wird eine Seite mit dem Fehler 404 zurückgegeben. Mehr über Presenter erfahren Sie auf der Seite [Presenter |presenters]. +Danach werden die Templates gerendert. Damit ist die Aufgabe des Presenters und der gesamten Anwendung erfüllt. Existiert das Template nicht, wird eine Fehlerseite 404 zurückgegeben. Mehr über Presenter erfahren Sie auf der Seite [Presenter|presenters]. [* request-flow.svg *] -Zur Sicherheit versuchen wir, den gesamten Prozess mit einer etwas anderen URL zu rekapitulieren: +Zur Sicherheit fassen wir den gesamten Ablauf noch einmal mit einer etwas anderen URL zusammen: -1) Die URL lautet `https://example.com` -2) Wir booten die Anwendung, der DI-Container wird erstellt und `Application::run()` wird gestartet -3) Der Router dekodiert die URL als Paar `Home:default` -4) Ein Objekt der Klasse `HomePresenter` wird erstellt -5) Die Methode `renderDefault()` wird aufgerufen (falls sie existiert) -6) Das Template z.B. `default.latte` mit dem Layout z.B. `@layout.latte` wird gerendert +1) Die URL ist `https://example.com` +2) Die Anwendung startet, der DI-Container wird erzeugt und `Application::run()` wird ausgeführt. +3) Der Router dekodiert die URL in das Paar `Home:default`. +4) Es wird eine Instanz der Klasse `HomePresenter` erzeugt. +5) Die Methode `renderDefault()` wird aufgerufen (sofern sie existiert). +6) Das Template, z. B. `default.latte`, wird samt Layout, z. B. `@layout.latte`, gerendert. -Vielleicht sind Sie jetzt auf viele neue Begriffe gestoßen, aber wir glauben, dass sie Sinn ergeben. Die Entwicklung von Anwendungen in Nette ist unglaublich entspannt. +Vielleicht sind Ihnen gerade viele neue Begriffe begegnet, aber wir glauben, dass sie Sinn ergeben. Anwendungen in Nette zu entwickeln ist bemerkenswert unkompliziert. Templates ========= -Wo wir schon bei Templates sind, in Nette wird das Template-System [Latte |latte:] verwendet. Daher auch die Endungen `.latte` bei den Templates. Latte wird zum einen verwendet, weil es das sicherste Template-System für PHP ist, und zum anderen auch das intuitivste System. Sie müssen nicht viel Neues lernen, Sie kommen mit PHP-Kenntnissen und ein paar Tags aus. Alles erfahren Sie [in der Dokumentation |templates]. +Da wir gerade bei Templates sind: Nette verwendet das Templating-System [Latte |latte:]. Deshalb haben die Template-Dateien die Endung `.latte`. Latte wird vor allem deshalb verwendet, weil es das sicherste Templating-System für PHP ist und zugleich das intuitivste. Sie müssen nicht viel Neues lernen; Kenntnisse von PHP und einigen wenigen Tags genügen. Alles Nötige finden Sie [in der Dokumentation |templates]. -Im Template werden [Links erstellt |creating-links] zu anderen Presentern & Aktionen wie folgt: +Im Template [erstellen Sie Links |creating-links] auf andere Presenter & Aktionen so: ```latte Produktdetail ``` -Einfach statt der realen URL schreiben Sie das bekannte Paar `Presenter:action` und geben eventuelle Parameter an. Der Trick liegt im `n:href`, das besagt, dass dieses Attribut von Nette verarbeitet wird. Und es generiert: +Schreiben Sie einfach das vertraute Paar `Presenter:Aktion` statt der tatsächlichen URL und ergänzen Sie eventuelle Parameter. Der Trick steckt in `n:href`, das Nette sagt, dieses Attribut zu verarbeiten. Es erzeugt dann: ```latte Produktdetail ``` -Die Generierung der URL übernimmt der bereits erwähnte Router. Denn Router in Nette sind außergewöhnlich, da sie nicht nur Transformationen von URL zum Paar Presenter:Aktion durchführen können, sondern auch umgekehrt, also aus dem Namen des Presenters + Aktion + Parametern eine URL generieren. Dadurch können Sie in Nette die Formen der URLs in der gesamten fertigen Anwendung vollständig ändern, ohne ein einziges Zeichen im Template oder Presenter zu ändern. Nur durch die Anpassung des Routers. Dadurch funktioniert auch die sogenannte Kanonisierung, was eine weitere einzigartige Eigenschaft von Nette ist, die zu besserem SEO (Optimierung der Auffindbarkeit im Internet) beiträgt, indem sie automatisch die Existenz von doppeltem Inhalt unter verschiedenen URLs verhindert. Viele Programmierer finden das erstaunlich. +Um die Erzeugung der URL kümmert sich der bereits erwähnte Router. Router sind in Nette außergewöhnlich, weil sie nicht nur die Umwandlung einer URL in das Paar `Presenter:Aktion` beherrschen, sondern auch den umgekehrten Weg: aus dem Namen des Presenters, der Aktion und den Parametern eine URL zu erzeugen. Dadurch können Sie in einer fertigen Anwendung in Nette das URL-Format vollständig ändern, ohne in den Templates oder Presentern ein einziges Zeichen anzufassen - Sie ändern lediglich den Router. Das ermöglicht auch die sogenannte Kanonisierung, eine weitere einzigartige Eigenschaft von Nette, die das SEO (Search Engine Optimization) verbessert, indem sie automatisch verhindert, dass derselbe Inhalt unter verschiedenen URLs existiert. Viele Programmierer finden das erstaunlich. Interaktive Komponenten ======================= -Über Presenter müssen wir Ihnen noch eine Sache verraten: Sie haben ein eingebautes Komponentensystem. Etwas Ähnliches kennen Ältere vielleicht aus Delphi oder ASP.NET Web Forms, auf etwas entfernt Ähnlichem bauen React oder Vue.js auf. In der Welt der PHP-Frameworks ist dies eine absolut einzigartige Angelegenheit. +Über Presenter müssen wir Ihnen noch eines sagen: Sie haben ein eingebautes Komponentensystem. Erfahrenere erinnern sich vielleicht an etwas Ähnliches aus Delphi oder ASP.NET Web Forms; React oder Vue.js bauen auf entfernt verwandten Konzepten auf. In der Welt der PHP-Frameworks ist das eine völlig einzigartige Eigenschaft. -Komponenten sind eigenständige wiederverwendbare Einheiten, die wir in Seiten (also Presenter) einfügen. Das können [Formulare |forms:in-presenter], [Datagrids |https://componette.org/contributte/datagrid/], Menüs, Abstimmungsumfragen sein, eigentlich alles, was sinnvoll wiederverwendet werden kann. Wir können eigene Komponenten erstellen oder einige aus dem [riesigen Angebot |https://componette.org] an Open-Source-Komponenten verwenden. +Komponenten sind eigenständige, wiederverwendbare Einheiten, die wir in Seiten (also in Presenter) einbetten. Das können [Formulare |forms:in-presenter] sein, [Datagrids |https://componette.org/contributte/datagrid/], Menüs, Umfragen - kurz alles, was sich sinnvoll wiederverwenden lässt. Wir können eigene Komponenten erstellen oder einige aus der [riesigen Auswahl |https://componette.org] an Open-Source-Komponenten nutzen. -Komponenten beeinflussen grundlegend den Ansatz zur Anwendungsentwicklung. Sie eröffnen Ihnen neue Möglichkeiten, Seiten aus vorgefertigten Einheiten zusammenzusetzen. Und außerdem haben sie etwas mit [Hollywood gemeinsam |components#Hollywood Style]. +Komponenten beeinflussen die Herangehensweise an die Anwendungsentwicklung grundlegend. Sie eröffnen neue Möglichkeiten, Seiten aus vorbereiteten Einheiten zusammenzusetzen. Und sie haben auch etwas mit [Hollywood |components#Hollywood Style] gemeinsam. DI-Container und Konfiguration ============================== -Der DI-Container oder die Objekt-Factory ist das Herz der gesamten Anwendung. +Der DI-Container, also die Objekt-Factory, ist das Herz der gesamten Anwendung. -Keine Sorge, es ist keine magische Blackbox, wie es vielleicht aus den vorherigen Zeilen scheinen mag. Eigentlich ist es eine ziemlich langweilige PHP-Klasse, die Nette generiert und im Cache-Verzeichnis speichert. Sie hat viele Methoden namens `createServiceAbcd()` und jede von ihnen kann ein bestimmtes Objekt erstellen und zurückgeben. Ja, es gibt auch eine Methode `createServiceApplication()`, die `Nette\Application\Application` erstellt, das wir in der Datei `index.php` zum Starten der Anwendung benötigten. Und es gibt Methoden, die einzelne Presenter erstellen. Und so weiter. +Keine Sorge, es ist keine magische Blackbox, wie es die vorangegangenen Zeilen vielleicht nahelegen. In Wirklichkeit ist es eine ziemlich langweilige PHP-Klasse, die Nette erzeugt und im Cache-Verzeichnis ablegt. Sie enthält viele Methoden mit Namen wie `createServiceAbcd()`, von denen jede ein bestimmtes Objekt erzeugen und zurückgeben kann. Ja, darunter ist auch die Methode `createServiceApplication__application()`, die die Instanz von `Nette\Application\Application` erzeugt, die wir in `index.php` zum Starten der Anwendung gebraucht haben. Und es gibt Methoden zum Erzeugen der einzelnen Presenter und so weiter. -Objekte, die der DI-Container erstellt, werden aus irgendeinem Grund Dienste genannt. +Die vom DI-Container erzeugten Objekte nennt man aus irgendeinem Grund Services. -Was an dieser Klasse wirklich besonders ist, ist, dass nicht Sie sie programmieren, sondern das Framework. Es generiert tatsächlich den PHP-Code und speichert ihn auf der Festplatte. Sie geben nur Anweisungen, welche Objekte der Container erstellen können soll und wie genau. Und diese Anweisungen sind in [Konfigurationsdateien |bootstrapping#Konfiguration des DI-Containers] geschrieben, für die das Format [NEON|neon:format] verwendet wird und die daher auch die Erweiterung `.neon` haben. +Das wirklich Besondere an dieser Klasse ist, dass Sie sie nicht programmieren - das übernimmt das Framework. Es erzeugt tatsächlich den PHP-Code und speichert ihn auf der Festplatte. Sie geben nur Anweisungen, welche Objekte der Container erzeugen können soll und wie genau. Diese Anweisungen stehen in [Konfigurationsdateien |bootstrapping#Konfiguration des DI-Containers], die das Format [NEON|neon:format] verwenden und daher die Endung `.neon` haben. -Konfigurationsdateien dienen rein dazu, den DI-Container zu instruieren. Wenn ich also zum Beispiel im Abschnitt [session |http:configuration#Session] die Option `expiration: 14 days` angebe, ruft der DI-Container beim Erstellen des Objekts `Nette\Http\Session`, das die Session repräsentiert, dessen Methode `setExpiration('14 days')` auf und damit wird die Konfiguration Realität. +Konfigurationsdateien dienen ausschließlich dazu, den DI-Container zu instruieren. Wenn Sie also zum Beispiel im Abschnitt [session |http:configuration#Session] die Option `expiration: 14 days` angeben, ruft der DI-Container beim Erzeugen des Objekts `Nette\Http\Session`, das die Session repräsentiert, dessen Methode `setExpiration('14 days')` auf und macht die Konfiguration damit Wirklichkeit. -Es gibt für Sie ein ganzes Kapitel, das beschreibt, was alles [konfiguriert |nette:configuring] werden kann und wie man [eigene Dienste definiert |dependency-injection:services]. +Für Sie ist ein ganzes Kapitel vorbereitet, das beschreibt, was sich alles [konfigurieren |nette:configuring] lässt und wie Sie [eigene Services definieren |dependency-injection:services]. -Sobald Sie etwas tiefer in die Erstellung von Diensten eintauchen, werden Sie auf das Wort [Autowiring |dependency-injection:autowiring] stoßen. Das ist ein Feature, das Ihnen das Leben unglaublich vereinfacht. Es kann Objekte automatisch dorthin übergeben, wo Sie sie benötigen (z.B. in den Konstruktoren Ihrer Klassen), ohne dass Sie etwas tun müssen. Sie werden feststellen, dass der DI-Container in Nette ein kleines Wunder ist. +Sobald Sie sich ein wenig in das Erzeugen von Services vertieft haben, begegnet Ihnen der Begriff [Autowiring |dependency-injection:autowiring]. Das ist eine Eigenschaft, die Ihnen das Leben unglaublich erleichtert. Sie kann Objekte automatisch dorthin übergeben, wo Sie sie brauchen (zum Beispiel in die Konstruktoren Ihrer Klassen), ohne dass Sie etwas tun müssen. Sie werden feststellen, dass der DI-Container in Nette ein kleines Wunder ist. -Wohin als nächstes? +Wie geht es weiter? =================== -Wir sind die Grundprinzipien von Anwendungen in Nette durchgegangen. Bisher sehr oberflächlich, aber bald werden Sie in die Tiefe vordringen und mit der Zeit wunderbare Webanwendungen erstellen. Wohin soll es als nächstes gehen? Haben Sie schon das Tutorial [Meine erste Anwendung schreiben|quickstart:] ausprobiert? +Wir haben die grundlegenden Prinzipien von Anwendungen in Nette durchgenommen. Bisher war es nur ein Blick an die Oberfläche, aber Sie werden bald tiefer eintauchen und mit der Zeit großartige Webanwendungen erschaffen. Wo geht es weiter? Haben Sie schon das Tutorial [Erstellen Sie Ihre erste Anwendung|quickstart:] ausprobiert? -Neben dem oben Beschriebenen verfügt Nette über ein ganzes Arsenal an [nützlichen Klassen|utils:], eine [Datenbankschicht|database:], usw. Versuchen Sie doch mal, einfach so durch die Dokumentation zu klicken. Oder den [Blog|https://blog.nette.org]. Sie werden viele interessante Dinge entdecken. +Über das oben Beschriebene hinaus bietet Nette ein ganzes Arsenal [nützlicher Klassen|utils:], eine [Datenbankschicht|database:] usw. Klicken Sie sich ruhig durch die Dokumentation. Oder besuchen Sie den [Blog|https://blog.nette.org]. Sie werden viel Interessantes entdecken. Möge Ihnen das Framework viel Freude bereiten 💙 diff --git a/application/de/multiplier.texy b/application/de/multiplier.texy index c4b504a032..61c6c08e84 100644 --- a/application/de/multiplier.texy +++ b/application/de/multiplier.texy @@ -2,31 +2,31 @@ Multiplier: dynamische Komponenten ********************************** .[perex] -Werkzeug zur dynamischen Erstellung interaktiver Komponenten +Ein Werkzeug für die dynamische Erstellung interaktiver Komponenten. -Beginnen wir mit einem typischen Beispiel: Wir haben eine Liste von Waren in einem E-Shop, und für jede Ware möchten wir ein Formular zum Hinzufügen in den Warenkorb anzeigen. Eine mögliche Variante ist, die gesamte Auflistung in ein einziges Formular zu packen. Einen viel bequemeren Weg bietet uns jedoch [api:Nette\Application\UI\Multiplier]. +Beginnen wir mit einem typischen Beispiel: Stellen Sie sich eine Produktliste in einem E-Shop vor, bei der Sie zu jedem Artikel ein Formular "In den Warenkorb legen" ausgeben möchten. Eine mögliche Variante ist, die gesamte Auflistung in ein einziges Formular zu packen. Einen viel bequemeren Weg bietet jedoch [api:Nette\Application\UI\Multiplier]. -Multiplier ermöglicht es, bequem eine Factory für mehrere Komponenten zu definieren. Es funktioniert nach dem Prinzip verschachtelter Komponenten - jede Komponente, die von [api:Nette\ComponentModel\Container] erbt, kann weitere Komponenten enthalten. +Der Multiplier erlaubt es, bequem eine Factory für mehrere Komponenten zu definieren. Er funktioniert nach dem Prinzip verschachtelter Komponenten - jede Komponente, die von [api:Nette\ComponentModel\Container] erbt, kann weitere Komponenten enthalten. .[tip] -Siehe das Kapitel über das [Komponentenmodell |components#Komponenten im Detail] in der Dokumentation oder den [Vortrag von Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. +Siehe das Kapitel über das [Komponentenmodell |components#Komponenten im Detail] in der Dokumentation. -Das Wesen des Multipliers ist, dass er als Elternteil fungiert, der seine Nachkommen dynamisch mithilfe eines im Konstruktor übergebenen Callbacks erstellen kann. Siehe Beispiel: +Das Wesen des Multipliers ist, dass er als Elternteil auftritt, der seine Kinder mithilfe eines im Konstruktor übergebenen Callbacks dynamisch erzeugen kann. Siehe das Beispiel: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Anzahl der Artikel:') + $form->addInteger('amount', 'Anzahl:') ->setRequired(); - $form->addSubmit('send', 'In den Warenkorb legen'); + $form->addSubmit('send', 'In den Warenkorb'); return $form; }); } ``` -Jetzt können wir im Template einfach für jeden Artikel das Formular rendern lassen - und jedes wird tatsächlich eine eindeutige Komponente sein. +Nun können wir im Template ganz einfach zu jedem Produkt das Formular ausgeben lassen - und jedes davon wird wirklich eine eigenständige Komponente sein. ```latte {foreach $items as $item} @@ -37,26 +37,26 @@ Jetzt können wir im Template einfach für jeden Artikel das Formular rendern la {/foreach} ``` -Das im Tag `{control}` übergebene Argument hat ein Format, das besagt: +Das im Tag `{control}` übergebene Argument hat ein Format, das bedeutet: -1. hole die Komponente `shopForm` -2. und hole daraus den Nachkommen `$item->id` +1. Hole die Komponente `shopForm`. +2. Hole aus ihr das Kind mit dem Namen `$item->id`. -Beim ersten Aufruf von Punkt **1.** existiert `shopForm` noch nicht, also wird seine Factory `createComponentShopForm` aufgerufen. Auf der erhaltenen Komponente (Instanz des Multipliers) wird dann die Factory des konkreten Formulars aufgerufen - was die anonyme Funktion ist, die wir dem Multiplier im Konstruktor übergeben haben. +Beim ersten Aufruf von Punkt **1** existiert die Komponente `shopForm` noch nicht, daher wird ihre Factory `createComponentShopForm` aufgerufen. Auf der so erhaltenen Komponente (einer Instanz des Multipliers) wird dann die Factory des konkreten Formulars aufgerufen - das ist die anonyme Funktion, die wir dem Konstruktor des Multipliers übergeben haben. -In der nächsten Iteration des foreache wird die Methode `createComponentShopForm` nicht mehr aufgerufen (die Komponente existiert), aber da wir einen anderen Nachkommen suchen (`$item->id` wird in jeder Iteration anders sein), wird die anonyme Funktion erneut aufgerufen und gibt uns ein neues Formular zurück. +In der nächsten Iteration der foreach-Schleife wird die Methode `createComponentShopForm` nicht erneut aufgerufen (die Komponente existiert bereits). Da wir jedoch ein anderes Kind suchen (denn `$item->id` ist in jeder Iteration anders), wird die anonyme Funktion erneut aufgerufen und liefert ein neues Formular zurück. -Das Einzige, was übrig bleibt, ist sicherzustellen, dass das Formular tatsächlich die richtige Ware in den Warenkorb legt - derzeit ist das Formular für jeden Artikel völlig identisch. Hier hilft uns eine Eigenschaft des Multipliers (und generell jeder Komponenten-Factory im Nette Framework), nämlich dass jede Factory als erstes Argument den Namen der zu erstellenden Komponente erhält. In unserem Fall ist das `$item->id`, was genau die Information ist, die wir benötigen. Es genügt also, die Erstellung des Formulars leicht anzupassen: +Es bleibt nur noch sicherzustellen, dass das Formular auch wirklich das richtige Produkt in den Warenkorb legt - derzeit ist das Formular bei jedem Produkt identisch. Dabei hilft uns eine Eigenschaft des Multipliers (und generell jeder Komponenten-Factory im Nette Framework): Jede Factory erhält als erstes Argument den Namen der erzeugten Komponente. Die Factory des Multipliers erhält darüber hinaus als zweites Argument die Instanz des Multipliers selbst. In unserem Fall ist das erste Argument `$item->id`, also genau die Information, die wir brauchen. Es genügt daher, die Erstellung des Formulars leicht anzupassen: ```php protected function createComponentShopForm(): Multiplier { - return new Multiplier(function ($itemId) { + return new Multiplier(function (string $itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Anzahl der Artikel:') + $form->addInteger('amount', 'Anzahl:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'In den Warenkorb legen'); + $form->addSubmit('send', 'In den Warenkorb'); return $form; }); } diff --git a/application/de/presenters.texy b/application/de/presenters.texy index e8764b84bc..27a758fbbe 100644 --- a/application/de/presenters.texy +++ b/application/de/presenters.texy @@ -3,37 +3,37 @@ Presenter
    -Wir werden lernen, wie man Presenter und Templates in Nette schreibt. Nach dem Lesen werden Sie wissen: +Wir sehen uns an, wie in Nette Presenter und Templates geschrieben werden. Nach der Lektüre werden Sie wissen: -- wie ein Presenter funktioniert +- wie Presenter funktionieren - was persistente Parameter sind - wie Templates gerendert werden
    -[Wir wissen bereits |how-it-works#Nette Application], dass ein Presenter eine Klasse ist, die eine bestimmte Seite einer Webanwendung repräsentiert, z. B. die Homepage, ein Produkt in einem E-Shop, ein Anmeldeformular, einen Sitemap-Feed usw. Eine Anwendung kann einen bis tausende Presenter haben. In anderen Frameworks werden sie auch Controller genannt. +[Wir wissen bereits |how-it-works#Nette Application], dass ein Presenter eine Klasse ist, die eine konkrete Seite einer Webanwendung repräsentiert, etwa die Startseite, ein Produkt im E-Shop, ein Anmeldeformular, einen Sitemap-Feed usw. Eine Anwendung kann von einem bis zu Tausenden von Presentern haben. In anderen Frameworks sind sie auch als Controller bekannt. -Normalerweise ist mit dem Begriff Presenter ein Nachkomme der Klasse [api:Nette\Application\UI\Presenter] gemeint, der für die Generierung von Weboberflächen geeignet ist und dem wir uns im Rest dieses Kapitels widmen werden. Im allgemeinen Sinn ist ein Presenter jedes Objekt, das das Interface [api:Nette\Application\IPresenter] implementiert. +Üblicherweise meint der Begriff Presenter einen Nachfahren der Klasse [api:Nette\Application\UI\Presenter], die sich zum Erzeugen von Weboberflächen eignet und der sich der Rest dieses Kapitels widmet. Im allgemeinen Sinn ist ein Presenter jedes Objekt, das das Interface [api:Nette\Application\IPresenter] implementiert. Lebenszyklus des Presenters =========================== -Die Aufgabe des Presenters ist es, eine Anfrage zu bearbeiten und eine Antwort zurückzugeben (dies kann eine HTML-Seite, ein Bild, eine Weiterleitung usw. sein). +Die Aufgabe des Presenters ist es, einen Request zu bearbeiten und eine Antwort zurückzugeben (das kann eine HTML-Seite sein, ein Bild, eine Weiterleitung usw.). -Zu Beginn wird ihm also eine Anfrage übergeben. Dies ist keine direkte HTTP-Anfrage, sondern ein Objekt [api:Nette\Application\Request], in das die HTTP-Anfrage mithilfe des Routers umgewandelt wurde. Mit diesem Objekt kommen wir normalerweise nicht in Berührung, da der Presenter die Verarbeitung der Anfrage geschickt an weitere Methoden delegiert, die wir uns jetzt ansehen werden. +Zu Beginn wird ihm also ein Request übergeben. Das ist nicht direkt der HTTP-Request, sondern ein Objekt [api:Nette\Application\Request], in das der HTTP-Request mithilfe des Routers umgewandelt wurde. Mit diesem Objekt kommen wir üblicherweise nicht direkt in Berührung, denn der Presenter delegiert die Bearbeitung des Requests geschickt an andere Methoden, die wir uns nun ansehen. -[* lifecycle.svg *] *** *Lebenszyklus des Presenters* .<> +[* lifecycle.svg *] *** Lebenszyklus des Presenters .<> -Das Bild zeigt eine Liste von Methoden, die der Reihe nach von oben nach unten aufgerufen werden, sofern sie existieren. Keine davon muss existieren, wir können einen völlig leeren Presenter ohne eine einzige Methode haben und darauf eine einfache statische Website aufbauen. +Das Diagramm zeigt eine Liste von Methoden, die der Reihe nach von oben nach unten aufgerufen werden, sofern sie existieren. Keine davon ist verpflichtend; Sie können einen völlig leeren Presenter ohne eine einzige Methode haben und darauf eine einfache statische Website aufbauen. `__construct()` --------------- -Der Konstruktor gehört nicht ganz zum Lebenszyklus des Presenters, da er im Moment der Objekterstellung aufgerufen wird. Aber wir erwähnen ihn wegen seiner Wichtigkeit. Der Konstruktor (zusammen mit der [inject-Methode|best-practices:inject-method-attribute]) dient zur Übergabe von Abhängigkeiten. +Der Konstruktor gehört streng genommen nicht zum Lebenszyklus des Presenters, da er im Moment der Objekterzeugung aufgerufen wird. Wir erwähnen ihn jedoch wegen seiner Bedeutung. Der Konstruktor (zusammen mit der [Inject-Methode|best-practices:inject-method-attribute]) dient der Übergabe von Abhängigkeiten. -Ein Presenter sollte nicht die Geschäftslogik der Anwendung handhaben, aus der Datenbank schreiben und lesen, Berechnungen durchführen usw. Dafür gibt es Klassen aus der Schicht, die wir als Model bezeichnen. Zum Beispiel kann die Klasse `ArticleRepository` für das Laden und Speichern von Artikeln zuständig sein. Damit der Presenter damit arbeiten kann, lässt er sie sich [mittels Dependency Injection übergeben |dependency-injection:passing-dependencies]: +Der Presenter sollte nicht die Geschäftslogik der Anwendung erledigen, nicht in die Datenbank schreiben oder aus ihr lesen, keine Berechnungen anstellen usw. Dafür sind Klassen in der Schicht zuständig, die wir Model nennen. Zum Beispiel kann eine Klasse `ArticleRepository` für das Laden und Speichern von Artikeln verantwortlich sein. Damit der Presenter mit ihr arbeiten kann, muss sie ihm [per Dependency Injection übergeben werden |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,47 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Unmittelbar nach Erhalt der Anfrage wird die Methode `startup()` aufgerufen. Sie können sie zur Initialisierung von Properties, zur Überprüfung von Benutzerberechtigungen usw. verwenden. Es ist erforderlich, dass die Methode immer den Vorfahren `parent::startup()` aufruft. +Unmittelbar nach dem Empfang des Requests wird die Methode `startup()` aufgerufen. Sie können sie zum Initialisieren von Properties, zum Prüfen von Benutzerberechtigungen usw. verwenden. Es ist erforderlich, dass diese Methode immer ihren Vorfahren aufruft: `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Ähnlich der Methode `render()`. Während `render()` dazu dient, Daten für ein bestimmtes Template vorzubereiten, das anschließend gerendert wird, wird in `action()` die Anfrage ohne Bezug zum Rendern des Templates verarbeitet. Zum Beispiel werden Daten verarbeitet, der Benutzer an- oder abgemeldet usw., und danach [woandershin weitergeleitet |#Weiterleitung]. +Ähnlich wie die Methode `render()`. Während `render()` dazu gedacht ist, Daten für ein konkretes Template vorzubereiten, das anschließend gerendert wird, verarbeitet `action()` einen Request, ohne dass danach zwingend ein Template gerendert wird. Sie kann zum Beispiel Daten verarbeiten, einen Benutzer an- oder abmelden und so weiter, und dann [woandershin weiterleiten |#Weiterleitung]. -Wichtig ist, dass `action()` vor `render()` aufgerufen wird, sodass wir darin gegebenenfalls den weiteren Verlauf ändern können, d.h. das zu rendernde Template und auch die aufzurufende `render()`-Methode ändern können. Und zwar mittels `setView('anderesView')`. +Wichtig ist, dass `action()` *vor* `render()` aufgerufen wird. Dadurch können wir den Verlauf des Requests in der Action-Methode noch ändern, etwa das Template, das gerendert wird, oder sogar die aufzurufende Methode `render()` mit `setView('otherView')` austauschen. -Der Methode werden Parameter aus der Anfrage übergeben. Es ist möglich und empfohlen, den Parametern Typen anzugeben, z.B. `actionShow(int $id, ?string $slug = null)` - wenn der Parameter `id` fehlt oder kein Integer ist, gibt der Presenter einen [Fehler 404 |#Fehler 404 und Co] zurück und beendet die Tätigkeit. +.{data-version:3.2.3} +Sie können mit der Methode `switch('otherAction')` sogar zu einer völlig anderen Aktion wechseln. Sie bricht die aktuelle Methode ab und führt stattdessen die Methoden `action()` und `render()` der neuen Aktion aus (und schaltet die automatische [Kanonisierung|#Kanonisierung] ab). Der Request selbst läuft weiter; unterbrochen wird nur die gerade laufende Methode. + +Der Methode werden die Parameter aus dem Request übergeben. Es ist möglich und empfehlenswert, für diese Parameter Typen anzugeben, z. B. `actionShow(int $id, ?string $slug = null)`. Fehlt der Parameter `id` oder ist er keine ganze Zahl, gibt der Presenter einen [Fehler 404 |#Fehler 404 usw.] zurück und endet. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Diese Methode verarbeitet sogenannte Signale, die wir im Kapitel über [Komponenten |components#Signal] kennenlernen werden. Sie ist nämlich hauptsächlich für Komponenten und die Verarbeitung von AJAX-Anfragen gedacht. +Diese Methode verarbeitet die sogenannten Signale, die wir im Kapitel über [Komponenten |components#Signal] kennenlernen. Sie ist vor allem für Komponenten und die Bearbeitung von AJAX-Requests gedacht. -Der Methode werden Parameter aus der Anfrage übergeben, wie im Fall von `action()`, einschließlich Typüberprüfung. +Der Methode werden, genau wie bei `action()`, die Parameter aus dem Request übergeben, samt Typprüfung. `beforeRender()` ---------------- -Die Methode `beforeRender`, wie der Name schon sagt, wird vor jeder `render()`-Methode aufgerufen. Sie wird für die gemeinsame Konfiguration des Templates, die Übergabe von Variablen für das Layout und ähnliches verwendet. +Die Methode `beforeRender` wird, wie ihr Name nahelegt, vor jeder Methode `render()` aufgerufen. Sie dient der gemeinsamen Konfiguration von Templates, der Übergabe von Variablen an das Layout und Ähnlichem. `render(args...)` .{toc: render()} ---------------------------------------------- -Der Ort, an dem wir das Template für das anschließende Rendern vorbereiten, ihm Daten übergeben usw. +Hier bereiten wir das Template für das anschließende Rendern vor, übergeben ihm Daten usw. -Der Methode werden Parameter aus der Anfrage übergeben, wie im Fall von `action()`, einschließlich Typüberprüfung. +Der Methode werden, genau wie bei `action()`, die Parameter aus dem Request übergeben, samt Typprüfung. ```php public function renderShow(int $id): void { - // Wir holen Daten aus dem Model und übergeben sie an das Template + // Daten aus dem Model holen und an das Template übergeben $this->template->article = $this->articles->getById($id); } ``` @@ -96,7 +99,7 @@ public function renderShow(int $id): void `afterRender()` --------------- -Die Methode `afterRender`, wie der Name wiederum andeutet, wird nach jeder `render()`-Methode aufgerufen. Sie wird eher selten verwendet. +Die Methode `afterRender` wird, wie der Name wiederum nahelegt, nach jeder Methode `render()` aufgerufen. Sie wird eher selten verwendet. `shutdown()` @@ -105,95 +108,117 @@ Die Methode `afterRender`, wie der Name wiederum andeutet, wird nach jeder `rend Wird am Ende des Lebenszyklus des Presenters aufgerufen. -**Ein guter Rat, bevor wir weitermachen**. Ein Presenter kann, wie man sieht, mehrere Aktionen/Views bedienen, also mehrere `render()`-Methoden haben. Wir empfehlen jedoch, Presenter mit einer oder möglichst wenigen Aktionen zu entwerfen. +Events +------ + +Neben den Methoden `startup()`, `beforeRender()` und `shutdown()`, die im Rahmen des Lebenszyklus des Presenters aufgerufen werden, lassen sich weitere Funktionen definieren, die automatisch aufgerufen werden. Der Presenter definiert sogenannte [Events |nette:glossary#Events], und Sie fügen deren Handler in die Arrays `$onStartup`, `$onRender` und `$onShutdown` ein. +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` -Senden der Antwort -================== +Die Handler im Array `$onStartup` werden unmittelbar vor der Methode `startup()` aufgerufen, die Handler in `$onRender` zwischen `beforeRender()` und `render()` und schließlich die Handler in `$onShutdown` unmittelbar vor `shutdown()`. -Die Antwort des Presenters ist normalerweise das [Rendern eines Templates mit einer HTML-Seite|templates], es kann aber auch das Senden einer Datei, JSON oder eine Weiterleitung zu einer anderen Seite sein. -Wir können jederzeit während des Lebenszyklus mit einer der folgenden Methoden eine Antwort senden und gleichzeitig den Presenter beenden: +**Ein Rat, bevor wir weitermachen:** Wie Sie sehen, kann ein Presenter mehrere Aktionen/Views bearbeiten, also mehrere Methoden `render()` haben. Wir empfehlen jedoch, Presenter mit einer oder möglichst wenigen Aktionen zu entwerfen. -- `redirect()`, `redirectPermanent()`, `redirectUrl()` und `forward()` [leiten weiter |#Weiterleitung] -- `error()` beendet den Presenter [aufgrund eines Fehlers |#Fehler 404 und Co] -- `sendJson($data)` beendet den Presenter und [sendet Daten |#Senden von JSON] im JSON-Format + +Eine Antwort senden +=================== + +Die Antwort des Presenters ist typischerweise das [Rendern eines Templates in eine HTML-Seite|templates], es kann aber auch das Senden einer Datei sein, von JSON oder sogar eine Weiterleitung auf eine andere Seite. + +Zu jedem Zeitpunkt des Lebenszyklus können wir mit einer der folgenden Methoden eine Antwort senden und den Presenter zugleich beenden: + +- `redirect()`, `redirectPermanent()`, `redirectUrl()` und `forward()` führen eine [Weiterleitung |#Weiterleitung] aus +- `error()` beendet den Presenter [wegen eines Fehlers |#Fehler 404 usw.] +- `sendJson($data)` beendet den Presenter und [sendet Daten |#JSON senden] im JSON-Format - `sendTemplate()` beendet den Presenter und [rendert sofort das Template |templates] - `sendResponse($response)` beendet den Presenter und sendet eine [eigene Antwort |#Antworten] - `terminate()` beendet den Presenter ohne Antwort -Wenn Sie keine dieser Methoden aufrufen, greift der Presenter automatisch auf das Rendern des Templates zurück. Warum? Weil wir in 99 % der Fälle ein Template rendern möchten, daher betrachtet der Presenter dieses Verhalten als Standard und möchte uns die Arbeit erleichtern. +Jede dieser Methoden beendet den Presenter sofort, indem sie die stille Beendigungs-Exception `Nette\Application\AbortException` wirft. +Rufen Sie keine dieser Methoden auf, geht der Presenter automatisch zum Rendern des Templates über. Warum? Weil wir in 99 % der Fälle ein Template rendern wollen, macht der Presenter dieses Verhalten zum Standardverhalten und erleichtert uns die Arbeit. -Erstellen von Links -=================== -Der Presenter verfügt über die Methode `link()`, mit der URL-Links zu anderen Presentern erstellt werden können. Der erste Parameter ist der Ziel-Presenter & Aktion, gefolgt von den übergebenen Argumenten, die als Array angegeben werden können: +Links erstellen +=============== + +Der Presenter hat die Methode `link()`, mit der URL-Links auf andere Presenter erstellt werden. Der erste Parameter ist der Ziel-Presenter & die Aktion, gefolgt von Argumenten, die sich auch als Array übergeben lassen: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$id, 'lang' => 'en']); ``` -Im Template werden Links zu anderen Presentern & Aktionen auf diese Weise erstellt: +Im Template werden Links auf andere Presenter & Aktionen so erstellt: ```latte Produktdetail ``` -Statt der echten URL schreiben Sie einfach das bekannte Paar `Presenter:action` und geben eventuelle Parameter an. Der Trick liegt in `n:href`, das besagt, dass dieses Attribut von Latte verarbeitet wird und die echte URL generiert. In Nette müssen Sie also überhaupt nicht über URLs nachdenken, nur über Presenter und Aktionen. +Schreiben Sie einfach das vertraute Paar `Presenter:action` statt der tatsächlichen URL und ergänzen Sie eventuelle Parameter. Der Trick steckt in `n:href`, das Latte sagt, dieses Attribut zu verarbeiten und die echte URL zu erzeugen. In Nette müssen Sie überhaupt nicht über URLs nachdenken, sondern nur über Presenter und Aktionen. -Weitere Informationen finden Sie im Kapitel [Erstellen von URL-Links|creating-links]. +Mehr dazu finden Sie im Kapitel [Erstellen von URL-Links|creating-links]. Weiterleitung ============= -Zum Wechsel zu einem anderen Presenter dienen die Methoden `redirect()` und `forward()`, die eine sehr ähnliche Syntax wie die Methode [link() |#Erstellen von Links] haben. +Zum Wechsel auf einen anderen Presenter dienen die Methoden `redirect()` und `forward()`. Sie haben eine sehr ähnliche Syntax wie die Methode [link() |#Links erstellen]. -Die Methode `forward()` wechselt sofort zum neuen Presenter ohne HTTP-Weiterleitung: +Die Methode `forward()` wechselt ohne HTTP-Weiterleitung sofort auf den neuen Presenter: ```php $this->forward('Product:show'); ``` -Ein Beispiel für eine sogenannte temporäre Weiterleitung mit dem HTTP-Code 302 (oder 303, wenn die Methode der aktuellen Anfrage POST ist): +Beispiel für eine temporäre Weiterleitung mit dem HTTP-Code 302 (oder 303, wenn die aktuelle Request-Methode POST ist): ```php $this->redirect('Product:show', $id); ``` -Eine permanente Weiterleitung mit dem HTTP-Code 301 erreichen Sie so: +Für eine dauerhafte Weiterleitung mit dem HTTP-Code 301 verwenden Sie dies: ```php $this->redirectPermanent('Product:show', $id); ``` -Zu einer anderen URL außerhalb der Anwendung kann mit der Methode `redirectUrl()` weitergeleitet werden. Als zweiter Parameter kann der HTTP-Code angegeben werden, Standard ist 302 (oder 303, wenn die Methode der aktuellen Anfrage POST ist): +Auf eine andere URL außerhalb der Anwendung leiten Sie mit der Methode `redirectUrl()` weiter. Als zweiter Parameter lässt sich der HTTP-Code angeben, Standard ist 302 (oder 303, wenn die aktuelle Request-Methode POST ist): ```php $this->redirectUrl('https://nette.org'); ``` -Die Weiterleitung beendet sofort die Tätigkeit des Presenters durch Auslösen der sogenannten stillen Beendigungs-Ausnahme `Nette\Application\AbortException`. +Eine Weiterleitung beendet die Tätigkeit des Presenters sofort, indem sie die sogenannte stille Beendigungs-Exception `Nette\Application\AbortException` wirft. -Vor der Weiterleitung können [#Flash-Nachrichten] gesendet werden, also Nachrichten, die nach der Weiterleitung im Template angezeigt werden. +Vor der Weiterleitung lassen sich [#Flash-Meldungen] senden, also Meldungen, die nach der Weiterleitung im Template angezeigt werden. -Flash-Nachrichten -================= +Flash-Meldungen +=============== -Dies sind Nachrichten, die normalerweise über das Ergebnis einer Operation informieren. Ein wichtiges Merkmal von Flash-Nachrichten ist, dass sie auch nach einer Weiterleitung im Template verfügbar sind. Auch nach der Anzeige bleiben sie noch weitere 30 Sekunden aktiv – zum Beispiel für den Fall, dass der Benutzer aufgrund einer fehlerhaften Übertragung die Seite neu lädt - die Nachricht verschwindet also nicht sofort. +Das sind Meldungen, die typischerweise über das Ergebnis einer Operation informieren. Eine wichtige Eigenschaft von Flash-Meldungen ist, dass sie im Template auch nach einer Weiterleitung verfügbar bleiben. Nach der Anzeige bleiben sie noch 30 Sekunden aktiv - lädt der Benutzer die Seite zum Beispiel wegen eines Übertragungsfehlers neu, verschwindet die Meldung nicht sofort. -Rufen Sie einfach die Methode [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] auf, und der Presenter kümmert sich um die Übergabe an das Template. Der erste Parameter ist der Text der Nachricht und der optionale zweite Parameter ihr Typ (error, warning, info usw.). Die Methode `flashMessage()` gibt eine Instanz der Flash-Nachricht zurück, der weitere Informationen hinzugefügt werden können. +Rufen Sie einfach die Methode [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] auf, um die Übergabe an das Template kümmert sich der Presenter. Der erste Parameter ist der Text der Meldung, der optionale zweite Parameter ihr Typ (z. B. error, warning, info). Die Methode `flashMessage()` gibt eine Instanz der Flash-Meldung zurück, der sich weitere Informationen hinzufügen lassen. ```php -$this->flashMessage('Der Eintrag wurde gelöscht.'); -$this->redirect(/* ... */); // und wir leiten weiter +$this->flashMessage('Das Element wurde gelöscht.'); +$this->redirect(/* ... */); // und weiterleiten ``` -Im Template stehen diese Nachrichten in der Variable `$flashes` als `stdClass`-Objekte zur Verfügung, die die Eigenschaften `message` (Nachrichtentext), `type` (Nachrichtentyp) enthalten und die bereits erwähnten Benutzerinformationen enthalten können. Wir rendern sie zum Beispiel so: +Im Template stehen diese Meldungen in der Variablen `$flashes` als Objekte `stdClass` zur Verfügung, die die Properties `message` (Text der Meldung) und `type` (Typ der Meldung) sowie gegebenenfalls die erwähnten eigenen Informationen enthalten. Wir rendern sie so: ```latte {foreach $flashes as $flash} @@ -202,10 +227,10 @@ Im Template stehen diese Nachrichten in der Variable `$flashes` als `stdClass`-O ``` -Fehler 404 und Co. -================== +Fehler 404 usw. +=============== -Wenn die Anfrage nicht erfüllt werden kann, zum Beispiel weil der Artikel, den wir anzeigen möchten, nicht in der Datenbank existiert, werfen wir einen 404-Fehler mit der Methode `error(?string $message = null, int $httpCode = 404)` aus. +Kann der Request nicht erfüllt werden, zum Beispiel weil der Artikel, den wir anzeigen wollen, nicht in der Datenbank existiert, werfen wir mit der Methode `error(string $message = '', int $httpCode = 404)` einen Fehler 404. ```php public function renderShow(int $id): void @@ -218,13 +243,13 @@ public function renderShow(int $id): void } ``` -Der HTTP-Fehlercode kann als zweiter Parameter übergeben werden, Standard ist 404. Die Methode funktioniert so, dass sie die Ausnahme `Nette\Application\BadRequestException` auslöst, woraufhin die `Application` die Steuerung an den Error-Presenter übergibt. Dies ist ein Presenter, dessen Aufgabe es ist, eine Seite anzuzeigen, die über den aufgetretenen Fehler informiert. Die Einstellung des Error-Presenters erfolgt in der [Anwendungskonfiguration|configuration]. +Der HTTP-Fehlercode lässt sich als zweiter Parameter übergeben, Standard ist 404. Die Methode funktioniert so, dass sie eine `Nette\Application\BadRequestException` wirft, woraufhin die `Application` die Steuerung an den Error-Presenter übergibt. Das ist ein Presenter, dessen Aufgabe es ist, eine Seite mit Informationen über den aufgetretenen Fehler anzuzeigen. Der Error-Presenter wird in der [Konfiguration der Anwendung|configuration] eingestellt. -Senden von JSON -=============== +JSON senden +=========== -Ein Beispiel für eine Action-Methode, die Daten im JSON-Format sendet und den Presenter beendet: +Die Methode `sendJson($data)` kodiert die übergebenen Daten in JSON, sendet sie als HTTP-Antwort und beendet den Presenter. Beispiel: ```php public function actionData(): void @@ -235,12 +260,12 @@ public function actionData(): void ``` -Anfrageparameter .{data-version:3.1.14} -======================================= +Parameter des Requests .{data-version:3.1.14} +============================================= -Der Presenter und auch jede Komponente erhalten ihre Parameter aus der HTTP-Anfrage. Ihren Wert erhalten Sie mit der Methode `getParameter($name)` oder `getParameters()`. Die Werte sind Zeichenketten oder Arrays von Zeichenketten, es handelt sich im Grunde um Rohdaten, die direkt aus der URL stammen. +Der Presenter und ebenso jede Komponente beziehen ihre Parameter aus dem HTTP-Request. Ihre Werte holen Sie sich mit den Methoden `getParameter($name)` oder `getParameters()`. Die Werte sind Strings oder Arrays von Strings, im Grunde rohe Daten direkt aus der URL. -Für mehr Komfort empfehlen wir, die Parameter über Properties zugänglich zu machen. Markieren Sie sie einfach mit dem Attribut `#[Parameter]`: +Für mehr Komfort empfehlen wir, auf die Parameter über Properties zuzugreifen. Kennzeichnen Sie sie einfach mit dem Attribut `#[Parameter]`: ```php use Nette\Application\Attributes\Parameter; // diese Zeile ist wichtig @@ -252,9 +277,9 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Bei der Property empfehlen wir, auch den Datentyp anzugeben (z.B. `string`), und Nette wandelt den Wert entsprechend automatisch um. Die Parameterwerte können auch [validiert werden |#Validierung von Parametern]. +Für die Property empfehlen wir, den Datentyp anzugeben (z. B. `string`), Nette wandelt den Wert dann automatisch entsprechend um. Die Werte der Parameter lassen sich auch [validieren |#Validierung der Parameter]. -Beim Erstellen eines Links kann der Wert der Parameter direkt festgelegt werden: +Beim Erstellen eines Links lässt sich der Wert des Parameters direkt setzen: ```latte klicken @@ -264,11 +289,11 @@ Beim Erstellen eines Links kann der Wert der Parameter direkt festgelegt werden: Persistente Parameter ===================== -Persistente Parameter dienen dazu, den Zustand zwischen verschiedenen Anfragen aufrechtzuerhalten. Ihr Wert bleibt auch nach dem Klicken auf einen Link gleich. Im Gegensatz zu Daten in der Session werden sie in der URL übertragen. Und das völlig automatisch, es ist also nicht notwendig, sie explizit in `link()` oder `n:href` anzugeben. +Persistente Parameter dienen dazu, den Zustand über verschiedene Requests hinweg zu erhalten. Ihr Wert bleibt auch nach dem Klick auf einen Link derselbe. Anders als Daten in der Session werden sie in der URL übertragen. Und das geschieht vollautomatisch, sie müssen also nicht ausdrücklich in `link()` oder `n:href` angegeben werden. -Ein Anwendungsbeispiel? Sie haben eine mehrsprachige Anwendung. Die aktuelle Sprache ist ein Parameter, der ständig Teil der URL sein muss. Aber es wäre unglaublich mühsam, ihn in jedem Link anzugeben. Also machen Sie daraus einen persistenten Parameter `lang`, und er wird von selbst übertragen. Großartig! +Ein Anwendungsbeispiel? Stellen Sie sich vor, Sie haben eine mehrsprachige Anwendung. Die aktuelle Sprache ist ein Parameter, der immer Teil der URL sein muss. Aber ihn in jedem Link anzugeben wäre unglaublich mühsam. Also machen Sie daraus den persistenten Parameter `lang`, und er wird automatisch mitgeführt. Praktisch! -Das Erstellen eines persistenten Parameters ist in Nette extrem einfach. Erstellen Sie einfach eine öffentliche Property und markieren Sie sie mit einem Attribut: (früher wurde `/** @persistent */` verwendet) +Einen persistenten Parameter in Nette zu erstellen ist ausgesprochen einfach. Legen Sie einfach eine public Property an und kennzeichnen Sie sie mit dem Attribut: (früher wurde `/** @persistent */` verwendet) ```php use Nette\Application\Attributes\Persistent; // diese Zeile ist wichtig @@ -280,14 +305,14 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Wenn `$this->lang` beispielsweise den Wert `'en'` hat, dann werden auch Links, die mit `link()` oder `n:href` erstellt wurden, den Parameter `lang=en` enthalten. Und nach dem Klicken auf den Link wird wieder `$this->lang = 'en'` sein. +Hat `$this->lang` zum Beispiel den Wert `'en'`, enthalten auch die mit `link()` oder `n:href` erstellten Links den Parameter `lang=en`. Und nach dem Klick auf den Link ist `$this->lang` wieder `'en'`. -Bei der Property empfehlen wir, auch den Datentyp anzugeben (z.B. `string`), und Sie können auch einen Standardwert angeben. Die Parameterwerte können [validiert werden |#Validierung von Parametern]. +Für die Property empfehlen wir, den Datentyp anzugeben (z. B. `string`), und Sie können auch einen Standardwert angeben. Die Werte der Parameter lassen sich [validieren |#Validierung der Parameter]. -Persistente Parameter werden standardmäßig zwischen allen Aktionen des jeweiligen Presenters übertragen. Damit sie auch zwischen mehreren Presentern übertragen werden, müssen sie entweder definiert werden: +Persistente Parameter werden üblicherweise zwischen allen Aktionen eines bestimmten Presenters übertragen. Damit sie auch über mehrere Presenter hinweg übertragen werden, müssen sie definiert werden entweder: - in einem gemeinsamen Vorfahren, von dem die Presenter erben -- in einem Trait, den die Presenter verwenden: +- oder in einem Trait, den die Presenter verwenden: ```php trait LanguageAware @@ -302,42 +327,62 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Beim Erstellen eines Links kann der Wert eines persistenten Parameters geändert werden: +Beim Erstellen eines Links lässt sich der Wert eines persistenten Parameters ändern: ```latte Detail auf Tschechisch ``` -Oder er kann *zurückgesetzt* werden, d.h. aus der URL entfernt werden. Dann nimmt er seinen Standardwert an: +Oder er lässt sich *zurücksetzen*, also aus der URL entfernen. Er nimmt dann seinen Standardwert an: ```latte klicken ``` +Gemeinsamer Parameterraum +========================= + +Die Parameter des Requests, die [persistenten Parameter |#Persistente Parameter] und die Parameter der Methoden `action`, `render` und `handle` (Signal) teilen sich einen einzigen Raum, in dem jeder über seinen Namen identifiziert wird. Erscheint derselbe Name in mehreren von ihnen, bezeichnen sie ein und denselben Wert. + +Das nutzt man oft zum Vorteil. Zum Beispiel sind der persistente Parameter `lang` und das Argument `$lang` einer Action- oder Signal-Methode ein und dasselbe - Sie können den aktuellen Wert eines persistenten Parameters lesen, indem Sie ihn einfach in der Signatur der Methode aufführen: + +```php +#[Persistent] +public string $lang; + +public function handleSearch(string $query, string $lang): void +{ + // $lang enthält den aktuellen Wert des persistenten Parameters lang +} +``` + +Weil dieser Raum gemeinsam ist, halten Sie die Namen der Parameter eindeutig, sofern sie sich nicht absichtlich einen Wert teilen sollen. Das gilt auch für Signale, die zusätzlich Parameter aus dem POST-Body des Requests lesen, siehe [Signale im Detail |components#Signale im Detail]. + + Interaktive Komponenten ======================= -Presenter haben ein eingebautes Komponentensystem. Komponenten sind separate, wiederverwendbare Einheiten, die wir in Presenter einfügen. Dies können [Formulare |forms:in-presenter], Datagrids, Menüs sein, eigentlich alles, was sinnvoll wiederverwendet werden kann. +Presenter haben ein eingebautes Komponentensystem. Komponenten sind eigenständige, wiederverwendbare Einheiten, die wir in Presenter einbetten. Das können [Formulare |forms:in-presenter] sein, Datagrids, Menüs - kurz alles, was sich sinnvoll wiederholt verwenden lässt. -Wie werden Komponenten in den Presenter eingefügt und anschließend verwendet? Das erfahren Sie im Kapitel [Komponenten |components]. Sie werden sogar herausfinden, was sie mit Hollywood gemeinsam haben. +Wie werden Komponenten in Presenter eingebettet und anschließend verwendet? Das erfahren Sie im Kapitel [Komponenten |components]. Sie erfahren dort sogar, was sie mit Hollywood gemeinsam haben. -Und wo kann ich Komponenten bekommen? Auf der Seite [Componette |https://componette.org/search/component] finden Sie Open-Source-Komponenten sowie eine Reihe weiterer Add-ons für Nette, die von Freiwilligen aus der Community rund um das Framework hier platziert wurden. +Und woher bekomme ich Komponenten? Auf [Componette |https://componette.org/search/component] finden Sie Open-Source-Komponenten und viele weitere Erweiterungen für Nette, beigesteuert von Freiwilligen aus der Community des Frameworks. -Wir gehen in die Tiefe -====================== +Tiefer eintauchen +================= .[tip] -Mit dem, was wir bisher in diesem Kapitel gezeigt haben, werden Sie wahrscheinlich vollkommen auskommen. Die folgenden Zeilen sind für diejenigen gedacht, die sich eingehender für Presenter interessieren und alles wissen möchten. +Was wir in diesem Kapitel bisher behandelt haben, dürfte für die meisten Anwendungsfälle genügen. Die folgenden Abschnitte sind für alle gedacht, die sich intensiver mit Presentern beschäftigen und absolut alles wissen wollen. -Validierung von Parametern --------------------------- +Validierung der Parameter +------------------------- -Die Werte der [#Anfrageparameter] und [persistenten Parameter |#Persistente Parameter], die aus der URL empfangen werden, schreibt die Methode `loadState()` in die Properties. Sie überprüft auch, ob der bei der Property angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt. +Die aus der URL empfangenen Werte der [#Parameter des Requests] und [#Persistente Parameter] werden von der Methode `loadState()` in die Properties geschrieben. Sie prüft außerdem, ob der in der Property angegebene Datentyp passt; andernfalls antwortet sie mit dem Fehler 404 und die Seite wird nicht angezeigt. -Vertrauen Sie niemals blind Parametern, da sie vom Benutzer leicht in der URL überschrieben werden können. So überprüfen wir beispielsweise, ob die Sprache `$this->lang` zu den unterstützten gehört. Ein geeigneter Weg ist, die erwähnte Methode `loadState()` zu überschreiben: +Vertrauen Sie den aus der URL empfangenen Parametern niemals blind, denn sie lassen sich vom Benutzer leicht überschreiben. So würden wir zum Beispiel prüfen, ob die Sprache `$this->lang` zu den unterstützten gehört. Ein geeigneter Weg dafür ist, die erwähnte Methode `loadState()` zu überschreiben: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -348,7 +393,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { parent::loadState($params); // hier wird $this->lang gesetzt - // es folgt die eigene Überprüfung des Wertes: + // es folgt die eigene Wertprüfung: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -357,65 +402,47 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Speichern und Wiederherstellen der Anfrage ------------------------------------------- +Request speichern und wiederherstellen +-------------------------------------- -Die Anfrage, die der Presenter bearbeitet, ist ein Objekt [api:Nette\Application\Request] und wird von der Presenter-Methode `getRequest()` zurückgegeben. +Der vom Presenter bearbeitete Request ist ein Objekt [api:Nette\Application\Request], das die Methode `getRequest()` des Presenters zurückgibt. -Die aktuelle Anfrage kann in der Session gespeichert oder daraus wiederhergestellt und vom Presenter erneut ausgeführt werden. Dies ist nützlich, zum Beispiel in einer Situation, in der ein Benutzer ein Formular ausfüllt und seine Anmeldung abläuft. Um die Daten nicht zu verlieren, speichern wir vor der Weiterleitung zur Anmeldeseite die aktuelle Anfrage mit `$reqId = $this->storeRequest()` in der Session. Diese Methode gibt ihren Identifikator in Form einer kurzen Zeichenkette zurück, den wir als Parameter an den Anmelde-Presenter übergeben. +Der aktuelle Request lässt sich in der Session speichern oder umgekehrt aus ihr wiederherstellen und vom Presenter erneut ausführen. Das ist zum Beispiel nützlich, wenn ein Benutzer ein Formular ausfüllt und seine Anmeldung abläuft. Damit keine Daten verloren gehen, speichern wir den aktuellen Request vor der Weiterleitung auf die Anmeldeseite mit `$reqId = $this->storeRequest()` in der Session. Das gibt seinen Bezeichner als kurzen String zurück, den wir dann als Parameter an den Anmelde-Presenter übergeben. -Nach der Anmeldung rufen wir die Methode `$this->restoreRequest($reqId)` auf, die die Anfrage aus der Session holt und dorthin weiterleitet (forward). Die Methode überprüft dabei, ob die Anfrage vom selben Benutzer erstellt wurde, der sich jetzt angemeldet hat. Wenn sich ein anderer Benutzer angemeldet hat oder der Schlüssel ungültig ist, tut sie nichts und das Programm läuft weiter. +Nach der Anmeldung rufen wir die Methode `$this->restoreRequest($reqId)` auf, die den Request aus der Session holt. POST-Requests werden an ihn weitergeleitet, die übrigen (GET) werden auf die URL des Requests weitergeleitet. Die Methode prüft, ob der Request von demselben Benutzer erstellt wurde, der nun angemeldet ist. Meldet sich ein anderer Benutzer an oder ist der Schlüssel ungültig, tut sie nichts und das Programm läuft wie gewohnt weiter. -Schauen Sie sich die Anleitung [Wie man zu einer früheren Seite zurückkehrt |best-practices:restore-request] an. +Siehe die Anleitung [Wie man zu einer früheren Seite zurückkehrt |best-practices:restore-request]. Kanonisierung ------------- -Presenter haben eine wirklich großartige Eigenschaft, die zu besserem SEO (Optimierung der Auffindbarkeit im Internet) beiträgt. Sie verhindern automatisch das Vorhandensein von doppeltem Inhalt unter verschiedenen URLs. Wenn zu einem bestimmten Ziel mehrere URL-Adressen führen, z.B. `/index` und `/index?page=1`, bestimmt das Framework eine davon als primäre (kanonische) und leitet die anderen mit dem HTTP-Code 301 dorthin weiter. Dank dessen indizieren Suchmaschinen die Seiten nicht zweimal und verwässern nicht deren Page Rank. +Presenter haben eine wirklich ausgezeichnete Eigenschaft, die zu besserem SEO (Search Engine Optimization) beiträgt. Sie verhindern automatisch, dass derselbe Inhalt unter verschiedenen URLs existiert. Führen mehrere URLs zu einem bestimmten Ziel, z. B. `/index` und `/index?page=1`, bestimmt das Framework eine davon als primär (kanonisch) und leitet die übrigen mit dem HTTP-Code 301 dorthin weiter. Dadurch indexieren Suchmaschinen Ihre Seiten nicht doppelt und verwässern deren Page Rank nicht. -Dieser Prozess wird Kanonisierung genannt. Die kanonische URL ist diejenige, die vom [Router|routing] generiert wird, in der Regel also die erste passende Route in der Sammlung. +Dieser Vorgang heißt Kanonisierung. Die kanonische URL ist diejenige, die der [Router|routing] erzeugt, typischerweise die erste passende Route in der Collection. -Die Kanonisierung ist standardmäßig aktiviert und kann über `$this->autoCanonicalize = false` deaktiviert werden. +Die Kanonisierung ist standardmäßig eingeschaltet und lässt sich mit `$this->autoCanonicalize = false` abschalten. -Bei einer AJAX- oder POST-Anfrage erfolgt keine Weiterleitung, da dies zu Datenverlust führen würde oder aus SEO-Sicht keinen Mehrwert hätte. +Bei AJAX- oder POST-Requests findet keine Weiterleitung statt, da dies zu Datenverlust führen könnte oder keinen zusätzlichen SEO-Nutzen brächte. -Sie können die Kanonisierung auch manuell mit der Methode `canonicalize()` auslösen, der ähnlich wie der Methode `link()` der Presenter, die Aktion und die Parameter übergeben werden. Sie erstellt einen Link und vergleicht ihn mit der aktuellen URL-Adresse. Wenn sie sich unterscheiden, wird auf den generierten Link weitergeleitet. +Die Kanonisierung lässt sich auch manuell mit der Methode `canonicalize()` auslösen. Ähnlich wie der Methode `link()` übergeben Sie ihr den Presenter, die Aktion und die Parameter. Sie erzeugt einen Link und vergleicht ihn mit der aktuellen URL-Adresse. Unterscheiden sie sich, leitet sie auf den erzeugten Link weiter. ```php public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // leitet weiter, wenn $slug sich von $realSlug unterscheidet + // leitet weiter, wenn $slug von $realSlug abweicht $this->canonicalize('Product:show', [$id, $realSlug]); } ``` - -Ereignisse ----------- - -Neben den Methoden `startup()`, `beforeRender()` und `shutdown()`, die als Teil des Lebenszyklus des Presenters aufgerufen werden, können noch weitere Funktionen definiert werden, die automatisch aufgerufen werden sollen. Der Presenter definiert sogenannte [Ereignisse |nette:glossary#Events Ereignisse], deren Handler Sie zu den Arrays `$onStartup`, `$onRender` und `$onShutdown` hinzufügen. - -```php -class ArticlePresenter extends Nette\Application\UI\Presenter -{ - public function __construct() - { - $this->onStartup[] = function () { - // ... - }; - } -} -``` - -Die Handler im Array `$onStartup` werden kurz vor der Methode `startup()` aufgerufen, `$onRender` zwischen `beforeRender()` und `render()` und schließlich `$onShutdown` kurz vor `shutdown()`. +Ein vollständiges Muster, das Route-Filter mit `canonicalize()` kombiniert, um SEO-freundliche URLs zu erzeugen, finden Sie unter [Schöne URLs mit Slugs |best-practices:pretty-urls]. Antworten --------- -Die Antwort, die ein Presenter zurückgibt, ist ein Objekt, das das Interface [api:Nette\Application\Response] implementiert. Es stehen eine Reihe von vorbereiteten Antworten zur Verfügung: +Die vom Presenter zurückgegebene Antwort ist ein Objekt, das das Interface [api:Nette\Application\Response] implementiert. Es stehen mehrere fertige Antworten zur Verfügung: - [api:Nette\Application\Responses\CallbackResponse] - sendet einen Callback - [api:Nette\Application\Responses\FileResponse] - sendet eine Datei @@ -430,43 +457,103 @@ Antworten werden mit der Methode `sendResponse()` gesendet: ```php use Nette\Application\Responses; -// Einfacher Text +// Reiner Text $this->sendResponse(new Responses\TextResponse('Hello Nette!')); // Sendet eine Datei $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Die Antwort wird ein Callback sein +// Sendet einen Callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { - echo '

    Hello

    '; + echo '

    Hallo

    '; } }; $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +Sie können auch eine eigene Antwort schreiben. Implementieren Sie einfach das Interface `Nette\Application\Response`, das eine einzige Methode `send()` hat, die den HTTP-Request und die HTTP-Response entgegennimmt. Das ist zum Beispiel nützlich, wenn Sie Daten streamen, die Sie nicht im Speicher halten wollen: -Zugriffsbeschränkung mit `#[Requires]` .{data-version:3.2.2} +```php +class CsvResponse implements Nette\Application\Response +{ + public function __construct( + private string $fileName, + private iterable $rows, + ) { + } + + public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void + { + $response->setContentType('text/csv', 'utf-8'); + $response->sendAsFile($this->fileName); + + $handle = fopen('php://output', 'w'); + foreach ($this->rows as $row) { + fputcsv($handle, $row); + } + + fclose($handle); + } +} +``` + +Im Presenter senden Sie sie dann wie gewohnt: `$this->sendResponse(new CsvResponse('export.csv', $rows));` + + +HTTP-Caching +------------ + +Die Methode `lastModified()` macht es leicht, HTTP-Caching zu nutzen. Sie übergeben ihr Datum und Uhrzeit der letzten Änderung des Inhalts (als Timestamp, String oder Objekt `DateTimeInterface`) und optional einen ETag-Validator (einen kurzen String, der die aktuelle Version des Inhalts identifiziert, etwa deren Hash) sowie eine Ablaufzeit. Hat der Browser bereits eine passende Version, sendet der Presenter die Antwort `304 Not Modified` und endet, sodass die Seite nicht unnötig gerendert und übertragen wird: + +```php +public function renderArticle(int $id): void +{ + $article = $this->articles->getById($id); + $this->lastModified($article->updatedAt); + // ... +} +``` + + +Fertigstellung des Templates .{data-version:3.3.0} +-------------------------------------------------- + +Wenn der Presenter ein Template rendert, ruft die Methode `sendTemplate()` unmittelbar vor dem Rendern `completeTemplate()` auf. Diese Methode füllt die mit dem Attribut `#[TemplateVariable]` gekennzeichneten Variablen und ermittelt die Template-Datei (die Standardvariablen setzt bereits die `TemplateFactory` beim Erzeugen des Templates). Sie können diese protected-Methode überschreiben, um Variablen zu ergänzen, die alle Views gemeinsam haben, oder um eine andere Datei zu setzen: + +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'Meine App'; +} +``` + + +Zugriffsbeschränkung mit `#[Requires]` .{data-version:3.2.3} ------------------------------------------------------------ -Das Attribut `#[Requires]` bietet erweiterte Möglichkeiten zur Zugriffsbeschränkung für Presenter und deren Methoden. Es kann verwendet werden, um HTTP-Methoden zu spezifizieren, eine AJAX-Anfrage zu erfordern, den Zugriff auf den gleichen Ursprung (Same Origin) zu beschränken und den Zugriff nur über Forwarding zu erlauben. Das Attribut kann sowohl auf Presenter-Klassen als auch auf einzelne Methoden `action()`, `render()`, `handle()` und `createComponent()` angewendet werden. +Das Attribut `#[Requires]` bietet fortgeschrittene Möglichkeiten, den Zugriff auf Presenter und deren Methoden einzuschränken. Damit lassen sich HTTP-Methoden festlegen, ein AJAX-Request verlangen, der Zugriff auf denselben Origin beschränken und der Zugriff nur über Forwarding erlauben. Das Attribut lässt sich sowohl auf Presenter-Klassen als auch auf einzelne Methoden wie `action()`, `render()`, `handle()` und `createComponent()` anwenden. -Sie können folgende Beschränkungen festlegen: +Sie können diese Einschränkungen angeben: - auf HTTP-Methoden: `#[Requires(methods: ['GET', 'POST'])]` -- Erfordern einer AJAX-Anfrage: `#[Requires(ajax: true)]` -- Zugriff nur vom gleichen Ursprung: `#[Requires(sameOrigin: true)]` +- Erfordernis eines AJAX-Requests: `#[Requires(ajax: true)]` +- Zugriff nur vom selben Origin: `#[Requires(sameOrigin: true)]` - Zugriff nur über Forwarding: `#[Requires(forward: true)]` -- Beschränkung auf bestimmte Aktionen: `#[Requires(actions: 'default')]` +- Einschränkung auf bestimmte Aktionen: `#[Requires(actions: 'default')]` -Details finden Sie in der Anleitung [Verwendung des Requires-Attributs |best-practices:attribute-requires]. +.[note] +Seit Version 3.3 wird die Übereinstimmung des Origins über den Browser-Header `Sec-Fetch-Site` geprüft (früher über ein SameSite-Cookie), was zuverlässiger ist und die exakte Übereinstimmung von Schema, Domain und Port prüft. +Details finden Sie in der Anleitung [Wie man das Attribut Requires verwendet |best-practices:attribute-requires]. -Überprüfung der HTTP-Methode ----------------------------- -Presenter in Nette überprüfen automatisch die HTTP-Methode jeder eingehenden Anfrage. Der Grund für diese Überprüfung ist hauptsächlich die Sicherheit. Standardmäßig sind die Methoden `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` erlaubt. +Prüfung der HTTP-Methode +------------------------ -Wenn Sie zusätzlich beispielsweise die Methode `OPTIONS` erlauben möchten, verwenden Sie dazu das Attribut `#[Requires]` (ab Nette Application v3.2): +Presenter in Nette prüfen bei jedem eingehenden Request automatisch die HTTP-Methode, vor allem aus Sicherheitsgründen. Standardmäßig sind die Methoden `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` erlaubt. + +Wollen Sie zusätzlich zum Beispiel die Methode `OPTIONS` erlauben, verwenden Sie das Attribut `#[Requires]` (seit Nette Application v3.2.3): ```php #[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] @@ -475,26 +562,34 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -In Version 3.1 erfolgt die Überprüfung in `checkHttpMethod()`, die prüft, ob die in der Anfrage angegebene Methode im Array `$presenter->allowedMethods` enthalten ist. Fügen Sie die Methode wie folgt hinzu: +Seit Version 3.1.13 erfolgt die Prüfung in `checkHttpMethod()`, die prüft, ob die im Request angegebene Methode im Array `$presenter->allowedMethods` enthalten ist. Seit Version 3.2.3 ist dieser Ansatz zugunsten von `#[Requires]` veraltet. Die Methode können Sie so überschreiben: ```php class MyPresenter extends Nette\Application\UI\Presenter { - protected function checkHttpMethod(): void - { - $this->allowedMethods[] = 'OPTIONS'; - parent::checkHttpMethod(); - } + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } } ``` -Es ist wichtig zu betonen, dass wenn Sie die Methode `OPTIONS` zulassen, Sie diese anschließend auch entsprechend in Ihrem Presenter behandeln müssen. Die Methode wird oft als sogenannter Preflight Request verwendet, den der Browser automatisch vor der eigentlichen Anfrage sendet, wenn überprüft werden muss, ob die Anfrage gemäß der CORS (Cross-Origin Resource Sharing) Richtlinie zulässig ist. Wenn Sie die Methode zulassen, aber keine korrekte Antwort implementieren, kann dies zu Inkonsistenzen und potenziellen Sicherheitsproblemen führen. +Wichtig ist zu betonen: Wenn Sie die Methode `OPTIONS` aktivieren, müssen Sie sie anschließend in Ihrem Presenter auch angemessen behandeln. Diese Methode wird oft als sogenannter Preflight-Request verwendet, den der Browser automatisch vor dem eigentlichen Request sendet, wenn festgestellt werden muss, ob der Request nach der CORS-Richtlinie (Cross-Origin Resource Sharing) zulässig ist. Aktivieren Sie die Methode, ohne die richtige Antwort zu implementieren, kann das zu Inkonsistenzen und potenziellen Sicherheitsproblemen führen. -Weitere Lektüre -=============== +Veraltete Aktionen kennzeichnen .{data-version:3.2.3} +----------------------------------------------------- + +Das Attribut `#[Deprecated]` kennzeichnet Aktionen, Signale oder ganze Presenter als veraltet und für eine künftige Entfernung vorgesehen. Beim Erzeugen von Links auf veraltete Teile der Anwendung wirft Nette eine Warnung, um die Entwickler darauf aufmerksam zu machen. + +Das Attribut lässt sich entweder auf die gesamte Presenter-Klasse oder auf einzelne Methoden `action()`, `render()` und `handle()` anwenden. + + +Weiterführende Lektüre +====================== - [Inject-Methoden und -Attribute |best-practices:inject-method-attribute] -- [Zusammensetzen von Presentern aus Traits |best-practices:presenter-traits] -- [Übergabe von Einstellungen an Presenter |best-practices:passing-settings-to-presenters] +- [Presenter aus Traits zusammensetzen |best-practices:presenter-traits] +- [Einstellungen an Presenter übergeben |best-practices:passing-settings-to-presenters] - [Wie man zu einer früheren Seite zurückkehrt |best-practices:restore-request] diff --git a/application/de/routing.texy b/application/de/routing.texy index ff2f7378e0..f175e154aa 100644 --- a/application/de/routing.texy +++ b/application/de/routing.texy @@ -3,26 +3,26 @@ Routing
    -Der Router kümmert sich um alles rund um URL-Adressen, damit Sie nicht mehr darüber nachdenken müssen. Wir zeigen Ihnen: +Der Router kümmert sich um alles rund um URL-Adressen, damit Sie nicht mehr über sie nachdenken müssen. Wir zeigen Ihnen: -- wie man den Router einstellt, damit die URLs den Vorstellungen entsprechen +- wie man den Router einstellt, damit die URLs so aussehen, wie Sie es möchten - wir sprechen über SEO und Weiterleitungen - und zeigen Ihnen, wie Sie einen eigenen Router schreiben
    -Menschenfreundlichere URLs (oder auch coole oder pretty URLs) sind benutzbarer, leichter zu merken und tragen positiv zur SEO bei. Nette berücksichtigt dies und kommt Entwicklern voll entgegen. Sie können für Ihre Anwendung genau die Struktur der URL-Adressen entwerfen, die Sie möchten. Sie können sie sogar erst dann entwerfen, wenn die Anwendung bereits fertig ist, da dies ohne Eingriffe in den Code oder die Templates auskommt. Sie wird nämlich auf elegante Weise an einer [einzigen Stelle |#Integration in die Anwendung] definiert, im Router, und ist somit nicht in Form von Annotationen in allen Presentern verstreut. +Benutzerfreundlichere URLs (auch cool oder pretty URLs genannt) sind besser nutzbar, leichter zu merken und tragen positiv zum SEO bei. Nette denkt daran und kommt Entwicklern voll entgegen. Sie können für Ihre Anwendung genau die Struktur der URL-Adressen entwerfen, die Sie möchten. Sie können sie sogar erst dann entwerfen, wenn die Anwendung bereits fertig ist, denn es sind dafür keine Eingriffe in den Code oder die Templates nötig. Sie wird nämlich auf elegante Weise an [einer einzigen Stelle |#Integration] definiert, im Router, und ist nicht in Form von Annotationen über alle Presenter verstreut. -Der Router in Nette ist außergewöhnlich, da er **bidirektional** ist. Er kann sowohl URLs in einer HTTP-Anfrage dekodieren als auch Links erstellen. Er spielt daher eine entscheidende Rolle in [Nette Application |how-it-works#Nette Application], da er einerseits entscheidet, welcher Presenter und welche Aktion die aktuelle Anfrage ausführen wird, andererseits aber auch für die [Generierung von URLs |creating-links] im Template usw. verwendet wird. +Der Router in Nette ist außergewöhnlich, weil er **bidirektional** ist. Er kann sowohl URLs aus HTTP-Requests dekodieren als auch Links erstellen. Er spielt daher eine entscheidende Rolle in [Nette Application |how-it-works#Nette Application], denn er entscheidet nicht nur, welcher Presenter und welche Aktion den aktuellen Request ausführen, sondern wird auch zum [Erzeugen von URLs |creating-links] im Template usw. verwendet. -Der Router ist jedoch nicht nur auf diese Verwendung beschränkt, Sie können ihn auch in Anwendungen einsetzen, in denen überhaupt keine Presenter verwendet werden, für REST-APIs usw. Mehr dazu im Abschnitt [#Eigenständige Verwendung]. +Der Router ist jedoch nicht auf diese Verwendung beschränkt; Sie können ihn auch in Anwendungen einsetzen, die überhaupt keine Presenter verwenden, für REST-APIs usw. Mehr dazu im Abschnitt [#Eigenständige Verwendung]. -Routen-Sammlung -=============== +Routensammlung +============== -Die angenehmste Art, die Form der URL-Adressen in der Anwendung zu definieren, bietet die Klasse [api:Nette\Application\Routers\RouteList]. Die Definition besteht aus einer Liste sogenannter Routen, d. h. Masken von URL-Adressen und den ihnen zugeordneten Presentern und Aktionen über eine einfache API. Wir müssen die Routen nicht benennen. +Den angenehmsten Weg, die Form der URL-Adressen in der Anwendung zu definieren, bietet die Klasse [api:Nette\Application\Routers\RouteList]. Die Definition besteht aus einer Liste sogenannter Routes, also Masken von URL-Adressen und den ihnen über eine einfache API zugeordneten Presentern und Aktionen. Die Routes müssen wir in keiner Weise benennen. ```php $router = new Nette\Application\Routers\RouteList; @@ -31,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -Das Beispiel besagt, dass wenn wir im Browser `https://domain.com/rss.xml` öffnen, der Presenter `Feed` mit der Aktion `rss` angezeigt wird, wenn `https://domain.com/article/12`, wird der Presenter `Article` mit der Aktion `view` angezeigt usw. Wenn keine passende Route gefunden wird, reagiert Nette Application mit dem Auslösen einer [BadRequestException |api:Nette\Application\BadRequestException], die dem Benutzer als Fehlerseite 404 Not Found angezeigt wird. +Das Beispiel besagt: Öffnen wir im Browser `https://domain.com/rss.xml`, wird der Presenter `Feed` mit der Aktion `rss` angezeigt; bei `https://domain.com/article/12` der Presenter `Article` mit der Aktion `view` usw. Wird keine passende Route gefunden, reagiert Nette Application mit dem Werfen einer [BadRequestException |api:Nette\Application\BadRequestException], die dem Benutzer als Fehlerseite 404 Not Found angezeigt wird. -Reihenfolge der Routen +Reihenfolge der Routes ---------------------- -Ganz **entscheidend ist die Reihenfolge**, in der die einzelnen Routen aufgeführt sind, da sie schrittweise von oben nach unten ausgewertet werden. Es gilt die Regel, dass wir Routen **von spezifisch nach allgemein** deklarieren: +Ganz **entscheidend ist die Reihenfolge**, in der die einzelnen Routes aufgeführt sind, denn sie werden der Reihe nach von oben nach unten ausgewertet. Es gilt die Regel, dass wir Routes **von speziell nach allgemein** deklarieren: ```php -// FALSCH: 'rss.xml' wird von der ersten Route abgefangen und diese Zeichenkette als verstanden +// FALSCH: 'rss.xml' wird von der ersten Route abgefangen, die diesen String als versteht $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -49,10 +49,10 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Routen werden auch bei der Generierung von Links von oben nach unten ausgewertet: +Auch beim Erzeugen von Links werden die Routes von oben nach unten ausgewertet: ```php -// FALSCH: Ein Link zu 'Feed:rss' wird als 'admin/feed/rss' generiert +// FALSCH: Ein Link auf 'Feed:rss' wird als 'admin/feed/rss' erzeugt $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -61,27 +61,27 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Wir werden Ihnen nicht verheimlichen, dass die korrekte Zusammenstellung der Routen eine gewisse Fertigkeit erfordert. Bevor Sie diese beherrschen, wird Ihnen das [Routing-Panel |#Debugging des Routers] ein nützlicher Helfer sein. +Wir werden Ihnen nicht verheimlichen, dass das korrekte Zusammenstellen der Routes eine gewisse Fertigkeit erfordert. Bis Sie diese beherrschen, wird Ihnen das [Routing-Panel |#Debugging des Routers] ein nützlicher Helfer sein. Maske und Parameter ------------------- -Die Maske beschreibt den relativen Pfad vom Stammverzeichnis der Website. Die einfachste Maske ist eine statische URL: +Die Maske beschreibt den relativen Pfad vom Wurzelverzeichnis der Website. Die einfachste Maske ist eine statische URL: ```php $router->addRoute('products', 'Products:default'); ``` -Oft enthalten Masken sogenannte **Parameter**. Diese werden in spitzen Klammern angegeben (z. B. ``) und an den Ziel-Presenter übergeben, beispielsweise an die Methode `renderShow(int $year)` oder an den persistenten Parameter `$year`: +Häufig enthalten Masken sogenannte **Parameter**. Diese werden in spitzen Klammern angegeben (z. B. ``) und an den Ziel-Presenter übergeben, zum Beispiel an die Methode `renderShow(int $year)` oder an den persistenten Parameter `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Das Beispiel besagt, dass wenn wir im Browser `https://example.com/chronicle/2020` öffnen, der Presenter `History` mit der Aktion `show` und dem Parameter `year: 2020` angezeigt wird. +Das Beispiel besagt: Öffnen wir im Browser `https://example.com/chronicle/2020`, wird der Presenter `History` mit der Aktion `show` und dem Parameter `year: 2020` angezeigt. -Wir können Parametern direkt in der Maske einen Standardwert zuweisen, wodurch sie optional werden: +Parametern können wir direkt in der Maske einen Standardwert zuweisen, wodurch sie optional werden: ```php $router->addRoute('chronicle/', 'History:show'); @@ -95,14 +95,14 @@ Parameter können natürlich auch der Name des Presenters und der Aktion sein. Z $router->addRoute('/', 'Home:default'); ``` -Die angegebene Route akzeptiert z. B. URLs in der Form `/article/edit` oder auch `/catalog/list` und versteht sie als Presenter und Aktionen `Article:edit` und `Catalog:list`. +Die angegebene Route akzeptiert z. B. URLs in der Form `/article/edit` oder auch `/catalog/list` und versteht sie als die Presenter und Aktionen `Article:edit` bzw. `Catalog:list`. -Gleichzeitig gibt sie den Parametern `presenter` und `action` die Standardwerte `Home` und `default`, wodurch sie ebenfalls optional sind. Die Route akzeptiert also auch eine URL in der Form `/article` und versteht sie als `Article:default`. Oder umgekehrt, ein Link zu `Product:default` generiert den Pfad `/product`, ein Link zum Standard `Home:default` den Pfad `/`. +Zugleich gibt sie den Parametern `presenter` und `action` die Standardwerte `Home` und `default`, wodurch auch sie optional sind. Die Route akzeptiert also auch eine URL in der Form `/article` und versteht sie als `Article:default`. Oder umgekehrt: Ein Link auf `Product:default` erzeugt den Pfad `/product`, ein Link auf den Standard `Home:default` den Pfad `/`. -Die Maske kann nicht nur den relativen Pfad vom Stammverzeichnis der Website beschreiben, sondern auch einen absoluten Pfad, wenn sie mit einem Schrägstrich beginnt, oder sogar eine gesamte absolute URL, wenn sie mit zwei Schrägstrichen beginnt: +Die Maske kann nicht nur den relativen Pfad vom Wurzelverzeichnis der Website beschreiben, sondern auch einen absoluten Pfad, wenn sie mit einem Schrägstrich beginnt, oder sogar eine ganze absolute URL, wenn sie mit zwei Schrägstrichen beginnt: ```php -// relativ zum Document Root +// relativ zum Document-Root $router->addRoute('/', /* ... */); // absoluter Pfad (relativ zur Domain) @@ -119,16 +119,16 @@ $router->addRoute('https://.example.com//', /* ... */); Validierungsausdrücke --------------------- -Für jeden Parameter kann eine Validierungsbedingung mithilfe eines [Regulären Ausdrucks|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php] festgelegt werden. Zum Beispiel bestimmen wir für den Parameter `id`, dass er nur Ziffern annehmen darf, mit dem Regulären Ausdruck `\d+`: +Für jeden Parameter lässt sich eine Validierungsbedingung mit einem [regulären Ausdruck|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php] angeben. Für den Parameter `id` legen wir zum Beispiel mit dem Regex `\d+` fest, dass er nur Ziffern enthalten darf: ```php $router->addRoute('/[/]', /* ... */); ``` -Der standardmäßige reguläre Ausdruck für alle Parameter ist `[^/]+`, d. h. alles außer einem Schrägstrich. Wenn ein Parameter auch Schrägstriche akzeptieren soll, geben wir den Ausdruck `.+` an: +Der Standard-Regex für alle Parameter ist `[^/]+`, also alles außer einem Schrägstrich. Soll ein Parameter auch Schrägstriche akzeptieren, setzen wir den Ausdruck auf `.+`: ```php -// akzeptiert https://example.com/a/b/c, path wird 'a/b/c' +// akzeptiert https://example.com/a/b/c, path ist dann 'a/b/c' $router->addRoute('', /* ... */); ``` @@ -136,17 +136,17 @@ $router->addRoute('', /* ... */); Optionale Sequenzen ------------------- -In der Maske können optionale Teile mithilfe von eckigen Klammern markiert werden. Jeder Teil der Maske kann optional sein, und er kann auch Parameter enthalten: +In der Maske lassen sich optionale Teile mit eckigen Klammern kennzeichnen. Jeder Teil der Maske kann optional sein und Parameter enthalten: ```php $router->addRoute('[/]', /* ... */); -// Akzeptiert Pfade: -// /cs/download => lang => cs, name => download +// Akzeptiert die Pfade: +// /en/download => lang => en, name => download // /download => lang => null, name => download ``` -Wenn ein Parameter Teil einer optionalen Sequenz ist, wird er natürlich auch optional. Wenn kein Standardwert angegeben ist, wird er null sein. +Ist ein Parameter Teil einer optionalen Sequenz, wird natürlich auch er optional. Hat er keinen angegebenen Standardwert, ist er null. Optionale Teile können auch in der Domain vorkommen: @@ -154,7 +154,7 @@ Optionale Teile können auch in der Domain vorkommen: $router->addRoute('//[.]example.com//', /* ... */); ``` -Sequenzen können beliebig verschachtelt und kombiniert werden: +Sequenzen lassen sich beliebig verschachteln und kombinieren: ```php $router->addRoute( @@ -162,24 +162,24 @@ $router->addRoute( 'Home:default', ); -// Akzeptiert Pfade: -// /cs/hello +// Akzeptiert die Pfade: +// /en/hello // /en-us/hello // /hello // /hello/page-12 ``` -Bei der URL-Generierung wird die kürzeste Variante angestrebt, sodass alles, was weggelassen werden kann, weggelassen wird. Daher generiert beispielsweise die Route `index[.html]` den Pfad `/index`. Das Verhalten kann durch Angabe eines Ausrufezeichens nach der linken eckigen Klammer umgekehrt werden: +Beim Erzeugen von URLs wird die kürzeste Variante bevorzugt, es wird also alles weggelassen, was weggelassen werden kann. Deshalb erzeugt zum Beispiel die Route `index[.html]` den Pfad `/index`. Dieses Verhalten lässt sich umkehren, indem man hinter die linke eckige Klammer ein Ausrufezeichen setzt: ```php -// akzeptiert /hello und /hello.html, generiert /hello +// akzeptiert /hello und /hello.html, erzeugt /hello $router->addRoute('[.html]', /* ... */); -// akzeptiert /hello und /hello.html, generiert /hello.html +// akzeptiert /hello und /hello.html, erzeugt /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Optionale Parameter (d. h. Parameter mit einem Standardwert) ohne eckige Klammern verhalten sich im Grunde so, als wären sie wie folgt geklammert: +Optionale Parameter (also Parameter mit einem Standardwert) ohne eckige Klammern verhalten sich im Grunde so, als wären sie folgendermaßen eingeklammert: ```php $router->addRoute('//', /* ... */); @@ -188,34 +188,34 @@ $router->addRoute('//', /* ... */); $router->addRoute('[/[/[]]]', /* ... */); ``` -Wenn wir das Verhalten des abschließenden Schrägstrichs beeinflussen möchten, damit z. B. anstelle von `/home/` nur `/home` generiert wird, kann dies wie folgt erreicht werden: +Wollen wir das Verhalten des abschließenden Schrägstrichs beeinflussen, damit zum Beispiel `/home` statt `/home/` erzeugt wird, erreichen wir das so: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Platzhalter ------------ +Wildcards +--------- -In der Maske des absoluten Pfads können wir folgende Platzhalter verwenden, um beispielsweise die Notwendigkeit zu vermeiden, die Domain in die Maske zu schreiben, die sich in der Entwicklungs- und Produktionsumgebung unterscheiden kann: +In der Maske einer absoluten URL können wir die folgenden Wildcards verwenden, um zum Beispiel die Domain nicht in die Maske schreiben zu müssen, die sich zwischen Entwicklungs- und Produktionsumgebung unterscheiden kann: - `%tld%` = Top-Level-Domain, z. B. `com` oder `org` - `%sld%` = Second-Level-Domain, z. B. `example` - `%domain%` = Domain ohne Subdomains, z. B. `example.com` -- `%host%` = Gesamter Host, z. B. `www.example.com` -- `%basePath%` = Pfad zum Stammverzeichnis +- `%host%` = gesamter Host, z. B. `www.example.com` +- `%basePath%` = Pfad zum Wurzelverzeichnis ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); -$router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('//www.%sld%.%tld%/%basePath%//', /* ... */); ``` -Erweiterte Notation -------------------- +Erweiterte Schreibweise +----------------------- -Das Ziel der Route, normalerweise in der Form `Presenter:action` geschrieben, kann auch mithilfe eines Arrays notiert werden, das die einzelnen Parameter und ihre Standardwerte definiert: +Das Ziel der Route, üblicherweise im Format `Presenter:action` geschrieben, lässt sich auch mit einem Array angeben, das die einzelnen Parameter und ihre Standardwerte definiert: ```php $router->addRoute('/[/]', [ @@ -224,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Für eine detailliertere Spezifikation kann eine noch erweiterte Form verwendet werden, in der wir neben den Standardwerten auch andere Eigenschaften der Parameter einstellen können, wie z. B. den Validierungs-regulären Ausdruck (siehe Parameter `id`): +Für eine genauere Angabe lässt sich eine noch ausführlichere Form verwenden, in der wir neben Standardwerten auch weitere Eigenschaften der Parameter setzen können, etwa einen regulären Ausdruck zur Validierung (siehe den Parameter `id`): ```php use Nette\Routing\Route; @@ -242,19 +242,29 @@ $router->addRoute('/[/]', [ ]); ``` -Es ist wichtig zu beachten, dass wenn die im Array definierten Parameter nicht in der Pfadmaske aufgeführt sind, ihre Werte nicht geändert werden können, auch nicht durch Query-Parameter, die nach dem Fragezeichen in der URL aufgeführt sind. +Wichtig ist: Sind im Array definierte Parameter nicht in der Pfadmaske aufgeführt, lassen sich ihre Werte nicht ändern, auch nicht über Query-Parameter, die in der URL hinter dem Fragezeichen angegeben werden. + +Das ist nützlich für **feste Parameter** - um einer bestimmten Seite eine kurze, einprägsame URL zu geben. Damit zum Beispiel `/tos` immer `Article:view` mit `id: 123` öffnet: + +```php +$router->addRoute('tos', [ + 'presenter' => 'Article', + 'action' => 'view', + 'id' => 123, +]); +``` Filter und Übersetzungen ------------------------ -Wir schreiben den Quellcode der Anwendung auf Englisch, aber wenn die Website deutsche URLs haben soll, dann wird einfaches Routing vom Typ: +Den Quellcode der Anwendung schreiben wir auf Englisch, wenn die Website aber tschechische URLs haben soll, dann erzeugt ein einfaches Routing wie: ```php $router->addRoute('/', 'Home:default'); ``` -englische URLs generieren, wie z. B. `/product/123` oder `/cart`. Wenn wir Presenter und Aktionen in der URL durch deutsche Wörter repräsentieren lassen wollen (z. B. `/produkt/123` oder `/warenkorb`), können wir ein Übersetzungswörterbuch verwenden. Für dessen Notation benötigen wir bereits die "gesprächigere" Variante des zweiten Parameters: +englische URLs, etwa `/product/123` oder `/cart`. Wollen wir, dass Presenter und Aktionen in der URL durch tschechische Wörter dargestellt werden (z. B. `/produkt/123` oder `/kosik`), können wir ein Übersetzungswörterbuch verwenden. Um es zu schreiben, brauchen wir bereits die "gesprächigere" Variante des zweiten Parameters: ```php use Nette\Routing\Route; @@ -263,26 +273,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // Zeichenkette in der URL => Presenter + // String in der URL => Presenter 'produkt' => 'Product', - 'warenkorb' => 'Cart', + 'kosik' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'seznam' => 'list', ], ], ]); ``` -Mehrere Schlüssel des Übersetzungswörterbuchs können auf denselben Presenter verweisen. Dadurch werden verschiedene Aliase dafür erstellt. Als kanonische Variante (also diejenige, die in der generierten URL enthalten sein wird) gilt der letzte Schlüssel. +Mehrere Schlüssel im Übersetzungswörterbuch können auf denselben Presenter führen. So entstehen für ihn verschiedene Aliase. Der letzte Schlüssel gilt als die kanonische Variante (also die, die in der erzeugten URL steht). -Die Übersetzungstabelle kann auf diese Weise für jeden Parameter verwendet werden. Wobei, wenn die Übersetzung nicht existiert, der ursprüngliche Wert genommen wird. Dieses Verhalten können wir durch Hinzufügen von `Route::FilterStrict => true` ändern, und die Route lehnt dann die URL ab, wenn der Wert nicht im Wörterbuch enthalten ist. +Die Übersetzungstabelle lässt sich auf diese Weise für jeden Parameter verwenden. Existiert keine Übersetzung, wird der ursprüngliche Wert genommen. Dieses Verhalten können wir mit `Route::FilterStrict => true` ändern, die Route weist die URL dann zurück, wenn der Wert nicht im Wörterbuch steht. -Neben dem Übersetzungswörterbuch in Form eines Arrays können auch eigene Übersetzungsfunktionen eingesetzt werden. +Neben dem Übersetzungswörterbuch in Form eines Arrays lassen sich eigene Übersetzungsfunktionen einsetzen. ```php use Nette\Routing\Route; @@ -298,15 +308,15 @@ $router->addRoute('//', [ ]); ``` -Die Funktion `Route::FilterIn` konvertiert zwischen dem Parameter in der URL und der Zeichenkette, die dann an den Presenter übergeben wird, die Funktion `FilterOut` stellt die Konvertierung in die entgegengesetzte Richtung sicher. +Die Funktion `Route::FilterIn` wandelt zwischen dem Parameter in der URL und dem String um, der dann an den Presenter übergeben wird; die Funktion `FilterOut` sorgt für die Umwandlung in die Gegenrichtung. -Die Parameter `presenter`, `action` und `module` haben bereits vordefinierte Filter, die zwischen dem PascalCase- bzw. camelCase-Stil und dem in URLs verwendeten kebab-case konvertieren. Der Standardwert der Parameter wird bereits in transformierter Form geschrieben, sodass wir beispielsweise im Fall des Presenters `` schreiben, nicht ``. +Die Parameter `presenter`, `action` und `module` haben bereits vordefinierte Filter, die zwischen dem PascalCase- bzw. camelCase-Stil und dem in URLs verwendeten kebab-case umwandeln. Der Standardwert der Parameter wird in der Form geschrieben, in der er an die Anwendung übergeben wird (PascalCase bei Presenter und Modul, camelCase bei der Aktion), im Fall eines Presenters schreiben wir also ``, nicht ``. Allgemeine Filter ----------------- -Neben Filtern, die für spezifische Parameter bestimmt sind, können wir auch allgemeine Filter definieren, die ein assoziatives Array aller Parameter erhalten, die sie beliebig modifizieren und dann zurückgeben können. Allgemeine Filter definieren wir unter dem Schlüssel `null`. +Neben Filtern für konkrete Parameter können wir auch allgemeine Filter definieren, die ein assoziatives Array aller Parameter erhalten, das sie beliebig verändern und dann zurückgeben können. Allgemeine Filter werden unter dem leeren Schlüssel definiert. ```php use Nette\Routing\Route; @@ -321,78 +331,94 @@ $router->addRoute('/', [ ]); ``` -Allgemeine Filter bieten die Möglichkeit, das Verhalten der Route auf beliebige Weise anzupassen. Wir können sie beispielsweise zur Modifikation von Parametern basierend auf anderen Parametern verwenden. Zum Beispiel die Übersetzung von `` und `` basierend auf dem aktuellen Wert des Parameters ``. +Allgemeine Filter bieten die Möglichkeit, das Verhalten der Route auf absolut beliebige Weise zu verändern. Wir können sie zum Beispiel nutzen, um Parameter anhand anderer Parameter zu verändern. Etwa `` und `` anhand des aktuellen Werts des Parameters `` zu übersetzen. -Wenn ein Parameter einen eigenen Filter definiert hat und gleichzeitig ein allgemeiner Filter existiert, wird der eigene `FilterIn` vor dem allgemeinen ausgeführt und umgekehrt der allgemeine `FilterOut` vor dem eigenen. Das heißt, innerhalb des allgemeinen Filters sind die Werte der Parameter `presenter` bzw. `action` im PascalCase- bzw. camelCase-Stil geschrieben. +Hat ein Parameter einen eigenen Filter definiert und existiert zugleich ein allgemeiner Filter, wird das eigene `FilterIn` vor dem allgemeinen ausgeführt und umgekehrt das allgemeine `FilterOut` vor dem eigenen. Innerhalb des allgemeinen Filters sind die Werte der Parameter `presenter` und `action` also im PascalCase- bzw. camelCase-Stil geschrieben. +Eine praktische Anwendung dieser Filter - das Erzeugen SEO-freundlicher URLs wie `/article/123-how-to-bake-bread` ohne jede Änderung an den Templates - finden Sie unter [Schöne URLs mit Slugs |best-practices:pretty-urls]. -Einwegrouten (OneWay) ---------------------- -Einwegrouten werden verwendet, um die Funktionalität alter URLs beizubehalten, die die Anwendung nicht mehr generiert, aber weiterhin akzeptiert. Wir markieren sie mit dem Flag `OneWay`: +Flag OneWay +----------- + +Einwegrouten dienen dazu, die Funktionsfähigkeit alter URLs zu erhalten, die die Anwendung nicht mehr erzeugt, aber weiterhin akzeptiert. Wir kennzeichnen sie mit dem Flag `OneWay`: ```php // alte URL /product-info?id=123 -$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); +$router->addRoute('product-info', 'Product:detail', oneWay: true); // neue URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -Beim Zugriff auf die alte URL leitet der Presenter automatisch auf die neue URL weiter, sodass Suchmaschinen diese Seiten nicht doppelt indizieren (siehe [#SEO und Kanonisierung]). +Beim Aufruf der alten URL leitet der Presenter automatisch auf die neue URL weiter, damit Suchmaschinen diese Seiten nicht doppelt indexieren (siehe [#SEO und Kanonisierung]). Dynamisches Routing mit Callbacks --------------------------------- -Dynamisches Routing mit Callbacks ermöglicht es Ihnen, Routen direkt Funktionen (Callbacks) zuzuordnen, die ausgeführt werden, wenn der entsprechende Pfad besucht wird. Diese flexible Funktionalität ermöglicht es Ihnen, schnell und effizient verschiedene Endpunkte (Endpoints) für Ihre Anwendung zu erstellen: +Dynamisches Routing mit Callbacks erlaubt es, Routes direkt Funktionen (Callbacks) zuzuweisen, die beim Aufruf des jeweiligen Pfads ausgeführt werden. Diese flexible Funktionalität ermöglicht es, schnell und effizient verschiedene Endpunkte für Ihre Anwendung zu erstellen: ```php $router->addRoute('test', function () { - echo 'Sie befinden sich unter der Adresse /test'; + echo 'Sie befinden sich auf der Adresse /test'; }); ``` -Sie können auch Parameter in der Maske definieren, die automatisch an Ihren Callback übergeben werden: +In der Maske können Sie auch Parameter definieren, die automatisch an Ihren Callback übergeben werden: ```php $router->addRoute('', function (string $lang) { echo match ($lang) { - 'cs' => 'Willkommen auf der tschechischen Version unserer Website!', - 'en' => 'Welcome to the English version of our website!', + 'cs' => 'Willkommen in der tschechischen Version unserer Website!', + 'en' => 'Willkommen in der englischen Version unserer Website!', }; }); ``` +Neben den Parametern aus der Maske kann der Callback auch Services aus dem DI-Container erhalten. Sie werden anhand des Parametertyps übergeben. Zusätzlich erhält der Parameter `$presenter` eine Instanz von [MicroPresenter |api:NetteModule\MicroPresenter], die die Route verarbeitet: + +```php +$router->addRoute('', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) { + // ... +}); +``` + Module ------ -Wenn wir mehrere Routen haben, die zu einem gemeinsamen [Modul |directory-structure#Presenter und Templates] gehören, verwenden wir `withModule()`: +Haben wir mehrere Routes, die zu einem gemeinsamen [Modul |directory-structure#Presenter und Templates] gehören, verwenden wir `withModule()`. Das angegebene Modul wird dem Presenter jeder Route in der Gruppe automatisch vorangestellt und verschwindet vollständig aus der URL: ```php $router = new RouteList; -$router->withModule('Forum') // Die folgenden Routen sind Teil des Moduls Forum - ->addRoute('rss', 'Feed:rss') // Der Presenter wird Forum:Feed sein +$router->withModule('Forum') // die folgenden Routes sind Teil des Moduls Forum + ->addRoute('rss', 'Feed:rss') // Presenter ist Forum:Feed ->addRoute('/') - ->withModule('Admin') // Die folgenden Routen sind Teil des Moduls Forum:Admin + ->withModule('Admin') // die folgenden Routes sind Teil des Moduls Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Eine Alternative ist die Verwendung des Parameters `module`: +Eine Alternative ist der Parameter `module`, der ebenfalls ein festes Modul setzt und es aus der URL heraushält: ```php -// Die URL manage/dashboard/default wird auf den Presenter Admin:Dashboard gemappt +// URL manage/dashboard/default wird auf den Presenter Admin:Dashboard abgebildet $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` +Der Name eines Presenters ist erst zusammen mit seinem Modul vollständig, z. B. `Front:Admin:ProductList`. Immer wenn ein solcher vollständiger Name in einem URL-Parameter landet, kodiert ihn der Router nach zwei einfachen Regeln: Jeder Doppelpunkt `:` (der Modultrenner) wird zu einem **Punkt**, und jede Wortgrenze in einem PascalCase-Namen wird zu einem **Bindestrich**. `Front:Admin:ProductList` erscheint in der URL also als `front.admin.product-list` und wird genauso wieder dekodiert. Genau deshalb erzeugt eine modulare Anwendung ohne eines der obigen Werkzeuge URLs voller Punkte. + +Sowohl `withModule()` als auch der Parameter `module` vermeiden das gerade dadurch, dass sie den bekannten Modulpräfix vom Presenter-Namen abschneiden, bevor er in die URL gelangt: Da das Modul eine Konstante ist, muss es überhaupt nicht kodiert werden. + +Manchmal wollen wir, dass sich das Modul selbst ändert und in der URL erscheint, und greifen daher direkt in der Maske zu ``. Achten Sie dabei auf eine entscheidende Kleinigkeit: **`` verschlingt den gesamten Modulpfad** - alles bis zum letzten Doppelpunkt im Presenter-Namen. Beim Presenter `Shop:Admin:Product` ist das also das Modul `Shop:Admin` und der Presenter `Product`, und weil Doppelpunkte zu Punkten werden, erhalten wir: + Subdomains ---------- -Routen-Sammlungen können nach Subdomains gegliedert werden: +Routensammlungen lassen sich nach Subdomains aufteilen: ```php $router = new RouteList; @@ -401,7 +427,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Im Domainnamen können auch [#Platzhalter] verwendet werden: +Im Domainnamen lassen sich auch [#Wildcards] verwenden: ```php $router = new RouteList; @@ -413,20 +439,20 @@ $router->withDomain('example.%tld%') Pfadpräfix ---------- -Routen-Sammlungen können nach dem Pfad in der URL gegliedert werden: +Routensammlungen lassen sich nach dem Pfad in der URL aufteilen: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // Fängt URL /eshop/rss ab - ->addRoute('/'); // Fängt URL /eshop// ab + ->addRoute('rss', 'Feed:rss') // passt auf die URL /eshop/rss + ->addRoute('/'); // passt auf die URL /eshop// ``` Kombinationen ------------- -Die oben genannten Gliederungen können miteinander kombiniert werden: +Die obigen Gruppierungen lassen sich miteinander kombinieren: ```php $router = (new RouteList) @@ -449,10 +475,10 @@ $router = (new RouteList) Query-Parameter --------------- -Masken können auch Query-Parameter enthalten (Parameter nach dem Fragezeichen in der URL). Für diese kann kein Validierungsausdruck definiert werden, aber der Name, unter dem sie an den Presenter übergeben werden, kann geändert werden: +Masken können auch Query-Parameter enthalten (Parameter hinter dem Fragezeichen in der URL). Für sie lässt sich kein Validierungsausdruck definieren, wohl aber der Name ändern, unter dem sie an den Presenter übergeben werden: ```php -// Den Query-Parameter 'cat' möchten wir in der Anwendung unter dem Namen 'categoryId' verwenden +// wir wollen den Query-Parameter 'cat' in der Anwendung unter dem Namen 'categoryId' verwenden $router->addRoute('product ? id= & cat=', /* ... */); ``` @@ -460,23 +486,23 @@ $router->addRoute('product ? id= & cat=', /* ... */); Foo-Parameter ------------- -Jetzt gehen wir tiefer. Foo-Parameter sind im Grunde unbenannte Parameter, die es ermöglichen, einen regulären Ausdruck abzugleichen. Ein Beispiel ist eine Route, die `/index`, `/index.html`, `/index.htm` und `/index.php` akzeptiert: +Jetzt gehen wir tiefer. Foo-Parameter sind im Grunde unbenannte Parameter, die es erlauben, einen regulären Ausdruck abzugleichen. Ein Beispiel ist eine Route, die `/index`, `/index.html`, `/index.htm` und `/index.php` akzeptiert: ```php $router->addRoute('index', /* ... */); ``` -Es kann auch explizit eine Zeichenkette definiert werden, die bei der URL-Generierung verwendet wird. Die Zeichenkette muss direkt nach dem Fragezeichen platziert werden. Die folgende Route ähnelt der vorherigen, aber generiert `/index.html` anstelle von `/index`, da die Zeichenkette `.html` als Generierungswert festgelegt ist: +Es lässt sich auch ausdrücklich der String festlegen, der beim Erzeugen der URL verwendet wird. Der String muss direkt hinter dem Fragezeichen stehen. Die folgende Route ähnelt der vorherigen, erzeugt aber `/index.html` statt `/index`, weil der String `.html` als Wert für die Erzeugung gesetzt ist: ```php $router->addRoute('index', /* ... */); ``` -Integration in die Anwendung -============================ +Integration +=========== -Um den erstellten Router in die Anwendung einzubinden, müssen wir den DI-Container darüber informieren. Der einfachste Weg ist, eine Factory vorzubereiten, die das Router-Objekt erstellt, und dem Container in der Konfiguration mitzuteilen, dass er sie verwenden soll. Nehmen wir an, wir schreiben zu diesem Zweck eine Methode `App\Core\RouterFactory::createRouter()`: +Um den erstellten Router in die Anwendung einzubinden, müssen wir dem DI-Container von ihm erzählen. Am einfachsten ist es, eine Factory vorzubereiten, die das Router-Objekt erzeugt, und dem Container in der Konfiguration zu sagen, dass er sie verwenden soll. Nehmen wir an, wir schreiben dafür die Methode `App\Core\RouterFactory::createRouter()`: ```php namespace App\Core; @@ -494,14 +520,14 @@ class RouterFactory } ``` -In die [Konfiguration |dependency-injection:services] schreiben wir dann: +Dann schreiben wir in die [Konfiguration |dependency-injection:services]: ```neon services: - App\Core\RouterFactory::createRouter ``` -Jegliche Abhängigkeiten, zum Beispiel auf eine Datenbank usw., werden der Factory-Methode als ihre Parameter mittels [Autowiring|dependency-injection:autowiring] übergeben: +Eventuelle Abhängigkeiten, etwa von einer Datenbank usw., werden der Factory-Methode über [Autowiring|dependency-injection:autowiring] als ihre Parameter übergeben: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -514,18 +540,18 @@ public static function createRouter(Nette\Database\Connection $db): RouteList SimpleRouter ============ -Ein viel einfacherer Router als die Routen-Sammlung ist [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Wir verwenden ihn, wenn wir keine besonderen Anforderungen an die URL-Form haben, wenn `mod_rewrite` (oder seine Alternativen) nicht verfügbar ist oder wenn wir uns noch nicht mit schönen URLs befassen wollen. +Ein weitaus einfacherer Router als die Routensammlung ist der [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Wir verwenden ihn, wenn wir keine besonderen Anforderungen an die Form der URL haben, wenn `mod_rewrite` (oder seine Alternativen) nicht verfügbar ist oder wenn wir uns mit pretty URLs noch nicht befassen wollen. -Er generiert Adressen ungefähr in dieser Form: +Er erzeugt Adressen ungefähr in dieser Form: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Der Parameter des SimpleRouter-Konstruktors ist der Standard-Presenter & die Standard-Aktion, auf den verwiesen werden soll, wenn wir eine Seite ohne Parameter öffnen, z. B. `http://example.com/`. +Der Parameter des Konstruktors von `SimpleRouter` ist der Standard-Presenter & die Standardaktion, also die Aktion, die ausgeführt wird, wenn wir z. B. `http://example.com/` ohne weitere Parameter öffnen. ```php -// Der Standard-Presenter wird 'Home' sein und die Aktion 'default' +// der Standard-Presenter wird 'Home' und die Aktion 'default' sein $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` @@ -540,19 +566,19 @@ services: SEO und Kanonisierung ===================== -Das Framework trägt zur SEO (Optimierung der Auffindbarkeit im Internet) bei, indem es doppelte Inhalte unter verschiedenen URLs verhindert. Wenn mehrere Adressen zu einem bestimmten Ziel führen, z. B. `/index` und `/index.html`, bestimmt das Framework die erste davon als primäre (kanonische) und leitet die anderen mit dem HTTP-Code 301 darauf um. Dadurch indizieren Suchmaschinen die Seiten nicht doppelt und verwässern ihren Page Rank nicht. +Das Framework trägt zum SEO (Search Engine Optimization) bei, indem es verhindert, dass derselbe Inhalt unter verschiedenen URLs existiert. Führen mehrere Adressen zu einem bestimmten Ziel, z. B. `/index` und `/index.html`, bestimmt das Framework die erste als primär (kanonisch) und leitet die übrigen mit dem HTTP-Code 301 dorthin weiter. Dadurch indexieren Suchmaschinen die Seiten nicht doppelt und verwässern deren Page Rank nicht. -Dieser Prozess wird Kanonisierung genannt. Die kanonische URL ist diejenige, die vom Router generiert wird, d. h. die erste passende Route in der Sammlung ohne das OneWay-Flag. Deshalb führen wir in der Sammlung die **primären Routen zuerst** auf. +Dieser Vorgang heißt Kanonisierung. Die kanonische URL ist diejenige, die der Router erzeugt, also die erste passende Route in der Collection ohne das Flag OneWay. Deshalb führen wir in der Collection die **primären Routes zuerst** auf. -Die Kanonisierung wird vom Presenter durchgeführt, mehr im Kapitel [Kanonisierung |presenters#Kanonisierung]. +Die Kanonisierung führt der Presenter durch, mehr im Kapitel [Kanonisierung |presenters#Kanonisierung]. HTTPS ===== -Um das HTTPS-Protokoll verwenden zu können, ist es notwendig, es beim Hosting zu aktivieren und den Server korrekt zu konfigurieren. +Um das Protokoll HTTPS zu verwenden, muss es beim Hosting aktiviert und der Server korrekt konfiguriert sein. -Die Weiterleitung der gesamten Website auf HTTPS muss auf Serverebene eingestellt werden, zum Beispiel mithilfe der .htaccess-Datei im Stammverzeichnis unserer Anwendung, und zwar mit dem HTTP-Code 301. Die Einstellungen können je nach Hosting variieren und sehen etwa so aus: +Die Weiterleitung der gesamten Website auf HTTPS muss auf Serverebene eingerichtet werden, zum Beispiel über die Datei `.htaccess` im Wurzelverzeichnis unserer Anwendung, mit dem HTTP-Code 301. Die Einstellung kann sich je nach Hosting unterscheiden und sieht ungefähr so aus: ``` @@ -564,15 +590,15 @@ Die Weiterleitung der gesamten Website auf HTTPS muss auf Serverebene eingestell ``` -Der Router generiert URLs mit demselben Protokoll, mit dem die Seite geladen wurde, daher muss nichts weiter eingestellt werden. +Der Router erzeugt URLs mit demselben Protokoll, mit dem die Seite geladen wurde, es muss also nichts weiter eingestellt werden. -Wenn wir aber ausnahmsweise benötigen, dass verschiedene Routen unter verschiedenen Protokollen laufen, geben wir es in der Routenmaske an: +Brauchen wir jedoch ausnahmsweise, dass verschiedene Routes unter verschiedenen Protokollen laufen, geben wir das in der Maske der Route an: ```php -// Wird eine Adresse mit HTTP generieren +// Erzeugt eine HTTP-Adresse $router->addRoute('http://%host%//', /* ... */); -// Wird eine Adresse mit HTTPS generieren +// Erzeugt eine HTTPS-Adresse $router->addRoute('https://%host%//', /* ... */); ``` @@ -580,24 +606,24 @@ $router->addRoute('https://%host%//', /* ... */); Debugging des Routers ===================== -Das Routing-Panel, das in der [Tracy Bar |tracy:] angezeigt wird, ist ein nützlicher Helfer, der eine Liste der Routen sowie die Parameter anzeigt, die der Router aus der URL erhalten hat. +Das in der [Tracy Bar |tracy:] angezeigte Routing-Panel ist ein nützlicher Helfer, der eine Liste der Routes anzeigt und außerdem die Parameter, die der Router aus der URL gewonnen hat. -Der grüne Balken mit dem Symbol ✓ stellt die Route dar, die die aktuelle URL verarbeitet hat, mit blauer Farbe und dem Symbol ≈ sind Routen gekennzeichnet, die die URL ebenfalls verarbeitet hätten, wenn die grüne sie nicht überholt hätte. Weiterhin sehen wir den aktuellen Presenter & die aktuelle Aktion. +Der grüne Balken mit dem Symbol ✓ stellt die Route dar, die die aktuelle URL verarbeitet hat; blaue Farbe und das Symbol ≈ kennzeichnen Routes, die die URL ebenfalls verarbeitet hätten, wenn ihnen die grüne nicht zuvorgekommen wäre. Weiter sehen wir den aktuellen Presenter & die aktuelle Aktion. [* routing-debugger.webp *] -Gleichzeitig, wenn es zu einer unerwarteten Weiterleitung aufgrund der [Kanonisierung |#SEO und Kanonisierung] kommt, ist es nützlich, in das Panel in der Leiste *redirect* zu schauen, wo Sie herausfinden, wie der Router die URL ursprünglich verstanden hat und warum er weitergeleitet hat. +Kommt es zugleich wegen der [Kanonisierung |#SEO und Kanonisierung] zu einer unerwarteten Weiterleitung, lohnt sich ein Blick in den Balken *redirect* im Panel, wo Sie herausfinden, wie der Router die URL ursprünglich verstanden hat und warum er weitergeleitet hat. .[note] -Beim Debuggen des Routers empfehlen wir, die Developer Tools im Browser zu öffnen (Strg+Shift+I oder Cmd+Option+I) und im Network-Panel den Cache zu deaktivieren, damit Weiterleitungen nicht darin gespeichert werden. +Beim Debuggen des Routers empfehlen wir, im Browser die Developer Tools zu öffnen (Strg+Umschalt+I oder Cmd+Option+I) und im Panel Network den Cache abzuschalten, damit die Weiterleitungen nicht darin gespeichert werden. Leistung ======== -Die Anzahl der Routen beeinflusst die Geschwindigkeit des Routers. Ihre Anzahl sollte definitiv nicht mehrere Dutzend überschreiten. Wenn Ihre Website eine zu komplizierte URL-Struktur hat, können Sie einen maßgeschneiderten [#Eigener Router] schreiben. +Die Anzahl der Routes beeinflusst die Geschwindigkeit des Routers. Ihre Anzahl sollte auf keinen Fall einige Dutzend überschreiten. Hat Ihre Website eine zu komplizierte URL-Struktur, können Sie einen [#Eigener Router] schreiben. -Wenn der Router keine Abhängigkeiten hat, zum Beispiel von einer Datenbank, und seine Factory keine Argumente entgegennimmt, können wir seine kompilierte Form direkt in den DI-Container serialisieren und dadurch die Anwendung geringfügig beschleunigen. +Hat der Router keine Abhängigkeiten, etwa von einer Datenbank, und nimmt seine Factory keine Argumente entgegen, können wir seine kompilierte Form direkt in den DI-Container serialisieren und die Anwendung damit etwas beschleunigen. ```neon routing: @@ -608,7 +634,7 @@ routing: Eigener Router ============== -Die folgenden Zeilen sind für sehr fortgeschrittene Benutzer bestimmt. Sie können Ihren eigenen Router erstellen und ihn ganz natürlich in die Routen-Sammlung integrieren. Der Router ist eine Implementierung des Interfaces [api:Nette\Routing\Router] mit zwei Methoden: +Die folgenden Zeilen sind für sehr fortgeschrittene Benutzer gedacht. Sie können einen eigenen Router erstellen und ihn ganz natürlich in die Routensammlung einbinden. Der Router ist eine Implementierung des Interfaces [api:Nette\Routing\Router] mit zwei Methoden: ```php use Nette\Http\IRequest as HttpRequest; @@ -628,7 +654,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Die Methode `match` verarbeitet die aktuelle Anfrage [$httpRequest |http:request], aus der nicht nur die URL, sondern auch Header usw. abgerufen werden können, in ein Array, das den Namen des Presenters und seine Parameter enthält. Wenn sie die Anfrage nicht verarbeiten kann, gibt sie null zurück. Bei der Verarbeitung der Anfrage müssen wir mindestens den Presenter und die Aktion zurückgeben. Der Name des Presenters ist vollständig und enthält auch eventuelle Module: +Die Methode `match` verarbeitet den aktuellen Request [$httpRequest |http:request], aus dem sich nicht nur die URL, sondern auch Header usw. gewinnen lassen, in ein Array mit dem Namen des Presenters und seinen Parametern. Kann sie den Request nicht verarbeiten, gibt sie null zurück. Bei der Verarbeitung des Requests müssen wir mindestens den Presenter zurückgeben; die Aktion ist optional und ist standardmäßig `default`, wenn sie nicht angegeben wird. Der Name des Presenters ist vollständig und enthält eventuelle Module: ```php [ @@ -637,9 +663,9 @@ Die Methode `match` verarbeitet die aktuelle Anfrage [$httpRequest |http:request ] ``` -Die Methode `constructUrl` erstellt umgekehrt aus dem Parameter-Array die resultierende absolute URL. Dazu kann sie Informationen aus dem Parameter [`$refUrl`|api:Nette\Http\UrlScript] verwenden, was die aktuelle URL ist. +Die Methode `constructUrl` konstruiert umgekehrt aus dem Array der Parameter die resultierende absolute URL. Sie kann dabei Informationen aus dem Parameter [`$refUrl`|api:Nette\Http\UrlScript] nutzen, der die aktuelle URL ist. -Zur Routen-Sammlung fügen Sie ihn mit `add()` hinzu: +Zur Routensammlung fügen Sie ihn mit `add()` hinzu: ```php $router = new Nette\Application\Routers\RouteList; @@ -652,13 +678,13 @@ $router->addRoute(/* ... */); Eigenständige Verwendung ======================== -Unter eigenständiger Verwendung verstehen wir die Nutzung der Fähigkeiten des Routers in einer Anwendung, die Nette Application und Presenter nicht verwendet. Dafür gilt fast alles, was wir in diesem Kapitel gezeigt haben, mit diesen Unterschieden: +Mit eigenständiger Verwendung meinen wir den Einsatz der Fähigkeiten des Routers in einer Anwendung, die Nette Application und Presenter nicht verwendet. Fast alles, was wir in diesem Kapitel gezeigt haben, gilt auch dafür, mit diesen Unterschieden: -- für Routen-Sammlungen verwenden wir die Klasse [api:Nette\Routing\RouteList] +- für Routensammlungen verwenden wir die Klasse [api:Nette\Routing\RouteList] - als einfachen Router die Klasse [api:Nette\Routing\SimpleRouter] -- da das Paar `Presenter:action` nicht existiert, verwenden wir die [#Erweiterte Notation] +- weil das Paar `Presenter:action` nicht existiert, verwenden wir die [#Erweiterte Schreibweise] -Also erstellen wir wieder eine Methode, die uns den Router zusammenstellt, z.B.: +Wir erstellen also wieder eine Methode, die uns den Router zusammenbaut, z. B.: ```php namespace App\Core; @@ -682,35 +708,35 @@ class RouterFactory } ``` -Wenn Sie einen DI-Container verwenden, was wir empfehlen, fügen wir die Methode wieder zur Konfiguration hinzu und holen danach den Router zusammen mit der HTTP-Anfrage aus dem Container: +Wenn Sie einen DI-Container verwenden, was wir empfehlen, fügen Sie die Methode wieder in die Konfiguration ein und holen sich dann den Router samt HTTP-Request aus dem Container: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Oder wir erstellen die Objekte direkt: +Oder erzeugen Sie die Objekte direkt: ```php $router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Jetzt muss der Router nur noch seine Arbeit aufnehmen: +Nun bleibt nur noch, den Router seine Arbeit tun zu lassen: ```php $params = $router->match($httpRequest); if ($params === null) { - // Es wurde keine passende Route gefunden, senden wir einen 404-Fehler + // keine passende Route gefunden, Fehler 404 senden exit; } -// Wir verarbeiten die erhaltenen Parameter +// die gewonnenen Parameter verarbeiten $controller = $params['controller']; // ... ``` -Und umgekehrt verwenden wir den Router, um einen Link zu erstellen: +Und umgekehrt den Router zum Konstruieren eines Links verwenden: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; @@ -718,4 +744,6 @@ $url = $router->constructUrl($params, $httpRequest->getUrl()); ``` -{{composer: nette/router}} +{{composer: nette/routing}} +{{repo: nette/routing}} +{{api: https://api.nette.org/routing/}} diff --git a/application/de/templates.texy b/application/de/templates.texy index 4b36ccee84..5b4d74fdc4 100644 --- a/application/de/templates.texy +++ b/application/de/templates.texy @@ -2,9 +2,9 @@ Templates ********* .[perex] -Nette verwendet das Template-System [Latte |latte:]. Zum einen, weil es das sicherste Template-System für PHP ist, und zum anderen, weil es das intuitivste System ist. Sie müssen nicht viel Neues lernen, PHP-Kenntnisse und ein paar Tags reichen aus. +Nette verwendet das Templating-System [Latte |latte:]. Latte wird verwendet, weil es das sicherste Templating-System für PHP ist und zugleich das intuitivste. Sie müssen nicht viel Neues lernen; Kenntnisse von PHP und einigen wenigen Tags genügen. -Es ist üblich, dass eine Seite aus einer Layout-Vorlage und einer Vorlage für die jeweilige Aktion zusammengesetzt wird. So kann zum Beispiel eine Layout-Vorlage aussehen, beachten Sie die `{block}` Blöcke und den `{include}` Tag: +Üblicherweise setzt sich eine Seite aus einem Layout-Template und dem Template der konkreten Aktion zusammen. So kann ein Layout-Template aussehen; beachten Sie die Blöcke `{block}` und den Tag `{include}`: ```latte @@ -20,26 +20,26 @@ Es ist üblich, dass eine Seite aus einer Layout-Vorlage und einer Vorlage für ``` -Und das wird die Aktionsvorlage sein: +Und so würde das Template der Aktion aussehen: ```latte -{block title}Homepage{/block} +{block title}Startseite{/block} {block content} -

    Homepage

    +

    Startseite

    ... {/block} ``` -Diese definiert den Block `content`, der anstelle von `{include content}` im Layout eingefügt wird, und definiert auch den Block `title` neu, der `{block title}` im Layout überschreibt. Versuchen Sie, sich das Ergebnis vorzustellen. +Es definiert den Block `content`, der im Layout anstelle von `{include content}` eingefügt wird, und definiert außerdem den Block `title` neu, der `{block title}` im Layout überschreibt. Versuchen Sie sich das Ergebnis vorzustellen. -Finden von Vorlagen -------------------- +Suche nach Templates +-------------------- -Sie müssen in den Presentern nicht angeben, welche Vorlage gerendert werden soll; das Framework leitet den Pfad selbst ab und erspart Ihnen das Schreiben. +In Presentern müssen Sie nicht angeben, welches Template gerendert werden soll; das Framework leitet den Pfad selbst ab und erspart Ihnen das Schreiben. -Wenn Sie eine Verzeichnisstruktur verwenden, in der jeder Presenter sein eigenes Verzeichnis hat, platzieren Sie die Vorlage einfach in diesem Verzeichnis unter dem Namen der Aktion (bzw. der View), d.h. für die Aktion `default` verwenden Sie die Vorlage `default.latte`: +Wenn Sie eine Verzeichnisstruktur verwenden, bei der jeder Presenter sein eigenes Verzeichnis hat, legen Sie das Template einfach in dieses Verzeichnis unter dem Namen der Aktion (also des Views). Für die Aktion `default` verwenden Sie zum Beispiel das Template `default.latte`: /--pre app/ @@ -49,46 +49,46 @@ app/ └── default.latte \-- -Wenn Sie eine Struktur verwenden, bei der sich die Presenter gemeinsam in einem Verzeichnis und die Vorlagen im Ordner `templates` befinden, speichern Sie sie entweder in der Datei `..latte` oder `/.latte`: +Wenn Sie eine Struktur verwenden, bei der die Presenter gemeinsam in einem Verzeichnis liegen und die Templates im Ordner `templates`, speichern Sie es entweder in der Datei `..latte` oder `/.latte`: /--pre app/ └── Presenters/ ├── HomePresenter.php └── templates/ - ├── Home.default.latte ← 1. Variante - └── Home/ - └── default.latte ← 2. Variante + ├── Home/ + │ └── default.latte ← 1. Variante + └── Home.default.latte ← 2. Variante \-- -Der Ordner `templates` kann auch eine Ebene höher platziert werden, d.h. auf derselben Ebene wie das Verzeichnis mit den Presenter-Klassen. +Das Verzeichnis `templates` kann auch eine Ebene höher liegen, also auf derselben Ebene wie das Verzeichnis mit den Presenter-Klassen. -Wenn die Vorlage nicht gefunden wird, antwortet der Presenter [mit einem Fehler 404 - Seite nicht gefunden |presenters#Fehler 404 und Co]. +Wird das Template nicht gefunden, antwortet der Presenter mit dem [Fehler 404 - Seite nicht gefunden |presenters#Fehler 404 usw.]. -Die View ändern Sie mit `$this->setView('andereView')`. Es ist auch möglich, die Datei mit der Vorlage direkt mit `$this->template->setFile('/path/to/template.latte')` anzugeben. +Den View ändern Sie mit `$this->setView('otherView')`. Es lässt sich auch direkt die Template-Datei mit `$this->template->setFile('/path/to/template.latte')` angeben. .[note] -Die Dateien, in denen nach Vorlagen gesucht wird, können durch Überschreiben der Methode [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] geändert werden, die ein Array möglicher Dateinamen zurückgibt. +Die Dateien, in denen nach Templates gesucht wird, lassen sich durch Überschreiben der Methode [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] ändern, die ein Array möglicher Dateinamen zurückgibt. -Finden der Layout-Vorlage -------------------------- +Suche nach dem Layout-Template +------------------------------ -Nette sucht auch automatisch nach der Layout-Datei. +Nette sucht auch die Layout-Datei automatisch. -Wenn Sie eine Verzeichnisstruktur verwenden, in der jeder Presenter sein eigenes Verzeichnis hat, platzieren Sie das Layout entweder im Ordner des Presenters, wenn es nur für diesen spezifisch ist, oder eine Ebene höher, wenn es für mehrere Presenter gemeinsam genutzt wird: +Wenn Sie eine Verzeichnisstruktur verwenden, bei der jeder Presenter sein eigenes Verzeichnis hat, legen Sie das Layout entweder in den Ordner mit dem Presenter, falls es nur für ihn gilt, oder eine Ebene höher, falls es mehreren Presentern gemeinsam ist: /--pre app/ └── Presentation/ ├── @layout.latte ← gemeinsames Layout └── Home/ - ├── @layout.latte ← nur für Presenter Home + ├── @layout.latte ← nur für den Presenter Home ├── HomePresenter.php └── default.latte \-- -Wenn Sie eine Struktur verwenden, bei der sich die Presenter gemeinsam in einem Verzeichnis und die Vorlagen im Ordner `templates` befinden, wird das Layout an diesen Stellen erwartet: +Wenn Sie eine Struktur verwenden, bei der die Presenter gemeinsam in einem Verzeichnis liegen und die Templates im Ordner `templates`, wird das Layout an diesen Orten erwartet: /--pre app/ @@ -96,33 +96,66 @@ app/ ├── HomePresenter.php └── templates/ ├── @layout.latte ← gemeinsames Layout - ├── Home.@layout.latte ← nur für Home, 1. Variante - └── Home/ - └── @layout.latte ← nur für Home, 2. Variante + ├── Home/ + │ └── @layout.latte ← nur für Home, 1. Variante + └── Home.@layout.latte ← nur für Home, 2. Variante \-- -Wenn sich der Presenter in einem Modul befindet, wird auch auf weiteren Verzeichnisebenen höher gesucht, je nach Verschachtelung des Moduls. +Liegt der Presenter in einem Modul, wird entsprechend der Verschachtelung der Module auch in den höheren Verzeichnisebenen gesucht. -Der Name des Layouts kann mit `$this->setLayout('layoutAdmin')` geändert werden, dann wird es in der Datei `@layoutAdmin.latte` erwartet. Es ist auch möglich, die Datei mit der Layout-Vorlage direkt mit `$this->setLayout('/path/to/template.latte')` anzugeben. +Den Namen des Layouts ändern Sie mit `$this->setLayout('layoutAdmin')`, es wird dann in der Datei `@layoutAdmin.latte` erwartet. Sie können die Datei des Layout-Templates auch direkt mit `$this->setLayout('/path/to/template.latte')` angeben. -Mit `$this->setLayout(false)` oder dem Tag `{layout none}` innerhalb der Vorlage wird die Layout-Suche deaktiviert. +Mit `$this->setLayout(false)` oder dem Tag `{layout none}` im Template wird die Suche nach dem Layout abgeschaltet. .[note] -Die Dateien, in denen nach Layout-Vorlagen gesucht wird, können durch Überschreiben der Methode [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] geändert werden, die ein Array möglicher Dateinamen zurückgibt. +Die Dateien, in denen nach Layout-Templates gesucht wird, lassen sich durch Überschreiben der Methode [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] ändern, die ein Array möglicher Dateinamen zurückgibt. -Variablen in der Vorlage ------------------------- +Variablen im Template +--------------------- -Variablen werden an die Vorlage übergeben, indem wir sie in `$this->template` schreiben. Danach stehen sie in der Vorlage als lokale Variablen zur Verfügung: +Variablen übergibt man an Templates, indem man sie in `$this->template` schreibt. Im Template stehen sie dann als lokale Variablen zur Verfügung: ```php $this->template->article = $this->articles->getById($id); ``` -So einfach können wir beliebige Variablen an Vorlagen übergeben. Bei der Entwicklung robuster Anwendungen ist es jedoch nützlicher, sich einzuschränken. Zum Beispiel, indem wir explizit eine Liste der Variablen definieren, die die Vorlage erwartet, und deren Typen. Dadurch kann PHP die Typen prüfen, die IDE korrekt Vorschläge machen und die statische Analyse Fehler aufdecken. +Um den Wert einer Property automatisch als Variable an das Template zu übergeben, kennzeichnen Sie sie mit dem Attribut `#[TemplateVariable]` und public-Sichtbarkeit: .{data-version:3.2.9} + +```php +use Nette\Application\Attributes\TemplateVariable; + +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + #[TemplateVariable] + public string $siteName = 'Mein Blog'; +} +``` + +Übergeben Sie dem Template eine Variable gleichen Namens, überschreibt `#[TemplateVariable]` sie nicht. + + +Standardvariablen +----------------- + +Presenter und Komponenten übergeben Templates automatisch mehrere nützliche Variablen: + +- `$basePath` ist der absolute URL-Pfad zum Wurzelverzeichnis (z. B. `/eshop`) +- `$baseUrl` ist die absolute URL zum Wurzelverzeichnis (z. B. `http://localhost/eshop`) +- `$user` ist ein Objekt, das [den Benutzer repräsentiert |security:authentication] +- `$presenter` ist der aktuelle Presenter +- `$control` ist die aktuelle Komponente oder der Presenter +- `$flashes` ist ein Array von [Meldungen |presenters#Flash-Meldungen], die mit der Funktion `flashMessage()` gesendet wurden + +Wenn Sie eine eigene Template-Klasse verwenden, werden diese Variablen übergeben, sofern Sie eine Property dafür anlegen. + -Und wie definieren wir eine solche Liste? Einfach in Form einer Klasse und ihrer Eigenschaften. Wir benennen sie ähnlich wie den Presenter, nur mit `Template` am Ende: +Typsichere Templates +-------------------- + +Bei der Entwicklung robuster Anwendungen ist es nützlich, ausdrücklich festzulegen, welche Variablen das Template erwartet und welche Typen sie haben. Das bringt Typprüfung in PHP, intelligente Hinweise in Ihrer IDE und ermöglicht der statischen Analyse, Fehler aufzudecken. + +Wie definiert man eine solche Liste? Einfach als Klasse mit Properties, die die Variablen des Templates darstellen. Benennen Sie sie ähnlich wie den Presenter, nur mit `Template` am Ende: ```php /** @@ -141,22 +174,24 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Das Objekt `$this->template` im Presenter ist nun eine Instanz der Klasse `ArticleTemplate`. PHP prüft also beim Schreiben die deklarierten Typen. Ab PHP Version 8.2 wird auch auf das Schreiben in eine nicht existierende Variable hingewiesen, in früheren Versionen kann dasselbe durch die Verwendung des Traits [Nette\SmartObject |utils:smartobject] erreicht werden. +Das Objekt `$this->template` im Presenter ist nun eine Instanz der Klasse `ArticleTemplate`. PHP prüft dadurch beim Schreiben die deklarierten Typen. + +Nette wählt die Template-Klasse automatisch. Zuerst sucht es eine Klasse namens `Template`, z. B. `ArticleEditTemplate` für die Aktion `edit`, und erst wenn diese nicht existiert, greift es auf `Template` zurück. -Die Annotation `@property-read` ist für IDEs und statische Analyse gedacht, dank ihr funktioniert die Autovervollständigung, siehe [PhpStorm und Code-Vervollständigung für $this->template |https://blog.nette.org/de/phpstorm-und-code-completion-for-this-template]. +Die Annotation `@property-read` ist für die IDE und die statische Analyse gedacht und ermöglicht Code-Vervollständigung, siehe "PhpStorm und Code-Vervollständigung für $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Den Luxus der Autovervollständigung können Sie sich auch in Vorlagen gönnen. Installieren Sie einfach das Latte-Plugin für PhpStorm und geben Sie den Klassennamen am Anfang der Vorlage an, mehr dazu im Artikel [Latte: wie man das Typsystem benutzt |https://blog.nette.org/de/latte-wie-man-das-typsystem-benutzt]: +Code-Vervollständigung lässt sich auch direkt in Templates nutzen. Installieren Sie einfach das Latte-Plugin für PhpStorm und geben Sie am Anfang des Templates den Klassennamen der Template-Parameter an, mehr im Kapitel [Latte: Typsystem |latte:type-system]: ```latte {templateType App\Presentation\Article\ArticleTemplate} ... ``` -So funktionieren auch Vorlagen in Komponenten. Halten Sie einfach die Namenskonvention ein und erstellen Sie für eine Komponente wie `FifteenControl` eine Vorlagenklasse `FifteenTemplate`. +Dasselbe gilt für Komponenten. Halten Sie sich einfach an die Namenskonvention und legen Sie für eine Komponente wie `FifteenControl` eine Parameterklasse `FifteenTemplate` an. -Wenn Sie `$template` als Instanz einer anderen Klasse erstellen müssen, verwenden Sie die Methode `createTemplate()`: +Wenn Sie eine andere Parameterklasse verwenden müssen, nutzen Sie die Methode `createTemplate()`: ```php public function renderDefault(): void @@ -168,58 +203,100 @@ public function renderDefault(): void } ``` +.{data-version:3.3.0} +Wenn Sie beeinflussen wollen, wie das Template vor dem Rendern fertiggestellt wird - zum Beispiel um Variablen zu ergänzen, die alle Aktionen gemeinsam haben -, können Sie im Presenter die Methode `completeTemplate()` überschreiben. Sie wird unmittelbar vor dem Rendern des Templates aufgerufen: -Standardvariablen ------------------ - -Presenter und Komponenten übergeben automatisch einige nützliche Variablen an die Vorlagen: - -- `$basePath` ist der absolute URL-Pfad zum Stammverzeichnis (z.B. `/eshop`) -- `$baseUrl` ist die absolute URL zum Stammverzeichnis (z.B. `http://localhost/eshop`) -- `$user` ist das Objekt, das den [Benutzer repräsentiert |security:authentication] -- `$presenter` ist der aktuelle Presenter -- `$control` ist die aktuelle Komponente oder der aktuelle Presenter -- `$flashes` Array von [Nachrichten |presenters#Flash-Nachrichten], die mit der Funktion `flashMessage()` gesendet wurden - -Wenn Sie Ihre eigene Vorlagenklasse verwenden, werden diese Variablen übergeben, wenn Sie eine Eigenschaft für sie erstellen. +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'Mein Blog'; +} +``` -Erstellen von Links -------------------- +Links erstellen +--------------- -In der Vorlage werden Links zu anderen Presentern & Aktionen auf diese Weise erstellt: +Im Template werden Links auf andere Presenter & Aktionen so erstellt: ```latte Produktdetail ``` -Das Attribut `n:href` ist sehr praktisch für HTML-Tags ``. Wenn wir den Link an anderer Stelle ausgeben möchten, zum Beispiel im Text, verwenden wir `{link}`: +Das Attribut `n:href` ist für HTML-Tags `` sehr praktisch. Wollen wir den Link anderswo ausgeben, zum Beispiel im Text, verwenden wir `{link}`: ```latte -Die Adresse ist: {link Home:default} +Die URL lautet: {link Home:default} ``` -Weitere Informationen finden Sie im Kapitel [Erstellen von URL-Links|creating-links]. +Mehr dazu finden Sie im Kapitel [Erstellen von URL-Links|creating-links]. Eigene Filter, Tags usw. ------------------------ -Das Latte-Template-System kann um eigene Filter, Funktionen, Tags usw. erweitert werden. Dies kann direkt in der Methode `render` oder `beforeRender()` geschehen: +Das Templating-System Latte lässt sich um eigene Filter, Funktionen, Tags und weitere Elemente erweitern. Dafür stehen drei Wege zur Verfügung, von schnellen Ad-hoc-Lösungen bis zu architektonischen Mustern für ganze Anwendungen. + +**Ad hoc in Methoden des Presenters** + +Der schnellste Weg ist, Filter oder Funktionen direkt im Code des Presenters oder der Komponente zu ergänzen. In Presentern eignen sich dafür die Methoden `beforeRender()` oder `render()`: ```php -public function beforeRender(): void +protected function beforeRender(): void { - // Hinzufügen eines Filters - $this->template->addFilter('foo', /* ... */); + // einen Filter hinzufügen + $this->template->addFilter('money', fn($val) => number_format($val, 2) . ' €'); + + // eine Funktion hinzufügen + $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); +} +``` + +Im Template: + +```latte +

    Preis: {$price|money}

    + +{if isWeekend($now)} ... {/if} +``` + +Für komplexere Logik können Sie das Objekt `Latte\Engine` direkt konfigurieren: - // oder wir konfigurieren direkt das Latte\Engine Objekt +```php +protected function beforeRender(): void +{ $latte = $this->template->getLatte(); - $latte->addFilterLoader(/* ... */); + $latte->setFeature(Latte\Feature::MigrationWarnings); } ``` -Latte in Version 3 bietet einen fortgeschritteneren Weg, nämlich die Erstellung einer [Extension |latte:extending-latte#Latte Extension] für jedes Webprojekt. Ein kurzes Beispiel einer solchen Klasse: +**Mit Attributen** + +Ein eleganterer Weg ist, Filter und Funktionen als Methoden direkt in der [Parameterklasse des Templates|#Typsichere Templates] des Presenters oder der Komponente zu definieren und mit Attributen zu kennzeichnen: + +```php +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + #[Latte\Attributes\TemplateFilter] + public function money(float $val): string + { + return number_format($val, 2) . ' €'; + } + + #[Latte\Attributes\TemplateFunction] + public function isWeekend(DateTimeInterface $date): bool + { + return $date->format('N') >= 6; + } +} +``` + +Latte findet und registriert die mit diesen Attributen gekennzeichneten Methoden automatisch. Der Name des Filters bzw. der Funktion im Template entspricht dem Methodennamen. Diese Methoden müssen public sein. + +**Global mit Extensions** + +Die bisherigen Wege eignen sich für Filter und Funktionen, die nur in bestimmten Presentern oder Komponenten gebraucht werden, nicht anwendungsweit. Für die gesamte Anwendung eignet sich am besten das Erstellen einer [Extension |latte:extending-latte#Latte Extension]. Diese Klasse bündelt alle Latte-Erweiterungen Ihres Projekts an einer Stelle. Ein kurzes Beispiel: ```php namespace App\Presentation\Accessory; @@ -251,11 +328,16 @@ final class LatteExtension extends Latte\Extension ]; } + private function filterTimeAgoInWords(DateTimeInterface $time): string + { + // ... + } + // ... } ``` -Wir registrieren sie über die [Konfiguration |configuration#Latte-Templates]: +Registrieren Sie die Extension über die [Konfiguration |configuration#Latte-Templates]: ```neon latte: @@ -263,13 +345,27 @@ latte: - App\Presentation\Accessory\LatteExtension ``` +Extensions bieten mehrere Vorteile: Unterstützung für Dependency Injection, Zugriff auf die Model-Schicht Ihrer Anwendung und zentrale Verwaltung aller Erweiterungen. Sie unterstützen außerdem eigene Tags, Provider, Compiler-Pässe und mehr. + + +Alle Templates einrichten +------------------------- + +Der Service `TemplateFactory`, der alle Templates erzeugt, bietet ein öffentliches Array von Callbacks `$onCreate`. Diese werden bei jedem Erzeugen eines beliebigen Templates aufgerufen, sodass Sie Filter, Funktionen oder Variablen für alle Templates der Anwendung von einer einzigen Stelle aus einrichten können. Jedes Callback erhält das neu erzeugte Template. Lassen Sie sich den Service `TemplateFactory` [übergeben |dependency-injection:passing-dependencies] und registrieren Sie die Callbacks, z. B. beim Start der Anwendung: + +```php +$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void { + $template->addFilter('money', fn($val) => number_format($val, 2) . ' €'); +}; +``` + -Übersetzung ------------ +Übersetzen +---------- -Wenn Sie eine mehrsprachige Anwendung programmieren, müssen Sie wahrscheinlich einige Texte in der Vorlage in verschiedenen Sprachen ausgeben. Zu diesem Zweck definiert das Nette Framework ein Übersetzungs-Interface [api:Nette\Localization\Translator], das nur eine Methode `translate()` hat. Diese nimmt die Nachricht `$message` entgegen, die normalerweise eine Zeichenkette ist, sowie beliebige weitere Parameter. Die Aufgabe besteht darin, die übersetzte Zeichenkette zurückzugeben. In Nette gibt es keine Standardimplementierung; Sie können je nach Bedarf aus mehreren fertigen Lösungen wählen, die Sie auf [Componette |https://componette.org/search/localization] finden. In deren Dokumentation erfahren Sie, wie Sie den Translator konfigurieren. +Wenn Sie eine mehrsprachige Anwendung programmieren, werden Sie manche Texte im Template in verschiedenen Sprachen ausgeben müssen. Das Nette Framework definiert dafür das Übersetzungs-Interface [api:Nette\Localization\Translator] mit der einzigen Methode `translate()`. Sie nimmt die Nachricht `$message` entgegen, die üblicherweise ein String ist, sowie beliebige weitere Parameter. Ihre Aufgabe ist es, den übersetzten String zurückzugeben. Nette enthält keine Standardimplementierung; Sie können nach Ihren Bedürfnissen aus mehreren fertigen Lösungen wählen, die auf [Componette |https://componette.org/search/localization] verfügbar sind. In deren Dokumentation erfahren Sie, wie der Translator konfiguriert wird. -Für Vorlagen kann ein Translator, den [wir uns übergeben lassen |dependency-injection:passing-dependencies], mit der Methode `setTranslator()` festgelegt werden: +Templates lassen sich mit einem Translator einrichten, den wir uns [übergeben lassen |dependency-injection:passing-dependencies], und zwar mit der Methode `setTranslator()`: ```php protected function beforeRender(): void @@ -279,7 +375,7 @@ protected function beforeRender(): void } ``` -Alternativ kann der Translator über die [Konfiguration |configuration#Latte-Templates] eingestellt werden: +Alternativ lässt sich der Translator über die [Konfiguration |configuration#Latte-Templates] setzen: ```neon latte: @@ -287,7 +383,7 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Danach kann der Translator beispielsweise als Filter `|translate` verwendet werden, einschließlich zusätzlicher Parameter, die an die Methode `translate()` übergeben werden (siehe `foo, bar`): +Der Translator lässt sich dann zum Beispiel als Filter `|translate` verwenden, samt weiteren Parametern, die an die Methode `translate()` übergeben werden (siehe `foo, bar`): ```latte
    {='Warenkorb'|translate} @@ -303,14 +399,14 @@ Oder als Unterstrich-Tag: {_$item, foo, bar} ``` -Für die Übersetzung eines Vorlagenabschnitts gibt es den paarweisen Tag `{translate}` (seit Latte 2.11, früher wurde der Tag `{_}` verwendet): +Für die Übersetzung eines Abschnitts des Templates gibt es den Paar-Tag `{translate}` (seit Latte 2.11, zuvor wurde der Tag `{_}` verwendet): ```latte -{translate}Bestellung{/translate} -{translate foo, bar}Bestellung{/translate} +{translate}Bestellen{/translate} +{translate foo, bar}Bestellen{/translate} ``` -Der Translator wird standardmäßig zur Laufzeit beim Rendern der Vorlage aufgerufen. Latte Version 3 kann jedoch alle statischen Texte bereits während der Kompilierung der Vorlage übersetzen. Dadurch wird Leistung gespart, da jede Zeichenkette nur einmal übersetzt wird und die resultierende Übersetzung in die kompilierte Form geschrieben wird. Im Cache-Verzeichnis entstehen so mehrere kompilierte Versionen der Vorlage, eine für jede Sprache. Dazu genügt es, die Sprache als zweiten Parameter anzugeben: +Der Translator wird normalerweise zur Laufzeit beim Rendern des Templates aufgerufen. Latte in Version 3 kann jedoch alle statischen Texte bereits während der Kompilierung des Templates übersetzen. Das spart Leistung, denn jeder String wird nur einmal übersetzt, und die entstandene Übersetzung wird in die kompilierte Form geschrieben. Im Cache-Verzeichnis entstehen dadurch mehrere kompilierte Versionen des Templates, eine für jede Sprache. Dazu genügt es, die Sprache als zweiten Parameter anzugeben: ```php protected function beforeRender(): void @@ -320,4 +416,4 @@ protected function beforeRender(): void } ``` -Mit statischem Text ist z.B. `{_'hello'}` oder `{translate}hello{/translate}` gemeint. Nicht-statische Texte, wie z.B. `{_$foo}`, werden weiterhin zur Laufzeit übersetzt. +Statischer Text bedeutet zum Beispiel `{_'hello'}` oder `{translate}hello{/translate}`. Nicht statische Texte wie `{_$foo}` werden weiterhin zur Laufzeit übersetzt. diff --git a/application/de/upgrading.texy b/application/de/upgrading.texy new file mode 100644 index 0000000000..a3a0914585 --- /dev/null +++ b/application/de/upgrading.texy @@ -0,0 +1,47 @@ +Upgrade +******* + + +Upgrade auf Version 3.0 +======================= + +Nette 3.0 ergänzt Typdeklarationen bei Methodenparametern und Rückgabewerten. Wenn Sie eine solche Methode in einer von Nette geerbten Klasse überschreiben (zum Beispiel in einem Presenter oder einer Komponente), müssen Sie dieselben Typdeklarationen ergänzen, sonst meldet PHP den Fehler "Declaration must be compatible". + +Das Interface `Nette\Application\IRouter` hat sich geändert. Die Methode `match()` gibt nun ein Array von Parametern zurück und `constructUrl()` nimmt eines entgegen, statt eines Objekts `Nette\Application\Request`. + +Nette prüft nun, ob jedes Signal vom selben Origin gesendet wird (also von derselben Domain und Subdomain). Diese Same-Origin-Policy ist ein kritischer Sicherheitsmechanismus, der mögliche Angriffsvektoren reduziert. Wenn Sie andere Origins zulassen wollen, ergänzen Sie bei der Handler-Methode die Annotation `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXy(): void +{ +} +``` + +Dasselbe gilt für das Absenden von Formularen. Wenn Sie das Absenden von anderen Origins zulassen wollen, gehen Sie so vor: + +```php +$form = new Nette\Application\UI\Form; +$form->allowCrossOrigin(); +``` + +Der Konstruktor von `Nette\ComponentModel\Component` wurde seit Jahren nicht mehr verwendet und in Version 3.0 entfernt. Es handelt sich um einen BC Break: Wenn Sie in einer Komponente oder einem Presenter, der von `Nette\Application\UI\Presenter` erbt, den Konstruktor des Vorfahren aufrufen, müssen Sie diesen Aufruf entfernen. + + +Upgrade auf Version 2.4 +======================= + +- `Route` und `SimpleRouter` erzeugen nun dasselbe Schema HTTP/HTTPS, über das die Website aufgerufen wurde. Eine Route, die ein bestimmtes Protokoll erfordert, lässt sich mit dem Schema definieren, z. B. `Route('http://domain.cz/')`. +- Bei Parametern der render/action-Methoden vom Typ bool (also mit dem Standardwert true oder false) und bei persistenten Parametern wird nun zwischen `false` und `null` unterschieden. Ist der Parameter nicht in der URL enthalten, ist sein Wert nun `null` (früher `false`). +- Die von `Presenter::getReflection()` zurückgegebene Klasse ist kein Nachfahre von `Nette\Reflection\ClassType` mehr, und `getReflection()->getMethod()` ist kein Nachfahre von `Nette\Reflection\Method` mehr. +- Das Flag `SECURED` und `Route::$defaultFlags` sind veraltet. + + +Upgrade auf Version 2.3 +======================= + +- Routes und Presenter-Namen sind **case-sensitive**. Nette warnt Sie, wenn Sie bei einem Presenter-Namen die falsche Groß-/Kleinschreibung verwenden; aus Performance-Gründen wird die Maske der Route nicht geprüft, überprüfen Sie sie daher selbst. +- `Route::addStyle()` und `Route::setStyleProperty()` sind veraltet und lösen nun `E_USER_DEPRECATED` aus. +- Die Template-Endung `.phtml` und die alte Link-Syntax werden nicht mehr unterstützt. diff --git a/application/en/@home.texy b/application/en/@home.texy index 30d983ea7d..6552aab1eb 100644 --- a/application/en/@home.texy +++ b/application/en/@home.texy @@ -58,13 +58,13 @@ Main Benefits - **Performance**: Smart cache, lazy loading of components - **Flexibility**: Easy URL modification even after application completion - **Components**: Unique system of reusable UI elements -- **Modern**: Full support for PHP 8.4+ and type system +- **Modern**: Full support for PHP 8.3+ and type system Getting Started --------------- -1. [How Applications Work? |how-it-works] - Understanding the basic architecture +1. [How Do Applications Work? |how-it-works] - Understanding the basic architecture 2. [Presenters |presenters] - Working with presenters and actions 3. [Templates |templates] - Creating templates in Latte 4. [Routing |routing] - Configuring URL addresses @@ -76,8 +76,8 @@ PHP Compatibility | version | compatible with PHP |-----------------------|------------------- -| Nette Application 4.0 | PHP 8.1 - 8.4 -| Nette Application 3.2 | PHP 8.1 - 8.4 +| Nette Application 3.3 | PHP 8.3 - 8.5 +| Nette Application 3.2 | PHP 8.1 - 8.5 | Nette Application 3.1 | PHP 7.2 - 8.3 | Nette Application 3.0 | PHP 7.1 - 8.0 | Nette Application 2.4 | PHP 5.6 - 8.0 diff --git a/application/en/@left-menu.texy b/application/en/@left-menu.texy index 0b0defb537..64ae948bd7 100644 --- a/application/en/@left-menu.texy +++ b/application/en/@left-menu.texy @@ -1,5 +1,6 @@ Nette Application ***************** +- [Overview |@home] - [How Do Applications Work? |how-it-works] - [Bootstrapping] - [Presenters] @@ -11,6 +12,7 @@ Nette Application - [AJAX & Snippets |ajax] - [Multiplier |multiplier] - [Configuration] +- [Upgrading] Further Reading diff --git a/application/en/ajax.texy b/application/en/ajax.texy index b65ca6adce..e6a0919e4a 100644 --- a/application/en/ajax.texy +++ b/application/en/ajax.texy @@ -35,7 +35,7 @@ If you want to send data in JSON format, use the [`sendJson()` |presenters#Sendi ```php public function actionExport(): void { - $this->sendJson($this->model->getData); + $this->sendJson($this->model->getData()); } ``` @@ -55,17 +55,17 @@ public function handleClick($param): void Snippets ======== -The most powerful tool offered by Nette for connecting the server with the client are snippets. With them, you can turn an ordinary application into an AJAX one with minimal effort and just a few lines of code. The Fifteen example demonstrates how it all works, and its code can be found on [GitHub |https://github.com/nette-examples/fifteen]. +Snippets are the most powerful tool Nette offers for connecting the server with the client. With them, you can turn an ordinary application into an AJAX one with minimal effort and just a few lines of code. The Fifteen example demonstrates how it all works, and its code can be found on [GitHub |https://github.com/nette-examples/fifteen]. Snippets allow you to update only parts of the page, instead of reloading the entire page. This is not only faster and more efficient but also provides a more comfortable user experience. Snippets might remind you of Hotwire for Ruby on Rails or Symfony UX Turbo. Interestingly, Nette introduced snippets 14 years earlier. -How do snippets work? When the page is first loaded (a non-AJAX request), the entire page, including all snippets, is loaded. When the user interacts with the page (e.g., clicks a button, submits a form, etc.), an AJAX request is initiated instead of reloading the entire page. The code in the presenter performs the action and decides which snippets need updating. Nette renders these snippets and sends them as a JSON payload containing an array with snippets. The handling code in the browser then inserts the received snippets back into the page. Thus, only the code of the changed snippets is transferred, saving bandwidth and speeding up loading compared to transferring the entire page content. +How do snippets work? When the page is first loaded (a non-AJAX request), the entire page, including all snippets, is loaded. When the user interacts with the page (e.g., clicks a button, submits a form, etc.), an AJAX request is initiated instead of reloading the entire page. The code in the presenter performs the action and decides which snippets need updating. Nette renders these snippets and sends them as a JSON payload containing an array with snippets. The handling code in the browser then inserts the received snippets back into the page. Thus, only the code of the changed snippets is transferred, saving bandwidth and speeding up loading compared to transferring the entire page content. If no snippet is invalidated using `redrawControl()`, Nette returns the entire page even for an AJAX request - snippets are sent only when something is invalidated. Naja ---- -To handle snippets on the browser side, the [Naja library |https://naja.js.org] is used. [Install it |https://naja.js.org/#/guide/01-install-setup-naja] as a Node.js package (for use with applications like Webpack, Rollup, Vite, Parcel, and others): +To handle snippets on the browser side, the [Naja library |https://naja.js.org] is used. [Install it |https://naja.js.org/#/guide/01-install-setup-naja] as a Node.js package (for use with bundlers like Webpack, Rollup, Vite, Parcel, and others): ```shell npm install naja @@ -74,7 +74,7 @@ npm install naja …or insert it directly into the page template: ```latte - + ``` First, you need to [initialize |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] the library: @@ -121,6 +121,8 @@ Nette allows for even finer control over what needs to be redrawn. The method ca $this->redrawControl('header'); ``` +You can also cancel a pending invalidation using the second parameter `$redraw`: calling `$this->redrawControl('header', redraw: false)` marks the snippet as not needing a redraw. The full signature is `redrawControl(?string $snippet = null, bool $redraw = true)`. + Snippets in Latte ----------------- @@ -155,6 +157,8 @@ Snippet names can also be expressions: {/foreach} ``` +On its own, this is a non-functional intermediate step: rendered outside a static `{snippet}` or `{snippetArea}`, a dynamic snippet triggers an `E_USER_WARNING` with the message *Dynamic snippets are allowed only inside static snippet/snippetArea.* We fix that below. + This creates several snippets like `item-0`, `item-1`, etc. If we were to directly invalidate a dynamic snippet (e.g., `item-1`), nothing would be redrawn. The reason is that snippets truly function as excerpts, and only they themselves are rendered directly. However, in the template, there is technically no snippet named `item-1`. It only comes into existence when the code surrounding the snippet, i.e., the foreach loop, is executed. Therefore, we mark the part of the template that needs to be executed using the `{snippetArea}` tag: ```latte @@ -226,6 +230,12 @@ public function actionDelete(int $id): void ``` +Redirecting +----------- + +During an AJAX request, the `redirect()` and `redirectUrl()` methods do not send an HTTP redirect. Instead, they write the target URL into the payload (the data object sent in the AJAX response), specifically its `payload.redirect` property, and send it; the actual redirection is then performed by the client-side library (Naja). + + Passing Parameters ================== diff --git a/application/en/bootstrapping.texy b/application/en/bootstrapping.texy index 8ee9c78236..a03335c55b 100644 --- a/application/en/bootstrapping.texy +++ b/application/en/bootstrapping.texy @@ -16,6 +16,9 @@ Bootstrapping is the process of initializing the application environment, creati Applications, whether web-based or scripts run from the command line, begin their execution with some form of environment initialization. In the old days, a file named perhaps `include.inc.php` was responsible for this, included by the initial file. In modern Nette applications, it has been replaced by the `Bootstrap` class, which, as part of the application, can be found in the `app/Bootstrap.php` file. It might look like this, for example: ```php +namespace App; + +use Nette; use Nette\Bootstrap\Configurator; class Bootstrap @@ -78,6 +81,9 @@ $application = $container->getByType(Nette\Application\Application::class); $application->run(); ``` +.[note] +The `$application` object emits [events |nette:glossary#Events] as it processes the request - `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown`, and `onError` (on an unhandled exception). You can attach handlers to them, which is handy for logging or application-wide monitoring. + As you can see, the [api:Nette\Bootstrap\Configurator] class helps with setting up the environment and creating the dependency injection (DI) container. We will now introduce it in more detail. @@ -124,6 +130,12 @@ $this->configurator->setDebugMode(false); Note that the value `true` forces development mode on, which should **never** happen on a production server. +Autodetection is handled internally by the static method `Configurator::detectDebugMode()`, which you can also call yourself, for example to detect development mode outside the configurator. It accepts an optional whitelist of IP addresses or computer names and returns whether the current request should run in development mode: + +```php +$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200'); +``` + Debugging Tool Tracy ==================== @@ -181,6 +193,8 @@ Configuration files are usually written in the [NEON format |neon:format]. In a .[tip] In development mode, the container is automatically updated whenever the code or configuration files change. In production mode, it is generated only once, and changes are not checked to maximize performance. +While `createContainer()` builds the container and returns its instance, the `loadContainer()` method returns only the name of the generated container class, which you can then instantiate yourself. This is useful in advanced scenarios. + Configuration files are loaded using `addConfig()`: ```php @@ -208,7 +222,7 @@ If items with the same keys appear in configuration files, they will be overwrit Static Parameters ----------------- -Parameters used in configuration files can be defined [in the `parameters` section |dependency-injection:configuration#Parameters] and also passed (or overridden) using the `addStaticParameters()` method (it has an alias `addParameters()`). It is important that different parameter values will cause the generation of additional DI containers, i.e., additional classes. +Parameters used in configuration files can be defined [in the `parameters` section |dependency-injection:configuration#Parameters] and also passed (or overridden) using the `addStaticParameters()` method (whose older, now deprecated alias is `addParameters()`). It is important that different parameter values will cause the generation of additional DI containers, i.e., additional classes. ```php $this->configurator->addStaticParameters([ @@ -242,14 +256,14 @@ $this->configurator->addDynamicParameters([ Default Parameters ------------------ -You can use these static parameters in the configuration files: +You can use these parameters in the configuration files: - `%appDir%` is the absolute path to the directory containing the `Bootstrap.php` file - `%wwwDir%` is the absolute path to the directory containing the entry file `index.php` - `%tempDir%` is the absolute path to the directory for temporary files - `%vendorDir%` is the absolute path to the directory where Composer installs libraries - `%rootDir%` is the absolute path to the root directory of the project -- `%baseUrl%` is the absolute URL to the root directory +- `%baseUrl%` is the absolute URL to the root directory (a dynamic parameter resolved at runtime) - `%debugMode%` indicates whether the application is in debug mode - `%consoleMode%` indicates whether the request came through the command line @@ -278,7 +292,7 @@ $this->configurator->addServices([ Different Environments ====================== -Feel free to modify the `Bootstrap` class according to your needs. You can add parameters to the `bootWebApplication()` method to distinguish between web projects. Or we can add other methods, such as `bootTestEnvironment()` which initializes the environment for unit tests, `bootConsoleApplication()` for scripts called from the command line, etc. +Feel free to modify the `Bootstrap` class according to your needs. You can add parameters to the `bootWebApplication()` method to distinguish between web projects. Or you can add other methods, such as `bootTestEnvironment()` which initializes the environment for unit tests, `bootConsoleApplication()` for scripts called from the command line, etc. ```php public function bootTestEnvironment(): Nette\DI\Container diff --git a/application/en/components.texy b/application/en/components.texy index 5bfc431e45..28bbf8dac7 100644 --- a/application/en/components.texy +++ b/application/en/components.texy @@ -31,7 +31,7 @@ class DefaultPresenter extends Nette\Application\UI\Presenter protected function createComponentPoll(): PollControl { $poll = new PollControl; - $poll->items = $this->item; + $poll->items = $this->items; return $poll; } } @@ -59,6 +59,11 @@ In the template, it is possible to render a component using the [{control} |#Ren {control poll} ``` +.[tip] +To dynamically create a variable number of components, use [Multiplier |multiplier]. + +Factory methods `createComponent()` don't work only in presenters. You can nest a component inside another component the same way, composing them into a tree - handy for example for a separately rendered form inside a component. + Hollywood Style =============== @@ -144,7 +149,7 @@ $control->getComponent('poll')->renderPaginator(123, 'hello'); The `getComponent()` method returns the `poll` component, and the `render()` method, or `renderPaginator()` if a different rendering method is specified in the tag after the colon, is called on this component. .[caution] -Beware, if **`=>`** appears anywhere in the parameters, all parameters will be wrapped in an array and passed as the first argument: +Beware, if **`=>`** appears in the parameters outside square brackets, all parameters will be wrapped in an array and passed as the first argument: ```latte {control poll, id: 123, message: 'hello'} @@ -214,7 +219,7 @@ Flash Messages A component has its own storage for flash messages, independent of the presenter. These are messages that, for example, inform about the result of an operation. An important feature of flash messages is that they are available in the template even after redirection. Even after being displayed, they remain active for another 30 seconds - for example, in case the user refreshes the page due to a transmission error - the message won't disappear immediately. -Sending is handled by the [flashMessage |api:Nette\Application\UI\Control::flashMessage()] method. The first parameter is the message text or an `stdClass` object representing the message. The optional second parameter is its type (error, warning, info, etc.). The `flashMessage()` method returns an instance of the flash message as an `stdClass` object, to which further information can be added. +Sending is handled by the [flashMessage |api:Nette\Application\UI\Control::flashMessage()] method. The first parameter is the message text (`string`, `Stringable`) or an `stdClass` object representing the message. The optional second parameter is its type (error, warning, info, etc.). The `flashMessage()` method returns an instance of the flash message as an `stdClass` object, to which further information can be added. ```php $this->flashMessage('Item was deleted.'); @@ -230,8 +235,8 @@ These messages are available to the template in the `$flashes` variable as `stdC ``` -Redirection After a Signal -========================== +Redirection After Processing a Signal +===================================== Processing a component's signal is often followed by a redirect. This is similar to forms - after submitting them, we also redirect to prevent data resubmission if the page is refreshed in the browser. @@ -259,7 +264,7 @@ Persistent parameters are used to maintain state in components across different For example, you have a component for content pagination. There might be several such components on a page. And we want all components to remain on their current page after clicking a link. Therefore, we make the page number (`page`) a persistent parameter. -Creating a persistent parameter in Nette is extremely simple. Just create a public property and mark it with the attribute: (previously `/** @persistent */` was used) +Creating a persistent parameter in Nette is extremely simple. Just create a public property and mark it with the attribute (previously `/** @persistent */` was used): ```php use Nette\Application\Attributes\Persistent; // this line is important @@ -289,12 +294,12 @@ Or it can be *reset*, i.e., removed from the URL. It will then assume its defaul Persistent Components ===================== -Not only parameters but also components can be persistent. For such a component, its persistent parameters are transferred even between different actions of the presenter or between multiple presenters. Persistent components are marked with an annotation in the presenter class. For example, we mark the `calendar` and `poll` components like this: +Not only parameters but also components can be persistent. Their persistent parameters are then transferred even between different actions of the presenter or between multiple presenters. We mark persistent components with an attribute on the presenter class. For example, we mark the `calendar` and `poll` components like this: ```php -/** - * @persistent(calendar, poll) - */ +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] class DefaultPresenter extends Nette\Application\UI\Presenter { } @@ -302,12 +307,12 @@ class DefaultPresenter extends Nette\Application\UI\Presenter Subcomponents within these components do not need to be marked; they become persistent too. -In PHP 8, you can also use attributes to mark persistent components: +The older annotation `@persistent` still works, but it is deprecated and triggers a warning: ```php -use Nette\Application\Attributes\Persistent; - -#[Persistent('calendar', 'poll')] +/** + * @persistent(calendar, poll) + */ class DefaultPresenter extends Nette\Application\UI\Presenter { } @@ -455,24 +460,38 @@ class PaginatingControl extends Control The opposite process, i.e., collecting values from persistent properties, is handled by the `saveState()` method. +Connecting to the Presenter +--------------------------- + +At the moment a component becomes part of the presenter hierarchy, its callbacks stored in the array `$onAnchor` are invoked. From that point on, the component has the presenter available, can safely create links, read persistent parameters, and so on. + +```php +$control->onAnchor[] = function ($control): void { + // the component now has the presenter available +}; +``` + + Signals in Depth ---------------- -A signal causes the page to reload exactly like the original request (except when called via AJAX) and invokes the `signalReceived($signal)` method, whose default implementation in the `Nette\Application\UI\Component` class attempts to call a method composed of the words `handle{Signal}`. Further processing is up to the given object. Objects inheriting from `Component` (i.e., `Control` and `Presenter`) react by trying to call the `handle{Signal}` method with the appropriate parameters. +A signal causes the page to reload exactly like the original request (except when called via AJAX) and invokes the `signalReceived($signal)` method, whose default implementation in the `Nette\Application\UI\Component` class attempts to call a method composed of the words `handle`. Further processing is up to the given object. Objects inheriting from `Component` (i.e., `Control` and `Presenter`) react by trying to call the `handle` method with the appropriate parameters. + +In other words: the definition of the `handle` function is taken, along with all parameters that came with the request, and parameters from the URL are assigned to the arguments by name, and an attempt is made to call the method. For example, the value from the `id` parameter in the URL is passed as the `$id` argument, `something` from the URL is passed as `$something`, etc. And if the method does not exist, the `signalReceived` method throws an [exception |api:Nette\Application\UI\BadSignalException]. -In other words: the definition of the `handle{Signal}` function is taken, along with all parameters that came with the request, and parameters from the URL are assigned to the arguments by name, and an attempt is made to call the method. For example, the value from the `id` parameter in the URL is passed as the `$id` argument, `something` from the URL is passed as `$something`, etc. And if the method does not exist, the `signalReceived` method throws an [exception |api:Nette\Application\UI\BadSignalException]. +Besides the parameters from the URL, a signal also reads the parameters sent in the **POST body of the request**. This comes in handy because signals are often invoked via JavaScript, where it is natural to send data using the POST method. However, if a parameter of the same name arrives both from the URL and from the POST body, the value **from the URL takes precedence**. Therefore, avoid giving a POST field the same name as a URL or route parameter, otherwise the URL value would silently override it. The signal parameters share a common space with the action and persistent parameters, see [Shared Parameter Space |presenters#Shared Parameter Space]. A signal can be received by any component, presenter, or object that implements the `SignalReceiver` interface and is connected to the component tree. The main recipients of signals will be `Presenters` and visual components inheriting from `Control`. A signal is intended to serve as a sign for an object that it should do something - a poll should count a vote from the user, a news block should expand and display twice as many news items, a form has been submitted and should process data, and so on. -The URL for a signal is created using the [Component::link() |api:Nette\Application\UI\Component::link()] method. As the `$destination` parameter, we pass the string `{signal}!` and as `$args`, an array of arguments we want to pass to the signal. The signal is always called on the current presenter and action with the current parameters; the signal parameters are just added. Additionally, the **parameter `?do` which specifies the signal** is added right at the beginning. +The URL for a signal is created using the [Component::link() |api:Nette\Application\UI\Component::link()] method. As the `$destination` parameter, we pass the string `{signal}!` and as `$args`, an array of arguments we want to pass to the signal. The signal is always called on the current presenter and action with the current parameters; the signal parameters are just added. Additionally, the **parameter `?do` which specifies the signal** is added. Its format is either `{signal}` or `{signalReceiver}-{signal}`. `{signalReceiver}` is the name of the component in the presenter. Therefore, a hyphen cannot be used in the component name - it is used to separate the component name and the signal, although it is possible to nest multiple components this way. The [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] method checks whether the component (first argument) is the recipient of the signal (second argument). The second argument can be omitted - then it checks if the component is the recipient of any signal. If the second parameter is set to `true`, it verifies whether the specified component or any of its descendants is the recipient. -At any stage preceding `handle{Signal}`, we can execute the signal manually by calling the [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] method, which takes care of handling the signal - it takes the component identified as the signal recipient (if no recipient is specified, it is the presenter itself) and sends the signal to it. +At any stage preceding `handle`, we can execute the signal manually by calling the [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] method, which takes care of handling the signal - it takes the component identified as the signal recipient (if no recipient is specified, it is the presenter itself) and sends the signal to it. Example: diff --git a/application/en/configuration.texy b/application/en/configuration.texy index a7c91cb890..09fed449ef 100644 --- a/application/en/configuration.texy +++ b/application/en/configuration.texy @@ -11,11 +11,11 @@ Application ```neon application: # show the "Nette Application" panel in Tracy BlueScreen? - debugger: ... # (bool) defaults to true + debugger: ... # (bool) enabled if Tracy is available - # will the error-presenter be called on error? - # effective only in development mode - catchExceptions: ... # (bool) defaults to true + # in production, exceptions are always handled by the error-presenter; + # this option only enables that behavior in development mode too + catchExceptions: ... # (bool) defaults to false - i.e. off in dev, always on in production # name of the error-presenter errorPresenter: Error # (string|array) defaults to 'Nette:Error' @@ -40,6 +40,8 @@ application: 5xx: Error5xx # for other exceptions ``` +Splitting them is useful because both situations are fundamentally different. A `BadRequestException` (codes 4xx) means that the application is fine and only the visitor asked for something that does not exist. Therefore, you can use a full-featured presenter that displays a friendly message in the layout of your website. On the contrary, a 5xx error means that something in the application has broken and you do not know what. Keep the 5xx presenter as minimal as possible, so that nothing else can fail while rendering it - ideally, it should not touch the database, the layout, or the logged-in user. + The `silentLinks` option determines how Nette behaves in development mode when link generation fails (for example, because the presenter does not exist, etc.). The default value `false` means that Nette triggers an `E_USER_WARNING` error. Setting it to `true` suppresses this error message. In a production environment, `E_USER_WARNING` is always triggered. This behavior can also be influenced by setting the presenter variable [$invalidLinkMode |creating-links#Invalid Links]. [Aliases simplify referencing |creating-links#Aliases] frequently used presenters. @@ -73,7 +75,7 @@ application: - %vendorDir%/mymodule ``` -Directory scanning can be turned off by setting the value to false. We do not recommend completely suppressing the automatic addition of presenters, as this will reduce application performance. +Directory scanning can be turned off by setting the value to `false`. Presenters are then no longer registered as services, so they can't be adjusted via the [decorator |dependency-injection:configuration#Decorator] section and their creation is slower. We therefore do not recommend completely suppressing automatic registration, as it will reduce application performance. Latte Templates @@ -84,7 +86,7 @@ This setting globally affects the behavior of Latte in components and presenters ```neon latte: # show the Latte panel in the Tracy Bar for the main template (true) or for all components (all)? - debugger: ... # (true|false|'all') defaults to true + debugger: ... # (true|false|'all') enabled if Tracy is available (debug mode only) # generate templates with declare(strict_types=1) header strictTypes: ... # (bool) defaults to false @@ -92,6 +94,12 @@ latte: # enable [strict parser mode |latte:develop#strict mode] strictParsing: ... # (bool) default is false + # limits the scope of variables to the loop body + scopedLoopVariables: ... # (bool) default is false + + # removes indentation caused by nesting in paired tags + dedent: ... # (bool) default is false + # enable [checking of generated code |latte:develop#Checking Generated Code] phpLinter: ... # (string) default is null @@ -102,7 +110,7 @@ latte: templateClass: App\MyTemplateClass # defaults to Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -If you are using Latte version 3, you can add new [extensions |latte:extending-latte#Latte Extension] using: +You can add new [extensions |latte:extending-latte#Latte Extension] using: ```neon latte: @@ -110,20 +118,6 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -If you are using Latte version 2, you can register new tags either by specifying the class name or by referencing a service. By default, the `install()` method is called, but this can be changed by specifying the name of another method: - -```neon -latte: - # registration of custom Latte tags - macros: - - App\MyLatteMacros::register # static method, classname or callable - - @App\MyLatteMacrosFactory # service with install() method - - @App\MyLatteMacrosFactory::register # service with register() method - -services: - - App\MyLatteMacrosFactory -``` - Routing ======= @@ -133,7 +127,7 @@ Basic settings: ```neon routing: # show the routing panel in Tracy Bar? - debugger: ... # (bool) defaults to true + debugger: ... # (bool) enabled if Tracy is available (debug mode only) # serialize the router into the DI container cache: ... # (bool) defaults to false @@ -185,7 +179,8 @@ These services are added to the DI container: |----------------------------|---------------------------------------------------|----------------------------------------- | `application.application` | [api:Nette\Application\Application] | the [application runner |how-it-works#Nette Application] | `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] -| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | presenter factory +| `application.presenterFactory` | [api:Nette\Application\IPresenterFactory] | presenter factory | `application.###` | [api:Nette\Application\UI\Presenter] | individual presenters +| `routing.router` | [api:Nette\Routing\Router] | router | `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | factory for `Latte\Engine` object | `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | factory for [`$this->template` |templates] diff --git a/application/en/creating-links.texy b/application/en/creating-links.texy index f831b5e018..c841e96c2b 100644 --- a/application/en/creating-links.texy +++ b/application/en/creating-links.texy @@ -40,11 +40,11 @@ It is also possible to pass named parameters. The following link passes the para If the `ProductPresenter::renderShow()` method does not have `$lang` in its signature, it can retrieve the parameter's value using `$lang = $this->getParameter('lang')` or from a [property |presenters#Request Parameters]. -If the parameters are stored in an array, they can be expanded using the `...` operator (or the `(expand)` operator in Latte 2.x): +If the parameters are stored in an array, they can be expanded using the `...` operator: ```latte {var $args = [$product->id, lang => en]} -product detail +product detail ``` So-called [persistent parameters |presenters#Persistent Parameters] are also automatically passed in links. @@ -73,6 +73,14 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'en']); Links can also be created without a presenter, using the [#LinkGenerator] and its `link()` method. +Sometimes you need to create a link now but generate the actual URL only later. For that, there is the `lazyLink()` method, which returns a `Nette\Application\UI\Link` object. The advantage is that you can pass this object on, for example into a template, and before it is rendered you can still adjust its parameters using the `setParameter()` method. The URL itself is assembled only when the object is converted to a string: + +```php +$link = $this->lazyLink('Product:show', $id); +// ... +echo $link; // the URL is generated only here +``` + Links to Presenter ================== @@ -122,6 +130,13 @@ We can link to a specific part of the page via a so-called fragment after the ha link to Home:default and fragment #main ``` +.{data-version:3.3.0} +The fragment can also be set dynamically as an argument with the `#` key. Its value is automatically encoded and takes precedence over the fragment specified in the destination: + +```php +$this->link('Home:default', ['#' => $fragment]); +``` + Absolute Paths ============== @@ -181,8 +196,8 @@ To determine if we are in a specific module or its submodule, use the `isModuleC ``` -Changing Link Base .{data-version:v3.2.7} -========================================= +Changing Link Base .{data-version:3.2.7} +======================================== By default, relative links are derived from the current presenter. This can be changed using `{linkBase}`: @@ -194,12 +209,13 @@ By default, relative links are derived from the current presenter. This can be c The link will lead to `Admin:Dashboard:Product:show`. Only relative links are affected - absolute links starting with a colon and links to the current presenter (`this`, `show`) remain unchanged. `{linkBase}` applies to the entire template and is especially useful in layout templates, where it ensures consistent links regardless of the calling presenter. +The tag must be placed at the beginning of the template, otherwise it throws a `CompileException`. Links to Signal =============== -The target of a link doesn't have to be just a presenter and action, but also a [signal |components#Signal] (they call the `handle()` method). Then the syntax is as follows: +The target of a link doesn't have to be just a presenter and action, but also a [signal |components#Signal] (it calls the `handle()` method). Then the syntax is as follows: ``` [//] [sub-component:]signal! [#fragment] @@ -240,8 +256,8 @@ $this->getPresenter()->link('Home:default') ``` -Aliases .{data-version:v3.2.2} -============================== +Aliases .{data-version:3.2.3} +============================= Sometimes it can be useful to assign an easily memorable alias to a Presenter:action pair. For example, naming the homepage `Front:Home:default` simply as `home` or `Admin:Dashboard:default` as `admin`. @@ -267,7 +283,7 @@ They are also supported in all methods that work with links, such as `redirect() Invalid Links ============= -It may happen that we create an invalid link - either because it leads to a non-existent presenter, or because it passes more parameters than the target method accepts in its signature, or when a URL cannot be generated for the target action. How to handle invalid links is determined by the static variable `Presenter::$invalidLinkMode`. It can take a combination of these values (constants): +It may happen that we create an invalid link - either because it leads to a non-existent presenter, or because it passes more parameters than the target method accepts in its signature, or when a URL cannot be generated for the target action. How to handle invalid links is set in the presenter using `$this->invalidLinkMode`. It can take a combination of these values (constants): - `Presenter::InvalidLinkSilent` - silent mode, returns the character # as the URL - `Presenter::InvalidLinkWarning` - an E_USER_WARNING warning is thrown, which will be logged in production mode, but will not interrupt script execution @@ -283,7 +299,7 @@ a[href^="#error:"] { } ``` -If we do not want warnings to be produced in the development environment, we can set the silent mode directly in the [configuration|configuration]. +If we do not want warnings to be produced in the development environment, we can suppress them directly in the [configuration|configuration]. ```neon application: @@ -294,7 +310,7 @@ application: LinkGenerator ============= -How to create links with similar comfort as the `link()` method, but without the presence of a presenter? That's what [api:Nette\Application\LinkGenerator] is for. +How to create links with similar convenience to the `link()` method, but without the presence of a presenter? That's what [api:Nette\Application\LinkGenerator] is for. LinkGenerator is a service that you can have passed via the constructor and then create links using its `link()` method. diff --git a/application/en/directory-structure.texy b/application/en/directory-structure.texy index a94b6148c7..9f837dc25f 100644 --- a/application/en/directory-structure.texy +++ b/application/en/directory-structure.texy @@ -206,7 +206,7 @@ Over time, more feed types are added, and we need more logic for them... No prob └── ebay.latte ← feed for eBay \-- -This transformation is completely smooth - just create new subfolders, divide the code into them and update links (e.g. from `Export:feed` to `Export:Feed:amazon`). Thanks to this, we can gradually expand the structure as needed, the nesting level is not limited in any way. +This transformation is completely smooth - just create new subfolders, divide the code into them and update links (e.g. from `Export:feed` to `Export:Feed:amazon`). Thanks to this, we can gradually expand the structure as needed; the nesting level is not limited in any way. For example, if in the administration you have many presenters related to order management, such as `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., you can create a module (folder) named `Order` for better organization, which will contain (folders for) presenters `Detail`, `Edit`, `Dispatch`, and others. @@ -420,7 +420,7 @@ Example: What belongs in the model and what in command scripts? For example, the logic for sending a single email is part of the model, while the bulk sending of thousands of emails belongs in `Tasks/`. -Tasks are usually [run from the command line |https://blog.nette.org/en/cli-scripts-in-nette-application] or via cron. They can also be run via an HTTP request, but security must be considered. The presenter that runs the task needs to be secured, for example, only for logged-in users or with a strong token and access from allowed IP addresses. For long-running tasks, it is necessary to increase the script time limit and use `session_write_close()` to avoid locking the session. +Tasks are usually run from the command line or via cron: the script in `bin/` creates the DI container using the [bootConsoleApplication() |bootstrapping#Different Environments] method and pulls the required service from it. They can also be run via an HTTP request, but security must be considered. The presenter that runs the task needs to be secured, for example, only for logged-in users or with a strong token and access from allowed IP addresses. For long-running tasks, it is necessary to increase the script time limit and use `session_write_close()` to avoid locking the session. Other Possible Directories @@ -470,7 +470,7 @@ Presenter Mapping Mapping defines the rules for deriving the class name from the presenter name. We specify them in the [configuration|configuration] under the key `application › mapping`. -On this page, we have shown that we place presenters in the `app/Presentation` folder (or `app/UI`). We must inform Nette of this convention in the configuration file. One line is sufficient: +On this page, we have shown that we place presenters in the `app/Presentation` folder (or `app/UI`). Since Nette Application 3.3, this is the default convention that does not need to be configured. If you use a different structure or want to specify the mapping explicitly, the default setting corresponds to this line: ```neon application: @@ -486,7 +486,7 @@ application: Mapping works by replacing the asterisk in the mask `App\Presentation\*Presenter` with the presenter name `Home`, resulting in the final class name `App\Presentation\HomePresenter`. Simple! -However, as you see in the examples in this and other chapters, we place presenter classes in eponymous subdirectories, for example, the `Home` presenter maps to the class `App\Presentation\Home\HomePresenter`. We achieve this by using double asterisks `**` (requires Nette Application 3.2): +However, as you see in the examples in this and other chapters, we place presenter classes in eponymous subdirectories, for example, the `Home` presenter maps to the class `App\Presentation\Home\HomePresenter`. We achieve this by using double asterisks `**` (requires Nette Application 3.2.3): ```neon application: @@ -516,7 +516,7 @@ application: It also works for deeper nested directory structures, such as the presenter `Admin:User:Edit`, where the segment with the asterisk repeats for each module level, resulting in the class `App\Presentation\Admin\User\Edit\EditPresenter`. -An alternative notation is to use an array consisting of three segments instead of a string. This notation is equivalent to the previous one: +An alternative notation is to use an array consisting of three segments instead of a string. For the examples shown above, this notation is equivalent to the previous one: ```neon application: diff --git a/application/en/how-it-works.texy b/application/en/how-it-works.texy index 5978b7abe3..0f2a627fdc 100644 --- a/application/en/how-it-works.texy +++ b/application/en/how-it-works.texy @@ -177,7 +177,7 @@ DI Container and Configuration The DI container, or object factory, is the heart of the entire application. -Don't worry, it's not some magical black box, as the preceding lines might suggest. In reality, it's a rather mundane PHP class generated by Nette and stored in the cache directory. It contains many methods named like `createServiceAbcd()`, each capable of creating and returning a specific object. Yes, there's also a `createServiceApplication()` method that produces the `Nette\Application\Application` instance we needed in `index.php` to run the application. There are also methods for creating individual presenters, and so on. +Don't worry, it's not some magical black box, as the preceding lines might suggest. In reality, it's a rather mundane PHP class generated by Nette and stored in the cache directory. It contains many methods named like `createServiceAbcd()`, each capable of creating and returning a specific object. Yes, there's also a `createServiceApplication__application()` method that produces the `Nette\Application\Application` instance we needed in `index.php` to run the application. There are also methods for creating individual presenters, and so on. The objects created by the DI container are, for some reason, called services. diff --git a/application/en/multiplier.texy b/application/en/multiplier.texy index d015e82caa..3f8e6dcbac 100644 --- a/application/en/multiplier.texy +++ b/application/en/multiplier.texy @@ -46,12 +46,12 @@ During the first call of point **1**, the `shopForm` component doesn't exist yet In the next iteration of the foreach loop, the `createComponentShopForm` method will not be called again (as the component already exists). However, because we are looking for a different child (since `$item->id` will be different in each iteration), the anonymous function will be called again, returning a new form. -The only thing left is to ensure that the form adds the correct product to the cart - currently, the form is identical for every product. A feature of Multiplier (and generally of any component factory in Nette Framework) helps us here: every factory receives the name of the component being created as its first argument. In our case, this will be `$item->id`, which is precisely the information we need. So, we just need to slightly modify the form creation: +The only thing left is to ensure that the form adds the correct product to the cart - currently, the form is identical for every product. A feature of Multiplier (and generally of any component factory in Nette Framework) helps us here: every factory receives the name of the component being created as its first argument. Additionally, a Multiplier factory receives the Multiplier instance itself as its second argument. In our case, the first argument will be `$item->id`, which is precisely the information we need. So, we just need to slightly modify the form creation: ```php protected function createComponentShopForm(): Multiplier { - return new Multiplier(function ($itemId) { + return new Multiplier(function (string $itemId) { $form = new Nette\Application\UI\Form; $form->addInteger('amount', 'Amount:') ->setRequired(); diff --git a/application/en/presenters.texy b/application/en/presenters.texy index 8ca73045ff..15e69e8988 100644 --- a/application/en/presenters.texy +++ b/application/en/presenters.texy @@ -60,6 +60,9 @@ Similar to the `render()` method. While `render()` is intended to pr It's important that `action()` is called *before* `render()`. This allows us to potentially change the course of the request within the action method, for instance, by changing the template that will be rendered or even the `render()` method that will be called, using `setView('otherView')`. +.{data-version:3.2.3} +You can even switch to an entirely different action using the `switch('otherAction')` method. It aborts the current method and instead runs the new action's `action()` and `render()` methods (and turns off automatic [canonicalization|#Canonization]). The request itself continues; only the currently running method is interrupted. + Parameters from the request are passed to the method. It's possible and recommended to specify types for these parameters, e.g., `actionShow(int $id, ?string $slug = null)`. If the `id` parameter is missing or is not an integer, the presenter returns a [404 error |#Error 404 etc] and terminates. @@ -105,6 +108,26 @@ The `afterRender` method, as the name suggests again, is called after every `ren Called at the end of the presenter's life cycle. +Events +------ + +In addition to the `startup()`, `beforeRender()`, and `shutdown()` methods, which are called as part of the presenter's life cycle, other functions can be defined to be called automatically. The presenter defines so-called [events |nette:glossary#Events], and you add their handlers to the `$onStartup`, `$onRender`, and `$onShutdown` arrays. + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` + +Handlers in the `$onStartup` array are called just before the `startup()` method, `$onRender` handlers between `beforeRender()` and `render()`, and finally `$onShutdown` handlers just before `shutdown()`. + + **A piece of advice before we continue:** As you can see, a presenter can handle multiple actions/views, meaning it can have multiple `render()` methods. However, we recommend designing presenters with one or as few actions as possible. @@ -207,7 +230,7 @@ In the template, these messages are available in the `$flashes` variable as `std Error 404 etc. ============== -If the request cannot be fulfilled, for example, because the article we want to display doesn't exist in the database, we throw a 404 error using the `error(?string $message = null, int $httpCode = 404)` method. +If the request cannot be fulfilled, for example, because the article we want to display doesn't exist in the database, we throw a 404 error using the `error(string $message = '', int $httpCode = 404)` method. ```php public function renderShow(int $id): void @@ -226,7 +249,7 @@ The HTTP error code can be passed as the second parameter; the default is 404. T Sending JSON ============ -Example of an action method that sends data in JSON format and terminates the presenter: +The `sendJson($data)` method encodes the given data into JSON, sends it as the HTTP response, and terminates the presenter. Example: ```php public function actionData(): void @@ -270,7 +293,7 @@ Persistent parameters are used to maintain state across different requests. Thei An example use case? Imagine you have a multilingual application. The current language is a parameter that must always be part of the URL. But it would be incredibly tedious to include it in every link. So, you make it a persistent parameter `lang`, and it will be carried along automatically. Neat! -Creating a persistent parameter in Nette is extremely simple. Just create a public property and mark it with the attribute: (previously, `/** @persistent */` was used) +Creating a persistent parameter in Nette is extremely simple. Just create a public property and mark it with the attribute (previously, `/** @persistent */` was used): ```php use Nette\Application\Attributes\Persistent; // this line is important @@ -317,6 +340,26 @@ Alternatively, it can be *reset*, i.e., removed from the URL. It will then assum ``` +Shared Parameter Space +====================== + +The request parameters, [persistent parameters |#Persistent Parameters], and the parameters of the `action`, `render`, and `handle` (signal) methods all share a single space, where each is identified by its name. If the same name appears in more than one of them, they refer to one and the same value. + +This is often used to advantage. For example, a persistent parameter `lang` and the `$lang` argument of an action or signal method are one and the same - you can read the current value of a persistent parameter simply by listing it in the method signature: + +```php +#[Persistent] +public string $lang; + +public function handleSearch(string $query, string $lang): void +{ + // $lang holds the current value of the persistent parameter lang +} +``` + +Because this space is shared, keep parameter names unique unless you deliberately want them to share a value. This applies to signals as well, which additionally read parameters from the request's POST body, see [Signals in Depth |components#Signals in Depth]. + + Interactive Components ====================== @@ -324,7 +367,7 @@ Presenters have a built-in component system. Components are separate reusable un How are components embedded into presenters and subsequently used? You'll learn this in the [Components |components] chapter. You'll even find out what they have in common with Hollywood. -And where can I get components? On [Componette |https://componette.org/search/component], you'll find open-source components and many other add-ons for Nette, contributed by volunteers from the framework community. +And where can you get components? On [Componette |https://componette.org/search/component], you'll find open-source components and many other add-ons for Nette, contributed by volunteers from the framework community. Going Deeper @@ -366,7 +409,7 @@ The request handled by the presenter is a [api:Nette\Application\Request] object The current request can be saved to the session or, conversely, restored from it and have the presenter execute it again. This is useful, for example, when a user is filling out a form and their login session expires. To avoid data loss, before redirecting to the login page, we save the current request to the session using `$reqId = $this->storeRequest()`. This returns its identifier as a short string, which we then pass as a parameter to the login presenter. -After logging in, we call the `$this->restoreRequest($reqId)` method, which retrieves the request from the session and forwards to it. The method verifies that the request was created by the same user who is now logged in. If a different user logs in or the key is invalid, it does nothing, and the program continues as usual. +After logging in, we call the `$this->restoreRequest($reqId)` method, which retrieves the request from the session. POST requests are forwarded to it, while the others (GET) are redirected to the request's URL. The method verifies that the request was created by the same user who is now logged in. If a different user logs in or the key is invalid, it does nothing, and the program continues as usual. See the guide [How to Return to a Previous Page |best-practices:restore-request]. @@ -393,25 +436,7 @@ public function actionShow(int $id, ?string $slug = null): void } ``` - -Events ------- - -In addition to the `startup()`, `beforeRender()`, and `shutdown()` methods, which are called as part of the presenter's life cycle, other functions can be defined to be called automatically. The presenter defines so-called [events |nette:glossary#Events], and you add their handlers to the `$onStartup`, `$onRender`, and `$onShutdown` arrays. - -```php -class ArticlePresenter extends Nette\Application\UI\Presenter -{ - public function __construct() - { - $this->onStartup[] = function () { - // ... - }; - } -} -``` - -Handlers in the `$onStartup` array are called just before the `startup()` method, `$onRender` handlers between `beforeRender()` and `render()`, and finally `$onShutdown` handlers just before `shutdown()`. +For a complete pattern that combines route filters with `canonicalize()` to produce SEO-friendly URLs, see [Pretty URLs with Slugs |best-practices:pretty-urls]. Responses @@ -447,8 +472,65 @@ $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $ht $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +You can also write your own response. Just implement the `Nette\Application\Response` interface, which has a single `send()` method receiving the HTTP request and response. This is useful, for example, when streaming data that you do not want to hold in memory: + +```php +class CsvResponse implements Nette\Application\Response +{ + public function __construct( + private string $fileName, + private iterable $rows, + ) { + } + + public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void + { + $response->setContentType('text/csv', 'utf-8'); + $response->sendAsFile($this->fileName); + + $handle = fopen('php://output', 'w'); + foreach ($this->rows as $row) { + fputcsv($handle, $row); + } + + fclose($handle); + } +} +``` + +You then send it in the presenter as usual: `$this->sendResponse(new CsvResponse('export.csv', $rows));` + -Access Restriction Using `#[Requires]` .{data-version:3.2.2} +HTTP Caching +------------ + +The `lastModified()` method makes it easy to take advantage of HTTP caching. You pass it the date and time of the content's last modification (as a timestamp, string, or `DateTimeInterface` object), and optionally an ETag validator (a short string identifying the current version of the content, such as a hash of it) and an expiration time. If the browser already holds a matching version, the presenter sends a `304 Not Modified` response and terminates, so the page isn't rendered or transferred needlessly: + +```php +public function renderArticle(int $id): void +{ + $article = $this->articles->getById($id); + $this->lastModified($article->updatedAt); + // ... +} +``` + + +Completing the Template .{data-version:3.3.0} +--------------------------------------------- + +When the presenter renders a template, the `sendTemplate()` method calls `completeTemplate()` just before rendering. This method fills in the variables marked with the `#[TemplateVariable]` attribute and locates the template file (the default variables are already set by the `TemplateFactory` when the template is created). You can override this protected method to add variables shared across all views or to set a different file: + +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My App'; +} +``` + + +Access Restriction Using `#[Requires]` .{data-version:3.2.3} ------------------------------------------------------------ The `#[Requires]` attribute provides advanced options for restricting access to presenters and their methods. It can be used to specify HTTP methods, require an AJAX request, restrict to the same origin, and allow access only via forwarding. The attribute can be applied both to presenter classes and to individual methods like `action()`, `render()`, `handle()`, and `createComponent()`. @@ -460,6 +542,9 @@ You can specify these restrictions: - access only via forwarding: `#[Requires(forward: true)]` - restrictions on specific actions: `#[Requires(actions: 'default')]` +.[note] +Since version 3.3, the same-origin match is verified using the browser's `Sec-Fetch-Site` header (previously via a SameSite cookie), which is more reliable and checks the exact match of scheme, domain, and port. + Details can be found in the guide [How to Use the Requires Attribute |best-practices:attribute-requires]. @@ -468,7 +553,7 @@ HTTP Method Check Presenters in Nette automatically verify the HTTP method of every incoming request, primarily for security reasons. By default, the methods `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` are allowed. -If you want to additionally allow, for example, the `OPTIONS` method, use the `#[Requires]` attribute (since Nette Application v3.2): +If you want to additionally allow, for example, the `OPTIONS` method, use the `#[Requires]` attribute (since Nette Application v3.2.3): ```php #[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] @@ -477,16 +562,16 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -In version 3.1, verification is performed in `checkHttpMethod()`, which checks if the method specified in the request is included in the `$presenter->allowedMethods` array. Add a method like this: +Since version 3.1.13, verification is performed in `checkHttpMethod()`, which checks whether the method specified in the request is included in the `$presenter->allowedMethods` array. As of version 3.2.3, this approach is deprecated in favor of `#[Requires]`. You can override the method like this: ```php class MyPresenter extends Nette\Application\UI\Presenter { - protected function checkHttpMethod(): void - { - $this->allowedMethods[] = 'OPTIONS'; - parent::checkHttpMethod(); - } + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } } ``` diff --git a/application/en/routing.texy b/application/en/routing.texy index 4b83f317f2..4c6c32aa86 100644 --- a/application/en/routing.texy +++ b/application/en/routing.texy @@ -198,7 +198,7 @@ $router->addRoute('[[/[/]]]', /* ... */); Wildcards --------- -In the absolute path mask, we can use the following wildcards to avoid, for example, having to write the domain into the mask, which might differ between development and production environments: +In the absolute URL mask, we can use the following wildcards to avoid, for example, having to write the domain into the mask, which might differ between development and production environments: - `%tld%` = top level domain, e.g., `com` or `org` - `%sld%` = second level domain, e.g., `example` @@ -208,7 +208,7 @@ In the absolute path mask, we can use the following wildcards to avoid, for exam ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); -$router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('//www.%sld%.%tld%/%basePath%//', /* ... */); ``` @@ -244,6 +244,16 @@ $router->addRoute('/[/]', [ It is important to note that if parameters defined in the array are not listed in the path mask, their values cannot be changed, not even using query parameters specified after the question mark in the URL. +This is useful for **fixed parameters** - giving a specific page a short, memorable URL. For example, to make `/tos` always open `Article:view` with `id: 123`: + +```php +$router->addRoute('tos', [ + 'presenter' => 'Article', + 'action' => 'view', + 'id' => 123, +]); +``` + Filters and Translations ------------------------ @@ -265,14 +275,14 @@ $router->addRoute('/', [ Route::FilterTable => [ // string in URL => presenter 'produkt' => 'Product', - 'einkaufswagen' => 'Cart', + 'kosik' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'seznam' => 'list', ], ], ]); @@ -300,7 +310,7 @@ $router->addRoute('//', [ The `Route::FilterIn` function converts between the parameter in the URL and the string that is then passed to the presenter; the `FilterOut` function ensures the conversion in the opposite direction. -The parameters `presenter`, `action`, and `module` already have predefined filters that convert between PascalCase or camelCase style and the kebab-case used in URLs. The default value of the parameters is written in the transformed form, so for example, in the case of a presenter, we write ``, not ``. +The parameters `presenter`, `action`, and `module` already have predefined filters that convert between PascalCase or camelCase style and the kebab-case used in URLs. The default value of the parameters is written in the form in which it is passed to the application (PascalCase for presenter and module, camelCase for action), so for example, in the case of a presenter, we write ``, not ``. General Filters @@ -325,6 +335,8 @@ General filters provide the ability to modify the route's behavior in absolutely If a parameter has its own filter defined and a general filter also exists, the custom `FilterIn` is executed before the general one, and conversely, the general `FilterOut` is executed before the custom one. Thus, inside the general filter, the values of the parameters `presenter` and `action` are written in PascalCase or camelCase style, respectively. +See [Pretty URLs with Slugs |best-practices:pretty-urls] for a practical use of these filters - generating SEO-friendly URLs like `/article/123-how-to-bake-bread` without modifying any templates. + OneWay Flag ----------- @@ -333,7 +345,7 @@ One-way routes are used to maintain the functionality of old URLs that the appli ```php // old URL /product-info?id=123 -$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); +$router->addRoute('product-info', 'Product:detail', oneWay: true); // new URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` @@ -363,11 +375,19 @@ $router->addRoute('', function (string $lang) { }); ``` +Besides parameters from the mask, the callback can also receive services from the DI container. They are passed based on the parameter type. Additionally, the parameter `$presenter` receives an instance of [MicroPresenter |api:NetteModule\MicroPresenter], which processes the route: + +```php +$router->addRoute('', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) { + // ... +}); +``` + Modules ------- -If we have multiple routes that belong to a common [module |directory-structure#Presenters and Templates], we use `withModule()`: +If we have multiple routes that belong to a common [module |directory-structure#Presenters and Templates], we use `withModule()`. The given module is automatically prepended to the presenter of every route in the group, and it disappears from the URL entirely: ```php $router = new RouteList; @@ -379,7 +399,7 @@ $router->withModule('Forum') // the following routes are part of the Forum modul ->addRoute('sign:in', 'Sign:in'); ``` -An alternative is to use the `module` parameter: +An alternative is the `module` parameter, which likewise sets a fixed module and keeps it out of the URL: ```php // URL manage/dashboard/default maps to presenter Admin:Dashboard @@ -388,6 +408,12 @@ $router->addRoute('manage//', [ ]); ``` +Every presenter name is complete only together with its module, e.g. `Front:Admin:ProductList`. Whenever such a full name lands in a URL parameter, the router encodes it by two simple rules: every colon `:` (the module separator) becomes a **dot**, and every word boundary in a PascalCase name becomes a **dash**. So `Front:Admin:ProductList` appears in the URL as `front.admin.product-list` and is decoded back the same way. This is why a modular application, without any of the tools above, produces URLs full of dots. + +Both `withModule()` and the `module` parameter avoid this precisely because they strip a known module prefix off the presenter name before it reaches the URL: since the module is a constant, it doesn't need to be encoded at all. + +Sometimes we want the module itself to vary and to appear in the URL, so we reach for `` directly in the mask. Beware of one crucial detail: **`` captures the whole module path** - everything up to the last colon in the presenter name. For the presenter `Shop:Admin:Product` that means the module `Shop:Admin` and the presenter `Product`, and since colons become dots, we get: + Subdomains ---------- @@ -595,7 +621,7 @@ When debugging the router, we recommend opening Developer Tools in the browser ( Performance =========== -The number of routes affects the speed of the router. Their number should definitely not exceed several dozen. If your website has a too complicated URL structure, you can write a custom [#Custom Router]. +The number of routes affects the speed of the router. Their number should definitely not exceed several dozen. If your website has too complicated a URL structure, you can write a custom [#Custom Router]. If the router has no dependencies, for example, on a database, and its factory accepts no arguments, we can serialize its compiled form directly into the DI container and thus slightly speed up the application. @@ -628,7 +654,7 @@ class MyRouter implements Nette\Routing\Router } ``` -The `match` method processes the current request [$httpRequest |http:request], from which not only the URL but also headers, etc., can be obtained, into an array containing the presenter name and its parameters. If it cannot process the request, it returns null. When processing the request, we must return at least the presenter and action. The presenter name is complete and includes any modules: +The `match` method processes the current request [$httpRequest |http:request], from which not only the URL but also headers, etc., can be obtained, into an array containing the presenter name and its parameters. If it cannot process the request, it returns null. When processing the request, we must return at least the presenter; the action is optional and defaults to `default` if not specified. The presenter name is complete and includes any modules: ```php [ @@ -718,4 +744,6 @@ $url = $router->constructUrl($params, $httpRequest->getUrl()); ``` -{{composer: nette/router}} +{{composer: nette/routing}} +{{repo: nette/routing}} +{{api: https://api.nette.org/routing/}} diff --git a/application/en/templates.texy b/application/en/templates.texy index 31617d0259..8466900e39 100644 --- a/application/en/templates.texy +++ b/application/en/templates.texy @@ -56,9 +56,9 @@ app/ └── Presenters/ ├── HomePresenter.php └── templates/ - ├── Home.default.latte ← 1st variant - └── Home/ - └── default.latte ← 2nd variant + ├── Home/ + │ └── default.latte ← 1st variant + └── Home.default.latte ← 2nd variant \-- The `templates` directory can also be placed one level higher, i.e., at the same level as the directory with presenter classes. @@ -96,9 +96,9 @@ app/ ├── HomePresenter.php └── templates/ ├── @layout.latte ← common layout - ├── Home.@layout.latte ← only for Home, 1st variant - └── Home/ - └── @layout.latte ← only for Home, 2nd variant + ├── Home/ + │ └── @layout.latte ← only for Home, 1st variant + └── Home.@layout.latte ← only for Home, 2nd variant \-- If the presenter is located in a module, it will also search further up the directory levels, according to the module nesting. @@ -120,7 +120,7 @@ Variables are passed to templates by writing them to `$this->template`. They the $this->template->article = $this->articles->getById($id); ``` -To automatically pass a property value to the template as a variable, mark it with the `#[TemplateVariable]` attribute and public or protected visibility: .{data-version:3.2.9} +To automatically pass a property value to the template as a variable, mark it with the `#[TemplateVariable]` attribute and public visibility: .{data-version:3.2.9} ```php use Nette\Application\Attributes\TemplateVariable; @@ -176,11 +176,13 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template The `$this->template` object in the presenter will now be an instance of the `ArticleTemplate` class. PHP will thus check the declared types when writing. +Nette chooses the template class automatically. First it looks for a class named `Template`, e.g. `ArticleEditTemplate` for the `edit` action, and only if it doesn't exist does it fall back to `Template`. + The `@property-read` annotation is for the IDE and static analysis, enabling code completion, see "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -You can also use code completion directly in templates. Just install the Latte plugin for PhpStorm and specify the template parameter class name at the beginning of the template, more in the article "Latte: how to use the type system":https://blog.nette.org/en/latte-how-to-use-type-system: +You can also use code completion directly in templates. Just install the Latte plugin for PhpStorm and specify the template parameter class name at the beginning of the template, more in the chapter [Latte: type system |latte:type-system]: ```latte {templateType App\Presentation\Article\ArticleTemplate} @@ -201,6 +203,17 @@ public function renderDefault(): void } ``` +.{data-version:3.3.0} +If you need to influence how the template is finalized before rendering - for example to add variables shared across all actions - you can override the `completeTemplate()` method in the presenter. It is called just before the template is rendered: + +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My blog'; +} +``` + Creating Links -------------- @@ -274,12 +287,12 @@ 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; } } ``` -Latte automatically discovers and registers methods marked with these attributes. The filter or function name in templates matches the method name. These methods must not be private. +Latte automatically discovers and registers methods marked with these attributes. The filter or function name in templates matches the method name. These methods must be public. **Globally Using Extensions** @@ -335,6 +348,18 @@ latte: Extensions offer several advantages: dependency injection support, access to your application's model layer, and centralized management of all extensions. They also support custom tags, providers, compiler passes, and more. +Setting Up All Templates +------------------------ + +The `TemplateFactory` service, which creates all templates, offers a public `$onCreate` array of callbacks. These are called every time any template is created, so you can set up filters, functions, or variables for all templates in the application from a single place. Each callback receives the newly created template. Have the `TemplateFactory` service [injected |dependency-injection:passing-dependencies] and register the callbacks, e.g. during application startup: + +```php +$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void { + $template->addFilter('money', fn($val) => '$' . number_format($val, 2)); +}; +``` + + Translating ----------- diff --git a/application/en/upgrading.texy b/application/en/upgrading.texy new file mode 100644 index 0000000000..f95527093f --- /dev/null +++ b/application/en/upgrading.texy @@ -0,0 +1,47 @@ +Upgrading +********* + + +Upgrading to Version 3.0 +======================== + +Nette 3.0 adds type hints to method parameters and return values. If you override such a method in a class inheriting from Nette (for example in a presenter or component), you must add the same type hints, otherwise PHP raises a "Declaration must be compatible" error. + +The `Nette\Application\IRouter` interface has changed. The `match()` method now returns, and `constructUrl()` accepts, an array of parameters instead of a `Nette\Application\Request` object. + +Nette now checks that each signal is sent from the same origin (i.e. the same domain and subdomain). This same-origin policy is a critical security mechanism that helps reduce possible attack vectors. If you want to allow other origins, add the `@crossOrigin` annotation to the handler method: + +```php +/** + * @crossOrigin + */ +public function handleXy(): void +{ +} +``` + +This also applies to form submissions. If you want to allow submission from other origins, do it this way: + +```php +$form = new Nette\Application\UI\Form; +$form->allowCrossOrigin(); +``` + +The constructor of `Nette\ComponentModel\Component` has not been used for years and was removed in version 3.0. It is a BC break: if you call the parent constructor in a component or presenter inheriting from `Nette\Application\UI\Presenter`, you must remove the call. + + +Upgrading to Version 2.4 +======================== + +- `Route` and `SimpleRouter` now generate the same HTTP/HTTPS scheme that was used to access the site. A route that requires a specific protocol can be defined with the scheme, e.g. `Route('http://domain.cz/')`. +- For bool-type parameters of render/action methods (i.e. with a default value of true or false) and for persistent parameters, `false` and `null` are now distinguished. If the parameter is not present in the URL, its value is now `null` (previously `false`). +- The class returned by `Presenter::getReflection()` is no longer a descendant of `Nette\Reflection\ClassType`, and `getReflection()->getMethod()` is no longer a descendant of `Nette\Reflection\Method`. +- The `SECURED` flag and `Route::$defaultFlags` are deprecated. + + +Upgrading to Version 2.3 +======================== + +- routes and presenter names are **case-sensitive**. Nette warns you if you use the wrong case in a presenter name; for performance reasons the Route mask is not checked, so verify it manually. +- `Route::addStyle()` and `Route::setStyleProperty()` are deprecated and now trigger `E_USER_DEPRECATED`. +- the template extension `.phtml` and the old link syntax are no longer supported. diff --git a/application/es/@home.texy b/application/es/@home.texy index 3e865c2c10..9d18759735 100644 --- a/application/es/@home.texy +++ b/application/es/@home.texy @@ -2,13 +2,13 @@ Nette Application ***************** .[perex] -Nette Application es el núcleo del framework Nette, que proporciona potentes herramientas para crear aplicaciones web modernas. Ofrece una serie de características excepcionales que facilitan significativamente el desarrollo y mejoran la seguridad y la mantenibilidad del código. +Nette Application es el núcleo de Nette Framework y ofrece herramientas potentes para crear aplicaciones web modernas. Aporta una serie de funciones excepcionales que simplifican notablemente el desarrollo y mejoran la seguridad y la mantenibilidad del código. Instalación ----------- -Puede descargar e instalar la librería usando [Composer|best-practices:composer]: +Descargue e instale la biblioteca con [Composer|best-practices:composer]: ```shell composer require nette/application @@ -18,68 +18,68 @@ composer require nette/application ¿Por qué elegir Nette Application? ---------------------------------- -Nette siempre ha sido un pionero en el campo de las tecnologías web. +Nette ha sido siempre un pionero en tecnologías web. -**Router bidireccional:** Nette cuenta con un sistema de enrutamiento avanzado que es único por su bidireccionalidad: no solo traduce las URL en acciones de la aplicación, sino que también puede generar direcciones URL inversamente. Esto significa que: -- Puede cambiar la estructura de URL de toda la aplicación en cualquier momento sin necesidad de modificar las plantillas +**Router bidireccional:** Nette dispone de un sistema de enrutamiento avanzado y único por su bidireccionalidad: no solo traduce las URL en acciones de la aplicación, sino que también sabe generar URL a la inversa. Esto significa que: +- Puede cambiar en cualquier momento la estructura de URL de toda la aplicación sin tocar las plantillas - Las URL se canonizan automáticamente, lo que mejora el SEO -- El enrutamiento se define en un solo lugar, no disperso en anotaciones +- El enrutamiento se define en un solo sitio, no disperso en anotaciones -**Componentes y señales:** El sistema de componentes incorporado, inspirado en Delphi y React.js, es completamente excepcional entre los frameworks PHP: -- Permite crear elementos de UI reutilizables -- Soporta la composición jerárquica de componentes -- Ofrece un manejo elegante de las peticiones AJAX mediante señales -- Una rica librería de componentes listos para usar en [Componette](https://componette.org) +**Componentes y señales:** el sistema integrado de componentes, inspirado en Delphi y React.js, es único entre los frameworks de PHP: +- Permite crear elementos de interfaz reutilizables +- Admite la composición jerárquica de componentes +- Ofrece un tratamiento elegante de las peticiones AJAX mediante señales +- Amplia biblioteca de componentes listos para usar en [Componette](https://componette.org) -**AJAX y snippets:** Nette introdujo una forma revolucionaria de trabajar con AJAX ya en 2009, mucho antes de soluciones similares como Hotwire para Ruby on Rails o Symfony UX Turbo: +**AJAX y snippets:** Nette introdujo en 2009 una forma revolucionaria de trabajar con AJAX, mucho antes que soluciones parecidas como Hotwire para Ruby on Rails o Symfony UX Turbo: - Los snippets permiten actualizar solo partes de la página sin necesidad de escribir JavaScript - Integración automática con el sistema de componentes -- Invalidación inteligente de partes de las páginas -- Cantidad mínima de datos transferidos +- Invalidación inteligente de secciones de la página +- Transferencia mínima de datos -**Plantillas intuitivas [Latte|latte:]:** El sistema de plantillas más seguro para PHP con funciones avanzadas: -- Protección automática contra XSS con escape sensible al contexto -- Extensibilidad mediante filtros, funciones y etiquetas personalizadas +**Plantillas [Latte|latte:] intuitivas:** el sistema de plantillas más seguro para PHP, con funciones avanzadas: +- Protección automática contra XSS con escapado sensible al contexto +- Ampliable con filtros, funciones y etiquetas propios - Herencia de plantillas y snippets para AJAX -- Excelente soporte para PHP 8.x con sistema de tipos +- Excelente soporte de PHP 8.x con sistema de tipos -**Inyección de dependencias:** Nette aprovecha al máximo la Inyección de Dependencias: +**Inyección de dependencias:** Nette aprovecha al máximo la inyección de dependencias: - Paso automático de dependencias (autowiring) -- Configuración mediante el claro formato NEON -- Soporte para fábricas de componentes +- Configuración con el claro formato NEON +- Soporte de factories de componentes Principales ventajas -------------------- -- **Seguridad**: Defensa automática contra [vulnerabilidades|nette:vulnerability-protection] como XSS, CSRF, etc. -- **Productividad**: Menos escritura, más funciones gracias a un diseño inteligente -- **Depuración**: [Depurador Tracy|tracy:] con panel de enrutamiento -- **Rendimiento**: Caché inteligente, carga diferida de componentes -- **Flexibilidad**: Fácil modificación de URL incluso después de completar la aplicación -- **Componentes**: Sistema único de elementos de UI reutilizables -- **Moderno**: Soporte completo para PHP 8.4+ y sistema de tipos +- **Seguridad**: protección automática contra [vulnerabilidades|nette:vulnerability-protection] como XSS, CSRF, etc. +- **Productividad**: menos escritura y más funciones gracias a un diseño inteligente +- **Depuración**: [depurador Tracy|tracy:] con panel de enrutamiento +- **Rendimiento**: caché inteligente, carga diferida de componentes +- **Flexibilidad**: modificación sencilla de las URL incluso con la aplicación terminada +- **Componentes**: sistema único de elementos de interfaz reutilizables +- **Modernidad**: pleno soporte de PHP 8.3+ y del sistema de tipos -Empezando ---------- +Primeros pasos +-------------- -1. [¿Cómo funcionan las aplicaciones? |how-it-works] - Comprensión de la arquitectura básica -2. [Presenters |presenters] - Trabajo con presenters y acciones -3. [Plantillas |templates] - Creación de plantillas en Latte -4. [Enrutamiento |routing] - Configuración de direcciones URL -5. [Componentes interactivos |components] - Uso del sistema de componentes +1. [¿Cómo funcionan las aplicaciones? |how-it-works] - comprender la arquitectura básica +2. [Presenters |presenters] - trabajar con presenters y acciones +3. [Plantillas |templates] - crear plantillas en Latte +4. [Enrutamiento |routing] - configurar las direcciones URL +5. [Componentes interactivos |components] - usar el sistema de componentes Compatibilidad con PHP ---------------------- -| versión | compatible con PHP -|-----------|------------------- -| Nette Application 4.0 | PHP 8.1 – 8.4 -| Nette Application 3.2 | PHP 8.1 – 8.4 -| Nette Application 3.1 | PHP 7.2 – 8.3 -| Nette Application 3.0 | PHP 7.1 – 8.0 -| Nette Application 2.4 | PHP 5.6 – 8.0 +| versión | compatible con PHP +|-----------------------|------------------- +| Nette Application 3.3 | PHP 8.3 - 8.5 +| Nette Application 3.2 | PHP 8.1 - 8.5 +| Nette Application 3.1 | PHP 7.2 - 8.3 +| Nette Application 3.0 | PHP 7.1 - 8.0 +| Nette Application 2.4 | PHP 5.6 - 8.0 -Válido para la última versión del parche. +Vale para las últimas versiones correctivas. diff --git a/application/es/@left-menu.texy b/application/es/@left-menu.texy index caab7237fb..3813496c5b 100644 --- a/application/es/@left-menu.texy +++ b/application/es/@left-menu.texy @@ -1,22 +1,24 @@ Nette Application ***************** +- [Introducción |@home] - [¿Cómo funcionan las aplicaciones? |how-it-works] -- [Bootstrapping] -- [Presenters |presenters] -- [Plantillas |templates] +- [Bootstrapping|bootstrapping] +- [Presenters|presenters] +- [Plantillas|templates] - [Estructura de directorios |directory-structure] -- [Enrutamiento |routing] +- [Enrutamiento|routing] - [Creación de enlaces URL |creating-links] - [Componentes interactivos |components] -- [AJAX & snippets |ajax] +- [AJAX y snippets |ajax] - [Multiplier |multiplier] -- [Configuración |configuration] +- [Configuración|configuration] +- [Actualización|upgrading] -Lectura adicional -***************** -- [¿Por qué usar Nette? |www:10-reasons-why-nette] +Lecturas adicionales +******************** +- [¿Por qué usar Nette?|www:10-reasons-why-nette] - [Instalación |nette:installation] -- [¡Escribamos nuestra primera aplicación! |quickstart:] -- [Tutoriales y procedimientos |best-practices:] -- [Resolución de problemas |nette:troubleshooting] +- [¡Cree su primera aplicación! |quickstart:] +- [Buenas prácticas |best-practices:] +- [Solución de problemas |nette:troubleshooting] diff --git a/application/es/@meta.texy b/application/es/@meta.texy index 1670b124ad..3798d9cda4 100644 --- a/application/es/@meta.texy +++ b/application/es/@meta.texy @@ -1 +1 @@ -{{sitename: Nette Documentación}} +{{sitename: Documentación de Nette}} diff --git a/application/es/ajax.texy b/application/es/ajax.texy index 1da5afd1e4..e7eb69f6cd 100644 --- a/application/es/ajax.texy +++ b/application/es/ajax.texy @@ -1,12 +1,12 @@ -AJAX y fragmentos -***************** +AJAX y snippets +***************
    -En la era de las aplicaciones web modernas, donde la funcionalidad a menudo se divide entre el servidor y el navegador, AJAX es un elemento de conexión esencial. ¿Qué posibilidades nos ofrece Nette Framework en esta área? -- envío de partes de la plantilla, los llamados fragmentos (snippets) -- paso de variables entre PHP y JavaScript -- herramientas para depurar peticiones AJAX +En la era de las aplicaciones web modernas, donde la funcionalidad suele repartirse entre el servidor y el navegador, AJAX es un elemento de unión imprescindible. ¿Qué posibilidades ofrece Nette Framework en este terreno? +- enviar partes de la plantilla, los llamados snippets +- pasar variables entre PHP y JavaScript +- herramientas para depurar las peticiones AJAX
    @@ -14,9 +14,9 @@ En la era de las aplicaciones web modernas, donde la funcionalidad a menudo se d Petición AJAX ============= -Una petición AJAX no difiere en principio de una petición HTTP clásica. Se llama a un presenter con ciertos parámetros. Y depende del presenter cómo reaccionará a la petición: puede devolver datos en formato JSON, enviar parte del código HTML, un documento XML, etc. +Una petición AJAX no difiere en lo esencial de una petición HTTP clásica. Se llama a un presenter con unos parámetros concretos. Es cosa del presenter decidir cómo responder a la petición: puede devolver datos en formato JSON, enviar una parte del código HTML, un documento XML, etc. -En el lado del navegador, inicializamos la petición AJAX usando la función `fetch()`: +Del lado del navegador iniciamos una petición AJAX con la función `fetch()`: ```js fetch(url, { @@ -24,22 +24,22 @@ fetch(url, { }) .then(response => response.json()) .then(payload => { - // procesar la respuesta + // procesa la respuesta }); ``` -En el lado del servidor, reconocemos una petición AJAX con el método `$httpRequest->isAjax()` del servicio [que encapsula la petición HTTP |http:request]. Para la detección, utiliza la cabecera HTTP `X-Requested-With`, por lo que es importante enviarla. Dentro del presenter, se puede usar el método `$this->isAjax()`. +Del lado del servidor, una petición AJAX se reconoce con el método `$httpRequest->isAjax()` del servicio que [encapsula la petición HTTP |http:request]. Usa para detectarla la cabecera HTTP `X-Requested-With`, así que es imprescindible enviarla. Dentro del presenter puede usar el método `$this->isAjax()`. -Si desea enviar datos en formato JSON, use el método [`sendJson()` |presenters#Envío de la respuesta]. El método también finaliza la actividad del presenter. +Si quiere enviar datos en formato JSON, use el método [`sendJson()` |presenters#Envío de una respuesta]. El método termina además la actividad del presenter. ```php public function actionExport(): void { - $this->sendJson($this->model->getData); + $this->sendJson($this->model->getData()); } ``` -Si planea responder con una plantilla especial diseñada para AJAX, puede hacerlo de la siguiente manera: +Si piensa responder con una plantilla especial pensada para AJAX, puede hacerlo así: ```php public function handleClick($param): void @@ -52,20 +52,20 @@ public function handleClick($param): void ``` -Fragmentos (Snippets) -===================== +Snippets +======== -El recurso más potente que ofrece Nette para conectar el servidor con el cliente son los fragmentos (snippets). Gracias a ellos, puedes convertir una aplicación ordinaria en una AJAX con un esfuerzo mínimo y unas pocas líneas de código. El ejemplo Fifteen demuestra cómo funciona todo, cuyo código puedes encontrar en [GitHub |https://github.com/nette-examples/fifteen]. +La herramienta más potente que ofrece Nette para conectar el servidor con el cliente son los snippets. Con ellos puede convertir una aplicación corriente en una aplicación AJAX con un esfuerzo mínimo y unas pocas líneas de código. El ejemplo Fifteen muestra cómo funciona todo, y su código está en [GitHub |https://github.com/nette-examples/fifteen]. -Los fragmentos, o recortes, permiten actualizar solo partes de la página, en lugar de recargar toda la página. Esto no solo es más rápido y eficiente, sino que también proporciona una experiencia de usuario más cómoda. Los fragmentos pueden recordarle a Hotwire para Ruby on Rails o Symfony UX Turbo. Curiosamente, Nette introdujo los fragmentos 14 años antes. +Los snippets permiten actualizar solo partes de la página en lugar de recargarla entera. Esto no solo es más rápido y eficiente, sino que ofrece una experiencia de usuario más cómoda. Los snippets pueden recordarle a Hotwire para Ruby on Rails o a Symfony UX Turbo. Curiosamente, Nette introdujo los snippets 14 años antes. -¿Cómo funcionan los fragmentos? En la primera carga de la página (petición no AJAX), se carga toda la página, incluidos todos los fragmentos. Cuando el usuario interactúa con la página (por ejemplo, hace clic en un botón, envía un formulario, etc.), en lugar de cargar toda la página, se realiza una petición AJAX. El código en el presenter ejecuta la acción y decide qué fragmentos deben actualizarse. Nette renderiza estos fragmentos y los envía en forma de array en formato JSON. El código de manejo en el navegador inserta los fragmentos recibidos de nuevo en la página. Por lo tanto, solo se transfiere el código de los fragmentos modificados, lo que ahorra ancho de banda y acelera la carga en comparación con la transferencia del contenido de toda la página. +¿Cómo funcionan los snippets? En la primera carga de la página (una petición no AJAX) se carga la página entera, con todos los snippets incluidos. Cuando el usuario interactúa con la página (pulsa un botón, envía un formulario, etc.), se inicia una petición AJAX en lugar de recargar la página entera. El código del presenter ejecuta la acción y decide qué snippets hay que actualizar. Nette renderiza esos snippets y los envía en una carga útil JSON con un array de snippets. El código de gestión del navegador inserta después los snippets recibidos de vuelta en la página. Así solo se transfiere el código de los snippets modificados, lo que ahorra ancho de banda y acelera la carga frente a transferir el contenido de la página entera. Si no se invalida ningún snippet con `redrawControl()`, Nette devuelve la página entera incluso en una petición AJAX: los snippets se envían solo cuando algo se invalida. Naja ---- -Para manejar los fragmentos en el lado del navegador, se utiliza la [librería Naja |https://naja.js.org]. [Instálela |https://naja.js.org/#/guide/01-install-setup-naja] como un paquete node.js (para usar con aplicaciones Webpack, Rollup, Vite, Parcel y otras): +Para gestionar los snippets del lado del navegador se usa la [biblioteca Naja |https://naja.js.org]. [Instálela |https://naja.js.org/#/guide/01-install-setup-naja] como paquete de Node.js (para usarla con empaquetadores como Webpack, Rollup, Vite, Parcel y otros): ```shell npm install naja @@ -74,25 +74,25 @@ npm install naja …o insértela directamente en la plantilla de la página: ```latte - + ``` -Primero, es necesario [inicializar |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] la librería: +Primero hay que [inicializar |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] la biblioteca: ```js naja.initialize(); ``` -Para convertir un enlace ordinario (señal) o el envío de un formulario en una petición AJAX, basta con marcar el enlace, formulario o botón correspondiente con la clase `ajax`: +Para convertir un enlace corriente (una señal) o el envío de un formulario en una petición AJAX, basta con marcar el enlace, el formulario o el botón correspondiente con la clase `ajax`: ```latte -Ir +Go -o +or
    @@ -100,54 +100,56 @@ o ``` -Redibujar fragmentos --------------------- +Redibujar los snippets +---------------------- -Cada objeto de la clase [Control |components] (incluido el propio Presenter) registra si se han producido cambios que requieran su redibujado. Para ello se utiliza el método `redrawControl()`: +Todo objeto de la clase [Control |components] (incluido el propio Presenter) lleva la cuenta de si se han producido cambios que exijan redibujarlo. Para eso sirve el método `redrawControl()`: ```php public function handleLogin(string $user): void { - // después de iniciar sesión, es necesario redibujar la parte relevante + // tras el acceso hay que redibujar la parte correspondiente $this->redrawControl(); // ... } ``` -Nette permite un control aún más fino de lo que se debe redibujar. De hecho, el método mencionado puede aceptar el nombre del fragmento como argumento. Por lo tanto, es posible invalidar (léase: forzar el redibujado) a nivel de partes de la plantilla. Si se invalida todo el componente, también se redibujará cada uno de sus fragmentos: +Nette permite un control aún más fino de lo que hay que redibujar. El método puede recibir como argumento el nombre del snippet. Así es posible invalidar (es decir, forzar el redibujado) a nivel de partes de la plantilla. Si se invalida el componente entero, se redibujarán también todos los snippets que contiene: ```php -// invalida el fragmento 'header' +// invalida el snippet 'header' $this->redrawControl('header'); ``` +También puede cancelar una invalidación pendiente con el segundo parámetro `$redraw`: llamar a `$this->redrawControl('header', redraw: false)` marca el snippet como no necesitado de redibujado. La firma completa es `redrawControl(?string $snippet = null, bool $redraw = true)`. + -Fragmentos en Latte -------------------- +Snippets en Latte +----------------- -Usar fragmentos en Latte es extremadamente fácil. Si desea definir una parte de la plantilla como un fragmento, simplemente envuélvala con las etiquetas `{snippet}` y `{/snippet}`: +Usar snippets en Latte es facilísimo. Para definir una parte de la plantilla como snippet, basta con envolverla con las etiquetas `{snippet}` y `{/snippet}`: ```latte {snippet header} -

    Hola ...

    +

    Hello ...

    {/snippet} ``` -El fragmento crea un elemento `
    ` en la página HTML con un `id` especial generado. Al redibujar el fragmento, se actualiza el contenido de este elemento. Por lo tanto, es necesario que en la renderización inicial de la página se rendericen también todos los fragmentos, aunque puedan estar vacíos al principio. +El snippet crea en la página HTML un elemento `
    ` con un `id` especial generado. Al redibujar el snippet se actualiza el contenido de ese elemento. Por eso es necesario que, al renderizar la página por primera vez, se rendericen también todos los snippets, aunque al principio puedan estar vacíos. -También puede crear un fragmento con un elemento distinto de `
    ` usando un n:attribute: +También puede crear un snippet con un elemento distinto de `
    ` mediante un n:atributo: ```latte
    -

    Hola ...

    +

    Hello ...

    ``` -Áreas de fragmentos -------------------- +Áreas de snippets +----------------- -Los nombres de los fragmentos también pueden ser expresiones: +Los nombres de los snippets pueden ser también expresiones: ```latte {foreach $items as $id => $item} @@ -155,7 +157,9 @@ Los nombres de los fragmentos también pueden ser expresiones: {/foreach} ``` -De esta manera, creamos varios fragmentos `item-0`, `item-1`, etc. Si invalidáramos directamente un fragmento dinámico (por ejemplo, `item-1`), no se redibujaría nada. La razón es que los fragmentos realmente funcionan como recortes y solo se renderizan ellos mismos directamente. Sin embargo, en la plantilla no hay realmente ningún fragmento llamado `item-1`. Este se crea solo al ejecutar el código alrededor del fragmento, es decir, el bucle foreach. Por lo tanto, marcamos la parte de la plantilla que se debe ejecutar usando la etiqueta `{snippetArea}`: +Por sí solo, esto es un paso intermedio no funcional: renderizado fuera de un `{snippet}` o `{snippetArea}` estático, un snippet dinámico provoca un `E_USER_WARNING` con el mensaje *Dynamic snippets are allowed only inside static snippet/snippetArea.* Lo arreglamos a continuación. + +Esto crea varios snippets como `item-0`, `item-1`, etc. Si invalidáramos directamente un snippet dinámico (por ejemplo, `item-1`), no se redibujaría nada. El motivo es que los snippets funcionan realmente como extractos y solo ellos mismos se renderizan directamente. En la plantilla, sin embargo, no existe técnicamente ningún snippet llamado `item-1`. Solo nace cuando se ejecuta el código que rodea al snippet, es decir, el bucle foreach. Por eso marcamos con la etiqueta `{snippetArea}` la parte de la plantilla que hay que ejecutar: ```latte
      @@ -165,16 +169,16 @@ De esta manera, creamos varios fragmentos `item-0`, `item-1`, etc. Si invalidár
    ``` -Y hacemos que se redibuje tanto el fragmento en sí como toda el área padre: +Y pedimos el redibujado tanto del snippet concreto como de toda el área padre: ```php $this->redrawControl('itemsContainer'); $this->redrawControl('item-1'); ``` -Al mismo tiempo, es conveniente asegurarse de que el array `$items` contenga solo los elementos que se deben redibujar. +Al mismo tiempo, conviene asegurarse de que el array `$items` contenga solo los elementos que deben redibujarse. -Si incluimos otra plantilla que contiene fragmentos en la plantilla usando la etiqueta `{include}`, es necesario incluir de nuevo la inclusión de la plantilla en `snippetArea` e invalidarla junto con el fragmento: +Si incluimos en la plantilla principal otra plantilla que contiene snippets mediante la etiqueta `{include}`, hay que envolver de nuevo la inclusión de la plantilla en un `snippetArea` e invalidarlo junto con el snippet: ```latte {snippetArea include} @@ -195,41 +199,47 @@ $this->redrawControl('item'); ``` -Fragmentos en componentes -------------------------- +Snippets en los componentes +--------------------------- -También puede crear fragmentos en [componentes|components] y Nette los redibujará automáticamente. Pero hay una cierta limitación: para redibujar los fragmentos, llama al método `render()` sin parámetros. Por lo tanto, no funcionará pasar parámetros en la plantilla: +Puede crear snippets dentro de los [componentes|components] y Nette los redibujará automáticamente. Hay, sin embargo, una limitación: para redibujar los snippets, Nette llama al método `render()` sin parámetro alguno. Por eso, pasar parámetros en la plantilla no funcionará: ```latte OK {control productGrid} -no funcionará: +will not work: {control productGrid $arg, $arg} {control productGrid:paginator} ``` -Envío de datos de usuario -------------------------- +Enviar datos propios +-------------------- -Junto con los fragmentos, puede enviar cualquier otro dato al cliente. Simplemente escríbalos en el objeto `payload`: +Junto con los snippets puede enviar al cliente cualquier dato adicional. Basta con escribirlo en el objeto `payload`: ```php public function actionDelete(int $id): void { // ... if ($this->isAjax()) { - $this->payload->message = 'Éxito'; + $this->payload->message = 'Success'; } } ``` +Redirección +----------- + +Durante una petición AJAX, los métodos `redirect()` y `redirectUrl()` no envían una redirección HTTP. En su lugar escriben la URL de destino en la carga útil (el objeto de datos enviado en la respuesta AJAX), en concreto en su propiedad `payload.redirect`, y la envían; la redirección propiamente dicha la realiza después la biblioteca del lado del cliente (Naja). + + Paso de parámetros ================== -Si enviamos parámetros a un componente mediante una petición AJAX, ya sean parámetros de señal o parámetros persistentes, debemos indicar en la petición su nombre global, que también incluye el nombre del componente. El nombre completo del parámetro lo devuelve el método `getParameterId()`. +Al enviar parámetros a un componente mediante una petición AJAX, ya sean parámetros de señal o parámetros persistentes, debemos indicar en la petición su nombre global, que incluye el nombre del componente. El método `getParameterId()` devuelve el nombre completo del parámetro. ```js let url = new URL({link //foo!}); @@ -247,3 +257,9 @@ public function handleFoo(int $bar): void { } ``` + + +Lecturas adicionales +==================== + +- [Snippets dinámicos |best-practices:dynamic-snippets] diff --git a/application/es/bootstrapping.texy b/application/es/bootstrapping.texy index 88870cd20d..283c9e693c 100644 --- a/application/es/bootstrapping.texy +++ b/application/es/bootstrapping.texy @@ -3,19 +3,22 @@ Bootstrapping
    -El bootstrapping es el proceso de inicialización del entorno de la aplicación, creación del contenedor de inyección de dependencias (DI) e inicio de la aplicación. Discutiremos: +El bootstrapping es el proceso de inicializar el entorno de la aplicación, crear el contenedor de inyección de dependencias (DI) y arrancar la aplicación. Hablaremos de: -- cómo la clase Bootstrap inicializa el entorno -- cómo las aplicaciones se configuran usando archivos NEON -- cómo distinguir entre modo de producción y desarrollo +- cómo inicializa el entorno la clase Bootstrap +- cómo se configuran las aplicaciones con archivos NEON +- cómo distinguir entre el modo de producción y el de desarrollo - cómo crear y configurar el contenedor DI
    -Las aplicaciones, ya sean web o scripts ejecutados desde la línea de comandos, comienzan su ejecución con alguna forma de inicialización del entorno. En tiempos pasados, esto solía estar a cargo de un archivo llamado, por ejemplo, `include.inc.php`, que el archivo inicial incluía. En las aplicaciones Nette modernas, ha sido reemplazado por la clase `Bootstrap`, que como parte de la aplicación se encuentra en el archivo `app/Bootstrap.php`. Puede verse, por ejemplo, así: +Las aplicaciones, ya sean web o scripts ejecutados desde la línea de comandos, empiezan su ejecución con alguna forma de inicialización del entorno. Antaño se encargaba de ello un archivo llamado quizá `include.inc.php`, incluido por el archivo inicial. En las aplicaciones modernas de Nette lo ha sustituido la clase `Bootstrap` que, como parte de la aplicación, se encuentra en el archivo `app/Bootstrap.php`. Podría tener, por ejemplo, este aspecto: ```php +namespace App; + +use Nette; use Nette\Bootstrap\Configurator; class Bootstrap @@ -26,9 +29,9 @@ class Bootstrap public function __construct() { $this->rootDir = dirname(__DIR__); - // El configurador es responsable de configurar el entorno de la aplicación y los servicios. + // El configurador se encarga de preparar el entorno y los servicios de la aplicación. $this->configurator = new Configurator; - // Establece el directorio para los archivos temporales generados por Nette (p. ej., plantillas compiladas) + // Establece el directorio de los archivos temporales que genera Nette (p. ej. las plantillas compiladas) $this->configurator->setTempDirectory($this->rootDir . '/temp'); } @@ -41,14 +44,14 @@ class Bootstrap private function initializeEnvironment(): void { - // Nette es inteligente y el modo de desarrollo se activa automáticamente, - // o puedes habilitarlo para una dirección IP específica descomentando la siguiente línea: + // Nette es listo y el modo de desarrollo se activa automáticamente, + // o puede activarlo para una dirección IP concreta descomentando la línea siguiente: // $this->configurator->setDebugMode('secret@23.75.345.200'); - // Activa Tracy: la "navaja suiza" definitiva para la depuración. + // Activa Tracy: la navaja suiza definitiva para depurar. $this->configurator->enableTracy($this->rootDir . '/log'); - // RobotLoader: carga automáticamente todas las clases en el directorio seleccionado + // RobotLoader: carga automáticamente todas las clases del directorio elegido $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); @@ -66,69 +69,78 @@ class Bootstrap index.php ========= -El archivo inicial en el caso de las aplicaciones web es `index.php`, que se encuentra en el [directorio público |directory-structure#Directorio público www] `www/`. Este solicita a la clase Bootstrap que inicialice el entorno y cree el contenedor DI. Luego, obtiene de él el servicio `Application`, que ejecuta la aplicación web: +En las aplicaciones web, el archivo inicial es `index.php`, situado en el [directorio público |directory-structure#Directorio público www/] `www/`. Le pide a la clase Bootstrap que inicialice el entorno y cree el contenedor DI. Después obtiene del contenedor el servicio `Application`, que ejecuta la aplicación web: ```php $bootstrap = new App\Bootstrap; -// Inicialización del entorno + creación del contenedor DI +// Inicializa el entorno + crea un contenedor DI $container = $bootstrap->bootWebApplication(); // El contenedor DI crea un objeto Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// Ejecución de la aplicación Nette y procesamiento de la petición entrante +// Arranca la aplicación Nette y procesa la petición entrante $application->run(); ``` -Como se puede ver, la clase [api:Nette\Bootstrap\Configurator] ayuda con la configuración del entorno y la creación del contenedor de inyección de dependencias (DI), que ahora presentaremos con más detalle. +.[note] +El objeto `$application` emite [eventos |nette:glossary#Eventos] mientras atiende la petición: `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown` y `onError` (ante una excepción no capturada). Puede engancharles manejadores, algo práctico para registrar o para supervisar toda la aplicación. + +Como ve, la clase [api:Nette\Bootstrap\Configurator] ayuda a configurar el entorno y a crear el contenedor de inyección de dependencias (DI). La presentaremos ahora en detalle. -Modo de desarrollo vs producción -================================ +Modo de desarrollo y modo de producción +======================================= -Nette se comporta de manera diferente dependiendo de si se ejecuta en un servidor de desarrollo o de producción: +Nette se comporta de forma distinta según se ejecute en un servidor de desarrollo o de producción: -🛠️ Modo de desarrollo (Development): - - Muestra la barra de depuración de Tracy con información útil (consultas SQL, tiempo de ejecución, memoria utilizada) - - En caso de error, muestra una página de error detallada con llamadas a funciones y contenido de variables - - Actualiza automáticamente la caché al cambiar las plantillas Latte, modificar archivos de configuración, etc. +🛠️ Modo de desarrollo: + - Muestra la barra de depuración de Tracy con información útil (consultas SQL, tiempo de ejecución, memoria usada) + - Ante un error, muestra una página de error detallada con las llamadas a funciones y el contenido de las variables + - Refresca automáticamente la caché al cambiar las plantillas de Latte, los archivos de configuración, etc. -🚀 Modo de producción (Production): - - No muestra ninguna información de depuración, todos los errores se registran en el log - - En caso de error, muestra ErrorPresenter o una página genérica "Server Error" - - ¡La caché nunca se actualiza automáticamente! - - Optimizado para velocidad y seguridad +🚀 Modo de producción: + - No muestra ninguna información de depuración; todos los errores se escriben en el registro + - Ante un error, muestra un ErrorPresenter o una página genérica de "Server Error" + - ¡La caché no se refresca nunca automáticamente! + - Está optimizado para la velocidad y la seguridad -La elección del modo se realiza por autodetección, por lo que generalmente no es necesario configurar nada ni cambiar manualmente: +El modo se elige por autodetección, así que normalmente no hace falta configurar nada ni cambiar de modo a mano: -- modo de desarrollo: en localhost (dirección IP `127.0.0.1` o `::1`) si no hay proxy presente (es decir, su cabecera HTTP) -- modo de producción: en cualquier otro lugar +- modo de desarrollo: en localhost (dirección IP `127.0.0.1` o `::1`) si no hay ningún proxy presente (es decir, no se detecta su cabecera HTTP) +- modo de producción: en todos los demás casos -Si queremos habilitar el modo de desarrollo también en otros casos, por ejemplo, para programadores que acceden desde una dirección IP específica, usamos `setDebugMode()`: +Si queremos activar el modo de desarrollo en otros casos, por ejemplo para los programadores que se conectan desde una dirección IP concreta, usamos `setDebugMode()`: ```php $this->configurator->setDebugMode('23.75.345.200'); // también se puede indicar un array de direcciones IP ``` -Definitivamente recomendamos combinar la dirección IP con una cookie. Guardamos un token secreto en la cookie `nette-debug`, por ejemplo, `secret1234`, y de esta manera activamos el modo de desarrollo para los programadores que acceden desde una dirección IP específica y que además tienen el token mencionado en la cookie: +Recomendamos vivamente combinar la dirección IP con un cookie. Guarde un token secreto, por ejemplo `secret1234`, en el cookie `nette-debug` y active así el modo de desarrollo para los programadores que se conecten desde una dirección IP concreta y tengan además ese token en su cookie: ```php $this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` -También podemos desactivar completamente el modo de desarrollo, incluso para localhost: +También podemos desactivar por completo el modo de desarrollo, incluso en localhost: ```php $this->configurator->setDebugMode(false); ``` -Atención, el valor `true` activa el modo de desarrollo de forma permanente, lo cual nunca debe ocurrir en un servidor de producción. +Tenga en cuenta que el valor `true` fuerza el modo de desarrollo, lo que **nunca** debería ocurrir en un servidor de producción. + +De la autodetección se encarga internamente el método estático `Configurator::detectDebugMode()`, al que también puede llamar usted mismo, por ejemplo para detectar el modo de desarrollo fuera del configurador. Acepta una lista blanca opcional de direcciones IP o nombres de equipo y devuelve si la petición actual debe ejecutarse en modo de desarrollo: + +```php +$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200'); +``` Herramienta de depuración Tracy =============================== -Para facilitar la depuración, también activaremos la excelente herramienta [Tracy |tracy:]. En el modo de desarrollo, visualiza los errores y en el modo de producción, registra los errores en el directorio especificado: +Para depurar con comodidad activaremos la excelente herramienta [Tracy |tracy:]. En modo de desarrollo visualiza los errores y, en modo de producción, los registra en el directorio indicado: ```php $this->configurator->enableTracy($this->rootDir . '/log'); @@ -138,19 +150,19 @@ $this->configurator->enableTracy($this->rootDir . '/log'); Archivos temporales =================== -Nette utiliza caché para el contenedor DI, RobotLoader, plantillas, etc. Por lo tanto, es necesario establecer la ruta al directorio donde se almacenará la caché: +Nette usa caché para el contenedor DI, RobotLoader, las plantillas, etc. Por eso hay que indicar la ruta del directorio donde se guardará esa caché: ```php $this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` -En Linux o macOS, establezca [permisos de escritura |nette:troubleshooting#Configuración de permisos de directorio] para los directorios `log/` y `temp/`. +En Linux o macOS, dé [permisos de escritura |nette:troubleshooting#Establecer los permisos de los directorios] a los directorios `log/` y `temp/`. RobotLoader =========== -Generalmente, querremos cargar clases automáticamente usando [RobotLoader |robot-loader:], por lo que debemos iniciarlo y hacer que cargue clases desde el directorio donde se encuentra `Bootstrap.php` (es decir, `__DIR__`), y todos los subdirectorios: +Normalmente querremos cargar las clases automáticamente con [RobotLoader |robot-loader:], así que hay que ponerlo en marcha y dejar que cargue las clases del directorio donde está `Bootstrap.php` (es decir, `__DIR__`) y de todos sus subdirectorios: ```php $this->configurator->createRobotLoader() @@ -158,13 +170,13 @@ $this->configurator->createRobotLoader() ->register(); ``` -Un enfoque alternativo es dejar que las clases se carguen solo a través de [Composer |best-practices:composer] cumpliendo con PSR-4. +Un enfoque alternativo es cargar las clases únicamente mediante [Composer |best-practices:composer], siguiendo PSR-4. Zona horaria ============ -A través del configurador, puede establecer la zona horaria predeterminada. +Puede fijar la zona horaria predeterminada mediante el configurador. ```php $this->configurator->setTimeZone('Europe/Prague'); @@ -174,20 +186,22 @@ $this->configurator->setTimeZone('Europe/Prague'); Configuración del contenedor DI =============================== -Parte del proceso de arranque es la creación del contenedor DI o fábrica de objetos, que es el corazón de toda la aplicación. En realidad, es una clase PHP que Nette genera y guarda en el directorio de caché. La fábrica produce los objetos clave de la aplicación y, mediante archivos de configuración, le instruimos cómo debe crearlos y configurarlos, influyendo así en el comportamiento de toda la aplicación. +Parte del proceso de arranque es la creación del contenedor DI, o factory de objetos, que es el corazón de toda la aplicación. En realidad es una clase PHP generada por Nette y guardada en el directorio de caché. La factory produce los objetos clave de la aplicación y, mediante los archivos de configuración, le indicamos cómo crearlos y ajustarlos, con lo que influimos en el comportamiento de toda la aplicación. -Los archivos de configuración generalmente se escriben en formato [NEON |neon:format]. En un capítulo aparte, aprenderá [qué se puede configurar |nette:configuring]. +Los archivos de configuración se escriben normalmente en [formato NEON |neon:format]. En un capítulo aparte puede leer [qué se puede configurar |nette:configuring]. .[tip] -En el modo de desarrollo, el contenedor se actualiza automáticamente cada vez que se cambia el código o los archivos de configuración. En el modo de producción, se genera solo una vez y los cambios no se verifican para maximizar el rendimiento. +En modo de desarrollo, el contenedor se actualiza automáticamente cada vez que cambian el código o los archivos de configuración. En modo de producción se genera una sola vez y los cambios no se comprueban, para maximizar el rendimiento. + +Mientras que `createContainer()` construye el contenedor y devuelve su instancia, el método `loadContainer()` devuelve solo el nombre de la clase de contenedor generada, que puede instanciar usted mismo. Esto resulta útil en escenarios avanzados. -Cargamos los archivos de configuración usando `addConfig()`: +Los archivos de configuración se cargan con `addConfig()`: ```php $this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` -Si queremos agregar más archivos de configuración, podemos llamar a la función `addConfig()` varias veces. +Si queremos añadir más archivos de configuración, podemos llamar varias veces a la función `addConfig()`. ```php $configDir = $this->rootDir . '/config'; @@ -198,17 +212,17 @@ if (PHP_SAPI === 'cli') { } ``` -El nombre `cli.php` no es un error tipográfico, la configuración también puede estar escrita en un archivo PHP que la devuelve como un array. +El nombre `cli.php` no es una errata; la configuración también se puede escribir en un archivo PHP que la devuelva como array. -También podemos agregar otros archivos de configuración en la [sección `includes` |dependency-injection:configuration#Inclusión de archivos]. +También podemos añadir otros archivos de configuración en [la sección `includes` |dependency-injection:configuration#Inclusión de archivos]. -Si aparecen elementos con las mismas claves en los archivos de configuración, se sobrescribirán o, en el caso de [arrays, se fusionarán |dependency-injection:configuration#Fusión]. El archivo cargado posteriormente tiene mayor prioridad que el anterior. El archivo en el que se indica la sección `includes` tiene mayor prioridad que los archivos incluidos en él. +Si en los archivos de configuración aparecen elementos con las mismas claves, se sobrescriben o, en el caso de los [arrays, se fusionan |dependency-injection:configuration#Fusión]. Un archivo incluido después tiene más prioridad que el anterior. El archivo en el que figura la sección `includes` tiene más prioridad que los archivos incluidos dentro de ella. Parámetros estáticos -------------------- -Los parámetros utilizados en los archivos de configuración se pueden definir en la [sección `parameters` |dependency-injection:configuration#Parámetros] y también se pueden pasar (o sobrescribir) con el método `addStaticParameters()` (tiene el alias `addParameters()`). Es importante que diferentes valores de parámetros provoquen la generación de contenedores DI adicionales, es decir, clases adicionales. +Los parámetros usados en los archivos de configuración se pueden definir [en la sección `parameters` |dependency-injection:configuration#Parámetros] y también pasar (o sobrescribir) con el método `addStaticParameters()` (cuyo alias antiguo, ahora obsoleto, es `addParameters()`). Es importante saber que valores distintos de los parámetros provocan la generación de contenedores DI adicionales, es decir, de clases adicionales. ```php $this->configurator->addStaticParameters([ @@ -216,13 +230,13 @@ $this->configurator->addStaticParameters([ ]); ``` -Al parámetro `projectId` se puede hacer referencia en la configuración con la notación habitual `%projectId%`. +El parámetro `projectId` se puede referenciar en la configuración con la notación habitual `%projectId%`. Parámetros dinámicos -------------------- -También podemos agregar parámetros dinámicos al contenedor, cuyos diferentes valores, a diferencia de los parámetros estáticos, no provocan la generación de nuevos contenedores DI. +Al contenedor también podemos añadir parámetros dinámicos, cuyos distintos valores, a diferencia de los estáticos, no provocan la generación de nuevos contenedores DI. ```php $this->configurator->addDynamicParameters([ @@ -230,7 +244,7 @@ $this->configurator->addDynamicParameters([ ]); ``` -De esta manera, podemos agregar fácilmente, por ejemplo, variables de entorno, a las que luego se puede hacer referencia en la configuración con la notación `%env.variable%`. +Así podemos añadir con facilidad, por ejemplo, las variables de entorno, que se pueden referenciar después en la configuración con la notación `%env.variable%`. ```php $this->configurator->addDynamicParameters([ @@ -242,21 +256,22 @@ $this->configurator->addDynamicParameters([ Parámetros predeterminados -------------------------- -En los archivos de configuración, puede utilizar estos parámetros estáticos: +Puede usar estos parámetros en los archivos de configuración: -- `%appDir%` es la ruta absoluta al directorio con el archivo `Bootstrap.php` -- `%wwwDir%` es la ruta absoluta al directorio con el archivo de entrada `index.php` -- `%tempDir%` es la ruta absoluta al directorio para archivos temporales -- `%vendorDir%` es la ruta absoluta al directorio donde Composer instala las librerías +- `%appDir%` es la ruta absoluta al directorio que contiene el archivo `Bootstrap.php` +- `%wwwDir%` es la ruta absoluta al directorio que contiene el archivo de entrada `index.php` +- `%tempDir%` es la ruta absoluta al directorio de los archivos temporales +- `%vendorDir%` es la ruta absoluta al directorio donde Composer instala las bibliotecas - `%rootDir%` es la ruta absoluta al directorio raíz del proyecto +- `%baseUrl%` es la URL absoluta al directorio raíz (un parámetro dinámico resuelto en tiempo de ejecución) - `%debugMode%` indica si la aplicación está en modo de depuración -- `%consoleMode%` indica si la petición llegó a través de la línea de comandos +- `%consoleMode%` indica si la petición llegó por la línea de comandos Servicios importados -------------------- -Ahora vamos más profundo. Aunque el propósito del contenedor DI es fabricar objetos, excepcionalmente puede surgir la necesidad de insertar un objeto existente en el contenedor. Hacemos esto definiendo el servicio con el indicador `imported: true`. +Vamos ahora un poco más al fondo. Aunque el cometido del contenedor DI es crear objetos, alguna vez puede surgir la necesidad de insertar en el contenedor un objeto ya existente. Lo hacemos definiendo el servicio con la bandera `imported: true`. ```neon services: @@ -265,7 +280,7 @@ services: imported: true ``` -Y en bootstrap insertamos el objeto en el contenedor: +Y en el bootstrap insertamos el objeto en el contenedor: ```php $this->configurator->addServices([ @@ -274,10 +289,10 @@ $this->configurator->addServices([ ``` -Entorno diferente -================= +Entornos distintos +================== -No dude en modificar la clase Bootstrap según sus necesidades. Puede agregar parámetros al método `bootWebApplication()` para distinguir proyectos web. O podemos agregar otros métodos, por ejemplo, `bootTestEnvironment()`, que inicializa el entorno para pruebas unitarias, `bootConsoleApplication()` para scripts llamados desde la línea de comandos, etc. +No dude en modificar la clase `Bootstrap` según sus necesidades. Puede añadir parámetros al método `bootWebApplication()` para distinguir entre proyectos web. O podemos añadir otros métodos, como `bootTestEnvironment()`, que inicializa el entorno para las pruebas unitarias, `bootConsoleApplication()` para los scripts llamados desde la línea de comandos, etc. ```php public function bootTestEnvironment(): Nette\DI\Container diff --git a/application/es/components.texy b/application/es/components.texy index e4cce895a9..a768929b2d 100644 --- a/application/es/components.texy +++ b/application/es/components.texy @@ -3,27 +3,27 @@ Componentes interactivos
    -Los componentes son objetos reutilizables independientes que insertamos en las páginas. Pueden ser formularios, datagrids, encuestas, en realidad cualquier cosa que tenga sentido usar repetidamente. Mostraremos: +Los componentes son objetos independientes y reutilizables que insertamos en las páginas. Pueden ser formularios, datagrids, encuestas, en definitiva cualquier cosa que tenga sentido usar repetidamente. Mostraremos: -- ¿cómo usar componentes? -- ¿cómo escribirlos? +- ¿cómo se usan los componentes? +- ¿cómo se escriben? - ¿qué son las señales?
    -Nette tiene incorporado un sistema de componentes. Algo similar pueden recordar los veteranos de Delphi o ASP.NET Web Forms, algo remotamente similar es la base de React o Vue.js. Sin embargo, en el mundo de los frameworks PHP, es una característica única. +Nette lleva incorporado un sistema de componentes. Algo parecido puede resultar familiar a los veteranos de Delphi o ASP.NET Web Forms; React o Vue.js se apoyan en algo lejanamente similar. En el mundo de los frameworks de PHP, sin embargo, es una característica única. -Mientras tanto, los componentes influyen fundamentalmente en el enfoque para la creación de aplicaciones. Puede componer páginas a partir de unidades prefabricadas. ¿Necesita un datagrid en la administración? Lo encontrará en [Componette |https://componette.org/search/component], un repositorio de complementos de código abierto (es decir, no solo componentes) para Nette y simplemente insértelo en el presenter. +Al mismo tiempo, los componentes influyen de manera esencial en la forma de desarrollar la aplicación. Puede componer las páginas a partir de unidades ya preparadas. ¿Necesita un datagrid en la administración? Búsquelo en [Componette |https://componette.org/search/component], un repositorio de complementos de código abierto (no solo componentes) para Nette, e insértelo sin más en el presenter. -Puede incorporar cualquier número de componentes en el presenter. Y en algunos componentes puede insertar otros componentes. Esto crea un árbol de componentes, cuya raíz es el presenter. +Puede incorporar al presenter cualquier número de componentes. Y dentro de algunos componentes puede insertar otros componentes. Así surge un árbol de componentes cuya raíz es el presenter. -Métodos de fábrica -================== +Métodos factory +=============== -¿Cómo se insertan los componentes en el presenter y se usan posteriormente? Generalmente mediante métodos de fábrica. +¿Cómo se insertan los componentes en el presenter y cómo se usan después? Normalmente mediante métodos factory. -Una fábrica de componentes es una forma elegante de crear componentes solo cuando realmente se necesitan (lazy / on demand). Toda la magia reside en la implementación de un método llamado `createComponent()`, donde `` es el nombre del componente a crear, y que crea y devuelve el componente. +Una factory de componentes es una forma elegante de crear los componentes solo cuando realmente hacen falta (lazy / on demand). Toda la magia consiste en implementar un método llamado `createComponent()`, donde `` es el nombre del componente que se crea, y que crea y devuelve dicho componente. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -31,49 +31,54 @@ class DefaultPresenter extends Nette\Application\UI\Presenter protected function createComponentPoll(): PollControl { $poll = new PollControl; - $poll->items = $this->item; + $poll->items = $this->items; return $poll; } } ``` -Gracias a que todos los componentes se crean en métodos separados, el código gana en claridad. +Como todos los componentes se crean en métodos separados, el código gana en claridad. .[note] -Los nombres de los componentes siempre comienzan con una letra minúscula, aunque en el nombre del método se escriban con mayúscula. +Los nombres de los componentes empiezan siempre por minúscula, aunque en el nombre del método se escriban con mayúscula. -Nunca llamamos a las fábricas directamente, se llaman solas en el momento en que usamos el componente por primera vez. Gracias a esto, el componente se crea en el momento adecuado y solo si realmente es necesario. Si no usamos el componente (por ejemplo, en una petición AJAX donde solo se transfiere una parte de la página, o al almacenar en caché la plantilla), no se crea en absoluto y ahorramos rendimiento del servidor. +Nunca llamamos a las factories directamente; se invocan solas la primera vez que usamos el componente. Gracias a eso, el componente se crea en el momento adecuado y solo si realmente hace falta. Si no usamos el componente (por ejemplo, en una petición AJAX en la que solo se transfiere una parte de la página, o al guardar la plantilla en caché), no se creará en absoluto, lo que ahorra rendimiento del servidor. ```php .{file:DefaultPresenter.php} -// accedemos al componente y si fue la primera vez, -// se llama a createComponentPoll() que lo crea +// accedemos al componente y, si es la primera vez, +// se llama a createComponentPoll(), que lo crea $poll = $this->getComponent('poll'); // sintaxis alternativa: $poll = $this['poll']; ``` -En la plantilla, es posible renderizar el componente usando la etiqueta [{control} |#Renderizado]. Por lo tanto, no es necesario pasar manualmente los componentes a la plantilla. +En la plantilla se puede renderizar un componente con la etiqueta [{control} |#Renderizado]. Por eso no hace falta pasar los componentes a la plantilla manualmente. ```latte -

    Votar

    +

    Please Vote

    {control poll} ``` +.[tip] +Para crear dinámicamente un número variable de componentes, use [Multiplier |multiplier]. + +Los métodos factory `createComponent()` no funcionan solo en los presenters. Del mismo modo puede anidar un componente dentro de otro componente y componerlos en un árbol, lo que resulta práctico, por ejemplo, para un formulario renderizado por separado dentro de un componente. + Estilo Hollywood ================ -Los componentes suelen utilizar una técnica fresca, que nos gusta llamar estilo Hollywood. Seguramente conoce la frase célebre que tan a menudo escuchan los participantes en las audiciones de cine: "No nos llame, nosotros le llamaremos". Y de eso se trata precisamente. +Los componentes usan habitualmente una técnica fresca que nos gusta llamar el estilo Hollywood. Seguro que conoce el tópico que oyen a menudo los participantes en los cástines de cine: "No nos llame, ya le llamaremos nosotros." Y de eso se trata exactamente. -En Nette, en lugar de tener que preguntar constantemente ("¿se envió el formulario?", "¿fue válido?" o "¿presionó el usuario este botón?"), le dice al framework "cuando suceda, llama a este método" y deja el resto del trabajo en él. Si programa en JavaScript, conoce íntimamente este estilo de programación. Escribe funciones que se llaman cuando ocurre un evento determinado. Y el lenguaje les pasa los parámetros apropiados. +En Nette, en lugar de tener que preguntar constantemente ("¿se ha enviado el formulario?", "¿era válido?" o "¿ha pulsado el usuario este botón?"), le dice al framework "cuando ocurra esto, llama a este método" y le deja a él el trabajo restante. Si programa en JavaScript, conoce a fondo este estilo de programación. Escribe funciones que se invocan cuando ocurre un determinado evento. Y el lenguaje les pasa los parámetros adecuados. -Esto cambia por completo la perspectiva sobre la escritura de aplicaciones. Cuantas más tareas pueda dejar en manos del framework, menos trabajo tendrá usted. Y menos cosas podrá olvidar. +Esto cambia por completo la perspectiva desde la que se escriben las aplicaciones. Cuantas más tareas pueda dejar al framework, menos trabajo tendrá. Y menos cosas se le podrán pasar por alto. -Escribiendo un componente -========================= +Escribir un componente +====================== -Bajo el término componente, generalmente nos referimos a un descendiente de la clase [api:Nette\Application\UI\Control]. (Por lo tanto, sería más preciso usar el término "controls", pero "controles" tiene un significado completamente diferente en español y más bien se ha impuesto "componentes".) El propio presenter [api:Nette\Application\UI\Presenter] es, por cierto, también un descendiente de la clase `Control`. +Con el término componente nos referimos normalmente a un descendiente de la clase [api:Nette\Application\UI\Control]. (Sería más exacto usar el término "controls", pero en algunos idiomas tiene otro significado y "componentes" se ha impuesto más.) El propio presenter [api:Nette\Application\UI\Presenter] es también descendiente de la clase `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -87,12 +92,12 @@ class PollControl extends Control Renderizado =========== -Ya sabemos que para renderizar un componente se usa la etiqueta `{control componentName}`. Esta en realidad llama al método `render()` del componente, en el que nos encargamos del renderizado. Tenemos disponible, al igual que en el presenter, una [plantilla Latte|templates] en la variable `$this->template`, a la que pasamos parámetros. A diferencia del presenter, debemos indicar el archivo con la plantilla y hacer que se renderice: +Ya sabemos que para renderizar un componente se usa la etiqueta `{control nombreComponente}`. En realidad llama al método `render()` del componente, en el que nos ocupamos del renderizado. Tenemos a disposición, igual que en el presenter, una [plantilla Latte|templates] en la variable `$this->template`, a la que pasamos los parámetros. A diferencia del presenter, debemos indicar el archivo de plantilla y mandar renderizarlo: ```php .{file:PollControl.php} public function render(): void { - // insertamos algunos parámetros en la plantilla + // pasamos algunos parámetros a la plantilla $this->template->param = $value; // y la renderizamos $this->template->render(__DIR__ . '/poll.latte'); @@ -112,7 +117,7 @@ public function render(int $id, string $message): void } ``` -A veces, un componente puede constar de varias partes que queremos renderizar por separado. Para cada una de ellas, creamos nuestro propio método de renderizado, aquí en el ejemplo, por ejemplo, `renderPaginator()`: +A veces un componente puede constar de varias partes que queremos renderizar por separado. Para cada una de ellas creamos su propio método de renderizado, aquí en el ejemplo `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,36 +126,36 @@ public function renderPaginator(): void } ``` -Y en la plantilla, luego la llamamos usando: +Y en la plantilla lo invocamos después con: ```latte {control poll:paginator} ``` -Para una mejor comprensión, es bueno saber cómo se traduce esta etiqueta a PHP. +Para entenderlo mejor, conviene saber cómo se traduce esta etiqueta a código PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -se traduce como: +se traduce a: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -El método `getComponent()` devuelve el componente `poll` y sobre este componente llama al método `render()`, respectivamente `renderPaginator()` si se indica otro método de renderizado en la etiqueta después de los dos puntos. +El método `getComponent()` devuelve el componente `poll` y sobre él se llama al método `render()`, o bien `renderPaginator()` si en la etiqueta se indica tras los dos puntos otro método de renderizado. .[caution] -Atención, si en cualquier lugar de los parámetros aparece **`=>`**, todos los parámetros se empaquetarán en un array y se pasarán como primer argumento: +Atención: si en los parámetros aparece **`=>`** fuera de corchetes, todos los parámetros se envolverán en un array y se pasarán como primer argumento: ```latte {control poll, id: 123, message: 'hello'} ``` -se traduce como: +se traduce a: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); @@ -162,28 +167,28 @@ Renderizado de un subcomponente: {control cartControl-someForm} ``` -se traduce como: +se traduce a: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Los componentes, al igual que los presenters, pasan automáticamente varias variables útiles a las plantillas: +Los componentes, igual que los presenters, pasan automáticamente a las plantillas varias variables útiles: -- `$basePath` es la ruta URL absoluta al directorio raíz (p. ej., `/eshop`) -- `$baseUrl` es la URL absoluta al directorio raíz (p. ej., `http://localhost/eshop`) -- `$user` es el objeto [que representa al usuario |security:authentication] +- `$basePath` es la ruta URL absoluta al directorio raíz (p. ej. `/eshop`) +- `$baseUrl` es la URL absoluta al directorio raíz (p. ej. `http://localhost/eshop`) +- `$user` es un objeto que [representa al usuario |security:authentication] - `$presenter` es el presenter actual - `$control` es el componente actual -- `$flashes` array de [mensajes |#Mensajes flash] enviados por la función `flashMessage()` +- `$flashes` es un array de [mensajes |#Mensajes flash] enviados con la función `flashMessage()` Señal ===== -Ya sabemos que la navegación en una aplicación Nette consiste en enlazar o redirigir a pares `Presenter:action`. Pero, ¿qué pasa si solo queremos realizar una acción en la **página actual**? Por ejemplo, cambiar el orden de las columnas en una tabla; eliminar un elemento; cambiar el modo claro/oscuro; enviar un formulario; votar en una encuesta; etc. +Ya sabemos que la navegación en una aplicación Nette consiste en enlazar o redirigir a pares `Presenter:action`. Pero ¿y si solo queremos realizar una acción en la **página actual**? Por ejemplo, cambiar la ordenación de las columnas de una tabla; borrar un elemento; cambiar entre modo claro y oscuro; enviar un formulario; votar en una encuesta; etc. -Este tipo de peticiones se llaman señales. Y de manera similar a como las acciones invocan los métodos `action()` o `render()`, las señales llaman a los métodos `handle()`. Mientras que el concepto de acción (o vista) está relacionado puramente con los presenters, las señales se aplican a todos los componentes. Y, por lo tanto, también a los presenters, porque `UI\Presenter` es un descendiente de `UI\Control`. +Este tipo de petición se llama señal. Y del mismo modo que las acciones invocan los métodos `action()` o `render()`, las señales llaman a los métodos `handle()`. Mientras que el concepto de acción (o vista) se refiere puramente a los presenters, las señales conciernen a todos los componentes. Y por tanto también a los presenters, porque `UI\Presenter` es descendiente de `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +197,36 @@ public function handleClick(int $x, int $y): void } ``` -El enlace que llama a la señal se crea de la manera habitual, es decir, en la plantilla con el atributo `n:href` o la etiqueta `{link}`, en el código con el método `link()`. Más en el capítulo [Creación de enlaces URL |creating-links#Enlaces a señal]. +Un enlace que llama a una señal se crea de la forma habitual, es decir, en la plantilla con el atributo `n:href` o la etiqueta `{link}`, y en el código con el método `link()`. Más en el capítulo [Creación de enlaces URL |creating-links#Enlaces a una señal]. ```latte -haz clic aquí +click here ``` -La señal siempre se llama en el presenter y la acción actuales, no es posible invocarla en otro presenter u otra acción. +Una señal se llama siempre sobre el presenter y la acción actuales; no es posible invocarla sobre otro presenter u otra acción. -Por lo tanto, la señal provoca la recarga de la página exactamente igual que en la petición original, solo que además llama al método de manejo de la señal con los parámetros correspondientes. Si el método no existe, se lanza una excepción [api:Nette\Application\UI\BadSignalException], que se muestra al usuario como una página de error 403 Forbidden. +La señal provoca, por tanto, la recarga de la página exactamente igual que la petición original, pero además llama al método de gestión de la señal con los parámetros adecuados. Si el método no existe, se lanza la excepción [api:Nette\Application\UI\BadSignalException], que se muestra al usuario como una página de error 403 Forbidden. -Fragmentos (Snippets) y AJAX -============================ +Snippets y AJAX +=============== -Las señales pueden recordarle un poco a AJAX: manejadores que se invocan en la página actual. Y tiene razón, las señales realmente se llaman a menudo usando AJAX y posteriormente transferimos al navegador solo las partes modificadas de la página. Es decir, los llamados fragmentos (snippets). Encontrará más información en la [página dedicada a AJAX |ajax]. +Las señales pueden recordarle un poco a AJAX: gestores que se invocan en la página actual. Y tiene razón, las señales se llaman de hecho a menudo mediante AJAX y a continuación solo se transfieren al navegador las partes modificadas de la página. Se llaman snippets. Encontrará más información en la [página dedicada a AJAX |ajax]. Mensajes flash ============== -El componente tiene su propio almacenamiento de mensajes flash independiente del presenter. Son mensajes que, por ejemplo, informan sobre el resultado de una operación. Una característica importante de los mensajes flash es que están disponibles en la plantilla incluso después de una redirección. Incluso después de mostrarse, permanecen activos durante otros 30 segundos, por ejemplo, en caso de que el usuario actualice la página debido a una transmisión errónea, el mensaje no desaparecerá de inmediato. +Un componente tiene su propio almacén de mensajes flash, independiente del presenter. Son mensajes que informan, por ejemplo, del resultado de una operación. Una propiedad importante de los mensajes flash es que están disponibles en la plantilla incluso después de una redirección. Incluso tras mostrarse siguen activos otros 30 segundos: por ejemplo, por si el usuario recarga la página debido a un error de transmisión, el mensaje no desaparecerá de inmediato. -El envío lo realiza el método [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. El primer parámetro es el texto del mensaje o un objeto `stdClass` que representa el mensaje. El segundo parámetro opcional es su tipo (error, warning, info, etc.). El método `flashMessage()` devuelve una instancia del mensaje flash como un objeto `stdClass`, al que se le puede agregar información adicional. +Del envío se ocupa el método [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. El primer parámetro es el texto del mensaje (`string`, `Stringable`) o un objeto `stdClass` que representa el mensaje. El segundo parámetro, opcional, es su tipo (error, warning, info, etc.). El método `flashMessage()` devuelve una instancia del mensaje flash como objeto `stdClass`, al que se le puede añadir más información. ```php -$this->flashMessage('El elemento ha sido eliminado.'); -$this->redirect(/* ... */); // y redirigimos +$this->flashMessage('Item was deleted.'); +$this->redirect(/* ... */); // y redirige ``` -En la plantilla, estos mensajes están disponibles en la variable `$flashes` como objetos `stdClass`, que contienen las propiedades `message` (texto del mensaje), `type` (tipo de mensaje) y pueden contener la información de usuario ya mencionada. Los renderizamos, por ejemplo, así: +Estos mensajes están disponibles en la plantilla en la variable `$flashes` como objetos `stdClass`, que contienen las propiedades `message` (texto del mensaje) y `type` (tipo del mensaje), y pueden contener la información de usuario ya mencionada. Los renderizamos, por ejemplo, así: ```latte {foreach $flashes as $flash} @@ -230,19 +235,19 @@ En la plantilla, estos mensajes están disponibles en la variable `$flashes` com ``` -Redirección después de una señal -================================ +Redirección tras procesar una señal +=================================== -Después de procesar una señal de componente, a menudo sigue una redirección. Es una situación similar a la de los formularios: después de enviarlos, también redirigimos para que al actualizar la página en el navegador no se vuelvan a enviar los datos. +Tras procesar la señal de un componente suele venir una redirección. Es algo parecido a los formularios: después de enviarlos también redirigimos, para evitar que se vuelvan a enviar los datos al recargar la página en el navegador. ```php -$this->redirect('this') // redirige al presenter y acción actuales +$this->redirect('this'); // redirige al presenter y la acción actuales ``` -Dado que el componente es un elemento reutilizable y generalmente no debería tener una vinculación directa con presenters específicos, los métodos `redirect()` y `link()` interpretan automáticamente el parámetro como una señal del componente: +Como un componente es un elemento reutilizable y normalmente no debería tener un vínculo directo con presenters concretos, los métodos `redirect()` y `link()` interpretan automáticamente el parámetro como una señal del componente: ```php -$this->redirect('click') // redirige a la señal 'click' del mismo componente +$this->redirect('click'); // redirige a la señal 'click' del mismo componente ``` Si necesita redirigir a otro presenter o acción, puede hacerlo a través del presenter: @@ -255,11 +260,11 @@ $this->getPresenter()->redirect('Product:show'); // redirige a otro presenter/ac Parámetros persistentes ======================= -Los parámetros persistentes sirven para mantener el estado en los componentes entre diferentes peticiones. Su valor permanece igual incluso después de hacer clic en un enlace. A diferencia de los datos en la sesión, se transfieren en la URL. Y esto de forma totalmente automática, incluidos los enlaces creados en otros componentes en la misma página. +Los parámetros persistentes sirven para mantener el estado de los componentes entre distintas peticiones. Su valor sigue siendo el mismo incluso después de pulsar un enlace. A diferencia de los datos de la sesión, se transfieren en la URL. Y esto ocurre de forma completamente automática, incluidos los enlaces creados en otros componentes de la misma página. -Tiene, por ejemplo, un componente para paginar contenido. Puede haber varios de estos componentes en una página. Y deseamos que después de hacer clic en un enlace, todos los componentes permanezcan en su página actual. Por lo tanto, hacemos que el número de página (`page`) sea un parámetro persistente. +Por ejemplo, tiene un componente para paginar contenido. Puede haber varios componentes así en una página. Y queremos que todos los componentes sigan en su página actual después de pulsar un enlace. Por eso convertimos el número de página (`page`) en un parámetro persistente. -Crear un parámetro persistente es extremadamente simple en Nette. Basta con crear una propiedad pública y marcarla con un atributo: (anteriormente se usaba `/** @persistent */`) +Crear un parámetro persistente en Nette es facilísimo. Basta con crear una propiedad pública y marcarla con el atributo: (antes se usaba `/** @persistent */`) ```php use Nette\Application\Attributes\Persistent; // esta línea es importante @@ -271,43 +276,43 @@ class PaginatingControl extends Control } ``` -Recomendamos indicar también el tipo de dato para la propiedad (p. ej., `int`) y puede indicar también un valor predeterminado. Los valores de los parámetros se pueden [validar |#Validación de parámetros persistentes]. +Recomendamos indicar el tipo de dato de la propiedad (p. ej. `int`), y también puede indicar un valor por defecto. Los valores de los parámetros se pueden [validar |#Validación de los parámetros persistentes]. -Al crear un enlace, se puede cambiar el valor del parámetro persistente: +Al crear un enlace se puede cambiar el valor de un parámetro persistente: ```latte -siguiente +next ``` -O se puede *resetear*, es decir, eliminar de la URL. Entonces tomará su valor predeterminado: +O se puede *resetear*, es decir, eliminar de la URL. Entonces adoptará su valor por defecto: ```latte -resetear +reset ``` Componentes persistentes ======================== -No solo los parámetros, sino también los componentes pueden ser persistentes. En tal componente, sus parámetros persistentes se transfieren también entre diferentes acciones del presenter o entre varios presenters. Marcamos los componentes persistentes con una anotación en la clase del presenter. Por ejemplo, así marcamos los componentes `calendar` y `poll`: +No solo los parámetros, también los componentes pueden ser persistentes. Sus parámetros persistentes se transfieren entonces incluso entre distintas acciones del presenter o entre varios presenters. Los componentes persistentes los marcamos con un atributo en la clase del presenter. Por ejemplo, marcamos así los componentes `calendar` y `poll`: ```php -/** - * @persistent(calendar, poll) - */ +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] class DefaultPresenter extends Nette\Application\UI\Presenter { } ``` -Los subcomponentes dentro de estos componentes no necesitan ser marcados, también se volverán persistentes. +No hace falta marcar los subcomponentes dentro de estos componentes; también pasan a ser persistentes. -En PHP 8, también puede usar atributos para marcar componentes persistentes: +La anotación antigua `@persistent` sigue funcionando, pero está obsoleta y provoca una advertencia: ```php -use Nette\Application\Attributes\Persistent; - -#[Persistent('calendar', 'poll')] +/** + * @persistent(calendar, poll) + */ class DefaultPresenter extends Nette\Application\UI\Presenter { } @@ -317,15 +322,15 @@ class DefaultPresenter extends Nette\Application\UI\Presenter Componentes con dependencias ============================ -¿Cómo crear componentes con dependencias sin "ensuciar" los presenters que los usarán? Gracias a las propiedades inteligentes del contenedor DI en Nette, al igual que al usar servicios clásicos, se puede dejar la mayor parte del trabajo al framework. +¿Cómo crear componentes con dependencias sin "ensuciar" los presenters que los van a usar? Gracias a las funciones inteligentes del contenedor DI de Nette, igual que ocurre con los servicios clásicos, se puede dejar la mayor parte del trabajo al framework. -Tomemos como ejemplo un componente que tiene una dependencia del servicio `PollFacade`: +Tomemos como ejemplo un componente que depende del servicio `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id de la encuesta para la que creamos el componente + private int $id, // ID de la encuesta para la que creamos el componente private PollFacade $facade, ) { } @@ -338,11 +343,11 @@ class PollControl extends Control } ``` -Si estuviéramos escribiendo un servicio clásico, no habría nada que resolver. El contenedor DI se encargaría invisiblemente de pasar todas las dependencias. Pero con los componentes, generalmente los tratamos de tal manera que creamos su nueva instancia directamente en el presenter en los [#métodos de fábrica] `createComponent…()`. Pero pasar todas las dependencias de todos los componentes al presenter para luego pasarlas a los componentes es engorroso. Y la cantidad de código escrito… +Si estuviéramos escribiendo un servicio clásico, no habría nada que discutir. El contenedor DI se encargaría de forma invisible de pasar todas las dependencias. Pero con los componentes solemos actuar creando una nueva instancia directamente en el presenter, dentro de los [métodos factory |#Métodos factory] `createComponent…()`. Pero pasar al presenter todas las dependencias de todos los componentes solo para pasárselas después a los componentes es engorroso. Y la cantidad de código que hay que escribir... -La pregunta lógica es, ¿por qué simplemente no registramos el componente como un servicio clásico, lo pasamos al presenter y luego lo devolvemos en el método `createComponent…()`? Sin embargo, tal enfoque es inapropiado, porque queremos poder crear el componente incluso varias veces. +La pregunta lógica es por qué no registramos simplemente el componente como un servicio clásico, se lo pasamos al presenter y luego lo devolvemos en el método `createComponent…()`. Este enfoque, sin embargo, no es adecuado, porque queremos poder crear el componente varias veces si hace falta. -La solución correcta es escribir una fábrica para el componente, es decir, una clase que nos cree el componente: +La solución correcta es escribir una factory para el componente, es decir, una clase que nos cree el componente: ```php class PollControlFactory @@ -359,7 +364,7 @@ class PollControlFactory } ``` -Así registramos la fábrica en nuestro contenedor en la configuración: +Registramos esta factory en nuestro contenedor en la configuración: ```neon services: @@ -384,7 +389,7 @@ class PollPresenter extends Nette\Application\UI\Presenter } ``` -Lo genial es que Nette DI puede [generar |dependency-injection:factory] tales fábricas simples, por lo que en lugar de todo su código, basta con escribir solo su interfaz: +Lo genial es que Nette DI sabe [generar |dependency-injection:factory] estas factories tan sencillas, de modo que en lugar de escribir todo su código basta con escribir su interfaz: ```php interface PollControlFactory @@ -393,21 +398,21 @@ interface PollControlFactory } ``` -Y eso es todo. Nette implementa internamente esta interfaz y la pasa al presenter, donde ya podemos usarla. Mágicamente, también agrega a nuestro componente el parámetro `$id` y la instancia de la clase `PollFacade`. +Y eso es todo. Nette implementa internamente esta interfaz y la inyecta en el presenter, donde podemos usarla. Añade mágicamente a nuestro componente el parámetro `$id` y una instancia de la clase `PollFacade`. -Componentes en profundidad -========================== +Los componentes en profundidad +============================== -Los componentes en Nette Application representan partes reutilizables de una aplicación web que insertamos en las páginas y a las que, por cierto, se dedica todo este capítulo. ¿Qué capacidades exactas tiene tal componente? +Los componentes en Nette Application representan partes reutilizables de una aplicación web que insertamos en las páginas, y a las que está dedicado todo este capítulo. ¿Qué es exactamente capaz de hacer un componente así? -1) es renderizable en la plantilla -2) sabe [qué parte suya |ajax#Fragmentos Snippets] debe renderizar en una petición AJAX (fragmentos) +1) se puede renderizar en una plantilla +2) sabe [qué parte de sí mismo |ajax#Snippets] debe renderizar durante una petición AJAX (snippets) 3) tiene la capacidad de guardar su estado en la URL (parámetros persistentes) 4) tiene la capacidad de reaccionar a las acciones del usuario (señales) -5) crea una estructura jerárquica (donde la raíz es el presenter) +5) crea una estructura jerárquica (cuya raíz es el presenter) -Cada una de estas funciones la realiza alguna de las clases de la línea de herencia. El renderizado (1 + 2) está a cargo de [api:Nette\Application\UI\Control], la inclusión en el [ciclo de vida |presenters#Ciclo de vida del presenter] (3, 4) de la clase [api:Nette\Application\UI\Component] y la creación de la estructura jerárquica (5) de las clases [Container y Component |component-model:]. +De cada una de estas funciones se encarga una de las clases de la línea de herencia. Del renderizado (1 + 2) se ocupa [api:Nette\Application\UI\Control], de la integración en el [ciclo de vida |presenters#Ciclo de vida del presenter] (3, 4) la clase [api:Nette\Application\UI\Component], y de la creación de la estructura jerárquica (5) las clases [Container y Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -428,12 +433,12 @@ Ciclo de vida del componente [* lifecycle-component.svg *] *** *Ciclo de vida del componente* .<> -Validación de parámetros persistentes -------------------------------------- +Validación de los parámetros persistentes +----------------------------------------- -Los valores de los [#parámetros persistentes] recibidos de la URL se escriben en las propiedades mediante el método `loadState()`. Este también comprueba si el tipo de dato indicado en la propiedad coincide, de lo contrario responde con un error 404 y la página no se muestra. +Los valores de los [parámetros persistentes |#Parámetros persistentes] recibidos de las URL los escribe en las propiedades el método `loadState()`. Este comprueba también si coinciden con el tipo de dato indicado en la propiedad; en caso contrario responde con un error 404 y la página no se muestra. -Nunca confíe ciegamente en los parámetros persistentes, ya que pueden ser fácilmente sobrescritos por el usuario en la URL. Así, por ejemplo, verificamos si el número de página `$this->page` es mayor que 0. Una forma adecuada es sobrescribir el método mencionado `loadState()`: +Nunca confíe ciegamente en los parámetros persistentes, porque el usuario puede sobrescribirlos fácilmente en la URL. Así comprobamos, por ejemplo, si el número de página `$this->page` es mayor que 0. Una forma adecuada es sobrescribir el mencionado método `loadState()`: ```php class PaginatingControl extends Control @@ -444,7 +449,7 @@ class PaginatingControl extends Control public function loadState(array $params): void { parent::loadState($params); // aquí se establece $this->page - // sigue la verificación propia del valor: + // sigue la comprobación propia del valor: if ($this->page < 1) { $this->error(); } @@ -452,27 +457,41 @@ class PaginatingControl extends Control } ``` -El proceso opuesto, es decir, la recopilación de valores de las propiedades persistentes, está a cargo del método `saveState()`. +El proceso inverso, es decir, la recogida de los valores de las propiedades persistentes, lo realiza el método `saveState()`. + + +Conexión con el presenter +------------------------- + +En el momento en que un componente pasa a formar parte de la jerarquía del presenter, se invocan sus callbacks guardados en el array `$onAnchor`. A partir de ese momento el componente tiene el presenter a su disposición, puede crear enlaces con seguridad, leer parámetros persistentes, etc. + +```php +$control->onAnchor[] = function ($control): void { + // el componente ya tiene el presenter a su disposición +}; +``` + +Las señales en profundidad +-------------------------- -Señales en profundidad ----------------------- +La señal provoca la recarga de la página exactamente igual que la petición original (salvo cuando se llama por AJAX) e invoca el método `signalReceived($signal)`, cuya implementación por defecto en la clase `Nette\Application\UI\Component` intenta llamar a un método compuesto por las palabras `handle`. El procesamiento posterior depende del objeto en cuestión. Los objetos que heredan de `Component` (es decir, `Control` y `Presenter`) reaccionan intentando llamar al método `handle` con los parámetros adecuados. -Una señal provoca la recarga de la página exactamente igual que en la petición original (excepto en el caso de que se llame por AJAX) e invoca el método `signalReceived($signal)`, cuya implementación predeterminada en la clase `Nette\Application\UI\Component` intenta llamar a un método compuesto por las palabras `handle{signal}`. El procesamiento posterior depende del objeto en cuestión. Los objetos que heredan de `Component` (es decir, `Control` y `Presenter`) reaccionan intentando llamar al método `handle{signal}` con los parámetros correspondientes. +Dicho de otro modo: se toma la definición de la función `handle` junto con todos los parámetros que llegaron con la petición, a los argumentos se les asignan por nombre los parámetros de la URL y se intenta llamar al método. Por ejemplo, el valor del parámetro `id` de la URL se pasa como argumento `$id`, `something` de la URL se pasa como `$something`, etc. Y si el método no existe, el método `signalReceived` lanza una [excepción |api:Nette\Application\UI\BadSignalException]. -En otras palabras: se toma la definición de la función `handle{signal}` y todos los parámetros que llegaron con la petición, y a los argumentos se les asignan los parámetros de la URL según el nombre e intenta llamar al método dado. Por ejemplo, como parámetro `$id` se pasa el valor del parámetro `id` en la URL, como `$something` se pasa `something` de la URL, etc. Y si el método no existe, el método `signalReceived` lanza una [excepción |api:Nette\Application\UI\BadSignalException]. +Además de los parámetros de la URL, la señal lee también los parámetros enviados en el **cuerpo POST de la petición**. Esto viene bien porque las señales se invocan a menudo mediante JavaScript, donde es natural enviar los datos por el método POST. Sin embargo, si llega un parámetro con el mismo nombre tanto de la URL como del cuerpo POST, tiene preferencia el valor **de la URL**. Por eso, evite dar a un campo POST el mismo nombre que a un parámetro de la URL o de la ruta; de lo contrario, el valor de la URL lo sobrescribiría silenciosamente. Los parámetros de las señales comparten un espacio común con los parámetros de acción y los persistentes, véase [Espacio común de parámetros |presenters#Espacio común de parámetros]. -La señal puede ser recibida por cualquier componente, presenter u objeto que implemente la interfaz `SignalReceiver` y esté conectado al árbol de componentes. +Una señal puede recibirla cualquier componente, presenter u objeto que implemente la interfaz `SignalReceiver` y esté conectado al árbol de componentes. -Los principales receptores de señales serán los `Presenters` y los componentes visuales que heredan de `Control`. La señal debe servir como una indicación para el objeto de que debe hacer algo: la encuesta debe contar el voto del usuario, el bloque de noticias debe expandirse y mostrar el doble de noticias, el formulario se envió y debe procesar los datos, y así sucesivamente. +Los principales destinatarios de las señales serán los `Presenters` y los componentes visuales que heredan de `Control`. La señal está pensada como una indicación al objeto de que debe hacer algo: la encuesta debe contar el voto del usuario, el bloque de noticias debe expandirse y mostrar el doble de noticias, el formulario se ha enviado y debe procesar los datos, etc. -La URL para la señal la creamos usando el método [Component::link() |api:Nette\Application\UI\Component::link()]. Como parámetro `$destination` pasamos la cadena `{signal}!` y como `$args` un array de argumentos que queremos pasar a la señal. La señal siempre se llama en el presenter y acción actuales con los parámetros actuales, los parámetros de la señal simplemente se agregan. Además, se agrega al principio el **parámetro `?do`, que determina la señal**. +La URL de una señal se crea con el método [Component::link() |api:Nette\Application\UI\Component::link()]. Como parámetro `$destination` pasamos la cadena `{signal}!` y como `$args` un array de argumentos que queremos pasar a la señal. La señal se llama siempre sobre el presenter y la acción actuales con los parámetros actuales; los parámetros de la señal solo se añaden. Además se añade el **parámetro `?do`, que indica la señal**. -Su formato es `{signal}` o `{signalReceiver}-{signal}`. `{signalReceiver}` es el nombre del componente en el presenter. Por eso no puede haber un guion en el nombre del componente; se usa para separar el nombre del componente y la señal, sin embargo, es posible anidar varios componentes de esta manera. +Su formato es `{signal}` o `{signalReceiver}-{signal}`. `{signalReceiver}` es el nombre del componente en el presenter. Por eso no puede usarse un guion en el nombre del componente: sirve para separar el nombre del componente y la señal, aunque de esta forma sea posible anidar varios componentes. -El método [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] verifica si el componente (primer argumento) es el receptor de la señal (segundo argumento). Podemos omitir el segundo argumento; entonces verifica si el componente es receptor de cualquier señal. Como segundo parámetro se puede indicar `true` y así verificar si el receptor no es solo el componente indicado, sino también cualquiera de sus descendientes. +El método [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] comprueba si el componente (primer argumento) es el destinatario de la señal (segundo argumento). El segundo argumento se puede omitir; entonces comprueba si el componente es el destinatario de alguna señal. Si el segundo parámetro se pone a `true`, verifica si el componente indicado o alguno de sus descendientes es el destinatario. -En cualquier fase anterior a `handle{signal}` podemos ejecutar la señal manualmente llamando al método [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], que se encarga de gestionar la señal: toma el componente que se determinó como receptor de la señal (si no se especifica un receptor de señal, es el propio presenter) y le envía la señal. +En cualquier fase previa a `handle` podemos ejecutar la señal manualmente llamando al método [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], que se ocupa de gestionar la señal: toma el componente identificado como destinatario de la señal (si no se indica ningún destinatario, es el propio presenter) y le envía la señal. Ejemplo: @@ -482,4 +501,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -De esta manera, la señal se ejecuta prematuramente y ya no se volverá a llamar. +Así se ejecuta la señal prematuramente y ya no se volverá a llamar. diff --git a/application/es/configuration.texy b/application/es/configuration.texy index 9496f35bd5..9ff649801c 100644 --- a/application/es/configuration.texy +++ b/application/es/configuration.texy @@ -1,8 +1,8 @@ -Configuración de aplicaciones -***************************** +Configuración de la aplicación +****************************** .[perex] -Resumen de las opciones de configuración para las aplicaciones Nette. +Resumen de las opciones de configuración de Nette Application. Application @@ -10,62 +10,64 @@ Application ```neon application: - # mostrar el panel "Nette Application" en Tracy BlueScreen? - debugger: ... # (bool) predeterminado es true + # ¿mostrar el panel "Nette Application" en la BlueScreen de Tracy? + debugger: ... # (bool) activado si Tracy está disponible - # se llamará al error-presenter en caso de error? - # solo tiene efecto en modo de desarrollo - catchExceptions: ... # (bool) predeterminado es true + # en producción, de las excepciones se ocupa siempre el error-presenter; + # esta opción solo activa ese comportamiento también en modo de desarrollo + catchExceptions: ... # (bool) el valor predeterminado es false, es decir, off en dev y siempre on en producción # nombre del error-presenter - errorPresenter: Error # (string|array) predeterminado es 'Nette:Error' + errorPresenter: Error # (string|array) el valor predeterminado es 'Nette:Error' - # define alias para presenters y acciones + # define alias para los presenters y las acciones aliases: ... - # define reglas para traducir el nombre del presenter a una clase + # define las reglas para traducir el nombre del presenter a una clase mapping: ... - # los enlaces erróneos no generan advertencias? + # ¿suprimir las advertencias de los enlaces no válidos? # solo tiene efecto en modo de desarrollo - silentLinks: ... # (bool) predeterminado es false + silentLinks: ... # (bool) el valor predeterminado es false ``` -Desde la versión 3.2 de `nette/application`, se puede definir un par de error-presenters: +Desde la versión 3.2 de `nette/application` es posible definir un par de error-presenters: ```neon application: errorPresenter: - 4xx: Error4xx # para la excepción Nette\Application\BadRequestException - 5xx: Error5xx # para otras excepciones + 4xx: Error4xx # para Nette\Application\BadRequestException + 5xx: Error5xx # para las demás excepciones ``` -La opción `silentLinks` determina cómo se comporta Nette en modo de desarrollo cuando falla la generación de un enlace (por ejemplo, porque no existe el presenter, etc.). El valor predeterminado `false` significa que Nette lanzará un error `E_USER_WARNING`. Establecerlo en `true` suprimirá este mensaje de error. En el entorno de producción, siempre se lanza `E_USER_WARNING`. Este comportamiento también se puede influir estableciendo la variable del presenter [$invalidLinkMode |creating-links#Enlaces no válidos]. +Separarlos resulta útil porque las dos situaciones son radicalmente distintas. Una `BadRequestException` (códigos 4xx) significa que la aplicación está bien y que el visitante simplemente ha pedido algo que no existe. Puede usar entonces un presenter con todas sus funciones, que muestre un mensaje amable dentro del layout de su web. Un error 5xx, en cambio, significa que algo se ha roto en la aplicación y no sabe qué. Mantenga el presenter 5xx lo más mínimo posible, para que nada más pueda fallar mientras se renderiza; lo ideal es que no toque la base de datos, ni el layout, ni el usuario conectado. + +La opción `silentLinks` determina cómo se comporta Nette en modo de desarrollo cuando falla la generación de un enlace (por ejemplo, porque el presenter no existe, etc.). El valor predeterminado `false` significa que Nette lanza un error `E_USER_WARNING`. Ponerla a `true` silencia ese mensaje de error. En un entorno de producción se lanza siempre `E_USER_WARNING`. Este comportamiento también se puede influir con la variable del presenter [$invalidLinkMode |creating-links#Enlaces no válidos]. -Los [Alias simplifican el enlace |creating-links#Alias] a presenters de uso frecuente. +Los [alias simplifican la referencia |creating-links#Alias] a los presenters de uso frecuente. -El [Mapeo define reglas |directory-structure#Mapeo de presenters] según las cuales se deriva el nombre de la clase a partir del nombre del presenter. +El [mapping define las reglas |directory-structure#Mapeo de presenters] por las que se deriva el nombre de la clase a partir del nombre del presenter. Registro automático de presenters --------------------------------- -Nette agrega automáticamente los presenters como servicios al contenedor DI, lo que acelera significativamente su creación. Cómo Nette busca los presenters se puede configurar: +Nette añade automáticamente los presenters como servicios al contenedor DI, lo que acelera notablemente su creación. La forma en que Nette localiza los presenters se puede configurar: ```neon application: - # buscar presenters en el mapa de clases de Composer? - scanComposer: ... # (bool) predeterminado es true + # ¿buscar los presenters en el class map de Composer? + scanComposer: ... # (bool) el valor predeterminado es true - # máscara que debe cumplir el nombre de la clase y el archivo - scanFilter: ... # (string) predeterminado es '*Presenter' + # máscara con la que deben encajar el nombre de la clase y el del archivo + scanFilter: ... # (string) el valor predeterminado es '*Presenter' - # en qué directorios buscar presenters? - scanDirs: # (string[]|false) predeterminado es '%appDir%' + # ¿en qué directorios buscar los presenters? + scanDirs: # (string[]|false) el valor predeterminado es '%appDir%' - %vendorDir%/mymodule ``` -Los directorios indicados en `scanDirs` no sobrescriben el valor predeterminado `%appDir%`, sino que lo complementan, por lo que `scanDirs` contendrá ambas rutas `%appDir%` y `%vendorDir%/mymodule`. Si quisiéramos omitir el directorio predeterminado, usaríamos un [signo de exclamación |dependency-injection:configuration#Fusión], que sobrescribe el valor: +Los directorios indicados en `scanDirs` no sustituyen al valor predeterminado `%appDir%`, sino que lo complementan, así que `scanDirs` contendrá ambas rutas: `%appDir%` y `%vendorDir%/mymodule`. Si queremos omitir el directorio predeterminado, usamos un [signo de exclamación |dependency-injection:configuration#Fusión]: ```neon application: @@ -73,36 +75,42 @@ application: - %vendorDir%/mymodule ``` -El escaneo de directorios se puede desactivar indicando el valor false. No recomendamos suprimir por completo la adición automática de presenters, ya que de lo contrario se reducirá el rendimiento de la aplicación. +El escaneo de directorios se puede desactivar poniendo el valor a `false`. Los presenters dejan entonces de registrarse como servicios, por lo que no se pueden ajustar mediante la sección [decorator |dependency-injection:configuration#Decorator] y su creación es más lenta. Por eso no recomendamos suprimir del todo el registro automático: reduciría el rendimiento de la aplicación. Plantillas Latte ================ -Con esta configuración, se puede influir globalmente en el comportamiento de Latte en componentes y presenters. +Este ajuste afecta globalmente al comportamiento de Latte en los componentes y los presenters. ```neon latte: - # mostrar el panel Latte en Tracy Bar para la plantilla principal (true) o todos los componentes (all)? - debugger: ... # (true|false|'all') predeterminado es true + # ¿mostrar el panel de Latte en la Tracy Bar para la plantilla principal (true) o para todos los componentes (all)? + debugger: ... # (true|false|'all') activado si Tracy está disponible (solo en modo de depuración) + + # genera las plantillas con la cabecera declare(strict_types=1) + strictTypes: ... # (bool) el valor predeterminado es false - # genera plantillas con la cabecera declare(strict_types=1) - strictTypes: ... # (bool) predeterminado es false + # activa el [modo estricto del parser |latte:develop#strict mode] + strictParsing: ... # (bool) el valor predeterminado es false - # activa el modo de [parser estricto |latte:develop#striktní režim] - strictParsing: ... # (bool) predeterminado es false + # limita el alcance de las variables al cuerpo del bucle + scopedLoopVariables: ... # (bool) el valor predeterminado es false - # activa la [verificación del código generado |latte:develop#Kontrola vygenerovaného kódu] - phpLinter: ... # (string) predeterminado es null + # elimina la indentación causada por el anidamiento en etiquetas pareadas + dedent: ... # (bool) el valor predeterminado es false - # establece la configuración regional - locale: cs_CZ # (string) predeterminado es null + # activa la [comprobación del código generado |latte:develop#Checking Generated Code] + phpLinter: ... # (string) el valor predeterminado es null + + # establece el locale + locale: cs_CZ # (string) el valor predeterminado es null # clase del objeto $this->template - templateClass: App\MyTemplateClass # predeterminado es Nette\Bridges\ApplicationLatte\DefaultTemplate + templateClass: App\MyTemplateClass # el valor predeterminado es Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Si usa Latte versión 3, puede agregar nuevas [extensiones |latte:extending-latte#Latte Extension] usando: +Puede añadir nuevas [extensiones |latte:extending-latte#Latte Extension] así: ```neon latte: @@ -110,36 +118,22 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Si usa Latte versión 2, puede registrar nuevas etiquetas (macros) ya sea indicando el nombre de la clase o una referencia al servicio. Por defecto, se llama al método `install()`, pero esto se puede cambiar indicando el nombre de otro método: - -```neon -latte: - # registro de etiquetas Latte personalizadas - macros: - - App\MyLatteMacros::register # método estático, nombre de clase o callable - - @App\MyLatteMacrosFactory # servicio con método install() - - @App\MyLatteMacrosFactory::register # servicio con método register() - -services: - - App\MyLatteMacrosFactory -``` - Enrutamiento ============ -Configuración básica: +Ajustes básicos: ```neon routing: - # mostrar el panel de enrutamiento en Tracy Bar? - debugger: ... # (bool) predeterminado es true + # ¿mostrar el panel de enrutamiento en la Tracy Bar? + debugger: ... # (bool) activado si Tracy está disponible (solo en modo de depuración) # serializa el router en el contenedor DI - cache: ... # (bool) predeterminado es false + cache: ... # (bool) el valor predeterminado es false ``` -El enrutamiento generalmente lo definimos en la clase [RouterFactory |routing#Colección de rutas]. Alternativamente, las rutas también se pueden definir en la configuración usando pares `máscara: acción`, pero este método no ofrece una variabilidad tan amplia en la configuración: +El enrutamiento se define normalmente en la clase [RouterFactory |routing#Colección de rutas]. Como alternativa, las rutas también se pueden definir en la configuración mediante parejas `máscara: acción`, pero este método no ofrece mucha flexibilidad: ```neon routing: @@ -152,23 +146,23 @@ routing: Constantes ========== -Creación de constantes PHP. +Creación de constantes de PHP. ```neon constants: Foobar: 'baz' ``` -Después de iniciar la aplicación, se creará la constante `Foobar`. +La constante `Foobar` se creará al arrancar la aplicación. .[note] -Las constantes no deben servir como variables disponibles globalmente. Para pasar valores a objetos, utilice la [inyección de dependencias |dependency-injection:passing-dependencies]. +Las constantes no deberían servir de variables globalmente accesibles. Para pasar valores a los objetos, use la [inyección de dependencias |dependency-injection:passing-dependencies]. PHP === -Configuración de directivas PHP. Un resumen de todas las directivas se encuentra en [php.net |https://www.php.net/manual/en/ini.list.php]. +Ajuste de las directivas de PHP. Encontrará un resumen de todas las directivas en [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: @@ -179,13 +173,14 @@ php: Servicios DI ============ -Estos servicios se agregan al contenedor DI: - -| Nombre | Tipo | Descripción -|---------------------------------------------------------- -| `application.application` | [api:Nette\Application\Application] | [ejecutor de toda la aplicación |how-it-works#Nette Application] -| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] -| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | fábrica de presenters -| `application.###` | [api:Nette\Application\UI\Presenter] | presenters individuales -| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | fábrica del objeto `Latte\Engine` -| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | fábrica para [`$this->template` |templates] +Estos servicios se añaden al contenedor DI: + +| Nombre | Tipo | Descripción +|----------------------------|---------------------------------------------------|----------------------------------------- +| `application.application` | [api:Nette\Application\Application] | el [lanzador de la aplicación |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\IPresenterFactory] | factory de presenters +| `application.###` | [api:Nette\Application\UI\Presenter] | los distintos presenters +| `routing.router` | [api:Nette\Routing\Router] | router +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | factory del objeto `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | factory de [`$this->template` |templates] diff --git a/application/es/creating-links.texy b/application/es/creating-links.texy index 76be2a6ad9..11815e20ca 100644 --- a/application/es/creating-links.texy +++ b/application/es/creating-links.texy @@ -3,132 +3,149 @@ Creación de enlaces URL
    -Crear enlaces en Nette es tan simple como señalar con el dedo. Solo necesita apuntar y el framework hará todo el trabajo por usted. Mostraremos: +Crear enlaces en Nette es tan fácil como señalar con el dedo. Basta con apuntar y el framework hará todo el trabajo por usted. Veremos: -- cómo crear enlaces en plantillas y en otros lugares +- cómo crear enlaces en las plantillas y fuera de ellas - cómo distinguir un enlace a la página actual - qué hacer con los enlaces no válidos
    -Gracias al [enrutamiento bidireccional |routing], nunca tendrá que escribir direcciones URL de su aplicación directamente en plantillas o código, que pueden cambiar más tarde, o componerlas de forma complicada. En el enlace, basta con indicar el presenter y la acción, pasar los parámetros necesarios y el framework generará la URL por sí mismo. De hecho, es muy similar a llamar a una función. Esto le gustará. +Gracias al [enrutamiento bidireccional |routing] nunca tendrá que escribir a fuego en las plantillas o en el código las URL de su aplicación, que quizá cambien más adelante o resulten complicadas de componer. En el enlace basta con indicar el presenter y la acción, pasar los parámetros que hagan falta, y el framework generará la URL por sí solo. En realidad es muy parecido a llamar a una función. Le va a gustar. En la plantilla del presenter ============================= -La mayoría de las veces creamos enlaces en plantillas y un excelente ayudante es el atributo `n:href`: +Lo más habitual es crear los enlaces en las plantillas, y el atributo `n:href` es una gran ayuda: ```latte -detalle +detail ``` -Observe que en lugar del atributo HTML `href`, usamos el [n:atributo |latte:syntax#n:atributos] `n:href`. Su valor no es una URL, como sería el caso del atributo `href`, sino el nombre del presenter y la acción. +Fíjese en que, en lugar del atributo HTML `href`, hemos usado el [n:atributo |latte:syntax#n:atributos] `n:href`. Su valor no es una URL, como ocurriría con el atributo `href`, sino el nombre del presenter y la acción. -Hacer clic en un enlace es, simplificando, algo así como llamar al método `ProductPresenter::renderShow()`. Y si tiene parámetros en su firma, podemos llamarlo con argumentos: +Pulsar un enlace es, dicho de forma sencilla, algo así como llamar al método `ProductPresenter::renderShow()`. Y si tiene parámetros en su firma, podemos llamarlo con argumentos: ```latte -detalle del producto +product detail ``` -También es posible pasar parámetros con nombre. El siguiente enlace pasa el parámetro `lang` con el valor `cs`: +También es posible pasar parámetros nombrados. El siguiente enlace pasa el parámetro `lang` con el valor `en`: ```latte -detalle del producto +product detail ``` -Si el método `ProductPresenter::renderShow()` no tiene `$lang` en su firma, puede obtener el valor del parámetro usando `$lang = $this->getParameter('lang')` o desde la [propiedad |presenters#Parámetros de la petición]. +Si el método `ProductPresenter::renderShow()` no tiene `$lang` en su firma, puede obtener el valor del parámetro con `$lang = $this->getParameter('lang')` o desde una [propiedad |presenters#Parámetros de la petición]. -Si los parámetros están almacenados en un array, se pueden expandir con el operador `...` (en Latte 2.x con el operador `(expand)`): +Si los parámetros están guardados en un array, se pueden expandir con el operador `...`: ```latte -{var $args = [$product->id, lang => cs]} -detalle del producto +{var $args = [$product->id, lang => en]} +product detail ``` -En los enlaces también se pasan automáticamente los llamados [parámetros persistentes |presenters#Parámetros persistentes]. +Los llamados [parámetros persistentes |presenters#Parámetros persistentes] también se pasan automáticamente en los enlaces. -El atributo `n:href` es muy útil para las etiquetas HTML ``. Si queremos mostrar el enlace en otro lugar, por ejemplo en el texto, usamos `{link}`: +El atributo `n:href` resulta muy práctico en las etiquetas HTML ``. Si queremos imprimir el enlace en otro sitio, por ejemplo dentro de un texto, usamos `{link}`: ```latte -La dirección es: {link Home:default} +URL is: {link Home:default} ``` En el código ============ -Para crear un enlace en el presenter, se utiliza el método `link()`: +Para crear un enlace en el presenter se usa el método `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Los parámetros también se pueden pasar mediante un array, donde también se pueden indicar parámetros con nombre: +Los parámetros también se pueden pasar como array, donde además se pueden indicar parámetros nombrados: ```php -$url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$product->id, 'lang' => 'en']); ``` -Los enlaces también se pueden crear sin un presenter, para eso está [#LinkGenerator] y su método `link()`. +Los enlaces también se pueden crear sin presenter, con el [#LinkGenerator] y su método `link()`. +A veces necesita crear un enlace ahora pero generar la URL real más tarde. Para eso está el método `lazyLink()`, que devuelve un objeto `Nette\Application\UI\Link`. La ventaja es que puede pasar ese objeto de un sitio a otro, por ejemplo a una plantilla, y antes de que se renderice todavía puede ajustar sus parámetros con el método `setParameter()`. La URL propiamente dicha se compone solo cuando el objeto se convierte en cadena: -Enlaces a presenter -=================== +```php +$link = $this->lazyLink('Product:show', $id); +// ... +echo $link; // la URL se genera solo aquí +``` + + +Enlaces a un presenter +====================== -Si el destino del enlace es un presenter y una acción, tiene esta sintaxis: +Si el destino del enlace es un presenter y una acción, la sintaxis es esta: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -El formato es compatible con todas las etiquetas Latte y todos los métodos del presenter que trabajan con enlaces, es decir, `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` y también [#LinkGenerator]. Así que, aunque en los ejemplos se use `n:href`, podría estar cualquiera de las funciones. +Este formato lo admiten todas las etiquetas de Latte y todos los métodos del presenter que trabajan con enlaces, es decir, `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` y también el [#LinkGenerator]. Así que, aunque en los ejemplos se use `n:href`, ahí podría estar cualquiera de esas funciones. -La forma básica es, por lo tanto, `Presenter:action`: +La forma básica es, por tanto, `Presenter:acción`: ```latte -página de inicio +home page ``` Si enlazamos a una acción del presenter actual, podemos omitir su nombre: ```latte -página de inicio +home page ``` -Si el destino es la acción `default`, podemos omitirla, pero los dos puntos deben permanecer: +Si la acción de destino es `default`, la podemos omitir, pero los dos puntos deben quedarse: ```latte -página de inicio +home page ``` -Los enlaces también pueden apuntar a otros [módulos |directory-structure#Presenters y plantillas]. Aquí, los enlaces se distinguen entre relativos a un submódulo anidado o absolutos. El principio es análogo a las rutas en el disco, solo que en lugar de barras inclinadas hay dos puntos. Supongamos que el presenter actual es parte del módulo `Front`, entonces escribimos: +Los enlaces también pueden apuntar a otros [módulos |directory-structure#Presenters y plantillas]. Aquí se distingue entre enlaces relativos a un submódulo anidado y enlaces absolutos. El principio es análogo al de las rutas de disco, solo que en lugar de barras se usan dos puntos. Suponiendo que el presenter actual forme parte del módulo `Front`, escribiríamos: ```latte -enlace a Front:Shop:Product:show -enlace a Admin:Product:show +link to Front:Shop:Product:show +link to Admin:Product:show ``` -Un caso especial es un enlace [a sí mismo |#Enlace a la página actual], donde indicamos `this` como destino. +Un caso especial es el enlace [a sí mismo |#Enlace a la página actual], donde indicamos `this` como destino. ```latte -refrescar +refresh ``` -Podemos enlazar a una parte específica de la página a través del llamado fragmento después del signo de almohadilla `#`: +Podemos enlazar a una parte concreta de la página mediante un fragmento tras el signo de almohadilla `#`: ```latte -enlace a Home:default y fragmento #main +link to Home:default and fragment #main +``` + +.{data-version:3.3.0} +El fragmento también se puede fijar dinámicamente como argumento con la clave `#`. Su valor se codifica automáticamente y tiene prioridad sobre el fragmento indicado en el destino: + +```php +$this->link('Home:default', ['#' => $fragment]); ``` Rutas absolutas =============== -Los enlaces generados mediante `link()` o `n:href` son siempre rutas absolutas (es decir, comienzan con el carácter `/`), pero no URL absolutas con protocolo y dominio como `https://domain`. +Los enlaces generados con `link()` o `n:href` son siempre rutas absolutas (es decir, empiezan por `/`), pero no URL absolutas con protocolo y dominio, como `https://domain`. + +Para generar una URL absoluta, añada dos barras al principio (por ejemplo, `n:href="//Home:"`). Como alternativa, puede hacer que el presenter genere solo enlaces absolutos poniendo `$this->absoluteUrls = true`. -Para generar una URL absoluta, agregue dos barras inclinadas al principio (p. ej., `n:href="//Home:"`). O se puede cambiar el presenter para que genere solo enlaces absolutos estableciendo `$this->absoluteUrls = true`. +En la plantilla también se puede usar el filtro `|absoluteUrl` para convertir una ruta relativa en absoluta. Enlace a la página actual @@ -137,24 +154,24 @@ Enlace a la página actual El destino `this` crea un enlace a la página actual: ```latte -refrescar +refresh ``` -Al mismo tiempo, se transfieren todos los parámetros indicados en la firma del método `action()` o `render()`, si `action()` no está definida. Así que si estamos en la página `Product:show` e `id: 123`, el enlace a `this` también pasará este parámetro. +Al mismo tiempo se transfieren todos los parámetros indicados en la firma del método `action()` o `render()` (si `action()` no está definido). Así, si estamos en la página `Product:show` con `id: 123`, el enlace a `this` pasará también ese parámetro. -Por supuesto, es posible especificar los parámetros directamente: +Por supuesto, es posible indicar los parámetros directamente: ```latte -refrescar +refresh ``` -La función `isLinkCurrent()` comprueba si el destino del enlace coincide con la página actual. Esto se puede usar, por ejemplo, en la plantilla para distinguir enlaces, etc. +La función `isLinkCurrent()` comprueba si el destino del enlace es idéntico a la página actual. Esto se puede usar, por ejemplo, en una plantilla para distinguir los enlaces, etc. -Los parámetros son los mismos que en el método `link()`, pero además es posible indicar un comodín `*` en lugar de una acción específica, que significa cualquier acción del presenter dado. +Los parámetros son los mismos que los del método `link()`, pero además es posible usar el comodín `*` en lugar de una acción concreta, lo que significa cualquier acción del presenter dado. ```latte {if !isLinkCurrent('Admin:login')} - Inicie sesión + Login {/if}
  • @@ -162,15 +179,15 @@ Los parámetros son los mismos que en el método `link()`, pero además es posib
  • ``` -En combinación con `n:href` en un solo elemento, se puede usar una forma abreviada: +Combinado con `n:href` en un mismo elemento, se puede usar una forma abreviada: ```latte ... ``` -El comodín `*` solo se puede usar en lugar de la acción, nikoliv presenteru. +El comodín `*` solo se puede usar en lugar de la acción, no del presenter. -Para determinar si estamos en un módulo específico o en su submódulo, usamos el método `isModuleCurrent(moduleName)`. +Para saber si estamos en un módulo concreto o en uno de sus submódulos, use el método `isModuleCurrent(moduleName)`. ```latte
  • @@ -179,41 +196,57 @@ Para determinar si estamos en un módulo específico o en su submódulo, usamos ``` -Enlaces a señal -=============== +Cambio de la base de los enlaces .{data-version:3.2.7} +====================================================== + +De forma predeterminada, los enlaces relativos se derivan del presenter actual. Esto se puede cambiar con `{linkBase}`: + +```latte +{linkBase Admin:Dashboard} +product detail +``` + +El enlace llevará a `Admin:Dashboard:Product:show`. Solo se ven afectados los enlaces relativos: los absolutos, que empiezan por dos puntos, y los enlaces al presenter actual (`this`, `show`) quedan sin cambios. + +`{linkBase}` vale para toda la plantilla y resulta especialmente útil en las plantillas de layout, donde garantiza enlaces coherentes con independencia del presenter que las use. +La etiqueta debe colocarse al principio de la plantilla, o lanzará una `CompileException`. + + +Enlaces a una señal +=================== -El destino de un enlace no tiene por qué ser solo un presenter y una acción, sino también una [señal |components#Señal] (llaman al método `handle()`). Entonces la sintaxis es la siguiente: +El destino de un enlace no tiene por qué ser solo un presenter y una acción, sino también una [señal |components#Señal] (que llama al método `handle()`). La sintaxis es entonces esta: ``` [//] [sub-component:]signal! [#fragment] ``` -La señal se distingue por el signo de exclamación: +La señal se distingue, pues, por el signo de exclamación: ```latte -señal +signal ``` -También se puede crear un enlace a la señal de un subcomponente (o sub-subcomponente): +También puede crear un enlace a la señal de un subcomponente (o de un subsubcomponente): ```latte -señal +signal ``` -Enlaces en componente -===================== +Enlaces en un componente +======================== -Dado que los [componentes|components] son unidades reutilizables independientes que no deberían tener ninguna vinculación con los presenters circundantes, los enlaces funcionan aquí de manera un poco diferente. El atributo Latte `n:href` y la etiqueta `{link}`, así como los métodos del componente como `link()` y otros, consideran el destino del enlace **siempre como el nombre de la señal**. Por lo tanto, ni siquiera es necesario indicar el signo de exclamación: +Como los [componentes|components] son unidades independientes y reutilizables que no deberían tener ningún vínculo con los presenters que los rodean, aquí los enlaces funcionan de forma algo distinta. El atributo de Latte `n:href` y la etiqueta `{link}`, así como los métodos del componente como `link()` y demás, **consideran siempre que el destino del enlace es el nombre de una señal**. Por eso ni siquiera hace falta poner el signo de exclamación: ```latte -señal, no acción +signal, not an action ``` -Si quisiéramos enlazar a presenters en la plantilla del componente, usaríamos la etiqueta `{plink}`: +Si en la plantilla del componente quisiéramos enlazar a presenters, usaríamos la etiqueta `{plink}`: ```latte -inicio +home ``` o en el código @@ -223,12 +256,12 @@ $this->getPresenter()->link('Home:default') ``` -Alias .{data-version:v3.2.2} -============================ +Alias .{data-version:3.2.3} +=========================== -A veces puede ser útil asignar un alias fácil de recordar al par Presenter:acción. Por ejemplo, nombrar la página de inicio `Front:Home:default` simplemente como `home` o `Admin:Dashboard:default` como `admin`. +A veces puede resultar útil asignar a una pareja Presenter:acción un alias fácil de recordar. Por ejemplo, llamar a la página de inicio `Front:Home:default` simplemente `home`, o a `Admin:Dashboard:default` llamarla `admin`. -Los alias se definen en la [configuración|configuration] bajo la clave `application › aliases`: +Los alias se definen en la [configuración|configuration], bajo la clave `application › aliases`: ```neon application: @@ -238,26 +271,26 @@ application: sign: Front:Sign:in ``` -En los enlaces, luego se escriben usando arroba, por ejemplo: +En los enlaces se escriben después con una arroba, por ejemplo: ```latte -administración +administration ``` -También son compatibles con todos los métodos que trabajan con enlaces, como `redirect()` y similares. +También están admitidos en todos los métodos que trabajan con enlaces, como `redirect()` y similares. Enlaces no válidos ================== -Puede suceder que creemos un enlace no válido, ya sea porque apunta a un presenter inexistente, o porque pasa más parámetros de los que el método de destino acepta en su firma, o cuando no se puede generar una URL para la acción de destino. Cómo tratar los enlaces no válidos lo determina la variable estática `Presenter::$invalidLinkMode`. Esta puede tomar una combinación de estos valores (constantes): +Puede ocurrir que creemos un enlace no válido, ya sea porque lleva a un presenter inexistente, porque pasa más parámetros de los que acepta en su firma el método de destino, o porque no se puede generar ninguna URL para la acción de destino. Cómo tratar los enlaces no válidos se fija en el presenter con `$this->invalidLinkMode`. Puede tomar una combinación de estos valores (constantes): -- `Presenter::InvalidLinkSilent` - modo silencioso, se devuelve el carácter # como URL -- `Presenter::InvalidLinkWarning` - se lanza una advertencia E_USER_WARNING, que se registrará en modo de producción, pero no causará la interrupción de la ejecución del script -- `Presenter::InvalidLinkTextual` - advertencia visual, muestra el error directamente en el enlace -- `Presenter::InvalidLinkException` - se lanza la excepción InvalidLinkException +- `Presenter::InvalidLinkSilent` - modo silencioso, devuelve el carácter # como URL +- `Presenter::InvalidLinkWarning` - se lanza una advertencia E_USER_WARNING, que se registrará en modo de producción, pero no interrumpirá la ejecución del script +- `Presenter::InvalidLinkTextual` - advertencia visual, imprime el error directamente en el enlace +- `Presenter::InvalidLinkException` - lanza InvalidLinkException -La configuración predeterminada es `InvalidLinkWarning` en modo de producción y `InvalidLinkWarning | InvalidLinkTextual` en desarrollo. `InvalidLinkWarning` en el entorno de producción no causa la interrupción del script, pero la advertencia se registrará. En el entorno de desarrollo, [Tracy |tracy:] lo captura y muestra una pantalla azul. `InvalidLinkTextual` funciona devolviendo un mensaje de error como URL, que comienza con los caracteres `#error:`. Para que dichos enlaces sean evidentes a primera vista, agregaremos a nuestro CSS: +El ajuste predeterminado es `InvalidLinkWarning` en modo de producción e `InvalidLinkWarning | InvalidLinkTextual` en modo de desarrollo. `InvalidLinkWarning` en el entorno de producción no interrumpe el script, pero la advertencia queda registrada. En el entorno de desarrollo, [Tracy |tracy:] la captura y muestra una pantalla azul. `InvalidLinkTextual` funciona devolviendo como URL un mensaje de error que empieza por los caracteres `#error:`. Para que esos enlaces salten a la vista, añada esto a su CSS: ```css a[href^="#error:"] { @@ -266,7 +299,7 @@ a[href^="#error:"] { } ``` -Si no queremos que se produzcan advertencias en el entorno de desarrollo, podemos establecer el modo silencioso directamente en la [configuración|configuration]. +Si no queremos que se produzcan advertencias en el entorno de desarrollo, podemos silenciarlas directamente en la [configuración|configuration]. ```neon application: @@ -277,10 +310,10 @@ application: LinkGenerator ============= -¿Cómo crear enlaces con una comodidad similar a la del método `link()`, pero sin la presencia de un presenter? Para eso está [api:Nette\Application\LinkGenerator]. +¿Cómo crear enlaces con una comodidad parecida a la del método `link()`, pero sin la presencia de un presenter? Para eso está [api:Nette\Application\LinkGenerator]. -LinkGenerator es un servicio que puede solicitar que se le pase a través del constructor y luego crear enlaces con su método `link()`. +LinkGenerator es un servicio que puede hacerse pasar por el constructor y con cuyo método `link()` puede crear después los enlaces. -Hay una diferencia con respecto a los presenters. LinkGenerator crea todos los enlaces directamente como URL absolutas. Y además, no existe un "presenter actual", por lo que no se puede indicar solo el nombre de la acción como destino `link('default')` ni indicar rutas relativas a módulos. +Hay una diferencia respecto a los presenters. LinkGenerator crea todos los enlaces directamente como URL absolutas. Además, no existe un "presenter actual", así que no es posible indicar como destino solo el nombre de la acción, `link('default')`, ni usar rutas relativas a los módulos. -Los enlaces no válidos siempre lanzan `Nette\Application\UI\InvalidLinkException`. +Los enlaces no válidos lanzan siempre `Nette\Application\UI\InvalidLinkException`. diff --git a/application/es/directory-structure.texy b/application/es/directory-structure.texy index 2392b7c719..057717ee41 100644 --- a/application/es/directory-structure.texy +++ b/application/es/directory-structure.texy @@ -3,43 +3,43 @@ Estructura de directorios de la aplicación
    -¿Cómo diseñar una estructura de directorios clara y escalable para proyectos en Nette Framework? Mostraremos las mejores prácticas que le ayudarán a organizar su código. Aprenderá: +¿Cómo diseñar una estructura de directorios clara y escalable para los proyectos en Nette Framework? Le mostraremos prácticas probadas que le ayudarán a organizar el código. Aprenderá: -- cómo **dividir lógicamente** la aplicación en directorios -- cómo diseñar la estructura para que **escale bien** con el crecimiento del proyecto -- cuáles son las **alternativas posibles** y sus ventajas o desventajas +- cómo **estructurar lógicamente** la aplicación en directorios +- cómo diseñar la estructura para que **escale bien** a medida que el proyecto crece +- cuáles son las **alternativas posibles** y sus ventajas o inconvenientes
    -Es importante mencionar que Nette Framework en sí mismo no impone ninguna estructura específica. Está diseñado para adaptarse fácilmente a cualquier necesidad y preferencia. +Es importante mencionar que Nette Framework en sí no impone ninguna estructura concreta. Está diseñado para adaptarse fácilmente a cualquier necesidad y preferencia. Estructura básica del proyecto ============================== -Aunque Nette Framework no dicta ninguna estructura de directorios fija, existe una disposición predeterminada probada en forma de [Web Project|https://github.com/nette/web-project]: +Aunque Nette Framework no dicta ninguna estructura de directorios fija, existe una disposición predeterminada probada en forma del [Web Project|https://github.com/nette/web-project]: /--pre web-project/ -├── app/ ← directorio con la aplicación +├── app/ ← directorio de la aplicación ├── assets/ ← archivos SCSS, JS, imágenes..., alternativamente resources/ ├── bin/ ← scripts para la línea de comandos ├── config/ ← configuración ├── log/ ← errores registrados ├── temp/ ← archivos temporales, caché -├── tests/ ← pruebas -├── vendor/ ← librerías instaladas por Composer +├── tests/ ← tests +├── vendor/ ← bibliotecas instaladas por Composer └── www/ ← directorio público (document-root) \-- -Puede modificar esta estructura libremente según sus necesidades: renombrar o mover carpetas. Después, solo necesita actualizar las rutas relativas a los directorios en el archivo `Bootstrap.php` y, opcionalmente, en `composer.json`. No se necesita nada más, ninguna reconfiguración complicada, ningún cambio de constantes. Nette dispone de una autodetección inteligente y reconoce automáticamente la ubicación de la aplicación, incluida su base de URL. +Puede modificar esta estructura libremente según sus necesidades: renombrar o mover carpetas. Después solo hace falta ajustar las rutas relativas a los directorios en `Bootstrap.php` y, eventualmente, en `composer.json`. Nada más, ninguna reconfiguración complicada, ningún cambio de constantes. Nette dispone de una autodetección inteligente y reconoce automáticamente la ubicación de la aplicación, incluida su URL base. Principios de organización del código ===================================== -Cuando explora un nuevo proyecto por primera vez, debería poder orientarse rápidamente en él. Imagine que expande el directorio `app/Model/` y ve esta estructura: +Cuando explora un proyecto nuevo por primera vez, debería poder orientarse rápidamente. Imagine que hace clic en el directorio `app/Model/` y ve esta estructura: /--pre app/Model/ @@ -48,9 +48,9 @@ Cuando explora un nuevo proyecto por primera vez, debería poder orientarse ráp └── Entities/ \-- -De ella, solo deduce que el proyecto utiliza algunos servicios, repositorios y entidades. No aprenderá nada sobre el propósito real de la aplicación. +De aquí solo se entera de que el proyecto usa unos servicios, unos repositorios y unas entidades. No se entera de nada sobre el propósito real de la aplicación. -Veamos otro enfoque: **organización por dominios**: +Veamos otro enfoque distinto: la **organización por dominios**: /--pre app/Model/ @@ -60,51 +60,51 @@ Veamos otro enfoque: **organización por dominios**: └── Product/ \-- -Aquí es diferente: a primera vista, está claro que se trata de una tienda electrónica. Los propios nombres de los directorios revelan lo que hace la aplicación: trabaja con pagos, pedidos y productos. +Aquí es diferente: a primera vista está claro que se trata de una tienda online. Los propios nombres de los directorios revelan lo que sabe hacer la aplicación: trabaja con pagos, pedidos y productos. -El primer enfoque (organización por tipo de clase) presenta una serie de problemas en la práctica: el código que está lógicamente relacionado está disperso en diferentes carpetas y tiene que saltar entre ellas. Por lo tanto, organizaremos por dominios. +El primer enfoque (organización por tipo de clase) trae en la práctica varios problemas: el código que está lógicamente relacionado queda fragmentado entre distintas carpetas y hay que ir saltando de una a otra. Por eso organizaremos por dominios. Espacios de nombres ------------------- -Es costumbre que la estructura de directorios corresponda a los espacios de nombres en la aplicación. Esto significa que la ubicación física de los archivos corresponde a su espacio de nombres. Por ejemplo, una clase ubicada en `app/Model/Product/ProductRepository.php` debería tener el espacio de nombres `App\Model\Product`. Este principio ayuda a orientarse en el código y simplifica la autocarga. +Es habitual que la estructura de directorios se corresponda con los espacios de nombres de la aplicación. Eso significa que la ubicación física de los archivos coincide con su espacio de nombres. Por ejemplo, una clase situada en `app/Model/Product/ProductRepository.php` debería tener el espacio de nombres `App\Model\Product`. Este principio ayuda a orientarse en el código y simplifica la carga automática. -Singular vs plural en los nombres ---------------------------------- +Singular y plural en los nombres +-------------------------------- -Observe que para los directorios principales de la aplicación usamos el singular: `app`, `config`, `log`, `temp`, `www`. Lo mismo dentro de la aplicación: `Model`, `Core`, `Presentation`. Esto se debe a que cada uno de ellos representa un concepto coherente. +Fíjese en que usamos el singular para los directorios principales de la aplicación: `app`, `config`, `log`, `temp`, `www`. Lo mismo dentro de la aplicación: `Model`, `Core`, `Presentation`. Es porque cada uno representa un único concepto coherente. -De manera similar, por ejemplo, `app/Model/Product` representa todo lo relacionado con los productos. No lo llamaremos `Products`, porque no es una carpeta llena de productos (eso significaría que habría archivos `nokia.php`, `samsung.php`). Es un espacio de nombres que contiene clases para trabajar con productos: `ProductRepository.php`, `ProductService.php`. +De forma parecida, `app/Model/Product` representa todo lo relacionado con los productos. No lo llamamos `Products` porque no es una carpeta llena de productos (que contendría archivos como `nokia.php`, `samsung.php`). Es un espacio de nombres que contiene clases para trabajar con productos: `ProductRepository.php`, `ProductService.php`. -La carpeta `app/Tasks` está en plural porque contiene un conjunto de scripts ejecutables independientes: `CleanupTask.php`, `ImportTask.php`. Cada uno de ellos es una unidad independiente. +La carpeta `app/Tasks` está en plural porque contiene un conjunto de scripts ejecutables independientes: `CleanupTask.php`, `ImportTask.php`. Cada uno de ellos es una unidad autónoma. -Para mantener la coherencia, recomendamos usar: -- Singular para espacios de nombres que representan una unidad funcional (aunque trabajen con múltiples entidades) -- Plural para colecciones de unidades independientes -- En caso de duda o si no quiere pensar en ello, elija el singular +Por coherencia, recomendamos usar: +- singular para los espacios de nombres que representan una unidad funcional (aunque trabajen con varias entidades) +- plural para las colecciones de unidades independientes +- en caso de duda, o si no quiere darle vueltas, elija el singular Directorio público `www/` ========================= -Este directorio es el único accesible desde la web (el llamado document-root). A menudo también puede encontrar el nombre `public/` en lugar de `www/`: es solo una cuestión de convención y no afecta la funcionalidad del framework. El directorio contiene: -- El [punto de entrada |bootstrapping#index.php] de la aplicación `index.php` -- El archivo `.htaccess` con reglas para mod_rewrite (para Apache) -- Archivos estáticos (CSS, JavaScript, imágenes) -- Archivos subidos +Este directorio es el único accesible desde la web (el document-root). A menudo puede encontrarse con el nombre `public/` en lugar de `www/`: es solo cuestión de convención y no afecta al funcionamiento de la aplicación. El directorio contiene: +- el [punto de entrada |bootstrapping#index.php] de la aplicación `index.php` +- el archivo `.htaccess` con las reglas para mod_rewrite (para Apache) +- archivos estáticos (CSS, JavaScript, imágenes) +- archivos subidos -Para la seguridad adecuada de la aplicación, es crucial tener el [document-root correctamente configurado |nette:troubleshooting#Cómo cambiar o eliminar el directorio www de la URL]. +Para la correcta seguridad de la aplicación es crucial tener el [document-root configurado correctamente |nette:troubleshooting#¿Cómo cambiar o quitar el directorio www de la URL?]. .[note] -Nunca coloque la carpeta `node_modules/` en este directorio: contiene miles de archivos que pueden ser ejecutables y no deben ser accesibles públicamente. +Nunca coloque en este directorio la carpeta `node_modules/`: contiene miles de archivos que pueden ser ejecutables y que no deberían ser accesibles públicamente. -Directorio de aplicación `app/` -=============================== +Directorio de la aplicación `app/` +================================== -Este es el directorio principal con el código de la aplicación. Estructura básica: +Este es el directorio principal, el que contiene el código de la aplicación. Estructura básica: /--pre app/ @@ -115,21 +115,21 @@ Este es el directorio principal con el código de la aplicación. Estructura bá └── Bootstrap.php ← clase de arranque de la aplicación \-- -`Bootstrap.php` es la [clase de inicio de la aplicación|bootstrapping] que inicializa el entorno, carga la configuración y crea el contenedor DI. +`Bootstrap.php` es la [clase de arranque de la aplicación|bootstrapping] que inicializa el entorno, carga la configuración y crea el contenedor DI. -Ahora veamos los subdirectorios individuales con más detalle. +Veamos ahora con más detalle cada uno de los subdirectorios. Presenters y plantillas ======================= -La parte de presentación de la aplicación la tenemos en el directorio `app/Presentation`. Una alternativa es el corto `app/UI`. Es el lugar para todos los presenters, sus plantillas y posibles clases auxiliares. +La parte de presentación de la aplicación está en el directorio `app/Presentation`. Una alternativa es el más corto `app/UI`. Es el lugar de todos los presenters, sus plantillas y las posibles clases auxiliares asociadas. -Organizamos esta capa por dominios. En un proyecto complejo que combina una tienda electrónica, un blog y una API, la estructura se vería así: +Esta capa la organizamos por dominios. En un proyecto complejo que combine tienda online, blog y API, la estructura sería así: /--pre app/Presentation/ -├── Shop/ ← frontend de la tienda electrónica +├── Shop/ ← frontend de la tienda │ ├── Product/ │ ├── Cart/ │ └── Order/ @@ -143,7 +143,7 @@ Organizamos esta capa por dominios. En un proyecto complejo que combina una tien └── V1/ \-- -Por el contrario, para un blog simple, usaríamos la siguiente división: +Por el contrario, para un blog sencillo usaríamos la siguiente estructura: /--pre app/Presentation/ @@ -157,9 +157,9 @@ Por el contrario, para un blog simple, usaríamos la siguiente división: └── Export/ ← RSS, sitemaps, etc. \-- -Carpetas como `Home/` o `Dashboard/` contienen presenters y plantillas. Carpetas como `Front/`, `Admin/` o `Api/` las llamamos **módulos**. Técnicamente, son directorios normales que sirven para la división lógica de la aplicación. +Carpetas como `Home/` o `Dashboard/` contienen presenters y plantillas. Carpetas como `Front/`, `Admin/` o `Api/` se llaman **módulos**. Técnicamente son directorios corrientes que sirven para dividir lógicamente la aplicación. -Cada carpeta con un presenter contiene un presenter con el mismo nombre y sus plantillas. Por ejemplo, la carpeta `Dashboard/` contiene: +Cada carpeta que contiene un presenter incluye el propio archivo del presenter y sus plantillas. Por ejemplo, la carpeta `Dashboard/` contiene: /--pre Dashboard/ @@ -167,7 +167,7 @@ Cada carpeta con un presenter contiene un presenter con el mismo nombre y sus pl └── default.latte ← plantilla \-- -Esta estructura de directorios se refleja en los espacios de nombres de las clases. Por ejemplo, `DashboardPresenter` se encuentra en el espacio de nombres `App\Presentation\Admin\Dashboard` (ver [#Mapeo de presenters]): +Esta estructura de directorios se refleja en los espacios de nombres de las clases. Por ejemplo, `DashboardPresenter` está en el espacio de nombres `App\Presentation\Admin\Dashboard` (véase [#Mapeo de presenters]): ```php namespace App\Presentation\Admin\Dashboard; @@ -178,13 +178,13 @@ class DashboardPresenter extends Nette\Application\UI\Presenter } ``` -Al presenter `Dashboard` dentro del módulo `Admin` nos referimos en la aplicación usando la notación de dos puntos como `Admin:Dashboard`. A su acción `default` entonces como `Admin:Dashboard:default`. En caso de módulos anidados, usamos más dos puntos, por ejemplo `Shop:Order:Detail:default`. +Al presenter `Dashboard` dentro del módulo `Admin` nos referimos en la aplicación con la notación de dos puntos como `Admin:Dashboard`. A su acción `default` nos referimos entonces como `Admin:Dashboard:default`. Con módulos anidados usamos varios grupos de dos puntos, por ejemplo `Shop:Order:Detail:default`. Desarrollo flexible de la estructura ------------------------------------ -Una de las grandes ventajas de esta estructura es cómo se adapta elegantemente a las crecientes necesidades del proyecto. Como ejemplo, tomemos la parte que genera feeds XML. Al principio, tenemos una forma simple: +Una de las grandes ventajas de esta estructura es lo elegantemente que se adapta a las necesidades crecientes del proyecto. Tomemos como ejemplo la parte que genera los feeds XML. Al principio tenemos una forma sencilla: /--pre Export/ @@ -193,7 +193,7 @@ Una de las grandes ventajas de esta estructura es cómo se adapta elegantemente └── feed.latte ← plantilla para el feed RSS \-- -Con el tiempo, se agregan otros tipos de feeds y necesitamos más lógica para ellos... ¡No hay problema! La carpeta `Export/` simplemente se convierte en un módulo: +Con el tiempo se añaden más tipos de feed y necesitamos más lógica para ellos... ¡Ningún problema! La carpeta `Export/` simplemente se convierte en un módulo: /--pre Export/ @@ -202,47 +202,47 @@ Con el tiempo, se agregan otros tipos de feeds y necesitamos más lógica para e │ └── sitemap.latte └── Feed/ ├── FeedPresenter.php - ├── zbozi.latte ← feed para Zboží.cz - └── heureka.latte ← feed para Heureka.cz + ├── amazon.latte ← feed para Amazon + └── ebay.latte ← feed para eBay \-- -Esta transformación es completamente fluida: basta con crear nuevas subcarpetas, dividir el código en ellas y actualizar los enlaces (p. ej., de `Export:feed` a `Export:Feed:zbozi`). Gracias a esto, podemos expandir gradualmente la estructura según sea necesario, el nivel de anidamiento no está limitado de ninguna manera. +Esta transformación es completamente fluida: basta con crear las nuevas subcarpetas, repartir el código en ellas y actualizar los enlaces (p. ej. de `Export:feed` a `Export:Feed:amazon`). Gracias a eso podemos ampliar la estructura gradualmente según haga falta; el nivel de anidamiento no está limitado de ninguna manera. -Si, por ejemplo, en la administración tiene muchos presenters relacionados con la gestión de pedidos, como `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., puede crear un módulo (carpeta) `Order` en este lugar para una mejor organización, que contendrá (carpetas para) los presenters `Detail`, `Edit`, `Dispatch` y otros. +Por ejemplo, si en la administración tiene muchos presenters relacionados con la gestión de pedidos, como `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., puede crear para una mejor organización un módulo (carpeta) llamado `Order`, que contendrá (las carpetas de) los presenters `Detail`, `Edit`, `Dispatch` y otros. Ubicación de las plantillas --------------------------- -En los ejemplos anteriores, vimos que las plantillas se ubican directamente en la carpeta con el presenter: +En los ejemplos anteriores hemos visto que las plantillas están directamente en la carpeta del presenter: /--pre Dashboard/ ├── DashboardPresenter.php ← presenter -├── DashboardTemplate.php ← clase opcional para la plantilla +├── DashboardTemplate.php ← clase de plantilla opcional └── default.latte ← plantilla \-- -Esta ubicación resulta ser la más conveniente en la práctica: tiene todos los archivos relacionados a mano. +Esta ubicación resulta ser la más cómoda en la práctica: tiene todos los archivos relacionados a mano. -Alternativamente, puede colocar las plantillas en una subcarpeta `templates/`. Nette admite ambas variantes. Incluso puede colocar las plantillas completamente fuera de la carpeta `Presentation/`. Todo sobre las opciones de ubicación de plantillas se encuentra en el capítulo [Búsqueda de plantillas |templates#Búsqueda de plantillas]. +Alternativamente puede colocar las plantillas en una subcarpeta `templates/`. Nette admite ambas variantes. Incluso puede colocar las plantillas completamente fuera de la carpeta `Presentation/`. Todo sobre las opciones de ubicación de las plantillas lo encontrará en el capítulo [Búsqueda de plantillas |templates#Búsqueda de plantillas]. Clases auxiliares y componentes ------------------------------- -A los presenters y plantillas a menudo les pertenecen otros archivos auxiliares. Los ubicamos lógicamente según su ámbito: +A los presenters y las plantillas los acompañan a menudo otros archivos auxiliares. Los colocamos de forma lógica según su alcance: -1. **Directamente junto al presenter** en caso de componentes específicos para ese presenter: +1. **Directamente junto al presenter** en el caso de componentes específicos de ese presenter: /--pre Product/ ├── ProductPresenter.php -├── ProductGrid.php ← componente para listar productos +├── ProductGrid.php ← componente para el listado de productos └── FilterForm.php ← formulario para filtrar \-- -2. **Para el módulo** - recomendamos usar la carpeta `Accessory`, que se coloca convenientemente al principio del alfabeto: +2. **Para el módulo**: recomendamos usar la carpeta `Accessory`, que queda convenientemente al principio por orden alfabético: /--pre Front/ @@ -253,7 +253,7 @@ A los presenters y plantillas a menudo les pertenecen otros archivos auxiliares. └── Cart/ \-- -3. **Para toda la aplicación** - en `Presentation/Accessory/`: +3. **Para toda la aplicación**: en `Presentation/Accessory/`: /--pre app/Presentation/ ├── Accessory/ @@ -263,30 +263,30 @@ A los presenters y plantillas a menudo les pertenecen otros archivos auxiliares. └── Admin/ \-- -O puede colocar clases auxiliares como `LatteExtension.php` o `TemplateFilters.php` en la carpeta de infraestructura `app/Core/Latte/`. Y los componentes en `app/Components`. La elección depende de las costumbres del equipo. +Alternativamente puede colocar las clases auxiliares como `LatteExtension.php` o `TemplateFilters.php` en la carpeta de infraestructura `app/Core/Latte/`. Y los componentes en `app/Components`. La elección depende de las convenciones del equipo. -Modelo - el corazón de la aplicación -==================================== +Model: el corazón de la aplicación +================================== -El modelo contiene toda la lógica de negocio de la aplicación. Para su organización, se aplica nuevamente la regla: estructuramos por dominios: +El modelo contiene toda la lógica de negocio de la aplicación. La regla para organizarlo es de nuevo: estructurar por dominios: /--pre app/Model/ -├── Payment/ ← todo sobre pagos +├── Payment/ ← todo sobre los pagos │ ├── PaymentFacade.php ← punto de entrada principal │ ├── PaymentRepository.php │ ├── Payment.php ← entidad -├── Order/ ← todo sobre pedidos +├── Order/ ← todo sobre los pedidos │ ├── OrderFacade.php │ ├── OrderRepository.php │ ├── Order.php -└── Shipping/ ← todo sobre envíos +└── Shipping/ ← todo sobre los envíos \-- -En el modelo, típicamente encontrará estos tipos de clases: +En el modelo se encuentra normalmente con estos tipos de clases: -**Fachadas (Facades)**: representan el punto de entrada principal a un dominio específico en la aplicación. Actúan como un orquestador que coordina la colaboración entre diferentes servicios con el fin de implementar casos de uso completos (como "crear pedido" o "procesar pago"). Bajo su capa de orquestación, la fachada oculta los detalles de implementación del resto de la aplicación, proporcionando así una interfaz limpia para trabajar con el dominio dado. +**Facades**: representan el punto de entrada principal a un dominio concreto dentro de la aplicación. Actúan como orquestador que coordina la colaboración entre distintos servicios para implementar casos de uso completos (como "crear un pedido" o "procesar un pago"). Bajo su capa de orquestación, la fachada oculta al resto de la aplicación los detalles de implementación y ofrece así una interfaz limpia para trabajar con ese dominio. ```php class OrderFacade @@ -295,13 +295,13 @@ class OrderFacade { // validación // creación del pedido - // envío de correo electrónico - // registro en estadísticas + // envío del correo + // escritura en las estadísticas } } ``` -**Servicios**: se centran en una operación de negocio específica dentro del dominio. A diferencia de la fachada, que orquesta casos de uso completos, el servicio implementa lógica de negocio específica (como cálculos de precios o procesamiento de pagos). Los servicios suelen ser sin estado y pueden ser utilizados ya sea por fachadas como bloques de construcción para operaciones más complejas, o directamente por otras partes de la aplicación para tareas más simples. +**Servicios**: se centran en operaciones de negocio concretas dentro de un dominio. A diferencia de las fachadas, que orquestan casos de uso completos, un servicio implementa una lógica de negocio concreta (como el cálculo de precios o el procesamiento de pagos). Los servicios normalmente no tienen estado y pueden usarlos las fachadas como bloques de construcción para operaciones más complejas, o bien directamente otras partes de la aplicación para tareas más simples. ```php class PricingService @@ -313,7 +313,7 @@ class PricingService } ``` -**Repositorios**: aseguran toda la comunicación con el almacenamiento de datos, típicamente una base de datos. Su tarea es cargar y guardar entidades e implementar métodos para su búsqueda. El repositorio aísla al resto de la aplicación de los detalles de implementación de la base de datos y proporciona una interfaz orientada a objetos para trabajar con los datos. +**Repositorios**: se ocupan de toda la comunicación con el almacén de datos, típicamente una base de datos. Su tarea es cargar y guardar entidades e implementar métodos para buscarlas. El repositorio aísla al resto de la aplicación de los detalles de implementación de la base de datos y ofrece una interfaz orientada a objetos para trabajar con los datos. ```php class OrderRepository @@ -328,10 +328,10 @@ class OrderRepository } ``` -**Entidades**: objetos que representan los principales conceptos de negocio en la aplicación, que tienen su identidad y cambian con el tiempo. Típicamente, son clases mapeadas a tablas de bases de datos usando un ORM (como Nette Database Explorer o Doctrine). Las entidades pueden contener reglas de negocio relacionadas con sus datos y lógica de validación. +**Entidades**: objetos que representan los principales conceptos de negocio de la aplicación, que tienen su propia identidad y cambian con el tiempo. Normalmente son clases mapeadas a tablas de la base de datos mediante un ORM (como Nette Database Explorer o Doctrine). Las entidades pueden contener reglas de negocio relativas a sus datos y lógica de validación. ```php -// Entidad mapeada a la tabla de base de datos orders +// Entidad mapeada a la tabla 'orders' de la base de datos class Order extends Nette\Database\Table\ActiveRow { public function addItem(Product $product, int $quantity): void @@ -345,13 +345,13 @@ class Order extends Nette\Database\Table\ActiveRow } ``` -**Objetos de valor (Value objects)**: objetos inmutables que representan valores sin identidad propia - por ejemplo, una cantidad monetaria o una dirección de correo electrónico. Dos instancias de un objeto de valor con los mismos valores son consideradas idénticas. +**Value Objects**: objetos inmutables que representan valores sin identidad propia, por ejemplo un importe monetario o una dirección de correo. Dos instancias de un value object con los mismos valores se consideran idénticas. Código de infraestructura ========================= -La carpeta `Core/` (o también `Infrastructure/`) es el hogar de la base técnica de la aplicación. El código de infraestructura típicamente incluye: +La carpeta `Core/` (o alternativamente `Infrastructure/`) alberga la base técnica de la aplicación. El código de infraestructura incluye normalmente: /--pre app/Core/ @@ -360,17 +360,17 @@ La carpeta `Core/` (o también `Infrastructure/`) es el hogar de la base técnic ├── Security/ ← autenticación y autorización │ ├── Authenticator.php │ └── Authorizator.php -├── Logging/ ← registro y monitoreo +├── Logging/ ← registro y monitorización │ ├── SentryLogger.php │ └── FileLogger.php ├── Cache/ ← capa de caché │ └── FullPageCache.php -└── Integration/ ← integración con servicios ext. +└── Integration/ ← integración con servicios externos ├── Slack/ └── Stripe/ \-- -En proyectos más pequeños, por supuesto, basta con una estructura plana: +Para proyectos más pequeños basta naturalmente con una estructura plana: /--pre Core/ @@ -379,66 +379,66 @@ En proyectos más pequeños, por supuesto, basta con una estructura plana: └── QueueMailer.php \-- -Se trata de código que: +Es el código que: -- Resuelve la infraestructura técnica (enrutamiento, registro, caché) -- Integra servicios externos (Sentry, Elasticsearch, Redis) -- Proporciona servicios básicos para toda la aplicación (correo, base de datos) -- Es mayormente independiente del dominio específico - la caché o el logger funciona igual para una tienda electrónica o un blog. +- se ocupa de la infraestructura técnica (enrutamiento, registro, caché) +- integra servicios externos (Sentry, Elasticsearch, Redis) +- proporciona servicios básicos para toda la aplicación (correo, base de datos) +- es en su mayor parte independiente de un dominio concreto: la caché o el logger funcionan igual para una tienda online que para un blog. -¿Duda si una clase determinada pertenece aquí o al modelo? La diferencia clave es que el código en `Core/`: +¿Se pregunta si una determinada clase pertenece aquí o al modelo? La diferencia clave es que el código de `Core/`: -- No sabe nada sobre el dominio (productos, pedidos, artículos) -- Es mayormente posible transferirlo a otro proyecto -- Resuelve "cómo funciona" (cómo enviar un correo), no "qué hace" (qué correo enviar) +- no sabe nada del dominio (productos, pedidos, artículos) +- normalmente se puede trasladar a otro proyecto +- resuelve el "cómo funciona" (cómo enviar un correo), no el "qué hace" (qué correo enviar) -Ejemplo para una mejor comprensión: +Un ejemplo para entenderlo mejor: -- `App\Core\MailerFactory` - crea instancias de la clase para enviar correos electrónicos, resuelve la configuración SMTP -- `App\Model\OrderMailer` - usa `MailerFactory` para enviar correos electrónicos sobre pedidos, conoce sus plantillas y sabe cuándo deben enviarse +- `App\Core\MailerFactory` crea instancias de la clase para enviar correos y se ocupa de la configuración SMTP +- `App\Model\OrderMailer` usa `MailerFactory` para enviar correos sobre pedidos, conoce sus plantillas y sabe cuándo deben enviarse Scripts de comandos =================== -Las aplicaciones a menudo necesitan realizar actividades fuera de las peticiones HTTP normales - ya sea procesamiento de datos en segundo plano, mantenimiento o tareas periódicas. Para la ejecución sirven scripts simples en el directorio `bin/`, la lógica de implementación la colocamos en `app/Tasks/` (o `app/Commands/`). +Las aplicaciones necesitan a menudo realizar actividades fuera de las peticiones HTTP habituales, ya sea el procesamiento de datos en segundo plano, el mantenimiento o tareas periódicas. Para ejecutarlas sirven scripts sencillos en el directorio `bin/`, mientras que la lógica de implementación propiamente dicha se coloca en `app/Tasks/` (o `app/Commands/`). Ejemplo: /--pre app/Tasks/ ├── Maintenance/ ← scripts de mantenimiento -│ ├── CleanupCommand.php ← eliminación de datos antiguos +│ ├── CleanupCommand.php ← borrado de datos antiguos │ └── DbOptimizeCommand.php ← optimización de la base de datos ├── Integration/ ← integración con sistemas externos │ ├── ImportProducts.php ← importación desde el sistema del proveedor │ └── SyncOrders.php ← sincronización de pedidos -└── Scheduled/ ← tareas periódicas +└── Scheduled/ ← tareas regulares ├── NewsletterCommand.php ← envío de newsletters - └── ReminderCommand.php ← notificaciones a clientes + └── ReminderCommand.php ← avisos a los clientes \-- -¿Qué pertenece al modelo y qué a los scripts de comandos? Por ejemplo, la lógica para enviar un correo electrónico es parte del modelo, el envío masivo de miles de correos electrónicos ya pertenece a `Tasks/`. +¿Qué pertenece al modelo y qué a los scripts de comandos? Por ejemplo, la lógica del envío de un único correo forma parte del modelo, mientras que el envío masivo de miles de correos pertenece a `Tasks/`. -Las tareas generalmente [se ejecutan desde la línea de comandos |https://blog.nette.org/en/cli-scripts-in-nette-application] o a través de cron. También se pueden ejecutar a través de una petición HTTP, pero es necesario pensar en la seguridad. El presenter que ejecuta la tarea debe estar protegido, por ejemplo, solo para usuarios autenticados o con un token fuerte y acceso desde direcciones IP permitidas. Para tareas largas, es necesario aumentar el límite de tiempo del script y usar `session_write_close()` para que la sesión no se bloquee. +Las tareas se ejecutan normalmente desde la línea de comandos o mediante cron: el script de `bin/` crea el contenedor DI con el método [bootConsoleApplication() |bootstrapping#Entornos distintos] y saca de él el servicio necesario. También se pueden ejecutar mediante una petición HTTP, pero hay que pensar en la seguridad. El presenter que ejecuta la tarea necesita estar protegido, por ejemplo solo para usuarios conectados o con un token fuerte y acceso desde direcciones IP permitidas. Para las tareas de larga duración es necesario aumentar el límite de tiempo del script y usar `session_write_close()` para no bloquear la sesión. Otros directorios posibles ========================== -Además de los directorios básicos mencionados, puede agregar otras carpetas especializadas según las necesidades del proyecto. Veamos las más comunes y su uso: +Además de los directorios básicos mencionados puede añadir otras carpetas especializadas según las necesidades del proyecto. Veamos las más habituales y su uso: /--pre app/ -├── Api/ ← lógica para la API independiente de la capa de presentación +├── Api/ ← lógica de la API independiente de la capa de presentación ├── Database/ ← scripts de migración y seeders para datos de prueba -├── Components/ ← componentes visuales compartidos en toda la aplicación -├── Event/ ← útil si usa arquitectura dirigida por eventos -├── Mail/ ← plantillas de correo electrónico y lógica relacionada +├── Components/ ← componentes visuales compartidos por toda la aplicación +├── Event/ ← útil si se usa una arquitectura orientada a eventos +├── Mail/ ← plantillas de correo y la lógica relacionada └── Utils/ ← clases auxiliares \-- -Para componentes visuales compartidos utilizados en presenters en toda la aplicación, se puede usar la carpeta `app/Components` o `app/Controls`: +Para los componentes visuales compartidos que se usan en los presenters de toda la aplicación puede usar la carpeta `app/Components` o `app/Controls`: /--pre app/Components/ @@ -452,13 +452,13 @@ Para componentes visuales compartidos utilizados en presenters en toda la aplica └── Menu.php \-- -Aquí pertenecen los componentes que tienen una lógica más compleja. Si desea compartir componentes entre varios proyectos, es recomendable extraerlos a un paquete composer separado. +Aquí es donde pertenecen los componentes con una lógica más compleja. Si quiere compartir componentes entre varios proyectos, conviene extraerlos a un paquete de Composer separado. -En el directorio `app/Mail` puede colocar la gestión de la comunicación por correo electrónico: +En el directorio `app/Mail` puede colocar la gestión de la comunicación por correo: /--pre app/Mail/ -├── templates/ ← plantillas de correo electrónico +├── templates/ ← plantillas de correo │ ├── order-confirmation.latte │ └── welcome.latte └── OrderMailer.php @@ -468,32 +468,32 @@ En el directorio `app/Mail` puede colocar la gestión de la comunicación por co Mapeo de presenters =================== -El mapeo define reglas para derivar el nombre de la clase a partir del nombre del presenter. Las especificamos en la [configuración|configuration] bajo la clave `application › mapping`. +El mapeo define las reglas para derivar el nombre de la clase a partir del nombre del presenter. Las indicamos en la [configuración|configuration] bajo la clave `application › mapping`. -En esta página, hemos mostrado que colocamos los presenters en la carpeta `app/Presentation` (o `app/UI`). Debemos comunicar esta convención a Nette en el archivo de configuración. Basta con una línea: +En esta página hemos mostrado que colocamos los presenters en la carpeta `app/Presentation` (o `app/UI`). Desde Nette Application 3.3 esta es la convención predeterminada, que no hace falta configurar. Si usa una estructura distinta o quiere indicar el mapeo explícitamente, la configuración predeterminada corresponde a esta línea: ```neon application: mapping: App\Presentation\*\**Presenter ``` -¿Cómo funciona el mapeo? Para una mejor comprensión, imaginemos primero una aplicación sin módulos. Queremos que las clases de los presenters caigan en el espacio de nombres `App\Presentation`, para que el presenter `Home` se mapee a la clase `App\Presentation\HomePresenter`. Lo cual logramos con esta configuración: +¿Cómo funciona el mapeo? Para entenderlo mejor, imaginemos primero una aplicación sin módulos. Queremos que las clases de los presenters caigan bajo el espacio de nombres `App\Presentation`, de modo que el presenter `Home` se mapee a la clase `App\Presentation\HomePresenter`. Esto se consigue con esta configuración: ```neon application: mapping: App\Presentation\*Presenter ``` -El mapeo funciona de tal manera que el nombre del presenter `Home` reemplaza el asterisco en la máscara `App\Presentation\*Presenter`, obteniendo así el nombre de clase resultante `App\Presentation\HomePresenter`. ¡Simple! +El mapeo funciona sustituyendo el asterisco de la máscara `App\Presentation\*Presenter` por el nombre del presenter `Home`, lo que da como resultado el nombre final de la clase `App\Presentation\HomePresenter`. ¡Sencillo! -Pero como puede ver en los ejemplos de este y otros capítulos, colocamos las clases de los presenters en subdirectorios epónimos, por ejemplo, el presenter `Home` se mapea a la clase `App\Presentation\Home\HomePresenter`. Esto se logra duplicando los dos puntos (requiere Nette Application 3.2): +Sin embargo, como ve en los ejemplos de este y otros capítulos, colocamos las clases de los presenters en subdirectorios del mismo nombre, por ejemplo el presenter `Home` se mapea a la clase `App\Presentation\Home\HomePresenter`. Esto lo conseguimos usando un asterisco doble `**` (requiere Nette Application 3.2.3): ```neon application: mapping: App\Presentation\**Presenter ``` -Ahora procederemos a mapear los presenters a módulos. Para cada módulo podemos definir un mapeo específico: +Ahora pasamos al mapeo de presenters en módulos. Podemos definir un mapeo específico para cada módulo: ```neon application: @@ -503,9 +503,9 @@ application: Api: App\Api\*Presenter ``` -Según esta configuración, el presenter `Front:Home` se mapea a la clase `App\Presentation\Front\Home\HomePresenter`, mientras que el presenter `Api:OAuth` a la clase `App\Api\OAuthPresenter`. +Según esta configuración, el presenter `Front:Home` se mapea a la clase `App\Presentation\Front\Home\HomePresenter`, mientras que el presenter `Api:OAuth` se mapea a la clase `App\Api\OAuthPresenter`. -Dado que los módulos `Front` y `Admin` tienen una forma similar de mapeo y probablemente habrá más módulos de este tipo, es posible crear una regla general que los reemplace. Así, se agregará un nuevo asterisco a la máscara de clase para el módulo: +Como los módulos `Front` y `Admin` tienen un patrón de mapeo parecido, y es probable que haya más módulos así, es posible crear una regla general que los sustituya. A la máscara de la clase se le añade un nuevo asterisco para el módulo: ```neon application: @@ -514,9 +514,9 @@ application: Api: App\Api\*Presenter ``` -Funciona también para estructuras de directorios más profundamente anidadas, como por ejemplo el presenter `Admin:User:Edit`, el segmento con asterisco se repite para cada nivel y el resultado es la clase `App\Presentation\Admin\User\Edit\EditPresenter`. +Funciona también para estructuras de directorios anidadas más profundamente, como el presenter `Admin:User:Edit`, donde el segmento con el asterisco se repite para cada nivel de módulo, dando como resultado la clase `App\Presentation\Admin\User\Edit\EditPresenter`. -Una notación alternativa es usar un array compuesto por tres segmentos en lugar de una cadena. Esta notación es equivalente a la anterior: +Una notación alternativa consiste en usar, en lugar de una cadena, un array formado por tres segmentos. Para los ejemplos mostrados arriba, esta notación es equivalente a la anterior: ```neon application: diff --git a/application/es/how-it-works.texy b/application/es/how-it-works.texy index 40246de021..f09f7e4a0f 100644 --- a/application/es/how-it-works.texy +++ b/application/es/how-it-works.texy @@ -3,11 +3,11 @@
    -Está leyendo el documento fundamental de la documentación de Nette. Aprenderá todo el principio de funcionamiento de las aplicaciones web. De la A a la Z, desde el momento del nacimiento hasta el último suspiro del script PHP. Después de leerlo, sabrá: +Está leyendo el capítulo fundacional de la documentación de Nette. Aprenderá de la A a la Z todo el principio de funcionamiento de las aplicaciones web, desde el instante en que nace una petición hasta que el script PHP termina de ejecutarse. Tras leerlo entenderá: - cómo funciona todo - qué son Bootstrap, Presenter y el contenedor DI -- cómo es la estructura de directorios +- qué aspecto tiene la estructura de directorios
    @@ -15,89 +15,89 @@ Está leyendo el documento fundamental de la documentación de Nette. Aprenderá Estructura de directorios ========================= -Abra el ejemplo del esqueleto de una aplicación web llamado [WebProject|https://github.com/nette/web-project] y mientras lee, puede mirar los archivos de los que se habla. +Abra el esqueleto de ejemplo de una aplicación web llamado [WebProject|https://github.com/nette/web-project]. Mientras lee, puede consultar los archivos de los que hablamos. -La estructura de directorios tiene este aspecto: +La estructura de directorios tiene más o menos este aspecto: /--pre web-project/ -├── app/ ← directorio con la aplicación -│ ├── Core/ ← clases básicas necesarias para el funcionamiento -│ │ └── RouterFactory.php ← configuración de direcciones URL -│ ├── Presentation/ ← presenters, plantillas, etc. -│ │ ├── @layout.latte ← plantilla de layout +├── app/ ← directorio de la aplicación +│ ├── Core/ ← clases básicas necesarias para funcionar +│ │ └── RouterFactory.php ← configuración de las direcciones URL +│ ├── Presentation/ ← presenters, plantillas y compañía +│ │ ├── @layout.latte ← plantilla del layout │ │ └── Home/ ← directorio del presenter Home │ │ ├── HomePresenter.php ← clase del presenter Home │ │ └── default.latte ← plantilla de la acción default │ └── Bootstrap.php ← clase de arranque Bootstrap -├── assets/ ← recursos (SCSS, TypeScript, imágenes de origen). +├── assets/ ← recursos (SCSS, TypeScript, imágenes fuente) ├── bin/ ← scripts ejecutados desde la línea de comandos ├── config/ ← archivos de configuración │ ├── common.neon │ └── services.neon ├── log/ ← errores registrados -├── temp/ ← archivos temporales, caché, etc. -├── vendor/ ← librerías instaladas por Composer +├── temp/ ← archivos temporales, caché, … +├── vendor/ ← bibliotecas instaladas por Composer │ ├── ... -│ └── autoload.php ← autoloading de todos los paquetes instalados -├── www/ ← directorio público o document-root del proyecto +│ └── autoload.php ← autocarga de todos los paquetes instalados +├── www/ ← directorio público, document root del proyecto │ ├── assets/ ← archivos estáticos compilados (CSS, JS, imágenes, ...) -│ ├── .htaccess ← reglas mod_rewrite -│ └── index.php ← archivo inicial con el que se inicia la aplicación -└── .htaccess ← prohíbe el acceso a todos los directorios excepto www +│ ├── .htaccess ← reglas de mod_rewrite +│ └── index.php ← archivo inicial que lanza la aplicación +└── .htaccess ← prohíbe el acceso a todos los directorios salvo www \-- -Puede cambiar la estructura de directorios como desee, renombrar o mover carpetas, es completamente flexible. Nette, además, dispone de una autodetección inteligente y reconoce automáticamente la ubicación de la aplicación, incluida su base de URL. +Puede cambiar la estructura de directorios como quiera, renombrar o mover carpetas; es del todo flexible. Nette dispone además de una autodetección inteligente y reconoce automáticamente la ubicación de la aplicación, incluida su base de URL. -En aplicaciones un poco más grandes, podemos [dividir las carpetas con presenters y plantillas en subdirectorios |directory-structure#Presenters y plantillas] y las clases en espacios de nombres, que llamamos módulos. +En aplicaciones algo mayores podemos organizar las carpetas de presenters y plantillas en [subdirectorios |directory-structure#Presenters y plantillas] y agrupar las clases en espacios de nombres, lo que llamamos módulos. -El directorio `www/` representa el llamado directorio público o document-root del proyecto. Puede renombrarlo sin necesidad de configurar nada más en el lado de la aplicación. Solo es necesario [configurar el hosting |nette:troubleshooting#Cómo cambiar o eliminar el directorio www de la URL] para que el document-root apunte a este directorio. +El directorio `www/` representa el directorio público o document-root del proyecto. Puede renombrarlo sin necesidad de configurar nada más del lado de la aplicación. Solo hace falta [configurar el alojamiento |nette:troubleshooting#¿Cómo cambiar o quitar el directorio www de la URL?] para que el document-root apunte a ese directorio. -También puede descargar WebProject directamente incluyendo Nette usando [Composer |best-practices:composer]: +También puede descargar WebProject directamente, con Nette incluido, mediante [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -En Linux o macOS, establezca [permisos de escritura |nette:troubleshooting#Configuración de permisos de directorio] para los directorios `log/` y `temp/`. +En Linux o macOS, dé [permisos de escritura |nette:troubleshooting#Establecer los permisos de los directorios] a los directorios `log/` y `temp/`. -La aplicación WebProject está lista para ejecutarse, no es necesario configurar absolutamente nada y puede mostrarla directamente en el navegador accediendo a la carpeta `www/`. +La aplicación WebProject está lista para funcionar; no hace falta configurar absolutamente nada y puede verla directamente en el navegador accediendo a la carpeta `www/`. Petición HTTP ============= -Todo comienza en el momento en que el usuario abre una página en el navegador. Es decir, cuando el navegador llama al servidor con una petición HTTP. La petición apunta a un único archivo PHP, que se encuentra en el directorio público `www/`, y este es `index.php`. Supongamos que se trata de una petición a la dirección `https://example.com/product/123`. Gracias a la [configuración adecuada del servidor |nette:troubleshooting#Cómo configurar el servidor para URLs amigables], incluso esta URL se mapea al archivo `index.php` y este se ejecuta. +Todo empieza cuando un usuario abre una página en su navegador. El navegador envía una petición HTTP al servidor. Esa petición apunta a un único archivo PHP situado en el directorio público `www/`, que es `index.php`. Supongamos que la petición es a la dirección `https://example.com/product/123`. Gracias a una [configuración del servidor |nette:troubleshooting#¿Cómo configurar el servidor para URL bonitas?] adecuada, también esa URL se asigna al archivo `index.php`, que se ejecuta entonces. Su tarea es: 1) inicializar el entorno -2) obtener la fábrica -3) iniciar la aplicación Nette, que gestionará la petición +2) obtener la factory +3) ejecutar la aplicación de Nette, que atiende la petición -¿Qué fábrica? ¡No fabricamos tractores, sino páginas web! Espere, se explicará de inmediato. +¿Qué factory? ¡No fabricamos tractores, construimos webs! Espere, se explicará enseguida. -Con las palabras "inicialización del entorno" nos referimos, por ejemplo, a que se activa [Tracy|tracy:], que es una herramienta increíble para el registro o la visualización de errores. En el servidor de producción, registra los errores, en el de desarrollo los muestra directamente. Por lo tanto, la inicialización también incluye la decisión de si el sitio web se ejecuta en modo de producción o de desarrollo. Para esto, Nette utiliza una [autodetección inteligente |bootstrapping#Modo de desarrollo vs producción]: si ejecuta el sitio web en localhost, se ejecuta en modo de desarrollo. Por lo tanto, no necesita configurar nada y la aplicación está lista tanto para el desarrollo como para la implementación en producción. Estos pasos se realizan y se describen detalladamente en el capítulo sobre la [clase Bootstrap|bootstrapping]. +Por "inicializar el entorno" entendemos, por ejemplo, activar [Tracy|tracy:], una herramienta magnífica para registrar o visualizar errores. En un servidor de producción registra los errores; en un entorno de desarrollo los muestra directamente. La inicialización incluye, por tanto, determinar si el sitio funciona en modo de producción o de desarrollo. Nette usa para ello una [autodetección inteligente |bootstrapping#Modo de desarrollo y modo de producción]: si ejecuta el sitio en localhost, funciona en modo de desarrollo. No necesita configurar nada y la aplicación está lista al instante tanto para el desarrollo como para el despliegue en vivo. Estos pasos se realizan y se describen en detalle en el capítulo sobre la [clase Bootstrap|bootstrapping]. -El tercer punto (sí, saltamos el segundo, pero volveremos a él) es el inicio de la aplicación. La gestión de las peticiones HTTP en Nette está a cargo de la clase `Nette\Application\Application` (en adelante `Application`), por lo que cuando decimos iniciar la aplicación, nos referimos específicamente a llamar al método con el nombre apropiado `run()` en el objeto de esta clase. +El tercer punto (sí, nos hemos saltado el segundo, pero volveremos a él) es lanzar la aplicación. De atender las peticiones HTTP en Nette se encarga la clase `Nette\Application\Application` (en adelante, `Application`). Así que, cuando decimos ejecutar la aplicación, nos referimos en concreto a llamar al método, muy bien llamado, `run()` de un objeto de esta clase. -Nette es un mentor que le guía para escribir aplicaciones limpias según metodologías probadas. Y una de las más probadas se llama **inyección de dependencias**, abreviada como DI. En este momento no queremos cargarle con la explicación de DI, para eso está [un capítulo aparte|dependency-injection:introduction], lo importante es la consecuencia de que los objetos clave generalmente nos los creará una fábrica de objetos, que se llama **contenedor DI** (abreviado como DIC). Sí, esa es la fábrica de la que hablamos hace un momento. Y también nos fabricará el objeto `Application`, por eso necesitamos primero el contenedor. Lo obtenemos usando la clase `Configurator` y le pedimos que fabrique el objeto `Application`, llamamos al método `run()` en él y así se inicia la aplicación Nette. Exactamente esto sucede en el archivo [index.php |bootstrapping#index.php]. +Nette actúa como un mentor y le guía para que escriba aplicaciones limpias según metodologías probadas. Una de las más asentadas es la **inyección de dependencias**, abreviada DI. No queremos cargarle ahora con la explicación de la DI; para eso hay un [capítulo aparte|dependency-injection:introduction]. La consecuencia esencial es que los objetos clave los suele crear una factory de objetos conocida como **contenedor DI** (o DIC). Sí, esa es la factory mencionada antes. Ella produce también el objeto `Application` para nosotros, y por eso necesitamos primero el contenedor. Lo obtenemos con la clase `Configurator`, dejamos que cree el objeto `Application`, llamamos a su método `run()` y con ello arranca la aplicación de Nette. Eso es justo lo que ocurre en el archivo [index.php |bootstrapping#index.php]. Nette Application ================= -La clase Application tiene una única tarea: responder a la petición HTTP. +La clase `Application` tiene una única tarea: responder a la petición HTTP. -Las aplicaciones escritas en Nette se dividen en muchos llamados presenters (en otros frameworks puede encontrar el término controller, es lo mismo), que son clases, cada una de las cuales representa alguna página específica del sitio web: p. ej., la página de inicio; un producto en una tienda electrónica; un formulario de inicio de sesión; un feed sitemap, etc. Una aplicación puede tener desde uno hasta miles de presenters. +Las aplicaciones escritas en Nette se dividen en muchos presenters (en otros frameworks puede encontrarse el término "controlador", que es en esencia lo mismo). Son clases que representan cada una una página concreta del sitio web: la página de inicio, un producto de una tienda en línea, un formulario de acceso, un feed de sitemap, etc. Una aplicación puede tener desde un presenter hasta miles. -Application comienza pidiendo al llamado router que decida a cuál de los presenters pasar la petición actual para su gestión. El router decide de quién es la responsabilidad. Mira la URL de entrada `https://example.com/product/123` y, basándose en cómo está configurado, decide que este es trabajo, por ejemplo, para el **presenter** `Product`, al que le pedirá como **acción** la visualización (`show`) del producto con `id: 123`. Es una buena costumbre escribir el par presenter + acción separado por dos puntos como `Product:show`. +`Application` empieza preguntando al router qué presenter debe atender la petición actual. El router determina la responsabilidad. Examina la URL de entrada `https://example.com/product/123` y, según su configuración, decide que esa tarea corresponde, por ejemplo, al **presenter** `Product`, que debe ejecutar la **acción** `show` para el producto de `id: 123`. Es buena práctica escribir la pareja presenter + acción separada por dos puntos, así: `Product:show`. -Por lo tanto, el router transformó la URL en el par `Presenter:action` + parámetros, en nuestro caso `Product:show` + `id: 123`. Puede ver cómo es un router de este tipo en el archivo `app/Core/RouterFactory.php` y lo describimos detalladamente en el capítulo [Enrutamiento |Routing]. +El router ha transformado, por tanto, la URL en la pareja `Presenter:acción` + parámetros, en nuestro caso `Product:show` + `id: 123`. Puede ver qué aspecto tiene ese router en el archivo `app/Core/RouterFactory.php`, y lo describimos en detalle en el capítulo [Enrutamiento |Routing]. -Sigamos. Application ya conoce el nombre del presenter y puede continuar. Creando el objeto de la clase `ProductPresenter`, que es el código del presenter `Product`. Más precisamente, pide al contenedor DI que fabrique el presenter, porque para eso está él. +Continuemos. `Application` conoce ya el nombre del presenter y puede seguir adelante. Lo hace creando una instancia de la clase `ProductPresenter`, que contiene el código del presenter `Product`. Más exactamente, pide al contenedor DI que cree el presenter, porque crear objetos es cosa suya. -El presenter puede verse así: +El presenter podría tener este aspecto: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -109,92 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // obtenemos datos del modelo y los pasamos a la plantilla + // obtiene los datos del modelo y se los pasa a la plantilla $this->template->product = $this->repository->getProduct($id); } } ``` -La gestión de la petición la asume el presenter. Y la tarea es clara: realiza la acción `show` con `id: 123`. Lo que en el lenguaje de los presenters significa que se llama al método `renderShow()` y en el parámetro `$id` recibe `123`. +El presenter toma el relevo en la atención de la petición. La tarea está clara: ejecutar la acción `show` con `id: 123`. En la terminología de los presenters, eso significa que se llama al método `renderShow()`, que recibe `123` en el parámetro `$id`. -Un presenter puede manejar múltiples acciones, es decir, tener múltiples métodos `render()`. Pero recomendamos diseñar presenters con una o la menor cantidad posible de acciones. +Un presenter puede atender varias acciones, es decir, puede tener varios métodos `render()`. Recomendamos, sin embargo, diseñar los presenters con una acción, o con las menos posibles. -Entonces, se llamó al método `renderShow(123)`, cuyo código es un ejemplo ficticio, pero puede ver en él cómo se pasan los datos a la plantilla, es decir, escribiendo en `$this->template`. +Así pues, se ha llamado al método `renderShow(123)`. Su código es un ejemplo ficticio, pero muestra cómo se pasan los datos a la plantilla: escribiendo en `$this->template`. -Posteriormente, el presenter devuelve una respuesta. Esta puede ser una página HTML, una imagen, un documento XML, el envío de un archivo desde el disco, JSON o incluso una redirección a otra página. Es importante que si no decimos explícitamente cómo debe responder (que es el caso de `ProductPresenter`), la respuesta será la renderización de una plantilla con una página HTML. ¿Por qué? Porque en el 99% de los casos queremos renderizar una plantilla, por lo que el presenter toma este comportamiento como predeterminado y quiere facilitarnos el trabajo. Ese es el propósito de Nette. +A continuación, el presenter devuelve una respuesta. Puede ser una página HTML, una imagen, un documento XML, el envío de un archivo del disco, JSON o quizá una redirección a otra página. Lo importante es que, si no indicamos explícitamente cómo responder (que es el caso de `ProductPresenter`), la respuesta será renderizar una plantilla en una página HTML. ¿Por qué? Porque en el 99 % de los casos queremos renderizar una plantilla. Por eso el presenter adopta ese comportamiento como predeterminado, para simplificarnos el trabajo. Esa es la esencia de Nette. -Ni siquiera tenemos que indicar qué plantilla renderizar, él mismo deduce la ruta hacia ella. En el caso de la acción `show`, simplemente intenta cargar la plantilla `show.latte` en el directorio con la clase `ProductPresenter`. También intentará localizar el layout en el archivo `@layout.latte` (más detalles sobre la [búsqueda de plantillas |templates#Búsqueda de plantillas]). +Ni siquiera necesitamos indicar qué plantilla renderizar; el framework deduce la ruta automáticamente. En el caso de la acción `show`, simplemente intenta cargar la plantilla `show.latte` situada en el mismo directorio que la clase `ProductPresenter`. También trata de encontrar el layout en el archivo `@layout.latte` (más detalles en [búsqueda de plantillas |templates#Búsqueda de plantillas]). -Y posteriormente renderiza las plantillas. Con esto, la tarea del presenter y de toda la aplicación está completa y la obra está terminada. Si la plantilla no existiera, se devolvería una página con error 404. Puede leer más sobre los presenters en la página [Presenters|presenters]. +Después se renderizan las plantillas. Con eso termina la tarea del presenter y de toda la aplicación. Si la plantilla no existe, se devuelve una página de error 404. Puede aprender más sobre los presenters en la página [Presenters|presenters]. [* request-flow.svg *] -Por si acaso, intentemos recapitular todo el proceso con una URL ligeramente diferente: +Por si acaso, recapitulemos todo el proceso con una URL ligeramente distinta: -1) La URL será `https://example.com` -2) Arrancamos la aplicación, se crea el contenedor y se ejecuta `Application::run()` -3) El router decodifica la URL como el par `Home:default` -4) Se crea el objeto de la clase `HomePresenter` -5) Se llama al método `renderDefault()` (si existe) -6) Se renderiza la plantilla, p. ej., `default.latte` con el layout, p. ej., `@layout.latte` +1) La URL es `https://example.com` +2) La aplicación arranca, se crea el contenedor DI y se ejecuta `Application::run()`. +3) El router decodifica la URL en la pareja `Home:default`. +4) Se crea una instancia de la clase `HomePresenter`. +5) Se llama al método `renderDefault()` (si existe). +6) Se renderiza la plantilla, por ejemplo `default.latte`, junto con el layout, por ejemplo `@layout.latte`. -Puede que ahora se haya encontrado con muchos conceptos nuevos, pero creemos que tienen sentido. Crear aplicaciones en Nette es increíblemente fácil. +Puede que se haya encontrado ahora con muchos conceptos nuevos, pero creemos que tienen sentido. Desarrollar aplicaciones en Nette es notablemente sencillo. Plantillas ========== -Ya que hablamos de plantillas, en Nette se utiliza el sistema de plantillas [Latte |latte:]. Por eso esas extensiones `.latte` en las plantillas. Latte se utiliza, por un lado, porque es el sistema de plantillas más seguro para PHP y, al mismo tiempo, el sistema más intuitivo. No necesita aprender mucho nuevo, le basta con conocer PHP y algunas etiquetas. Todo lo aprenderá [en la documentación |templates]. +Puesto que hablamos de plantillas, Nette usa el sistema de plantillas [Latte |latte:]. Por eso los archivos de plantilla tienen la extensión `.latte`. Latte se emplea ante todo porque es el sistema de plantillas más seguro para PHP, y también el más intuitivo. No necesita aprender mucho nuevo; basta con saber PHP y unas cuantas etiquetas. Encontrará todo lo necesario [en la documentación |templates]. -En la plantilla, se [crean enlaces |creating-links] a otros presenters y acciones de esta manera: +En la plantilla, los [enlaces |creating-links] a otros presenters y acciones se crean así: ```latte -detalle del producto +product detail ``` -Simplemente, en lugar de la URL real, escribe el par conocido `Presenter:action` e indica los parámetros necesarios. El truco está en `n:href`, que indica que este atributo será procesado por Nette. Y generará: +Basta con escribir la conocida pareja `Presenter:acción` en lugar de la URL real e incluir los parámetros necesarios. El truco está en `n:href`, que le dice a Nette que procese ese atributo. Generará entonces: ```latte -detalle del producto +product detail ``` -La generación de URL está a cargo del router mencionado anteriormente. Es decir, los routers en Nette son excepcionales porque pueden realizar no solo transformaciones de URL al par presenter:action, sino también al revés, es decir, generar una URL a partir del nombre del presenter + acción + parámetros. Gracias a esto, en Nette puede cambiar completamente las formas de las URL en toda la aplicación terminada, sin cambiar un solo carácter en la plantilla o el presenter. Simplemente modificando el router. También gracias a esto funciona la llamada canonización, que es otra característica única de Nette que contribuye a un mejor SEO (optimización para motores de búsqueda) al evitar automáticamente la existencia de contenido duplicado en diferentes URL. Muchos programadores lo consideran asombroso. +De generar las URL se encarga el router mencionado antes. Los routers de Nette son excepcionales porque saben hacer no solo la transformación de una URL en la pareja `Presenter:acción`, sino también la inversa: generar una URL a partir del nombre del presenter, la acción y los parámetros. Gracias a ello puede cambiar por completo el formato de las URL de toda su aplicación terminada sin tocar un solo carácter de las plantillas ni de los presenters: basta con modificar el router. Esto permite además la llamada canonización, otra función única de Nette que mejora el SEO (optimización para buscadores) al impedir automáticamente que exista contenido duplicado en URL distintas. A muchos programadores esta capacidad les parece asombrosa. Componentes interactivos ======================== -Sobre los presenters debemos revelarle una cosa más: tienen incorporado un sistema de componentes. Algo similar pueden recordar los veteranos de Delphi o ASP.NET Web Forms, algo remotamente similar es la base de React o Vue.js. En el mundo de los frameworks PHP, es una característica absolutamente única. +Tenemos que contarle una cosa más sobre los presenters: llevan incorporado un sistema de componentes. Quien tenga más experiencia recordará algo parecido de Delphi o de ASP.NET Web Forms; React o Vue.js se apoyan en conceptos algo emparentados. En el mundo de los frameworks de PHP, esta es una función completamente única. -Los componentes son unidades reutilizables independientes que insertamos en las páginas (es decir, presenters). Pueden ser [formularios |forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], menús, encuestas de votación, en realidad cualquier cosa que tenga sentido usar repetidamente. Podemos crear nuestros propios componentes o usar algunos de la [enorme oferta |https://componette.org] de componentes de código abierto. +Los componentes son unidades independientes y reutilizables que insertamos en las páginas (es decir, en los presenters). Pueden ser [formularios |forms:in-presenter], [datagrids |https://componette.org/contributte/datagrid/], menús, encuestas: en esencia, cualquier cosa que tenga sentido reutilizar. Podemos crear nuestros propios componentes o aprovechar alguno de la [amplísima oferta |https://componette.org] de componentes de código abierto. -Los componentes influyen fundamentalmente en el enfoque para la creación de aplicaciones. Le abrirán nuevas posibilidades de componer páginas a partir de unidades prefabricadas. Y además tienen algo en común con [Hollywood |components#Estilo Hollywood]. +Los componentes influyen de raíz en la forma de desarrollar aplicaciones. Abren nuevas posibilidades para componer páginas a partir de unidades ya preparadas. Y además tienen algo en común con [Hollywood |components#Estilo Hollywood]. Contenedor DI y configuración ============================= -El contenedor DI o fábrica de objetos es el corazón de toda la aplicación. +El contenedor DI, o factory de objetos, es el corazón de toda la aplicación. -No se preocupe, no es ninguna caja negra mágica, como podría parecer por las líneas anteriores. En realidad, es una clase PHP bastante aburrida, que Nette genera y guarda en el directorio de caché. Tiene muchos métodos llamados como `createServiceAbcd()` y cada uno de ellos sabe cómo fabricar y devolver algún objeto. Sí, también está el método `createServiceApplication()`, que fabrica `Nette\Application\Application`, que necesitábamos en el archivo `index.php` para iniciar la aplicación. Y hay métodos que fabrican los presenters individuales. Y así sucesivamente. +No se preocupe, no es ninguna caja negra mágica, por mucho que las líneas anteriores lo puedan sugerir. En realidad es una clase PHP bastante prosaica, generada por Nette y guardada en el directorio de caché. Contiene muchos métodos con nombres del estilo `createServiceAbcd()`, cada uno capaz de crear y devolver un objeto concreto. Sí, también hay un método `createServiceApplication__application()` que produce la instancia de `Nette\Application\Application` que necesitábamos en `index.php` para ejecutar la aplicación. Y hay métodos para crear los distintos presenters, y así sucesivamente. -A los objetos que crea el contenedor DI, por alguna razón, se les llama servicios. +A los objetos creados por el contenedor DI se los llama, por algún motivo, servicios. -Lo que es realmente especial de esta clase es que no la programa usted, sino el framework. Él realmente genera el código PHP y lo guarda en el disco. Usted solo da instrucciones sobre qué objetos debe saber fabricar el contenedor y cómo exactamente. Y estas instrucciones están escritas en [archivos de configuración |bootstrapping#Configuración del contenedor DI], para los cuales se utiliza el formato [NEON|neon:format] y, por lo tanto, también tienen la extensión `.neon`. +Lo verdaderamente especial de esta clase es que usted no la programa: lo hace el framework. Genera realmente el código PHP y lo guarda en disco. Usted se limita a dar instrucciones sobre qué objetos debe saber crear el contenedor y cómo exactamente. Esas instrucciones se escriben en [archivos de configuración |bootstrapping#Configuración del contenedor DI], que usan el formato [NEON|neon:format] y, por tanto, tienen la extensión `.neon`. -Los archivos de configuración sirven puramente para instruir al contenedor DI. Así que, por ejemplo, si indico en la sección [session |http:configuration#Sesión] la opción `expiration: 14 days`, el contenedor DI al crear el objeto `Nette\Http\Session` que representa la sesión, llamará a su método `setExpiration('14 days')` y así la configuración se hará realidad. +Los archivos de configuración sirven puramente para instruir al contenedor DI. Así, por ejemplo, si indica la opción `expiration: 14 days` en la sección [session |http:configuration#Sesión], el contenedor DI, al crear el objeto `Nette\Http\Session` que representa la sesión, llamará a su método `setExpiration('14 days')` y convertirá así la configuración en realidad. -Hay un capítulo completo preparado para usted que describe qué se puede [configurar |nette:configuring] y cómo [definir servicios propios |dependency-injection:services]. +Tiene preparado todo un capítulo que describe qué se puede [configurar |nette:configuring] y cómo [definir sus propios servicios |dependency-injection:services]. -Una vez que se adentre un poco en la creación de servicios, se encontrará con la palabra [autowiring |dependency-injection:autowiring]. Esta es una característica que le simplificará la vida de manera increíble. Sabe cómo pasar automáticamente objetos donde los necesita (por ejemplo, en los constructores de sus clases), sin que tenga que hacer nada. Descubrirá que el contenedor DI en Nette es un pequeño milagro. +En cuanto profundice un poco en la creación de servicios, se encontrará con el término [autowiring |dependency-injection:autowiring]. Es una función que le simplificará la vida de forma increíble. Sabe pasar automáticamente los objetos allí donde los necesita (por ejemplo, en los constructores de sus clases) sin que usted tenga que hacer nada. Descubrirá que el contenedor DI de Nette es un pequeño milagro. -¿A dónde ir ahora? -================== +¿Y ahora qué? +============= -Hemos repasado los principios básicos de las aplicaciones en Nette. Hasta ahora muy superficialmente, pero pronto profundizará y con el tiempo creará maravillosas aplicaciones web. ¿A dónde continuar ahora? ¿Ya ha probado el tutorial [Escribiendo la primera aplicación|quickstart:]? +Hemos repasado los principios fundamentales de las aplicaciones de Nette. Ha sido, de momento, una visión superficial, pero pronto profundizará y, con el tiempo, creará aplicaciones web estupendas. ¿Adónde ir ahora? ¿Ha probado ya el tutorial [Cree su primera aplicación|quickstart:]? -Además de lo descrito anteriormente, Nette dispone de todo un arsenal de [clases útiles|utils:], [capa de base de datos|database:], etc. Intente simplemente hacer clic en la documentación. O en el [blog|https://blog.nette.org]. Descubrirá muchas cosas interesantes. +Además de lo descrito arriba, Nette ofrece todo un arsenal de [clases útiles|utils:], una [capa de base de datos|database:], etc. Pruebe a navegar por la documentación. O visite el [blog|https://blog.nette.org]. Descubrirá muchas cosas interesantes. Que el framework le traiga mucha alegría 💙 diff --git a/application/es/multiplier.texy b/application/es/multiplier.texy index 7dc7ac9411..c121342b6a 100644 --- a/application/es/multiplier.texy +++ b/application/es/multiplier.texy @@ -2,31 +2,31 @@ Multiplier: componentes dinámicos ********************************* .[perex] -Herramienta para la creación dinámica de componentes interactivos +Una herramienta para crear dinámicamente componentes interactivos. -Partamos de un ejemplo típico: tenemos una lista de productos en una tienda online, y para cada uno queremos mostrar un formulario para añadir el producto al carrito. Una de las posibles variantes es envolver todo el listado en un único formulario. Sin embargo, un método mucho más cómodo nos lo ofrece [api:Nette\Application\UI\Multiplier]. +Empecemos por un ejemplo típico: imagine una lista de productos en una tienda en línea donde quiere un formulario "Añadir al carrito" para cada artículo. Un enfoque posible es envolver todo el listado en un único formulario. Un método mucho más cómodo, sin embargo, lo ofrece [api:Nette\Application\UI\Multiplier]. -Multiplier permite definir cómodamente una pequeña fábrica para múltiples componentes. Funciona según el principio de componentes anidados: cada componente que hereda de [api:Nette\ComponentModel\Container] puede contener otros componentes. +Multiplier le permite definir cómodamente una factory para varios componentes. Funciona según el principio de los componentes anidados: cualquier componente que herede de [api:Nette\ComponentModel\Container] puede contener otros componentes. .[tip] -Vea el capítulo sobre el [modelo de componentes |components#Componentes en profundidad] en la documentación o la [charla de Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. +Vea el capítulo sobre el [modelo de componentes |components#Los componentes en profundidad] de la documentación. -La esencia de Multiplier es que actúa como un padre que puede crear dinámicamente sus hijos mediante un callback pasado en el constructor. Vea el ejemplo: +La esencia de Multiplier es que actúa como un padre capaz de crear dinámicamente a sus hijos mediante un callback pasado al constructor. Vea el ejemplo: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Cantidad de productos:') + $form->addInteger('amount', 'Amount:') ->setRequired(); - $form->addSubmit('send', 'Añadir al carrito'); + $form->addSubmit('send', 'Add to cart'); return $form; }); } ``` -Ahora podemos simplemente, en la plantilla, renderizar un formulario para cada producto, y cada uno será realmente un componente único. +Ahora, en la plantilla, podemos renderizar sin más el formulario de cada producto, y cada uno será realmente un componente único. ```latte {foreach $items as $item} @@ -37,26 +37,26 @@ Ahora podemos simplemente, en la plantilla, renderizar un formulario para cada p {/foreach} ``` -El argumento pasado en la etiqueta `{control}` tiene un formato que indica: +El argumento pasado en la etiqueta `{control}` sigue un formato que significa: -1. obtén el componente `shopForm` -2. y de él obtén el hijo `$item->id` +1. Obtener el componente `shopForm`. +2. De él, obtener el hijo llamado `$item->id`. -En la primera llamada al punto **1.**, `shopForm` aún no existe, por lo que se llama a su fábrica `createComponentShopForm`. Sobre el componente obtenido (instancia de Multiplier) se llama entonces a la fábrica del formulario específico, que es la función anónima que pasamos a Multiplier en el constructor. +Durante la primera llamada del punto **1**, el componente `shopForm` aún no existe, así que se llama a su factory `createComponentShopForm`. Después, sobre el componente obtenido (una instancia de Multiplier) se llama a la factory del formulario concreto, que es la función anónima que pasamos al constructor de Multiplier. -En la siguiente iteración del `foreach`, el método `createComponentShopForm` ya no será llamado (el componente existe), pero como buscamos a su otro hijo (`$item->id` será diferente en cada iteración), se volverá a llamar a la función anónima y nos devolverá un nuevo formulario. +En la siguiente iteración del bucle foreach ya no se llamará al método `createComponentShopForm` (porque el componente ya existe). Pero como buscamos un hijo distinto (ya que `$item->id` será distinto en cada iteración), la función anónima se llamará de nuevo y devolverá un formulario nuevo. -Lo único que queda es asegurar que el formulario añada al carrito realmente el producto que debe; actualmente, el formulario es completamente idéntico para cada producto. Nos ayudará una propiedad de Multiplier (y en general de cada fábrica de componentes en Nette Framework), y es que cada fábrica recibe como primer argumento el nombre del componente que se está creando. En nuestro caso, será `$item->id`, que es exactamente el dato que necesitamos. Basta con modificar ligeramente la creación del formulario: +Solo queda asegurarse de que el formulario añada al carrito el producto correcto: por ahora, el formulario es idéntico para todos los productos. Aquí nos ayuda una característica de Multiplier (y, en general, de cualquier factory de componentes en Nette Framework): toda factory recibe como primer argumento el nombre del componente que se está creando. Además, una factory de Multiplier recibe como segundo argumento la propia instancia de Multiplier. En nuestro caso, el primer argumento será `$item->id`, que es justo la información que necesitamos. Así que basta con modificar ligeramente la creación del formulario: ```php protected function createComponentShopForm(): Multiplier { - return new Multiplier(function ($itemId) { + return new Multiplier(function (string $itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Cantidad de productos:') + $form->addInteger('amount', 'Amount:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Añadir al carrito'); + $form->addSubmit('send', 'Add to cart'); return $form; }); } diff --git a/application/es/presenters.texy b/application/es/presenters.texy index 20185ba405..e714797364 100644 --- a/application/es/presenters.texy +++ b/application/es/presenters.texy @@ -3,37 +3,37 @@ Presenters
    -Nos familiarizaremos con cómo se escriben los presenters y las plantillas en Nette. Después de leerlo, sabrá: +Veremos cómo se escriben en Nette los presenters y las plantillas. Después de leerlo entenderá: - cómo funciona un presenter - qué son los parámetros persistentes -- cómo se dibujan las plantillas +- cómo se renderizan las plantillas
    -[Ya sabemos |how-it-works#Nette Application] que un presenter es una clase que representa alguna página específica de una aplicación web, p. ej., la página de inicio; un producto en una tienda electrónica; un formulario de inicio de sesión; un feed sitemap, etc. Una aplicación puede tener desde uno hasta miles de presenters. En otros frameworks también se les llama controllers. +[Ya sabemos |how-it-works#Nette Application] que un presenter es una clase que representa una página concreta de la aplicación web, por ejemplo la portada, un producto de una tienda online, un formulario de acceso, un feed sitemap, etc. Una aplicación puede tener desde un presenter hasta miles. En otros frameworks se les llama también controladores. -Generalmente, bajo el término presenter se entiende un descendiente de la clase [api:Nette\Application\UI\Presenter], que es adecuado para generar interfaces web y al que nos dedicaremos en el resto de este capítulo. En sentido general, un presenter es cualquier objeto que implementa la interfaz [api:Nette\Application\IPresenter]. +Normalmente con el término presenter nos referimos a un descendiente de la clase [api:Nette\Application\UI\Presenter], que es adecuado para generar interfaces web y al que se dedicará el resto de este capítulo. En sentido general, un presenter es cualquier objeto que implemente la interfaz [api:Nette\Application\IPresenter]. Ciclo de vida del presenter =========================== -La tarea del presenter es gestionar una petición y devolver una respuesta (que puede ser una página HTML, una imagen, una redirección, etc.). +La tarea del presenter es procesar una petición y devolver una respuesta (que puede ser una página HTML, una imagen, una redirección, etc.). -Por lo tanto, al principio se le pasa una petición. No es directamente una petición HTTP, sino un objeto [api:Nette\Application\Request], al que se transformó la petición HTTP con la ayuda del router. Generalmente no interactuamos con este objeto, ya que el presenter delega inteligentemente el procesamiento de la petición a otros métodos, que ahora mostraremos. +Así pues, al principio se le pasa una petición. No es directamente la petición HTTP, sino un objeto [api:Nette\Application\Request], en el que se transformó la petición HTTP con ayuda del router. Normalmente no trabajamos directamente con este objeto, porque el presenter delega ingeniosamente el procesamiento de la petición en otros métodos, que veremos ahora. -[* lifecycle.svg *] *** *Ciclo de vida del presenter* .<> +[* lifecycle.svg *] *** Ciclo de vida del presenter .<> -La imagen representa una lista de métodos que se llaman sucesivamente de arriba abajo, si existen. Ninguno de ellos tiene por qué existir, podemos tener un presenter completamente vacío sin un solo método y construir sobre él un sitio web estático simple. +El diagrama muestra la lista de métodos que se llaman sucesivamente de arriba abajo, si es que existen. Ninguno es obligatorio; puede tener un presenter completamente vacío, sin un solo método, y construir sobre él un sitio web estático sencillo. `__construct()` --------------- -El constructor no pertenece exactamente al ciclo de vida del presenter, porque se llama en el momento de la creación del objeto. Pero lo mencionamos por su importancia. El constructor (junto con el [método inject|best-practices:inject-method-attribute]) sirve para pasar dependencias. +El constructor no pertenece del todo al ciclo de vida del presenter, porque se llama en el momento de crear el objeto. Pero lo mencionamos por su importancia. El constructor (junto con el [método inject|best-practices:inject-method-attribute]) sirve para pasar las dependencias. -El presenter no debería encargarse de la lógica de negocio de la aplicación, escribir y leer de la base de datos, realizar cálculos, etc. Para eso están las clases de la capa que llamamos modelo. Por ejemplo, la clase `ArticleRepository` puede encargarse de cargar y guardar artículos. Para que el presenter pueda trabajar con ella, se la deja [pasar mediante inyección de dependencias |dependency-injection:passing-dependencies]: +El presenter no debería ocuparse de la lógica de negocio de la aplicación, escribir o leer en la base de datos, hacer cálculos, etc. De eso se encargan las clases de la capa que llamamos modelo. Por ejemplo, la clase `ArticleRepository` puede encargarse de cargar y guardar los artículos. Para que el presenter pueda trabajar con ella, hay que [pasársela mediante inyección de dependencias |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,47 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Inmediatamente después de recibir la petición, se llama al método `startup()`. Puede usarlo para inicializar propiedades, verificar permisos de usuario, etc. Se requiere que el método siempre llame al ancestro `parent::startup()`. +Inmediatamente después de recibir la petición se invoca el método `startup()`. Puede usarlo para inicializar propiedades, comprobar los permisos del usuario, etc. Es obligatorio que este método llame siempre a su antecesor: `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Análogo al método `render()`. Mientras que `render()` está destinado a preparar datos para una plantilla específica que luego se renderizará, en `action()` se procesa la petición sin conexión con la renderización de la plantilla. Por ejemplo, se procesan datos, se inicia o cierra sesión de usuario, y así sucesivamente, y luego [se redirige a otro lugar |#Redirección]. +Parecido al método `render()`. Mientras que `render()` está pensado para preparar los datos de una plantilla concreta que después se renderizará, `action()` procesa la petición sin que necesariamente se renderice después una plantilla. Por ejemplo, puede procesar datos, conectar o desconectar al usuario, etc., y luego [redirigir a otro sitio |#Redirección]. -Es importante que `action()` se llame antes que `render()`, por lo que en él podemos cambiar el curso posterior de los acontecimientos, es decir, cambiar la plantilla que se dibujará, y también el método `render()` que se llamará. Y esto usando `setView('otraVista')`. +Es importante que `action()` se llame *antes* que `render()`. Eso nos permite cambiar el curso de la petición dentro del método de acción, por ejemplo cambiando la plantilla que se renderizará o incluso el método `render()` que se llamará, mediante `setView('otherView')`. -Al método se le pasan parámetros de la petición. Es posible y recomendable indicar tipos para los parámetros, p. ej., `actionShow(int $id, ?string $slug = null)` - si falta el parámetro `id` o si no es un entero, el presenter devolverá un [error 404 |#Error 404 y cía] y finalizará la actividad. +.{data-version:3.2.3} +Incluso puede cambiar a una acción completamente distinta con el método `switch('otherAction')`. Aborta el método actual y ejecuta en su lugar los métodos `action()` y `render()` de la nueva acción (y desactiva la [canonización|#Canonización] automática). La propia petición continúa; solo se interrumpe el método que se estaba ejecutando. + +A este método se le pasan los parámetros de la petición. Es posible y recomendable indicar los tipos de estos parámetros, p. ej. `actionShow(int $id, ?string $slug = null)`. Si falta el parámetro `id` o no es un número entero, el presenter devuelve un [error 404 |#Error 404 y otros] y termina. `handle(args...)` .{toc: handle()} -------------------------------------------------- -El método procesa las llamadas señales, con las que nos familiarizaremos en el capítulo dedicado a los [componentes |components#Señal]. De hecho, está destinado principalmente a componentes y al procesamiento de peticiones AJAX. +Este método procesa las llamadas señales, que conoceremos en el capítulo dedicado a los [componentes |components#Señal]. Está pensado sobre todo para los componentes y el procesamiento de las peticiones AJAX. -Al método se le pasan parámetros de la petición, como en el caso de `action()`, incluida la verificación de tipos. +A este método se le pasan los parámetros de la petición, igual que en `action()`, incluida la comprobación de tipos. `beforeRender()` ---------------- -El método `beforeRender`, como su nombre indica, se llama antes de cada método `render()`. Se utiliza para la configuración común de la plantilla, pasar variables para el layout y similares. +El método `beforeRender`, como su nombre indica, se llama antes de cada método `render()`. Sirve para la configuración común de la plantilla, para pasar variables al layout y cosas parecidas. `render(args...)` .{toc: render()} ---------------------------------------------- -El lugar donde preparamos la plantilla para su posterior renderización, le pasamos datos, etc. +Aquí es donde preparamos la plantilla para su posterior renderizado, le pasamos los datos, etc. -Al método se le pasan parámetros de la petición, como en el caso de `action()`, incluida la verificación de tipos. +A este método se le pasan los parámetros de la petición, igual que en `action()`, incluida la comprobación de tipos. ```php public function renderShow(int $id): void { - // obtenemos datos del modelo y los pasamos a la plantilla + // obtiene los datos del modelo y se los pasa a la plantilla $this->template->article = $this->articles->getById($id); } ``` @@ -96,7 +99,7 @@ public function renderShow(int $id): void `afterRender()` --------------- -El método `afterRender`, como su nombre indica de nuevo, se llama después de cada método `render()`. Se usa más bien excepcionalmente. +El método `afterRender`, de nuevo como su nombre indica, se llama después de cada método `render()`. Se usa más bien poco. `shutdown()` @@ -105,44 +108,66 @@ El método `afterRender`, como su nombre indica de nuevo, se llama después de c Se llama al final del ciclo de vida del presenter. -**Un buen consejo antes de continuar**. Como puede ver, un presenter puede manejar múltiples acciones/vistas, es decir, tener múltiples métodos `render()`. Pero recomendamos diseñar presenters con una o la menor cantidad posible de acciones. +Eventos +------- + +Además de los métodos `startup()`, `beforeRender()` y `shutdown()`, que se llaman como parte del ciclo de vida del presenter, se pueden definir otras funciones que se llamen automáticamente. El presenter define los llamados [eventos |nette:glossary#Eventos], y sus manejadores se añaden a los arrays `$onStartup`, `$onRender` y `$onShutdown`. + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` + +Los manejadores del array `$onStartup` se llaman justo antes del método `startup()`, los de `$onRender` entre `beforeRender()` y `render()`, y por último los de `$onShutdown` justo antes de `shutdown()`. + +**Un consejo antes de continuar:** como ve, un presenter puede gestionar varias acciones/vistas, es decir, tener varios métodos `render()`. Pero recomendamos diseñar los presenters con una sola acción o con el menor número posible de ellas. -Envío de la respuesta -===================== -La respuesta del presenter suele ser la [renderización de una plantilla con una página HTML|templates], pero también puede ser el envío de un archivo, JSON o incluso una redirección a otra página. +Envío de una respuesta +====================== -En cualquier momento durante el ciclo de vida, podemos enviar una respuesta con uno de los siguientes métodos y, al mismo tiempo, finalizar el presenter: +La respuesta del presenter suele ser el [renderizado de una plantilla en una página HTML|templates], pero también puede ser el envío de un archivo, de JSON o incluso una redirección a otra página. -- `redirect()`, `redirectPermanent()`, `redirectUrl()` y `forward()` [redirigen |#Redirección] -- `error()` finaliza el presenter [debido a un error |#Error 404 y cía] -- `sendJson($data)` finaliza el presenter y [envía datos |#Envío de JSON] en formato JSON -- `sendTemplate()` finaliza el presenter e inmediatamente [renderiza la plantilla |templates] -- `sendResponse($response)` finaliza el presenter y envía una [respuesta propia |#Respuestas] -- `terminate()` finaliza el presenter sin respuesta +En cualquier momento del ciclo de vida podemos usar alguno de los siguientes métodos para enviar una respuesta y terminar al mismo tiempo el presenter: -Si no llama a ninguno de estos métodos, el presenter procederá automáticamente a renderizar la plantilla. ¿Por qué? Porque en el 99% de los casos queremos renderizar una plantilla, por lo que el presenter toma este comportamiento como predeterminado y quiere facilitarnos el trabajo. +- `redirect()`, `redirectPermanent()`, `redirectUrl()` y `forward()` realizan una [redirección |#Redirección] +- `error()` termina el presenter [por un error |#Error 404 y otros] +- `sendJson($data)` termina el presenter y [envía los datos |#Envío de JSON] en formato JSON +- `sendTemplate()` termina el presenter y [renderiza inmediatamente la plantilla |templates] +- `sendResponse($response)` termina el presenter y envía una [respuesta propia |#Respuestas] +- `terminate()` termina el presenter sin respuesta + +Cada uno de estos métodos termina inmediatamente el presenter lanzando la excepción de terminación silenciosa `Nette\Application\AbortException`. + +Si no llama a ninguno de estos métodos, el presenter pasa automáticamente a renderizar la plantilla. ¿Por qué? Porque en el 99 % de los casos queremos renderizar una plantilla, así que el presenter adopta este comportamiento como predeterminado para facilitarnos el trabajo. Creación de enlaces =================== -El presenter dispone del método `link()`, mediante el cual se pueden crear enlaces URL a otros presenters. El primer parámetro es el presenter y la acción de destino, seguido de los argumentos pasados, que pueden indicarse como un array: +El presenter tiene el método `link()`, con el que se crean enlaces URL a otros presenters. El primer parámetro es el presenter y la acción de destino, seguido de los argumentos, que se pueden pasar como array: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$id, 'lang' => 'en']); ``` -En la plantilla, se crean enlaces a otros presenters y acciones de esta manera: +En la plantilla, los enlaces a otros presenters y acciones se crean así: ```latte -detalle del producto +product detail ``` -Simplemente, en lugar de la URL real, escribe el par conocido `Presenter:action` e indica los parámetros necesarios. El truco está en `n:href`, que indica que este atributo será procesado por Latte y generará la URL real. En Nette, por lo tanto, no necesita pensar en absoluto en las URL, solo en los presenters y las acciones. +Simplemente escriba en lugar de la URL real el conocido par `Presenter:action` e indique los parámetros que hagan falta. El truco está en `n:href`, que le dice a Latte que procese este atributo y genere la URL real. En Nette no tiene que pensar en absoluto en las URL, solo en los presenters y las acciones. Encontrará más información en el capítulo [Creación de enlaces URL|creating-links]. @@ -150,50 +175,50 @@ Encontrará más información en el capítulo [Creación de enlaces URL|creating Redirección =========== -Para pasar a otro presenter se utilizan los métodos `redirect()` y `forward()`, que tienen una sintaxis muy similar al método [link() |#Creación de enlaces]. +Para pasar a otro presenter sirven los métodos `redirect()` y `forward()`, que tienen una sintaxis muy parecida a la del método [link() |#Creación de enlaces]. -El método `forward()` pasa al nuevo presenter inmediatamente sin redirección HTTP: +El método `forward()` pasa al nuevo presenter inmediatamente, sin redirección HTTP: ```php $this->forward('Product:show'); ``` -Ejemplo de la llamada redirección temporal con código HTTP 302 (o 303, si el método de la petición actual es POST): +Ejemplo de redirección temporal con el código HTTP 302 (o 303, si el método de la petición actual es POST): ```php $this->redirect('Product:show', $id); ``` -La redirección permanente con código HTTP 301 se logra así: +Para conseguir una redirección permanente con el código HTTP 301, use esto: ```php $this->redirectPermanent('Product:show', $id); ``` -A otra URL fuera de la aplicación se puede redirigir con el método `redirectUrl()`. Como segundo parámetro se puede indicar el código HTTP, el predeterminado es 302 (o 303, si el método de la petición actual es POST): +A otra URL fuera de la aplicación puede redirigir con el método `redirectUrl()`. El código HTTP se puede indicar como segundo parámetro; el predeterminado es 302 (o 303, si el método de la petición actual es POST): ```php $this->redirectUrl('https://nette.org'); ``` -La redirección finaliza inmediatamente la actividad del presenter lanzando la llamada excepción de finalización silenciosa `Nette\Application\AbortException`. +La redirección termina inmediatamente la actividad del presenter lanzando la llamada excepción de terminación silenciosa `Nette\Application\AbortException`. -Antes de la redirección se puede enviar un [mensaje flash |#Mensajes flash], es decir, mensajes que se mostrarán en la plantilla después de la redirección. +Antes de la redirección se pueden enviar [#Mensajes flash], es decir, mensajes que se mostrarán en la plantilla después de redirigir. Mensajes flash ============== -Son mensajes que generalmente informan sobre el resultado de alguna operación. Una característica importante de los mensajes flash es que están disponibles en la plantilla incluso después de una redirección. Incluso después de mostrarse, permanecen activos durante otros 30 segundos, por ejemplo, en caso de que el usuario actualice la página debido a una transmisión errónea, el mensaje no desaparecerá de inmediato. +Son mensajes que suelen informar del resultado de alguna operación. Una propiedad importante de los mensajes flash es que siguen disponibles en la plantilla incluso después de una redirección. Una vez mostrados siguen activos otros 30 segundos: por ejemplo, si el usuario recarga la página debido a un error de transmisión, el mensaje no desaparecerá de inmediato. -Basta con llamar al método [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] y el presenter se encargará de pasarlo a la plantilla. El primer parámetro es el texto del mensaje y el segundo parámetro opcional es su tipo (error, warning, info, etc.). El método `flashMessage()` devuelve una instancia del mensaje flash, a la que se le puede agregar información adicional. +Basta con llamar al método [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] y el presenter se encarga de pasarlo a la plantilla. El primer parámetro es el texto del mensaje y el segundo, opcional, es su tipo (p. ej. error, warning, info). El método `flashMessage()` devuelve una instancia del mensaje flash, a la que se le puede añadir más información. ```php -$this->flashMessage('El elemento ha sido eliminado.'); -$this->redirect(/* ... */); // y redirigimos +$this->flashMessage('The item has been deleted.'); +$this->redirect(/* ... */); // y redirige ``` -En la plantilla, estos mensajes están disponibles en la variable `$flashes` como objetos `stdClass`, que contienen las propiedades `message` (texto del mensaje), `type` (tipo de mensaje) y pueden contener la información de usuario ya mencionada. Los renderizamos, por ejemplo, así: +En la plantilla, estos mensajes están disponibles en la variable `$flashes` como objetos `stdClass` que contienen las propiedades `message` (el texto del mensaje), `type` (el tipo del mensaje) y, eventualmente, la información añadida por el usuario ya mencionada. Los renderizamos así: ```latte {foreach $flashes as $flash} @@ -202,10 +227,10 @@ En la plantilla, estos mensajes están disponibles en la variable `$flashes` com ``` -Error 404 y cía. -================ +Error 404 y otros +================= -Si no se puede cumplir la petición, por ejemplo, porque el artículo que queremos mostrar no existe en la base de datos, lanzamos un error 404 con el método `error(?string $message = null, int $httpCode = 404)`. +Si no podemos atender la petición, por ejemplo porque el artículo que queremos mostrar no existe en la base de datos, lanzamos un error 404 con el método `error(string $message = '', int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,13 +243,13 @@ public function renderShow(int $id): void } ``` -El código HTTP del error se puede pasar como segundo parámetro, el predeterminado es 404. El método funciona lanzando la excepción `Nette\Application\BadRequestException`, tras lo cual `Application` pasa el control al error-presenter. Que es un presenter cuya tarea es mostrar una página informando sobre el error ocurrido. La configuración del error-presenter se realiza en la [configuración de application|configuration]. +El código HTTP del error se puede pasar como segundo parámetro; el predeterminado es 404. El método funciona lanzando la excepción `Nette\Application\BadRequestException`, tras la cual `Application` pasa el control al presenter de error. Es un presenter cuya tarea es mostrar una página que informe del error ocurrido. El presenter de error se establece en la [configuración de la aplicación|configuration]. Envío de JSON ============= -Ejemplo de un método de acción que envía datos en formato JSON y finaliza el presenter: +El método `sendJson($data)` codifica los datos indicados en JSON, los envía como respuesta HTTP y termina el presenter. Ejemplo: ```php public function actionData(): void @@ -238,9 +263,9 @@ public function actionData(): void Parámetros de la petición .{data-version:3.1.14} ================================================ -El presenter y también cada componente obtienen sus parámetros de la petición HTTP. Puede obtener su valor con el método `getParameter($name)` o `getParameters()`. Los valores son cadenas o arrays de cadenas, son básicamente datos brutos obtenidos directamente de la URL. +El presenter, y también cada componente, obtiene sus parámetros de la petición HTTP. Puede consultar sus valores con los métodos `getParameter($name)` o `getParameters()`. Los valores son cadenas o arrays de cadenas, en esencia datos en bruto obtenidos directamente de la URL. -Para mayor comodidad, recomendamos acceder a los parámetros a través de propiedades. Basta con marcarlas con el atributo `#[Parameter]`: +Para mayor comodidad recomendamos acceder a los parámetros mediante propiedades. Basta con marcarlas con el atributo `#[Parameter]`: ```php use Nette\Application\Attributes\Parameter; // esta línea es importante @@ -252,9 +277,9 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Recomendamos indicar también el tipo de dato para la propiedad (p. ej., `string`) y Nette lo convertirá automáticamente según él. Los valores de los parámetros también se pueden [validar |#Validación de parámetros]. +Para la propiedad recomendamos indicar el tipo de dato (p. ej. `string`) y Nette convertirá el valor automáticamente. Los valores de los parámetros también se pueden [validar |#Validación de los parámetros]. -Al crear un enlace, se puede establecer directamente el valor de los parámetros: +Al crear un enlace puede establecer directamente el valor del parámetro: ```latte click @@ -264,11 +289,11 @@ Al crear un enlace, se puede establecer directamente el valor de los parámetros Parámetros persistentes ======================= -Los parámetros persistentes sirven para mantener el estado entre diferentes peticiones. Su valor permanece igual incluso después de hacer clic en un enlace. A diferencia de los datos en la sesión, se transfieren en la URL. Y esto de forma totalmente automática, por lo que no es necesario indicarlos explícitamente en `link()` o `n:href`. +Los parámetros persistentes sirven para mantener el estado entre distintas peticiones. Su valor sigue siendo el mismo incluso después de pulsar un enlace. A diferencia de los datos de la sesión, se transfieren en la URL. Y esto ocurre de forma completamente automática, así que no hace falta indicarlos explícitamente en `link()` ni en `n:href`. -¿Un ejemplo de uso? Tiene una aplicación multilingüe. El idioma actual es un parámetro que debe estar constantemente presente en la URL. Pero sería increíblemente tedioso indicarlo en cada enlace. Así que lo convierte en un parámetro persistente `lang` y se transferirá solo. ¡Genial! +¿Un ejemplo de uso? Imagine que tiene una aplicación multilingüe. El idioma actual es un parámetro que debe formar parte siempre de la URL. Pero sería increíblemente tedioso indicarlo en cada enlace. Así que lo convierte en el parámetro persistente `lang` y se irá arrastrando solo. ¡Genial! -Crear un parámetro persistente es extremadamente simple en Nette. Basta con crear una propiedad pública y marcarla con un atributo: (anteriormente se usaba `/** @persistent */`) +Crear un parámetro persistente en Nette es facilísimo. Basta con crear una propiedad pública y marcarla con el atributo: (antes se usaba `/** @persistent */`) ```php use Nette\Application\Attributes\Persistent; // esta línea es importante @@ -280,14 +305,14 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Si `$this->lang` tiene el valor, por ejemplo, `'en'`, entonces también los enlaces creados mediante `link()` o `n:href` contendrán el parámetro `lang=en`. Y después de hacer clic en el enlace, nuevamente `$this->lang = 'en'`. +Si `$this->lang` tiene, por ejemplo, el valor `'en'`, los enlaces creados con `link()` o `n:href` contendrán también el parámetro `lang=en`. Y después de pulsar el enlace, `$this->lang` volverá a ser `'en'`. -Recomendamos indicar también el tipo de dato para la propiedad (p. ej., `string`) y puede indicar también un valor predeterminado. Los valores de los parámetros se pueden [validar |#Validación de parámetros]. +Para la propiedad recomendamos indicar el tipo de dato (p. ej. `string`) y también puede indicar un valor por defecto. Los valores de los parámetros se pueden [validar |#Validación de los parámetros]. -Los parámetros persistentes se transfieren estándarmente entre todas las acciones del presenter dado. Para que se transfieran también entre varios presenters, es necesario definirlos ya sea: +Los parámetros persistentes se transfieren normalmente entre todas las acciones de un presenter dado. Para transferirlos también entre varios presenters hay que definirlos: -- en un ancestro común del que heredan los presenters -- en un trait que usen los presenters: +- en un antecesor común del que hereden los presenters +- o en un trait que usen los presenters: ```php trait LanguageAware @@ -302,42 +327,62 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Al crear un enlace, se puede cambiar el valor del parámetro persistente: +Al crear un enlace se puede cambiar el valor de un parámetro persistente: ```latte -detalle en checo +detail in Czech ``` -O se puede *resetear*, es decir, eliminar de la URL. Entonces tomará su valor predeterminado: +O se puede *resetear*, es decir, eliminar de la URL. Entonces adoptará su valor por defecto: ```latte -haz clic +click +``` + + +Espacio común de parámetros +=========================== + +Los parámetros de la petición, los [parámetros persistentes |#Parámetros persistentes] y los parámetros de los métodos `action`, `render` y `handle` (señal) comparten un único espacio, en el que cada uno se identifica por su nombre. Si el mismo nombre aparece en varios de ellos, se refieren a un único y mismo valor. + +Esto se aprovecha a menudo. Por ejemplo, el parámetro persistente `lang` y el argumento `$lang` de un método de acción o de señal son una y la misma cosa: puede leer el valor actual de un parámetro persistente con solo indicarlo en la firma del método: + +```php +#[Persistent] +public string $lang; + +public function handleSearch(string $query, string $lang): void +{ + // $lang contiene el valor actual del parámetro persistente lang +} ``` +Como este espacio es compartido, mantenga los nombres de los parámetros únicos, salvo que quiera deliberadamente que compartan un valor. Esto vale también para las señales, que además leen parámetros del cuerpo POST de la petición, véase [Las señales en profundidad |components#Las señales en profundidad]. + Componentes interactivos ======================== -Los presenters tienen incorporado un sistema de componentes. Los componentes son unidades reutilizables independientes que insertamos en los presenters. Pueden ser [formularios |forms:in-presenter], datagrids, menús, en realidad cualquier cosa que tenga sentido usar repetidamente. +Los presenters llevan incorporado un sistema de componentes. Los componentes son unidades independientes y reutilizables que insertamos en los presenters. Pueden ser [formularios |forms:in-presenter], datagrids, menús, en definitiva cualquier cosa que tenga sentido usar repetidamente. -¿Cómo se insertan los componentes en el presenter y se usan posteriormente? Eso lo aprenderá en el capítulo [Componentes |components]. Incluso descubrirá qué tienen en común con Hollywood. +¿Cómo se insertan los componentes en los presenters y cómo se usan después? Lo aprenderá en el capítulo [Componentes |components]. Descubrirá incluso qué tienen en común con Hollywood. -¿Y dónde puedo obtener componentes? En la página [Componette |https://componette.org/search/component] encontrará componentes de código abierto y también muchos otros complementos para Nette, que voluntarios de la comunidad alrededor del framework han colocado aquí. +¿Y dónde puedo conseguir componentes? En [Componette |https://componette.org/search/component] encontrará componentes de código abierto y muchos otros complementos para Nette, aportados por voluntarios de la comunidad del framework. -Vamos a profundizar -=================== +Profundizando +============= .[tip] -Con lo que hemos mostrado hasta ahora en este capítulo, probablemente le sea suficiente. Las siguientes líneas están destinadas a aquellos que se interesan por los presenters en profundidad y quieren saber absolutamente todo. +Lo que hemos visto hasta ahora en este capítulo bastará probablemente para la mayoría de los usos. Las secciones siguientes están pensadas para quien quiera profundizar en los presenters y saberlo absolutamente todo. -Validación de parámetros ------------------------- +Validación de los parámetros +---------------------------- -Los valores de los [#parámetros de la petición] y los [#parámetros persistentes] recibidos de la URL se escriben en las propiedades mediante el método `loadState()`. Este también comprueba si el tipo de dato indicado en la propiedad coincide, de lo contrario responde con un error 404 y la página no se muestra. +Los valores de los [#Parámetros de la petición] y de los [#Parámetros persistentes] recibidos de las URL los escribe en las propiedades el método `loadState()`. Este comprueba también si coinciden con el tipo de dato indicado en la propiedad; en caso contrario responderá con un error 404 y la página no se mostrará. -Nunca confíe ciegamente en los parámetros, ya que pueden ser fácilmente sobrescritos por el usuario en la URL. Así, por ejemplo, verificamos si el idioma `$this->lang` está entre los soportados. Una forma adecuada es sobrescribir el método mencionado `loadState()`: +Nunca confíe ciegamente en los parámetros recibidos de la URL, porque el usuario puede sobrescribirlos fácilmente. Así, por ejemplo, comprobaríamos si el idioma `$this->lang` está entre los admitidos. Una forma adecuada de hacerlo es sobrescribir el mencionado método `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -348,7 +393,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { parent::loadState($params); // aquí se establece $this->lang - // sigue la verificación propia del valor: + // sigue la comprobación propia del valor: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -357,65 +402,47 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Guardado y restauración de la petición --------------------------------------- +Guardar y restaurar la petición +------------------------------- -La petición que gestiona el presenter es un objeto [api:Nette\Application\Request] y lo devuelve el método del presenter `getRequest()`. +La petición que procesa el presenter es un objeto [api:Nette\Application\Request], que devuelve el método `getRequest()` del presenter. -La petición actual se puede guardar en la sesión o, por el contrario, restaurarla desde ella y hacer que el presenter la ejecute de nuevo. Esto es útil, por ejemplo, en una situación en la que el usuario está rellenando un formulario y su sesión expira. Para no perder los datos, antes de redirigir a la página de inicio de sesión, guardamos la petición actual en la sesión usando `$reqId = $this->storeRequest()`, que devuelve su identificador en forma de cadena corta y lo pasamos como parámetro al presenter de inicio de sesión. +La petición actual se puede guardar en la sesión o, al revés, restaurarla desde ella y hacer que el presenter la ejecute otra vez. Esto resulta útil, por ejemplo, cuando el usuario está rellenando un formulario y su sesión de acceso caduca. Para no perder los datos, antes de redirigir a la página de acceso guardamos la petición actual en la sesión con `$reqId = $this->storeRequest()`. Esto devuelve su identificador en forma de cadena corta, que luego pasamos como parámetro al presenter de acceso. -Después de iniciar sesión, llamamos al método `$this->restoreRequest($reqId)`, que recupera la petición de la sesión y hace forward a ella. El método, al mismo tiempo, verifica que la petición la creó el mismo usuario que ahora ha iniciado sesión. Si iniciara sesión otro usuario o la clave fuera inválida, no hace nada y el programa continúa. +Tras el acceso llamamos al método `$this->restoreRequest($reqId)`, que recupera la petición de la sesión. Las peticiones POST se le reenvían, mientras que las demás (GET) se redirigen a la URL de la petición. El método comprueba que la petición la creó el mismo usuario que ahora está conectado. Si se conecta otro usuario o la clave no es válida, no hace nada y el programa continúa con normalidad. -Consulte el tutorial [Cómo volver a una página anterior |best-practices:restore-request]. +Véase la guía [Cómo volver a una página anterior |best-practices:restore-request]. Canonización ------------ -Los presenters tienen una característica realmente genial que contribuye a un mejor SEO (optimización para motores de búsqueda). Evitan automáticamente la existencia de contenido duplicado en diferentes URL. Si a un destino determinado conducen varias direcciones URL, p. ej., `/index` y `/index?page=1`, el framework determina una de ellas como primaria (canónica) y redirige las demás a ella usando el código HTTP 301. Gracias a esto, los motores de búsqueda no indexan sus páginas dos veces y no diluyen su page rank. +Los presenters tienen una característica realmente excelente que contribuye a un mejor SEO (Search Engine Optimization). Impiden automáticamente que exista contenido duplicado en URL distintas. Si a un destino concreto llevan varias URL, p. ej. `/index` y `/index?page=1`, el framework designa una de ellas como principal (canónica) y redirige las demás a ella con el código HTTP 301. Gracias a eso, los buscadores no indexan sus páginas dos veces ni diluyen su page rank. -Este proceso se llama canonización. La URL canónica es la que genera el [router|routing], generalmente, por lo tanto, la primera ruta correspondiente en la colección. +Este proceso se llama canonización. La URL canónica es la que genera el [router|routing], normalmente la primera ruta coincidente de la colección. -La canonización está activada por defecto y se puede desactivar mediante `$this->autoCanonicalize = false`. +La canonización está activada de forma predeterminada y se puede desactivar con `$this->autoCanonicalize = false`. -La redirección no ocurre en una petición AJAX o POST, porque se perderían datos o no tendría valor añadido desde el punto de vista del SEO. +La redirección no se produce en las peticiones AJAX ni POST, porque podría provocar la pérdida de datos o no aportaría ningún valor SEO añadido. -También puede invocar la canonización manualmente usando el método `canonicalize()`, al que, de manera similar al método `link()`, se le pasa el presenter, la acción y los parámetros. Crea un enlace y lo compara con la dirección URL actual. Si difieren, redirige al enlace generado. +También puede provocar la canonización manualmente con el método `canonicalize()`. Igual que al método `link()`, se le pasan el presenter, la acción y los parámetros. Genera un enlace y lo compara con la dirección URL actual. Si difieren, redirige al enlace generado. ```php public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // redirige si $slug difiere de $realSlug + // redirige si $slug es distinto de $realSlug $this->canonicalize('Product:show', [$id, $realSlug]); } ``` - -Eventos -------- - -Además de los métodos `startup()`, `beforeRender()` y `shutdown()`, que se llaman como parte del ciclo de vida del presenter, se pueden definir otras funciones que deben llamarse automáticamente. El presenter define los llamados [eventos |nette:glossary#Eventos], cuyos manejadores agrega a los arrays `$onStartup`, `$onRender` y `$onShutdown`. - -```php -class ArticlePresenter extends Nette\Application\UI\Presenter -{ - public function __construct() - { - $this->onStartup[] = function () { - // ... - }; - } -} -``` - -Los manejadores en el array `$onStartup` se llaman justo antes del método `startup()`, luego `$onRender` entre `beforeRender()` y `render()` y finalmente `$onShutdown` justo antes de `shutdown()`. +Para ver un patrón completo que combina los filtros de ruta con `canonicalize()` para obtener URL amigables para el SEO, consulte [URLs amigables con slugs |best-practices:pretty-urls]. Respuestas ---------- -La respuesta que devuelve el presenter es un objeto que implementa la interfaz [api:Nette\Application\Response]. Hay disponibles varias respuestas preparadas: +La respuesta que devuelve el presenter es un objeto que implementa la interfaz [api:Nette\Application\Response]. Hay disponibles varias respuestas ya preparadas: - [api:Nette\Application\Responses\CallbackResponse] - envía un callback - [api:Nette\Application\Responses\FileResponse] - envía un archivo @@ -436,7 +463,7 @@ $this->sendResponse(new Responses\TextResponse('Hello Nette!')); // Envía un archivo $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// La respuesta será un callback +// Envía un callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -445,28 +472,88 @@ $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $ht $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +También puede escribir su propia respuesta. Basta con implementar la interfaz `Nette\Application\Response`, que tiene un único método `send()` que recibe la petición y la respuesta HTTP. Esto es útil, por ejemplo, al transmitir datos que no quiere mantener en memoria: + +```php +class CsvResponse implements Nette\Application\Response +{ + public function __construct( + private string $fileName, + private iterable $rows, + ) { + } + + public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void + { + $response->setContentType('text/csv', 'utf-8'); + $response->sendAsFile($this->fileName); + + $handle = fopen('php://output', 'w'); + foreach ($this->rows as $row) { + fputcsv($handle, $row); + } + + fclose($handle); + } +} +``` + +Después la envía en el presenter como de costumbre: `$this->sendResponse(new CsvResponse('export.csv', $rows));` + + +Caché HTTP +---------- + +El método `lastModified()` permite aprovechar fácilmente la caché HTTP. Se le pasa la fecha y hora de la última modificación del contenido (como timestamp, cadena u objeto `DateTimeInterface`) y, opcionalmente, un validador ETag (una cadena corta que identifica la versión actual del contenido, por ejemplo su hash) y un tiempo de expiración. Si el navegador ya tiene una versión coincidente, el presenter envía una respuesta `304 Not Modified` y termina, con lo que la página no se renderiza ni se transfiere innecesariamente: + +```php +public function renderArticle(int $id): void +{ + $article = $this->articles->getById($id); + $this->lastModified($article->updatedAt); + // ... +} +``` + + +Completar la plantilla .{data-version:3.3.0} +-------------------------------------------- + +Cuando el presenter renderiza la plantilla, el método `sendTemplate()` llama a `completeTemplate()` justo antes del renderizado. Este método rellena las variables marcadas con el atributo `#[TemplateVariable]` y localiza el archivo de la plantilla (las variables predeterminadas ya las establece `TemplateFactory` al crear la plantilla). Puede sobrescribir este método protegido para añadir variables compartidas por todas las vistas o para establecer otro archivo: + +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My App'; +} +``` + -Restricción de acceso mediante `#[Requires]` .{data-version:3.2.2} ------------------------------------------------------------------- +Restricción de acceso con `#[Requires]` .{data-version:3.2.3} +------------------------------------------------------------- -El atributo `#[Requires]` proporciona opciones avanzadas para restringir el acceso a presenters y sus métodos. Se puede usar para especificar métodos HTTP, requerir una petición AJAX, restringir al mismo origen (same origin) y acceso solo a través de forward. El atributo se puede aplicar tanto a clases de presenters como a métodos individuales `action()`, `render()`, `handle()` y `createComponent()`. +El atributo `#[Requires]` ofrece opciones avanzadas para restringir el acceso a los presenters y a sus métodos. Sirve para indicar métodos HTTP, exigir una petición AJAX, limitarlo al mismo origen y permitir el acceso solo mediante forward. El atributo se puede aplicar tanto a las clases de los presenters como a métodos concretos como `action()`, `render()`, `handle()` y `createComponent()`. -Puede especificar estas restricciones: -- a métodos HTTP: `#[Requires(methods: ['GET', 'POST'])]` -- requerir una petición AJAX: `#[Requires(ajax: true)]` +Puede indicar estas restricciones: +- sobre los métodos HTTP: `#[Requires(methods: ['GET', 'POST'])]` +- exigiendo una petición AJAX: `#[Requires(ajax: true)]` - acceso solo desde el mismo origen: `#[Requires(sameOrigin: true)]` -- acceso solo a través de forward: `#[Requires(forward: true)]` -- restricción a acciones específicas: `#[Requires(actions: 'default')]` +- acceso solo mediante forward: `#[Requires(forward: true)]` +- restricciones para acciones concretas: `#[Requires(actions: 'default')]` + +.[note] +Desde la versión 3.3, la coincidencia de origen se comprueba mediante la cabecera `Sec-Fetch-Site` del navegador (antes mediante una cookie SameSite), lo que es más fiable y comprueba la coincidencia exacta de esquema, dominio y puerto. -Encontrará detalles en el tutorial [Cómo usar el atributo Requires |best-practices:attribute-requires]. +Los detalles los encontrará en la guía [Cómo usar el atributo Requires |best-practices:attribute-requires]. -Verificación del método HTTP +Comprobación del método HTTP ---------------------------- -Los presenters en Nette verifican automáticamente el método HTTP de cada petición entrante. La razón de esta verificación es principalmente la seguridad. Por defecto, se permiten los métodos `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. +Los presenters en Nette comprueban automáticamente el método HTTP de cada petición entrante, sobre todo por motivos de seguridad. De forma predeterminada se permiten los métodos `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. -Si desea permitir adicionalmente, por ejemplo, el método `OPTIONS`, use para ello el atributo `#[Requires]` (desde Nette Application v3.2): +Si quiere permitir además, por ejemplo, el método `OPTIONS`, use el atributo `#[Requires]` (desde Nette Application v3.2.3): ```php #[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] @@ -475,26 +562,34 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -En la versión 3.1, la verificación se realiza en `checkHttpMethod()`, que comprueba si el método especificado en la petición está contenido en el array `$presenter->allowedMethods`. La adición del método se hace así: +Desde la versión 3.1.13, la comprobación se realiza en `checkHttpMethod()`, que verifica si el método indicado en la petición está incluido en el array `$presenter->allowedMethods`. Desde la versión 3.2.3, este enfoque está obsoleto en favor de `#[Requires]`. El método se puede sobrescribir así: ```php class MyPresenter extends Nette\Application\UI\Presenter { - protected function checkHttpMethod(): void - { - $this->allowedMethods[] = 'OPTIONS'; - parent::checkHttpMethod(); - } + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } } ``` -Es importante destacar que si permite el método `OPTIONS`, debe luego también gestionarlo adecuadamente dentro de su presenter. El método se usa a menudo como la llamada petición preflight, que el navegador envía automáticamente antes de la petición real, cuando es necesario averiguar si la petición está permitida desde el punto de vista de la política CORS (Cross-Origin Resource Sharing). Si permite el método, pero no implementa la respuesta correcta, puede llevar a inconsistencias y posibles problemas de seguridad. +Es importante subrayar que, si habilita el método `OPTIONS`, debe después gestionarlo adecuadamente dentro de su presenter. Este método se usa a menudo como la llamada petición preflight, que el navegador envía automáticamente antes de la petición propiamente dicha cuando hay que averiguar si la petición es admisible según la política CORS (Cross-Origin Resource Sharing). Si habilita el método pero no implementa la respuesta correcta, puede provocar inconsistencias y posibles problemas de seguridad. -Lectura adicional -================= +Marcar acciones obsoletas .{data-version:3.2.3} +----------------------------------------------- + +El atributo `#[Deprecated]` marca acciones, señales o presenters enteros como obsoletos y destinados a desaparecer en el futuro. Al generar enlaces a partes obsoletas de la aplicación, Nette lanza una advertencia para avisar a los desarrolladores. + +El atributo se puede aplicar tanto a toda la clase del presenter como a métodos concretos `action()`, `render()` y `handle()`. + + +Lecturas adicionales +==================== - [Métodos y atributos inject |best-practices:inject-method-attribute] -- [Composición de presenters a partir de traits |best-practices:presenter-traits] -- [Paso de configuraciones a presenters |best-practices:passing-settings-to-presenters] +- [Componer presenters a partir de traits |best-practices:presenter-traits] +- [Pasar ajustes a los presenters |best-practices:passing-settings-to-presenters] - [Cómo volver a una página anterior |best-practices:restore-request] diff --git a/application/es/routing.texy b/application/es/routing.texy index d906f83f48..7aa9ce9fb1 100644 --- a/application/es/routing.texy +++ b/application/es/routing.texy @@ -3,26 +3,26 @@ Enrutamiento
    -El Router se encarga de todo lo relacionado con las direcciones URL, para que usted ya no tenga que pensar en ellas. Mostraremos: +El router se ocupa de todo lo relacionado con las direcciones URL, de modo que usted no tenga que pensar en ellas. Le mostraremos: -- cómo configurar el router para que las URL sean según sus deseos -- hablaremos de SEO y redirección -- y mostraremos cómo escribir su propio router +- cómo configurar el router para que las URL tengan el aspecto que desea +- hablaremos del SEO y de las redirecciones +- y le enseñaremos cómo escribir un router propio
    -Las URL más humanas (o también cool o pretty URL) son más usables, memorables y contribuyen positivamente al SEO. Nette piensa en esto y apoya plenamente a los desarrolladores. Puede diseñar para su aplicación exactamente la estructura de direcciones URL que desee. Incluso puede diseñarla cuando la aplicación ya está terminada, porque se puede hacer sin intervenciones en el código o las plantillas. Se define de manera elegante en un [único lugar |#Integración en la aplicación], en el router, y no está dispersa en forma de anotaciones en todos los presenters. +Las URL más amables para las personas (también llamadas cool o pretty URLs) son más utilizables, se recuerdan mejor y contribuyen positivamente al SEO. Nette lo tiene en cuenta y satisface plenamente las necesidades de los desarrolladores. Puede diseñar para su aplicación exactamente la estructura de URL que quiera. Incluso puede diseñarla cuando la aplicación ya está terminada, porque no requiere ningún cambio en el código ni en las plantillas. Se define de forma elegante en [un único lugar |#Integración], el router, en vez de andar dispersa como anotaciones por todos los presenters. -El Router en Nette es extraordinario porque es **bidireccional.** Sabe tanto decodificar URL en la petición HTTP como crear enlaces. Juega, por lo tanto, un papel fundamental en [Nette Application |how-it-works#Nette Application], porque por un lado decide qué presenter y acción ejecutará la petición actual, pero también se utiliza para [generar URL |creating-links] en la plantilla, etc. +El router en Nette es excepcional porque es **bidireccional.** Sabe tanto descodificar las URL de las peticiones HTTP como crear enlaces. Por eso desempeña un papel clave en [Nette Application |how-it-works#Nette Application], ya que no solo decide qué presenter y qué acción ejecutarán la petición actual, sino que se usa también para [generar las URL |creating-links] en las plantillas, etc. -Sin embargo, el router no está limitado solo a este uso, puede usarlo en aplicaciones donde no se usan presenters en absoluto, para API REST, etc. Más en la sección [#Uso independiente]. +El router no se limita, sin embargo, a este uso; puede utilizarlo en aplicaciones donde no se usan presenters en absoluto, para API REST, etc. Más detalles en la sección [#Uso independiente]. Colección de rutas ================== -La forma más agradable de definir la apariencia de las direcciones URL en la aplicación la ofrece la clase [api:Nette\Application\Routers\RouteList]. La definición consiste en una lista de las llamadas rutas, es decir, máscaras de direcciones URL y sus presenters y acciones asociados mediante una API simple. No necesitamos nombrar las rutas de ninguna manera. +La forma más agradable de definir la estructura de las direcciones URL de una aplicación la ofrece la clase [api:Nette\Application\Routers\RouteList]. La definición consiste en una lista de las llamadas rutas, es decir, máscaras de direcciones URL y los presenters y acciones asociados, mediante una API sencilla. No hace falta nombrar las rutas de ninguna manera. ```php $router = new Nette\Application\Routers\RouteList; @@ -31,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -El ejemplo dice que si abrimos `https://domain.com/rss.xml` en el navegador, se mostrará el presenter `Feed` con la acción `rss`, si `https://domain.com/article/12`, se mostrará el presenter `Article` con la acción `view`, etc. En caso de no encontrar una ruta adecuada, Nette Application reacciona lanzando una excepción [BadRequestException |api:Nette\Application\BadRequestException], que se muestra al usuario como una página de error 404 Not Found. +El ejemplo muestra que, si abrimos en el navegador `https://domain.com/rss.xml`, se mostrará el presenter `Feed` con la acción `rss`. Si abrimos `https://domain.com/article/12`, se mostrará el presenter `Article` con la acción `view`, etc. Si no se encuentra ninguna ruta adecuada, Nette Application reacciona lanzando la excepción [BadRequestException |api:Nette\Application\BadRequestException], que se muestra al usuario como una página de error 404 Not Found. Orden de las rutas ------------------ -Es absolutamente **clave el orden** en que se indican las rutas individuales, porque se evalúan secuencialmente de arriba abajo. Se aplica la regla de que declaramos las rutas **de específicas a generales**: +El **orden** en el que se indican las distintas rutas es absolutamente **crucial**, porque se evalúan sucesivamente de arriba abajo. La regla es que declaramos las rutas **de las específicas a las generales**: ```php -// MAL: 'rss.xml' lo captura la primera ruta y entiende esta cadena como +// MAL: 'rss.xml' lo captura la primera ruta y entiende esa cadena como $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -49,7 +49,7 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Las rutas también se evalúan de arriba abajo al generar enlaces: +Las rutas se evalúan de arriba abajo también al generar los enlaces: ```php // MAL: el enlace a 'Feed:rss' se genera como 'admin/feed/rss' @@ -61,57 +61,57 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -No le ocultaremos que la correcta composición de las rutas requiere cierta habilidad. Antes de que la domine, le será útil el [panel de enrutamiento |#Depuración del router]. +No le vamos a ocultar que montar las rutas correctamente requiere cierta destreza. Hasta que le coja el truco, el [panel de enrutamiento |#Depuración del router] le será de gran ayuda. Máscara y parámetros -------------------- -La máscara describe la ruta relativa desde el directorio raíz del sitio web. La máscara más simple es una URL estática: +La máscara describe la ruta relativa desde el directorio raíz del sitio web. La máscara más sencilla es una URL estática: ```php $router->addRoute('products', 'Products:default'); ``` -A menudo, las máscaras contienen los llamados **parámetros**. Estos se indican entre corchetes angulares (p. ej., ``) y se pasan al presenter de destino, por ejemplo, al método `renderShow(int $year)` o al parámetro persistente `$year`: +A menudo las máscaras contienen los llamados **parámetros**. Se escriben entre corchetes angulares (p. ej. ``) y se pasan al presenter de destino, por ejemplo al método `renderShow(int $year)` o al parámetro persistente `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -El ejemplo dice que si abrimos `https://example.com/chronicle/2020` en el navegador, se mostrará el presenter `History` con la acción `show` y el parámetro `year: 2020`. +El ejemplo muestra que, si abrimos en el navegador `https://example.com/chronicle/2020`, se mostrará el presenter `History` con la acción `show` y el parámetro `year: 2020`. -Podemos asignar un valor predeterminado a los parámetros directamente en la máscara y así se vuelven opcionales: +A los parámetros les podemos indicar un valor por defecto directamente en la máscara, con lo que se vuelven opcionales: ```php $router->addRoute('chronicle/', 'History:show'); ``` -La ruta ahora aceptará también la URL `https://example.com/chronicle/`, que nuevamente mostrará `History:show` con el parámetro `year: 2020`. +La ruta aceptará ahora también la URL `https://example.com/chronicle/`, que mostrará de nuevo `History:show` con el parámetro `year: 2020`. -El parámetro puede ser, por supuesto, también el nombre del presenter y la acción. Por ejemplo, así: +Naturalmente, el nombre del presenter y de la acción también pueden ser parámetros. Por ejemplo: ```php $router->addRoute('/', 'Home:default'); ``` -La ruta indicada acepta, p. ej., URL en la forma `/article/edit` o también `/catalog/list` y las entiende como presenters y acciones `Article:edit` y `Catalog:list`. +La ruta indicada acepta, por ejemplo, URL con la forma `/article/edit` o `/catalog/list` y las entiende como los presenters y acciones `Article:edit` y `Catalog:list`, respectivamente. -Al mismo tiempo, da a los parámetros `presenter` y `action` los valores predeterminados `Home` y `default` y, por lo tanto, también son opcionales. Así que la ruta acepta también URL en la forma `/article` y la entiende como `Article:default`. O al revés, un enlace a `Product:default` generará la ruta `/product`, un enlace al predeterminado `Home:default` la ruta `/`. +Al mismo tiempo da a los parámetros `presenter` y `action` los valores por defecto `Home` y `default`, con lo que también son opcionales. Así, la ruta acepta también una URL como `/article` y la entiende como `Article:default`. O al revés: un enlace a `Product:default` genera la ruta `/product` y un enlace al predeterminado `Home:default` genera la ruta `/`. -La máscara puede describir no solo la ruta relativa desde el directorio raíz del sitio web, sino también la ruta absoluta, si comienza con una barra inclinada, o incluso una URL absoluta completa, si comienza con dos barras inclinadas: +La máscara puede describir no solo la ruta relativa desde el directorio raíz del sitio web, sino también una ruta absoluta si empieza por una barra, o incluso la URL absoluta entera si empieza por dos barras: ```php -// relativo al document root +// relativa al document root $router->addRoute('/', /* ... */); // ruta absoluta (relativa al dominio) $router->addRoute('//', /* ... */); -// URL absoluta incluyendo dominio (relativa al esquema) +// URL absoluta incluido el dominio (relativa al esquema) $router->addRoute('//.example.com//', /* ... */); -// URL absoluta incluyendo esquema +// URL absoluta incluido el esquema $router->addRoute('https://.example.com//', /* ... */); ``` @@ -119,13 +119,13 @@ $router->addRoute('https://.example.com//', /* ... */); Expresiones de validación ------------------------- -Para cada parámetro se puede establecer una condición de validación mediante una [expresión regular|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Por ejemplo, para el parámetro `id` determinamos que solo puede tomar dígitos usando la expresión regular `\d+`: +A cada parámetro se le puede indicar una condición de validación mediante una [expresión regular|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Por ejemplo, al parámetro `id` le indicamos con la expresión regular `\d+` que solo puede contener cifras: ```php $router->addRoute('/[/]', /* ... */); ``` -La expresión regular predeterminada para todos los parámetros es `[^/]+`, es decir, todo excepto la barra inclinada. Si un parámetro debe aceptar también barras inclinadas, indicamos la expresión `.+`: +La expresión regular predeterminada para todos los parámetros es `[^/]+`, es decir, todo menos la barra. Si un parámetro debe aceptar también barras, indicamos la expresión `.+`: ```php // acepta https://example.com/a/b/c, path será 'a/b/c' @@ -136,19 +136,19 @@ $router->addRoute('', /* ... */); Secuencias opcionales --------------------- -En la máscara se pueden marcar partes opcionales usando corchetes. Opcional puede ser cualquier parte de la máscara, también pueden contener parámetros: +En la máscara se pueden marcar partes opcionales con corchetes. Cualquier parte de la máscara puede ser opcional y puede contener parámetros: ```php $router->addRoute('[/]', /* ... */); -// Acepta rutas: -// /cs/download => lang => cs, name => download +// Acepta las rutas: +// /en/download => lang => en, name => download // /download => lang => null, name => download ``` -Cuando un parámetro es parte de una secuencia opcional, se vuelve, por supuesto, también opcional. Si no tiene un valor predeterminado indicado, será null. +Cuando un parámetro forma parte de una secuencia opcional, se vuelve naturalmente opcional también. Si no tiene indicado un valor por defecto, será null. -Las partes opcionales también pueden estar en el dominio: +Las partes opcionales pueden estar también en el dominio: ```php $router->addRoute('//[.]example.com//', /* ... */); @@ -162,14 +162,14 @@ $router->addRoute( 'Home:default', ); -// Acepta rutas: -// /cs/hello +// Acepta las rutas: +// /en/hello // /en-us/hello // /hello // /hello/page-12 ``` -Al generar URL, se busca la variante más corta, por lo que todo lo que se puede omitir, se omite. Por eso, por ejemplo, la ruta `index[.html]` genera la ruta `/index`. Se puede invertir el comportamiento indicando un signo de exclamación después del corchete izquierdo: +Al generar las URL se busca la variante más corta, así que todo lo que se pueda omitir se omite. Por eso, por ejemplo, la ruta `index[.html]` genera la ruta `/index`. Este comportamiento se puede invertir escribiendo un signo de exclamación después del corchete izquierdo: ```php // acepta /hello y /hello.html, genera /hello @@ -179,7 +179,7 @@ $router->addRoute('[.html]', /* ... */); $router->addRoute('[!.html]', /* ... */); ``` -Los parámetros opcionales (es decir, parámetros que tienen un valor predeterminado) sin corchetes se comportan básicamente como si estuvieran entre paréntesis de la siguiente manera: +Los parámetros opcionales (es decir, los que tienen un valor por defecto) sin corchetes se comportan en esencia como si estuvieran encerrados de la siguiente manera: ```php $router->addRoute('//', /* ... */); @@ -188,7 +188,7 @@ $router->addRoute('//', /* ... */); $router->addRoute('[/[/[]]]', /* ... */); ``` -Si quisiéramos influir en el comportamiento de la barra inclinada final, para que, por ejemplo, en lugar de `/home/` se genere solo `/home`, se puede lograr así: +Si queremos influir en el comportamiento de la barra final, para que se genere por ejemplo `/home` en lugar de `/home/`, se puede conseguir así: ```php $router->addRoute('[[/[/]]]', /* ... */); @@ -198,24 +198,24 @@ $router->addRoute('[[/[/]]]', /* ... */); Comodines --------- -En la máscara de ruta absoluta, podemos usar los siguientes comodines y evitar así, por ejemplo, la necesidad de escribir en la máscara el dominio, que puede diferir en el entorno de desarrollo y producción: +En la máscara de una URL absoluta podemos usar los siguientes comodines para no tener que escribir en la máscara, por ejemplo, el dominio, que puede diferir entre el entorno de desarrollo y el de producción: -- `%tld%` = dominio de nivel superior, p. ej., `com` u `org` -- `%sld%` = dominio de segundo nivel, p. ej., `example` -- `%domain%` = dominio sin subdominios, p. ej., `example.com` -- `%host%` = host completo, p. ej., `www.example.com` +- `%tld%` = dominio de primer nivel, p. ej. `com` u `org` +- `%sld%` = dominio de segundo nivel, p. ej. `example` +- `%domain%` = dominio sin subdominios, p. ej. `example.com` +- `%host%` = host entero, p. ej. `www.example.com` - `%basePath%` = ruta al directorio raíz ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); -$router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('//www.%sld%.%tld%/%basePath%//', /* ... */); ``` -Notación extendida ------------------- +Notación avanzada +----------------- -El destino de la ruta, generalmente escrito en la forma `Presenter:action`, también puede escribirse usando un array que define los parámetros individuales y sus valores predeterminados: +El destino de la ruta, que se escribe normalmente con el formato `Presenter:action`, se puede escribir también mediante un array que define los distintos parámetros y sus valores por defecto: ```php $router->addRoute('/[/]', [ @@ -224,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Para una especificación más detallada, se puede usar una forma aún más extendida, donde además de los valores predeterminados podemos establecer otras propiedades de los parámetros, como por ejemplo la expresión regular de validación (ver parámetro `id`): +Para una especificación más detallada se puede usar una forma aún más extendida, en la que, además de los valores por defecto, podemos establecer otras propiedades de los parámetros, como una expresión regular de validación (véase el parámetro `id`): ```php use Nette\Routing\Route; @@ -242,19 +242,29 @@ $router->addRoute('/[/]', [ ]); ``` -Es importante señalar que si los parámetros definidos en el array no se indican en la máscara de la ruta, sus valores no se pueden cambiar, ni siquiera mediante parámetros de consulta indicados después del signo de interrogación en la URL. +Es importante señalar que, si los parámetros definidos en el array no aparecen en la máscara de la ruta, sus valores no se pueden cambiar, ni siquiera con los parámetros de consulta indicados tras el signo de interrogación en la URL. + +Esto es útil para los **parámetros fijos**: dar a una página concreta una URL corta y fácil de recordar. Por ejemplo, para que `/tos` abra siempre `Article:view` con `id: 123`: + +```php +$router->addRoute('tos', [ + 'presenter' => 'Article', + 'action' => 'view', + 'id' => 123, +]); +``` Filtros y traducciones ---------------------- -Escribimos los códigos fuente de la aplicación en inglés, pero si el sitio web debe tener URL en español, entonces un enrutamiento simple como: +El código fuente de la aplicación lo escribimos en inglés, pero si el sitio web debe tener las URL en checo, un enrutamiento sencillo como: ```php $router->addRoute('/', 'Home:default'); ``` -generará URL en inglés, como `/product/123` o `/cart`. Si queremos que los presenters y las acciones en la URL estén representados por palabras en español (p. ej., `/producto/123` o `/carrito`), podemos utilizar un diccionario de traducción. Para su escritura ya necesitamos la variante "más detallada" del segundo parámetro: +generará URL en inglés, como `/product/123` o `/cart`. Si queremos que los presenters y las acciones en la URL estén representados por palabras checas (p. ej. `/produkt/123` o `/kosik`), podemos usar un diccionario de traducción. Para escribirlo necesitamos ya la variante "más locuaz" del segundo parámetro: ```php use Nette\Routing\Route; @@ -264,25 +274,25 @@ $router->addRoute('/', [ Route::Value => 'Home', Route::FilterTable => [ // cadena en la URL => presenter - 'producto' => 'Product', - 'carrito' => 'Cart', - 'catalogo' => 'Catalog', + 'produkt' => 'Product', + 'kosik' => 'Cart', + 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'lista' => 'list', + 'seznam' => 'list', ], ], ]); ``` -Varias claves del diccionario de traducción pueden llevar al mismo presenter. De esta manera se crean diferentes alias para él. La variante canónica (es decir, la que estará en la URL generada) se considera la última clave. +Varias claves del diccionario de traducción pueden llevar al mismo presenter. Así se le crean distintos alias. La última clave se considera la variante canónica (es decir, la que aparecerá en la URL generada). -La tabla de traducción se puede usar de esta manera para cualquier parámetro. Si la traducción no existe, se toma el valor original. Este comportamiento se puede cambiar agregando `Route::FilterStrict => true` y la ruta rechazará la URL si el valor no está en el diccionario. +La tabla de traducción se puede usar de esta manera para cualquier parámetro. Si la traducción no existe, se toma el valor original. Podemos cambiar este comportamiento añadiendo `Route::FilterStrict => true` y la ruta rechazará entonces la URL si el valor no está en el diccionario. -Además del diccionario de traducción en forma de array, se pueden implementar funciones de traducción propias. +Además del diccionario de traducción en forma de array, se pueden emplear funciones de traducción propias. ```php use Nette\Routing\Route; @@ -298,15 +308,15 @@ $router->addRoute('//', [ ]); ``` -La función `Route::FilterIn` convierte entre el parámetro en la URL y la cadena que luego se pasa al presenter, la función `FilterOut` asegura la conversión en la dirección opuesta. +La función `Route::FilterIn` convierte entre el parámetro de la URL y la cadena que se pasa después al presenter; la función `FilterOut` se encarga de la conversión en sentido contrario. -Los parámetros `presenter`, `action` y `module` ya tienen filtros predefinidos que convierten entre el estilo PascalCase resp. camelCase y kebab-case utilizado en la URL. El valor predeterminado de los parámetros se escribe ya en la forma transformada, por lo que, por ejemplo, en el caso del presenter escribimos ``, no ``. +Los parámetros `presenter`, `action` y `module` ya tienen filtros predefinidos que convierten entre el estilo PascalCase o camelCase y el kebab-case usado en la URL. El valor por defecto de los parámetros se escribe ya en la forma en la que se pasa a la aplicación (PascalCase para presenter y module, camelCase para action), así que, por ejemplo, en el caso del presenter escribimos ``, no ``. Filtros generales ----------------- -Además de los filtros destinados a parámetros específicos, también podemos definir filtros generales que reciben un array asociativo de todos los parámetros, que pueden modificar de cualquier manera y luego devolverlos. Los filtros generales los definimos bajo la clave `null`. +Además de los filtros destinados a parámetros concretos, podemos definir también filtros generales, que reciben un array asociativo de todos los parámetros, que pueden modificar de cualquier manera y devolver después. Los filtros generales se definen bajo la clave vacía. ```php use Nette\Routing\Route; @@ -321,78 +331,94 @@ $router->addRoute('/', [ ]); ``` -Los filtros generales dan la posibilidad de modificar el comportamiento de la ruta de absolutamente cualquier manera. Podemos usarlos, por ejemplo, para modificar parámetros basándose en otros parámetros. Por ejemplo, la traducción de `` y `` basada en el valor actual del parámetro ``. +Los filtros generales ofrecen la posibilidad de modificar el comportamiento de la ruta de absolutamente cualquier manera. Podemos usarlos, por ejemplo, para modificar unos parámetros a partir de otros. Por ejemplo, para traducir `` y `` según el valor actual del parámetro ``. -Si un parámetro tiene definido un filtro propio y al mismo tiempo existe un filtro general, se ejecuta el `FilterIn` propio antes del general y, a la inversa, el `FilterOut` general antes del propio. Es decir, dentro del filtro general, los valores de los parámetros `presenter` resp. `action` están escritos en estilo PascalCase resp. camelCase. +Si un parámetro tiene definido su propio filtro y existe además un filtro general, el `FilterIn` propio se ejecuta antes que el general y, al revés, el `FilterOut` general se ejecuta antes que el propio. Así, dentro del filtro general los valores de los parámetros `presenter` y `action` están escritos en estilo PascalCase o camelCase, respectivamente. +Véase [URLs amigables con slugs |best-practices:pretty-urls] para un uso práctico de estos filtros: generar URL amigables para el SEO como `/article/123-how-to-bake-bread` sin modificar ninguna plantilla. -Rutas de un solo sentido OneWay -------------------------------- -Las rutas de un solo sentido se utilizan para mantener la funcionalidad de las URL antiguas que la aplicación ya no genera, pero sigue aceptando. Las marcamos con el indicador `OneWay`: +Bandera OneWay +-------------- + +Las rutas de un solo sentido se usan para mantener la funcionalidad de URL antiguas que la aplicación ya no genera, pero que sigue aceptando. Las marcamos con la bandera `OneWay`: ```php // URL antigua /product-info?id=123 -$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// nueva URL /product/123 +$router->addRoute('product-info', 'Product:detail', oneWay: true); +// URL nueva /product/123 $router->addRoute('product/', 'Product:detail'); ``` -Al acceder a la URL antigua, el presenter redirige automáticamente a la nueva URL, por lo que los motores de búsqueda no indexarán estas páginas dos veces (ver [#SEO y canonización]). +Al acceder a la URL antigua, el presenter redirige automáticamente a la nueva, de modo que los buscadores no indexen estas páginas dos veces (véase [#SEO y canonización]). Enrutamiento dinámico con callbacks ----------------------------------- -El enrutamiento dinámico con callbacks le permite asignar directamente funciones (callbacks) a las rutas, que se ejecutarán cuando se visite la ruta dada. Esta funcionalidad flexible le permite crear rápida y eficientemente diferentes puntos finales (endpoints) para su aplicación: +El enrutamiento dinámico con callbacks le permite asignar directamente a las rutas funciones (callbacks) que se ejecutan al visitar la ruta dada. Esta funcionalidad flexible le permite crear rápida y eficazmente distintos endpoints para su aplicación: ```php $router->addRoute('test', function () { - echo 'estás en la dirección /test'; + echo 'You are at the /test address'; }); ``` -También puede definir parámetros en la máscara, que se pasarán automáticamente a su callback: +En la máscara puede definir también parámetros, que se pasan automáticamente a su callback: ```php $router->addRoute('', function (string $lang) { echo match ($lang) { - 'cs' => '¡Bienvenido a la versión checa de nuestro sitio web!', + 'cs' => 'Welcome to the Czech version of our website!', 'en' => 'Welcome to the English version of our website!', }; }); ``` +Además de los parámetros de la máscara, el callback puede recibir también servicios del contenedor DI. Se pasan según el tipo del parámetro. Además, el parámetro `$presenter` recibe una instancia de [MicroPresenter |api:NetteModule\MicroPresenter], que procesa la ruta: + +```php +$router->addRoute('', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) { + // ... +}); +``` + Módulos ------- -Si tenemos varias rutas que pertenecen a un [módulo |directory-structure#Presenters y plantillas] común, utilizamos `withModule()`: +Si tenemos varias rutas que pertenecen a un [módulo |directory-structure#Presenters y plantillas] común, usamos `withModule()`. El módulo indicado se antepone automáticamente al presenter de cada ruta del grupo y desaparece por completo de la URL: ```php $router = new RouteList; -$router->withModule('Forum') // las siguientes rutas son parte del módulo Forum +$router->withModule('Forum') // las rutas siguientes forman parte del módulo Forum ->addRoute('rss', 'Feed:rss') // el presenter será Forum:Feed ->addRoute('/') - ->withModule('Admin') // las siguientes rutas son parte del módulo Forum:Admin + ->withModule('Admin') // las rutas siguientes forman parte del módulo Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Una alternativa es usar el parámetro `module`: +Una alternativa es el parámetro `module`, que igualmente fija un módulo y lo mantiene fuera de la URL: ```php -// La URL manage/dashboard/default se mapea al presenter Admin:Dashboard +// la URL manage/dashboard/default se mapea al presenter Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` +El nombre de un presenter está completo solo junto con su módulo, p. ej. `Front:Admin:ProductList`. Siempre que ese nombre completo acaba en un parámetro de la URL, el router lo codifica con dos reglas sencillas: cada dos puntos `:` (el separador de módulos) se convierte en un **punto** y cada límite entre palabras de un nombre en PascalCase se convierte en un **guion**. Así, `Front:Admin:ProductList` se escribe en la URL como `front.admin.product-list` y de la misma manera se descodifica de vuelta. Precisamente por eso una aplicación modular sin ninguna de las herramientas anteriores genera URL llenas de puntos. + +Tanto `withModule()` como el parámetro `module` evitan esto justamente porque recortan el prefijo de módulo conocido del nombre del presenter antes de que llegue a la URL: como el módulo es una constante, no hace falta codificarlo de ninguna manera. + +A veces queremos que el módulo mismo varíe y aparezca en la URL, así que recurrimos a `` directamente en la máscara. Cuidado, sin embargo, con un detalle esencial: **`` absorbe toda la ruta de módulos**, todo hasta los últimos dos puntos del nombre del presenter. En el presenter `Shop:Admin:Product` eso es el módulo `Shop:Admin` y el presenter `Product`, y como los dos puntos se convierten en puntos, obtenemos: + Subdominios ----------- -Podemos dividir las colecciones de rutas según subdominios: +Las colecciones de rutas se pueden dividir según los subdominios: ```php $router = new RouteList; @@ -401,7 +427,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -En el nombre del dominio también se pueden usar [#Comodines]: +En el nombre del dominio se pueden usar también [#Comodines]: ```php $router = new RouteList; @@ -413,20 +439,20 @@ $router->withDomain('example.%tld%') Prefijo de ruta --------------- -Podemos dividir las colecciones de rutas según la ruta en la URL: +Las colecciones de rutas se pueden dividir según la ruta de la URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // captura la URL /eshop/rss - ->addRoute('/'); // captura la URL /eshop// + ->addRoute('rss', 'Feed:rss') // coincide con la URL /eshop/rss + ->addRoute('/'); // coincide con la URL /eshop// ``` Combinaciones ------------- -Podemos combinar las divisiones anteriores entre sí: +Las agrupaciones anteriores se pueden combinar entre sí: ```php $router = (new RouteList) @@ -449,34 +475,34 @@ $router = (new RouteList) Parámetros de consulta ---------------------- -Las máscaras también pueden contener parámetros de consulta (parámetros después del signo de interrogación en la URL). A estos no se les puede definir una expresión de validación, pero se puede cambiar el nombre bajo el cual se pasan al presenter: +Las máscaras pueden contener también parámetros de consulta (los parámetros que van tras el signo de interrogación en la URL). Para ellos no se puede definir una expresión de validación, pero sí se puede cambiar el nombre con el que se pasan al presenter: ```php -// queremos usar el parámetro de consulta 'cat' en la aplicación bajo el nombre 'categoryId' +// queremos usar el parámetro de consulta 'cat' con el nombre 'categoryId' en la aplicación $router->addRoute('product ? id= & cat=', /* ... */); ``` -Parámetros Foo +Parámetros foo -------------- -Ahora vamos más profundo. Los parámetros Foo son básicamente parámetros sin nombre que permiten hacer coincidir una expresión regular. Un ejemplo es una ruta que acepta `/index`, `/index.html`, `/index.htm` y `/index.php`: +Ahora vamos a profundizar. Los parámetros foo son, en esencia, parámetros sin nombre que permiten hacer coincidir una expresión regular. Un ejemplo es una ruta que acepta `/index`, `/index.html`, `/index.htm` e `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -También se puede definir explícitamente la cadena que se usará al generar la URL. La cadena debe colocarse directamente después del signo de interrogación. La siguiente ruta es similar a la anterior, pero genera `/index.html` en lugar de `/index`, porque la cadena `.html` está configurada como valor de generación: +También es posible definir explícitamente la cadena que se usará al generar la URL. La cadena debe colocarse justo después del signo de interrogación. La siguiente ruta es parecida a la anterior, pero genera `/index.html` en lugar de `/index`, porque la cadena `.html` está establecida como valor de generación: ```php $router->addRoute('index', /* ... */); ``` -Integración en la aplicación -============================ +Integración +=========== -Para incorporar el router creado en la aplicación, debemos decírselo al contenedor DI. La forma más fácil es preparar una fábrica que produzca el objeto router e indicar en la configuración del contenedor que debe usarla. Supongamos que para este propósito escribimos el método `App\Core\RouterFactory::createRouter()`: +Para integrar el router creado en la aplicación tenemos que informar de él al contenedor DI. La forma más sencilla es preparar una factory que cree el objeto del router e indicar al contenedor en la configuración que la use. Digamos que escribimos para ello el método `App\Core\RouterFactory::createRouter()`: ```php namespace App\Core; @@ -494,14 +520,14 @@ class RouterFactory } ``` -En la [configuración |dependency-injection:services] luego escribimos: +Después escribimos en la [configuración |dependency-injection:services]: ```neon services: - App\Core\RouterFactory::createRouter ``` -Cualquier dependencia, por ejemplo, a la base de datos, etc., se pasa al método de fábrica como sus parámetros mediante [autowiring|dependency-injection:autowiring]: +Las eventuales dependencias, por ejemplo de la base de datos, se pasan al método factory como sus parámetros mediante el [autowiring|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -514,15 +540,15 @@ public static function createRouter(Nette\Database\Connection $db): RouteList SimpleRouter ============ -Un router mucho más simple que la colección de rutas es [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Lo usamos cuando no tenemos requisitos especiales sobre la forma de la URL, si no está disponible `mod_rewrite` (o sus alternativas) o si aún no queremos ocuparnos de URL bonitas. +Un router mucho más sencillo que la colección de rutas es [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Lo usamos cuando no tenemos requisitos especiales para el formato de las URL, cuando `mod_rewrite` (o sus alternativas) no está disponible o cuando todavía no queremos ocuparnos de las URL bonitas. -Genera direcciones aproximadamente en esta forma: +Genera direcciones más o menos con esta forma: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -El parámetro del constructor de SimpleRouter es el presenter y la acción predeterminados a los que se debe dirigir si abrimos la página sin parámetros, p. ej., `http://example.com/`. +El parámetro del constructor de `SimpleRouter` es el presenter y la acción predeterminados, es decir, la acción que se ejecutará si abrimos, por ejemplo, `http://example.com/` sin parámetros adicionales. ```php // el presenter predeterminado será 'Home' y la acción 'default' @@ -540,19 +566,19 @@ services: SEO y canonización ================== -El framework contribuye al SEO (optimización para motores de búsqueda) evitando la duplicidad de contenido en diferentes URL. Si a un destino determinado conducen varias direcciones, p. ej., `/index` y `/index.html`, el framework determina la primera de ellas como primaria (canónica) y redirige las demás a ella usando el código HTTP 301. Gracias a esto, los motores de búsqueda no indexan sus páginas dos veces y no diluyen su page rank. +El framework contribuye al SEO (Search Engine Optimization) impidiendo la duplicidad de contenido en URL distintas. Si a un destino concreto llevan varias direcciones, p. ej. `/index` e `/index.html`, el framework designa la primera como principal (canónica) y redirige las demás a ella con el código HTTP 301. Gracias a eso, los buscadores no indexan las páginas dos veces ni diluyen su page rank. -Este proceso se llama canonización. La URL canónica es la que genera el router, es decir, la primera ruta que cumple en la colección sin el indicador OneWay. Por eso, en la colección indicamos **las rutas primarias primero**. +Este proceso se llama canonización. La URL canónica es la que genera el router, es decir, la primera ruta coincidente de la colección sin la bandera OneWay. Por eso indicamos en la colección **primero las rutas principales**. -La canonización la realiza el presenter, más en el capítulo [canonización |presenters#Canonización]. +De la canonización se encarga el presenter, más en el capítulo [canonización |presenters#Canonización]. HTTPS ===== -Para poder usar el protocolo HTTPS, es necesario habilitarlo en el hosting y configurar correctamente el servidor. +Para usar el protocolo HTTPS es necesario activarlo en el hosting y configurar el servidor correctamente. -La redirección de todo el sitio a HTTPS debe configurarse a nivel de servidor, por ejemplo, mediante el archivo .htaccess en el directorio raíz de nuestra aplicación, y con el código HTTP 301. La configuración puede variar según el hosting y se ve aproximadamente así: +La redirección de todo el sitio web a HTTPS hay que establecerla a nivel del servidor, por ejemplo mediante el archivo `.htaccess` en el directorio raíz de nuestra aplicación, con el código HTTP 301. La configuración puede variar según el hosting y tener más o menos este aspecto: ``` @@ -564,15 +590,15 @@ La redirección de todo el sitio a HTTPS debe configurarse a nivel de servidor, ``` -El router genera URL con el mismo protocolo con el que se cargó la página, por lo que no es necesario configurar nada más. +El router genera las URL con el mismo protocolo con el que se cargó la página, así que no hace falta configurar nada más. -Pero si excepcionalmente necesitamos que diferentes rutas se ejecuten bajo diferentes protocolos, lo indicamos en la máscara de la ruta: +Sin embargo, si excepcionalmente necesitamos que distintas rutas funcionen bajo protocolos distintos, lo indicamos en la máscara de la ruta: ```php -// Generará una dirección con HTTP +// Generará una dirección HTTP $router->addRoute('http://%host%//', /* ... */); -// Generará una dirección con HTTPs +// Generará una dirección HTTPS $router->addRoute('https://%host%//', /* ... */); ``` @@ -580,24 +606,24 @@ $router->addRoute('https://%host%//', /* ... */); Depuración del router ===================== -El panel de enrutamiento que se muestra en [Tracy Bar |tracy:] es un ayudante útil que muestra la lista de rutas y también los parámetros que el router obtuvo de la URL. +El panel de enrutamiento que se muestra en la [Tracy Bar |tracy:] es un ayudante útil que muestra la lista de rutas y también los parámetros que el router obtuvo de la URL. -La barra verde con el símbolo ✓ representa la ruta que procesó la URL actual, en color azul y con el símbolo ≈ están marcadas las rutas que también procesarían la URL si la verde no se les hubiera adelantado. Además, vemos el presenter y la acción actuales. +La barra verde con el símbolo ✓ representa la ruta que procesó la URL actual; el color azul y el símbolo ≈ señalan las rutas que también habrían procesado la URL si la verde no se les hubiera adelantado. Más abajo vemos el presenter y la acción actuales. [* routing-debugger.webp *] -Al mismo tiempo, si ocurre una redirección inesperada debido a la [canonización |#SEO y canonización], es útil mirar el panel en la barra *redirect*, donde descubrirá cómo el router entendió originalmente la URL y por qué redirigió. +Al mismo tiempo, si se produce una redirección inesperada a causa de la [canonización |#SEO y canonización], conviene mirar en el panel la barra *redirect*, donde averiguará cómo entendió el router la URL originalmente y por qué redirigió. .[note] -Al depurar el router, recomendamos abrir las Herramientas de desarrollador en el navegador (Ctrl+Shift+I o Cmd+Option+I) y en el panel Network desactivar la caché, para que no se guarden las redirecciones en ella. +Al depurar el router recomendamos abrir las herramientas para desarrolladores del navegador (Ctrl+Shift+I o Cmd+Option+I) y desactivar la caché en el panel Network, para que las redirecciones no se guarden en ella. Rendimiento =========== -El número de rutas influye en la velocidad del router. Su número definitivamente no debería exceder varias decenas. Si su sitio web tiene una estructura de URL demasiado complicada, puede escribir un [#Router propio] a medida. +El número de rutas influye en la velocidad del router. Su número no debería superar en ningún caso unas pocas decenas. Si su sitio web tiene una estructura de URL demasiado complicada, puede escribir un [#Router propio] propio. -Si el router no tiene dependencias, por ejemplo, a la base de datos, y su fábrica no acepta ningún argumento, podemos serializar su forma compilada directamente en el contenedor DI y así acelerar ligeramente la aplicación. +Si el router no tiene dependencias, por ejemplo de la base de datos, y su factory no acepta argumentos, podemos serializar su forma compilada directamente en el contenedor DI y acelerar así ligeramente la aplicación. ```neon routing: @@ -608,7 +634,7 @@ routing: Router propio ============= -Las siguientes líneas están destinadas a usuarios muy avanzados. Puede crear su propio router e integrarlo de forma completamente natural en la colección de rutas. El router es una implementación de la interfaz [api:Nette\Routing\Router] con dos métodos: +Las siguientes líneas están destinadas a usuarios muy avanzados. Puede crear su propio router e integrarlo con toda naturalidad en la colección de rutas. El router es una implementación de la interfaz [api:Nette\Routing\Router] con dos métodos: ```php use Nette\Http\IRequest as HttpRequest; @@ -628,7 +654,7 @@ class MyRouter implements Nette\Routing\Router } ``` -El método `match` procesa la petición actual [$httpRequest |http:request], de la cual se puede obtener no solo la URL, sino también las cabeceras, etc., en un array que contiene el nombre del presenter y sus parámetros. Si no puede procesar la petición, devuelve null. Al procesar la petición, debemos devolver como mínimo el presenter y la acción. El nombre del presenter es completo y contiene también posibles módulos: +El método `match` procesa la petición actual [$httpRequest |http:request], de la que se puede obtener no solo la URL sino también las cabeceras, etc., y la convierte en un array que contiene el nombre del presenter y sus parámetros. Si no puede procesar la petición, devuelve null. Al procesar la petición debemos devolver al menos el presenter; la acción es opcional y, si no se indica, es `default`. El nombre del presenter es completo e incluye los eventuales módulos: ```php [ @@ -637,9 +663,9 @@ El método `match` procesa la petición actual [$httpRequest |http:request], de ] ``` -El método `constructUrl`, por el contrario, construye la URL absoluta resultante a partir del array de parámetros. Para ello puede utilizar información del parámetro [`$refUrl`|api:Nette\Http\UrlScript], que es la URL actual. +El método `constructUrl`, por el contrario, construye la URL absoluta resultante a partir del array de parámetros. Puede usar la información del parámetro [`$refUrl`|api:Nette\Http\UrlScript], que es la URL actual. -Lo agrega a la colección de rutas usando `add()`: +Lo añadimos a la colección de rutas con `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -652,13 +678,13 @@ $router->addRoute(/* ... */); Uso independiente ================= -Por uso independiente entendemos el uso de las capacidades del router en una aplicación que no utiliza Nette Application ni presenters. Se aplica casi todo lo que hemos mostrado en este capítulo, con estas diferencias: +Por uso independiente entendemos el aprovechamiento de las capacidades del router en una aplicación que no usa Nette Application ni presenters. Vale para él casi todo lo que hemos mostrado en este capítulo, con estas diferencias: -- para colecciones de rutas usamos la clase [api:Nette\Routing\RouteList] -- como simple router la clase [api:Nette\Routing\SimpleRouter] -- como no existe el par `Presenter:action`, usamos la [#Notación extendida] +- para las colecciones de rutas usamos la clase [api:Nette\Routing\RouteList] +- como router sencillo, la clase [api:Nette\Routing\SimpleRouter] +- como no existe el par `Presenter:action`, usamos la [#Notación avanzada] -Así que nuevamente creamos un método que nos construya el router, p. ej.: +Así que creamos de nuevo un método que nos monte el router, p. ej.: ```php namespace App\Core; @@ -682,35 +708,35 @@ class RouterFactory } ``` -Si usa un contenedor DI, lo cual recomendamos, nuevamente agregamos el método a la configuración y luego obtenemos el router junto con la petición HTTP del contenedor: +Si usa un contenedor DI, cosa que recomendamos, añada de nuevo el método a la configuración y obtenga después del contenedor el router junto con la petición HTTP: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -O fabricamos los objetos directamente: +O cree los objetos directamente: ```php $router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Ahora solo queda poner el router a trabajar: +Ahora ya solo queda dejar que el router haga su trabajo: ```php $params = $router->match($httpRequest); if ($params === null) { - // no se encontró una ruta que cumpliera, enviamos error 404 + // no se encontró ninguna ruta coincidente, se envía un error 404 exit; } -// procesamos los parámetros obtenidos +// procesa los parámetros obtenidos $controller = $params['controller']; // ... ``` -Y a la inversa, usamos el router para construir un enlace: +Y al revés, use el router para construir un enlace: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; @@ -718,4 +744,6 @@ $url = $router->constructUrl($params, $httpRequest->getUrl()); ``` -{{composer: nette/router}} +{{composer: nette/routing}} +{{repo: nette/routing}} +{{api: https://api.nette.org/routing/}} diff --git a/application/es/templates.texy b/application/es/templates.texy index b9c7148897..1aa2c3ba8d 100644 --- a/application/es/templates.texy +++ b/application/es/templates.texy @@ -2,15 +2,15 @@ Plantillas ********** .[perex] -Nette utiliza el sistema de plantillas [Latte |latte:]. Por un lado, porque es el sistema de plantillas más seguro para PHP y, al mismo tiempo, el sistema más intuitivo. No necesita aprender mucho nuevo, le basta con conocer PHP y algunas etiquetas. +Nette usa el sistema de plantillas [Latte |latte:]. Se usa Latte porque es el sistema de plantillas más seguro para PHP y, a la vez, el más intuitivo. No necesita aprender mucho nuevo; bastará con saber PHP y unas cuantas etiquetas. -Es habitual que una página se componga de una plantilla de layout + la plantilla de la acción dada. Así puede verse, por ejemplo, una plantilla de layout, observe los bloques `{block}` y la etiqueta `{include}`: +Es habitual que una página se componga de una plantilla de layout más la plantilla de la acción concreta. Este podría ser el aspecto de una plantilla de layout; fíjese en los bloques `{block}` y en la etiqueta `{include}`: ```latte - {block title}Mi Aplicación{/block} + {block title}My App{/block}
    ...
    @@ -20,26 +20,26 @@ Es habitual que una página se componga de una plantilla de layout + la plantill ``` -Y esta será la plantilla de la acción: +Y esta sería la plantilla de la acción: ```latte -{block title}Página de inicio{/block} +{block title}Homepage{/block} {block content} -

    Página de inicio

    +

    Homepage

    ... {/block} ``` -Esta define el bloque `content`, que se inserta en lugar de `{include content}` en el layout, y también re-define el bloque `title`, que sobrescribe `{block title}` en el layout. Intente imaginar el resultado. +Define el bloque `content`, que se inserta en el lugar de `{include content}` del layout, y redefine además el bloque `title`, que sobrescribe el `{block title}` del layout. Trate de imaginar el resultado. Búsqueda de plantillas ---------------------- -No necesita indicar en los presenters qué plantilla se debe renderizar, el framework deduce la ruta por sí mismo y le ahorra escribir. +En los presenters no necesita indicar qué plantilla debe renderizarse; el framework deduce la ruta automáticamente y le ahorra escribirla. -Si utiliza una estructura de directorios donde cada presenter tiene su propio directorio, simplemente coloque la plantilla en este directorio bajo el nombre de la acción (resp. vista), es decir, para la acción `default` use la plantilla `default.latte`: +Si usa una estructura de directorios en la que cada presenter tiene el suyo, basta con colocar la plantilla en ese directorio con el nombre de la acción (es decir, de la vista). Por ejemplo, para la acción `default`, use la plantilla `default.latte`: /--pre app/ @@ -49,34 +49,34 @@ app/ └── default.latte \-- -Si utiliza una estructura donde los presenters están juntos en un directorio y las plantillas en la carpeta `templates`, guárdela ya sea en el archivo `..latte` o `/.latte`: +Si usa una estructura en la que los presenters están juntos en un directorio y las plantillas en una carpeta `templates`, guárdela o bien en el archivo `..latte`, o bien en `/.latte`: /--pre app/ └── Presenters/ ├── HomePresenter.php └── templates/ - ├── Home.default.latte ← 1ª variante - └── Home/ - └── default.latte ← 2ª variante + ├── Home/ + │ └── default.latte ← 1.ª variante + └── Home.default.latte ← 2.ª variante \-- -El directorio `templates` también puede estar ubicado un nivel más arriba, es decir, al mismo nivel que el directorio con las clases de los presenters. +El directorio `templates` también se puede colocar un nivel más arriba, es decir, al mismo nivel que el directorio con las clases de los presenters. -Si no se encuentra la plantilla, el presenter responde con un [error 404 - página no encontrada |presenters#Error 404 y cía]. +Si no se encuentra la plantilla, el presenter responde con un [error 404 - página no encontrada |presenters#Error 404 y otros]. -Puede cambiar la vista usando `$this->setView('otraVista')`. También puede especificar directamente el archivo de plantilla usando `$this->template->setFile('/ruta/a/plantilla.latte')`. +Puede cambiar la vista con `$this->setView('otherView')`. También es posible indicar directamente el archivo de plantilla con `$this->template->setFile('/path/to/template.latte')`. .[note] -Los archivos donde se buscan las plantillas se pueden cambiar sobrescribiendo el método [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], que devuelve un array de posibles nombres de archivo. +Los archivos en los que se buscan las plantillas se pueden cambiar sobrescribiendo el método [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], que devuelve un array con los posibles nombres de archivo. Búsqueda de la plantilla de layout ---------------------------------- -Nette también localiza automáticamente el archivo de layout. +Nette busca también automáticamente el archivo del layout. -Si utiliza una estructura de directorios donde cada presenter tiene su propio directorio, coloque el layout ya sea en la carpeta con el presenter, si es específico solo para él, o un nivel más arriba, si es común para varios presenters: +Si usa una estructura de directorios en la que cada presenter tiene el suyo, coloque el layout o bien en la carpeta del presenter, si es específico solo de él, o bien un nivel más arriba, si es común a varios presenters: /--pre app/ @@ -88,7 +88,7 @@ app/ └── default.latte \-- -Si utiliza una estructura donde los presenters están juntos en un directorio y las plantillas en la carpeta `templates`, se esperará el layout en estos lugares: +Si usa una estructura en la que los presenters están agrupados en un directorio y las plantillas en una carpeta `templates`, el layout se buscará en estos lugares: /--pre app/ @@ -96,33 +96,66 @@ app/ ├── HomePresenter.php └── templates/ ├── @layout.latte ← layout común - ├── Home.@layout.latte ← solo para Home, 1ª variante - └── Home/ - └── @layout.latte ← solo para Home, 2ª variante + ├── Home/ + │ └── @layout.latte ← solo para Home, 1.ª variante + └── Home.@layout.latte ← solo para Home, 2.ª variante \-- -Si el presenter se encuentra en un módulo, también se buscará en niveles de directorio superiores, según el anidamiento del módulo. +Si el presenter está en un módulo, la búsqueda continuará además hacia arriba por los niveles de directorio, según el anidamiento de los módulos. -El nombre del layout se puede cambiar usando `$this->setLayout('layoutAdmin')` y entonces se esperará en el archivo `@layoutAdmin.latte`. También se puede especificar directamente el archivo de plantilla de layout usando `$this->setLayout('/ruta/a/plantilla.latte')`. +El nombre del layout se puede cambiar con `$this->setLayout('layoutAdmin')`, y entonces se buscará en el archivo `@layoutAdmin.latte`. También puede indicar directamente el archivo de plantilla del layout con `$this->setLayout('/path/to/template.latte')`. -Usando `$this->setLayout(false)` o la etiqueta `{layout none}` dentro de la plantilla, se desactiva la búsqueda de layout. +Usar `$this->setLayout(false)` o la etiqueta `{layout none}` dentro de la plantilla desactiva la búsqueda del layout. .[note] -Los archivos donde se buscan las plantillas de layout se pueden cambiar sobrescribiendo el método [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], que devuelve un array de posibles nombres de archivo. +Los archivos en los que se buscan las plantillas de layout se pueden cambiar sobrescribiendo el método [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], que devuelve un array con los posibles nombres de archivo. -Variables en la plantilla +Variables de la plantilla ------------------------- -Pasamos variables a la plantilla escribiéndolas en `$this->template` y luego las tenemos disponibles en la plantilla como variables locales: +Las variables se pasan a las plantillas escribiéndolas en `$this->template`. Quedan entonces disponibles en la plantilla como variables locales: ```php $this->template->article = $this->articles->getById($id); ``` -De esta manera simple podemos pasar cualquier variable a las plantillas. Sin embargo, en el desarrollo de aplicaciones robustas, suele ser más útil limitarse. Por ejemplo, definiendo explícitamente la lista de variables que espera la plantilla y sus tipos. Gracias a esto, PHP podrá verificar los tipos, el IDE sugerir correctamente y el análisis estático detectar errores. +Para pasar automáticamente a la plantilla el valor de una propiedad como variable, márquela con el atributo `#[TemplateVariable]` y con visibilidad pública: .{data-version:3.2.9} -¿Y cómo definimos tal lista? Simplemente en forma de una clase y sus propiedades. La nombramos de manera similar al presenter, solo que con `Template` al final: +```php +use Nette\Application\Attributes\TemplateVariable; + +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + #[TemplateVariable] + public string $siteName = 'My blog'; +} +``` + +Si pasa a la plantilla una variable con el mismo nombre, `#[TemplateVariable]` no la sobrescribirá. + + +Variables predeterminadas +------------------------- + +Los presenters y los componentes pasan automáticamente a las plantillas varias variables útiles: + +- `$basePath` es la ruta URL absoluta al directorio raíz (por ejemplo, `/eshop`) +- `$baseUrl` es la URL absoluta al directorio raíz (por ejemplo, `http://localhost/eshop`) +- `$user` es un objeto que [representa al usuario |security:authentication] +- `$presenter` es el presenter actual +- `$control` es el componente o presenter actual +- `$flashes` es un array de [mensajes |presenters#Mensajes flash] enviados con la función `flashMessage()` + +Si usa una clase de plantilla propia, estas variables se pasan si crea una propiedad para ellas. + + +Plantillas con tipos seguros +---------------------------- + +Al desarrollar aplicaciones robustas resulta útil definir explícitamente qué variables espera la plantilla y de qué tipos son. Esto aporta comprobación de tipos en PHP, sugerencias inteligentes en su IDE y permite que el análisis estático detecte errores. + +¿Cómo se define esa lista? Sencillamente, como una clase con propiedades que representan las variables de la plantilla. Nómbrela como al presenter, solo que con `Template` al final: ```php /** @@ -141,22 +174,24 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -El objeto `$this->template` en el presenter será ahora una instancia de la clase `ArticleTemplate`. Así que PHP verificará los tipos declarados al escribir. Y a partir de la versión PHP 8.2 advertirá también sobre la escritura en una variable inexistente, en versiones anteriores se puede lograr lo mismo usando el trait [Nette\SmartObject |utils:smartobject]. +El objeto `$this->template` del presenter será ahora una instancia de la clase `ArticleTemplate`. PHP comprobará así los tipos declarados al escribir en él. + +Nette elige la clase de plantilla automáticamente. Primero busca una clase llamada `Template`, por ejemplo `ArticleEditTemplate` para la acción `edit`, y solo si no existe recurre a `Template`. -La anotación `@property-read` está destinada al IDE y al análisis estático, gracias a ella funcionará la sugerencia, ver [PhpStorm y autocompletado de código para $this⁠-⁠>⁠template |https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template]. +La anotación `@property-read` es para el IDE y el análisis estático, y habilita el autocompletado; vea "PhpStorm y el autocompletado de $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Puede disfrutar del lujo de la sugerencia también en las plantillas, basta con instalar el plugin para Latte en PhpStorm e indicar al principio de la plantilla el nombre de la clase, más en el artículo [Latte: cómo usar el sistema de tipos |https://blog.nette.org/es/latte-how-to-use-type-system]: +También puede usar el autocompletado directamente en las plantillas. Basta con instalar el plugin de Latte para PhpStorm e indicar al principio de la plantilla el nombre de la clase de parámetros; más información en el capítulo [Latte: sistema de tipos |latte:type-system]: ```latte {templateType App\Presentation\Article\ArticleTemplate} ... ``` -Así funcionan también las plantillas en los componentes, basta con seguir la convención de nombres y para un componente, p. ej., `FifteenControl` crear una clase de plantilla `FifteenTemplate`. +Lo mismo vale para los componentes. Basta con seguir la convención de nombres y crear una clase de parámetros `FifteenTemplate` para un componente como `FifteenControl`. -Si necesita crear `$template` como una instancia de otra clase, utilice el método `createTemplate()`: +Si necesita usar otra clase de parámetros, use el método `createTemplate()`: ```php public function renderDefault(): void @@ -168,58 +203,100 @@ public function renderDefault(): void } ``` +.{data-version:3.3.0} +Si necesita influir en cómo se completa la plantilla antes de renderizarla, por ejemplo para añadir variables compartidas por todas las acciones, puede sobrescribir el método `completeTemplate()` del presenter. Se llama justo antes de renderizar la plantilla: -Variables predeterminadas -------------------------- - -Los presenters y componentes pasan automáticamente varias variables útiles a las plantillas: - -- `$basePath` es la ruta URL absoluta al directorio raíz (p. ej., `/eshop`) -- `$baseUrl` es la URL absoluta al directorio raíz (p. ej., `http://localhost/eshop`) -- `$user` es el objeto [que representa al usuario |security:authentication] -- `$presenter` es el presenter actual -- `$control` es el componente o presenter actual -- `$flashes` array de [mensajes |presenters#Mensajes flash] enviados por la función `flashMessage()` - -Si utiliza su propia clase de plantilla, estas variables se pasarán si crea una propiedad para ellas. +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My blog'; +} +``` Creación de enlaces ------------------- -En la plantilla, se crean enlaces a otros presenters y acciones de esta manera: +En la plantilla, los enlaces a otros presenters y acciones se crean así: ```latte -detalle del producto +product detail ``` -El atributo `n:href` es muy útil para las etiquetas HTML ``. Si queremos mostrar el enlace en otro lugar, por ejemplo en el texto, usamos `{link}`: +El atributo `n:href` resulta muy práctico en las etiquetas HTML ``. Si queremos imprimir el enlace en otro sitio, por ejemplo dentro de un texto, usamos `{link}`: ```latte -La dirección es: {link Home:default} +URL is: {link Home:default} ``` Encontrará más información en el capítulo [Creación de enlaces URL|creating-links]. -Filtros personalizados, etiquetas, etc. ---------------------------------------- +Filtros, etiquetas y demás propios +---------------------------------- -El sistema de plantillas Latte se puede extender con filtros, funciones, etiquetas, etc. personalizados. Se puede hacer directamente en el método `render` o `beforeRender()`: +El sistema de plantillas Latte se puede ampliar con filtros, funciones, etiquetas y otros elementos propios. Hay tres enfoques disponibles, desde soluciones rápidas ad hoc hasta patrones arquitectónicos para aplicaciones enteras. + +**Ad hoc en métodos del presenter** + +El enfoque más rápido es añadir los filtros o funciones directamente en el código del presenter o del componente. En los presenters van bien para eso los métodos `beforeRender()` o `render()`: ```php -public function beforeRender(): void +protected function beforeRender(): void { - // agregar filtro - $this->template->addFilter('foo', /* ... */); + // añadir un filtro + $this->template->addFilter('money', fn($val) => '$' . number_format($val, 2)); + + // añadir una función + $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); +} +``` + +En la plantilla: + +```latte +

    Price: {$price|money}

    - // o configuramos directamente el objeto Latte\Engine +{if isWeekend($now)} ... {/if} +``` + +Para lógica más compleja puede configurar directamente el objeto `Latte\Engine`: + +```php +protected function beforeRender(): void +{ $latte = $this->template->getLatte(); - $latte->addFilterLoader(/* ... */); + $latte->setFeature(Latte\Feature::MigrationWarnings); } ``` -Latte en la versión 3 ofrece una forma más avanzada y es crear una [extension |latte:extending-latte#Latte Extension] para cada proyecto web. Un ejemplo fragmentario de tal clase: +**Con atributos** + +Un enfoque más elegante es definir los filtros y las funciones como métodos directamente en la [clase de parámetros de la plantilla|#Plantillas con tipos seguros] del presenter o del componente, marcados con atributos: + +```php +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + #[Latte\Attributes\TemplateFilter] + public function money(float $val): string + { + return '$' . number_format($val, 2); + } + + #[Latte\Attributes\TemplateFunction] + public function isWeekend(DateTimeInterface $date): bool + { + return $date->format('N') >= 6; + } +} +``` + +Latte descubre y registra automáticamente los métodos marcados con estos atributos. El nombre del filtro o de la función en las plantillas coincide con el del método. Estos métodos deben ser públicos. + +**De forma global, mediante extensiones** + +Los enfoques anteriores encajan con filtros y funciones que solo hacen falta en presenters o componentes concretos, no en toda la aplicación. Para la aplicación entera, lo mejor es crear una [extensión |latte:extending-latte#Latte Extension]. Esta clase centraliza todas las extensiones de Latte de su proyecto. Un ejemplo breve: ```php namespace App\Presentation\Accessory; @@ -251,11 +328,16 @@ final class LatteExtension extends Latte\Extension ]; } + private function filterTimeAgoInWords(DateTimeInterface $time): string + { + // ... + } + // ... } ``` -La registramos usando la [configuración |configuration#Plantillas Latte]: +Registre la extensión mediante la [configuración |configuration#Plantillas Latte]: ```neon latte: @@ -263,13 +345,27 @@ latte: - App\Presentation\Accessory\LatteExtension ``` +Las extensiones ofrecen varias ventajas: soporte de inyección de dependencias, acceso a la capa de modelo de su aplicación y gestión centralizada de todas las extensiones. También admiten etiquetas propias, proveedores, pases del compilador y más. + + +Configurar todas las plantillas +------------------------------- + +El servicio `TemplateFactory`, que crea todas las plantillas, ofrece un array público de callbacks `$onCreate`. Se llaman cada vez que se crea cualquier plantilla, así que puede configurar filtros, funciones o variables para todas las plantillas de la aplicación desde un solo sitio. Cada callback recibe la plantilla recién creada. Hágase [inyectar |dependency-injection:passing-dependencies] el servicio `TemplateFactory` y registre los callbacks, por ejemplo durante el arranque de la aplicación: + +```php +$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void { + $template->addFilter('money', fn($val) => '$' . number_format($val, 2)); +}; +``` + Traducción ---------- -Si programa una aplicación multilingüe, probablemente necesitará mostrar algunos textos en la plantilla en diferentes idiomas. Nette Framework define para este propósito una interfaz para la traducción [api:Nette\Localization\Translator], que tiene un único método `translate()`. Este recibe el mensaje `$message`, que generalmente suele ser una cadena, y cualquier otro parámetro. La tarea es devolver la cadena traducida. En Nette no hay ninguna implementación predeterminada, puede elegir según sus necesidades entre varias soluciones listas que encontrará en [Componette |https://componette.org/search/localization]. En su documentación aprenderá cómo configurar el traductor. +Si programa una aplicación multilingüe, probablemente necesitará imprimir algunos textos de la plantilla en distintos idiomas. Nette Framework define para ello la interfaz de traducción [api:Nette\Localization\Translator], que tiene un único método, `translate()`. Este acepta el mensaje `$message`, que suele ser una cadena, y cualesquiera otros parámetros. Su tarea es devolver la cadena traducida. Nette no trae ninguna implementación predeterminada; puede elegir entre varias soluciones ya hechas disponibles en [Componette |https://componette.org/search/localization], según sus necesidades. Su documentación explica cómo configurar el traductor. -A las plantillas se les puede establecer un traductor, que [pasamos |dependency-injection:passing-dependencies], con el método `setTranslator()`: +Las plantillas se pueden configurar con un traductor que nos [hacemos pasar |dependency-injection:passing-dependencies], mediante el método `setTranslator()`: ```php protected function beforeRender(): void @@ -279,7 +375,7 @@ protected function beforeRender(): void } ``` -El traductor alternativamente se puede establecer mediante la [configuración |configuration#Plantillas Latte]: +Como alternativa, el traductor se puede fijar mediante la [configuración |configuration#Plantillas Latte]: ```neon latte: @@ -287,10 +383,10 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Luego, el traductor se puede usar, por ejemplo, como filtro `|translate`, incluyendo parámetros complementarios que se pasan al método `translate()` (ver `foo, bar`): +Después, el traductor se puede usar, por ejemplo, como filtro `|translate`, incluidos los parámetros adicionales que se pasan al método `translate()` (véase `foo, bar`): ```latte -
    {='Carrito'|translate} +{='Basket'|translate} {$item|translate} {$item|translate, foo, bar} ``` @@ -298,19 +394,19 @@ Luego, el traductor se puede usar, por ejemplo, como filtro `|translate`, incluy O como etiqueta de guion bajo: ```latte -{_'Carrito'} +{_'Basket'} {_$item} {_$item, foo, bar} ``` -Para traducir una sección de la plantilla, existe una etiqueta par `{translate}` (desde Latte 2.11, antes se usaba la etiqueta `{_}`): +Para traducir una sección de la plantilla existe la etiqueta par `{translate}` (desde Latte 2.11; antes se usaba la etiqueta `{_}`): ```latte -{translate}Pedido{/translate} -{translate foo, bar}Pedido{/translate} +{translate}Order{/translate} +{translate foo, bar}Order{/translate} ``` -El traductor se llama estándarmente en tiempo de ejecución al renderizar la plantilla. Sin embargo, Latte versión 3 puede traducir todos los textos estáticos ya durante la compilación de la plantilla. Esto ahorra rendimiento, porque cada cadena se traduce solo una vez y la traducción resultante se escribe en la forma compilada. En el directorio de caché se crean así múltiples versiones compiladas de la plantilla, una para cada idioma. Para ello basta con indicar el idioma como segundo parámetro: +El traductor se llama normalmente en tiempo de ejecución, al renderizar la plantilla. La versión 3 de Latte, sin embargo, sabe traducir todos los textos estáticos ya durante la compilación de la plantilla. Esto ahorra rendimiento, porque cada cadena se traduce una sola vez y la traducción resultante se escribe en la forma compilada. Así se crean varias versiones compiladas de la plantilla en el directorio de caché, una por idioma. Para ello basta con indicar el idioma como segundo parámetro: ```php protected function beforeRender(): void @@ -320,4 +416,4 @@ protected function beforeRender(): void } ``` -Por texto estático se entiende, por ejemplo, `{_'hello'}` o `{translate}hello{/translate}`. Los textos no estáticos, como por ejemplo `{_$foo}`, seguirán traduciéndose en tiempo de ejecución. +Por texto estático se entiende, por ejemplo, `{_'hello'}` o `{translate}hello{/translate}`. Los textos no estáticos, como `{_$foo}`, se seguirán traduciendo en tiempo de ejecución. diff --git a/application/es/upgrading.texy b/application/es/upgrading.texy new file mode 100644 index 0000000000..405d660c16 --- /dev/null +++ b/application/es/upgrading.texy @@ -0,0 +1,47 @@ +Actualización +************* + + +Actualización a la versión 3.0 +============================== + +Nette 3.0 añade declaraciones de tipo a los parámetros y a los valores de retorno de los métodos. Si sobrescribe uno de esos métodos en una clase que hereda de Nette (por ejemplo, en un presenter o en un componente), debe añadir las mismas declaraciones de tipo, o PHP lanzará el error "Declaration must be compatible". + +La interfaz `Nette\Application\IRouter` ha cambiado. El método `match()` devuelve ahora, y `constructUrl()` acepta, un array de parámetros en lugar de un objeto `Nette\Application\Request`. + +Nette comprueba ahora que cada señal se envía desde el mismo origen (es decir, el mismo dominio y subdominio). Esta política del mismo origen es un mecanismo de seguridad crítico que ayuda a reducir los posibles vectores de ataque. Si quiere permitir otros orígenes, añada al método manejador la anotación `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXy(): void +{ +} +``` + +Esto vale también para el envío de formularios. Si quiere permitir el envío desde otros orígenes, hágalo así: + +```php +$form = new Nette\Application\UI\Form; +$form->allowCrossOrigin(); +``` + +El constructor de `Nette\ComponentModel\Component` no se usaba desde hacía años y se eliminó en la versión 3.0. Es una ruptura de compatibilidad: si llama al constructor padre en un componente o presenter que hereda de `Nette\Application\UI\Presenter`, debe eliminar esa llamada. + + +Actualización a la versión 2.4 +============================== + +- `Route` y `SimpleRouter` generan ahora el mismo esquema HTTP/HTTPS con el que se accedió al sitio. Una ruta que exija un protocolo concreto se puede definir con el esquema, por ejemplo `Route('http://domain.cz/')`. +- En los parámetros de tipo bool de los métodos render/action (es decir, con valor predeterminado true o false) y en los parámetros persistentes se distingue ahora entre `false` y `null`. Si el parámetro no está presente en la URL, su valor es ahora `null` (antes era `false`). +- La clase devuelta por `Presenter::getReflection()` ya no es descendiente de `Nette\Reflection\ClassType`, y `getReflection()->getMethod()` ya no es descendiente de `Nette\Reflection\Method`. +- La bandera `SECURED` y `Route::$defaultFlags` están obsoletas. + + +Actualización a la versión 2.3 +============================== + +- las rutas y los nombres de los presenters **distinguen mayúsculas y minúsculas**. Nette le avisa si usa mayúsculas o minúsculas incorrectas en el nombre de un presenter; por motivos de rendimiento no se comprueba la máscara de Route, así que verifíquela usted mismo. +- `Route::addStyle()` y `Route::setStyleProperty()` están obsoletos y ahora emiten `E_USER_DEPRECATED`. +- la extensión de plantilla `.phtml` y la sintaxis antigua de los enlaces ya no están admitidas. diff --git a/application/fr/@home.texy b/application/fr/@home.texy index 2548bf3db7..b35a1bddef 100644 --- a/application/fr/@home.texy +++ b/application/fr/@home.texy @@ -74,12 +74,12 @@ Pour commencer Compatibilité avec PHP ---------------------- -| version | compatible avec PHP -|-----------|------------------- -| Nette Application 4.0 | PHP 8.1 – 8.4 -| Nette Application 3.2 | PHP 8.1 – 8.4 -| Nette Application 3.1 | PHP 7.2 – 8.3 -| Nette Application 3.0 | PHP 7.1 – 8.0 -| Nette Application 2.4 | PHP 5.6 – 8.0 +| version | compatible avec PHP +|-----------------------|------------------- +| Nette Application 3.3 | PHP 8.3 - 8.5 +| Nette Application 3.2 | PHP 8.1 - 8.5 +| Nette Application 3.1 | PHP 7.2 - 8.3 +| Nette Application 3.0 | PHP 7.1 - 8.0 +| Nette Application 2.4 | PHP 5.6 - 8.0 S'applique à la dernière version patch. diff --git a/application/fr/@left-menu.texy b/application/fr/@left-menu.texy index 3d59487c72..650fd56ae6 100644 --- a/application/fr/@left-menu.texy +++ b/application/fr/@left-menu.texy @@ -1,22 +1,24 @@ Nette Application ***************** +- [Introduction |@home] - [Comment fonctionnent les applications ? |how-it-works] -- [Bootstrapping] -- [Presenters |presenters] -- [Templates |templates] +- [Bootstrapping|bootstrapping] +- [Presenters|presenters] +- [Templates|templates] - [Structure des répertoires |directory-structure] -- [Routage |routing] +- [Routage|routing] - [Création de liens URL |creating-links] - [Composants interactifs |components] - [AJAX & snippets |ajax] - [Multiplier |multiplier] -- [Configuration |configuration] +- [Configuration|configuration] +- [Mise à niveau|upgrading] -Lectures complémentaires -************************ -- [Pourquoi utiliser Nette ? |www:10-reasons-why-nette] +Pour aller plus loin +******************** +- [Pourquoi utiliser Nette ?|www:10-reasons-why-nette] - [Installation |nette:installation] -- [Écrivons notre première application ! |quickstart:] -- [Tutoriels et bonnes pratiques |best-practices:] +- [Créez votre première application ! |quickstart:] +- [Bonnes pratiques |best-practices:] - [Résolution de problèmes |nette:troubleshooting] diff --git a/application/fr/ajax.texy b/application/fr/ajax.texy index 40956d685b..7abb4466fa 100644 --- a/application/fr/ajax.texy +++ b/application/fr/ajax.texy @@ -59,7 +59,7 @@ Le moyen le plus puissant offert par Nette pour connecter le serveur et le clien Les snippets, ou extraits, permettent de mettre à jour uniquement des parties de la page, au lieu de recharger la page entière. C'est non seulement plus rapide et plus efficace, mais cela offre également une expérience utilisateur plus confortable. Les snippets peuvent vous rappeler Hotwire pour Ruby on Rails ou Symfony UX Turbo. Il est intéressant de noter que Nette a introduit les snippets 14 ans plus tôt. -Comment fonctionnent les snippets ? Lors du premier chargement de la page (requête non-AJAX), la page entière est chargée, y compris tous les snippets. Lorsque l'utilisateur interagit avec la page (par exemple, clique sur un bouton, soumet un formulaire, etc.), une requête AJAX est déclenchée au lieu de charger la page entière. Le code dans le presenter exécute l'action et décide quels snippets doivent être mis à jour. Nette rend ces snippets et les envoie sous forme de tableau au format JSON. Le code de gestion dans le navigateur réinsère les snippets reçus dans la page. Ainsi, seul le code des snippets modifiés est transféré, ce qui économise de la bande passante et accélère le chargement par rapport au transfert du contenu de la page entière. +Comment fonctionnent les snippets ? Lors du premier chargement de la page (requête non-AJAX), la page entière est chargée, y compris tous les snippets. Lorsque l'utilisateur interagit avec la page (par exemple, clique sur un bouton, soumet un formulaire, etc.), une requête AJAX est déclenchée au lieu de charger la page entière. Le code dans le presenter exécute l'action et décide quels snippets doivent être mis à jour. Nette rend ces snippets et les envoie dans une charge utile JSON contenant un tableau de snippets. Le code de gestion dans le navigateur réinsère les snippets reçus dans la page. Ainsi, seul le code des snippets modifiés est transféré, ce qui économise de la bande passante et accélère le chargement par rapport au transfert du contenu de la page entière. Si aucun snippet n'est invalidé par `redrawControl()`, Nette renvoie la page entière même pour une requête AJAX : les snippets ne sont envoyés que lorsque quelque chose est invalidé. Naja @@ -121,6 +121,8 @@ Nette permet un contrôle encore plus fin de ce qui doit être redessiné. En ef $this->redrawControl('header'); ``` +Vous pouvez aussi annuler une invalidation en attente à l'aide du second paramètre `$redraw` : l'appel `$this->redrawControl('header', redraw: false)` marque le snippet comme n'ayant pas besoin d'être redessiné. La signature complète est `redrawControl(?string $snippet = null, bool $redraw = true)`. + Snippets dans Latte ------------------- @@ -155,6 +157,8 @@ Les noms des snippets peuvent aussi être des expressions : {/foreach} ``` +Pris isolément, c'est une étape intermédiaire non fonctionnelle : rendu en dehors d'un `{snippet}` ou `{snippetArea}` statique, un snippet dynamique déclenche un `E_USER_WARNING` avec le message *Dynamic snippets are allowed only inside static snippet/snippetArea.* Nous corrigeons cela ci-dessous. + Cela crée plusieurs snippets `item-0`, `item-1`, etc. Si nous invalidions directement un snippet dynamique (par exemple `item-1`), rien ne serait redessiné. La raison est que les snippets fonctionnent vraiment comme des extraits et ne sont rendus qu'eux-mêmes directement. Cependant, il n'y a en fait aucun snippet nommé `item-1` dans le template. Il n'est créé que lors de l'exécution du code autour du snippet, c'est-à-dire la boucle foreach. Nous marquons donc la partie du template qui doit être exécutée à l'aide de la balise `{snippetArea}` : ```latte @@ -226,6 +230,12 @@ public function actionDelete(int $id): void ``` +Redirection +----------- + +Pendant une requête AJAX, les méthodes `redirect()` et `redirectUrl()` n'envoient pas de redirection HTTP. Elles écrivent l'URL cible dans le payload (l'objet de données envoyé dans la réponse AJAX), plus précisément dans sa propriété `payload.redirect`, et l'envoient ; la redirection elle-même est ensuite effectuée par la bibliothèque côté client (Naja). + + Transmission de Paramètres ========================== @@ -247,3 +257,9 @@ public function handleFoo(int $bar): void { } ``` + + +Lectures complémentaires +======================== + +- [Snippets dynamiques |best-practices:dynamic-snippets] diff --git a/application/fr/bootstrapping.texy b/application/fr/bootstrapping.texy index eb39389dad..d1c2d317b9 100644 --- a/application/fr/bootstrapping.texy +++ b/application/fr/bootstrapping.texy @@ -16,6 +16,9 @@ Le bootstrapping est le processus d'initialisation de l'environnement de l'appli Les applications, qu'elles soient web ou des scripts exécutés depuis la ligne de commande, commencent leur exécution par une forme d'initialisation de l'environnement. Autrefois, un fichier nommé par exemple `include.inc.php` s'en chargeait, inclus par le fichier initial. Dans les applications Nette modernes, il a été remplacé par la classe `Bootstrap`, que vous trouverez dans le fichier `app/Bootstrap.php` en tant que partie de l'application. Elle peut ressembler à ceci, par exemple : ```php +namespace App; + +use Nette; use Nette\Bootstrap\Configurator; class Bootstrap @@ -66,7 +69,7 @@ class Bootstrap index.php ========= -Le fichier initial pour les applications web est `index.php`, situé dans le [répertoire public |directory-structure#Répertoire public www] `www/`. Il demande à la classe Bootstrap d'initialiser l'environnement et de créer le conteneur DI. Ensuite, il obtient le service `Application` à partir de celui-ci, qui lance l'application web : +Le fichier initial pour les applications web est `index.php`, situé dans le [répertoire public |directory-structure#Répertoire public www/] `www/`. Il demande à la classe Bootstrap d'initialiser l'environnement et de créer le conteneur DI. Ensuite, il obtient le service `Application` à partir de celui-ci, qui lance l'application web : ```php $bootstrap = new App\Bootstrap; @@ -78,6 +81,9 @@ $application = $container->getByType(Nette\Application\Application::class); $application->run(); ``` +.[note] +L'objet `$application` émet des [événements |nette:glossary#Événements] au fil du traitement de la requête : `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown` et `onError` (sur une exception non traitée). Vous pouvez y attacher des gestionnaires, ce qui est pratique pour la journalisation ou la surveillance à l'échelle de l'application. + Comme vous pouvez le voir, la classe [api:Nette\Bootstrap\Configurator] aide à configurer l'environnement et à créer le conteneur d'injection de dépendances (DI), que nous allons maintenant présenter plus en détail. @@ -124,6 +130,12 @@ $this->configurator->setDebugMode(false); Attention, la valeur `true` active le mode développement de manière forcée, ce qui ne doit jamais se produire sur un serveur de production. +En interne, la détection automatique est assurée par la méthode statique `Configurator::detectDebugMode()`, que vous pouvez aussi appeler vous-même, par exemple pour détecter le mode développement en dehors du configurateur. Elle accepte une liste blanche facultative d'adresses IP ou de noms de machines et retourne si la requête courante doit s'exécuter en mode développement : + +```php +$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200'); +``` + Outil de Débogage Tracy ======================= @@ -144,7 +156,7 @@ Nette utilise un cache pour le conteneur DI, RobotLoader, les templates, etc. Il $this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` -Sous Linux ou macOS, définissez les [permissions d'écriture |nette:troubleshooting#Configuration des permissions de répertoire] pour les répertoires `log/` et `temp/`. +Sous Linux ou macOS, définissez les [permissions d'écriture |nette:troubleshooting#Régler les permissions des répertoires] pour les répertoires `log/` et `temp/`. RobotLoader @@ -181,7 +193,9 @@ Les fichiers de configuration sont généralement écrits au format [NEON |neon: .[tip] En mode développement, le conteneur est automatiquement mis à jour à chaque modification du code ou des fichiers de configuration. En mode production, il n'est généré qu'une seule fois et les modifications ne sont pas vérifiées pour maximiser les performances. -Nous chargeons les fichiers de configuration à l'aide de `addConfig()` : +Là où `createContainer()` construit le conteneur et retourne son instance, la méthode `loadContainer()` ne retourne que le nom de la classe du conteneur généré, que vous pouvez ensuite instancier vous-même. C'est utile dans les scénarios avancés. + +Nous chargeons les fichiers de configuration à l'aide de `addConfig()`: ```php $this->configurator->addConfig($this->rootDir . '/config/common.neon'); @@ -200,7 +214,7 @@ if (PHP_SAPI === 'cli') { Le nom `cli.php` n'est pas une faute de frappe ; la configuration peut également être écrite dans un fichier PHP qui la retourne sous forme de tableau. -Nous pouvons également ajouter d'autres fichiers de configuration dans la [section `includes` |dependency-injection:configuration#Inclusion de fichiers]. +Nous pouvons également ajouter d'autres fichiers de configuration dans la [section `includes` |dependency-injection:configuration#Inclure des fichiers]. Si des éléments avec les mêmes clés apparaissent dans les fichiers de configuration, ils seront écrasés ou, dans le cas des [tableaux, fusionnés |dependency-injection:configuration#Fusion]. Le fichier inclus ultérieurement a une priorité plus élevée que le précédent. Le fichier dans lequel la section `includes` est listée a une priorité plus élevée que les fichiers qui y sont inclus. @@ -249,6 +263,7 @@ Dans les fichiers de configuration, vous pouvez utiliser ces paramètres statiqu - `%tempDir%` est le chemin absolu vers le répertoire des fichiers temporaires - `%vendorDir%` est le chemin absolu vers le répertoire où Composer installe les bibliothèques - `%rootDir%` est le chemin absolu vers le répertoire racine du projet +- `%baseUrl%` est l'URL absolue vers le répertoire racine (paramètre dynamique résolu à l'exécution) - `%debugMode%` indique si l'application est en mode débogage - `%consoleMode%` indique si la requête provient de la ligne de commande @@ -277,7 +292,7 @@ $this->configurator->addServices([ Environnements Différents ========================= -N'hésitez pas à modifier la classe Bootstrap selon vos besoins. Vous pouvez ajouter des paramètres à la méthode `bootWebApplication()` pour distinguer les projets web. Ou nous pouvons ajouter d'autres méthodes, par exemple `bootTestEnvironment()`, qui initialise l'environnement pour les tests unitaires, `bootConsoleApplication()` pour les scripts appelés depuis la ligne de commande, etc. +N'hésitez pas à modifier la classe `Bootstrap` selon vos besoins. Vous pouvez ajouter des paramètres à la méthode `bootWebApplication()` pour distinguer les projets web. Ou nous pouvons ajouter d'autres méthodes, par exemple `bootTestEnvironment()`, qui initialise l'environnement pour les tests unitaires, `bootConsoleApplication()` pour les scripts appelés depuis la ligne de commande, etc. ```php public function bootTestEnvironment(): Nette\DI\Container diff --git a/application/fr/components.texy b/application/fr/components.texy index 4392cdea5b..561d497835 100644 --- a/application/fr/components.texy +++ b/application/fr/components.texy @@ -59,6 +59,11 @@ Dans le template, il est possible de rendre un composant à l'aide de la balise {control poll} ``` +.[tip] +Pour créer dynamiquement un nombre variable de composants, utilisez [Multiplier |multiplier]. + +Les méthodes fabriques `createComponent()` ne fonctionnent pas qu'au sein des presenters. Vous pouvez imbriquer un composant dans un autre exactement de la même façon et les composer en arbre, ce qui est pratique par exemple pour un formulaire rendu séparément à l'intérieur d'un composant. + Style Hollywood =============== @@ -212,9 +217,9 @@ Les signaux vous rappellent peut-être un peu AJAX : des gestionnaires qui sont Messages Flash ============== -Un composant possède son propre stockage de messages flash indépendant du presenter. Ce sont des messages qui informent par exemple du résultat d'une opération. Une caractéristique importante des messages flash est qu'ils sont disponibles dans le template même après une redirection. Même après affichage, ils restent actifs pendant 30 secondes supplémentaires – par exemple, au cas où l'utilisateur rafraîchirait la page en raison d'une erreur de transmission - le message ne disparaîtra donc pas immédiatement. +Un composant possède son propre stockage de messages flash indépendant du presenter. Ce sont des messages qui informent par exemple du résultat d'une opération. Une caractéristique importante des messages flash est qu'ils sont disponibles dans le template même après une redirection. Même après affichage, ils restent actifs pendant 30 secondes supplémentaires - par exemple, au cas où l'utilisateur rafraîchirait la page en raison d'une erreur de transmission - le message ne disparaîtra donc pas immédiatement. -L'envoi est géré par la méthode [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Le premier paramètre est le texte du message ou un objet `stdClass` représentant le message. Le deuxième paramètre facultatif est son type (erreur, avertissement, info, etc.). La méthode `flashMessage()` retourne une instance du message flash sous forme d'objet `stdClass`, auquel des informations supplémentaires peuvent être ajoutées. +L'envoi est géré par la méthode [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Le premier paramètre est le texte du message (`string`, `Stringable`) ou un objet `stdClass` représentant le message. Le deuxième paramètre facultatif est son type (erreur, avertissement, info, etc.). La méthode `flashMessage()` retourne une instance du message flash sous forme d'objet `stdClass`, auquel des informations supplémentaires peuvent être ajoutées. ```php $this->flashMessage('L\'élément a été supprimé.'); @@ -289,25 +294,25 @@ Ou il peut être *réinitialisé*, c'est-à-dire supprimé de l'URL. Il prendra Composants persistants ====================== -Non seulement les paramètres, mais aussi les composants peuvent être persistants. Pour un tel composant, ses paramètres persistants sont également transmis entre différentes actions du presenter ou entre plusieurs presenters. Nous marquons les composants persistants avec une annotation dans la classe du presenter. Par exemple, nous marquons ainsi les composants `calendar` et `poll` : +Non seulement les paramètres, mais aussi les composants peuvent être persistants. Pour un tel composant, ses paramètres persistants sont également transmis entre différentes actions du presenter ou entre plusieurs presenters. Nous marquons les composants persistants par un attribut sur la classe du presenter. Par exemple, nous marquons ainsi les composants `calendar` et `poll` : ```php -/** - * @persistent(calendar, poll) - */ +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] class DefaultPresenter extends Nette\Application\UI\Presenter { } ``` -Il n'est pas nécessaire de marquer les sous-composants à l'intérieur de ces composants ; ils deviendront également persistants. +Il n'est pas nécessaire de marquer les sous-composants à l'intérieur de ces composants ; ils deviennent persistants eux aussi. -En PHP 8, vous pouvez également utiliser des attributs pour marquer les composants persistants : +L'ancienne annotation `@persistent` fonctionne encore, mais elle est dépréciée et déclenche un avertissement : ```php -use Nette\Application\Attributes\Persistent; - -#[Persistent('calendar', 'poll')] +/** + * @persistent(calendar, poll) + */ class DefaultPresenter extends Nette\Application\UI\Presenter { } @@ -338,7 +343,7 @@ class PollControl extends Control } ``` -Si nous écrivions un service classique, il n'y aurait rien à faire. Le conteneur DI se chargerait invisiblement de transmettre toutes les dépendances. Mais avec les composants, nous les traitons généralement en créant leur nouvelle instance directement dans le presenter dans les [#méthodes factory] `createComponent…()`. Mais transmettre toutes les dépendances de tous les composants au presenter pour ensuite les transmettre aux composants est lourd. Et tout ce code écrit… +Si nous écrivions un service classique, il n'y aurait rien à faire. Le conteneur DI se chargerait invisiblement de transmettre toutes les dépendances. Mais avec les composants, nous les traitons généralement en créant leur nouvelle instance directement dans le presenter dans les [#Méthodes Factory] `createComponent…()`. Mais transmettre toutes les dépendances de tous les composants au presenter pour ensuite les transmettre aux composants est lourd. Et tout ce code écrit… La question logique est, pourquoi ne pas simplement enregistrer le composant comme un service classique, le passer au presenter et ensuite le retourner dans la méthode `createComponent…()` ? Une telle approche est cependant inappropriée, car nous voulons pouvoir créer le composant plusieurs fois si nécessaire. @@ -431,7 +436,7 @@ Cycle de vie du composant Validation des paramètres persistants ------------------------------------- -Les valeurs des [#paramètres persistants] reçues de l'URL sont écrites dans les propriétés par la méthode `loadState()`. Celle-ci vérifie également si le type de données indiqué pour la propriété correspond, sinon elle répond par une erreur 404 et la page ne s'affiche pas. +Les valeurs des [#Paramètres persistants] reçues de l'URL sont écrites dans les propriétés par la méthode `loadState()`. Celle-ci vérifie également si le type de données indiqué pour la propriété correspond, sinon elle répond par une erreur 404 et la page ne s'affiche pas. Ne faites jamais confiance aveuglément aux paramètres persistants, car ils peuvent être facilement modifiés par l'utilisateur dans l'URL. Voici comment nous vérifions, par exemple, si le numéro de page `$this->page` est supérieur à 0. Une bonne approche consiste à redéfinir la méthode `loadState()` mentionnée : @@ -455,6 +460,18 @@ class PaginatingControl extends Control Le processus inverse, c'est-à-dire la collecte des valeurs des propriétés persistantes, est géré par la méthode `saveState()`. +Raccordement au presenter +------------------------- + +Au moment où un composant devient partie de la hiérarchie du presenter, les callbacks stockés dans son tableau `$onAnchor` sont invoqués. À partir de là, le composant a le presenter à disposition, peut créer des liens en toute sécurité, lire les paramètres persistants, etc. + +```php +$control->onAnchor[] = function ($control): void { + // le composant a maintenant le presenter à disposition +}; +``` + + Signaux en profondeur --------------------- @@ -462,17 +479,19 @@ Un signal provoque un rechargement de la page exactement comme lors de la requê En d'autres termes : la définition de la fonction `handle{signal}` est prise, ainsi que tous les paramètres qui sont arrivés avec la requête, et les paramètres de l'URL sont substitués aux arguments par nom, puis on tente d'appeler la méthode donnée. Par exemple, la valeur du paramètre `id` dans l'URL est passée comme paramètre `$id`, `something` de l'URL est passé comme `$something`, etc. Et si la méthode n'existe pas, la méthode `signalReceived` lève une [exception |api:Nette\Application\UI\BadSignalException]. +Outre les paramètres de l'URL, un signal lit aussi les paramètres envoyés dans le **corps POST de la requête**. C'est bien pratique, car les signaux sont souvent invoqués depuis JavaScript, où il est naturel d'envoyer les données par la méthode POST. Si toutefois un paramètre du même nom arrive à la fois de l'URL et du corps POST, c'est la valeur **de l'URL qui l'emporte**. Évitez donc de donner à un champ POST le même nom qu'à un paramètre d'URL ou de route, sans quoi la valeur de l'URL l'écraserait en silence. Les paramètres des signaux partagent un espace commun avec les paramètres d'action et les paramètres persistants, voyez [Espace de paramètres partagé |presenters#Espace de paramètres partagé]. + Un signal peut être reçu par n'importe quel composant, presenter ou objet qui implémente l'interface `SignalReceiver` et est connecté à l'arbre des composants. -Les principaux destinataires des signaux seront les `Presenters` et les composants visuels héritant de `Control`. Un signal doit servir de signe à un objet qu'il doit faire quelque chose – un sondage doit compter un vote d'un utilisateur, un bloc d'actualités doit se déplier et afficher deux fois plus d'actualités, un formulaire a été soumis et doit traiter les données, etc. +Les principaux destinataires des signaux seront les `Presenters` et les composants visuels héritant de `Control`. Un signal doit servir de signe à un objet qu'il doit faire quelque chose - un sondage doit compter un vote d'un utilisateur, un bloc d'actualités doit se déplier et afficher deux fois plus d'actualités, un formulaire a été soumis et doit traiter les données, etc. Nous créons l'URL pour un signal à l'aide de la méthode [Component::link() |api:Nette\Application\UI\Component::link()]. Comme paramètre `$destination`, nous passons la chaîne `{signal}!` et comme `$args`, un tableau d'arguments que nous voulons passer au signal. Le signal est toujours appelé sur le presenter et l'action actuels avec les paramètres actuels ; les paramètres du signal sont simplement ajoutés. De plus, le **paramètre `?do`, qui spécifie le signal**, est ajouté au début. -Son format est soit `{signal}`, soit `{signalReceiver}-{signal}`. `{signalReceiver}` est le nom du composant dans le presenter. C'est pourquoi un trait d'union ne peut pas être utilisé dans le nom d'un composant – il est utilisé pour séparer le nom du composant et le signal, mais il est possible d'imbriquer plusieurs composants de cette manière. +Son format est soit `{signal}`, soit `{signalReceiver}-{signal}`. `{signalReceiver}` est le nom du composant dans le presenter. C'est pourquoi un trait d'union ne peut pas être utilisé dans le nom d'un composant - il est utilisé pour séparer le nom du composant et le signal, mais il est possible d'imbriquer plusieurs composants de cette manière. -La méthode [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] vérifie si le composant (premier argument) est le destinataire du signal (deuxième argument). Nous pouvons omettre le deuxième argument – il vérifie alors si le composant est le destinataire de n'importe quel signal. On peut passer `true` comme deuxième paramètre pour vérifier si non seulement le composant spécifié est le destinataire, mais aussi n'importe lequel de ses descendants. +La méthode [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] vérifie si le composant (premier argument) est le destinataire du signal (deuxième argument). Nous pouvons omettre le deuxième argument - il vérifie alors si le composant est le destinataire de n'importe quel signal. On peut passer `true` comme deuxième paramètre pour vérifier si non seulement le composant spécifié est le destinataire, mais aussi n'importe lequel de ses descendants. -À n'importe quelle étape précédant `handle{signal}`, nous pouvons exécuter le signal manuellement en appelant la méthode [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], qui se charge de traiter le signal – elle prend le composant désigné comme destinataire du signal (s'il n'y a pas de destinataire spécifié, c'est le presenter lui-même) et lui envoie le signal. +À n'importe quelle étape précédant `handle{signal}`, nous pouvons exécuter le signal manuellement en appelant la méthode [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], qui se charge de traiter le signal - elle prend le composant désigné comme destinataire du signal (s'il n'y a pas de destinataire spécifié, c'est le presenter lui-même) et lui envoie le signal. Exemple : diff --git a/application/fr/configuration.texy b/application/fr/configuration.texy index aedc9c6739..ec195628c6 100644 --- a/application/fr/configuration.texy +++ b/application/fr/configuration.texy @@ -40,6 +40,8 @@ application: 5xx: Error5xx # pour les autres exceptions ``` +Les séparer est utile, car les deux situations sont fondamentalement différentes. Une `BadRequestException` (codes 4xx) signifie que l'application va bien et que le visiteur a simplement demandé quelque chose qui n'existe pas. Vous pouvez donc y employer un presenter complet, qui affiche un message avenant dans le layout de votre site. À l'inverse, une erreur 5xx signifie que quelque chose s'est cassé dans l'application, sans qu'on sache quoi. Gardez le presenter 5xx aussi minimal que possible, pour que rien d'autre ne puisse échouer pendant son rendu : idéalement, il ne devrait toucher ni à la base de données, ni au layout, ni à l'utilisateur connecté. + L'option `silentLinks` détermine comment Nette se comporte en mode développement lorsque la génération d'un lien échoue (par exemple, parce que le presenter n'existe pas, etc.). La valeur par défaut `false` signifie que Nette lèvera une erreur `E_USER_WARNING`. La définir sur `true` supprimera ce message d'erreur. En environnement de production, `E_USER_WARNING` est toujours levé. Ce comportement peut également être influencé en définissant la variable du presenter [$invalidLinkMode |creating-links#Liens invalides]. Les [alias simplifient la création de liens |creating-links#Alias] vers les presenters fréquemment utilisés. @@ -73,7 +75,7 @@ application: - %vendorDir%/mymodule ``` -L'analyse des répertoires peut être désactivée en spécifiant la valeur `false`. Nous ne recommandons pas de supprimer complètement l'ajout automatique des presenters, car cela réduirait les performances de l'application. +L'analyse des répertoires peut être désactivée en spécifiant la valeur `false`. Les presenters ne sont alors plus enregistrés comme services : on ne peut donc plus les ajuster via la section [decorator |dependency-injection:configuration#Decorator] et leur création est plus lente. Nous ne recommandons pas de supprimer complètement l'enregistrement automatique, car cela réduirait les performances de l'application. Templates Latte @@ -83,16 +85,22 @@ Ce paramètre permet d'influencer globalement le comportement de Latte dans les ```neon latte: - # afficher le panneau Latte dans la barre Tracy pour le template principal (true) ou tous les composants (all) ? - debugger: ... # (true|false|'all') par défaut true + # afficher le panneau Latte dans la barre Tracy pour le template principal (true) ou pour tous les composants (all) ? + debugger: ... # (true|false|'all') activé si Tracy est disponible (mode développement uniquement) # génère les templates avec l'en-tête declare(strict_types=1) strictTypes: ... # (bool) par défaut false - # active le mode [parseur strict |latte:develop#strict-mode] + # active le mode [parseur strict |latte:develop#strict mode] strictParsing: ... # (bool) par défaut false - # active le [contrôle du code généré |latte:develop#checking-generated-code] + # limite la portée des variables au corps de la boucle + scopedLoopVariables: ... # (bool) par défaut false + + # supprime l'indentation due à l'imbrication dans les balises paires + dedent: ... # (bool) par défaut false + + # active le [contrôle du code généré |latte:develop#Checking Generated Code] phpLinter: ... # (string) par défaut null # définit la locale @@ -102,7 +110,7 @@ latte: templateClass: App\MyTemplateClass # par défaut Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Si vous utilisez Latte version 3, vous pouvez ajouter de nouvelles [extensions |latte:extending-latte#Latte Extension] en utilisant : +Vous pouvez ajouter de nouvelles [extensions |latte:extending-latte#Latte Extension] avec : ```neon latte: @@ -110,20 +118,6 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Si vous utilisez Latte version 2, vous pouvez enregistrer de nouvelles balises soit en spécifiant le nom de la classe, soit par référence à un service. Par défaut, la méthode `install()` est appelée, mais cela peut être modifié en spécifiant le nom d'une autre méthode : - -```neon -latte: - # enregistrement des balises Latte personnalisées - macros: - - App\MyLatteMacros::register # méthode statique, nom de classe ou callable - - @App\MyLatteMacrosFactory # service avec la méthode install() - - @App\MyLatteMacrosFactory::register # service avec la méthode register() - -services: - - App\MyLatteMacrosFactory -``` - Routage ======= @@ -181,11 +175,12 @@ Services DI Ces services sont ajoutés au conteneur DI : -| Nom | Type | Description -|---------------------------------------------------------------------------------| +| Nom | Type | Description +|----------------------------|---------------------------------------------------|----------------------------------------- | `application.application` | [api:Nette\Application\Application] | [lanceur de toute l'application |how-it-works#Nette Application] | `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] -| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | factory de presenters +| `application.presenterFactory` | [api:Nette\Application\IPresenterFactory] | factory de presenters | `application.###` | [api:Nette\Application\UI\Presenter] | presenters individuels +| `routing.router` | [api:Nette\Routing\Router] | routeur | `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | factory de l'objet `Latte\Engine` | `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | factory pour [`$this->template` |templates] diff --git a/application/fr/creating-links.texy b/application/fr/creating-links.texy index 1ceacf2bd0..1df0b3cecb 100644 --- a/application/fr/creating-links.texy +++ b/application/fr/creating-links.texy @@ -40,7 +40,7 @@ Il est également possible de passer des paramètres nommés. Le lien suivant pa Si la méthode `ProductPresenter::renderShow()` n'a pas `$lang` dans sa signature, elle peut récupérer la valeur du paramètre en utilisant `$lang = $this->getParameter('lang')` ou depuis une [propriété |presenters#Paramètres de la requête]. -Si les paramètres sont stockés dans un tableau, ils peuvent être développés avec l'opérateur `...` (dans Latte 2.x, l'opérateur `(expand)`) : +Si les paramètres sont stockés dans un tableau, ils peuvent être développés avec l'opérateur `...` : ```latte {var $args = [$product->id, lang => cs]} @@ -73,6 +73,14 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Les liens peuvent également être créés sans presenter, c'est à cela que sert [#LinkGenerator] et sa méthode `link()`. +Il arrive que vous ayez besoin de créer un lien maintenant, mais de ne générer l'URL réelle que plus tard. La méthode `lazyLink()` est là pour ça : elle retourne un objet `Nette\Application\UI\Link`. L'avantage est que vous pouvez transmettre cet objet, par exemple à un template, et ajuster encore ses paramètres avec la méthode `setParameter()` avant qu'il ne soit rendu. L'URL elle-même n'est assemblée qu'à la conversion de l'objet en chaîne : + +```php +$link = $this->lazyLink('Product:show', $id); +// ... +echo $link; // l'URL n'est générée qu'ici +``` + Liens vers un presenter ======================= @@ -122,6 +130,13 @@ Nous pouvons lier vers une partie spécifique de la page via un fragment après lien vers Home:default et le fragment #main ``` +.{data-version:3.3.0} +Le fragment peut aussi être défini dynamiquement, comme argument portant la clé `#`. Sa valeur est encodée automatiquement et l'emporte sur le fragment indiqué dans la destination : + +```php +$this->link('Home:default', ['#' => $fragment]); +``` + Chemins absolus =============== @@ -130,6 +145,8 @@ Les liens générés à l'aide de `link()` ou `n:href` sont toujours des chemins Pour générer une URL absolue, ajoutez deux barres obliques au début (par ex. `n:href="//Home:"`). Ou vous pouvez configurer le presenter pour ne générer que des liens absolus en définissant `$this->absoluteUrls = true`. +Le filtre `|absoluteUrl` peut également servir dans le template à convertir un chemin relatif en chemin absolu. + Lien vers la page actuelle ========================== @@ -179,6 +196,22 @@ Pour vérifier si nous sommes dans un certain module ou son sous-module, utilise ``` +Changer la base des liens .{data-version:3.2.7} +=============================================== + +Par défaut, les liens relatifs sont dérivés du presenter courant. On peut changer cela avec `{linkBase}` : + +```latte +{linkBase Admin:Dashboard} +product detail +``` + +Le lien mènera vers `Admin:Dashboard:Product:show`. Seuls les liens relatifs sont concernés : les liens absolus commençant par un deux-points et les liens vers le presenter courant (`this`, `show`) restent inchangés. + +`{linkBase}` s'applique à tout le template et se révèle particulièrement utile dans les templates de layout, où il garantit des liens cohérents quel que soit le presenter appelant. +La balise doit être placée au début du template, sinon elle lève une `CompileException`. + + Liens vers un signal ==================== diff --git a/application/fr/directory-structure.texy b/application/fr/directory-structure.texy index 48cf3ac4bd..1c9f1a4f77 100644 --- a/application/fr/directory-structure.texy +++ b/application/fr/directory-structure.texy @@ -95,7 +95,7 @@ Ce répertoire est le seul accessible depuis le web (appelé document-root). Vou - Les fichiers statiques (CSS, JavaScript, images) - Les fichiers uploadés -Pour une sécurité correcte de l'application, il est essentiel d'avoir le [document-root correctement configuré |nette:troubleshooting#Comment changer ou supprimer le répertoire www de l URL]. +Pour une sécurité correcte de l'application, il est essentiel d'avoir le [document-root correctement configuré |nette:troubleshooting#Comment changer ou supprimer le répertoire www de l'URL ?]. .[note] Ne placez jamais le dossier `node_modules/` dans ce répertoire - il contient des milliers de fichiers qui peuvent être exécutables et ne devraient pas être accessibles publiquement. @@ -202,8 +202,8 @@ Avec le temps, d'autres types de flux sont ajoutés et nous avons besoin de plus │ └── sitemap.latte └── Feed/ ├── FeedPresenter.php - ├── zbozi.latte ← flux pour Zboží.cz - └── heureka.latte ← flux pour Heureka.cz + ├── amazon.latte ← flux pour Amazon + └── ebay.latte ← flux pour eBay \-- Cette transformation est absolument fluide - il suffit de créer de nouveaux sous-dossiers, d'y répartir le code et de mettre à jour les liens (par exemple de `Export:feed` à `Export:Feed:zbozi`). Grâce à cela, nous pouvons étendre progressivement la structure selon les besoins, le niveau d'imbrication n'est pas limité. @@ -420,7 +420,7 @@ Exemple : Qu'est-ce qui appartient au modèle et qu'est-ce qui appartient aux scripts de commande ? Par exemple, la logique pour envoyer un seul e-mail fait partie du modèle, l'envoi en masse de milliers d'e-mails appartient déjà à `Tasks/`. -Les tâches sont généralement [lancées depuis la ligne de commande |https://blog.nette.org/en/cli-scripts-in-nette-application] ou via cron. Elles peuvent également être lancées via une requête HTTP, mais il faut penser à la sécurité. Le presenter qui lance la tâche doit être sécurisé, par exemple uniquement pour les utilisateurs connectés ou avec un jeton fort et un accès depuis des adresses IP autorisées. Pour les tâches longues, il est nécessaire d'augmenter la limite de temps du script et d'utiliser `session_write_close()` pour ne pas verrouiller la session. +Les tâches sont généralement lancées depuis la ligne de commande ou via cron : le script de `bin/` crée le conteneur DI à l'aide de la méthode [bootConsoleApplication() |bootstrapping#Environnements Différents] et en tire le service nécessaire. Elles peuvent également être lancées via une requête HTTP, mais il faut penser à la sécurité. Le presenter qui lance la tâche doit être sécurisé, par exemple uniquement pour les utilisateurs connectés ou avec un jeton fort et un accès depuis des adresses IP autorisées. Pour les tâches longues, il est nécessaire d'augmenter la limite de temps du script et d'utiliser `session_write_close()` pour ne pas verrouiller la session. Autres répertoires possibles @@ -470,7 +470,7 @@ Mapping des presenters Le mapping définit les règles pour dériver le nom de la classe à partir du nom du presenter. Nous les spécifions dans la [configuration|configuration] sous la clé `application › mapping`. -Sur cette page, nous avons montré que nous plaçons les presenters dans le dossier `app/Presentation` (ou `app/UI`). Nous devons communiquer cette convention à Nette dans le fichier de configuration. Une seule ligne suffit : +Sur cette page, nous avons montré que nous plaçons les presenters dans le dossier `app/Presentation` (ou `app/UI`). Depuis Nette Application 3.3, c'est la convention par défaut, qu'il n'est pas nécessaire de configurer. Si vous utilisez une autre structure ou si vous voulez indiquer le mapping explicitement, le réglage par défaut correspond à cette ligne : ```neon application: @@ -486,7 +486,7 @@ application: Le mapping fonctionne de telle sorte que le nom du presenter `Home` remplace l'astérisque dans le masque `App\Presentation\*Presenter`, ce qui donne le nom de classe résultant `App\Presentation\HomePresenter`. Simple ! -Mais comme vous pouvez le voir dans les exemples de ce chapitre et d'autres, nous plaçons les classes de presenter dans des sous-répertoires éponymes, par exemple le presenter `Home` est mappé sur la classe `App\Presentation\Home\HomePresenter`. Nous obtenons cela en doublant les deux-points (nécessite Nette Application 3.2) : +Mais comme vous pouvez le voir dans les exemples de ce chapitre et d'autres, nous plaçons les classes de presenter dans des sous-répertoires éponymes, par exemple le presenter `Home` est mappé sur la classe `App\Presentation\Home\HomePresenter`. Nous obtenons cela avec la double astérisque `**` (nécessite Nette Application 3.2.3) : ```neon application: diff --git a/application/fr/how-it-works.texy b/application/fr/how-it-works.texy index 160ad93fba..78d5411595 100644 --- a/application/fr/how-it-works.texy +++ b/application/fr/how-it-works.texy @@ -3,7 +3,7 @@ Comment fonctionnent les applications ?
    -Vous lisez actuellement le guide fondamental de la documentation Nette. Vous apprendrez tout le principe de fonctionnement des applications web. De A à Z, du début à la fin de l'exécution du script PHP. Après lecture, vous saurez : +Vous lisez en ce moment le chapitre fondateur de la documentation de Nette. Vous y apprendrez de A à Z tout le principe de fonctionnement des applications web, depuis l'instant où naît une requête jusqu'à la fin de l'exécution du script PHP. Après cette lecture, vous saurez : - comment tout cela fonctionne - ce qu'est Bootstrap, un Presenter et un conteneur DI @@ -51,7 +51,7 @@ Vous pouvez modifier la structure des répertoires comme vous le souhaitez, reno Pour les applications légèrement plus grandes, nous pouvons [diviser les dossiers avec les presenters et les templates en sous-répertoires |directory-structure#Presenters et templates] et les classes en espaces de noms, que nous appelons modules. -Le répertoire `www/` représente le répertoire public ou document-root du projet. Vous pouvez le renommer sans aucune configuration supplémentaire côté application. Il suffit de [configurer l'hébergement |nette:troubleshooting#Comment changer ou supprimer le répertoire www de l URL] pour que le document-root pointe vers ce répertoire. +Le répertoire `www/` représente le répertoire public ou document-root du projet. Vous pouvez le renommer sans aucune configuration supplémentaire côté application. Il suffit de [configurer l'hébergement |nette:troubleshooting#Comment changer ou supprimer le répertoire www de l'URL ?] pour que le document-root pointe vers ce répertoire. Vous pouvez également télécharger directement WebProject incluant Nette en utilisant [Composer |best-practices:composer] : @@ -59,7 +59,7 @@ Vous pouvez également télécharger directement WebProject incluant Nette en ut composer create-project nette/web-project ``` -Sous Linux ou macOS, définissez les [permissions d'écriture |nette:troubleshooting#Configuration des permissions de répertoire] pour les répertoires `log/` et `temp/`. +Sous Linux ou macOS, définissez les [permissions d'écriture |nette:troubleshooting#Régler les permissions des répertoires] pour les répertoires `log/` et `temp/`. L'application WebProject est prête à être lancée, il n'y a absolument rien à configurer et vous pouvez l'afficher directement dans le navigateur en accédant au dossier `www/`. @@ -67,7 +67,7 @@ L'application WebProject est prête à être lancée, il n'y a absolument rien Requête HTTP ============ -Tout commence au moment où l'utilisateur ouvre une page dans le navigateur. C'est-à-dire lorsque le navigateur contacte le serveur avec une requête HTTP. La requête pointe vers un seul fichier PHP, qui se trouve dans le répertoire public `www/`, et c'est `index.php`. Supposons qu'il s'agisse d'une requête pour l'adresse `https://example.com/product/123`. Grâce à une [configuration serveur appropriée |nette:troubleshooting#Comment configurer le serveur pour les jolies URL], cette URL est également associée au fichier `index.php`, qui est ensuite exécuté. +Tout commence au moment où l'utilisateur ouvre une page dans le navigateur. C'est-à-dire lorsque le navigateur contacte le serveur avec une requête HTTP. La requête pointe vers un seul fichier PHP, qui se trouve dans le répertoire public `www/`, et c'est `index.php`. Supposons qu'il s'agisse d'une requête pour l'adresse `https://example.com/product/123`. Grâce à une [configuration serveur appropriée |nette:troubleshooting#Comment configurer un serveur pour des URL élégantes ?], cette URL est également associée au fichier `index.php`, qui est ensuite exécuté. Ses tâches sont : @@ -95,7 +95,7 @@ Les applications écrites en Nette sont divisées en de nombreux presenters (dan Le routeur a donc transformé l'URL en une paire `Presenter:action` + paramètres, dans notre cas `Product:show` + `id: 123`. À quoi ressemble un tel routeur, vous pouvez le voir dans le fichier `app/Core/RouterFactory.php` et nous le décrivons en détail dans le chapitre [Routage |Routing]. -Continuons. `Application` connaît déjà le nom du presenter et peut continuer. En créant l'objet de la classe `ProductPresenter`, qui est le code du presenter `Product`. Plus précisément, il demande au conteneur DI de créer le presenter, car c'est son rôle. +Continuons. `Application` connaît désormais le nom du presenter et peut poursuivre. Elle le fait en créant une instance de la classe `ProductPresenter`, qui contient le code du presenter `Product`. Plus précisément, elle demande au conteneur DI de créer le presenter, car créer les objets est son rôle. Le presenter peut ressembler à ceci : @@ -145,7 +145,7 @@ Vous avez peut-être rencontré de nombreux nouveaux termes maintenant, mais nou Templates ========= -Puisque nous avons parlé des templates, Nette utilise le système de templates [Latte |latte:]. D'une part parce que c'est le système de templates le plus sécurisé pour PHP, et d'autre part parce que c'est aussi le système le plus intuitif. Vous n'avez pas besoin d'apprendre beaucoup de nouveautés, la connaissance de PHP et de quelques balises suffit. Vous apprendrez tout dans [la documentation |templates]. +Puisque nous parlons de templates, Nette utilise le système de templates [Latte |latte:]. C'est pourquoi les fichiers de template portent l'extension `.latte`. Latte est employé avant tout parce que c'est le système de templates le plus sûr pour PHP, et aussi le plus intuitif. Vous n'avez pas besoin d'apprendre beaucoup de nouveautés, la connaissance de PHP et de quelques balises suffit. Vous apprendrez tout dans [la documentation |templates]. Dans le template, des [liens |creating-links] sont créés vers d'autres presenters & actions comme ceci : @@ -159,7 +159,7 @@ Au lieu d'une URL réelle, écrivez simplement la paire connue `Presenter:action détail du produit ``` -La génération d'URL est gérée par le routeur mentionné précédemment. En effet, les routeurs dans Nette sont exceptionnels car ils peuvent effectuer non seulement des transformations d'URL en paire presenter:action, mais aussi l'inverse, c'est-à-dire générer une URL à partir du nom du presenter + action + paramètres. Grâce à cela, dans Nette, vous pouvez complètement changer les formes d'URL dans toute une application terminée, sans changer un seul caractère dans le template ou le presenter. Juste en modifiant le routeur. Grâce à cela fonctionne également la canonisation, qui est une autre caractéristique unique de Nette, contribuant à un meilleur SEO (optimisation pour les moteurs de recherche) en empêchant automatiquement l'existence de contenu dupliqué sur différentes URL. De nombreux programmeurs trouvent cela impressionnant. +La génération d'URL est gérée par le routeur mentionné précédemment. En effet, les routeurs dans Nette sont exceptionnels car ils savent effectuer non seulement la transformation d'une URL en paire `Presenter:action`, mais aussi l'inverse, c'est-à-dire générer une URL à partir du nom du presenter + action + paramètres. Grâce à cela, dans Nette, vous pouvez complètement changer les formes d'URL dans toute une application terminée, sans changer un seul caractère dans le template ou le presenter. Juste en modifiant le routeur. Grâce à cela fonctionne également la canonisation, qui est une autre caractéristique unique de Nette, contribuant à un meilleur SEO (optimisation pour les moteurs de recherche) en empêchant automatiquement l'existence de contenu dupliqué sur différentes URL. De nombreux programmeurs trouvent cela impressionnant. Composants interactifs diff --git a/application/fr/multiplier.texy b/application/fr/multiplier.texy index c8c57bdd44..0948ec3122 100644 --- a/application/fr/multiplier.texy +++ b/application/fr/multiplier.texy @@ -9,7 +9,7 @@ Partons d'un exemple typique : nous avons une liste de produits dans une boutiqu Multiplier permet de définir facilement une petite factory pour plusieurs composants. Il fonctionne sur le principe des composants imbriqués - chaque composant héritant de [api:Nette\ComponentModel\Container] peut contenir d'autres composants. .[tip] -Voir le chapitre sur le [modèle de composant |components#Composants en profondeur] dans la documentation ou la [présentation de Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. +Voir le chapitre sur le [modèle de composant |components#Composants en profondeur] dans la documentation. L'essence de Multiplier est qu'il agit en tant que parent capable de créer dynamiquement ses enfants à l'aide d'un callback passé dans le constructeur. Voir l'exemple : diff --git a/application/fr/presenters.texy b/application/fr/presenters.texy index 670c01a8b7..a4c22cce50 100644 --- a/application/fr/presenters.texy +++ b/application/fr/presenters.texy @@ -58,9 +58,12 @@ Immédiatement après réception de la requête, la méthode `startup()` est app Analogue à la méthode `render()`. Alors que `render()` est destinée à préparer les données pour un template spécifique qui sera ensuite rendu, `action()` traite la requête sans lien avec le rendu du template. Par exemple, elle traite les données, connecte ou déconnecte l'utilisateur, etc., puis [redirige ailleurs |#Redirection]. -Il est important que `action()` soit appelée avant `render()`, nous pouvons donc éventuellement y modifier le déroulement ultérieur, c'est-à-dire changer le template qui sera rendu, ainsi que la méthode `render()` qui sera appelée. Ceci est fait en utilisant `setView('autreVue')`. +Il est important que `action()` soit appelée *avant* `render()`. Cela nous permet de modifier le cours de la requête dans la méthode d'action, par exemple en changeant le template qui sera rendu, voire la méthode `render()` qui sera appelée, à l'aide de `setView('otherView')`. -Les paramètres de la requête sont passés à la méthode. Il est possible et recommandé de spécifier les types des paramètres, par ex. `actionShow(int $id, ?string $slug = null)` - si le paramètre `id` est manquant ou s'il n'est pas un entier, le presenter retournera une [erreur 404 |#Erreur 404 et autres] et terminera son activité. +.{data-version:3.2.3} +Vous pouvez même basculer vers une action entièrement différente avec la méthode `switch('otherAction')`. Elle interrompt la méthode courante et exécute à la place les méthodes `action()` et `render()` de la nouvelle action (et désactive la [canonisation|#Canonisation] automatique). La requête elle-même se poursuit ; seule la méthode en cours d'exécution est interrompue. + +Les paramètres de la requête sont passés à la méthode. Il est possible et recommandé de préciser leurs types, par ex. `actionShow(int $id, ?string $slug = null)`. Si le paramètre `id` est absent ou n'est pas un entier, le presenter retourne une [erreur 404 |#Erreur 404 et autres] et termine son activité. `handle(args...)` .{toc: handle()} @@ -105,7 +108,27 @@ La méthode `afterRender`, comme son nom l'indique encore une fois, est appelée Appelée à la fin du cycle de vie du presenter. -**Un bon conseil avant de continuer**. Comme vous pouvez le voir, un presenter peut gérer plusieurs actions/vues, c'est-à-dire avoir plusieurs méthodes `render()`. Mais nous recommandons de concevoir des presenters avec une seule ou le moins d'actions possible. +Événements +---------- + +En plus des méthodes `startup()`, `beforeRender()` et `shutdown()`, qui sont appelées dans le cadre du cycle de vie du presenter, il est possible de définir d'autres fonctions appelées automatiquement. Le presenter définit ce qu'on appelle des [événements |nette:glossary#Événements], dont vous ajoutez les gestionnaires aux tableaux `$onStartup`, `$onRender` et `$onShutdown`. + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` + +Les gestionnaires du tableau `$onStartup` sont appelés juste avant la méthode `startup()`, ceux de `$onRender` entre `beforeRender()` et `render()`, et enfin ceux de `$onShutdown` juste avant `shutdown()`. + + +**Un bon conseil avant de continuer :** comme vous pouvez le voir, un presenter peut gérer plusieurs actions/vues, c'est-à-dire avoir plusieurs méthodes `render()`. Nous recommandons toutefois de concevoir des presenters avec une seule action, ou le moins d'actions possible. Envoi de la réponse @@ -116,13 +139,15 @@ La réponse d'un presenter est généralement le [rendu d'un template avec une p À tout moment du cycle de vie, nous pouvons envoyer une réponse avec l'une des méthodes suivantes et ainsi terminer le presenter : - `redirect()`, `redirectPermanent()`, `redirectUrl()` et `forward()` [redirigent |#Redirection] -- `error()` termine le presenter [en raison d'une erreur |#Erreur 404 et autres] +- `error()` termine le presenter [en raison d'une erreur |#Erreur 404 et autres] - `sendJson($data)` termine le presenter et [envoie les données |#Envoi de JSON] au format JSON - `sendTemplate()` termine le presenter et [rend immédiatement le template |templates] - `sendResponse($response)` termine le presenter et envoie une [réponse personnalisée |#Réponses] - `terminate()` termine le presenter sans réponse -Si vous n'appelez aucune de ces méthodes, le presenter procédera automatiquement au rendu du template. Pourquoi ? Parce que dans 99 % des cas, nous voulons rendre un template, donc le presenter considère ce comportement comme celui par défaut et veut nous faciliter le travail. +Chacune de ces méthodes met immédiatement fin au presenter en levant une exception de terminaison silencieuse `Nette\Application\AbortException`. + +Si vous n'appelez aucune de ces méthodes, le presenter procède automatiquement au rendu du template. Pourquoi ? Parce que dans 99 % des cas, nous voulons rendre un template : le presenter adopte donc ce comportement par défaut pour nous simplifier le travail. Création de liens @@ -178,13 +203,13 @@ $this->redirectUrl('https://nette.org'); La redirection termine immédiatement l'activité du presenter en levant une exception de terminaison silencieuse appelée `Nette\Application\AbortException`. -Avant la redirection, il est possible d'envoyer des [#messages flash], c'est-à-dire des messages qui seront affichés dans le template après la redirection. +Avant la redirection, il est possible d'envoyer des [#Messages Flash], c'est-à-dire des messages qui seront affichés dans le template après la redirection. Messages Flash ============== -Ce sont des messages informant généralement du résultat d'une opération. Une caractéristique importante des messages flash est qu'ils sont disponibles dans le template même après une redirection. Même après affichage, ils restent actifs pendant 30 secondes supplémentaires – par exemple, au cas où l'utilisateur rafraîchirait la page en raison d'une erreur de transmission - le message ne disparaîtra donc pas immédiatement. +Ce sont des messages informant généralement du résultat d'une opération. Une caractéristique importante des messages flash est qu'ils sont disponibles dans le template même après une redirection. Même après affichage, ils restent actifs pendant 30 secondes supplémentaires - par exemple, au cas où l'utilisateur rafraîchirait la page en raison d'une erreur de transmission - le message ne disparaîtra donc pas immédiatement. Il suffit d'appeler la méthode [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] et le presenter se chargera de la transmettre au template. Le premier paramètre est le texte du message et le deuxième paramètre facultatif est son type (error, warning, info, etc.). La méthode `flashMessage()` retourne une instance du message flash, à laquelle des informations supplémentaires peuvent être ajoutées. @@ -224,7 +249,7 @@ Le code d'erreur HTTP peut être passé comme deuxième paramètre, la valeur pa Envoi de JSON ============= -Exemple de méthode d'action qui envoie des données au format JSON et termine le presenter : +La méthode `sendJson($data)` encode les données données en JSON, les envoie comme réponse HTTP et termine le presenter. Exemple : ```php public function actionData(): void @@ -315,6 +340,26 @@ Ou il peut être *réinitialisé*, c'est-à-dire supprimé de l'URL. Il prendra ``` +Espace de paramètres partagé +============================ + +Les paramètres de la requête, les [paramètres persistants |#Paramètres persistants] et les paramètres des méthodes `action`, `render` et `handle` (signal) partagent un seul et même espace, où chacun est identifié par son nom. Si le même nom apparaît dans plusieurs d'entre eux, il désigne une seule et même valeur. + +On en tire souvent parti. Un paramètre persistant `lang` et l'argument `$lang` d'une méthode d'action ou de signal ne font qu'un : vous pouvez lire la valeur courante d'un paramètre persistant simplement en le déclarant dans la signature de la méthode : + +```php +#[Persistent] +public string $lang; + +public function handleSearch(string $query, string $lang): void +{ + // $lang contient la valeur courante du paramètre persistant lang +} +``` + +Comme cet espace est partagé, veillez à ce que les noms de paramètres soient uniques, sauf si vous voulez délibérément qu'ils partagent une valeur. Cela vaut aussi pour les signaux, qui lisent en outre les paramètres du corps POST de la requête, voyez [Les signaux en profondeur |components#Signaux en profondeur]. + + Composants interactifs ====================== @@ -335,7 +380,7 @@ Ce que nous avons montré jusqu'à présent dans ce chapitre vous suffira probab Validation des paramètres ------------------------- -Les valeurs des [#paramètres de la requête] et des [#paramètres persistants] reçues de l'URL sont écrites dans les propriétés par la méthode `loadState()`. Celle-ci vérifie également si le type de données indiqué pour la propriété correspond, sinon elle répond par une erreur 404 et la page ne s'affiche pas. +Les valeurs des [#Paramètres de la requête] et des [#Paramètres persistants] reçues de l'URL sont écrites dans les propriétés par la méthode `loadState()`. Celle-ci vérifie également si le type de données indiqué pour la propriété correspond, sinon elle répond par une erreur 404 et la page ne s'affiche pas. Ne faites jamais confiance aveuglément aux paramètres, car ils peuvent être facilement modifiés par l'utilisateur dans l'URL. Voici comment nous vérifions, par exemple, si la langue `$this->lang` fait partie des langues prises en charge. Une bonne approche consiste à redéfinir la méthode `loadState()` mentionnée : @@ -391,25 +436,7 @@ public function actionShow(int $id, ?string $slug = null): void } ``` - -Événements ----------- - -En plus des méthodes `startup()`, `beforeRender()` et `shutdown()`, qui sont appelées dans le cadre du cycle de vie du presenter, il est possible de définir d'autres fonctions qui doivent être appelées automatiquement. Le presenter définit ce qu'on appelle des [événements |nette:glossary#Événements events], dont vous ajoutez les gestionnaires aux tableaux `$onStartup`, `$onRender` et `$onShutdown`. - -```php -class ArticlePresenter extends Nette\Application\UI\Presenter -{ - public function __construct() - { - $this->onStartup[] = function () { - // ... - }; - } -} -``` - -Les gestionnaires dans le tableau `$onStartup` sont appelés juste avant la méthode `startup()`, ensuite `$onRender` entre `beforeRender()` et `render()` et enfin `$onShutdown` juste avant `shutdown()`. +Pour un motif complet combinant les filtres de route et `canonicalize()` afin de produire des URL favorables au référencement, voyez [URL propres avec slugs |best-practices:pretty-urls]. Réponses @@ -445,6 +472,63 @@ $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $ht $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +Vous pouvez aussi écrire votre propre réponse. Il suffit d'implémenter l'interface `Nette\Application\Response`, qui n'a qu'une méthode `send()` recevant la requête et la réponse HTTP. C'est utile, par exemple, pour diffuser des données que vous ne voulez pas garder en mémoire : + +```php +class CsvResponse implements Nette\Application\Response +{ + public function __construct( + private string $fileName, + private iterable $rows, + ) { + } + + public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void + { + $response->setContentType('text/csv', 'utf-8'); + $response->sendAsFile($this->fileName); + + $handle = fopen('php://output', 'w'); + foreach ($this->rows as $row) { + fputcsv($handle, $row); + } + + fclose($handle); + } +} +``` + +Vous l'envoyez ensuite dans le presenter comme d'habitude : `$this->sendResponse(new CsvResponse('export.csv', $rows));` + + +Cache HTTP +---------- + +La méthode `lastModified()` permet de tirer facilement parti du cache HTTP. Vous lui passez la date et l'heure de dernière modification du contenu (sous forme de timestamp, de chaîne ou d'objet `DateTimeInterface`) et, éventuellement, un validateur ETag (une courte chaîne identifiant la version courante du contenu, comme son hachage) et une durée d'expiration. Si le navigateur détient déjà une version correspondante, le presenter envoie une réponse `304 Not Modified` et termine : la page n'est donc ni rendue ni transférée inutilement : + +```php +public function renderArticle(int $id): void +{ + $article = $this->articles->getById($id); + $this->lastModified($article->updatedAt); + // ... +} +``` + + +Finalisation du template .{data-version:3.3.0} +---------------------------------------------- + +Quand le presenter rend un template, la méthode `sendTemplate()` appelle `completeTemplate()` juste avant le rendu. Cette méthode remplit les variables marquées de l'attribut `#[TemplateVariable]` et localise le fichier de template (les variables par défaut sont déjà définies par la `TemplateFactory` lors de la création du template). Vous pouvez redéfinir cette méthode protégée pour ajouter des variables partagées par toutes les vues ou pour indiquer un autre fichier : + +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My App'; +} +``` + Restriction d'accès avec `#[Requires]` .{data-version:3.2.2} ------------------------------------------------------------ @@ -458,6 +542,9 @@ Vous pouvez spécifier ces restrictions : - accès uniquement via forward : `#[Requires(forward: true)]` - restriction à des actions spécifiques : `#[Requires(actions: 'default')]` +.[note] +Depuis la version 3.3, la correspondance de même origine est vérifiée à l'aide de l'en-tête `Sec-Fetch-Site` du navigateur (auparavant via un cookie SameSite), ce qui est plus fiable et contrôle la correspondance exacte du schéma, du domaine et du port. + Les détails se trouvent dans le guide [Comment utiliser l'attribut Requires |best-practices:attribute-requires]. @@ -475,7 +562,7 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Dans la version 3.1, la vérification est effectuée dans `checkHttpMethod()`, qui vérifie si la méthode spécifiée dans la requête est contenue dans le tableau `$presenter->allowedMethods`. L'ajout de la méthode se fait comme ceci : +Depuis la version 3.1.13, la vérification est effectuée dans `checkHttpMethod()`, qui contrôle si la méthode indiquée dans la requête figure dans le tableau `$presenter->allowedMethods`. Depuis la version 3.2.3, cette approche est dépréciée au profit de `#[Requires]`. Vous pouvez redéfinir la méthode ainsi : ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -491,6 +578,14 @@ class MyPresenter extends Nette\Application\UI\Presenter Il est important de souligner que si vous autorisez la méthode `OPTIONS`, vous devez ensuite la gérer correctement dans votre presenter. La méthode est souvent utilisée comme une requête dite preflight, que le navigateur envoie automatiquement avant la requête réelle lorsqu'il est nécessaire de déterminer si la requête est autorisée du point de vue de la politique CORS (Cross-Origin Resource Sharing). Si vous autorisez la méthode mais n'implémentez pas la réponse correcte, cela peut entraîner des incohérences et des problèmes de sécurité potentiels. +Marquer les actions obsolètes .{data-version:3.2.3} +--------------------------------------------------- + +L'attribut `#[Deprecated]` marque des actions, des signaux ou des presenters entiers comme obsolètes et voués à disparaître. Lors de la génération de liens vers ces parties obsolètes de l'application, Nette lève un avertissement pour alerter les développeurs. + +Vous pouvez appliquer l'attribut à toute la classe du presenter ou à chaque méthode `action()`, `render()` et `handle()`. + + Lectures complémentaires ======================== diff --git a/application/fr/routing.texy b/application/fr/routing.texy index 66bfebc31b..64ec3dafce 100644 --- a/application/fr/routing.texy +++ b/application/fr/routing.texy @@ -12,7 +12,7 @@ Le routeur s'occupe de tout ce qui concerne les adresses URL, afin que vous n'ay
    -Les URL conviviales (aussi appelées cool ou pretty URL) sont plus utilisables, plus faciles à mémoriser et contribuent positivement au SEO. Nette y pense et répond pleinement aux attentes des développeurs. Vous pouvez concevoir pour votre application exactement la structure d'adresses URL que vous souhaitez. Vous pouvez même la concevoir lorsque l'application est déjà terminée, car cela se fait sans intervention dans le code ou les templates. Elle est définie de manière élégante en un [seul endroit |#Intégration dans l application], dans le routeur, et n'est donc pas dispersée sous forme d'annotations dans tous les presenters. +Les URL conviviales (aussi appelées cool ou pretty URL) sont plus utilisables, plus faciles à mémoriser et contribuent positivement au SEO. Nette y pense et répond pleinement aux attentes des développeurs. Vous pouvez concevoir pour votre application exactement la structure d'adresses URL que vous souhaitez. Vous pouvez même la concevoir lorsque l'application est déjà terminée, car cela se fait sans intervention dans le code ou les templates. Elle est définie de manière élégante en un [seul endroit |#Intégration dans l'application], dans le routeur, et n'est donc pas dispersée sous forme d'annotations dans tous les presenters. Le routeur dans Nette est exceptionnel car il est **bidirectionnel.** Il sait à la fois décoder les URL dans la requête HTTP et créer des liens. Il joue donc un rôle essentiel dans [Nette Application |how-it-works#Nette Application], car il décide non seulement quel presenter et quelle action exécuteront la requête actuelle, mais il est également utilisé pour la [génération d'URL |creating-links] dans le template, etc. @@ -244,6 +244,16 @@ $router->addRoute('/[/]', [ Il est important de noter que si les paramètres définis dans le tableau ne sont pas spécifiés dans le masque du chemin, leurs valeurs ne peuvent pas être modifiées, même à l'aide des paramètres de requête spécifiés après le point d'interrogation dans l'URL. +C'est utile pour les **paramètres fixes** : donner à une page précise une URL courte et facile à retenir. Par exemple, pour que `/tos` ouvre toujours `Article:view` avec `id: 123` : + +```php +$router->addRoute('tos', [ + 'presenter' => 'Article', + 'action' => 'view', + 'id' => 123, +]); +``` + Filtres et traductions ---------------------- @@ -306,7 +316,7 @@ Les paramètres `presenter`, `action` et `module` ont déjà des filtres prédé Filtres généraux ---------------- -En plus des filtres destinés à des paramètres spécifiques, nous pouvons également définir des filtres généraux qui reçoivent un tableau associatif de tous les paramètres, qu'ils peuvent modifier de n'importe quelle manière, puis les retournent. Nous définissons les filtres généraux sous la clé `null`. +En plus des filtres destinés à des paramètres spécifiques, nous pouvons également définir des filtres généraux qui reçoivent un tableau associatif de tous les paramètres, qu'ils peuvent modifier de n'importe quelle manière, puis les retournent. Nous définissons les filtres généraux sous la clé vide. ```php use Nette\Routing\Route; @@ -325,6 +335,8 @@ Les filtres généraux donnent la possibilité de modifier le comportement de la Si un paramètre a un filtre personnalisé défini et qu'un filtre général existe également, le `FilterIn` personnalisé est exécuté avant le général et inversement, le `FilterOut` général est exécuté avant le personnalisé. Ainsi, à l'intérieur du filtre général, les valeurs des paramètres `presenter` ou `action` sont écrites dans le style PascalCase ou camelCase. +Voyez [URL propres avec slugs |best-practices:pretty-urls] pour un usage concret de ces filtres : générer des URL favorables au référencement, du type `/article/123-how-to-bake-bread`, sans toucher au moindre template. + Routes unidirectionnelles OneWay -------------------------------- @@ -344,7 +356,7 @@ Lors de l'accès à l'ancienne URL, le presenter redirige automatiquement vers l Routage dynamique avec callbacks -------------------------------- -Le routage dynamique avec callbacks vous permet d'assigner directement des fonctions (callbacks) aux routes, qui seront exécutées lorsque le chemin donné sera visité. Cette fonctionnalité flexible vous permet de créer rapidement et efficacement différents points de terminaison (endpoints) pour votre application : +Le routage dynamique avec callbacks vous permet d'assigner directement des fonctions (callbacks) aux routes, qui seront exécutées lorsque le chemin donné sera visité. Cette fonctionnalité flexible vous permet de créer rapidement et efficacement différents points de terminaison (endpoints) pour votre application : ```php $router->addRoute('test', function () { @@ -363,11 +375,19 @@ $router->addRoute('', function (string $lang) { }); ``` +Outre les paramètres du masque, le callback peut aussi recevoir des services du conteneur DI. Ils lui sont passés d'après le type du paramètre. De plus, le paramètre `$presenter` reçoit une instance de [MicroPresenter |api:NetteModule\MicroPresenter], qui traite la route : + +```php +$router->addRoute('', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) { + // ... +}); +``` + Modules ------- -Si nous avons plusieurs routes qui appartiennent à un [module |directory-structure#Presenters et templates] commun, nous utilisons `withModule()` : +Si nous avons plusieurs routes qui appartiennent à un [module |directory-structure#Presenters et templates] commun, nous utilisons `withModule()`. Le module donné est automatiquement préfixé au presenter de chaque route du groupe, et il disparaît complètement de l'URL : ```php $router = new RouteList; @@ -379,7 +399,7 @@ $router->withModule('Forum') // les routes suivantes font partie du module Forum ->addRoute('sign:in', 'Sign:in'); ``` -Une alternative est d'utiliser le paramètre `module` : +Une alternative est le paramètre `module`, qui fixe lui aussi un module donné et le garde hors de l'URL : ```php // L'URL manage/dashboard/default est mappée sur le presenter Admin:Dashboard @@ -388,6 +408,12 @@ $router->addRoute('manage//', [ ]); ``` +Tout nom de presenter n'est complet qu'accompagné de son module, par ex. `Front:Admin:ProductList`. Chaque fois qu'un tel nom complet atterrit dans un paramètre d'URL, le routeur l'encode selon deux règles simples : chaque deux-points `:` (le séparateur de modules) devient un **point**, et chaque frontière de mot d'un nom en PascalCase devient un **tiret**. Ainsi, `Front:Admin:ProductList` apparaît dans l'URL comme `front.admin.product-list` et se décode de la même façon en sens inverse. C'est pourquoi une application modulaire produit, sans aucun des outils ci-dessus, des URL pleines de points. + +`withModule()` comme le paramètre `module` évitent cela précisément parce qu'ils retirent un préfixe de module connu du nom du presenter avant qu'il n'atteigne l'URL : le module étant constant, il n'a pas besoin d'être encodé du tout. + +Il arrive que nous voulions faire varier le module lui-même et le voir apparaître dans l'URL ; nous employons alors `` directement dans le masque. Attention à un détail capital : **`` capture tout le chemin du module**, c'est-à-dire tout ce qui précède le dernier deux-points du nom du presenter. Pour le presenter `Shop:Admin:Product`, cela signifie le module `Shop:Admin` et le presenter `Product` ; et comme les deux-points deviennent des points, nous obtenons : + Sous-domaines ------------- @@ -522,7 +548,7 @@ Il génère des adresses à peu près sous cette forme : http://example.com/?presenter=Product&action=detail&id=123 ``` -Le paramètre du constructeur de SimpleRouter est le presenter & action par défaut vers lequel il faut diriger si nous ouvrons la page sans paramètres, par ex. `http://example.com/`. +Le paramètre du constructeur de `SimpleRouter` est le presenter & action par défaut, c'est-à-dire l'action à exécuter si nous ouvrons par exemple `http://example.com/` sans autres paramètres. ```php // le presenter par défaut sera 'Home' et l'action 'default' @@ -552,7 +578,7 @@ HTTPS Pour pouvoir utiliser le protocole HTTPS, il est nécessaire de l'activer sur l'hébergement et de configurer correctement le serveur. -La redirection de l'ensemble du site vers HTTPS doit être configurée au niveau du serveur, par exemple à l'aide du fichier .htaccess dans le répertoire racine de notre application, et ce avec le code HTTP 301. La configuration peut varier en fonction de l'hébergement et ressemble à peu près à ceci : +La redirection de l'ensemble du site vers HTTPS doit être configurée au niveau du serveur, par exemple à l'aide du fichier `.htaccess` dans le répertoire racine de notre application, et ce avec le code HTTP 301. La configuration peut varier en fonction de l'hébergement et ressemble à peu près à ceci : ``` @@ -608,7 +634,7 @@ routing: Routeur personnalisé ==================== -Les lignes suivantes sont destinées aux utilisateurs très avancés. Vous pouvez créer votre propre routeur et l'intégrer tout naturellement dans la collection de routes. Le routeur est une implémentation de l'interface [api:Nette\Routing\Router] se dvěma metodami: +Les lignes suivantes sont destinées aux utilisateurs très avancés. Vous pouvez créer votre propre routeur et l'intégrer tout naturellement dans la collection de routes. Le routeur est une implémentation de l'interface [api:Nette\Routing\Router] avec deux méthodes : ```php use Nette\Http\IRequest as HttpRequest; @@ -628,7 +654,7 @@ class MyRouter implements Nette\Routing\Router } ``` -La méthode `match` traite la requête actuelle [$httpRequest |http:request], à partir de laquelle on peut obtenir non seulement l'URL, mais aussi les en-têtes, etc., en un tableau contenant le nom du presenter et ses paramètres. Si elle ne peut pas traiter la requête, elle retourne null. Lors du traitement de la requête, nous devons retourner au minimum le presenter et l'action. Le nom du presenter est complet et contient également d'éventuels modules : +La méthode `match` traite la requête actuelle [$httpRequest |http:request], à partir de laquelle on peut obtenir non seulement l'URL, mais aussi les en-têtes, etc., en un tableau contenant le nom du presenter et ses paramètres. Si elle ne peut pas traiter la requête, elle retourne null. Lors du traitement de la requête, nous devons renvoyer au minimum le presenter ; l'action est facultative et vaut `default` par défaut si elle n'est pas indiquée. Le nom du presenter est complet et contient également d'éventuels modules : ```php [ @@ -718,4 +744,6 @@ $url = $router->constructUrl($params, $httpRequest->getUrl()); ``` -{{composer: nette/router}} +{{composer: nette/routing}} +{{repo: nette/routing}} +{{api: https://api.nette.org/routing/}} diff --git a/application/fr/templates.texy b/application/fr/templates.texy index 7216f421d8..2b1931c088 100644 --- a/application/fr/templates.texy +++ b/application/fr/templates.texy @@ -63,7 +63,7 @@ app/ Le répertoire `templates` peut également être placé un niveau plus haut, c'est-à-dire au même niveau que le répertoire contenant les classes des presenters. -Si le template n'est pas trouvé, le presenter répondra par une [erreur 404 - page non trouvée |presenters#Erreur 404 et autres]. +Si le template n'est pas trouvé, le presenter répondra par une [erreur 404 - page non trouvée |presenters#Erreur 404 et autres]. Vous pouvez changer la vue en utilisant `$this->setView('autreVue')`. Il est également possible de spécifier directement le fichier de template en utilisant `$this->template->setFile('/chemin/vers/template.latte')`. @@ -114,15 +114,48 @@ Les fichiers où les templates de layout sont recherchés peuvent être modifié Variables dans le template -------------------------- -Nous passons des variables au template en les écrivant dans `$this->template` et elles sont ensuite disponibles dans le template comme variables locales : +Les variables sont passées aux templates en les écrivant dans `$this->template`. Elles deviennent alors disponibles dans le template comme variables locales : ```php $this->template->article = $this->articles->getById($id); ``` -Nous pouvons ainsi passer facilement n'importe quelle variable aux templates. Cependant, lors du développement d'applications robustes, il est plus utile de se limiter. Par exemple, en définissant explicitement la liste des variables que le template attend et leurs types. Grâce à cela, PHP pourra vérifier les types, l'IDE pourra suggérer correctement et l'analyse statique pourra détecter les erreurs. +Pour passer automatiquement la valeur d'une propriété au template sous forme de variable, marquez-la de l'attribut `#[TemplateVariable]` et rendez-la publique : .{data-version:3.2.9} -Et comment définir une telle liste ? Simplement sous la forme d'une classe et de ses propriétés. Nous la nommerons de manière similaire au presenter, mais avec `Template` à la fin : +```php +use Nette\Application\Attributes\TemplateVariable; + +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + #[TemplateVariable] + public string $siteName = 'My blog'; +} +``` + +Si vous passez au template une variable du même nom, `#[TemplateVariable]` ne l'écrasera pas. + + +Variables par défaut +-------------------- + +Les presenters et les composants transmettent automatiquement plusieurs variables utiles aux templates : + +- `$basePath` est le chemin URL absolu vers le répertoire racine (par ex. `/eshop`) +- `$baseUrl` est l'URL absolue vers le répertoire racine (par ex. `http://localhost/eshop`) +- `$user` est l'objet [représentant l'utilisateur |security:authentication] +- `$presenter` est le presenter actuel +- `$control` est le composant ou presenter actuel +- `$flashes` tableau des [messages |presenters#Messages Flash] envoyés par la fonction `flashMessage()` + +Si vous utilisez votre propre classe de template, ces variables seront transmises si vous créez une propriété pour elles. + + +Templates typés +--------------- + +Quand vous développez des applications robustes, il est utile de définir explicitement quelles variables le template attend et de quels types. Cela apporte le contrôle de types de PHP, des suggestions pertinentes dans l'IDE et permet à l'analyse statique de détecter les erreurs. + +Comment définir une telle liste ? Simplement sous forme de classe dont les propriétés représentent les variables du template. Nommez-la comme le presenter, en ajoutant simplement `Template` à la fin : ```php /** @@ -141,22 +174,24 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -L'objet `$this->template` dans le presenter sera désormais une instance de la classe `ArticleTemplate`. Ainsi, PHP vérifiera les types déclarés lors de l'écriture. Et à partir de PHP 8.2, il avertira également en cas d'écriture dans une variable inexistante ; dans les versions précédentes, le même résultat peut être obtenu en utilisant le trait [Nette\SmartObject |utils:smartobject]. +L'objet `$this->template` du presenter sera désormais une instance de la classe `ArticleTemplate`. PHP contrôlera donc les types déclarés lors de l'écriture. + +Nette choisit la classe de template automatiquement. Il cherche d'abord une classe nommée `Template`, par ex. `ArticleEditTemplate` pour l'action `edit`, et ne se rabat sur `Template` que si elle n'existe pas. -L'annotation `@property-read` est destinée à l'IDE et à l'analyse statique, grâce à elle, l'autocomplétion fonctionnera, voir [PhpStorm et l'autocomplétion pour $this⁠-⁠>⁠template|https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template]. +L'annotation `@property-read` est destinée à l'IDE et à l'analyse statique : elle active la complétion, voyez "PhpStorm et la complétion pour $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Vous pouvez également profiter du luxe de l'autocomplétion dans les templates, il suffit d'installer le plugin pour Latte dans PhpStorm et d'indiquer le nom de la classe au début du template, plus d'informations dans l'article [Latte : comment gérer le système de types|https://blog.nette.org/fr/latte-how-to-use-type-system] : +Vous pouvez aussi utiliser la complétion directement dans les templates. Il suffit d'installer le plugin Latte pour PhpStorm et d'indiquer au début du template le nom de la classe de paramètres, plus de détails dans le chapitre [Latte : système de types |latte:type-system] : ```latte {templateType App\Presentation\Article\ArticleTemplate} ... ``` -C'est ainsi que fonctionnent également les templates dans les composants, il suffit de respecter la convention de nommage et pour un composant par ex. `FifteenControl` créer une classe de template `FifteenTemplate`. +Cela vaut également pour les composants. Il suffit de suivre la convention de nommage et de créer une classe de paramètres `FifteenTemplate` pour un composant comme `FifteenControl`. -Si vous avez besoin de créer `$template` comme instance d'une autre classe, utilisez la méthode `createTemplate()` : +Si vous avez besoin d'une autre classe de paramètres, utilisez la méthode `createTemplate()` : ```php public function renderDefault(): void @@ -168,20 +203,16 @@ public function renderDefault(): void } ``` +.{data-version:3.3.0} +Si vous avez besoin d'influencer la façon dont le template est finalisé avant le rendu - par exemple pour ajouter des variables partagées par toutes les actions - vous pouvez redéfinir la méthode `completeTemplate()` dans le presenter. Elle est appelée juste avant le rendu du template : -Variables par défaut --------------------- - -Les presenters et les composants transmettent automatiquement plusieurs variables utiles aux templates : - -- `$basePath` est le chemin URL absolu vers le répertoire racine (par ex. `/eshop`) -- `$baseUrl` est l'URL absolue vers le répertoire racine (par ex. `http://localhost/eshop`) -- `$user` est l'objet [représentant l'utilisateur |security:authentication] -- `$presenter` est le presenter actuel -- `$control` est le composant ou presenter actuel -- `$flashes` tableau des [messages |presenters#Messages Flash] envoyés par la fonction `flashMessage()` - -Si vous utilisez votre propre classe de template, ces variables seront transmises si vous créez une propriété pour elles. +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My blog'; +} +``` Création de liens @@ -205,21 +236,67 @@ Plus d'informations peuvent être trouvées dans le chapitre [Création de liens Filtres personnalisés, balises, etc. ------------------------------------ -Le système de templates Latte peut être étendu avec des filtres, fonctions, balises personnalisés, etc. Cela peut être fait directement dans la méthode `render` ou `beforeRender()` : +Le système de templates Latte peut être étendu avec des filtres, des fonctions, des balises et d'autres éléments personnalisés. Trois approches sont possibles, de la solution ad hoc rapide aux motifs architecturaux valables pour toute l'application. + +**Ad hoc, dans les méthodes du presenter** + +L'approche la plus rapide consiste à ajouter les filtres ou les fonctions directement dans le code du presenter ou du composant. Dans les presenters, les méthodes `beforeRender()` ou `render()` s'y prêtent bien : ```php -public function beforeRender(): void +protected function beforeRender(): void { // ajout d'un filtre - $this->template->addFilter('foo', /* ... */); + $this->template->addFilter('money', fn($val) => '$' . number_format($val, 2)); - // ou nous configurons directement l'objet Latte\Engine + // ajout d'une fonction + $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); +} +``` + +Dans le template : + +```latte +

    Price: {$price|money}

    + +{if isWeekend($now)} ... {/if} +``` + +Pour une logique plus complexe, vous pouvez configurer directement l'objet `Latte\Engine` : + +```php +protected function beforeRender(): void +{ $latte = $this->template->getLatte(); - $latte->addFilterLoader(/* ... */); + $latte->setFeature(Latte\Feature::MigrationWarnings); +} +``` + +**À l'aide d'attributs** + +Une approche plus élégante consiste à définir les filtres et les fonctions comme méthodes directement dans la [classe de paramètres de template|#Templates typés] du presenter ou du composant, marquées par des attributs : + +```php +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + #[Latte\Attributes\TemplateFilter] + public function money(float $val): string + { + return '$' . number_format($val, 2); + } + + #[Latte\Attributes\TemplateFunction] + public function isWeekend(DateTimeInterface $date): bool + { + return $date->format('N') >= 6; + } } ``` -Latte version 3 offre une méthode plus avancée, à savoir la création d'une [extension |latte:extending-latte#Latte Extension] pour chaque projet web. Exemple succinct d'une telle classe : +Latte découvre et enregistre automatiquement les méthodes marquées par ces attributs. Le nom du filtre ou de la fonction dans les templates correspond au nom de la méthode. Ces méthodes doivent être publiques. + +**Globalement, à l'aide d'extensions** + +Les approches précédentes conviennent aux filtres et fonctions dont vous n'avez besoin que dans certains presenters ou composants, pas dans toute l'application. Pour l'application entière, le mieux est de créer une [extension |latte:extending-latte#Latte Extension]. Cette classe centralise toutes les extensions de Latte de votre projet. Exemple succinct : ```php namespace App\Presentation\Accessory; @@ -251,11 +328,16 @@ final class LatteExtension extends Latte\Extension ]; } + private function filterTimeAgoInWords(DateTimeInterface $time): string + { + // ... + } + // ... } ``` -Nous l'enregistrons à l'aide de la [configuration |configuration#Templates Latte] : +Enregistrez l'extension via la [configuration |configuration#Templates Latte] : ```neon latte: @@ -263,6 +345,20 @@ latte: - App\Presentation\Accessory\LatteExtension ``` +Les extensions offrent plusieurs avantages : la prise en charge de l'injection de dépendances, l'accès à la couche modèle de votre application et la gestion centralisée de toutes les extensions. Elles prennent aussi en charge les balises personnalisées, les providers, les passes de compilation et bien d'autres choses. + + +Configurer tous les templates +----------------------------- + +Le service `TemplateFactory`, qui crée tous les templates, expose un tableau public de callbacks `$onCreate`. Ils sont appelés à chaque création d'un template : depuis un seul endroit, vous pouvez donc définir des filtres, des fonctions ou des variables pour tous les templates de l'application. Chaque callback reçoit le template fraîchement créé. Faites-vous [injecter |dependency-injection:passing-dependencies] le service `TemplateFactory` et enregistrez les callbacks, par exemple au démarrage de l'application : + +```php +$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void { + $template->addFilter('money', fn($val) => '$' . number_format($val, 2)); +}; +``` + Traduction ---------- diff --git a/application/fr/upgrading.texy b/application/fr/upgrading.texy new file mode 100644 index 0000000000..0df1a3b5ea --- /dev/null +++ b/application/fr/upgrading.texy @@ -0,0 +1,47 @@ +Mise à niveau +************* + + +Mise à niveau vers la version 3.0 +================================= + +Nette 3.0 ajoute des déclarations de type aux paramètres et aux valeurs de retour des méthodes. Si vous redéfinissez une telle méthode dans une classe héritant de Nette (dans un presenter ou un composant, par exemple), vous devez y ajouter les mêmes déclarations de type, sans quoi PHP lève une erreur "Declaration must be compatible". + +L'interface `Nette\Application\IRouter` a changé. La méthode `match()` retourne désormais un tableau de paramètres, et `constructUrl()` en accepte un, au lieu d'un objet `Nette\Application\Request`. + +Nette vérifie désormais que chaque signal est envoyé depuis la même origine (c'est-à-dire le même domaine et le même sous-domaine). Cette politique de même origine est un mécanisme de sécurité essentiel, qui aide à réduire les vecteurs d'attaque possibles. Si vous voulez autoriser d'autres origines, ajoutez l'annotation `@crossOrigin` à la méthode de traitement : + +```php +/** + * @crossOrigin + */ +public function handleXy(): void +{ +} +``` + +Cela vaut aussi pour l'envoi des formulaires. Si vous voulez autoriser l'envoi depuis d'autres origines, procédez ainsi : + +```php +$form = new Nette\Application\UI\Form; +$form->allowCrossOrigin(); +``` + +Le constructeur de `Nette\ComponentModel\Component` n'était plus utilisé depuis des années et a été supprimé en version 3.0. C'est une rupture de compatibilité : si vous appelez le constructeur parent dans un composant ou un presenter héritant de `Nette\Application\UI\Presenter`, vous devez retirer cet appel. + + +Mise à niveau vers la version 2.4 +================================= + +- `Route` et `SimpleRouter` génèrent désormais le même schéma HTTP/HTTPS que celui par lequel le site a été atteint. Une route qui exige un protocole précis peut être définie avec le schéma, par ex. `Route('http://domain.cz/')`. +- Pour les paramètres de type bool des méthodes render/action (c'est-à-dire ceux dont la valeur par défaut est true ou false) et pour les paramètres persistants, `false` et `null` sont désormais distingués. Si le paramètre est absent de l'URL, sa valeur est maintenant `null` (auparavant `false`). +- La classe retournée par `Presenter::getReflection()` n'est plus un descendant de `Nette\Reflection\ClassType`, et `getReflection()->getMethod()` n'est plus un descendant de `Nette\Reflection\Method`. +- Le drapeau `SECURED` et `Route::$defaultFlags` sont dépréciés. + + +Mise à niveau vers la version 2.3 +================================= + +- les routes et les noms de presenters sont **sensibles à la casse**. Nette vous avertit si vous employez une mauvaise casse dans un nom de presenter ; pour des raisons de performance, le masque de Route n'est pas vérifié, contrôlez-le donc vous-même. +- `Route::addStyle()` et `Route::setStyleProperty()` sont dépréciées et déclenchent désormais `E_USER_DEPRECATED`. +- l'extension de template `.phtml` et l'ancienne syntaxe des liens ne sont plus prises en charge. diff --git a/application/it/@home.texy b/application/it/@home.texy index ff9cedc27c..3a4b81e569 100644 --- a/application/it/@home.texy +++ b/application/it/@home.texy @@ -2,13 +2,13 @@ Nette Application ***************** .[perex] -Nette Application è il cuore del framework Nette e fornisce potenti strumenti per la creazione di moderne applicazioni web. Offre una serie di funzionalità eccezionali che semplificano notevolmente lo sviluppo e migliorano la sicurezza e la manutenibilità del codice. +Nette Application è il cuore del framework Nette e offre strumenti potenti per creare applicazioni web moderne. Mette a disposizione una serie di funzionalità eccezionali, che semplificano notevolmente lo sviluppo e migliorano la sicurezza e la manutenibilità del codice. Installazione ------------- -È possibile scaricare e installare la libreria utilizzando lo strumento [Composer|best-practices:composer]: +Scaricate e installate la libreria con [Composer|best-practices:composer]: ```shell composer require nette/application @@ -18,68 +18,68 @@ composer require nette/application Perché scegliere Nette Application? ----------------------------------- -Nette è sempre stato un pioniere nel campo delle tecnologie web. +Nette è sempre stata pioniera nelle tecnologie web. -**Router bidirezionale:** Nette dispone di un sistema di routing avanzato, unico per la sua bidirezionalità: non solo traduce gli URL in azioni dell'applicazione, ma può anche generare indirizzi URL a ritroso. Ciò significa che: -- È possibile modificare in qualsiasi momento la struttura degli URL dell'intera applicazione senza dover modificare i template. -- Gli URL vengono automaticamente canonizzati, migliorando la SEO. -- Il routing è definito in un unico punto, non sparso nelle annotazioni. +**Router bidirezionale:** Nette dispone di un sistema di routing avanzato, unico nella sua bidirezionalità: non solo traduce gli URL in azioni dell'applicazione, ma sa anche generare gli URL in senso inverso. Questo significa che: +- potete cambiare in qualsiasi momento la struttura degli URL dell'intera applicazione senza dover modificare i template +- gli URL vengono canonizzati automaticamente, il che migliora la SEO +- il routing è definito in un unico punto, non sparso nelle annotazioni -**Componenti e segnali:** Il sistema di componenti integrato, ispirato a Delphi e React.js, è del tutto eccezionale tra i framework PHP: -- Permette di creare elementi UI riutilizzabili. -- Supporta la composizione gerarchica dei componenti. -- Offre un'elegante gestione delle request AJAX tramite segnali. -- Una ricca libreria di componenti pronti è disponibile su [Componette](https://componette.org). +**Componenti e segnali:** il sistema di componenti integrato, ispirato a Delphi e a React.js, è unico tra i framework PHP: +- permette di creare elementi di interfaccia riutilizzabili +- supporta la composizione gerarchica dei componenti +- offre una gestione elegante delle richieste AJAX tramite i segnali +- ricca libreria di componenti già pronti su [Componette](https://componette.org) -**AJAX e snippet:** Nette ha introdotto un modo rivoluzionario di lavorare con AJAX già nel 2009, molto prima di soluzioni simili come Hotwire per Ruby on Rails o Symfony UX Turbo: -- Gli snippet consentono di aggiornare solo parti della pagina senza dover scrivere JavaScript. -- Integrazione automatica con il sistema dei componenti. -- Invalidazione intelligente di parti delle pagine. -- Quantità minima di dati trasferiti. +**AJAX e snippet:** nel 2009 Nette ha introdotto un modo rivoluzionario di lavorare con AJAX, molto prima di soluzioni simili come Hotwire per Ruby on Rails o Symfony UX Turbo: +- gli snippet permettono di aggiornare solo parti della pagina senza dover scrivere JavaScript +- integrazione automatica con il sistema di componenti +- invalidazione intelligente delle sezioni della pagina +- trasferimento minimo di dati -**Template intuitivi [Latte|latte:]:** Il sistema di template più sicuro per PHP con funzionalità avanzate: -- Protezione automatica contro XSS con escaping sensibile al contesto. -- Estensibilità tramite filtri, funzioni e tag personalizzati. -- Ereditarietà dei template e snippet per AJAX. -- Eccellente supporto per PHP 8.x con sistema di tipi. +**Template [Latte|latte:] intuitivi:** il sistema di template più sicuro per PHP, con funzionalità avanzate: +- protezione automatica contro l'XSS con escaping sensibile al contesto +- estensibile con filtri, funzioni e tag personalizzati +- ereditarietà dei template e snippet per AJAX +- ottimo supporto di PHP 8.x e del sistema di tipi -**Dependency Injection:** Nette sfrutta appieno la Dependency Injection: -- Passaggio automatico delle dipendenze (autowiring). -- Configurazione tramite il chiaro formato NEON. -- Supporto per le factory di componenti. +**Dependency Injection:** Nette sfrutta pienamente la dependency injection: +- passaggio automatico delle dipendenze (autowiring) +- configurazione nel chiaro formato NEON +- supporto per le factory dei componenti Vantaggi principali ------------------- -- **Sicurezza**: Difesa automatica contro [vulnerabilità|nette:vulnerability-protection] come XSS, CSRF, ecc. -- **Produttività**: Meno codice da scrivere, più funzionalità grazie a un design intelligente. -- **Debugging**: [Tracy debugger|tracy:] con pannello di routing. -- **Prestazioni**: Cache intelligente, lazy loading dei componenti. -- **Flessibilità**: Facile modifica degli URL anche dopo il completamento dell'applicazione. -- **Componenti**: Sistema unico di elementi UI riutilizzabili. -- **Moderno**: Pieno supporto per PHP 8.4+ e sistema di tipi. +- **Sicurezza**: protezione automatica contro le [vulnerabilità|nette:vulnerability-protection] come XSS, CSRF e simili +- **Produttività**: meno codice da scrivere e più funzionalità, grazie a una progettazione intelligente +- **Debugging**: il [debugger Tracy|tracy:] con il pannello del routing +- **Prestazioni**: cache intelligente, caricamento pigro dei componenti +- **Flessibilità**: modifica semplice degli URL anche a lavoro finito +- **Componenti**: un sistema unico di elementi di interfaccia riutilizzabili +- **Modernità**: pieno supporto di PHP 8.3+ e del sistema di tipi -Per iniziare ------------- +Primi passi +----------- -1. [Come funzionano le applicazioni? |how-it-works] - Comprensione dell'architettura di base. -2. [Presenter |presenters] - Lavorare con i presenter e le azioni. -3. [Template |templates] - Creazione di template in Latte. -4. [Routing |routing] - Configurazione degli indirizzi URL. -5. [Componenti interattivi |components] - Utilizzo del sistema di componenti. +1. [Come funzionano le applicazioni? |how-it-works] - comprendere l'architettura di base +2. [Presenter |presenters] - lavorare con i presenter e le azioni +3. [Template |templates] - creare template in Latte +4. [Routing |routing] - configurare gli indirizzi URL +5. [Componenti interattivi |components] - usare il sistema di componenti Compatibilità con PHP --------------------- -| versione | compatibile con PHP -|------------------------|------------------- -| Nette Application 4.0 | PHP 8.1 – 8.4 -| Nette Application 3.2 | PHP 8.1 – 8.4 -| Nette Application 3.1 | PHP 7.2 – 8.3 -| Nette Application 3.0 | PHP 7.1 – 8.0 -| Nette Application 2.4 | PHP 5.6 – 8.0 +| versione | compatibile con PHP +|-----------------------|------------------- +| Nette Application 3.3 | PHP 8.3 - 8.5 +| Nette Application 3.2 | PHP 8.1 - 8.5 +| Nette Application 3.1 | PHP 7.2 - 8.3 +| Nette Application 3.0 | PHP 7.1 - 8.0 +| Nette Application 2.4 | PHP 5.6 - 8.0 -Si applica all'ultima versione patch. +Vale per le ultime versioni patch. diff --git a/application/it/@left-menu.texy b/application/it/@left-menu.texy index 011cf72065..9db6fbd67b 100644 --- a/application/it/@left-menu.texy +++ b/application/it/@left-menu.texy @@ -1,22 +1,24 @@ Nette Application ***************** +- [Panoramica |@home] - [Come funzionano le applicazioni? |how-it-works] -- [Bootstrapping] -- [Presenter |presenters] -- [Template |templates] +- [Bootstrapping|bootstrapping] +- [Presenter|presenters] +- [Template|templates] - [Struttura delle directory |directory-structure] -- [Routing |routing] +- [Routing|routing] - [Creazione di link URL |creating-links] - [Componenti interattivi |components] -- [AJAX & snippet |ajax] +- [AJAX e snippet |ajax] - [Multiplier |multiplier] -- [Configurazione |configuration] +- [Configurazione|configuration] +- [Aggiornamento|upgrading] -Ulteriori letture -***************** -- [Perché usare Nette? |www:10-reasons-why-nette] +Letture consigliate +******************* +- [Perché usare Nette?|www:10-reasons-why-nette] - [Installazione |nette:installation] -- [Scriviamo la prima applicazione! |quickstart:] -- [Guide e procedure |best-practices:] +- [Crea la tua prima applicazione! |quickstart:] +- [Best practice |best-practices:] - [Risoluzione dei problemi |nette:troubleshooting] diff --git a/application/it/ajax.texy b/application/it/ajax.texy index 54c00466b6..0968c573d6 100644 --- a/application/it/ajax.texy +++ b/application/it/ajax.texy @@ -1,12 +1,12 @@ -AJAX & snippet +AJAX e snippet **************
    -Nell'era delle moderne applicazioni web, dove la funzionalità è spesso suddivisa tra server e browser, AJAX è un elemento di collegamento essenziale. Quali opzioni ci offre Nette Framework in questo campo? -- invio di parti del template, i cosiddetti snippet -- passaggio di variabili tra PHP e JavaScript -- strumenti per il debug delle richieste AJAX +Nell'era delle applicazioni web moderne, in cui le funzionalità sono spesso ripartite tra il server e il browser, AJAX è l'elemento di collegamento indispensabile. Quali possibilità offre Nette Framework in questo campo? +- l'invio di parti del template, i cosiddetti snippet +- il passaggio di variabili tra PHP e JavaScript +- strumenti per il debugging delle richieste AJAX
    @@ -14,9 +14,9 @@ Nell'era delle moderne applicazioni web, dove la funzionalità è spesso suddivi Richiesta AJAX ============== -Una richiesta AJAX non è fondamentalmente diversa da una classica richiesta HTTP. Viene chiamato un presenter con determinati parametri. E spetta al presenter decidere come reagire alla richiesta: può restituire dati in formato JSON, inviare una parte di codice HTML, un documento XML, ecc. +Una richiesta AJAX non differisce sostanzialmente da una classica richiesta HTTP. Viene chiamato un presenter con determinati parametri. Sta al presenter decidere come rispondere alla richiesta: può restituire dati in formato JSON, inviare una parte di codice HTML, un documento XML e così via. -Sul lato browser, inizializziamo la richiesta AJAX utilizzando la funzione `fetch()`: +Sul lato browser avviamo una richiesta AJAX con la funzione `fetch()`: ```js fetch(url, { @@ -24,22 +24,22 @@ fetch(url, { }) .then(response => response.json()) .then(payload => { - // elaborazione della risposta + // elabora la risposta }); ``` -Sul lato server, riconosciamo una richiesta AJAX con il metodo `$httpRequest->isAjax()` del servizio [incapsulando la richiesta HTTP |http:request]. Per il rilevamento utilizza l'header HTTP `X-Requested-With`, quindi è importante inviarlo. All'interno del presenter è possibile utilizzare il metodo `$this->isAjax()`. +Sul lato server una richiesta AJAX si riconosce con il metodo `$httpRequest->isAjax()` del servizio che [incapsula la richiesta HTTP |http:request]. Per il rilevamento usa l'header HTTP `X-Requested-With`, quindi è essenziale inviarlo. Dentro il presenter potete usare il metodo `$this->isAjax()`. -Se si desidera inviare dati in formato JSON, utilizzare il metodo [`sendJson()` |presenters#Invio della risposta]. Il metodo termina anche l'attività del presenter. +Se volete inviare dati in formato JSON, usate il metodo [`sendJson()` |presenters#Inviare una risposta]. Il metodo termina anche l'attività del presenter. ```php public function actionExport(): void { - $this->sendJson($this->model->getData); + $this->sendJson($this->model->getData()); } ``` -Se si prevede di rispondere con un template speciale progettato per AJAX, è possibile farlo come segue: +Se avete in programma di rispondere con un template speciale pensato per AJAX, potete farlo così: ```php public function handleClick($param): void @@ -55,35 +55,35 @@ public function handleClick($param): void Snippet ======= -Lo strumento più potente offerto da Nette per collegare il server al client sono gli snippet. Grazie ad essi, è possibile trasformare un'applicazione ordinaria in una AJAX con uno sforzo minimo e poche righe di codice. L'esempio Fifteen, il cui codice si trova su [GitHub |https://github.com/nette-examples/fifteen], dimostra come funziona il tutto. +Lo strumento più potente che Nette offre per collegare il server al client sono gli snippet. Con essi potete trasformare una normale applicazione in una applicazione AJAX con il minimo sforzo e poche righe di codice. L'esempio Fifteen mostra come funziona il tutto e il suo codice si trova su [GitHub |https://github.com/nette-examples/fifteen]. -Gli snippet, o frammenti, consentono di aggiornare solo parti della pagina invece di ricaricare l'intera pagina. Questo non solo è più veloce ed efficiente, ma offre anche un'esperienza utente più confortevole. Gli snippet potrebbero ricordarvi Hotwire per Ruby on Rails o Symfony UX Turbo. È interessante notare che Nette ha introdotto gli snippet già 14 anni prima. +Gli snippet permettono di aggiornare solo parti della pagina, invece di ricaricarla per intero. Non è solo più veloce ed efficiente, ma offre anche un'esperienza d'uso più comoda. Gli snippet vi ricorderanno forse Hotwire per Ruby on Rails o Symfony UX Turbo. È curioso che Nette abbia introdotto gli snippet 14 anni prima. -Come funzionano gli snippet? Al primo caricamento della pagina (richiesta non AJAX), viene caricata l'intera pagina, inclusi tutti gli snippet. Quando l'utente interagisce con la pagina (ad esempio, fa clic su un pulsante, invia un form, ecc.), viene attivata una richiesta AJAX invece di caricare l'intera pagina. Il codice nel presenter esegue l'azione e decide quali snippet devono essere aggiornati. Nette esegue il rendering di questi snippet e li invia come array in formato JSON. Il codice di gestione nel browser riceve gli snippet e li inserisce nuovamente nella pagina. Viene trasferito solo il codice degli snippet modificati, risparmiando larghezza di banda e accelerando il caricamento rispetto al trasferimento dell'intero contenuto della pagina. +Come funzionano gli snippet? Al primo caricamento della pagina (una richiesta non AJAX) viene caricata l'intera pagina, snippet compresi. Quando l'utente interagisce con la pagina (per esempio clicca un pulsante, invia un form ecc.), invece di ricaricare l'intera pagina viene avviata una richiesta AJAX. Il codice del presenter esegue l'azione e decide quali snippet vanno aggiornati. Nette disegna questi snippet e li invia come payload JSON contenente un array con gli snippet. Il codice che li gestisce nel browser inserisce poi gli snippet ricevuti nella pagina. Viene quindi trasferito solo il codice degli snippet cambiati, il che risparmia banda e accelera il caricamento rispetto al trasferimento dell'intero contenuto della pagina. Se nessuno snippet viene invalidato con `redrawControl()`, Nette restituisce l'intera pagina anche per una richiesta AJAX: gli snippet vengono inviati solo quando qualcosa è stato invalidato. Naja ---- -Per gestire gli snippet sul lato browser, viene utilizzata la [libreria Naja |https://naja.js.org]. [Installala |https://naja.js.org/#/guide/01-install-setup-naja] come pacchetto node.js (per l'uso con applicazioni Webpack, Rollup, Vite, Parcel e altre): +Per gestire gli snippet sul lato browser si usa la [libreria Naja |https://naja.js.org]. [Installatela |https://naja.js.org/#/guide/01-install-setup-naja] come pacchetto Node.js (per l'uso con bundler come Webpack, Rollup, Vite, Parcel e altri): ```shell npm install naja ``` -…o inseriscila direttamente nel template della pagina: +…oppure inseritela direttamente nel template della pagina: ```latte - + ``` -Innanzitutto, è necessario [inizializzare |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] la libreria: +Per prima cosa dovete [inizializzare |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] la libreria: ```js naja.initialize(); ``` -Per trasformare un link ordinario (segnale) o l'invio di un form in una richiesta AJAX, è sufficiente contrassegnare il link, il form o il pulsante pertinente con la classe `ajax`: +Per trasformare un normale link (segnale) o l'invio di un form in una richiesta AJAX, basta contrassegnare il link, il form o il pulsante con la classe `ajax`: ```latte Vai @@ -100,32 +100,34 @@ oppure ``` -Ridisegno degli snippet +Ridisegnare gli snippet ----------------------- -Ogni oggetto della classe [Control |components] (incluso il Presenter stesso) tiene traccia se ci sono state modifiche che richiedono il suo ridisegno. A tale scopo viene utilizzato il metodo `redrawControl()`: +Ogni oggetto della classe [Control |components] (compreso il presenter stesso) tiene traccia del fatto che siano avvenuti cambiamenti che ne richiedono il ridisegno. A questo scopo si usa il metodo `redrawControl()`: ```php public function handleLogin(string $user): void { - // dopo il login, è necessario ridisegnare la parte pertinente + // dopo il login è necessario ridisegnare la parte interessata $this->redrawControl(); // ... } ``` -Nette consente un controllo ancora più preciso su cosa deve essere ridisegnato. Il metodo menzionato può infatti accettare il nome dello snippet come argomento. È quindi possibile invalidare (cioè: forzare il ridisegno) a livello di parti del template. Se l'intero componente viene invalidato, verrà ridisegnato anche ogni suo snippet: +Nette permette un controllo ancora più fine su ciò che va ridisegnato. Il metodo può ricevere come argomento il nome dello snippet. È quindi possibile invalidare (cioè forzare il ridisegno) a livello di singole parti del template. Se viene invalidato l'intero componente, verranno ridisegnati anche tutti i suoi snippet: ```php // invalida lo snippet 'header' $this->redrawControl('header'); ``` +Potete anche annullare un'invalidazione in sospeso con il secondo parametro `$redraw`: chiamando `$this->redrawControl('header', redraw: false)` contrassegnate lo snippet come non bisognoso di ridisegno. La firma completa è `redrawControl(?string $snippet = null, bool $redraw = true)`. + Snippet in Latte ---------------- -L'uso degli snippet in Latte è estremamente facile. Per definire una parte del template come snippet, è sufficiente racchiuderla tra i tag `{snippet}` e `{/snippet}`: +Usare gli snippet in Latte è estremamente semplice. Per definire una parte del template come snippet basta racchiuderla tra i tag `{snippet}` e `{/snippet}`: ```latte {snippet header} @@ -133,9 +135,9 @@ L'uso degli snippet in Latte è estremamente facile. Per definire una parte del {/snippet} ``` -Lo snippet crea un elemento `
    ` nella pagina HTML con un `id` speciale generato. Quando lo snippet viene ridisegnato, il contenuto di questo elemento viene aggiornato. Pertanto, è necessario che al rendering iniziale della pagina vengano renderizzati anche tutti gli snippet, anche se potrebbero essere inizialmente vuoti. +Lo snippet crea nella pagina HTML un elemento `
    ` con un `id` speciale generato. Quando lo snippet viene ridisegnato, il contenuto di questo elemento viene aggiornato. È quindi necessario che al primo rendering della pagina vengano disegnati anche tutti gli snippet, anche se all'inizio potrebbero essere vuoti. -È possibile creare uno snippet con un elemento diverso da `
    ` utilizzando l'attributo n: +Potete creare uno snippet anche con un elemento diverso da `
    `, usando un n:attributo: ```latte
    @@ -144,10 +146,10 @@ Lo snippet crea un elemento `
    ` nella pagina HTML con un `id` speciale gener ``` -Aree di snippet ---------------- +Aree snippet +------------ -I nomi degli snippet possono anche essere espressioni: +I nomi degli snippet possono essere anche espressioni: ```latte {foreach $items as $id => $item} @@ -155,7 +157,9 @@ I nomi degli snippet possono anche essere espressioni: {/foreach} ``` -In questo modo vengono creati diversi snippet `item-0`, `item-1`, ecc. Se invalidassimo direttamente uno snippet dinamico (ad esempio `item-1`), non verrebbe ridisegnato nulla. Il motivo è che gli snippet funzionano davvero come ritagli e vengono renderizzati solo direttamente. Tuttavia, nel template non esiste effettivamente uno snippet chiamato `item-1`. Questo viene creato solo eseguendo il codice circostante lo snippet, cioè il ciclo foreach. Pertanto, contrassegniamo la parte del template che deve essere eseguita utilizzando il tag `{snippetArea}`: +Di per sé è un passaggio intermedio non funzionante: disegnato fuori da uno `{snippet}` o `{snippetArea}` statico, uno snippet dinamico emette un `E_USER_WARNING` con il messaggio *Dynamic snippets are allowed only inside static snippet/snippetArea.* Lo sistemiamo qui sotto. + +Così vengono creati diversi snippet come `item-0`, `item-1` ecc. Se invalidassimo direttamente uno snippet dinamico (per esempio `item-1`), non verrebbe ridisegnato nulla. Il motivo è che gli snippet funzionano davvero come estratti e vengono disegnati direttamente solo loro stessi. Nel template, però, tecnicamente non esiste alcuno snippet chiamato `item-1`: esso nasce solo quando viene eseguito il codice che circonda lo snippet, cioè il ciclo foreach. Contrassegniamo quindi con il tag `{snippetArea}` la parte del template che deve essere eseguita: ```latte
      @@ -165,16 +169,16 @@ In questo modo vengono creati diversi snippet `item-0`, `item-1`, ecc. Se invali
    ``` -E facciamo ridisegnare sia lo snippet stesso che l'intera area genitore: +E chiediamo il ridisegno sia del singolo snippet sia dell'intera area genitore: ```php $this->redrawControl('itemsContainer'); $this->redrawControl('item-1'); ``` -Allo stesso tempo, è consigliabile assicurarsi che l'array `$items` contenga solo gli elementi che devono essere ridisegnati. +Allo stesso tempo è opportuno assicurarsi che l'array `$items` contenga solo gli elementi da ridisegnare. -Se inseriamo un altro template nel template utilizzando il tag `{include}`, che contiene snippet, è necessario includere nuovamente l'inserimento del template in `snippetArea` e invalidarlo insieme allo snippet: +Se nel template principale includiamo con il tag `{include}` un altro template che contiene snippet, è necessario racchiudere di nuovo l'inclusione del template in uno `snippetArea` e invalidarlo insieme allo snippet: ```latte {snippetArea include} @@ -198,7 +202,7 @@ $this->redrawControl('item'); Snippet nei componenti ---------------------- -È possibile creare snippet anche nei [componenti |components] e Nette li ridisegnerà automaticamente. Ma c'è una limitazione: per ridisegnare gli snippet, chiama il metodo `render()` senza parametri. Quindi, il passaggio di parametri nel template non funzionerà: +Potete creare snippet dentro i [componenti|components] e Nette li ridisegnerà automaticamente. C'è però un limite: per ridisegnare gli snippet Nette chiama il metodo `render()` senza alcun parametro. Passare parametri nel template non funzionerà quindi: ```latte OK @@ -210,10 +214,10 @@ non funzionerà: ``` -Invio di dati utente --------------------- +Inviare dati personalizzati +--------------------------- -Insieme agli snippet, è possibile inviare al client qualsiasi altro dato. È sufficiente scriverli nell'oggetto `payload`: +Insieme agli snippet potete inviare al client qualsiasi dato aggiuntivo. Basta scriverlo nell'oggetto `payload`: ```php public function actionDelete(int $id): void @@ -226,10 +230,16 @@ public function actionDelete(int $id): void ``` -Passaggio di parametri -====================== +Redirect +-------- + +Durante una richiesta AJAX i metodi `redirect()` e `redirectUrl()` non inviano un redirect HTTP. Scrivono invece l'URL di destinazione nel payload (l'oggetto dati inviato nella risposta AJAX), precisamente nella sua proprietà `payload.redirect`, e lo inviano; il redirect vero e proprio viene poi eseguito dalla libreria lato client (Naja). + -Se inviamo parametri a un componente tramite una richiesta AJAX, siano essi parametri di segnale o parametri persistenti, dobbiamo specificare il loro nome globale nella richiesta, che include anche il nome del componente. Il nome completo del parametro viene restituito dal metodo `getParameterId()`. +Passare i parametri +=================== + +Quando inviamo dei parametri a un componente tramite una richiesta AJAX, siano essi parametri di segnale o parametri persistenti, dobbiamo indicarne nella richiesta il nome globale, che comprende il nome del componente. Il metodo `getParameterId()` restituisce il nome completo del parametro. ```js let url = new URL({link //foo!}); @@ -247,3 +257,9 @@ public function handleFoo(int $bar): void { } ``` + + +Letture consigliate +=================== + +- [Snippet dinamici |best-practices:dynamic-snippets] diff --git a/application/it/bootstrapping.texy b/application/it/bootstrapping.texy index 63e749dfe6..ab11b76910 100644 --- a/application/it/bootstrapping.texy +++ b/application/it/bootstrapping.texy @@ -3,19 +3,22 @@ Bootstrapping
    -Il bootstrapping è il processo di inizializzazione dell'ambiente dell'applicazione, creazione di un contenitore di dependency injection (DI) e avvio dell'applicazione. Discuteremo: +Il bootstrapping è il processo di inizializzazione dell'ambiente dell'applicazione, di creazione del container di dependency injection (DI) e di avvio dell'applicazione. Parleremo di: - come la classe Bootstrap inizializza l'ambiente -- come le applicazioni sono configurate utilizzando file NEON -- come distinguere tra modalità di produzione e sviluppo -- come creare e configurare il contenitore DI +- come si configurano le applicazioni con i file NEON +- come distinguere tra modalità di produzione e di sviluppo +- come creare e configurare il container DI
    -Le applicazioni, siano esse web o script eseguiti dalla riga di comando, iniziano la loro esecuzione con una qualche forma di inizializzazione dell'ambiente. In passato, questo compito era spesso affidato a un file chiamato, ad esempio, `include.inc.php`, che il file iniziale includeva. Nelle moderne applicazioni Nette, questo è stato sostituito dalla classe `Bootstrap`, che, come parte dell'applicazione, si trova nel file `app/Bootstrap.php`. Potrebbe assomigliare, ad esempio, a questo: +Le applicazioni, siano esse web o script eseguiti dalla riga di comando, iniziano la propria esecuzione con una qualche forma di inizializzazione dell'ambiente. In passato se ne occupava un file chiamato magari `include.inc.php`, incluso dal file iniziale. Nelle moderne applicazioni Nette è stato sostituito dalla classe `Bootstrap`, che, in quanto parte dell'applicazione, si trova nel file `app/Bootstrap.php`. Potrebbe avere per esempio questo aspetto: ```php +namespace App; + +use Nette; use Nette\Bootstrap\Configurator; class Bootstrap @@ -26,9 +29,9 @@ class Bootstrap public function __construct() { $this->rootDir = dirname(__DIR__); - // Il Configurator è responsabile dell'impostazione dell'ambiente e dei servizi dell'applicazione. + // il configurator si occupa di impostare l'ambiente dell'applicazione e i servizi. $this->configurator = new Configurator; - // Imposta la directory per i file temporanei generati da Nette (es. template compilati) + // imposta la directory dei file temporanei generati da Nette (per esempio i template compilati) $this->configurator->setTempDirectory($this->rootDir . '/temp'); } @@ -41,14 +44,14 @@ class Bootstrap private function initializeEnvironment(): void { - // Nette è intelligente e la modalità di sviluppo si attiva automaticamente, - // oppure puoi abilitarla per un indirizzo IP specifico decommentando la riga seguente: + // Nette è furbo e la modalità di sviluppo si attiva da sola, + // oppure potete attivarla per un determinato indirizzo IP togliendo il commento alla riga seguente: // $this->configurator->setDebugMode('secret@23.75.345.200'); - // Attiva Tracy: l'ultimo "coltellino svizzero" per il debug. + // attiva Tracy: il "coltellino svizzero" definitivo per il debugging. $this->configurator->enableTracy($this->rootDir . '/log'); - // RobotLoader: carica automaticamente tutte le classi nella directory selezionata + // RobotLoader: carica automaticamente tutte le classi della directory scelta $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); @@ -56,7 +59,7 @@ class Bootstrap private function setupContainer(): void { - // Carica i file di configurazione + // carica i file di configurazione $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } @@ -66,91 +69,100 @@ class Bootstrap index.php ========= -Il file iniziale nel caso delle applicazioni web è `index.php`, che si trova nella [directory pubblica |directory-structure#Directory pubblica www] `www/`. Questo file fa inizializzare l'ambiente e creare il container DI dalla classe Bootstrap. Successivamente, ottiene il servizio `Application` da esso, che avvia l'applicazione web: +Nel caso delle applicazioni web il file iniziale è `index.php`, che si trova nella [directory pubblica |directory-structure#Directory pubblica www/] `www/`. Chiede alla classe Bootstrap di inizializzare l'ambiente e di creare il container DI. Poi ottiene dal container il servizio `Application`, che esegue l'applicazione web: ```php $bootstrap = new App\Bootstrap; -// Inizializzazione dell'ambiente + creazione del container DI +// inizializza l'ambiente e crea il container DI $container = $bootstrap->bootWebApplication(); -// Il container DI crea l'oggetto Nette\Application\Application +// il container DI crea un oggetto Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// Avvio dell'applicazione Nette ed elaborazione della richiesta in arrivo +// avvia l'applicazione Nette ed elabora la richiesta in arrivo $application->run(); ``` -Come si può vedere, la classe [api:Nette\Bootstrap\Configurator] aiuta con l'impostazione dell'ambiente e la creazione del container di dependency injection (DI), che ora presenteremo più in dettaglio. +.[note] +Elaborando la richiesta, l'oggetto `$application` emette [eventi |nette:glossary#Eventi]: `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown` e `onError` (in caso di eccezione non gestita). Potete agganciarvi dei gestori, il che torna comodo per il logging o per il monitoraggio dell'intera applicazione. +Come vedete, la classe [api:Nette\Bootstrap\Configurator] aiuta a impostare l'ambiente e a creare il container di dependency injection (DI). Ve la presentiamo ora più in dettaglio. -Modalità Sviluppo vs Produzione -=============================== -Nette si comporta diversamente a seconda che sia in esecuzione su un server di sviluppo o di produzione: +Modalità di sviluppo e di produzione +==================================== + +Nette si comporta in modo diverso a seconda che giri su un server di sviluppo o di produzione: -🛠️ Modalità Sviluppo (Development): - - Mostra la debugbar di Tracy con informazioni utili (query SQL, tempo di esecuzione, memoria utilizzata) - - In caso di errore, mostra una pagina di errore dettagliata con le chiamate alle funzioni e il contenuto delle variabili - - Aggiorna automaticamente la cache quando vengono modificati i template Latte, i file di configurazione, ecc. +🛠️ Modalità di sviluppo: + - mostra la barra di debug di Tracy con informazioni utili (query SQL, tempo di esecuzione, memoria usata) + - in caso di errore mostra una pagina di errore dettagliata, con le chiamate di funzione e il contenuto delle variabili + - aggiorna automaticamente la cache quando cambiano i template Latte, i file di configurazione ecc. -🚀 Modalità Produzione (Production): - - Non mostra alcuna informazione di debug, tutti gli errori vengono scritti nel log - - In caso di errore, mostra ErrorPresenter o una pagina generica "Server Error" - - La cache non viene mai aggiornata automaticamente! - - Ottimizzato per velocità e sicurezza +🚀 Modalità di produzione: + - non mostra alcuna informazione di debug; tutti gli errori vengono scritti nel log + - in caso di errore mostra un ErrorPresenter oppure una pagina generica "Server Error" + - la cache non viene mai aggiornata automaticamente! + - ottimizzata per velocità e sicurezza -La selezione della modalità avviene tramite autodetect, quindi di solito non è necessario configurare nulla o passare manualmente: +La scelta della modalità avviene per rilevamento automatico, quindi di norma non c'è bisogno di configurare nulla né di cambiare modalità manualmente: -- modalità sviluppo: su localhost (indirizzo IP `127.0.0.1` o `::1`) se non è presente un proxy (cioè il suo header HTTP) -- modalità produzione: ovunque altrove +- modalità di sviluppo: su localhost (indirizzo IP `127.0.0.1` o `::1`) se non è presente un proxy (cioè se non viene rilevato il suo header HTTP) +- modalità di produzione: ovunque altrove -Se vogliamo abilitare la modalità di sviluppo anche in altri casi, ad esempio per i programmatori che accedono da un indirizzo IP specifico, utilizziamo `setDebugMode()`: +Se vogliamo attivare la modalità di sviluppo anche in altri casi, per esempio per i programmatori che accedono da un determinato indirizzo IP, usiamo `setDebugMode()`: ```php -$this->configurator->setDebugMode('23.75.345.200'); // è possibile specificare anche un array di indirizzi IP +$this->configurator->setDebugMode('23.75.345.200'); // si può indicare anche un array di indirizzi IP ``` -Consigliamo vivamente di combinare l'indirizzo IP con un cookie. Memorizziamo un token segreto nel cookie `nette-debug`, ad esempio `secret1234`, e in questo modo attiviamo la modalità di sviluppo per i programmatori che accedono da un indirizzo IP specifico e che hanno anche il token menzionato nel cookie: +Consigliamo vivamente di combinare l'indirizzo IP con un cookie. Salvate un token segreto, per esempio `secret1234`, nel cookie `nette-debug` e attivate così la modalità di sviluppo per i programmatori che accedono da un determinato indirizzo IP e che hanno anche il token indicato nel cookie: ```php $this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Possiamo anche disattivare completamente la modalità di sviluppo, anche per localhost: +Possiamo anche disattivare del tutto la modalità di sviluppo, perfino su localhost: ```php $this->configurator->setDebugMode(false); ``` -Attenzione, il valore `true` attiva forzatamente la modalità di sviluppo, cosa che non deve mai accadere su un server di produzione. +Attenzione: il valore `true` forza l'attivazione della modalità di sviluppo, cosa che su un server di produzione non deve **mai** accadere. +Del rilevamento automatico si occupa internamente il metodo statico `Configurator::detectDebugMode()`, che potete chiamare anche voi, per esempio per rilevare la modalità di sviluppo fuori dal configurator. Accetta un elenco facoltativo di indirizzi IP o di nomi di computer autorizzati e restituisce se la richiesta corrente deve girare in modalità di sviluppo: -Strumento di Debug Tracy -======================== +```php +$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200'); +``` -Per un facile debug, attiviamo anche l'ottimo strumento [Tracy |tracy:]. In modalità sviluppo, visualizza gli errori e in modalità produzione, registra gli errori nella directory specificata: + +Lo strumento di debug Tracy +=========================== + +Per un debugging semplice attiviamo l'eccellente strumento [Tracy |tracy:]. In modalità di sviluppo visualizza gli errori, in modalità di produzione li registra nella directory indicata: ```php $this->configurator->enableTracy($this->rootDir . '/log'); ``` -File Temporanei +File temporanei =============== -Nette utilizza la cache per il container DI, RobotLoader, template, ecc. Pertanto, è necessario impostare il percorso della directory in cui verrà memorizzata la cache: +Nette usa la cache per il container DI, per RobotLoader, per i template ecc. È quindi necessario impostare il percorso della directory in cui la cache verrà salvata: ```php $this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` -Su Linux o macOS, imposta i [permessi di scrittura |nette:troubleshooting#Impostazione dei permessi delle directory] per le directory `log/` e `temp/`. +Su Linux o macOS impostate i [permessi di scrittura |nette:troubleshooting#Impostazione dei permessi delle directory] per le directory `log/` e `temp/`. RobotLoader =========== -Di solito, vorremo caricare automaticamente le classi utilizzando [RobotLoader |robot-loader:], quindi dobbiamo avviarlo e fargli caricare le classi dalla directory in cui si trova `Bootstrap.php` (cioè `__DIR__`), e da tutte le sottodirectory: +Di solito vorremo caricare automaticamente le classi con [RobotLoader |robot-loader:], quindi dobbiamo avviarlo e fargli caricare le classi dalla directory in cui si trova `Bootstrap.php` (cioè `__DIR__`) e da tutte le sue sottodirectory: ```php $this->configurator->createRobotLoader() @@ -158,36 +170,38 @@ $this->configurator->createRobotLoader() ->register(); ``` -Un approccio alternativo è far caricare le classi solo tramite [Composer |best-practices:composer] rispettando PSR-4. +Un approccio alternativo è caricare le classi esclusivamente tramite [Composer |best-practices:composer], secondo PSR-4. -Timezone -======== +Fuso orario +=========== -Tramite il configuratore è possibile impostare il fuso orario predefinito. +Tramite il configurator potete impostare il fuso orario predefinito. ```php $this->configurator->setTimeZone('Europe/Prague'); ``` -Configurazione del Container DI +Configurazione del container DI =============================== -Parte del processo di avvio è la creazione del container DI, ovvero la factory di oggetti, che è il cuore dell'intera applicazione. Si tratta in realtà di una classe PHP generata da Nette e salvata nella directory della cache. La factory produce gli oggetti chiave dell'applicazione e, tramite i file di configurazione, le istruiamo su come crearli e impostarli, influenzando così il comportamento dell'intera applicazione. +Parte del processo di avvio è la creazione del container DI, cioè della factory di oggetti, che è il cuore dell'intera applicazione. È in realtà una classe PHP generata da Nette e salvata nella directory della cache. La factory produce gli oggetti chiave dell'applicazione e con i file di configurazione le diciamo come crearli e impostarli, influenzando così il comportamento dell'intera applicazione. -I file di configurazione sono solitamente scritti nel formato [NEON |neon:format]. In un capitolo separato, imparerai [cosa può essere configurato |nette:configuring]. +I file di configurazione si scrivono di norma nel [formato NEON |neon:format]. In un capitolo a parte potete leggere [cosa si può configurare |nette:configuring]. .[tip] -In modalità sviluppo, il container viene aggiornato automaticamente ad ogni modifica del codice o dei file di configurazione. In modalità produzione, viene generato solo una volta e le modifiche non vengono controllate per massimizzare le prestazioni. +In modalità di sviluppo il container viene aggiornato automaticamente ogni volta che cambiano il codice o i file di configurazione. In modalità di produzione viene generato una sola volta e le modifiche non vengono controllate, per massimizzare le prestazioni. + +Mentre `createContainer()` costruisce il container e ne restituisce l'istanza, il metodo `loadContainer()` restituisce solo il nome della classe del container generata, che potete poi istanziare voi. È utile negli scenari avanzati. -Carichiamo i file di configurazione utilizzando `addConfig()`: +I file di configurazione si caricano con `addConfig()`: ```php $this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` -Se vogliamo aggiungere più file di configurazione, possiamo chiamare la funzione `addConfig()` più volte. +Se vogliamo aggiungere altri file di configurazione, possiamo chiamare la funzione `addConfig()` più volte. ```php $configDir = $this->rootDir . '/config'; @@ -198,17 +212,17 @@ if (PHP_SAPI === 'cli') { } ``` -Il nome `cli.php` non è un errore di battitura, la configurazione può anche essere scritta in un file PHP che la restituisce come array. +Il nome `cli.php` non è un errore di battitura: la configurazione si può scrivere anche in un file PHP che la restituisce come array. -Possiamo anche aggiungere altri file di configurazione nella [sezione `includes` |dependency-injection:configuration#Inclusione di file]. +Possiamo aggiungere altri file di configurazione anche nella [sezione `includes` |dependency-injection:configuration#Inclusione di file]. -Se nei file di configurazione compaiono elementi con le stesse chiavi, verranno sovrascritti o, nel caso di [array, uniti |dependency-injection:configuration#Unione]. Il file incluso successivamente ha una priorità maggiore rispetto al precedente. Il file in cui è specificata la sezione `includes` ha una priorità maggiore rispetto ai file inclusi in esso. +Se nei file di configurazione compaiono elementi con le stesse chiavi, essi verranno sovrascritti oppure, nel caso degli [array, uniti |dependency-injection:configuration#Unione]. Un file incluso più tardi ha priorità maggiore rispetto al precedente. Il file in cui è indicata la sezione `includes` ha priorità maggiore rispetto ai file inclusi al suo interno. -Parametri Statici +Parametri statici ----------------- -I parametri utilizzati nei file di configurazione possono essere definiti [nella sezione `parameters` |dependency-injection:configuration#Parametri] e anche passati (o sovrascritti) con il metodo `addStaticParameters()` (ha l'alias `addParameters()`). È importante notare che valori diversi dei parametri causeranno la generazione di ulteriori container DI, ovvero ulteriori classi. +I parametri usati nei file di configurazione si possono definire [nella sezione `parameters` |dependency-injection:configuration#Parametri] e anche passare (o sovrascrivere) con il metodo `addStaticParameters()` (il cui vecchio alias, ora deprecato, è `addParameters()`). È importante sapere che valori diversi dei parametri provocano la generazione di altri container DI, cioè di altre classi. ```php $this->configurator->addStaticParameters([ @@ -216,13 +230,13 @@ $this->configurator->addStaticParameters([ ]); ``` -Al parametro `projectId` si può fare riferimento nella configurazione con la consueta notazione `%projectId%`. +Nella configurazione si può fare riferimento al parametro `projectId` con la notazione consueta `%projectId%`. -Parametri Dinamici +Parametri dinamici ------------------ -Possiamo aggiungere al container anche parametri dinamici, i cui valori diversi, a differenza dei parametri statici, non causano la generazione di nuovi container DI. +Al container possiamo aggiungere anche parametri dinamici, i cui valori diversi, a differenza dei parametri statici, non provocano la generazione di nuovi container DI. ```php $this->configurator->addDynamicParameters([ @@ -230,7 +244,7 @@ $this->configurator->addDynamicParameters([ ]); ``` -In questo modo possiamo aggiungere semplicemente, ad esempio, variabili d'ambiente, a cui si può fare riferimento nella configurazione con la notazione `%env.variable%`. +Così possiamo aggiungere facilmente, per esempio, le variabili d'ambiente, alle quali si può poi fare riferimento nella configurazione con la notazione `%env.variabile%`. ```php $this->configurator->addDynamicParameters([ @@ -239,24 +253,25 @@ $this->configurator->addDynamicParameters([ ``` -Parametri Predefiniti +Parametri predefiniti --------------------- -Nei file di configurazione è possibile utilizzare questi parametri statici: +Nei file di configurazione potete usare questi parametri: -- `%appDir%` è il percorso assoluto alla directory contenente il file `Bootstrap.php` -- `%wwwDir%` è il percorso assoluto alla directory contenente il file di input `index.php` -- `%tempDir%` è il percorso assoluto alla directory per i file temporanei -- `%vendorDir%` è il percorso assoluto alla directory in cui Composer installa le librerie -- `%rootDir%` è il percorso assoluto alla directory principale del progetto +- `%appDir%` è il percorso assoluto della directory che contiene il file `Bootstrap.php` +- `%wwwDir%` è il percorso assoluto della directory che contiene il file d'ingresso `index.php` +- `%tempDir%` è il percorso assoluto della directory dei file temporanei +- `%vendorDir%` è il percorso assoluto della directory in cui Composer installa le librerie +- `%rootDir%` è il percorso assoluto della directory radice del progetto +- `%baseUrl%` è l'URL assoluto della directory radice (un parametro dinamico, risolto in fase di esecuzione) - `%debugMode%` indica se l'applicazione è in modalità debug -- `%consoleMode%` indica se la richiesta è arrivata tramite la riga di comando +- `%consoleMode%` indica se la richiesta è arrivata dalla riga di comando -Servizi Importati +Servizi importati ----------------- -Ora andiamo più a fondo. Sebbene lo scopo del container DI sia quello di creare oggetti, eccezionalmente potrebbe sorgere la necessità di inserire un oggetto esistente nel container. Lo facciamo definendo il servizio con il flag `imported: true`. +Ora andiamo più a fondo. Benché lo scopo del container DI sia creare oggetti, di tanto in tanto può nascere l'esigenza di inserire nel container un oggetto già esistente. Lo facciamo definendo il servizio con il flag `imported: true`. ```neon services: @@ -274,10 +289,10 @@ $this->configurator->addServices([ ``` -Ambienti Diversi +Ambienti diversi ================ -Non aver paura di modificare la classe Bootstrap secondo le tue esigenze. Puoi aggiungere parametri al metodo `bootWebApplication()` per distinguere i progetti web. Oppure possiamo aggiungere altri metodi, ad esempio `bootTestEnvironment()`, che inizializza l'ambiente per i test unitari, `bootConsoleApplication()` per gli script chiamati dalla riga di comando, ecc. +Modificate pure la classe `Bootstrap` secondo le vostre esigenze. Potete aggiungere parametri al metodo `bootWebApplication()` per distinguere tra progetti web. Oppure possiamo aggiungere altri metodi, come `bootTestEnvironment()`, che inizializza l'ambiente per i test unitari, `bootConsoleApplication()` per gli script chiamati dalla riga di comando ecc. ```php public function bootTestEnvironment(): Nette\DI\Container diff --git a/application/it/components.texy b/application/it/components.texy index 2d21975f48..fe52021058 100644 --- a/application/it/components.texy +++ b/application/it/components.texy @@ -1,9 +1,9 @@ -Componenti Interattivi +Componenti interattivi **********************
    -I componenti sono oggetti riutilizzabili indipendenti che inseriamo nelle pagine. Possono essere form, datagrid, sondaggi, praticamente qualsiasi cosa che abbia senso usare ripetutamente. Vedremo: +I componenti sono oggetti riutilizzabili autonomi che incorporiamo nelle pagine. Possono essere form, datagrid, sondaggi, in pratica tutto ciò che ha senso usare più volte. Vi mostreremo: - come usare i componenti? - come scriverli? @@ -11,19 +11,19 @@ I componenti sono oggetti riutilizzabili indipendenti che inseriamo nelle pagine
    -Nette ha un sistema di componenti integrato. Qualcosa di simile potrebbe essere familiare ai veterani di Delphi o ASP.NET Web Forms, qualcosa di lontanamente simile è alla base di React o Vue.js. Tuttavia, nel mondo dei framework PHP, è una caratteristica unica. +Nette ha un sistema di componenti integrato. Qualcosa di simile sarà familiare ai veterani di Delphi o di ASP.NET Web Forms; React o Vue.js si basano su qualcosa di vagamente affine. Nel mondo dei framework PHP, però, è una funzionalità unica. -Eppure, i componenti influenzano fondamentalmente l'approccio alla creazione di applicazioni. Puoi comporre le pagine da unità pre-preparate. Hai bisogno di un datagrid nell'amministrazione? Lo trovi su [Componette |https://componette.org/search/component], un repository di add-on open-source (quindi non solo componenti) per Nette, e lo inserisci semplicemente nel presenter. +Allo stesso tempo i componenti influenzano profondamente il modo di sviluppare le applicazioni. Potete comporre le pagine a partire da unità già pronte. Vi serve una datagrid nella vostra amministrazione? Cercatela su [Componette |https://componette.org/search/component], un repository di estensioni open source (non solo componenti) per Nette, e inseritela semplicemente nel presenter. -Puoi incorporare un numero qualsiasi di componenti in un presenter. E in alcuni componenti puoi inserire altri componenti. Questo crea un albero di componenti, la cui radice è il presenter. +Nel presenter potete incorporare quanti componenti volete. E dentro alcuni componenti potete incorporarne altri. Nasce così un albero di componenti, con il presenter come radice. -Metodi Factory +Metodi factory ============== -Come vengono inseriti i componenti nel presenter e successivamente utilizzati? Di solito tramite metodi factory. +Come si inseriscono i componenti nel presenter e come li si usa poi? Di solito tramite metodi factory. -Una factory di componenti è un modo elegante per creare componenti solo quando sono effettivamente necessari (lazy / on demand). L'intera magia sta nell'implementare un metodo chiamato `createComponent()`, dove `` è il nome del componente da creare, che crea e restituisce il componente. +Una factory di componenti è un modo elegante di creare i componenti solo quando servono davvero (lazy / on demand). Tutta la magia sta nell'implementare un metodo chiamato `createComponent()`, dove `` è il nome del componente da creare, che crea e restituisce il componente. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -31,27 +31,27 @@ class DefaultPresenter extends Nette\Application\UI\Presenter protected function createComponentPoll(): PollControl { $poll = new PollControl; - $poll->items = $this->item; + $poll->items = $this->items; return $poll; } } ``` -Grazie al fatto che tutti i componenti vengono creati in metodi separati, il codice guadagna in chiarezza. +Poiché tutti i componenti vengono creati in metodi separati, il codice risulta più chiaro. .[note] -I nomi dei componenti iniziano sempre con una lettera minuscola, anche se sono scritti con una lettera maiuscola nel nome del metodo. +I nomi dei componenti iniziano sempre con una lettera minuscola, anche se nel nome del metodo sono scritti con la maiuscola. -Le factory non vengono mai chiamate direttamente; vengono chiamate automaticamente la prima volta che utilizziamo il componente. Grazie a ciò, il componente viene creato al momento giusto e solo quando è effettivamente necessario. Se non utilizziamo il componente (ad esempio, durante una richiesta AJAX in cui viene trasferita solo una parte della pagina, o durante la cache del template), non viene creato affatto e risparmiamo le prestazioni del server. +Non chiamiamo mai le factory direttamente: vengono chiamate automaticamente la prima volta che usiamo il componente. Grazie a questo il componente viene creato al momento giusto e solo se serve davvero. Se non usiamo il componente (per esempio durante una richiesta AJAX in cui viene trasferita solo una parte della pagina, oppure quando il template è in cache), non verrà creato affatto, risparmiando prestazioni del server. ```php .{file:DefaultPresenter.php} -// accediamo al componente e se è la prima volta, -// viene chiamato createComponentPoll() che lo crea +// accediamo al componente e, se è la prima volta, +// viene chiamato createComponentPoll(), che lo crea $poll = $this->getComponent('poll'); // sintassi alternativa: $poll = $this['poll']; ``` -Nel template, è possibile renderizzare un componente utilizzando il tag [{control} |#Rendering]. Pertanto, non è necessario passare manualmente i componenti al template. +Nel template è possibile disegnare un componente con il tag [{control} |#Rendering]. Non c'è quindi bisogno di passare manualmente i componenti al template. ```latte

    Vota

    @@ -59,21 +59,26 @@ Nel template, è possibile renderizzare un componente utilizzando il tag [{contr {control poll} ``` +.[tip] +Per creare dinamicamente un numero variabile di componenti usate [Multiplier |multiplier]. -Stile Hollywood -=============== +I metodi factory `createComponent()` non funzionano solo nei presenter. Allo stesso modo potete annidare un componente dentro un altro componente, componendoli in un albero: comodo per esempio per un form disegnato separatamente dentro un componente. -I componenti utilizzano comunemente una tecnica fresca che ci piace chiamare Stile Hollywood. Sicuramente conosci la frase famosa che i partecipanti ai casting cinematografici sentono così spesso: "Non chiamateci, vi chiameremo noi". Ed è proprio di questo che si tratta. -In Nette, invece di dover chiedere costantemente qualcosa ("il form è stato inviato?", "era valido?" o "l'utente ha premuto questo pulsante?"), dici al framework "quando succede, chiama questo metodo" e lasci il resto del lavoro a lui. Se programmi in JavaScript, conosci intimamente questo stile di programmazione. Scrivi funzioni che vengono chiamate quando si verifica un certo evento. E il linguaggio passa loro i parametri appropriati. +Stile hollywoodiano +=================== -Questo cambia completamente la prospettiva sulla scrittura delle applicazioni. Più compiti puoi lasciare al framework, meno lavoro hai tu. E meno cose puoi dimenticare. +I componenti usano abitualmente una tecnica fresca che ci piace chiamare stile hollywoodiano. Conoscete di sicuro il cliché che sentono spesso i partecipanti ai provini cinematografici: "Non chiamateci, vi chiameremo noi." Ed è esattamente di questo che si tratta. +In Nette, invece di dover porre continuamente domande ("il form è stato inviato?", "era valido?", "l'utente ha premuto questo pulsante?"), dite al framework "quando succede questo, chiama questo metodo" e gli lasciate il resto del lavoro. Se programmate in JavaScript conoscete benissimo questo stile di programmazione: scrivete funzioni che vengono chiamate quando si verifica un certo evento. E il linguaggio passa loro i parametri appropriati. -Scrivere un Componente +Questo cambia completamente la prospettiva sullo scrivere applicazioni. Più compiti riuscite a lasciare al framework, meno lavoro avete. E meno cose potete trascurare. + + +Scrivere un componente ====================== -Con il termine componente, di solito intendiamo un discendente della classe [api:Nette\Application\UI\Control]. (Sarebbe quindi più preciso usare il termine "controlli", ma "controlli" ha un significato completamente diverso in italiano e "componenti" si è affermato di più.) Il presenter stesso [api:Nette\Application\UI\Presenter] è, tra l'altro, anche un discendente della classe `Control`. +Con il termine componente intendiamo di norma un discendente della classe [api:Nette\Application\UI\Control]. (Sarebbe più preciso usare il termine "control", ma in alcune lingue ha un significato diverso e "componente" si è affermato di più.) Anche il presenter [api:Nette\Application\UI\Presenter] è un discendente della classe `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -87,19 +92,19 @@ class PollControl extends Control Rendering ========= -Sappiamo già che per renderizzare un componente si usa il tag `{control componentName}`. Questo in realtà chiama il metodo `render()` del componente, in cui ci occupiamo del rendering. Abbiamo a disposizione, proprio come nel presenter, un [template Latte|templates] nella variabile `$this->template`, a cui passiamo i parametri. A differenza del presenter, dobbiamo specificare il file del template e farlo renderizzare: +Sappiamo già che per disegnare un componente si usa il tag `{control nomeComponente}`. In realtà esso chiama il metodo `render()` del componente, nel quale ci occupiamo del rendering. Abbiamo a disposizione, come nel presenter, un [template Latte|templates] nella variabile `$this->template`, alla quale passiamo i parametri. A differenza del presenter, dobbiamo indicare il file del template e farlo disegnare: ```php .{file:PollControl.php} public function render(): void { - // inseriamo alcuni parametri nel template + // inserisce alcuni parametri nel template $this->template->param = $value; - // e lo renderizziamo + // e lo disegna $this->template->render(__DIR__ . '/poll.latte'); } ``` -Il tag `{control}` consente di passare parametri al metodo `render()`: +Il tag `{control}` permette di passare parametri al metodo `render()`: ```latte {control poll $id, $message} @@ -112,7 +117,7 @@ public function render(int $id, string $message): void } ``` -A volte un componente può essere composto da più parti che vogliamo renderizzare separatamente. Per ognuna di esse, creiamo il nostro metodo di rendering, qui nell'esempio `renderPaginator()`: +A volte un componente può essere composto da più parti che vogliamo disegnare separatamente. Per ognuna di esse creiamo un proprio metodo di rendering, qui nell'esempio `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +126,69 @@ public function renderPaginator(): void } ``` -E nel template, lo chiamiamo poi usando: +E nel template lo richiamiamo poi così: ```latte {control poll:paginator} ``` -Per una migliore comprensione, è utile sapere come questo tag viene tradotto in PHP. +Per capire meglio è utile sapere come questo tag si traduce in codice PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -viene tradotto come: +si traduce in: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -Il metodo `getComponent()` restituisce il componente `poll` e su questo componente chiama il metodo `render()`, rispettivamente `renderPaginator()` se nel tag dopo i due punti è specificato un modo di rendering diverso. +Il metodo `getComponent()` restituisce il componente `poll` e su questo componente viene chiamato il metodo `render()`, oppure `renderPaginator()` se nel tag, dopo i due punti, è indicato un metodo di rendering diverso. .[caution] -Attenzione, se da qualche parte nei parametri compare **`=>`**, tutti i parametri verranno impacchettati in un array e passati come primo argomento: +Attenzione: se nei parametri compare **`=>`** fuori dalle parentesi quadre, tutti i parametri verranno racchiusi in un array e passati come primo argomento: ```latte {control poll, id: 123, message: 'hello'} ``` -viene tradotto come: +si traduce in: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Rendering di un sub-componente: +Rendering di un sottocomponente: ```latte {control cartControl-someForm} ``` -viene tradotto come: +si traduce in: ```php $control->getComponent("cartControl-someForm")->render(); ``` -I componenti, come i presenter, passano automaticamente diverse variabili utili ai template: +I componenti, come i presenter, passano automaticamente ai template alcune variabili utili: -- `$basePath` è il percorso URL assoluto alla directory principale (es. `/eshop`) -- `$baseUrl` è l'URL assoluto alla directory principale (es. `http://localhost/eshop`) -- `$user` è l'oggetto [che rappresenta l'utente |security:authentication] +- `$basePath` è il percorso URL assoluto della directory radice (per esempio `/eshop`) +- `$baseUrl` è l'URL assoluto della directory radice (per esempio `http://localhost/eshop`) +- `$user` è un oggetto che [rappresenta l'utente |security:authentication] - `$presenter` è il presenter corrente - `$control` è il componente corrente -- `$flashes` è l'array di [messaggi |#Messaggi flash] inviati dalla funzione `flashMessage()` +- `$flashes` è un array dei [messaggi |#Messaggi flash] inviati dalla funzione `flashMessage()` Segnale ======= -Sappiamo già che la navigazione in un'applicazione Nette consiste nel collegare o reindirizzare a coppie `Presenter:action`. Ma cosa succede se vogliamo solo eseguire un'azione sulla **pagina corrente**? Ad esempio, cambiare l'ordinamento delle colonne in una tabella; eliminare un elemento; passare alla modalità chiaro/scuro; inviare un form; votare in un sondaggio; ecc. +Sappiamo già che la navigazione in un'applicazione Nette consiste nel collegare o nel reindirizzare verso coppie `Presenter:azione`. Ma cosa succede se vogliamo solo eseguire un'azione sulla **pagina corrente**? Per esempio cambiare l'ordinamento delle colonne di una tabella, eliminare un elemento, passare dalla modalità chiara a quella scura, inviare un form, votare in un sondaggio e così via. -Questo tipo di richiesta è chiamato segnale. E proprio come le azioni invocano i metodi `action()` o `render()`, i segnali chiamano i metodi `handle()`. Mentre il concetto di azione (o view) è legato puramente ai presenter, i segnali riguardano tutti i componenti. E quindi anche i presenter, perché `UI\Presenter` è un discendente di `UI\Control`. +Questo tipo di richiesta si chiama segnale. E come le azioni richiamano i metodi `action()` o `render()`, i segnali chiamano i metodi `handle()`. Mentre il concetto di azione (o vista) riguarda esclusivamente i presenter, i segnali riguardano tutti i componenti. E quindi anche i presenter, perché `UI\Presenter` è un discendente di `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +197,36 @@ public function handleClick(int $x, int $y): void } ``` -Un link che chiama un segnale viene creato nel modo consueto, cioè nel template con l'attributo `n:href` o il tag `{link}`, nel codice con il metodo `link()`. Maggiori informazioni nel capitolo [Creazione di link URL |creating-links#Link a segnali]. +Un link che chiama un segnale si crea nel modo consueto, cioè nel template con l'attributo `n:href` o con il tag `{link}`, nel codice con il metodo `link()`. Maggiori informazioni nel capitolo [Creazione di link URL |creating-links#Link a un segnale]. ```latte clicca qui ``` -Un segnale viene sempre chiamato sul presenter e sull'azione correnti, non è possibile invocarlo su un altro presenter o un'altra azione. +Un segnale viene sempre chiamato sul presenter e sull'azione correnti; non è possibile richiamarlo su un altro presenter o su un'altra azione. -Quindi, un segnale provoca il ricaricamento della pagina proprio come nella richiesta originale, ma in più chiama il metodo di gestione del segnale con i parametri appropriati. Se il metodo non esiste, viene lanciata un'eccezione [api:Nette\Application\UI\BadSignalException], che viene mostrata all'utente come una pagina di errore 403 Forbidden. +Un segnale provoca quindi il ricaricamento della pagina esattamente come la richiesta originale, ma in più chiama il metodo di gestione del segnale con i parametri appropriati. Se il metodo non esiste, viene sollevata l'eccezione [api:Nette\Application\UI\BadSignalException], che viene mostrata all'utente come pagina di errore 403 Forbidden. Snippet e AJAX ============== -I segnali potrebbero ricordarvi un po' AJAX: gestori che vengono invocati sulla pagina corrente. E avete ragione, i segnali vengono infatti spesso chiamati tramite AJAX e successivamente vengono trasferite al browser solo le parti modificate della pagina. Ovvero i cosiddetti snippet. Maggiori informazioni si trovano sulla [pagina dedicata ad AJAX |ajax]. +I segnali vi ricorderanno un po' AJAX: handler richiamati sulla pagina corrente. E avete ragione, i segnali vengono davvero chiamati spesso tramite AJAX e, di conseguenza, al browser vengono trasferite solo le parti della pagina che sono cambiate. Sono i cosiddetti snippet. Maggiori informazioni si trovano nella [pagina dedicata ad AJAX |ajax]. Messaggi flash ============== -Un componente ha il proprio storage di messaggi flash indipendente dal presenter. Si tratta di messaggi che, ad esempio, informano sul risultato di un'operazione. Una caratteristica importante dei messaggi flash è che sono disponibili nel template anche dopo un redirect. Anche dopo essere stati visualizzati, rimangono attivi per altri 30 secondi – ad esempio, nel caso in cui l'utente aggiorni la pagina a causa di un errore di trasmissione - il messaggio non scomparirà immediatamente. +Un componente ha un proprio archivio di messaggi flash, indipendente da quello del presenter. Sono messaggi che, per esempio, informano sull'esito di un'operazione. Una caratteristica importante dei messaggi flash è che sono disponibili nel template anche dopo un redirect. Anche dopo essere stati mostrati restano attivi per altri 30 secondi, per esempio nel caso in cui l'utente ricarichi la pagina per un errore di trasmissione: il messaggio non sparisce subito. -L'invio è gestito dal metodo [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Il primo parametro è il testo del messaggio o un oggetto `stdClass` che rappresenta il messaggio. Il secondo parametro opzionale è il suo tipo (error, warning, info, ecc.). Il metodo `flashMessage()` restituisce un'istanza del messaggio flash come oggetto `stdClass`, a cui è possibile aggiungere ulteriori informazioni. +Dell'invio si occupa il metodo [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Il primo parametro è il testo del messaggio (`string`, `Stringable`) oppure un oggetto `stdClass` che rappresenta il messaggio. Il secondo parametro facoltativo è il suo tipo (error, warning, info ecc.). Il metodo `flashMessage()` restituisce un'istanza del messaggio flash come oggetto `stdClass`, al quale si possono aggiungere altre informazioni. ```php -$this->flashMessage('L\'elemento è stato eliminato.'); -$this->redirect(/* ... */); // e reindirizziamo +$this->flashMessage('Item was deleted.'); +$this->redirect(/* ... */); // e reindirizza ``` -Nel template, questi messaggi sono disponibili nella variabile `$flashes` come oggetti `stdClass`, che contengono le proprietà `message` (testo del messaggio), `type` (tipo del messaggio) e possono contenere le informazioni utente già menzionate. Li renderizziamo ad esempio così: +Questi messaggi sono disponibili nel template nella variabile `$flashes` come oggetti `stdClass`, che contengono le proprietà `message` (testo del messaggio), `type` (tipo del messaggio) e possono contenere le informazioni aggiunte dall'utente. Li disegniamo per esempio così: ```latte {foreach $flashes as $flash} @@ -230,22 +235,22 @@ Nel template, questi messaggi sono disponibili nella variabile `$flashes` come o ``` -Redirect dopo un segnale -======================== +Redirect dopo l'elaborazione di un segnale +========================================== -Dopo l'elaborazione di un segnale di componente, spesso segue un redirect. È una situazione simile a quella dei form - dopo il loro invio reindirizziamo anche, in modo che l'aggiornamento della pagina nel browser non provochi un nuovo invio dei dati. +L'elaborazione del segnale di un componente è spesso seguita da un redirect. È come per i form: dopo il loro invio reindirizziamo, per impedire il reinvio dei dati se la pagina viene ricaricata nel browser. ```php -$this->redirect('this') // reindirizza al presenter e all'azione correnti +$this->redirect('this'); // reindirizza al presenter e all'azione correnti ``` -Poiché un componente è un elemento riutilizzabile e di solito non dovrebbe avere un legame diretto con presenter specifici, i metodi `redirect()` e `link()` interpretano automaticamente il parametro come un segnale del componente: +Poiché un componente è un elemento riutilizzabile e di norma non dovrebbe avere un legame diretto con presenter specifici, i metodi `redirect()` e `link()` interpretano automaticamente il parametro come un segnale del componente: ```php -$this->redirect('click') // reindirizza al segnale 'click' dello stesso componente +$this->redirect('click'); // reindirizza al segnale 'click' dello stesso componente ``` -Se è necessario reindirizzare a un altro presenter o azione, è possibile farlo tramite il presenter: +Se avete bisogno di reindirizzare a un altro presenter o a un'altra azione, potete farlo tramite il presenter: ```php $this->getPresenter()->redirect('Product:show'); // reindirizza a un altro presenter/azione @@ -255,11 +260,11 @@ $this->getPresenter()->redirect('Product:show'); // reindirizza a un altro prese Parametri persistenti ===================== -I parametri persistenti servono a mantenere lo stato nei componenti tra richieste diverse. Il loro valore rimane lo stesso anche dopo aver cliccato su un link. A differenza dei dati nella sessione, vengono trasferiti nell'URL. E questo avviene in modo completamente automatico, inclusi i link creati in altri componenti sulla stessa pagina. +I parametri persistenti servono a mantenere lo stato nei componenti tra richieste diverse. Il loro valore resta lo stesso anche dopo aver cliccato un link. A differenza dei dati di sessione, vengono trasferiti nell'URL. E questo avviene in modo completamente automatico, compresi i link creati in altri componenti della stessa pagina. -Ad esempio, hai un componente per la paginazione del contenuto. Possono esserci più componenti di questo tipo su una pagina. E desideriamo che, dopo aver cliccato su un link, tutti i componenti rimangano sulla loro pagina corrente. Pertanto, rendiamo il numero di pagina (`page`) un parametro persistente. +Avete per esempio un componente per la paginazione dei contenuti. Su una pagina possono essercene diversi. E vogliamo che tutti i componenti restino sulla propria pagina corrente dopo aver cliccato un link. Rendiamo quindi il numero di pagina (`page`) un parametro persistente. -La creazione di un parametro persistente in Nette è estremamente semplice. Basta creare una proprietà pubblica e contrassegnarla con un attributo: (in precedenza si usava `/** @persistent */`) +Creare un parametro persistente in Nette è estremamente semplice. Basta creare una proprietà pubblica e contrassegnarla con l'attributo: (in precedenza si usava `/** @persistent */`) ```php use Nette\Application\Attributes\Persistent; // questa riga è importante @@ -271,43 +276,43 @@ class PaginatingControl extends Control } ``` -Per la proprietà, si consiglia di specificare anche il tipo di dati (es. `int`) e si può anche specificare un valore predefinito. I valori dei parametri possono essere [validati |#Validazione dei parametri persistenti]. +Consigliamo di indicare il tipo di dato della proprietà (per esempio `int`) e potete anche fornire un valore predefinito. I valori dei parametri si possono [validare |#Validazione dei parametri persistenti]. -Durante la creazione di un link, è possibile modificare il valore del parametro persistente: +Quando si crea un link, il valore di un parametro persistente si può cambiare: ```latte -successivo +successiva ``` -Oppure può essere *resettato*, cioè rimosso dall'URL. Assumerà quindi il suo valore predefinito: +Oppure lo si può *azzerare*, cioè rimuovere dall'URL. Assumerà allora il proprio valore predefinito: ```latte -reset +azzera ``` Componenti persistenti ====================== -Non solo i parametri, ma anche i componenti possono essere persistenti. Per un tale componente, i suoi parametri persistenti vengono trasferiti anche tra diverse azioni del presenter o tra più presenter. I componenti persistenti sono contrassegnati da un'annotazione nella classe del presenter. Ad esempio, in questo modo contrassegniamo i componenti `calendar` e `poll`: +Non solo i parametri, ma anche i componenti possono essere persistenti. I loro parametri persistenti vengono allora trasferiti anche tra azioni diverse del presenter o tra più presenter. Contrassegniamo i componenti persistenti con un attributo sulla classe del presenter. Contrassegniamo per esempio così i componenti `calendar` e `poll`: ```php -/** - * @persistent(calendar, poll) - */ +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] class DefaultPresenter extends Nette\Application\UI\Presenter { } ``` -I sottocomponenti all'interno di questi componenti non devono essere contrassegnati, diventeranno persistenti anch'essi. +I sottocomponenti di questi componenti non devono essere contrassegnati: diventano persistenti anch'essi. -In PHP 8, è possibile utilizzare anche attributi per contrassegnare i componenti persistenti: +La vecchia annotazione `@persistent` funziona ancora, ma è deprecata ed emette un avviso: ```php -use Nette\Application\Attributes\Persistent; - -#[Persistent('calendar', 'poll')] +/** + * @persistent(calendar, poll) + */ class DefaultPresenter extends Nette\Application\UI\Presenter { } @@ -317,15 +322,15 @@ class DefaultPresenter extends Nette\Application\UI\Presenter Componenti con dipendenze ========================= -Come creare componenti con dipendenze senza "inquinare" i presenter che li utilizzeranno? Grazie alle proprietà intelligenti del container DI in Nette, proprio come nell'uso dei servizi classici, è possibile lasciare la maggior parte del lavoro al framework. +Come creare componenti con dipendenze senza "ingombrare" i presenter che li useranno? Grazie alle funzionalità intelligenti del container DI in Nette, come per i servizi classici, gran parte del lavoro si può lasciare al framework. -Prendiamo come esempio un componente che ha una dipendenza dal servizio `PollFacade`: +Prendiamo l'esempio di un componente che ha una dipendenza dal servizio `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id del sondaggio per cui creiamo il componente + private int $id, // ID del sondaggio per cui creiamo il componente private PollFacade $facade, ) { } @@ -338,11 +343,11 @@ class PollControl extends Control } ``` -Se stessimo scrivendo un servizio classico, non ci sarebbe nulla da risolvere. Il container DI si occuperebbe invisibilmente di passare tutte le dipendenze. Ma con i componenti, di solito li gestiamo creando una nuova istanza direttamente nel presenter nei [#metodi factory] `createComponent…()`. Ma passare tutte le dipendenze di tutti i componenti al presenter per poi passarle ai componenti è macchinoso. E quanto codice scritto… +Se stessimo scrivendo un servizio classico non ci sarebbe nulla da discutere: il container DI si occuperebbe invisibilmente di passare tutte le dipendenze. Con i componenti, però, di norma li gestiamo creando una nuova istanza direttamente nel presenter, nei [metodi factory |#Metodi factory] `createComponent…()`. Ma passare al presenter tutte le dipendenze di tutti i componenti solo per girarle ai componenti è scomodo. E quanto codice da scrivere... -La domanda logica è: perché non registriamo semplicemente il componente come un servizio classico, lo passiamo al presenter e poi lo restituiamo nel metodo `createComponent…()`? Tale approccio è però inappropriato, perché vogliamo poter creare il componente anche più volte. +La domanda logica è: perché non registriamo semplicemente il componente come servizio classico, non lo passiamo al presenter e non lo restituiamo nel metodo `createComponent…()`? Questo approccio è però inadatto, perché vogliamo poter creare il componente più volte, se serve. -La soluzione corretta è scrivere una factory per il componente, cioè una classe che ci creerà il componente: +La soluzione corretta è scrivere una factory per il componente, cioè una classe che crei il componente al posto nostro: ```php class PollControlFactory @@ -366,7 +371,7 @@ services: - PollControlFactory ``` -e infine la utilizziamo nel nostro presenter: +e infine la usiamo nel nostro presenter: ```php class PollPresenter extends Nette\Application\UI\Presenter @@ -384,7 +389,7 @@ class PollPresenter extends Nette\Application\UI\Presenter } ``` -La cosa fantastica è che Nette DI può [generare |dependency-injection:factory] tali semplici factory, quindi invece del suo intero codice, basta scrivere solo la sua interfaccia: +La cosa bella è che Nette DI sa [generare |dependency-injection:factory] factory semplici come questa, quindi invece di scriverne tutto il codice vi basta scriverne l'interfaccia: ```php interface PollControlFactory @@ -393,21 +398,21 @@ interface PollControlFactory } ``` -E questo è tutto. Nette implementa internamente questa interfaccia e la passa al presenter, dove possiamo già utilizzarla. Aggiunge magicamente anche il parametro `$id` e l'istanza della classe `PollFacade` al nostro componente. +E questo è tutto. Nette implementa internamente questa interfaccia e la inietta nel presenter, dove possiamo usarla. Aggiunge come per magia al nostro componente il parametro `$id` e un'istanza della classe `PollFacade`. -Componenti in profondità -======================== +I componenti in profondità +========================== -I componenti in Nette Application rappresentano parti riutilizzabili dell'applicazione web che inseriamo nelle pagine e a cui, del resto, è dedicato l'intero capitolo. Quali capacità ha esattamente un tale componente? +I componenti in Nette Application rappresentano parti riutilizzabili di un'applicazione web che incorporiamo nelle pagine, ed è a essi che è dedicato tutto questo capitolo. Quali sono esattamente le capacità di un componente del genere? -1) è renderizzabile nel template -2) sa [quale sua parte |ajax#Snippet] deve renderizzare durante una richiesta AJAX (snippet) -3) ha la capacità di memorizzare il proprio stato nell'URL (parametri persistenti) -4) ha la capacità di reagire alle azioni dell'utente (segnali) -5) crea una struttura gerarchica (dove la radice è il presenter) +1) È disegnabile in un template +2) Sa [quale parte di sé |ajax#Snippet] disegnare durante una richiesta AJAX (snippet) +3) Ha la capacità di salvare il proprio stato nell'URL (parametri persistenti) +4) Ha la capacità di reagire alle azioni dell'utente (segnali) +5) Crea una struttura gerarchica (la cui radice è il presenter) -Ognuna di queste funzioni è gestita da una delle classi della linea ereditaria. Il rendering (1 + 2) è gestito da [api:Nette\Application\UI\Control], l'integrazione nel [ciclo di vita |presenters#Ciclo di vita del presenter] (3, 4) dalla classe [api:Nette\Application\UI\Component] e la creazione della struttura gerarchica (5) dalle classi [Container e Component |component-model:]. +Di ognuna di queste funzioni si occupa una delle classi della catena di ereditarietà. Del rendering (1 + 2) si occupa [api:Nette\Application\UI\Control], dell'integrazione nel [ciclo di vita |presenters#Ciclo di vita del presenter] (3, 4) la classe [api:Nette\Application\UI\Component] e della creazione della struttura gerarchica (5) le classi [Container e Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -431,9 +436,9 @@ Ciclo di vita del componente Validazione dei parametri persistenti ------------------------------------- -I valori dei [#parametri persistenti] ricevuti dall'URL vengono scritti nelle proprietà dal metodo `loadState()`. Questo controlla anche se il tipo di dati specificato nella proprietà corrisponde, altrimenti risponde con un errore 404 e la pagina non viene visualizzata. +I valori dei [parametri persistenti |#Parametri persistenti] ricevuti dagli URL vengono scritti nelle proprietà dal metodo `loadState()`. Esso controlla anche che il tipo di dato indicato per la proprietà corrisponda; in caso contrario risponde con un errore 404 e la pagina non viene mostrata. -Non fidarti mai ciecamente dei parametri persistenti, perché possono essere facilmente sovrascritti dall'utente nell'URL. In questo modo, ad esempio, verifichiamo se il numero di pagina `$this->page` è maggiore di 0. Un modo appropriato è sovrascrivere il metodo `loadState()` menzionato: +Non fidatevi mai ciecamente dei parametri persistenti, perché l'utente può facilmente sovrascriverli nell'URL. Ecco come controlliamo, per esempio, che il numero di pagina `$this->page` sia maggiore di 0. Un modo adatto è sovrascrivere il metodo `loadState()` già menzionato: ```php class PaginatingControl extends Control @@ -444,7 +449,7 @@ class PaginatingControl extends Control public function loadState(array $params): void { parent::loadState($params); // qui viene impostato $this->page - // segue il controllo del valore personalizzato: + // segue il controllo personalizzato del valore: if ($this->page < 1) { $this->error(); } @@ -452,27 +457,41 @@ class PaginatingControl extends Control } ``` -Il processo opposto, cioè la raccolta dei valori dalle proprietà persistenti, è gestito dal metodo `saveState()`. +Del processo inverso, cioè della raccolta dei valori dalle proprietà persistenti, si occupa il metodo `saveState()`. + + +Collegamento al presenter +------------------------- + +Nel momento in cui un componente entra a far parte della gerarchia del presenter, vengono richiamate le sue callback salvate nell'array `$onAnchor`. Da quel momento in poi il componente ha a disposizione il presenter, può creare link in sicurezza, leggere i parametri persistenti e così via. + +```php +$control->onAnchor[] = function ($control): void { + // il componente ha ora a disposizione il presenter +}; +``` + +I segnali in profondità +----------------------- -Segnali in profondità ---------------------- +Un segnale provoca il ricaricamento della pagina esattamente come la richiesta originale (tranne quando viene chiamato via AJAX) e richiama il metodo `signalReceived($signal)`, la cui implementazione predefinita nella classe `Nette\Application\UI\Component` prova a chiamare un metodo composto dalle parole `handle`. L'ulteriore elaborazione spetta all'oggetto in questione. Gli oggetti che ereditano da `Component` (cioè `Control` e `Presenter`) reagiscono provando a chiamare il metodo `handle` con i parametri appropriati. -Un segnale provoca il ricaricamento della pagina proprio come nella richiesta originale (tranne quando viene chiamato tramite AJAX) e invoca il metodo `signalReceived($signal)`, la cui implementazione predefinita nella classe `Nette\Application\UI\Component` tenta di chiamare un metodo composto dalle parole `handle{signal}`. L'ulteriore elaborazione dipende dall'oggetto specifico. Gli oggetti che ereditano da `Component` (cioè `Control` e `Presenter`) reagiscono cercando di chiamare il metodo `handle{signal}` con i parametri appropriati. +In altre parole: si prende la definizione della funzione `handle`, insieme a tutti i parametri arrivati con la richiesta, si assegnano per nome agli argomenti i parametri provenienti dall'URL e si prova a chiamare il metodo. Per esempio il valore del parametro `id` nell'URL viene passato come argomento `$id`, `something` dall'URL viene passato come `$something` e così via. E se il metodo non esiste, il metodo `signalReceived` solleva un'[eccezione |api:Nette\Application\UI\BadSignalException]. -In altre parole: prende la definizione della funzione `handle{signal}` e tutti i parametri che sono arrivati con la richiesta, e agli argomenti vengono assegnati i parametri dall'URL in base al nome e tenta di chiamare il metodo dato. Ad esempio, come parametro `$id` viene passato il valore dal parametro `id` nell'URL, come `$something` viene passato `something` dall'URL, ecc. E se il metodo non esiste, il metodo `signalReceived` lancia un'[eccezione |api:Nette\Application\UI\BadSignalException]. +Oltre ai parametri provenienti dall'URL, un segnale legge anche i parametri inviati nel **corpo POST della richiesta**. Questo torna comodo, perché i segnali vengono spesso richiamati via JavaScript, dove è naturale inviare i dati con il metodo POST. Se però un parametro con lo stesso nome arriva sia dall'URL sia dal corpo POST, ha la precedenza il valore **proveniente dall'URL**. Evitate quindi di dare a un campo POST lo stesso nome di un parametro dell'URL o della route, altrimenti il valore dell'URL lo sovrascriverebbe in silenzio. I parametri dei segnali condividono lo spazio con i parametri delle azioni e con quelli persistenti, vedi [Spazio condiviso dei parametri |presenters#Spazio condiviso dei parametri]. -Un segnale può essere ricevuto da qualsiasi componente, presenter o oggetto che implementa l'interfaccia `SignalReceiver` ed è connesso all'albero dei componenti. +Un segnale può essere ricevuto da qualsiasi componente, presenter o oggetto che implementi l'interfaccia `SignalReceiver` e sia collegato all'albero dei componenti. -I principali destinatari dei segnali saranno i `Presenter` e i componenti visivi che ereditano da `Control`. Un segnale serve come indicazione per un oggetto che deve fare qualcosa – un sondaggio deve contare il voto di un utente, un blocco di notizie deve espandersi e mostrare il doppio delle notizie, un form è stato inviato e deve elaborare i dati, e così via. +I principali destinatari dei segnali saranno i `Presenter` e i componenti visivi che ereditano da `Control`. Un segnale è pensato per fare da segnale a un oggetto affinché faccia qualcosa: un sondaggio deve conteggiare un voto dell'utente, un blocco di notizie deve espandersi e mostrare il doppio delle notizie, un form è stato inviato e deve elaborare i dati e così via. -L'URL per un segnale viene creato utilizzando il metodo [Component::link() |api:Nette\Application\UI\Component::link()]. Come parametro `$destination` passiamo la stringa `{signal}!` e come `$args` un array di argomenti che vogliamo passare al segnale. Il segnale viene sempre chiamato sul presenter e sull'azione correnti con i parametri correnti, vengono aggiunti solo i parametri del segnale. Inoltre, viene aggiunto all'inizio il **parametro `?do`, che specifica il segnale**. +L'URL di un segnale si crea con il metodo [Component::link() |api:Nette\Application\UI\Component::link()]. Come parametro `$destination` passiamo la stringa `{segnale}!` e come `$args` un array di argomenti che vogliamo passare al segnale. Il segnale viene sempre chiamato sul presenter e sull'azione correnti, con i parametri correnti; i parametri del segnale si limitano ad aggiungersi. In più viene aggiunto il **parametro `?do`, che indica il segnale**. -Il suo formato è `{signal}` o `{signalReceiver}-{signal}`. `{signalReceiver}` è il nome del componente nel presenter. Pertanto, non può esserci un trattino nel nome del componente – viene utilizzato per separare il nome del componente e il segnale, tuttavia è possibile nidificare più componenti in questo modo. +Il suo formato è `{segnale}` oppure `{destinatarioDelSegnale}-{segnale}`. `{destinatarioDelSegnale}` è il nome del componente nel presenter. Nel nome del componente non si può quindi usare il trattino: serve a separare il nome del componente dal segnale, anche se è possibile annidare così più componenti. -Il metodo [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] verifica se il componente (primo argomento) è il destinatario del segnale (secondo argomento). Possiamo omettere il secondo argomento – quindi verifica se il componente è il destinatario di qualsiasi segnale. Come secondo parametro è possibile specificare `true` e verificare così se il destinatario non è solo il componente specificato, ma anche uno qualsiasi dei suoi discendenti. +Il metodo [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] controlla se il componente (primo argomento) è il destinatario del segnale (secondo argomento). Il secondo argomento si può omettere: in tal caso controlla se il componente è destinatario di un qualsiasi segnale. Se il secondo parametro è impostato a `true`, verifica se il componente indicato o uno qualsiasi dei suoi discendenti è il destinatario. -In qualsiasi fase precedente a `handle{signal}` possiamo eseguire il segnale manualmente chiamando il metodo [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], che si occupa di gestire il segnale – prende il componente che è stato determinato come destinatario del segnale (se non è specificato alcun destinatario del segnale, è il presenter stesso) e gli invia il segnale. +In qualsiasi fase precedente a `handle` possiamo eseguire manualmente il segnale chiamando il metodo [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], che si occupa della gestione del segnale: prende il componente individuato come destinatario del segnale (se non è indicato alcun destinatario, è il presenter stesso) e gli invia il segnale. Esempio: @@ -482,4 +501,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -In questo modo il segnale viene eseguito prematuramente e non verrà più chiamato. +Così il segnale viene eseguito in anticipo e non verrà chiamato di nuovo. diff --git a/application/it/configuration.texy b/application/it/configuration.texy index 04a2397c8a..10520edde4 100644 --- a/application/it/configuration.texy +++ b/application/it/configuration.texy @@ -1,8 +1,8 @@ -Configurazione delle Applicazioni -********************************* +Configurazione dell'applicazione +******************************** .[perex] -Panoramica delle opzioni di configurazione per le Applicazioni Nette. +Panoramica delle opzioni di configurazione di Nette Application. Application @@ -10,62 +10,64 @@ Application ```neon application: - # visualizzare il pannello "Nette Application" in Tracy BlueScreen? - debugger: ... # (bool) il default è true + # mostrare il pannello "Nette Application" nella BlueScreen di Tracy? + debugger: ... # (bool) attivo se Tracy è disponibile - # verrà chiamato l'error-presenter in caso di errore? - # ha effetto solo in modalità sviluppo - catchExceptions: ... # (bool) il default è true + # in produzione le eccezioni sono sempre gestite dall'error-presenter; + # questa opzione attiva quel comportamento anche in modalità di sviluppo + catchExceptions: ... # (bool) di norma false, cioè disattivo in dev, sempre attivo in produzione # nome dell'error-presenter - errorPresenter: Error # (string|array) il default è 'Nette:Error' + errorPresenter: Error # (string|array) di norma 'Nette:Error' - # definisce alias per presenter e azioni + # definisce gli alias per i presenter e le azioni aliases: ... - # definisce le regole per la traduzione del nome del presenter in classe + # definisce le regole per tradurre il nome del presenter in una classe mapping: ... - # i link errati non generano avvisi? - # ha effetto solo in modalità sviluppo - silentLinks: ... # (bool) il default è false + # silenziare gli avvisi sui link non validi? + # ha effetto solo in modalità di sviluppo + silentLinks: ... # (bool) di norma false ``` -Dalla versione `nette/application` 3.2 è possibile definire una coppia di error-presenter: +Da `nette/application` versione 3.2 è possibile definire una coppia di error presenter: ```neon application: errorPresenter: - 4xx: Error4xx # per l'eccezione Nette\Application\BadRequestException + 4xx: Error4xx # per Nette\Application\BadRequestException 5xx: Error5xx # per le altre eccezioni ``` -L'opzione `silentLinks` determina come Nette si comporta in modalità sviluppo quando la generazione di un link fallisce (ad esempio perché il presenter non esiste, ecc.). Il valore predefinito `false` significa che Nette genera un errore `E_USER_WARNING`. Impostandolo su `true` si sopprime questo messaggio di errore. Nell'ambiente di produzione, `E_USER_WARNING` viene sempre generato. Questo comportamento può essere influenzato anche impostando la variabile del presenter [$invalidLinkMode |creating-links#Link non validi]. +Separarli è utile, perché le due situazioni sono radicalmente diverse. Una `BadRequestException` (codici 4xx) significa che l'applicazione sta bene e che semplicemente il visitatore ha chiesto qualcosa che non esiste. Potete quindi usare un presenter a pieno regime, che mostri un messaggio cordiale nel layout del vostro sito. Un errore 5xx significa invece che qualcosa nell'applicazione si è rotto e non sapete cosa. Tenete il presenter 5xx il più minimale possibile, così che durante il suo rendering non possa rompersi nient'altro: idealmente non dovrebbe toccare il database, il layout o l'utente connesso. -Gli [Alias semplificano il collegamento |creating-links#Alias] ai presenter usati frequentemente. +L'opzione `silentLinks` stabilisce come si comporta Nette in modalità di sviluppo quando la generazione di un link fallisce (per esempio perché il presenter non esiste ecc.). Il valore predefinito `false` significa che Nette emette un errore `E_USER_WARNING`. Impostandola a `true` questo messaggio di errore viene silenziato. In un ambiente di produzione `E_USER_WARNING` viene sempre emesso. Su questo comportamento si può influire anche impostando la variabile del presenter [$invalidLinkMode |creating-links#Link non validi]. -La [Mappatura definisce le regole |directory-structure#Mappatura dei presenter], secondo le quali dal nome del presenter si deriva il nome della classe. +Gli [alias semplificano il riferimento |creating-links#Alias] ai presenter usati più di frequente. + +Il [mapping definisce le regole |directory-structure#Mapping dei presenter] con cui il nome della classe viene ricavato dal nome del presenter. Registrazione automatica dei presenter -------------------------------------- -Nette aggiunge automaticamente i presenter come servizi al container DI, il che accelera notevolmente la loro creazione. Come Nette trova i presenter può essere configurato: +Nette aggiunge automaticamente i presenter come servizi al container DI, il che ne accelera notevolmente la creazione. Il modo in cui Nette individua i presenter si può configurare: ```neon application: - # cercare i presenter nella mappa delle classi di Composer? - scanComposer: ... # (bool) il default è true + # cercare i presenter nella class map di Composer? + scanComposer: ... # (bool) di norma true - # maschera a cui devono corrispondere il nome della classe e del file - scanFilter: ... # (string) il default è '*Presenter' + # maschera a cui devono corrispondere il nome della classe e quello del file + scanFilter: ... # (string) di norma '*Presenter' # in quali directory cercare i presenter? - scanDirs: # (string[]|false) il default è '%appDir%' + scanDirs: # (string[]|false) di norma '%appDir%' - %vendorDir%/mymodule ``` -Le directory specificate in `scanDirs` non sovrascrivono il valore predefinito `%appDir%`, ma lo completano, quindi `scanDirs` conterrà entrambi i percorsi `%appDir%` e `%vendorDir%/mymodule`. Se volessimo omettere la directory predefinita, useremmo un [punto esclamativo |dependency-injection:configuration#Unione], che sovrascrive il valore: +Le directory elencate in `scanDirs` non sostituiscono il valore predefinito `%appDir%`, ma lo integrano, quindi `scanDirs` conterrà entrambi i percorsi, `%appDir%` e `%vendorDir%/mymodule`. Se vogliamo escludere la directory predefinita, usiamo un [punto esclamativo |dependency-injection:configuration#Unione]: ```neon application: @@ -73,36 +75,42 @@ application: - %vendorDir%/mymodule ``` -La scansione delle directory può essere disattivata specificando il valore `false`. Non consigliamo di sopprimere completamente l'aggiunta automatica dei presenter, perché altrimenti le prestazioni dell'applicazione diminuiranno. +La scansione delle directory si può disattivare impostando il valore a `false`. I presenter non vengono allora più registrati come servizi, quindi non si possono modificare tramite la sezione [decorator |dependency-injection:configuration#Decorator] e la loro creazione è più lenta. Non consigliamo quindi di sopprimere del tutto la registrazione automatica, perché riduce le prestazioni dell'applicazione. Template Latte ============== -Con questa impostazione è possibile influenzare globalmente il comportamento di Latte nei componenti e nei presenter. +Questa impostazione influisce globalmente sul comportamento di Latte nei componenti e nei presenter. ```neon latte: - # visualizzare il pannello Latte nella Tracy Bar per il template principale (true) o tutti i componenti (all)? - debugger: ... # (true|false|'all') il default è true + # mostrare il pannello di Latte nella Tracy Bar per il template principale (true) o per tutti i componenti (all)? + debugger: ... # (true|false|'all') attivo se Tracy è disponibile (solo in modalità debug) + + # genera i template con l'intestazione declare(strict_types=1) + strictTypes: ... # (bool) di norma false - # genera template con l'intestazione declare(strict_types=1) - strictTypes: ... # (bool) il default è false + # attiva la [modalità di analisi rigorosa |latte:develop#strict mode] + strictParsing: ... # (bool) di norma false - # attiva la modalità [parser rigoroso |latte:develop#striktní režim] - strictParsing: ... # (bool) il default è false + # limita l'ambito delle variabili al corpo del ciclo + scopedLoopVariables: ... # (bool) di norma false - # attiva il [controllo del codice generato |latte:develop#Kontrola vygenerovaného kódu] - phpLinter: ... # (string) il default è null + # rimuove l'indentazione dovuta all'annidamento nei tag di tipo pari + dedent: ... # (bool) di norma false - # imposta la locale - locale: it_IT # (string) il default è null + # attiva il [controllo del codice generato |latte:develop#Checking Generated Code] + phpLinter: ... # (string) di norma null + + # imposta il locale + locale: cs_CZ # (string) di norma null # classe dell'oggetto $this->template - templateClass: App\MyTemplateClass # il default è Nette\Bridges\ApplicationLatte\DefaultTemplate + templateClass: App\MyTemplateClass # di norma Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Se usi Latte versione 3, puoi aggiungere nuove [estensioni |latte:extending-latte#Latte Extension] usando: +Potete aggiungere nuove [estensioni |latte:extending-latte#Estensione di Latte] così: ```neon latte: @@ -110,20 +118,6 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Se usi Latte versione 2, puoi registrare nuovi tag specificando il nome della classe o un riferimento a un servizio. Per impostazione predefinita, viene chiamato il metodo `install()`, ma questo può essere modificato specificando il nome di un altro metodo: - -```neon -latte: - # registrazione di tag Latte personalizzati - macros: - - App\MyLatteMacros::register # metodo statico, nomeclasse o callable - - @App\MyLatteMacrosFactory # servizio con metodo install() - - @App\MyLatteMacrosFactory::register # servizio con metodo register() - -services: - - App\MyLatteMacrosFactory -``` - Routing ======= @@ -132,14 +126,14 @@ Impostazioni di base: ```neon routing: - # visualizzare il pannello di routing nella Tracy Bar? - debugger: ... # (bool) il default è true + # mostrare il pannello del routing nella Tracy Bar? + debugger: ... # (bool) attivo se Tracy è disponibile (solo in modalità debug) # serializza il router nel container DI - cache: ... # (bool) il default è false + cache: ... # (bool) di norma false ``` -Il routing viene solitamente definito nella classe [RouterFactory |routing#Collezione di route]. In alternativa, le route possono essere definite anche nella configurazione utilizzando coppie `maschera: azione`, ma questo metodo non offre una così ampia variabilità nelle impostazioni: +Il routing si definisce di solito nella classe [RouterFactory |routing#Collezione di route]. In alternativa le route si possono definire anche nella configurazione, con coppie `maschera: azione`, ma questo metodo non offre molta flessibilità: ```neon routing: @@ -159,20 +153,20 @@ constants: Foobar: 'baz' ``` -Dopo l'avvio dell'applicazione, verrà creata la costante `Foobar`. +La costante `Foobar` verrà creata dopo l'avvio dell'applicazione. .[note] -Le costanti non dovrebbero servire come una sorta di variabili globalmente disponibili. Per passare valori agli oggetti, utilizza la [dependency injection |dependency-injection:passing-dependencies]. +Le costanti non devono servire da variabili globalmente accessibili. Per passare valori agli oggetti usate la [dependency injection |dependency-injection:passing-dependencies]. PHP === -Impostazione delle direttive PHP. Una panoramica di tutte le direttive si trova su [php.net |https://www.php.net/manual/en/ini.list.php]. +Impostazione delle direttive di PHP. Una panoramica di tutte le direttive si trova su [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: - date.timezone: Europe/Rome + date.timezone: Europe/Prague ``` @@ -181,11 +175,12 @@ Servizi DI Questi servizi vengono aggiunti al container DI: -| Nome | Tipo | Descrizione -|---------------------------------------------------------- -| `application.application` | [api:Nette\Application\Application] | [avviatore dell'intera applicazione |how-it-works#Nette Application] -| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] -| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | factory per i presenter -| `application.###` | [api:Nette\Application\UI\Presenter] | singoli presenter -| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | factory dell'oggetto `Latte\Engine` -| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | factory per [`$this->template` |templates] +| Nome | Tipo | Descrizione +|----------------------------|---------------------------------------------------|----------------------------------------- +| `application.application` | [api:Nette\Application\Application] | l'[esecutore dell'applicazione |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\IPresenterFactory] | factory dei presenter +| `application.###` | [api:Nette\Application\UI\Presenter] | i singoli presenter +| `routing.router` | [api:Nette\Routing\Router] | router +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | factory dell'oggetto `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | factory di [`$this->template` |templates] diff --git a/application/it/creating-links.texy b/application/it/creating-links.texy index 6dc629a81b..9068de2e9f 100644 --- a/application/it/creating-links.texy +++ b/application/it/creating-links.texy @@ -3,132 +3,149 @@ Creazione di link URL
    -Creare link in Nette è semplice come puntare il dito. Basta mirare e il framework farà tutto il lavoro per te. Vedremo: +Creare link in Nette è semplice come puntare il dito. Basta mirare e il framework farà tutto il lavoro per voi. Vi mostreremo: - come creare link nei template e altrove -- come distinguere un link alla pagina corrente +- come riconoscere un link alla pagina corrente - cosa fare con i link non validi
    -Grazie al [routing bidirezionale |routing], non dovrai mai scrivere hardcoded gli indirizzi URL della tua applicazione nei template o nel codice, che potrebbero cambiare in seguito, o comporli in modo complicato. Nel link, basta specificare il presenter e l'azione, passare eventuali parametri e il framework genererà l'URL da solo. In realtà, è molto simile a chiamare una funzione. Ti piacerà. +Grazie al [routing bidirezionale |routing] non dovrete mai scrivere a mano nei template o nel codice gli URL della vostra applicazione, che potrebbero cambiare in seguito o essere complicati da comporre. Nel link basta indicare il presenter e l'azione, passare eventuali parametri, e il framework genererà l'URL da sé. In realtà è molto simile a chiamare una funzione. Vi piacerà. Nel template del presenter ========================== -Il più delle volte creiamo link nei template e un ottimo aiuto è l'attributo `n:href`: +Il più delle volte creiamo i link nei template, e l'attributo `n:href` è un ottimo aiuto: ```latte dettaglio ``` -Nota che invece dell'attributo HTML `href`, abbiamo usato l'[attributo n |latte:syntax#n:attributi] `n:href`. Il suo valore non è quindi un URL, come sarebbe nel caso dell'attributo `href`, ma il nome del presenter e dell'azione. +Notate che al posto dell'attributo HTML `href` abbiamo usato l'[n:attributo |latte:syntax#n:attributi] `n:href`. Il suo valore non è un URL, come sarebbe per l'attributo `href`, ma il nome del presenter e dell'azione. -Cliccare sul link è, in parole povere, qualcosa come chiamare il metodo `ProductPresenter::renderShow()`. E se ha parametri nella sua firma, possiamo chiamarlo con argomenti: +Cliccare su un link è, detta semplicemente, un po' come chiamare il metodo `ProductPresenter::renderShow()`. E se questo ha dei parametri nella propria firma, possiamo chiamarlo con degli argomenti: ```latte -dettaglio prodotto +dettaglio del prodotto ``` -È possibile passare anche parametri nominati. Il seguente link passa il parametro `lang` con il valore `cs`: +È possibile passare anche parametri nominali. Il link seguente passa il parametro `lang` con il valore `en`: ```latte -dettaglio prodotto +dettaglio del prodotto ``` -Se il metodo `ProductPresenter::renderShow()` non ha `$lang` nella sua firma, può ottenere il valore del parametro usando `$lang = $this->getParameter('lang')` o dalla [proprietà |presenters#Parametri della richiesta]. +Se il metodo `ProductPresenter::renderShow()` non ha `$lang` nella propria firma, può ottenere il valore del parametro con `$lang = $this->getParameter('lang')` oppure da una [proprietà |presenters#Parametri della richiesta]. -Se i parametri sono memorizzati in un array, possono essere espansi con l'operatore `...` (in Latte 2.x con l'operatore `(expand)`): +Se i parametri sono salvati in un array, si possono espandere con l'operatore `...`: ```latte -{var $args = [$product->id, lang => cs]} -dettaglio prodotto +{var $args = [$product->id, lang => en]} +dettaglio del prodotto ``` -Nei link vengono automaticamente passati anche i cosiddetti [parametri persistenti |presenters#Parametri persistenti]. +Nei link vengono passati automaticamente anche i cosiddetti [parametri persistenti |presenters#Parametri persistenti]. -L'attributo `n:href` è molto utile per i tag HTML ``. Se vogliamo stampare il link altrove, ad esempio nel testo, usiamo `{link}`: +L'attributo `n:href` è molto comodo per i tag HTML ``. Se vogliamo stampare il link altrove, per esempio nel testo, usiamo `{link}`: ```latte -L'indirizzo è: {link Home:default} +L'URL è: {link Home:default} ``` Nel codice ========== -Per creare un link nel presenter, si usa il metodo `link()`: +Per creare un link nel presenter si usa il metodo `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -I parametri possono essere passati anche tramite un array, dove è possibile specificare anche parametri nominati: +I parametri si possono passare anche come array, in cui si possono indicare anche parametri nominali: ```php -$url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$product->id, 'lang' => 'en']); ``` -I link possono essere creati anche senza un presenter, per questo c'è [#LinkGenerator] e il suo metodo `link()`. +I link si possono creare anche senza un presenter, con il [#LinkGenerator] e il suo metodo `link()`. +A volte vi serve creare un link subito, ma generare l'URL vero e proprio solo più tardi. A questo serve il metodo `lazyLink()`, che restituisce un oggetto `Nette\Application\UI\Link`. Il vantaggio è che potete passare questo oggetto, per esempio a un template, e prima che venga disegnato potete ancora modificarne i parametri con il metodo `setParameter()`. L'URL viene composto solo quando l'oggetto viene convertito in stringa: -Link al presenter -================= +```php +$link = $this->lazyLink('Product:show', $id); +// ... +echo $link; // l'URL viene generato solo qui +``` + + +Link a un presenter +=================== -Se la destinazione del link è un presenter e un'azione, ha questa sintassi: +Se la destinazione del link è un presenter con un'azione, la sintassi è questa: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Il formato è supportato da tutti i tag Latte e da tutti i metodi del presenter che lavorano con i link, cioè `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` e anche [#LinkGenerator]. Quindi, anche se negli esempi viene usato `n:href`, potrebbe esserci una qualsiasi delle funzioni. +Questo formato è supportato da tutti i tag di Latte e da tutti i metodi del presenter che lavorano con i link, cioè `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` e anche dal [#LinkGenerator]. Quindi, anche se negli esempi si usa `n:href`, al suo posto potrebbe esserci una qualsiasi di queste funzioni. -La forma base è quindi `Presenter:action`: +La forma di base è dunque `Presenter:azione`: ```latte -pagina iniziale +home page ``` -Se ci colleghiamo a un'azione del presenter corrente, possiamo omettere il suo nome: +Se colleghiamo a un'azione del presenter corrente, possiamo ometterne il nome: ```latte -pagina iniziale +home page ``` -Se la destinazione è l'azione `default`, possiamo ometterla, ma i due punti devono rimanere: +Se l'azione di destinazione è `default`, possiamo ometterla, ma i due punti devono restare: ```latte -pagina iniziale +home page ``` -I link possono anche puntare ad altri [moduli |directory-structure#Presenter e template]. Qui i link si distinguono in relativi a un sottomodulo nidificato o assoluti. Il principio è analogo ai percorsi su disco, solo che al posto degli slash ci sono i due punti. Supponiamo che il presenter corrente faccia parte del modulo `Front`, allora scriveremo: +I link possono puntare anche ad altri [moduli |directory-structure#Presenter e template]. Qui si distingue tra link relativi a un sottomodulo annidato e link assoluti. Il principio è analogo a quello dei percorsi su disco, solo che al posto delle barre si usano i due punti. Supponendo che il presenter corrente faccia parte del modulo `Front`, scriveremmo: ```latte link a Front:Shop:Product:show link a Admin:Product:show ``` -Un caso speciale è un link [a se stesso |#Link alla pagina corrente], dove specifichiamo `this` come destinazione. +Un caso particolare è il link [a sé stessi |#Link alla pagina corrente], dove indichiamo come destinazione `this`. ```latte aggiorna ``` -Possiamo collegarci a una parte specifica della pagina tramite il cosiddetto frammento dopo il simbolo cancelletto `#`: +Possiamo collegare a una parte precisa della pagina tramite il cosiddetto frammento dopo il cancelletto `#`: ```latte -link a Home:default e frammento #main +link a Home:default e al frammento #main +``` + +.{data-version:3.3.0} +Il frammento si può impostare anche dinamicamente, come argomento con la chiave `#`. Il suo valore viene codificato automaticamente e ha la precedenza sul frammento indicato nella destinazione: + +```php +$this->link('Home:default', ['#' => $fragment]); ``` Percorsi assoluti ================= -I link generati usando `link()` o `n:href` sono sempre percorsi assoluti (cioè iniziano con `/`), ma non URL assoluti con protocollo e dominio come `https://domain`. +I link generati con `link()` o `n:href` sono sempre percorsi assoluti (cioè iniziano con `/`), ma non URL assoluti con protocollo e dominio, come `https://domain`. + +Per generare un URL assoluto aggiungete due barre all'inizio (per esempio `n:href="//Home:"`). In alternativa potete far generare al presenter solo link assoluti impostando `$this->absoluteUrls = true`. -Per generare un URL assoluto, aggiungi due slash all'inizio (es. `n:href="//Home:"`). Oppure si può impostare il presenter per generare solo link assoluti impostando `$this->absoluteUrls = true`. +Nel template si può usare anche il filtro `|absoluteUrl` per convertire un percorso relativo in uno assoluto. Link alla pagina corrente @@ -140,17 +157,17 @@ La destinazione `this` crea un link alla pagina corrente: aggiorna ``` -Allo stesso tempo, vengono trasferiti anche tutti i parametri specificati nella firma del metodo `action()` o `render()`, se `action()` non è definita. Quindi, se siamo sulla pagina `Product:show` e `id: 123`, il link a `this` passerà anche questo parametro. +Allo stesso tempo vengono trasferiti tutti i parametri indicati nella firma del metodo `action()` o `render()` (se `action()` non è definito). Se quindi ci troviamo sulla pagina `Product:show` con `id: 123`, anche il link a `this` passerà questo parametro. -Ovviamente, è possibile specificare i parametri direttamente: +Naturalmente è possibile indicare i parametri direttamente: ```latte aggiorna ``` -La funzione `isLinkCurrent()` verifica se la destinazione del link è identica alla pagina corrente. Questo può essere utilizzato, ad esempio, nel template per distinguere i link, ecc. +La funzione `isLinkCurrent()` controlla se la destinazione del link coincide con la pagina corrente. Si può usare per esempio in un template per distinguere i link e simili. -I parametri sono gli stessi del metodo `link()`, ma è anche possibile specificare un carattere jolly `*` invece di un'azione specifica, che significa qualsiasi azione del presenter dato. +I parametri sono gli stessi del metodo `link()`, ma al posto di un'azione specifica si può usare anche il carattere jolly `*`, che indica una qualsiasi azione del presenter indicato. ```latte {if !isLinkCurrent('Admin:login')} @@ -162,15 +179,15 @@ I parametri sono gli stessi del metodo `link()`, ma è anche possibile specifica
  • ``` -In combinazione con `n:href` in un unico elemento, si può usare la forma abbreviata: +In combinazione con `n:href` su un unico elemento si può usare una forma abbreviata: ```latte ... ``` -Il carattere jolly `*` può essere usato solo al posto dell'azione, non del presenter. +Il carattere jolly `*` si può usare solo al posto dell'azione, non del presenter. -Per verificare se siamo in un certo modulo o in un suo sottomodulo, usiamo il metodo `isModuleCurrent(moduleName)`. +Per stabilire se ci troviamo in un determinato modulo o in un suo sottomodulo, usate il metodo `isModuleCurrent(moduleName)`. ```latte
  • @@ -179,56 +196,72 @@ Per verificare se siamo in un certo modulo o in un suo sottomodulo, usiamo il me ``` -Link a segnali -============== +Cambiare la base dei link .{data-version:3.2.7} +=============================================== -La destinazione di un link non deve essere solo un presenter e un'azione, ma anche un [segnale |components#Segnale] (chiamano il metodo `handle()`). Allora la sintassi è la seguente: +Per impostazione predefinita i link relativi sono derivati dal presenter corrente. Lo si può cambiare con `{linkBase}`: + +```latte +{linkBase Admin:Dashboard} +dettaglio del prodotto +``` + +Il link porterà a `Admin:Dashboard:Product:show`. Ne sono interessati solo i link relativi: i link assoluti che iniziano con i due punti e i link al presenter corrente (`this`, `show`) restano invariati. + +`{linkBase}` vale per l'intero template ed è particolarmente utile nei template di layout, dove garantisce link coerenti indipendentemente dal presenter chiamante. +Il tag va collocato all'inizio del template, altrimenti solleva una `CompileException`. + + +Link a un segnale +================= + +La destinazione di un link non deve essere per forza un presenter con un'azione: può essere anche un [segnale |components#Segnale] (che chiama il metodo `handle()`). La sintassi è allora questa: ``` -[//] [sub-component:]signal! [#fragment] +[//] [sotto-componente:]segnale! [#fragment] ``` -Il segnale è quindi distinto dal punto esclamativo: +Il segnale si distingue quindi per il punto esclamativo: ```latte segnale ``` -È possibile creare anche un link al segnale di un sottocomponente (o sotto-sottocomponente): +Potete creare anche un link a un segnale di un sottocomponente (o di un sotto-sottocomponente): ```latte segnale ``` -Link nel componente -=================== +Link in un componente +===================== -Poiché i [componenti |components] sono unità riutilizzabili separate che non dovrebbero avere legami con i presenter circostanti, i link funzionano in modo leggermente diverso qui. L'attributo Latte `n:href` e il tag `{link}` così come i metodi del componente come `link()` e altri considerano la destinazione del link **sempre come il nome del segnale**. Pertanto, non è nemmeno necessario specificare il punto esclamativo: +Poiché i [componenti|components] sono unità riutilizzabili autonome, che non dovrebbero avere alcun legame con i presenter circostanti, qui i link funzionano in modo un po' diverso. L'attributo Latte `n:href` e il tag `{link}`, così come i metodi del componente come `link()` e altri, **considerano sempre la destinazione del link come il nome di un segnale**. Non è quindi nemmeno necessario indicare il punto esclamativo: ```latte -segnale, non azione +segnale, non un'azione ``` -Se volessimo collegarci ai presenter nel template del componente, useremmo il tag `{plink}`: +Se volessimo collegare ai presenter nel template di un componente, useremmo il tag `{plink}`: ```latte -inizio +home ``` -o nel codice +oppure nel codice ```php $this->getPresenter()->link('Home:default') ``` -Alias .{data-version:v3.2.2} -============================ +Alias .{data-version:3.2.3} +=========================== -A volte può essere utile assegnare un alias facilmente memorizzabile alla coppia Presenter:azione. Ad esempio, chiamare la pagina iniziale `Front:Home:default` semplicemente `home` o `Admin:Dashboard:default` come `admin`. +A volte può essere utile assegnare a una coppia Presenter:azione un alias facile da ricordare. Per esempio chiamare la home page `Front:Home:default` semplicemente `home`, oppure `Admin:Dashboard:default` come `admin`. -Gli alias sono definiti nella [configurazione|configuration] sotto la chiave `application › aliases`: +Gli alias si definiscono nella [configurazione|configuration], sotto la chiave `application › aliases`: ```neon application: @@ -238,7 +271,7 @@ application: sign: Front:Sign:in ``` -Nei link, vengono poi scritti usando la chiocciola, ad esempio: +Nei link si scrivono poi con la chiocciola, per esempio: ```latte amministrazione @@ -250,14 +283,14 @@ Sono supportati anche in tutti i metodi che lavorano con i link, come `redirect( Link non validi =============== -Può capitare di creare un link non valido - sia perché punta a un presenter inesistente, sia perché passa più parametri di quelli che il metodo di destinazione accetta nella sua firma, o quando non è possibile generare un URL per l'azione di destinazione. Come gestire i link non validi è determinato dalla variabile statica `Presenter::$invalidLinkMode`. Può assumere una combinazione di questi valori (costanti): +Può capitare di creare un link non valido: perché porta a un presenter inesistente, perché passa più parametri di quanti ne accetti il metodo di destinazione nella propria firma, oppure perché per l'azione di destinazione non è possibile generare un URL. Come gestire i link non validi si imposta nel presenter con `$this->invalidLinkMode`. Può assumere una combinazione di questi valori (costanti): -- `Presenter::InvalidLinkSilent` - modalità silenziosa, come URL viene restituito il carattere # -- `Presenter::InvalidLinkWarning` - viene generato un avviso E_USER_WARNING, che verrà registrato in modalità produzione, ma non causerà l'interruzione dell'esecuzione dello script +- `Presenter::InvalidLinkSilent` - modalità silenziosa, restituisce come URL il carattere # +- `Presenter::InvalidLinkWarning` - viene emesso un avviso E_USER_WARNING, che in modalità di produzione verrà registrato nel log ma non interromperà l'esecuzione dello script - `Presenter::InvalidLinkTextual` - avviso visivo, stampa l'errore direttamente nel link -- `Presenter::InvalidLinkException` - viene lanciata l'eccezione InvalidLinkException +- `Presenter::InvalidLinkException` - solleva InvalidLinkException -L'impostazione predefinita è `InvalidLinkWarning` in modalità produzione e `InvalidLinkWarning | InvalidLinkTextual` in modalità sviluppo. `InvalidLinkWarning` nell'ambiente di produzione non causerà l'interruzione dello script, ma l'avviso verrà registrato. Nell'ambiente di sviluppo, verrà catturato da [Tracy |tracy:] e verrà visualizzato un bluescreen. `InvalidLinkTextual` funziona restituendo un messaggio di errore come URL, che inizia con i caratteri `#error:`. Per rendere tali link evidenti a prima vista, aggiungiamo al CSS: +L'impostazione predefinita è `InvalidLinkWarning` in modalità di produzione e `InvalidLinkWarning | InvalidLinkTextual` in modalità di sviluppo. In ambiente di produzione `InvalidLinkWarning` non provoca l'interruzione dello script, ma l'avviso verrà registrato nel log. In ambiente di sviluppo lo intercetta [Tracy |tracy:] e mostra una schermata blu. `InvalidLinkTextual` funziona restituendo come URL un messaggio di errore che inizia con i caratteri `#error:`. Perché link del genere si notino a colpo d'occhio, aggiungete al vostro CSS: ```css a[href^="#error:"] { @@ -266,7 +299,7 @@ a[href^="#error:"] { } ``` -Se non vogliamo che vengano prodotti avvisi nell'ambiente di sviluppo, possiamo impostare la modalità silenziosa direttamente nella [configurazione|configuration]. +Se non vogliamo che in ambiente di sviluppo vengano emessi avvisi, possiamo silenziarli direttamente nella [configurazione|configuration]. ```neon application: @@ -277,10 +310,10 @@ application: LinkGenerator ============= -Come creare link con una comodità simile a quella del metodo `link()`, ma senza la presenza di un presenter? Per questo c'è [api:Nette\Application\LinkGenerator]. +Come creare link con la stessa comodità del metodo `link()`, ma senza la presenza di un presenter? A questo serve [api:Nette\Application\LinkGenerator]. -LinkGenerator è un servizio che puoi farti passare tramite il costruttore e poi creare link con il suo metodo `link()`. +LinkGenerator è un servizio che potete farvi passare tramite il costruttore e con cui potete poi creare link usandone il metodo `link()`. -Rispetto ai presenter, c'è una differenza. LinkGenerator crea tutti i link direttamente come URL assoluti. Inoltre, non esiste un "presenter corrente", quindi non è possibile specificare solo il nome dell'azione `link('default')` come destinazione o specificare percorsi relativi ai moduli. +C'è una differenza rispetto ai presenter. LinkGenerator crea tutti i link direttamente come URL assoluti. Inoltre non esiste un "presenter corrente", quindi non è possibile indicare come destinazione solo il nome dell'azione, `link('default')`, né usare percorsi relativi ai moduli. -I link non validi lanciano sempre `Nette\Application\UI\InvalidLinkException`. +I link non validi sollevano sempre `Nette\Application\UI\InvalidLinkException`. diff --git a/application/it/directory-structure.texy b/application/it/directory-structure.texy index 7ed91fdfa6..f4a1ae9c48 100644 --- a/application/it/directory-structure.texy +++ b/application/it/directory-structure.texy @@ -1,29 +1,29 @@ -Struttura della Directory dell'Applicazione +Struttura delle directory dell'applicazione *******************************************
    -Come progettare una struttura di directory chiara e scalabile per i progetti in Nette Framework? Mostreremo le best practice che ti aiuteranno a organizzare il codice. Imparerai: +Come progettare una struttura di directory chiara e scalabile per i progetti in Nette Framework? Vi mostreremo pratiche collaudate che vi aiuteranno a organizzare il codice. Imparerete: -- come **dividere logicamente** l'applicazione in directory -- come progettare la struttura in modo che **scali bene** con la crescita del progetto -- quali sono le **alternative possibili** e i loro vantaggi o svantaggi +- come strutturare **logicamente** l'applicazione in directory +- come progettare la struttura perché **scali bene** con la crescita del progetto +- quali sono le **alternative possibili** e i loro pregi o difetti
    -È importante menzionare che Nette Framework stesso non impone alcuna struttura specifica. È progettato per essere facilmente adattabile a qualsiasi esigenza e preferenza. +È importante dire che Nette Framework stesso non impone alcuna struttura specifica. È progettato per adattarsi facilmente a qualsiasi esigenza e preferenza. Struttura di base del progetto ============================== -Sebbene Nette Framework non detti alcuna struttura di directory fissa, esiste una disposizione predefinita comprovata sotto forma di [Web Project|https://github.com/nette/web-project]: +Benché Nette Framework non imponga alcuna struttura fissa di directory, esiste una disposizione predefinita collaudata, sotto forma di [Web Project|https://github.com/nette/web-project]: /--pre web-project/ -├── app/ ← directory con l'applicazione -├── assets/ ← file SCSS, JS, immagini..., alternativamente resources/ +├── app/ ← directory dell'applicazione +├── assets/ ← file SCSS, JS, immagini..., in alternativa resources/ ├── bin/ ← script per la riga di comando ├── config/ ← configurazione ├── log/ ← errori registrati @@ -33,13 +33,13 @@ Sebbene Nette Framework non detti alcuna struttura di directory fissa, esiste un └── www/ ← directory pubblica (document-root) \-- -Puoi modificare liberamente questa struttura in base alle tue esigenze - rinominare o spostare le cartelle. Successivamente, basta solo aggiornare i percorsi relativi alle directory nel file `Bootstrap.php` e eventualmente `composer.json`. Non è necessario nient'altro, nessuna riconfigurazione complessa, nessuna modifica delle costanti. Nette dispone di un intelligente autodetect e riconosce automaticamente la posizione dell'applicazione, inclusa la sua base URL. +Potete modificare liberamente questa struttura secondo le vostre esigenze, rinominando o spostando le cartelle. Dovete poi solo sistemare i percorsi relativi delle directory in `Bootstrap.php` ed eventualmente in `composer.json`. Non serve nient'altro: nessuna riconfigurazione complicata, nessuna modifica alle costanti. Nette dispone di un rilevamento automatico intelligente e riconosce da sé la posizione dell'applicazione, compresa la base del suo URL. Principi di organizzazione del codice ===================================== -Quando esplori per la prima volta un nuovo progetto, dovresti orientarti rapidamente. Immagina di espandere la directory `app/Model/` e vedere questa struttura: +Quando esplorate per la prima volta un nuovo progetto, dovreste riuscire a orientarvi rapidamente. Immaginate di cliccare sulla directory `app/Model/` e di vedere questa struttura: /--pre app/Model/ @@ -48,9 +48,9 @@ Quando esplori per la prima volta un nuovo progetto, dovresti orientarti rapidam └── Entities/ \-- -Da essa deduci solo che il progetto utilizza alcuni servizi, repository ed entità. Non impari assolutamente nulla sullo scopo effettivo dell'applicazione. +Da qui imparate solo che il progetto usa dei servizi, dei repository e delle entità. Non imparate nulla sullo scopo reale dell'applicazione. -Vediamo un approccio diverso - **organizzazione per domini**: +Guardiamo un approccio diverso, l'**organizzazione per domini**: /--pre app/Model/ @@ -60,76 +60,76 @@ Vediamo un approccio diverso - **organizzazione per domini**: └── Product/ \-- -Qui è diverso - a prima vista è chiaro che si tratta di un e-shop. I nomi stessi delle directory rivelano cosa sa fare l'applicazione - lavora con pagamenti, ordini e prodotti. +Qui è diverso: a colpo d'occhio è chiaro che si tratta di un e-shop. I nomi stessi delle directory rivelano cosa sa fare l'applicazione: lavora con pagamenti, ordini e prodotti. -Il primo approccio (organizzazione per tipo di classi) porta in pratica una serie di problemi: il codice che è logicamente correlato è frammentato in diverse cartelle e devi saltare tra di esse. Pertanto, organizzeremo per domini. +Il primo approccio (l'organizzazione per tipo di classe) porta nella pratica diversi problemi: il codice logicamente collegato è frammentato in cartelle diverse e dovete saltare tra di esse. Organizzeremo quindi per domini. Namespace --------- -È consuetudine che la struttura delle directory corrisponda ai namespace nell'applicazione. Ciò significa che la posizione fisica dei file corrisponde al loro namespace. Ad esempio, una classe situata in `app/Model/Product/ProductRepository.php` dovrebbe avere il namespace `App\Model\Product`. Questo principio aiuta nell'orientamento nel codice e semplifica l'autoloading. +È consuetudine che la struttura delle directory corrisponda ai namespace dell'applicazione. Questo significa che la posizione fisica dei file coincide con il loro namespace. Per esempio, una classe che si trova in `app/Model/Product/ProductRepository.php` dovrebbe avere il namespace `App\Model\Product`. Questo principio aiuta a orientarsi nel codice e semplifica l'autoloading. -Singolare vs Plurale nei nomi ------------------------------ +Singolare o plurale nei nomi +---------------------------- -Nota che per le directory principali dell'applicazione usiamo il singolare: `app`, `config`, `log`, `temp`, `www`. Allo stesso modo anche all'interno dell'applicazione: `Model`, `Core`, `Presentation`. Questo perché ognuna di esse rappresenta un concetto unitario. +Notate che per le directory principali dell'applicazione usiamo il singolare: `app`, `config`, `log`, `temp`, `www`. Lo stesso vale dentro l'applicazione: `Model`, `Core`, `Presentation`. Questo perché ciascuna rappresenta un unico concetto coerente. -Allo stesso modo, ad esempio, `app/Model/Product` rappresenta tutto ciò che riguarda i prodotti. Non lo chiameremo `Products`, perché non è una cartella piena di prodotti (ci sarebbero file `nokia.php`, `samsung.php`). È un namespace contenente classi per lavorare con i prodotti - `ProductRepository.php`, `ProductService.php`. +Allo stesso modo `app/Model/Product` rappresenta tutto ciò che riguarda i prodotti. Non la chiamiamo `Products` perché non è una cartella piena di prodotti (conterrebbe file come `nokia.php`, `samsung.php`). È un namespace che contiene le classi per lavorare con i prodotti: `ProductRepository.php`, `ProductService.php`. -La cartella `app/Tasks` è al plurale perché contiene un insieme di script eseguibili separati - `CleanupTask.php`, `ImportTask.php`. Ognuno di essi è un'unità separata. +La cartella `app/Tasks` è al plurale perché contiene un insieme di script eseguibili separati: `CleanupTask.php`, `ImportTask.php`. Ognuno di essi è un'unità indipendente. -Per coerenza, consigliamo di utilizzare: -- Singolare per namespace che rappresentano un'unità funzionale (anche se lavorano con più entità) -- Plurale per collezioni di unità separate -- In caso di incertezza o se non vuoi pensarci, scegli il singolare +Per coerenza consigliamo di usare: +- il singolare per i namespace che rappresentano un'unità funzionale (anche se lavorano con più entità) +- il plurale per le raccolte di unità indipendenti +- in caso di incertezza, o se non volete pensarci, scegliete il singolare Directory pubblica `www/` ========================= -Questa directory è l'unica accessibile dal web (la cosiddetta document-root). Spesso si può incontrare anche il nome `public/` invece di `www/` - è solo una questione di convenzione e non influisce sulla funzionalità del framework. La directory contiene: -- [Punto di ingresso |bootstrapping#index.php] dell'applicazione `index.php` -- File `.htaccess` con regole per mod_rewrite (per Apache) -- File statici (CSS, JavaScript, immagini) -- File caricati +Questa directory è l'unica accessibile dal web (il document-root). Spesso potreste incontrare il nome `public/` al posto di `www/`: è solo una questione di convenzione e non influisce sul funzionamento dell'applicazione. La directory contiene: +- il [punto d'ingresso |bootstrapping#index.php] dell'applicazione `index.php` +- il file `.htaccess` con le regole di mod_rewrite (per Apache) +- i file statici (CSS, JavaScript, immagini) +- i file caricati -Per una corretta sicurezza dell'applicazione, è fondamentale avere la [document-root configurata correttamente |nette:troubleshooting#Come modificare o rimuovere la directory www dall URL]. +Per una corretta sicurezza dell'applicazione è essenziale avere il [document-root configurato correttamente |nette:troubleshooting#Come cambiare o rimuovere la directory www dall'URL?]. .[note] -Non posizionare mai la cartella `node_modules/` in questa directory - contiene migliaia di file che possono essere eseguibili e non dovrebbero essere accessibili pubblicamente. +Non collocate mai la cartella `node_modules/` in questa directory: contiene migliaia di file che potrebbero essere eseguibili e non dovrebbero essere accessibili pubblicamente. Directory dell'applicazione `app/` ================================== -Questa è la directory principale con il codice dell'applicazione. Struttura di base: +È la directory principale, che contiene il codice dell'applicazione. Struttura di base: /--pre app/ ├── Core/ ← questioni infrastrutturali ├── Model/ ← logica di business ├── Presentation/ ← presenter e template -├── Tasks/ ← script di comando +├── Tasks/ ← script da riga di comando └── Bootstrap.php ← classe di avvio dell'applicazione \-- `Bootstrap.php` è la [classe di avvio dell'applicazione|bootstrapping], che inizializza l'ambiente, carica la configurazione e crea il container DI. -Vediamo ora più nel dettaglio le singole sottodirectory. +Vediamo ora più in dettaglio le singole sottodirectory. Presenter e template ==================== -La parte di presentazione dell'applicazione si trova nella directory `app/Presentation`. Un'alternativa è la breve `app/UI`. È il posto per tutti i presenter, i loro template e eventuali classi di supporto. +La parte di presentazione dell'applicazione si trova nella directory `app/Presentation`. Un'alternativa è la più breve `app/UI`. È il posto di tutti i presenter, dei loro template e delle eventuali classi di supporto collegate. -Organizziamo questo layer per domini. In un progetto complesso che combina e-shop, blog e API, la struttura sarebbe simile a questa: +Organizziamo questo strato per domini. In un progetto complesso che unisce un e-shop, un blog e un'API, la struttura avrebbe questo aspetto: /--pre app/Presentation/ -├── Shop/ ← frontend e-shop +├── Shop/ ← frontend dell'e-shop │ ├── Product/ │ ├── Cart/ │ └── Order/ @@ -139,11 +139,11 @@ Organizziamo questo layer per domini. In un progetto complesso che combina e-sho ├── Admin/ ← amministrazione │ ├── Dashboard/ │ └── Products/ -└── Api/ ← endpoint API +└── Api/ ← endpoint dell'API └── V1/ \-- -Al contrario, per un semplice blog, useremmo la seguente suddivisione: +Al contrario, per un semplice blog useremmo questa struttura: /--pre app/Presentation/ @@ -154,12 +154,12 @@ Al contrario, per un semplice blog, useremmo la seguente suddivisione: │ ├── Dashboard/ │ └── Posts/ ├── Error/ -└── Export/ ← RSS, sitemap, ecc. +└── Export/ ← RSS, sitemap ecc. \-- -Cartelle come `Home/` o `Dashboard/` contengono presenter e template. Cartelle come `Front/`, `Admin/` o `Api/` le chiamiamo **moduli**. Tecnicamente, sono directory normali che servono a dividere logicamente l'applicazione. +Cartelle come `Home/` o `Dashboard/` contengono presenter e template. Cartelle come `Front/`, `Admin/` o `Api/` si chiamano **moduli**. Tecnicamente sono normali directory usate per la suddivisione logica dell'applicazione. -Ogni cartella con un presenter contiene un presenter con lo stesso nome e i suoi template. Ad esempio, la cartella `Dashboard/` contiene: +Ogni cartella che contiene un presenter comprende il file del presenter stesso e i suoi template. Per esempio la cartella `Dashboard/` contiene: /--pre Dashboard/ @@ -167,7 +167,7 @@ Ogni cartella con un presenter contiene un presenter con lo stesso nome e i suoi └── default.latte ← template \-- -Questa struttura di directory si riflette nei namespace delle classi. Ad esempio, `DashboardPresenter` si trova nel namespace `App\Presentation\Admin\Dashboard` (vedi [#Mappatura dei presenter]): +Questa struttura di directory si riflette nei namespace delle classi. Per esempio `DashboardPresenter` si trova nel namespace `App\Presentation\Admin\Dashboard` (vedi [#Mapping dei presenter]): ```php namespace App\Presentation\Admin\Dashboard; @@ -178,22 +178,22 @@ class DashboardPresenter extends Nette\Application\UI\Presenter } ``` -Al presenter `Dashboard` all'interno del modulo `Admin` facciamo riferimento nell'applicazione usando la notazione con i due punti come `Admin:Dashboard`. Alla sua azione `default` poi come `Admin:Dashboard:default`. In caso di moduli nidificati, usiamo più due punti, ad esempio `Shop:Order:Detail:default`. +Nell'applicazione ci riferiamo al presenter `Dashboard` del modulo `Admin` con la notazione a due punti, come `Admin:Dashboard`. La sua azione `default` si indica poi come `Admin:Dashboard:default`. Per i moduli annidati usiamo più volte i due punti, per esempio `Shop:Order:Detail:default`. Sviluppo flessibile della struttura ----------------------------------- -Uno dei grandi vantaggi di questa struttura è come si adatta elegantemente alle crescenti esigenze del progetto. Prendiamo come esempio la parte che genera feed XML. All'inizio abbiamo una forma semplice: +Uno dei grandi vantaggi di questa struttura è la sua eleganza nell'adattarsi alle esigenze crescenti del progetto. Prendiamo come esempio la parte che genera i feed XML. All'inizio abbiamo una forma semplice: /--pre Export/ ├── ExportPresenter.php ← un presenter per tutte le esportazioni -├── sitemap.latte ← template per la sitemap -└── feed.latte ← template per il feed RSS +├── sitemap.latte ← template della sitemap +└── feed.latte ← template del feed RSS \-- -Con il tempo, si aggiungono altri tipi di feed e abbiamo bisogno di più logica per essi... Nessun problema! La cartella `Export/` diventa semplicemente un modulo: +Col tempo si aggiungono altri tipi di feed e ci serve più logica per gestirli... Nessun problema! La cartella `Export/` diventa semplicemente un modulo: /--pre Export/ @@ -202,38 +202,38 @@ Con il tempo, si aggiungono altri tipi di feed e abbiamo bisogno di più logica │ └── sitemap.latte └── Feed/ ├── FeedPresenter.php - ├── zbozi.latte ← feed per Zboží.cz - └── heureka.latte ← feed per Heureka.cz + ├── amazon.latte ← feed per Amazon + └── ebay.latte ← feed per eBay \-- -Questa trasformazione è assolutamente fluida - basta creare nuove sottocartelle, dividerci il codice e aggiornare i link (ad esempio da `Export:feed` a `Export:Feed:zbozi`). Grazie a ciò, possiamo espandere gradualmente la struttura secondo necessità, il livello di nidificazione non è limitato in alcun modo. +Questa trasformazione è del tutto indolore: basta creare le nuove sottocartelle, dividervi il codice e aggiornare i link (per esempio da `Export:feed` a `Export:Feed:amazon`). Grazie a questo possiamo ampliare gradualmente la struttura secondo necessità, e il livello di annidamento non è limitato in alcun modo. -Se, ad esempio, nell'amministrazione hai molti presenter relativi alla gestione degli ordini, come sono `OrderDetail`, `OrderEdit`, `OrderDispatch` ecc., puoi creare un modulo (cartella) `Order` in questo punto per una migliore organizzazione, che conterrà (le cartelle per) i presenter `Detail`, `Edit`, `Dispatch` e altri. +Se per esempio nell'amministrazione avete molti presenter legati alla gestione degli ordini, come `OrderDetail`, `OrderEdit`, `OrderDispatch` ecc., per una migliore organizzazione potete creare un modulo (una cartella) chiamato `Order`, che conterrà (le cartelle dei) presenter `Detail`, `Edit`, `Dispatch` e altri. -Posizionamento dei template ---------------------------- +Posizione dei template +---------------------- -Negli esempi precedenti abbiamo visto che i template si trovano direttamente nella cartella con il presenter: +Negli esempi precedenti abbiamo visto che i template si trovano direttamente nella cartella del presenter: /--pre Dashboard/ ├── DashboardPresenter.php ← presenter -├── DashboardTemplate.php ← classe opzionale per il template +├── DashboardTemplate.php ← classe del template, facoltativa └── default.latte ← template \-- -Questa posizione si rivela in pratica la più comoda - hai tutti i file correlati subito a portata di mano. +Nella pratica questa collocazione si rivela la più comoda: avete tutti i file collegati subito a portata di mano. -In alternativa, puoi posizionare i template in una sottocartella `templates/`. Nette supporta entrambe le varianti. Puoi persino posizionare i template completamente al di fuori della cartella `Presentation/`. Tutto sulle possibilità di posizionamento dei template si trova nel capitolo [Ricerca dei template |templates#Ricerca dei template]. +In alternativa potete collocare i template in una sottocartella `templates/`. Nette supporta entrambe le varianti. Potete perfino collocare i template completamente fuori dalla cartella `Presentation/`. Tutto ciò che riguarda le possibilità di collocazione dei template si trova nel capitolo [Ricerca dei template |templates#Ricerca dei template]. Classi di supporto e componenti ------------------------------- -Ai presenter e ai template spesso appartengono anche altri file di supporto. Li posizioniamo logicamente in base al loro ambito di applicazione: +Ai presenter e ai template si accompagnano spesso altri file di supporto. Li collochiamo logicamente in base al loro ambito: -1. **Direttamente presso il presenter** nel caso di componenti specifici per quel presenter: +1. **Direttamente con il presenter**, nel caso di componenti specifici di quel presenter: /--pre Product/ @@ -242,7 +242,7 @@ Ai presenter e ai template spesso appartengono anche altri file di supporto. Li └── FilterForm.php ← form per il filtraggio \-- -2. **Per il modulo** - consigliamo di utilizzare la cartella `Accessory`, che si posiziona ordinatamente all'inizio dell'alfabeto: +2. **Per il modulo**: consigliamo di usare la cartella `Accessory`, che in ordine alfabetico si colloca comodamente all'inizio: /--pre Front/ @@ -253,7 +253,7 @@ Ai presenter e ai template spesso appartengono anche altri file di supporto. Li └── Cart/ \-- -3. **Per l'intera applicazione** - in `Presentation/Accessory/`: +3. **Per l'intera applicazione**: in `Presentation/Accessory/`: /--pre app/Presentation/ ├── Accessory/ @@ -263,30 +263,30 @@ Ai presenter e ai template spesso appartengono anche altri file di supporto. Li └── Admin/ \-- -Oppure puoi posizionare classi di supporto come `LatteExtension.php` o `TemplateFilters.php` nella cartella infrastrutturale `app/Core/Latte/`. E i componenti in `app/Components`. La scelta dipende dalle abitudini del team. +In alternativa potete collocare le classi di supporto come `LatteExtension.php` o `TemplateFilters.php` nella cartella infrastrutturale `app/Core/Latte/`. E i componenti in `app/Components`. La scelta dipende dalle convenzioni del team. -Model - il cuore dell'applicazione -================================== +Model, il cuore dell'applicazione +================================= -Il model contiene tutta la logica di business dell'applicazione. Per la sua organizzazione vale di nuovo la regola - strutturiamo per domini: +Il model contiene tutta la logica di business dell'applicazione. La regola per organizzarlo è di nuovo: struttura per domini. /--pre app/Model/ ├── Payment/ ← tutto ciò che riguarda i pagamenti -│ ├── PaymentFacade.php ← punto di ingresso principale +│ ├── PaymentFacade.php ← punto d'ingresso principale │ ├── PaymentRepository.php │ ├── Payment.php ← entità ├── Order/ ← tutto ciò che riguarda gli ordini │ ├── OrderFacade.php │ ├── OrderRepository.php │ ├── Order.php -└── Shipping/ ← tutto ciò che riguarda la spedizione +└── Shipping/ ← tutto ciò che riguarda le spedizioni \-- -Nel model si incontrano tipicamente questi tipi di classi: +Nel model incontrate di norma questi tipi di classi: -**Facade**: rappresentano il punto di ingresso principale a un dominio specifico nell'applicazione. Agiscono come orchestratori che coordinano la collaborazione tra diversi servizi allo scopo di implementare use-case completi (come "crea ordine" o "elabora pagamento"). Sotto il suo layer di orchestrazione, la facade nasconde i dettagli implementativi al resto dell'applicazione, fornendo così un'interfaccia pulita per lavorare con il dominio dato. +**Facade**: rappresentano il punto d'ingresso principale in un determinato dominio dell'applicazione. Fanno da orchestratore e coordinano la collaborazione tra i vari servizi per realizzare interi casi d'uso (come "crea ordine" o "elabora pagamento"). Sotto il proprio strato di orchestrazione la facade nasconde i dettagli implementativi al resto dell'applicazione, offrendo così un'interfaccia pulita per lavorare con quel dominio. ```php class OrderFacade @@ -301,7 +301,7 @@ class OrderFacade } ``` -**Servizi**: si concentrano su un'operazione di business specifica all'interno del dominio. A differenza della facade, che orchestra interi use-case, un servizio implementa una logica di business specifica (come calcoli di prezzi o elaborazione di pagamenti). I servizi sono tipicamente senza stato e possono essere utilizzati sia dalle facade come blocchi di costruzione per operazioni più complesse, sia direttamente da altre parti dell'applicazione per compiti più semplici. +**Servizi**: si concentrano su operazioni di business specifiche all'interno di un dominio. A differenza delle facade, che orchestrano interi casi d'uso, un servizio implementa una logica di business precisa (come il calcolo dei prezzi o l'elaborazione dei pagamenti). I servizi sono di norma privi di stato e possono essere usati sia dalle facade come mattoni di operazioni più complesse, sia direttamente da altre parti dell'applicazione per compiti più semplici. ```php class PricingService @@ -313,7 +313,7 @@ class PricingService } ``` -**Repository**: assicurano tutta la comunicazione con l'archivio dati, tipicamente un database. Il suo compito è caricare e salvare entità e implementare metodi per la loro ricerca. Il repository isola il resto dell'applicazione dai dettagli implementativi del database e fornisce un'interfaccia orientata agli oggetti per lavorare con i dati. +**Repository**: gestiscono tutta la comunicazione con l'archivio dei dati, di norma un database. Il loro compito è caricare e salvare le entità e implementare i metodi per cercarle. Un repository protegge il resto dell'applicazione dai dettagli implementativi del database e offre un'interfaccia orientata agli oggetti per lavorare con i dati. ```php class OrderRepository @@ -328,10 +328,10 @@ class OrderRepository } ``` -**Entità**: oggetti che rappresentano i principali concetti di business nell'applicazione, che hanno una loro identità e cambiano nel tempo. Tipicamente si tratta di classi mappate su tabelle di database tramite ORM (come Nette Database Explorer o Doctrine). Le entità possono contenere regole di business relative ai loro dati e logica di validazione. +**Entità**: oggetti che rappresentano i principali concetti di business dell'applicazione, che hanno una propria identità e cambiano nel tempo. Di norma sono classi mappate sulle tabelle del database tramite un ORM (come Nette Database Explorer o Doctrine). Le entità possono contenere regole di business relative ai propri dati e logica di validazione. ```php -// Entità mappata sulla tabella di database orders +// entità mappata sulla tabella di database 'orders' class Order extends Nette\Database\Table\ActiveRow { public function addItem(Product $product, int $quantity): void @@ -345,17 +345,17 @@ class Order extends Nette\Database\Table\ActiveRow } ``` -**Value object**: oggetti immutabili che rappresentano valori senza una propria identità - ad esempio un importo monetario o un indirizzo e-mail. Due istanze di un value object con gli stessi valori sono considerate identiche. +**Value object**: oggetti immutabili che rappresentano valori privi di identità propria, per esempio un importo monetario o un indirizzo e-mail. Due istanze di un value object con gli stessi valori sono considerate identiche. Codice infrastrutturale ======================= -La cartella `Core/` (o anche `Infrastructure/`) è la casa della base tecnica dell'applicazione. Il codice infrastrutturale include tipicamente: +La cartella `Core/` (o, in alternativa, `Infrastructure/`) ospita le fondamenta tecniche dell'applicazione. Il codice infrastrutturale comprende di norma: /--pre app/Core/ -├── Router/ ← routing e gestione URL +├── Router/ ← routing e gestione degli URL │ └── RouterFactory.php ├── Security/ ← autenticazione e autorizzazione │ ├── Authenticator.php @@ -363,14 +363,14 @@ La cartella `Core/` (o anche `Infrastructure/`) è la casa della base tecnica de ├── Logging/ ← logging e monitoraggio │ ├── SentryLogger.php │ └── FileLogger.php -├── Cache/ ← layer di caching +├── Cache/ ← strato di caching │ └── FullPageCache.php -└── Integration/ ← integrazione con servizi est. +└── Integration/ ← integrazione con servizi esterni ├── Slack/ └── Stripe/ \-- -Per progetti più piccoli, ovviamente, basta una suddivisione piatta: +Per i progetti più piccoli basta naturalmente una struttura piatta: /--pre Core/ @@ -379,121 +379,121 @@ Per progetti più piccoli, ovviamente, basta una suddivisione piatta: └── QueueMailer.php \-- -Si tratta di codice che: +È il codice che: -- Risolve l'infrastruttura tecnica (routing, logging, caching) -- Integra servizi esterni (Sentry, Elasticsearch, Redis) -- Fornisce servizi di base per l'intera applicazione (mail, database) -- È per lo più indipendente dal dominio specifico - la cache o il logger funzionano allo stesso modo per un eshop o un blog. +- si occupa dell'infrastruttura tecnica (routing, logging, caching) +- integra servizi esterni (Sentry, Elasticsearch, Redis) +- offre servizi di base all'intera applicazione (posta, database) +- è per lo più indipendente da un dominio specifico: la cache o il logger funzionano allo stesso modo per un e-shop o per un blog. -Hai dubbi se una certa classe appartiene qui o al model? La differenza chiave è che il codice in `Core/`: +Vi state chiedendo se una certa classe appartenga a questa cartella o al model? La differenza fondamentale è che il codice in `Core/`: -- Non sa nulla del dominio (prodotti, ordini, articoli) -- È per lo più possibile trasferirlo a un altro progetto -- Risolve "come funziona" (come inviare una mail), non "cosa fa" (quale mail inviare) +- non sa nulla del dominio (prodotti, ordini, articoli) +- di norma si può trasferire in un altro progetto +- risolve il "come funziona" (come inviare un'e-mail), non il "cosa fa" (quale e-mail inviare) -Esempio per una migliore comprensione: +Un esempio per capire meglio: -- `App\Core\MailerFactory` - crea istanze della classe per l'invio di e-mail, gestisce le impostazioni SMTP -- `App\Model\OrderMailer` - utilizza `MailerFactory` per inviare e-mail sugli ordini, conosce i loro template e sa quando devono essere inviati +- `App\Core\MailerFactory` - crea istanze della classe per inviare e-mail, gestisce le impostazioni SMTP +- `App\Model\OrderMailer` - usa `MailerFactory` per inviare le e-mail relative agli ordini, ne conosce i template e sa quando vanno inviate -Script di comando -================= +Script da riga di comando +========================= -Le applicazioni spesso necessitano di eseguire attività al di fuori delle normali richieste HTTP - che si tratti di elaborazione dati in background, manutenzione o attività periodiche. Per l'esecuzione servono semplici script nella directory `bin/`, la logica implementativa la posizioniamo poi in `app/Tasks/` (eventualmente `app/Commands/`). +Le applicazioni hanno spesso bisogno di svolgere attività fuori dalle normali richieste HTTP: che si tratti di elaborazione dati in background, di manutenzione o di attività periodiche. Per l'esecuzione si usano semplici script nella directory `bin/`, mentre la logica implementativa vera e propria si colloca in `app/Tasks/` (o in `app/Commands/`). Esempio: /--pre app/Tasks/ ├── Maintenance/ ← script di manutenzione -│ ├── CleanupCommand.php ← cancellazione di dati vecchi +│ ├── CleanupCommand.php ← eliminazione dei dati vecchi │ └── DbOptimizeCommand.php ← ottimizzazione del database ├── Integration/ ← integrazione con sistemi esterni │ ├── ImportProducts.php ← importazione dal sistema del fornitore │ └── SyncOrders.php ← sincronizzazione degli ordini -└── Scheduled/ ← attività regolari - ├── NewsletterCommand.php ← invio di newsletter +└── Scheduled/ ← attività periodiche + ├── NewsletterCommand.php ← invio delle newsletter └── ReminderCommand.php ← notifiche ai clienti \-- -Cosa appartiene al model e cosa agli script di comando? Ad esempio, la logica per l'invio di una singola e-mail fa parte del model, l'invio massivo di migliaia di e-mail appartiene già a `Tasks/`. +Cosa appartiene al model e cosa agli script da riga di comando? Per esempio la logica per inviare una singola e-mail fa parte del model, mentre l'invio massivo di migliaia di e-mail appartiene a `Tasks/`. -Le attività vengono solitamente [eseguite dalla riga di comando |https://blog.nette.org/en/cli-scripts-in-nette-application] o tramite cron. Possono essere eseguite anche tramite richiesta HTTP, ma è necessario pensare alla sicurezza. Il presenter che avvia l'attività deve essere protetto, ad esempio solo per utenti loggati o con un token forte e accesso da indirizzi IP consentiti. Per attività lunghe è necessario aumentare il limite di tempo dello script e utilizzare `session_write_close()`, per non bloccare la sessione. +I task si eseguono di solito dalla riga di comando o via cron: lo script in `bin/` crea il container DI con il metodo [bootConsoleApplication() |bootstrapping#Ambienti diversi] e ne estrae il servizio necessario. Si possono eseguire anche tramite una richiesta HTTP, ma bisogna pensare alla sicurezza. Il presenter che esegue il task va protetto, per esempio consentendolo solo agli utenti connessi oppure con un token robusto e l'accesso da indirizzi IP autorizzati. Per i task di lunga durata è necessario aumentare il limite di tempo dello script e usare `session_write_close()` per non bloccare la sessione. -Altre possibili directory +Altre directory possibili ========================= -Oltre alle directory di base menzionate, puoi aggiungere altre cartelle specializzate in base alle esigenze del progetto. Vediamo le più comuni e il loro utilizzo: +Oltre alle directory di base già menzionate, potete aggiungere altre cartelle specializzate secondo le esigenze del progetto. Vediamo le più comuni e il loro uso: /--pre app/ -├── Api/ ← logica per API indipendente dal layer di presentazione -├── Database/ ← script di migrazione e seeder per dati di test +├── Api/ ← logica dell'API, indipendente dallo strato di presentazione +├── Database/ ← script di migrazione e seeder per i dati di test ├── Components/ ← componenti visivi condivisi in tutta l'applicazione -├── Event/ ← utile se usi architettura event-driven -├── Mail/ ← template e-mail e logica correlata -└── Utils/ ← classi di utilità +├── Event/ ← utile se usate un'architettura a eventi +├── Mail/ ← template delle e-mail e logica collegata +└── Utils/ ← classi di supporto \-- -Per i componenti visivi condivisi utilizzati nei presenter in tutta l'applicazione, è possibile utilizzare la cartella `app/Components` o `app/Controls`: +Per i componenti visivi condivisi, usati nei presenter di tutta l'applicazione, potete usare la cartella `app/Components` o `app/Controls`: /--pre app/Components/ -├── Form/ ← componenti form condivisi +├── Form/ ← componenti di form condivisi │ ├── SignInForm.php │ └── UserForm.php -├── Grid/ ← componenti per elenchi di dati +├── Grid/ ← componenti per gli elenchi di dati │ └── DataGrid.php └── Navigation/ ← elementi di navigazione ├── Breadcrumbs.php └── Menu.php \-- -Qui appartengono i componenti che hanno una logica più complessa. Se vuoi condividere componenti tra più progetti, è consigliabile estrarli in un pacchetto composer separato. +È qui che appartengono i componenti con logica più complessa. Se volete condividere i componenti tra più progetti, conviene estrarli in un pacchetto Composer separato. -Nella directory `app/Mail` puoi posizionare la gestione della comunicazione e-mail: +Nella directory `app/Mail` potete collocare la gestione della comunicazione via e-mail: /--pre app/Mail/ -├── templates/ ← template e-mail +├── templates/ ← template delle e-mail │ ├── order-confirmation.latte │ └── welcome.latte └── OrderMailer.php \-- -Mappatura dei presenter -======================= +Mapping dei presenter +===================== -La mappatura definisce le regole per derivare il nome della classe dal nome del presenter. Le specifichiamo nella [configurazione|configuration] sotto la chiave `application › mapping`. +Il mapping definisce le regole per ricavare il nome della classe dal nome del presenter. Le indichiamo nella [configurazione|configuration], sotto la chiave `application › mapping`. -In questa pagina abbiamo mostrato che posizioniamo i presenter nella cartella `app/Presentation` (eventualmente `app/UI`). Dobbiamo comunicare questa convenzione a Nette nel file di configurazione. Basta una riga: +In questa pagina abbiamo mostrato che collochiamo i presenter nella cartella `app/Presentation` (o `app/UI`). Da Nette Application 3.3 questa è la convenzione predefinita, che non serve configurare. Se usate una struttura diversa o volete indicare il mapping esplicitamente, l'impostazione predefinita corrisponde a questa riga: ```neon application: mapping: App\Presentation\*\**Presenter ``` -Come funziona la mappatura? Per una migliore comprensione, immaginiamo prima un'applicazione senza moduli. Vogliamo che le classi dei presenter rientrino nel namespace `App\Presentation`, in modo che il presenter `Home` si mappi sulla classe `App\Presentation\HomePresenter`. Cosa che otteniamo con questa configurazione: +Come funziona il mapping? Per capire meglio, immaginiamo prima un'applicazione senza moduli. Vogliamo che le classi dei presenter ricadano nel namespace `App\Presentation`, così che il presenter `Home` sia mappato sulla classe `App\Presentation\HomePresenter`. Lo si ottiene con questa configurazione: ```neon application: mapping: App\Presentation\*Presenter ``` -La mappatura funziona in modo che il nome del presenter `Home` sostituisca l'asterisco nella maschera `App\Presentation\*Presenter`, ottenendo così il nome della classe risultante `App\Presentation\HomePresenter`. Semplice! +Il mapping funziona sostituendo l'asterisco nella maschera `App\Presentation\*Presenter` con il nome del presenter `Home`, ottenendo il nome finale della classe `App\Presentation\HomePresenter`. Semplice! -Come però vedi negli esempi in questo e altri capitoli, posizioniamo le classi dei presenter in sottodirectory omonime, ad esempio il presenter `Home` si mappa sulla classe `App\Presentation\Home\HomePresenter`. Otteniamo ciò raddoppiando i due punti (richiede Nette Application 3.2): +Come vedete negli esempi di questo e di altri capitoli, però, collochiamo le classi dei presenter in sottodirectory omonime: per esempio il presenter `Home` è mappato sulla classe `App\Presentation\Home\HomePresenter`. Lo otteniamo usando il doppio asterisco `**` (richiede Nette Application 3.2.3): ```neon application: mapping: App\Presentation\**Presenter ``` -Ora passiamo alla mappatura dei presenter nei moduli. Per ogni modulo possiamo definire una mappatura specifica: +Passiamo ora al mapping dei presenter nei moduli. Possiamo definire un mapping specifico per ogni modulo: ```neon application: @@ -503,9 +503,9 @@ application: Api: App\Api\*Presenter ``` -Secondo questa configurazione, il presenter `Front:Home` si mappa sulla classe `App\Presentation\Front\Home\HomePresenter`, mentre il presenter `Api:OAuth` sulla classe `App\Api\OAuthPresenter`. +Secondo questa configurazione il presenter `Front:Home` è mappato sulla classe `App\Presentation\Front\Home\HomePresenter`, mentre il presenter `Api:OAuth` è mappato sulla classe `App\Api\OAuthPresenter`. -Poiché i moduli `Front` e `Admin` hanno un modo simile di mappatura e probabilmente ci saranno più moduli di questo tipo, è possibile creare una regola generale che li sostituisca. Alla maschera della classe si aggiunge così un nuovo asterisco per il modulo: +Poiché i moduli `Front` e `Admin` hanno uno schema di mapping simile, e di moduli così ce ne saranno probabilmente altri, è possibile creare una regola generale che li sostituisca. Nella maschera della classe si aggiunge un nuovo asterisco per il modulo: ```neon application: @@ -514,9 +514,9 @@ application: Api: App\Api\*Presenter ``` -Funziona anche per strutture di directory più profondamente nidificate, come ad esempio il presenter `Admin:User:Edit`, il segmento con l'asterisco si ripete per ogni livello e il risultato è la classe `App\Presentation\Admin\User\Edit\EditPresenter`. +Funziona anche per strutture di directory annidate più in profondità, come il presenter `Admin:User:Edit`, dove il segmento con l'asterisco si ripete per ogni livello di modulo, dando come risultato la classe `App\Presentation\Admin\User\Edit\EditPresenter`. -Una notazione alternativa è usare un array composto da tre segmenti invece di una stringa. Questa notazione è equivalente alla precedente: +Una notazione alternativa è usare, al posto di una stringa, un array composto da tre segmenti. Per gli esempi mostrati sopra questa notazione equivale alla precedente: ```neon application: diff --git a/application/it/how-it-works.texy b/application/it/how-it-works.texy index 5231df6437..faf548f9db 100644 --- a/application/it/how-it-works.texy +++ b/application/it/how-it-works.texy @@ -3,11 +3,11 @@ Come funzionano le applicazioni?
    -Stai leggendo il documento fondamentale della documentazione di Nette. Imparerai l'intero principio di funzionamento delle applicazioni web. Dalla A alla Z, dal momento della nascita fino all'ultimo respiro dello script PHP. Dopo aver letto, saprai: +State leggendo il capitolo fondamentale della documentazione di Nette. Imparerete tutti i principi del funzionamento delle applicazioni web, dalla A alla Z, dal momento in cui nasce una richiesta fino a quando lo script PHP termina l'esecuzione. Dopo la lettura capirete: - come funziona il tutto -- cos'è Bootstrap, Presenter e il container DI -- come appare la struttura delle directory +- cosa sono Bootstrap, il presenter e il container DI +- che aspetto ha la struttura delle directory
    @@ -15,14 +15,14 @@ Stai leggendo il documento fondamentale della documentazione di Nette. Imparerai Struttura delle directory ========================= -Apri l'esempio dello scheletro dell'applicazione web chiamato [WebProject|https://github.com/nette/web-project] e mentre leggi, puoi guardare i file di cui si parla. +Aprite lo scheletro di esempio di un'applicazione web, chiamato [WebProject|https://github.com/nette/web-project]. Mentre leggete potete consultare i file di cui si parla. -La struttura delle directory assomiglia a qualcosa del genere: +La struttura delle directory ha più o meno questo aspetto: /--pre web-project/ -├── app/ ← directory con l'applicazione -│ ├── Core/ ← classi di base necessarie per il funzionamento +├── app/ ← directory dell'applicazione +│ ├── Core/ ← classi fondamentali necessarie al funzionamento │ │ └── RouterFactory.php ← configurazione degli indirizzi URL │ ├── Presentation/ ← presenter, template & co. │ │ ├── @layout.latte ← template del layout @@ -40,64 +40,64 @@ La struttura delle directory assomiglia a qualcosa del genere: ├── vendor/ ← librerie installate da Composer │ ├── ... │ └── autoload.php ← autoloading di tutti i pacchetti installati -├── www/ ← directory pubblica o document-root del progetto +├── www/ ← directory pubblica, document root del progetto │ ├── assets/ ← file statici compilati (CSS, JS, immagini, ...) -│ ├── .htaccess ← regole mod_rewrite -│ └── index.php ← file iniziale con cui si avvia l'applicazione +│ ├── .htaccess ← regole di mod_rewrite +│ └── index.php ← file iniziale che avvia l'applicazione └── .htaccess ← vieta l'accesso a tutte le directory tranne www \-- -Puoi modificare la struttura delle directory come preferisci, rinominare o spostare le cartelle, è completamente flessibile. Nette dispone inoltre di un intelligente autodetect e riconosce automaticamente la posizione dell'applicazione, inclusa la sua base URL. +Potete cambiare la struttura delle directory in qualsiasi modo, rinominare o spostare le cartelle: è del tutto flessibile. Nette dispone inoltre di un rilevamento automatico intelligente e riconosce da sé la posizione dell'applicazione, compresa la base del suo URL. -Per applicazioni un po' più grandi, possiamo [dividere le cartelle con presenter e template in sottodirectory |directory-structure#Presenter e template] e le classi in namespace, che chiamiamo moduli. +Nelle applicazioni un po' più grandi possiamo organizzare le cartelle dei presenter e dei template in [sottodirectory |directory-structure#Presenter e template] e raggruppare le classi in namespace, che chiamiamo moduli. -La directory `www/` rappresenta la cosiddetta directory pubblica o document-root del progetto. Puoi rinominarla senza dover configurare nient'altro lato applicazione. È solo necessario [configurare l'hosting |nette:troubleshooting#Come modificare o rimuovere la directory www dall URL] in modo che la document-root punti a questa directory. +La directory `www/` rappresenta la directory pubblica, il document-root del progetto. Potete rinominarla senza dover configurare nient'altro sul lato applicativo. Basta [configurare l'hosting |nette:troubleshooting#Come cambiare o rimuovere la directory www dall'URL?] in modo che il document-root punti a questa directory. -Puoi anche scaricare direttamente WebProject incluso Nette usando [Composer |best-practices:composer]: +Potete anche scaricare WebProject direttamente, Nette compreso, con [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -Su Linux o macOS, imposta i [permessi di scrittura |nette:troubleshooting#Impostazione dei permessi delle directory] per le directory `log/` e `temp/`. +Su Linux o macOS impostate i [permessi di scrittura |nette:troubleshooting#Impostazione dei permessi delle directory] per le directory `log/` e `temp/`. -L'applicazione WebProject è pronta per essere eseguita, non è necessario configurare assolutamente nulla e puoi visualizzarla direttamente nel browser accedendo alla cartella `www/`. +L'applicazione WebProject è pronta all'uso: non c'è proprio nulla da configurare e potete vederla subito nel browser accedendo alla cartella `www/`. Richiesta HTTP ============== -Tutto inizia nel momento in cui l'utente apre una pagina nel browser. Cioè quando il browser bussa al server con una richiesta HTTP. La richiesta punta a un unico file PHP, che si trova nella directory pubblica `www/`, e questo è `index.php`. Supponiamo che si tratti di una richiesta all'indirizzo `https://example.com/product/123`. Grazie a un'adeguata [configurazione del server |nette:troubleshooting#Come configurare il server per URL leggibili] anche questo URL viene mappato sul file `index.php` e questo viene eseguito. +Tutto inizia quando un utente apre una pagina nel proprio browser. Il browser invia una richiesta HTTP al server. Questa richiesta punta a un unico file PHP che si trova nella directory pubblica `www/`, cioè `index.php`. Supponiamo che la richiesta sia per l'indirizzo `https://example.com/product/123`. Grazie a un'opportuna [configurazione del server |nette:troubleshooting#Come configurare il server per gli URL leggibili?], anche questo URL viene mappato sul file `index.php`, che viene poi eseguito. Il suo compito è: 1) inizializzare l'ambiente 2) ottenere la factory -3) avviare l'applicazione Nette, che gestirà la richiesta +3) avviare l'applicazione Nette, che gestisce la richiesta -Quale factory? Non produciamo trattori, ma pagine web! Aspetta, si chiarirà subito. +Quale factory? Non stiamo mica producendo trattori, stiamo costruendo siti web! Un attimo di pazienza, verrà spiegato tra poco. -Con "inizializzazione dell'ambiente" intendiamo, ad esempio, che viene attivato [Tracy|tracy:], che è uno strumento fantastico per il logging o la visualizzazione degli errori. Sul server di produzione registra gli errori, su quello di sviluppo li visualizza direttamente. Quindi l'inizializzazione include anche la decisione se il sito web è in esecuzione in modalità produzione o sviluppo. Per questo Nette utilizza un [intelligente autodetect |bootstrapping#Modalità Sviluppo vs Produzione]: se avvii il sito web su localhost, viene eseguito in modalità sviluppo. Non devi quindi configurare nulla e l'applicazione è subito pronta sia per lo sviluppo che per la distribuzione in produzione. Questi passaggi vengono eseguiti e sono descritti in dettaglio nel capitolo sulla [classe Bootstrap|bootstrapping]. +Per "inizializzazione dell'ambiente" intendiamo, per esempio, l'attivazione di [Tracy|tracy:], un fantastico strumento per registrare o visualizzare gli errori. Su un server di produzione registra gli errori, in ambiente di sviluppo li mostra direttamente. L'inizializzazione comprende quindi anche stabilire se il sito gira in modalità produzione o sviluppo. Nette usa a questo scopo un [rilevamento automatico intelligente |bootstrapping#Modalità di sviluppo e di produzione]: se eseguite il sito su localhost, lavora in modalità di sviluppo. Non dovete configurare nulla e l'applicazione è subito pronta sia per lo sviluppo sia per la messa online. Questi passaggi vengono eseguiti e descritti in dettaglio nel capitolo sulla [classe Bootstrap|bootstrapping]. -Il terzo punto (sì, abbiamo saltato il secondo, ma ci torneremo) è l'avvio dell'applicazione. La gestione delle richieste HTTP in Nette è affidata alla classe `Nette\Application\Application` (di seguito `Application`), quindi quando diciamo avviare l'applicazione, intendiamo specificamente chiamare il metodo con il nome appropriato `run()` sull'oggetto di questa classe. +Il terzo punto (sì, abbiamo saltato il secondo, ma ci torneremo) è l'avvio dell'applicazione. In Nette la gestione delle richieste HTTP è compito della classe `Nette\Application\Application` (d'ora in poi `Application`). Quando quindi diciamo "avviare l'applicazione", intendiamo concretamente chiamare il metodo, dal nome azzeccato, `run()` su un oggetto di questa classe. -Nette è un mentore che ti guida a scrivere applicazioni pulite secondo metodologie comprovate. E una di quelle assolutamente più comprovate si chiama **dependency injection**, abbreviata in DI. In questo momento non vogliamo appesantirti con la spiegazione della DI, per questo c'è un [capitolo separato|dependency-injection:introduction], l'importante è la conseguenza che gli oggetti chiave ci verranno solitamente creati da una factory di oggetti, chiamata **container DI** (abbreviato in DIC). Sì, è quella factory di cui si parlava poco fa. E ci produrrà anche l'oggetto `Application`, perciò abbiamo prima bisogno del container. Lo otteniamo tramite la classe `Configurator` e gli facciamo produrre l'oggetto `Application`, chiamiamo su di esso il metodo `run()` e così si avvia l'applicazione Nette. Esattamente questo accade nel file [index.php |bootstrapping#index.php]. +Nette fa da mentore e vi guida a scrivere applicazioni pulite secondo metodologie collaudate. Una delle più consolidate è la **dependency injection**, in breve DI. Non vogliamo appesantirvi ora con la spiegazione della DI: c'è un [capitolo dedicato|dependency-injection:introduction]. La conseguenza essenziale è che gli oggetti chiave vengono di norma creati da una factory di oggetti nota come **container DI** (o DIC). Sì, è la factory di cui si parlava prima. È lei a produrre per noi anche l'oggetto `Application`, ed è per questo che ci serve prima il container. Lo otteniamo con la classe `Configurator`, gli facciamo creare l'oggetto `Application`, ne chiamiamo il metodo `run()` e così l'applicazione Nette parte. È esattamente quello che avviene nel file [index.php |bootstrapping#index.php]. Nette Application ================= -La classe Application ha un unico compito: rispondere alla richiesta HTTP. +La classe `Application` ha un solo compito: rispondere alla richiesta HTTP. -Le applicazioni scritte in Nette si dividono in molti cosiddetti presenter (in altri framework potresti incontrare il termine controller, è la stessa cosa), che sono classi, ognuna delle quali rappresenta una specifica pagina del sito web: ad esempio homepage; prodotto in un e-shop; modulo di login; feed sitemap ecc. Un'applicazione può avere da uno a migliaia di presenter. +Le applicazioni scritte in Nette sono divise in tanti cosiddetti presenter (in altri framework potreste incontrare il termine "controller", che è in sostanza la stessa cosa). Sono classi che rappresentano ciascuna una determinata pagina del sito: per esempio la home page, un prodotto di un e-shop, un form di login, un feed sitemap e così via. Un'applicazione può avere da uno a migliaia di presenter. -Application inizia chiedendo al cosiddetto router di decidere a quale dei presenter passare la richiesta corrente per la gestione. Il router decide di chi è la responsabilità. Guarda l'URL di input `https://example.com/product/123` e in base a come è impostato, decide che questo è il lavoro, ad esempio, per il **presenter** `Product`, dal quale vorrà come **azione** la visualizzazione (`show`) del prodotto con `id: 123`. È buona abitudine scrivere la coppia presenter + azione separata da due punti come `Product:show`. +`Application` comincia chiedendo al cosiddetto router di decidere quale presenter deve gestire la richiesta corrente. È il router a stabilire la competenza. Esamina l'URL in ingresso `https://example.com/product/123` e, in base alla propria configurazione, decide che questo compito spetta per esempio al **presenter** `Product`, che deve eseguire l'**azione** `show` per il prodotto con `id: 123`. È buona pratica scrivere la coppia presenter + azione separata dai due punti, come `Product:show`. -Quindi il router ha trasformato l'URL nella coppia `Presenter:action` + parametri, nel nostro caso `Product:show` + `id: 123`. Come appare un tale router puoi vederlo nel file `app/Core/RouterFactory.php` e lo descriviamo in dettaglio nel capitolo [Routing]. +Il router ha quindi trasformato l'URL nella coppia `Presenter:azione` + parametri, nel nostro caso `Product:show` + `id: 123`. Che aspetto abbia un router del genere lo vedete nel file `app/Core/RouterFactory.php` e lo descriviamo in dettaglio nel capitolo [Routing |Routing]. -Andiamo avanti. Application conosce già il nome del presenter e può continuare. Creando l'oggetto della classe `ProductPresenter`, che è il codice del presenter `Product`. Più precisamente, chiede al container DI di creare il presenter, perché la creazione è compito suo. +Andiamo avanti. `Application` conosce ora il nome del presenter e può proseguire. Lo fa creando un'istanza della classe `ProductPresenter`, che contiene il codice del presenter `Product`. Più precisamente, chiede al container DI di creare il presenter, perché la creazione degli oggetti è compito suo. -Il presenter potrebbe assomigliare a questo: +Il presenter potrebbe avere questo aspetto: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -109,92 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // otteniamo i dati dal model e li passiamo al template + // ottiene i dati dal modello e li passa al template $this->template->product = $this->repository->getProduct($id); } } ``` -La gestione della richiesta viene assunta dal presenter. E il compito è chiaro: esegui l'azione `show` con `id: 123`. Che nel linguaggio dei presenter significa che viene chiamato il metodo `renderShow()` e nel parametro `$id` riceve `123`. +Il presenter prende in carico la gestione della richiesta. Il compito è chiaro: eseguire l'azione `show` con `id: 123`. Nella terminologia dei presenter questo significa che viene chiamato il metodo `renderShow()`, che riceve `123` nel parametro `$id`. -Un presenter può gestire più azioni, cioè avere più metodi `render()`. Ma consigliamo di progettare presenter con una o il minor numero possibile di azioni. +Un presenter può gestire più azioni, può cioè avere più metodi `render()`. Consigliamo però di progettare i presenter con una sola azione o comunque con il minor numero possibile di azioni. -Quindi, è stato chiamato il metodo `renderShow(123)`, il cui codice è un esempio fittizio, ma puoi vedere come vengono passati i dati al template, cioè scrivendo in `$this->template`. +È stato quindi chiamato il metodo `renderShow(123)`. Il suo codice è un esempio inventato, ma mostra come i dati vengono passati al template, cioè scrivendoli in `$this->template`. -Successivamente, il presenter restituisce una risposta. Questa può essere una pagina HTML, un'immagine, un documento XML, l'invio di un file dal disco, JSON o magari un redirect a un'altra pagina. È importante notare che se non diciamo esplicitamente come deve rispondere (che è il caso di `ProductPresenter`), la risposta sarà il rendering del template con la pagina HTML. Perché? Perché nel 99% dei casi vogliamo renderizzare un template, quindi il presenter considera questo comportamento come predefinito e vuole semplificarci il lavoro. Questo è lo scopo di Nette. +Il presenter restituisce poi una risposta. Può essere una pagina HTML, un'immagine, un documento XML, l'invio di un file dal disco, un JSON oppure un redirect a un'altra pagina. Attenzione: se non indichiamo esplicitamente come rispondere (come nel caso di `ProductPresenter`), la risposta sarà il rendering di un template in una pagina HTML. Perché? Perché nel 99% dei casi vogliamo disegnare un template. Il presenter adotta quindi questo comportamento come predefinito, per semplificarci il lavoro. È l'essenza di Nette. -Non dobbiamo nemmeno specificare quale template renderizzare, ne deduce il percorso da solo. Nel caso dell'azione `show`, prova semplicemente a caricare il template `show.latte` nella directory con la classe `ProductPresenter`. Tenterà anche di trovare il layout nel file `@layout.latte` (maggiori dettagli sulla [ricerca dei template |templates#Ricerca dei template]). +Non dobbiamo nemmeno indicare quale template disegnare: il framework ne deduce automaticamente il percorso. Nel caso dell'azione `show` prova semplicemente a caricare il template `show.latte` che si trova nella stessa directory della classe `ProductPresenter`. Cerca inoltre il layout nel file `@layout.latte` (maggiori dettagli sulla [ricerca dei template |templates#Ricerca dei template]). -E successivamente renderizza i template. Con questo, il compito del presenter e dell'intera applicazione è completato e l'opera è finita. Se il template non esistesse, verrebbe restituita una pagina con errore 404. Maggiori informazioni sui presenter si trovano nella pagina [Presenter|presenters]. +Poi i template vengono disegnati. Così si conclude il compito del presenter e dell'intera applicazione. Se il template non esiste, viene restituita una pagina di errore 404. Potete saperne di più sui presenter nella pagina [Presenter|presenters]. [* request-flow.svg *] -Per sicurezza, proviamo a riepilogare l'intero processo con un URL leggermente diverso: +Per sicurezza ricapitoliamo l'intero processo con un URL leggermente diverso: -1) L'URL sarà `https://example.com` -2) avviamo l'applicazione, viene creato il container e viene eseguito `Application::run()` -3) il router decodifica l'URL come coppia `Home:default` -4) viene creato l'oggetto della classe `HomePresenter` -5) viene chiamato il metodo `renderDefault()` (se esiste) -6) viene renderizzato il template ad es. `default.latte` con il layout ad es. `@layout.latte` +1) L'URL è `https://example.com` +2) L'applicazione si avvia, viene creato il container DI e viene eseguito `Application::run()`. +3) Il router decodifica l'URL nella coppia `Home:default`. +4) Viene creata un'istanza della classe `HomePresenter`. +5) Viene chiamato il metodo `renderDefault()` (se esiste). +6) Viene disegnato il template, per esempio `default.latte`, insieme al layout, per esempio `@layout.latte`. -Potresti aver incontrato molti nuovi concetti ora, ma crediamo che abbiano senso. Creare applicazioni in Nette è un'enorme comodità. +Avrete forse incontrato ora molti concetti nuovi, ma crediamo che abbiano senso. Sviluppare applicazioni in Nette è straordinariamente semplice. Template ======== -Dato che abbiamo menzionato i template, in Nette si utilizza il sistema di templating [Latte |latte:]. Questo perché è il sistema di templating più sicuro per PHP, e allo stesso tempo il sistema più intuitivo. Non devi imparare molto di nuovo, ti basta la conoscenza di PHP e alcuni tag. Tutto si trova [nella documentazione |templates]. +A proposito di template, Nette usa il sistema di template [Latte |latte:]. Ecco perché i file dei template hanno estensione `.latte`. Latte viene usato soprattutto perché è il sistema di template più sicuro per PHP, e anche il più intuitivo. Non dovete imparare molto di nuovo: bastano la conoscenza di PHP e qualche tag. Trovate tutto ciò che vi serve [nella documentazione |templates]. -Nel template si [creano link |creating-links] ad altri presenter & azioni in questo modo: +Nel template [create i link |creating-links] verso altri presenter e azioni così: ```latte -dettaglio prodotto +dettaglio del prodotto ``` -Semplicemente, invece dell'URL reale, scrivi la coppia nota `Presenter:action` e specifichi eventuali parametri. Il trucco sta in `n:href`, che dice che questo attributo sarà elaborato da Nette. E genererà: +Basta scrivere la familiare coppia `Presenter:azione` al posto dell'URL vero e proprio e aggiungere gli eventuali parametri necessari. Il trucco sta in `n:href`, che dice a Nette di elaborare questo attributo. Verrà allora generato: ```latte -dettaglio prodotto +dettaglio del prodotto ``` -La generazione dell'URL è gestita dal router menzionato in precedenza. Infatti, i router in Nette sono eccezionali perché possono eseguire non solo trasformazioni da URL a coppia presenter:action, ma anche viceversa, cioè generare un URL dal nome del presenter + azione + parametri. Grazie a ciò, in Nette puoi cambiare completamente le forme degli URL in tutta l'applicazione finita, senza cambiare un singolo carattere nel template o nel presenter. Semplicemente modificando il router. Grazie a ciò funziona anche la cosiddetta canonizzazione, che è un'altra caratteristica unica di Nette, che contribuisce a un migliore SEO (ottimizzazione della reperibilità su Internet) impedendo automaticamente l'esistenza di contenuti duplicati su URL diversi. Molti programmatori lo trovano sorprendente. +Della generazione dell'URL si occupa il router già menzionato. I router in Nette sono eccezionali perché sanno eseguire non solo la trasformazione da URL a coppia `Presenter:azione`, ma anche quella inversa: generare un URL a partire dal nome del presenter, dall'azione e dai parametri. Grazie a questo, in Nette potete cambiare completamente il formato degli URL in tutta l'applicazione già finita senza modificare un solo carattere nei template o nei presenter, semplicemente modificando il router. Ciò rende possibile anche la cosiddetta canonizzazione, un'altra funzionalità unica di Nette che migliora la SEO (Search Engine Optimization) impedendo automaticamente che lo stesso contenuto esista su URL diversi. Molti programmatori trovano questa possibilità stupefacente. -Componenti Interattivi +Componenti interattivi ====================== -Sui presenter dobbiamo rivelarti ancora una cosa: hanno un sistema di componenti integrato. Qualcosa di simile potrebbe essere familiare ai veterani di Delphi o ASP.NET Web Forms, qualcosa di lontanamente simile è alla base di React o Vue.js. Nel mondo dei framework PHP, è una caratteristica assolutamente unica. +Dobbiamo dirvi ancora una cosa sui presenter: hanno un sistema di componenti integrato. Chi ha più esperienza ricorderà forse qualcosa di simile in Delphi o in ASP.NET Web Forms; React o Vue.js si basano su concetti in qualche modo affini. Nel mondo dei framework PHP è una funzionalità del tutto unica. -I componenti sono unità riutilizzabili separate che inseriamo nelle pagine (cioè nei presenter). Possono essere [form |forms:in-presenter], [datagrid |https://componette.org/contributte/datagrid/], menu, sondaggi, praticamente qualsiasi cosa che abbia senso usare ripetutamente. Possiamo creare i nostri componenti o usare alcuni dell'[enorme offerta |https://componette.org] di componenti open source. +I componenti sono unità indipendenti e riutilizzabili che incorporiamo nelle pagine (cioè nei presenter). Possono essere [form |forms:in-presenter], [datagrid |https://componette.org/contributte/datagrid/], menu, sondaggi: in pratica tutto ciò che ha senso riutilizzare. Possiamo creare componenti nostri oppure attingere all'[ampia scelta |https://componette.org] di componenti open source. -I componenti influenzano fondamentalmente l'approccio alla creazione di applicazioni. Ti apriranno nuove possibilità di comporre pagine da unità pre-preparate. E inoltre hanno qualcosa in comune con [Hollywood |components#Stile Hollywood]. +I componenti influenzano profondamente il modo di sviluppare le applicazioni. Aprono nuove possibilità di comporre le pagine a partire da unità già pronte. E hanno anche qualcosa in comune con [Hollywood |components#Stile hollywoodiano]. -Container DI e Configurazione +Container DI e configurazione ============================= -Il container DI o factory di oggetti è il cuore dell'intera applicazione. +Il container DI, cioè la factory di oggetti, è il cuore dell'intera applicazione. -Non preoccuparti, non è una scatola nera magica, come potrebbe sembrare dalle righe precedenti. In realtà, è una classe PHP piuttosto noiosa, che viene generata da Nette e salvata nella directory della cache. Ha molti metodi chiamati come `createServiceAbcd()` e ognuno di essi sa come creare e restituire un oggetto. Sì, c'è anche il metodo `createServiceApplication()`, che crea `Nette\Application\Application`, di cui avevamo bisogno nel file `index.php` per avviare l'applicazione. E ci sono metodi che creano i singoli presenter. E così via. +Niente paura, non è una scatola nera magica, come potrebbero suggerire le righe precedenti. In realtà è una classe PHP piuttosto banale, generata da Nette e salvata nella directory della cache. Contiene molti metodi con nomi come `createServiceAbcd()`, ognuno capace di creare e restituire un determinato oggetto. Sì, c'è anche un metodo `createServiceApplication__application()`, che produce l'istanza di `Nette\Application\Application` che ci serviva in `index.php` per avviare l'applicazione. Ci sono anche i metodi per creare i singoli presenter e così via. -Agli oggetti che il container DI crea, per qualche motivo, si dice servizi. +Gli oggetti creati dal container DI si chiamano, per qualche motivo, servizi. -Ciò che è veramente speciale di questa classe è che non la programmi tu, ma il framework. Genera effettivamente il codice PHP e lo salva su disco. Tu dai solo istruzioni su quali oggetti il container deve saper creare e come esattamente. E queste istruzioni sono scritte nei [file di configurazione |bootstrapping#Configurazione del Container DI], per i quali si usa il formato [NEON|neon:format] e quindi hanno anche l'estensione `.neon`. +La cosa davvero particolare di questa classe è che non la programmate voi: la genera il framework. Genera davvero il codice PHP e lo salva su disco. Voi vi limitate a fornire le istruzioni su quali oggetti il container deve saper creare, e come esattamente. Queste istruzioni si scrivono nei [file di configurazione |bootstrapping#Configurazione del container DI], che usano il formato [NEON|neon:format] e hanno quindi estensione `.neon`. -I file di configurazione servono puramente a istruire il container DI. Quindi, se ad esempio specifico nella sezione [session |http:configuration#Sessione] l'opzione `expiration: 14 days`, il container DI, durante la creazione dell'oggetto `Nette\Http\Session` che rappresenta la sessione, chiamerà il suo metodo `setExpiration('14 days')` e così la configurazione diventerà realtà. +I file di configurazione servono esclusivamente a istruire il container DI. Se quindi indicate, per esempio, l'opzione `expiration: 14 days` nella sezione [session |http:configuration#Sessione], il container DI, creando l'oggetto `Nette\Http\Session` che rappresenta la sessione, ne chiamerà il metodo `setExpiration('14 days')`, rendendo così reale la configurazione. -C'è un intero capitolo preparato per te che descrive cosa può essere [configurato |nette:configuring] e come [definire i propri servizi |dependency-injection:services]. +C'è un intero capitolo pronto per voi che descrive cosa si può [configurare |nette:configuring] e come [definire i propri servizi |dependency-injection:services]. -Non appena ti addentrerai un po' nella creazione di servizi, incontrerai la parola [autowiring |dependency-injection:autowiring]. Questa è una chicca che ti semplificherà la vita in modo incredibile. Sa passare automaticamente gli oggetti dove ne hai bisogno (ad esempio nei costruttori delle tue classi), senza che tu debba fare nulla. Scoprirai che il container DI in Nette è un piccolo miracolo. +Appena vi addentrerete un po' nella creazione dei servizi, incontrerete il termine [autowiring |dependency-injection:autowiring]. È una funzionalità che vi semplificherà incredibilmente la vita: sa passare automaticamente gli oggetti dove vi servono (per esempio nei costruttori delle vostre classi), senza che dobbiate fare nulla. Scoprirete che il container DI di Nette è un piccolo miracolo. -Dove andare dopo? -================= +E adesso? +========= -Abbiamo esaminato i principi di base delle applicazioni in Nette. Finora molto superficialmente, ma presto approfondirai e col tempo creerai fantastiche applicazioni web. Dove continuare? Hai già provato il tutorial [Scriviamo la prima applicazione|quickstart:]? +Abbiamo trattato i principi fondamentali delle applicazioni Nette. Finora è stata una panoramica superficiale, ma presto approfondirete e col tempo creerete applicazioni web straordinarie. Dove andare adesso? Avete già provato il tutorial [Crea la tua prima applicazione|quickstart:]? -Oltre a quanto descritto sopra, Nette dispone di un intero arsenale di [classi utili|utils:], un [layer di database|database:], ecc. Prova a sfogliare la documentazione solo per curiosità. O il [blog|https://blog.nette.org]. Scoprirai molte cose interessanti. +Oltre a quanto descritto sopra, Nette offre tutto un arsenale di [classi utili|utils:], uno [strato per il database|database:] e altro ancora. Provate a curiosare nella documentazione. Oppure visitate il [blog|https://blog.nette.org]. Scoprirete molte cose interessanti. -Che il framework ti porti molta gioia 💙 +Che il framework vi porti molta gioia 💙 diff --git a/application/it/multiplier.texy b/application/it/multiplier.texy index 4bdb5b42a0..f4c003c96d 100644 --- a/application/it/multiplier.texy +++ b/application/it/multiplier.texy @@ -2,23 +2,23 @@ Multiplier: componenti dinamici ******************************* .[perex] -Strumento per la creazione dinamica di componenti interattivi +Uno strumento per creare dinamicamente componenti interattivi. -Partiamo da un esempio tipico: abbiamo un elenco di prodotti in un e-shop, e per ognuno vogliamo visualizzare un form per aggiungere il prodotto al carrello. Una delle possibili varianti è racchiudere l'intero elenco in un unico form. Tuttavia, un modo molto più comodo ci viene offerto da [api:Nette\Application\UI\Multiplier]. +Cominciamo con un esempio tipico: immaginate l'elenco dei prodotti di un e-shop, in cui per ogni articolo volete un form "Aggiungi al carrello". Un approccio possibile è racchiudere l'intero elenco in un unico form. Un metodo molto più comodo lo offre però [api:Nette\Application\UI\Multiplier]. -Multiplier consente di definire comodamente una piccola factory per più componenti. Funziona sul principio dei componenti nidificati - ogni componente che eredita da [api:Nette\ComponentModel\Container] può contenere altri componenti. +Multiplier vi permette di definire comodamente una factory per più componenti. Funziona sul principio dei componenti annidati: qualsiasi componente che eredita da [api:Nette\ComponentModel\Container] può contenere altri componenti. .[tip] -Vedi il capitolo sul [modello a componenti |components#Componenti in profondità] nella documentazione o la [presentazione di Honza Tvrdík|https://www.youtube.com/watch?v=8y3LLexWu-I]. +Vedi nella documentazione il capitolo sul [modello a componenti |components#I componenti in profondità]. -L'essenza di Multiplier è che agisce come genitore, che può creare dinamicamente i propri figli tramite un callback passato nel costruttore. Vedi l'esempio: +L'essenza di Multiplier sta nel fatto che agisce da genitore capace di creare dinamicamente i propri figli tramite una callback passata al costruttore. Ecco l'esempio: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Quantità:') + $form->addInteger('amount', 'Quantità:') ->setRequired(); $form->addSubmit('send', 'Aggiungi al carrello'); return $form; @@ -26,7 +26,7 @@ protected function createComponentShopForm(): Multiplier } ``` -Ora possiamo semplicemente far renderizzare un form per ogni prodotto nel template - e ognuno sarà effettivamente un componente unico. +Ora, nel template, possiamo semplicemente disegnare il form per ogni prodotto, e ognuno sarà davvero un componente a sé. ```latte {foreach $items as $item} @@ -37,23 +37,23 @@ Ora possiamo semplicemente far renderizzare un form per ogni prodotto nel templa {/foreach} ``` -L'argomento passato nel tag `{control}` è nel formato che dice: +L'argomento passato nel tag `{control}` segue un formato che significa: -1. ottieni il componente `shopForm` -2. e da esso ottieni il figlio `$item->id` +1. Ottieni il componente `shopForm`. +2. Da esso ottieni il figlio chiamato `$item->id`. -Alla prima chiamata del punto **1.** `shopForm` non esiste ancora, quindi viene chiamata la sua factory `createComponentShopForm`. Sul componente ottenuto (istanza di Multiplier) viene poi chiamata la factory del form specifico - che è la funzione anonima che abbiamo passato a Multiplier nel costruttore. +Alla prima esecuzione del punto **1** il componente `shopForm` non esiste ancora, quindi viene chiamata la sua factory `createComponentShopForm`. Poi, sul componente ottenuto (un'istanza di Multiplier), viene chiamata la factory del form specifico, cioè la funzione anonima che abbiamo passato al costruttore di Multiplier. -Nella successiva iterazione del foreach, il metodo `createComponentShopForm` non verrà più chiamato (il componente esiste), ma poiché stiamo cercando un suo figlio diverso (`$item->id` sarà diverso in ogni iterazione), la funzione anonima verrà chiamata di nuovo e ci restituirà un nuovo form. +Nell'iterazione successiva del ciclo foreach il metodo `createComponentShopForm` non verrà chiamato di nuovo (perché il componente esiste già). Poiché però cerchiamo un figlio diverso (dato che `$item->id` sarà diverso a ogni iterazione), la funzione anonima verrà chiamata di nuovo e restituirà un nuovo form. -L'unica cosa che resta da fare è assicurarsi che il form aggiunga al carrello effettivamente il prodotto che deve - attualmente il form è completamente identico per ogni prodotto. Ci aiuta una proprietà di Multiplier (e in generale di ogni factory di componente in Nette Framework), ovvero che ogni factory riceve come primo argomento il nome del componente creato. Nel nostro caso sarà `$item->id`, che è esattamente l'informazione di cui abbiamo bisogno. Basta quindi modificare leggermente la creazione del form: +Resta solo da garantire che il form aggiunga al carrello il prodotto giusto: al momento il form è identico per ogni prodotto. Qui ci aiuta una caratteristica di Multiplier (e in generale di qualsiasi factory di componenti in Nette Framework): ogni factory riceve come primo argomento il nome del componente che si sta creando. La factory di Multiplier riceve inoltre come secondo argomento l'istanza stessa di Multiplier. Nel nostro caso il primo argomento sarà `$item->id`, che è esattamente l'informazione che ci serve. Basta quindi modificare leggermente la creazione del form: ```php protected function createComponentShopForm(): Multiplier { - return new Multiplier(function ($itemId) { + return new Multiplier(function (string $itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Quantità:') + $form->addInteger('amount', 'Quantità:') ->setRequired(); $form->addHidden('itemId', $itemId); $form->addSubmit('send', 'Aggiungi al carrello'); diff --git a/application/it/presenters.texy b/application/it/presenters.texy index 21c74bdd7f..072276889b 100644 --- a/application/it/presenters.texy +++ b/application/it/presenters.texy @@ -3,37 +3,37 @@ Presenter
    -Impareremo come scrivere presenter e template in Nette. Dopo aver letto, saprai: +Esamineremo come si scrivono i presenter e i template in Nette. Dopo la lettura capirete: -- come funziona un presenter +- come funzionano i presenter - cosa sono i parametri persistenti -- come vengono renderizzati i template +- come vengono disegnati i template
    -[Sappiamo già |how-it-works#Nette Application] che un presenter è una classe che rappresenta una specifica pagina di un'applicazione web, ad es. la homepage; un prodotto in un e-shop; un modulo di login; un feed sitemap, ecc. Un'applicazione può avere da uno a migliaia di presenter. In altri framework vengono anche chiamati controller. +[Sappiamo già |how-it-works#Nette Application] che un presenter è una classe che rappresenta una determinata pagina di un'applicazione web, per esempio la home page, un prodotto di un e-shop, un form di login, un feed sitemap e così via. Un'applicazione può avere da uno a migliaia di presenter. In altri framework sono noti anche come controller. -Di solito, con il termine presenter si intende un discendente della classe [api:Nette\Application\UI\Presenter], che è adatto per generare interfacce web e al quale ci dedicheremo nel resto di questo capitolo. In senso generale, un presenter è qualsiasi oggetto che implementa l'interfaccia [api:Nette\Application\IPresenter]. +Di norma con il termine presenter si intende un discendente della classe [api:Nette\Application\UI\Presenter], adatta a generare interfacce web e alla quale sarà dedicato il resto di questo capitolo. In senso generale, un presenter è qualsiasi oggetto che implementi l'interfaccia [api:Nette\Application\IPresenter]. Ciclo di vita del presenter =========================== -Il compito del presenter è gestire la richiesta e restituire una risposta (che può essere una pagina HTML, un'immagine, un redirect, ecc.). +Il compito del presenter è gestire una richiesta e restituire una risposta (che può essere una pagina HTML, un'immagine, un redirect ecc.). -Quindi, all'inizio, gli viene passata la richiesta. Non è direttamente la richiesta HTTP, ma l'oggetto [api:Nette\Application\Request], nel quale la richiesta HTTP è stata trasformata con l'aiuto del router. Di solito non entriamo in contatto con questo oggetto, poiché il presenter delega intelligentemente l'elaborazione della richiesta ad altri metodi, che ora vedremo. +All'inizio, quindi, gli viene passata una richiesta. Non è la richiesta HTTP diretta, ma un oggetto [api:Nette\Application\Request], nel quale la richiesta HTTP è stata trasformata con l'aiuto del router. Di norma non interagiamo direttamente con questo oggetto, perché il presenter delega abilmente l'elaborazione della richiesta ad altri metodi, che esamineremo ora. -[* lifecycle.svg *] *** *Ciclo di vita del presenter* .<> +[* lifecycle.svg *] *** Ciclo di vita del presenter .<> -L'immagine rappresenta l'elenco dei metodi che vengono chiamati in sequenza dall'alto verso il basso, se esistono. Nessuno di essi deve esistere, possiamo avere un presenter completamente vuoto senza un singolo metodo e costruirci sopra un semplice sito web statico. +Lo schema mostra l'elenco dei metodi che vengono chiamati in sequenza dall'alto verso il basso, se esistono. Nessuno di essi è obbligatorio: potete avere un presenter completamente vuoto, senza un solo metodo, e costruirci sopra un semplice sito statico. `__construct()` --------------- -Il costruttore non appartiene propriamente al ciclo di vita del presenter, perché viene chiamato al momento della creazione dell'oggetto. Ma lo menzioniamo per la sua importanza. Il costruttore (insieme al [metodo inject|best-practices:inject-method-attribute]) serve a passare le dipendenze. +Il costruttore non appartiene in senso stretto al ciclo di vita del presenter, perché viene chiamato nel momento in cui l'oggetto viene creato. Lo menzioniamo però per la sua importanza. Il costruttore (insieme al [metodo inject|best-practices:inject-method-attribute]) serve a passare le dipendenze. -Il presenter non dovrebbe occuparsi della logica di business dell'applicazione, scrivere e leggere dal database, eseguire calcoli, ecc. Per questo ci sono classi del layer che chiamiamo model. Ad esempio, la classe `ArticleRepository` può essere responsabile del caricamento e del salvataggio degli articoli. Affinché il presenter possa lavorarci, se la fa [passare tramite dependency injection |dependency-injection:passing-dependencies]: +Il presenter non deve occuparsi della logica di business dell'applicazione, non deve scrivere sul database o leggerne, non deve eseguire calcoli e così via. È compito delle classi dello strato che chiamiamo model. Per esempio, una classe `ArticleRepository` può occuparsi di caricare e salvare gli articoli. Perché il presenter possa lavorarci, deve farsela [passare tramite dependency injection |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,47 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Subito dopo aver ricevuto la richiesta, viene chiamato il metodo `startup()`. Puoi usarlo per inizializzare le proprietà, verificare i permessi utente, ecc. È richiesto che il metodo chiami sempre il genitore `parent::startup()`. +Subito dopo aver ricevuto la richiesta viene richiamato il metodo `startup()`. Potete usarlo per inizializzare le proprietà, controllare i permessi dell'utente e così via. È obbligatorio che questo metodo chiami sempre il metodo genitore: `parent::startup()`. -`action(args...)` .{toc: action()} +`action(args...)` .{toc: action()} -------------------------------------------------- -Analogo al metodo `render()`. Mentre `render()` è destinato a preparare i dati per un template specifico che verrà successivamente renderizzato, in `action()` la richiesta viene elaborata senza relazione con il rendering del template. Ad esempio, vengono elaborati i dati, l'utente viene loggato o sloggato, e così via, e poi [reindirizza altrove |#Redirect]. +È simile al metodo `render()`. Mentre `render()` serve a preparare i dati per un determinato template che verrà poi disegnato, `action()` elabora una richiesta senza per forza disegnare poi un template. Per esempio può elaborare dei dati, autenticare o disconnettere un utente e così via, per poi [reindirizzare altrove |#Redirect]. -È importante notare che `action()` viene chiamato prima di `render()`, quindi al suo interno possiamo eventualmente cambiare il corso successivo degli eventi, cioè cambiare il template che verrà renderizzato, e anche il metodo `render()` che verrà chiamato. E questo tramite `setView('altraView')`. +È importante che `action()` venga chiamato *prima* di `render()`. Questo ci permette eventualmente di cambiare il corso della richiesta dentro il metodo action, per esempio cambiando il template che verrà disegnato o perfino il metodo `render()` che verrà chiamato, con `setView('otherView')`. -Al metodo vengono passati i parametri dalla richiesta. È possibile e consigliato specificare i tipi per i parametri, ad es. `actionShow(int $id, ?string $slug = null)` - se il parametro `id` manca o se non è un intero, il presenter restituirà un [errore 404 |#Errore 404 e simili] e terminerà l'attività. +.{data-version:3.2.3} +Potete perfino passare a un'azione completamente diversa con il metodo `switch('otherAction')`. Interrompe il metodo corrente ed esegue invece i metodi `action()` e `render()` della nuova azione (e disattiva la [canonizzazione|#Canonizzazione] automatica). La richiesta in sé prosegue; viene interrotto solo il metodo attualmente in esecuzione. +Al metodo vengono passati i parametri della richiesta. È possibile, e consigliato, indicare i tipi di questi parametri, per esempio `actionShow(int $id, ?string $slug = null)`. Se il parametro `id` manca o non è un intero, il presenter restituisce un [errore 404 |#Errore 404 e simili] e termina. -`handle(args...)` .{toc: handle()} --------------------------------------------------- -Il metodo elabora i cosiddetti segnali, che conosceremo nel capitolo dedicato ai [componenti |components#Segnale]. È infatti destinato principalmente ai componenti e all'elaborazione delle richieste AJAX. +`handle(args...)` .{toc: handle()} +--------------------------------------------------- + +Questo metodo elabora i cosiddetti segnali, di cui parleremo nel capitolo dedicato ai [componenti |components#Segnale]. È destinato soprattutto ai componenti e alla gestione delle richieste AJAX. -Al metodo vengono passati i parametri dalla richiesta, come nel caso di `action()`, incluso il controllo del tipo. +Al metodo vengono passati i parametri della richiesta, come per `action()`, compreso il controllo dei tipi. `beforeRender()` ---------------- -Il metodo `beforeRender`, come suggerisce il nome, viene chiamato prima di ogni metodo `render()`. Viene utilizzato per la configurazione comune del template, il passaggio di variabili per il layout e simili. +Il metodo `beforeRender`, come suggerisce il nome, viene chiamato prima di ogni metodo `render()`. Serve per la configurazione comune del template, per passare variabili al layout e per compiti simili. -`render(args...)` .{toc: render()} ----------------------------------------------- +`render(args...)` .{toc: render()} +----------------------------------------------- -Il luogo dove prepariamo il template per il successivo rendering, gli passiamo i dati, ecc. +È qui che prepariamo il template per il successivo rendering, gli passiamo i dati e così via. -Al metodo vengono passati i parametri dalla richiesta, come nel caso di `action()`, incluso il controllo del tipo. +Al metodo vengono passati i parametri della richiesta, come per `action()`, compreso il controllo dei tipi. ```php public function renderShow(int $id): void { - // otteniamo i dati dal model e li passiamo al template + // ottiene i dati dal modello e li passa al template $this->template->article = $this->articles->getById($id); } ``` @@ -96,7 +99,7 @@ public function renderShow(int $id): void `afterRender()` --------------- -Il metodo `afterRender`, come suggerisce nuovamente il nome, viene chiamato dopo ogni metodo `render()`. Viene utilizzato piuttosto eccezionalmente. +Il metodo `afterRender`, come suggerisce di nuovo il nome, viene chiamato dopo ogni metodo `render()`. Si usa piuttosto di rado. `shutdown()` @@ -105,44 +108,66 @@ Il metodo `afterRender`, come suggerisce nuovamente il nome, viene chiamato dopo Viene chiamato alla fine del ciclo di vita del presenter. -**Un buon consiglio prima di andare avanti**. Come si vede, un presenter può gestire più azioni/view, cioè avere più metodi `render()`. Ma consigliamo di progettare presenter con una o il minor numero possibile di azioni. +Eventi +------ + +Oltre ai metodi `startup()`, `beforeRender()` e `shutdown()`, chiamati nell'ambito del ciclo di vita del presenter, si possono definire altre funzioni da chiamare automaticamente. Il presenter definisce i cosiddetti [eventi |nette:glossary#Eventi] e voi aggiungete i loro gestori negli array `$onStartup`, `$onRender` e `$onShutdown`. + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` + +I gestori dell'array `$onStartup` vengono chiamati subito prima del metodo `startup()`, quelli di `$onRender` tra `beforeRender()` e `render()` e infine quelli di `$onShutdown` subito prima di `shutdown()`. + + +**Un consiglio prima di proseguire:** come vedete, un presenter può gestire più azioni/viste, può cioè avere più metodi `render()`. Consigliamo però di progettare i presenter con una sola azione o comunque con il minor numero possibile di azioni. -Invio della risposta +Inviare una risposta ==================== -La risposta del presenter è di solito il [rendering di un template con una pagina HTML|templates], ma può anche essere l'invio di un file, JSON o magari un redirect a un'altra pagina. +La risposta del presenter è di norma il [rendering di un template in una pagina HTML|templates], ma può essere anche l'invio di un file, di un JSON o perfino un redirect a un'altra pagina. -In qualsiasi momento durante il ciclo di vita, possiamo inviare una risposta con uno dei seguenti metodi e allo stesso tempo terminare il presenter: +In qualsiasi momento del ciclo di vita possiamo usare uno dei metodi seguenti per inviare una risposta e terminare contemporaneamente il presenter: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` e `forward()` [reindirizzano |#Redirect] -- `error()` termina il presenter [a causa di un errore |#Errore 404 e simili] -- `sendJson($data)` termina il presenter e [invia dati |#Invio di JSON] in formato JSON -- `sendTemplate()` termina il presenter e immediatamente [renderizza il template |templates] +- `redirect()`, `redirectPermanent()`, `redirectUrl()` e `forward()` eseguono un [redirect |#Redirect] +- `error()` termina il presenter [a causa di un errore |#Errore 404 e simili] +- `sendJson($data)` termina il presenter e [invia i dati |#Inviare JSON] in formato JSON +- `sendTemplate()` termina il presenter e [disegna subito il template |templates] - `sendResponse($response)` termina il presenter e invia una [risposta personalizzata |#Risposte] -- `terminate()` termina il presenter senza risposta +- `terminate()` termina il presenter senza alcuna risposta + +Ognuno di questi metodi termina immediatamente il presenter sollevando l'eccezione di terminazione silenziosa `Nette\Application\AbortException`. -Se non chiami nessuno di questi metodi, il presenter procederà automaticamente al rendering del template. Perché? Perché nel 99% dei casi vogliamo renderizzare un template, quindi il presenter considera questo comportamento come predefinito e vuole semplificarci il lavoro. +Se non chiamate nessuno di questi metodi, il presenter passa automaticamente al rendering del template. Perché? Perché nel 99% dei casi vogliamo disegnare un template, quindi il presenter adotta questo comportamento come predefinito per semplificarci il lavoro. -Creazione di link -================= +Creare i link +============= -Il presenter dispone del metodo `link()`, tramite il quale è possibile creare link URL ad altri presenter. Il primo parametro è il presenter & azione di destinazione, seguono gli argomenti passati, che possono essere specificati come array: +Il presenter ha un metodo `link()` che serve a creare link URL verso altri presenter. Il primo parametro è il presenter e l'azione di destinazione, seguiti dagli argomenti, che si possono passare come array: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$id, 'lang' => 'en']); ``` -Nel template, i link ad altri presenter & azioni vengono creati in questo modo: +Nel template i link verso altri presenter e azioni si creano così: ```latte -dettaglio prodotto +dettaglio del prodotto ``` -Semplicemente, invece dell'URL reale, scrivi la coppia nota `Presenter:action` e specifichi eventuali parametri. Il trucco sta in `n:href`, che dice che questo attributo sarà elaborato da Latte e genererà l'URL reale. In Nette, quindi, non devi affatto pensare agli URL, ma solo ai presenter e alle azioni. +Basta scrivere la familiare coppia `Presenter:azione` al posto dell'URL vero e proprio e aggiungere gli eventuali parametri necessari. Il trucco sta in `n:href`, che dice a Latte di elaborare questo attributo e di generare l'URL reale. In Nette non dovete pensare affatto agli URL, solo ai presenter e alle azioni. Maggiori informazioni si trovano nel capitolo [Creazione di link URL|creating-links]. @@ -150,50 +175,50 @@ Maggiori informazioni si trovano nel capitolo [Creazione di link URL|creating-li Redirect ======== -Per passare a un altro presenter si usano i metodi `redirect()` e `forward()`, che hanno una sintassi molto simile al metodo [link() |#Creazione di link]. +Per passare a un altro presenter si usano i metodi `redirect()` e `forward()`. Hanno una sintassi molto simile a quella del metodo [link() |#Creare i link]. -Il metodo `forward()` passa immediatamente al nuovo presenter senza redirect HTTP: +Il metodo `forward()` passa subito al nuovo presenter, senza un redirect HTTP: ```php $this->forward('Product:show'); ``` -Esempio del cosiddetto redirect temporaneo con codice HTTP 302 (o 303, se il metodo della richiesta corrente è POST): +Esempio di redirect temporaneo con codice HTTP 302 (o 303 se il metodo della richiesta corrente è POST): ```php $this->redirect('Product:show', $id); ``` -Il redirect permanente con codice HTTP 301 si ottiene così: +Per ottenere un redirect permanente con codice HTTP 301, usate questo: ```php $this->redirectPermanent('Product:show', $id); ``` -È possibile reindirizzare a un altro URL al di fuori dell'applicazione con il metodo `redirectUrl()`. Come secondo parametro è possibile specificare il codice HTTP, il predefinito è 302 (o 303, se il metodo della richiesta corrente è POST): +Potete reindirizzare a un altro URL fuori dall'applicazione con il metodo `redirectUrl()`. Il codice HTTP si può indicare come secondo parametro; il valore predefinito è 302 (o 303 se il metodo della richiesta corrente è POST): ```php $this->redirectUrl('https://nette.org'); ``` -Il redirect termina immediatamente l'attività del presenter lanciando la cosiddetta eccezione di terminazione silenziosa `Nette\Application\AbortException`. +Il redirect termina immediatamente l'attività del presenter sollevando la cosiddetta eccezione di terminazione silenziosa, `Nette\Application\AbortException`. -Prima del redirect è possibile inviare un [messaggio flash |#Messaggi flash], cioè messaggi che verranno visualizzati nel template dopo il redirect. +Prima del redirect è possibile inviare [messaggi flash |#Messaggi flash], cioè messaggi che verranno mostrati nel template dopo il redirect. Messaggi flash ============== -Si tratta di messaggi che solitamente informano sul risultato di qualche operazione. Una caratteristica importante dei messaggi flash è che sono disponibili nel template anche dopo un redirect. Anche dopo essere stati visualizzati, rimangono attivi per altri 30 secondi – ad esempio, nel caso in cui l'utente aggiorni la pagina a causa di un errore di trasmissione - il messaggio non scomparirà immediatamente. +Sono messaggi che di norma informano sull'esito di qualche operazione. Una caratteristica importante dei messaggi flash è che restano disponibili nel template anche dopo un redirect. Una volta mostrati restano attivi per altri 30 secondi: se per esempio l'utente ricarica la pagina per un errore di trasmissione, il messaggio non sparisce subito. -Basta chiamare il metodo [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] e il presenter si occuperà di passarlo al template. Il primo parametro è il testo del messaggio e il secondo parametro opzionale è il suo tipo (error, warning, info, ecc.). Il metodo `flashMessage()` restituisce un'istanza del messaggio flash, alla quale è possibile aggiungere ulteriori informazioni. +Basta chiamare il metodo [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] e il presenter si occupa di passarlo al template. Il primo parametro è il testo del messaggio, il secondo, facoltativo, è il suo tipo (per esempio error, warning, info). Il metodo `flashMessage()` restituisce un'istanza del messaggio flash, alla quale si possono aggiungere altre informazioni. ```php $this->flashMessage('L\'elemento è stato eliminato.'); -$this->redirect(/* ... */); // e reindirizziamo +$this->redirect(/* ... */); // e reindirizza ``` -Nel template, questi messaggi sono disponibili nella variabile `$flashes` come oggetti `stdClass`, che contengono le proprietà `message` (testo del messaggio), `type` (tipo del messaggio) e possono contenere le informazioni utente già menzionate. Li renderizziamo ad esempio così: +Nel template questi messaggi sono disponibili nella variabile `$flashes` come oggetti `stdClass`, che contengono le proprietà `message` (il testo del messaggio), `type` (il tipo del messaggio) ed eventualmente le informazioni aggiunte dall'utente già menzionate. Li disegniamo così: ```latte {foreach $flashes as $flash} @@ -205,7 +230,7 @@ Nel template, questi messaggi sono disponibili nella variabile `$flashes` come o Errore 404 e simili =================== -Se non è possibile soddisfare la richiesta, ad esempio perché l'articolo che vogliamo visualizzare non esiste nel database, lanciamo un errore 404 con il metodo `error(?string $message = null, int $httpCode = 404)`. +Se la richiesta non si può soddisfare, per esempio perché l'articolo che vogliamo mostrare non esiste nel database, solleviamo un errore 404 con il metodo `error(string $message = '', int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,13 +243,13 @@ public function renderShow(int $id): void } ``` -Il codice HTTP dell'errore può essere passato come secondo parametro, il predefinito è 404. Il metodo funziona lanciando l'eccezione `Nette\Application\BadRequestException`, dopodiché `Application` passa il controllo all'error-presenter. Che è un presenter il cui compito è visualizzare una pagina che informa sull'errore verificatosi. L'impostazione dell'error-presenter viene eseguita nella [configurazione application|configuration]. +Il codice di errore HTTP si può passare come secondo parametro; il valore predefinito è 404. Il metodo funziona sollevando una `Nette\Application\BadRequestException`, dopo la quale `Application` passa il controllo all'error presenter. È un presenter il cui compito è mostrare una pagina che informa dell'errore avvenuto. L'error presenter si imposta nella [configurazione dell'applicazione|configuration]. -Invio di JSON -============= +Inviare JSON +============ -Esempio di un metodo action che invia dati in formato JSON e termina il presenter: +Il metodo `sendJson($data)` codifica i dati indicati in JSON, li invia come risposta HTTP e termina il presenter. Esempio: ```php public function actionData(): void @@ -238,9 +263,9 @@ public function actionData(): void Parametri della richiesta .{data-version:3.1.14} ================================================ -Il presenter e anche ogni componente ottengono i propri parametri dalla richiesta HTTP. Il loro valore si ottiene con il metodo `getParameter($name)` o `getParameters()`. I valori sono stringhe o array di stringhe, si tratta fondamentalmente di dati grezzi ottenuti direttamente dall'URL. +Il presenter, e anche ogni componente, ottiene i propri parametri dalla richiesta HTTP. Potete leggerne i valori con i metodi `getParameter($name)` o `getParameters()`. I valori sono stringhe o array di stringhe, in sostanza dati grezzi ottenuti direttamente dall'URL. -Per maggiore comodità, consigliamo di rendere accessibili i parametri tramite property. Basta contrassegnarli con l'attributo `#[Parameter]`: +Per maggiore comodità consigliamo di accedere ai parametri tramite le proprietà. Basta contrassegnarle con l'attributo `#[Parameter]`: ```php use Nette\Application\Attributes\Parameter; // questa riga è importante @@ -252,9 +277,9 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Per la property, consigliamo di specificare anche il tipo di dati (es. `string`) e Nette convertirà automaticamente il valore in base ad esso. I valori dei parametri possono anche essere [validati |#Validazione dei parametri]. +Per la proprietà consigliamo di indicare il tipo di dato (per esempio `string`) e Nette convertirà automaticamente il valore di conseguenza. I valori dei parametri si possono anche [validare |#Validazione dei parametri]. -Durante la creazione di un link, è possibile impostare direttamente il valore dei parametri: +Quando create un link potete impostare direttamente il valore del parametro: ```latte clicca @@ -264,11 +289,11 @@ Durante la creazione di un link, è possibile impostare direttamente il valore d Parametri persistenti ===================== -I parametri persistenti servono a mantenere lo stato tra richieste diverse. Il loro valore rimane lo stesso anche dopo aver cliccato su un link. A differenza dei dati nella sessione, vengono trasferiti nell'URL. E questo avviene in modo completamente automatico, non è quindi necessario specificarli esplicitamente in `link()` o `n:href`. +I parametri persistenti servono a mantenere lo stato tra richieste diverse. Il loro valore resta lo stesso anche dopo aver cliccato un link. A differenza dei dati di sessione, vengono trasmessi nell'URL. E questo avviene in modo completamente automatico, quindi non c'è bisogno di indicarli esplicitamente in `link()` o in `n:href`. -Esempio di utilizzo? Hai un'applicazione multilingue. La lingua corrente è un parametro che deve essere costantemente parte dell'URL. Ma sarebbe incredibilmente noioso specificarlo in ogni link. Quindi lo rendi un parametro persistente `lang` e verrà trasferito da solo. Fantastico! +Un esempio d'uso? Immaginate di avere un'applicazione multilingue. La lingua corrente è un parametro che deve sempre far parte dell'URL. Ma sarebbe incredibilmente noioso indicarlo in ogni link. Lo rendete quindi un parametro persistente `lang` e verrà trascinato automaticamente. Comodo! -La creazione di un parametro persistente in Nette è estremamente semplice. Basta creare una property pubblica e contrassegnarla con un attributo: (in precedenza si usava `/** @persistent */`) +Creare un parametro persistente in Nette è estremamente semplice. Basta creare una proprietà pubblica e contrassegnarla con l'attributo: (in precedenza si usava `/** @persistent */`) ```php use Nette\Application\Attributes\Persistent; // questa riga è importante @@ -280,14 +305,14 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Se `$this->lang` avrà il valore, ad esempio, `'en'`, anche i link creati tramite `link()` o `n:href` conterranno il parametro `lang=en`. E dopo aver cliccato sul link, sarà di nuovo `$this->lang = 'en'`. +Se `$this->lang` ha un valore come `'en'`, allora anche i link creati con `link()` o `n:href` conterranno il parametro `lang=en`. E dopo aver cliccato il link, `$this->lang` sarà di nuovo `'en'`. -Per la property, consigliamo di specificare anche il tipo di dati (es. `string`) e puoi anche specificare un valore predefinito. I valori dei parametri possono essere [validati |#Validazione dei parametri]. +Per la proprietà consigliamo di indicare il tipo di dato (per esempio `string`) e potete anche fornire un valore predefinito. I valori dei parametri si possono [validare |#Validazione dei parametri]. -I parametri persistenti vengono standardmente trasferiti tra tutte le azioni del presenter dato. Affinché vengano trasferiti anche tra più presenter, è necessario definirli o: +I parametri persistenti vengono di norma trasferiti tra tutte le azioni di un determinato presenter. Per trasferirli anche tra più presenter, vanno definiti: -- in un antenato comune da cui ereditano i presenter -- in un trait che i presenter utilizzeranno: +- in un antenato comune dal quale i presenter ereditano +- oppure in un trait che i presenter usano: ```php trait LanguageAware @@ -302,42 +327,62 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Durante la creazione di un link, è possibile modificare il valore del parametro persistente: +Quando si crea un link, il valore di un parametro persistente si può cambiare: ```latte dettaglio in ceco ``` -Oppure può essere *resettato*, cioè rimosso dall'URL. Assumerà quindi il suo valore predefinito: +In alternativa lo si può *azzerare*, cioè rimuovere dall'URL. Assumerà allora il proprio valore predefinito: ```latte clicca ``` -Componenti Interattivi +Spazio condiviso dei parametri +============================== + +I parametri della richiesta, i [parametri persistenti |#Parametri persistenti] e i parametri dei metodi `action`, `render` e `handle` (segnale) condividono un unico spazio, in cui ognuno è identificato dal proprio nome. Se lo stesso nome compare in più di uno di essi, si riferiscono a uno stesso e identico valore. + +Spesso lo si sfrutta a proprio vantaggio. Per esempio, il parametro persistente `lang` e l'argomento `$lang` di un metodo di azione o di segnale sono la stessa cosa: potete leggere il valore corrente di un parametro persistente semplicemente elencandolo nella firma del metodo: + +```php +#[Persistent] +public string $lang; + +public function handleSearch(string $query, string $lang): void +{ + // $lang contiene il valore corrente del parametro persistente lang +} +``` + +Poiché questo spazio è condiviso, tenete unici i nomi dei parametri, a meno che non vogliate deliberatamente che condividano un valore. Questo vale anche per i segnali, che in più leggono i parametri dal corpo POST della richiesta, vedi [I segnali in profondità |components#I segnali in profondità]. + + +Componenti interattivi ====================== -I presenter hanno un sistema di componenti integrato. I componenti sono unità riutilizzabili separate che inseriamo nei presenter. Possono essere [form |forms:in-presenter], datagrid, menu, praticamente qualsiasi cosa che abbia senso usare ripetutamente. +I presenter hanno un sistema di componenti integrato. I componenti sono unità riutilizzabili autonome che incorporiamo nei presenter. Possono essere [form |forms:in-presenter], datagrid, menu, in pratica tutto ciò che ha senso usare più volte. -Come vengono inseriti i componenti nel presenter e successivamente utilizzati? Lo imparerai nel capitolo [Componenti |components]. Scoprirai persino cosa hanno in comune con Hollywood. +Come si incorporano i componenti nei presenter e come li si usa poi? Lo imparerete nel capitolo [Componenti |components]. Scoprirete perfino cosa hanno in comune con Hollywood. -E dove posso ottenere i componenti? Sulla pagina [Componette |https://componette.org/search/component] troverai componenti open-source e anche numerosi altri add-on per Nette, che sono stati inseriti qui da volontari della comunità attorno al framework. +E dove trovo i componenti? Su [Componette |https://componette.org/search/component] trovate componenti open source e molte altre estensioni per Nette, offerte da volontari della comunità del framework. -Andiamo in profondità -===================== +Andiamo più a fondo +=================== .[tip] -Con quello che abbiamo mostrato finora in questo capitolo, probabilmente ti basterà. Le righe seguenti sono destinate a coloro che sono interessati ai presenter in profondità e vogliono sapere assolutamente tutto. +Quello che abbiamo trattato finora in questo capitolo basterà probabilmente per la maggior parte degli usi. Le sezioni seguenti sono destinate a chi vuole approfondire i presenter e sapere assolutamente tutto. Validazione dei parametri ------------------------- -I valori dei [#parametri della richiesta] e dei [#parametri persistenti] ricevuti dall'URL vengono scritti nelle properties dal metodo `loadState()`. Questo controlla anche se il tipo di dati specificato nella property corrisponde, altrimenti risponde con un errore 404 e la pagina non viene visualizzata. +I valori dei [parametri della richiesta |#Parametri della richiesta] e dei [parametri persistenti |#Parametri persistenti] ricevuti dagli URL vengono scritti nelle proprietà dal metodo `loadState()`. Esso controlla anche che il tipo di dato indicato nella proprietà corrisponda; in caso contrario risponde con un errore 404 e la pagina non viene mostrata. -Non fidarti mai ciecamente dei parametri, perché possono essere facilmente sovrascritti dall'utente nell'URL. In questo modo, ad esempio, verifichiamo se la lingua `$this->lang` è tra quelle supportate. Un modo appropriato è sovrascrivere il metodo `loadState()` menzionato: +Non fidatevi mai ciecamente dei parametri ricevuti dall'URL, perché l'utente può sovrascriverli facilmente. Ecco per esempio come verificheremmo che la lingua `$this->lang` sia tra quelle supportate. Un modo adatto è sovrascrivere il metodo `loadState()` già menzionato: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -348,7 +393,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { parent::loadState($params); // qui viene impostato $this->lang - // segue il controllo del valore personalizzato: + // segue il controllo personalizzato del valore: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -357,106 +402,148 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Salvataggio e ripristino della richiesta ----------------------------------------- +Salvare e ripristinare la richiesta +----------------------------------- -La richiesta che il presenter gestisce è un oggetto [api:Nette\Application\Request] e viene restituita dal metodo del presenter `getRequest()`. +La richiesta gestita dal presenter è un oggetto [api:Nette\Application\Request], restituito dal metodo `getRequest()` del presenter. -La richiesta corrente può essere salvata nella sessione o, al contrario, ripristinata da essa e fatta eseguire nuovamente dal presenter. Questo è utile, ad esempio, nella situazione in cui l'utente sta compilando un modulo e la sua sessione scade. Per non perdere i dati, prima del redirect alla pagina di login, salviamo la richiesta corrente nella sessione tramite `$reqId = $this->storeRequest()`, che restituisce il suo identificatore sotto forma di una breve stringa e lo passiamo come parametro al presenter di login. +La richiesta corrente si può salvare nella sessione oppure, al contrario, ripristinarla da essa e farla eseguire di nuovo al presenter. È utile, per esempio, quando un utente sta compilando un form e la sua sessione di login scade. Per non perdere i dati, prima di reindirizzare alla pagina di login salviamo la richiesta corrente nella sessione con `$reqId = $this->storeRequest()`. Questo restituisce il suo identificatore come stringa breve, che passiamo poi come parametro al presenter di login. -Dopo il login, chiamiamo il metodo `$this->restoreRequest($reqId)`, che recupera la richiesta dalla sessione e vi inoltra. Il metodo verifica nel frattempo che la richiesta sia stata creata dallo stesso utente che si è appena loggato. Se si loggasse un altro utente o la chiave non fosse valida, non fa nulla e il programma continua. +Dopo il login chiamiamo il metodo `$this->restoreRequest($reqId)`, che recupera la richiesta dalla sessione. Le richieste POST le vengono inoltrate, mentre le altre (GET) vengono reindirizzate all'URL della richiesta. Il metodo verifica che la richiesta sia stata creata dallo stesso utente ora connesso. Se accede un utente diverso o se la chiave non è valida, non fa nulla e il programma prosegue come al solito. -Dai un'occhiata alla guida [Come tornare alla pagina precedente |best-practices:restore-request]. +Vedi la guida [Come tornare a una pagina precedente |best-practices:restore-request]. Canonizzazione -------------- -I presenter hanno una caratteristica davvero fantastica che contribuisce a un migliore SEO (ottimizzazione della reperibilità su Internet). Impediscono automaticamente l'esistenza di contenuti duplicati su URL diversi. Se a una determinata destinazione portano più indirizzi URL, ad es. `/index` e `/index?page=1`, il framework ne determina uno come primario (canonico) e reindirizza gli altri ad esso tramite il codice HTTP 301. Grazie a ciò, i motori di ricerca non indicizzano le pagine due volte e non diluiscono il loro page rank. +I presenter hanno una funzionalità davvero eccellente, che contribuisce a una migliore SEO (Search Engine Optimization). Impediscono automaticamente l'esistenza di contenuto duplicato su URL diversi. Se più URL portano a una determinata destinazione, per esempio `/index` e `/index?page=1`, il framework ne designa uno come principale (canonico) e vi reindirizza gli altri con il codice HTTP 301. Grazie a questo i motori di ricerca non indicizzano le vostre pagine due volte e non ne diluiscono il page rank. -Questo processo si chiama canonizzazione. L'URL canonico è quello generato dal [router|routing], di solito quindi la prima route corrispondente nella collezione. +Questo processo si chiama canonizzazione. L'URL canonico è quello generato dal [router|routing], di norma la prima route corrispondente della collezione. -La canonizzazione è attivata per impostazione predefinita e può essere disattivata tramite `$this->autoCanonicalize = false`. +La canonizzazione è attiva per impostazione predefinita e si può disattivare con `$this->autoCanonicalize = false`. -Il redirect non avviene durante una richiesta AJAX o POST, perché si verificherebbe una perdita di dati o non avrebbe valore aggiunto dal punto di vista SEO. +Il redirect non avviene durante le richieste AJAX o POST, perché potrebbe portare a una perdita di dati o non offrirebbe alcun valore aggiunto in termini di SEO. -La canonizzazione può essere invocata anche manualmente tramite il metodo `canonicalize()`, al quale, in modo simile al metodo `link()`, vengono passati il presenter, l'azione e i parametri. Crea un link e lo confronta con l'URL corrente. Se differiscono, reindirizza al link generato. +Potete anche avviare la canonizzazione manualmente con il metodo `canonicalize()`. Come per il metodo `link()`, gli passate il presenter, l'azione e i parametri. Genera un link e lo confronta con l'indirizzo URL corrente. Se differiscono, reindirizza al link generato. ```php public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // reindirizza, se $slug differisce da $realSlug + // reindirizza se $slug è diverso da $realSlug $this->canonicalize('Product:show', [$id, $realSlug]); } ``` - -Eventi ------- - -Oltre ai metodi `startup()`, `beforeRender()` e `shutdown()`, che vengono chiamati come parte del ciclo di vita del presenter, è possibile definire altre funzioni che devono essere chiamate automaticamente. Il presenter definisce i cosiddetti [eventi |nette:glossary#Eventi], i cui handler aggiungi agli array `$onStartup`, `$onRender` e `$onShutdown`. - -```php -class ArticlePresenter extends Nette\Application\UI\Presenter -{ - public function __construct() - { - $this->onStartup[] = function () { - // ... - }; - } -} -``` - -Gli handler nell'array `$onStartup` vengono chiamati poco prima del metodo `startup()`, poi `$onRender` tra `beforeRender()` e `render()` e infine `$onShutdown` poco prima di `shutdown()`. +Per uno schema completo che combina i filtri delle route con `canonicalize()` per produrre URL adatti alla SEO, vedi [URL leggibili con gli slug |best-practices:pretty-urls]. Risposte -------- -La risposta restituita dal presenter è un oggetto che implementa l'interfaccia [api:Nette\Application\Response]. Sono disponibili numerose risposte pronte: +La risposta restituita dal presenter è un oggetto che implementa l'interfaccia [api:Nette\Application\Response]. Sono disponibili diverse risposte già pronte: -- [api:Nette\Application\Responses\CallbackResponse] - invia un callback -- [api:Nette\Application\Responses\FileResponse] - invia un file +- [api:Nette\Application\Responses\CallbackResponse] - invia una callback +- [api:Nette\Application\Responses\FileResponse] - invia il file - [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - invia JSON - [api:Nette\Application\Responses\RedirectResponse] - redirect - [api:Nette\Application\Responses\TextResponse] - invia testo - [api:Nette\Application\Responses\VoidResponse] - risposta vuota -Le risposte vengono inviate con il metodo `sendResponse()`: +Le risposte si inviano con il metodo `sendResponse()`: ```php use Nette\Application\Responses; -// Testo semplice -$this->sendResponse(new Responses\TextResponse('Ciao Nette!')); +// testo semplice +$this->sendResponse(new Responses\TextResponse('Hello Nette!')); -// Invia un file +// invia un file $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// La risposta sarà un callback +// invia una callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { - echo '

    Ciao

    '; + echo '

    Hello

    '; } }; $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +Potete anche scrivere una vostra risposta. Basta implementare l'interfaccia `Nette\Application\Response`, che ha un unico metodo `send()`, il quale riceve la richiesta e la risposta HTTP. È utile, per esempio, quando si trasmettono in streaming dati che non volete tenere in memoria: + +```php +class CsvResponse implements Nette\Application\Response +{ + public function __construct( + private string $fileName, + private iterable $rows, + ) { + } + + public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void + { + $response->setContentType('text/csv', 'utf-8'); + $response->sendAsFile($this->fileName); + + $handle = fopen('php://output', 'w'); + foreach ($this->rows as $row) { + fputcsv($handle, $row); + } + + fclose($handle); + } +} +``` + +La inviate poi nel presenter come al solito: `$this->sendResponse(new CsvResponse('export.csv', $rows));` + + +Caching HTTP +------------ + +Il metodo `lastModified()` rende semplice sfruttare il caching HTTP. Gli passate la data e l'ora dell'ultima modifica del contenuto (come timestamp, stringa oppure oggetto `DateTimeInterface`) e, facoltativamente, un validatore ETag (una breve stringa che identifica la versione corrente del contenuto, per esempio un suo hash) e un tempo di scadenza. Se il browser possiede già una versione corrispondente, il presenter invia una risposta `304 Not Modified` e termina, così la pagina non viene disegnata né trasferita inutilmente: + +```php +public function renderArticle(int $id): void +{ + $article = $this->articles->getById($id); + $this->lastModified($article->updatedAt); + // ... +} +``` + + +Completare il template .{data-version:3.3.0} +-------------------------------------------- + +Quando il presenter disegna un template, il metodo `sendTemplate()` chiama `completeTemplate()` subito prima del rendering. Questo metodo riempie le variabili contrassegnate con l'attributo `#[TemplateVariable]` e individua il file del template (le variabili predefinite sono già impostate dal `TemplateFactory` al momento della creazione del template). Potete sovrascrivere questo metodo protected per aggiungere variabili condivise da tutte le viste o per impostare un file diverso: + +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My App'; +} +``` + -Restrizione dell'accesso tramite `#[Requires]` .{data-version:3.2.2} --------------------------------------------------------------------- +Limitare l'accesso con `#[Requires]` .{data-version:3.2.3} +---------------------------------------------------------- -L'attributo `#[Requires]` offre opzioni avanzate per limitare l'accesso ai presenter e ai loro metodi. Può essere utilizzato per specificare metodi HTTP, richiedere una richiesta AJAX, limitare alla stessa origine (same origin) e l'accesso solo tramite forward. L'attributo può essere applicato sia alle classi dei presenter che ai singoli metodi `action()`, `render()`, `handle()` e `createComponent()`. +L'attributo `#[Requires]` offre possibilità avanzate per limitare l'accesso ai presenter e ai loro metodi. Si può usare per indicare i metodi HTTP, per richiedere una richiesta AJAX, per limitare alla stessa origine e per consentire l'accesso solo tramite forwarding. L'attributo si può applicare sia alle classi dei presenter sia ai singoli metodi come `action()`, `render()`, `handle()` e `createComponent()`. -È possibile specificare queste restrizioni: +Potete indicare queste restrizioni: - sui metodi HTTP: `#[Requires(methods: ['GET', 'POST'])]` -- richiesta di una richiesta AJAX: `#[Requires(ajax: true)]` +- richiedere una richiesta AJAX: `#[Requires(ajax: true)]` - accesso solo dalla stessa origine: `#[Requires(sameOrigin: true)]` -- accesso solo tramite forward: `#[Requires(forward: true)]` -- restrizione ad azioni specifiche: `#[Requires(actions: 'default')]` +- accesso solo tramite forwarding: `#[Requires(forward: true)]` +- restrizioni su azioni specifiche: `#[Requires(actions: 'default')]` + +.[note] +Dalla versione 3.3 la corrispondenza della stessa origine è verificata tramite l'header `Sec-Fetch-Site` del browser (prima tramite un cookie SameSite), il che è più affidabile e controlla la corrispondenza esatta di schema, dominio e porta. I dettagli si trovano nella guida [Come usare l'attributo Requires |best-practices:attribute-requires]. @@ -464,9 +551,9 @@ I dettagli si trovano nella guida [Come usare l'attributo Requires |best-practic Controllo del metodo HTTP ------------------------- -I presenter in Nette verificano automaticamente il metodo HTTP di ogni richiesta in arrivo. Il motivo di questo controllo è principalmente la sicurezza. Standardmente sono consentiti i metodi `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. +I presenter in Nette verificano automaticamente il metodo HTTP di ogni richiesta in arrivo, soprattutto per motivi di sicurezza. Per impostazione predefinita sono ammessi i metodi `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. -Se si desidera consentire inoltre, ad esempio, il metodo `OPTIONS`, utilizzare l'attributo `#[Requires]` (da Nette Application v3.2): +Se volete ammettere in più, per esempio, il metodo `OPTIONS`, usate l'attributo `#[Requires]` (da Nette Application v3.2.3): ```php #[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] @@ -475,26 +562,34 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Nella versione 3.1, la verifica viene eseguita in `checkHttpMethod()`, che verifica se il metodo specificato nella richiesta è contenuto nell'array `$presenter->allowedMethods`. L'aggiunta del metodo si fa così: +Dalla versione 3.1.13 la verifica avviene in `checkHttpMethod()`, che controlla se il metodo indicato nella richiesta è presente nell'array `$presenter->allowedMethods`. Dalla versione 3.2.3 questo approccio è deprecato in favore di `#[Requires]`. Potete sovrascrivere il metodo così: ```php class MyPresenter extends Nette\Application\UI\Presenter { - protected function checkHttpMethod(): void - { - $this->allowedMethods[] = 'OPTIONS'; - parent::checkHttpMethod(); - } + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } } ``` -È importante sottolineare che se si consente il metodo `OPTIONS`, è necessario successivamente gestirlo adeguatamente all'interno del proprio presenter. Il metodo è spesso utilizzato come cosiddetta richiesta preflight, che il browser invia automaticamente prima della richiesta effettiva, quando è necessario verificare se la richiesta è consentita dal punto di vista della politica CORS (Cross-Origin Resource Sharing). Se si consente il metodo, ma non si implementa la risposta corretta, ciò può portare a incoerenze e potenziali problemi di sicurezza. +È importante sottolineare che, se attivate il metodo `OPTIONS`, dovete poi gestirlo in modo appropriato nel vostro presenter. Questo metodo è spesso usato come cosiddetta richiesta preflight, che il browser invia automaticamente prima della richiesta vera e propria quando è necessario stabilire se la richiesta è ammissibile secondo la policy CORS (Cross-Origin Resource Sharing). Se attivate il metodo ma non implementate la risposta corretta, si possono creare incoerenze e potenziali problemi di sicurezza. + + +Contrassegnare le azioni deprecate .{data-version:3.2.3} +-------------------------------------------------------- +L'attributo `#[Deprecated]` contrassegna azioni, segnali o interi presenter come deprecati e destinati alla rimozione futura. Generando link verso parti deprecate dell'applicazione, Nette emette un avviso per allertare gli sviluppatori. -Ulteriori letture -================= +Potete applicare l'attributo all'intera classe del presenter oppure ai singoli metodi `action()`, `render()` e `handle()`. + + +Letture consigliate +=================== - [Metodi e attributi inject |best-practices:inject-method-attribute] -- [Composizione di presenter da trait |best-practices:presenter-traits] -- [Passaggio di impostazioni ai presenter |best-practices:passing-settings-to-presenters] -- [Come tornare alla pagina precedente |best-practices:restore-request] +- [Comporre i presenter con i trait |best-practices:presenter-traits] +- [Passare le impostazioni ai presenter |best-practices:passing-settings-to-presenters] +- [Come tornare a una pagina precedente |best-practices:restore-request] diff --git a/application/it/routing.texy b/application/it/routing.texy index 36b8f5968c..88b63e9400 100644 --- a/application/it/routing.texy +++ b/application/it/routing.texy @@ -3,26 +3,26 @@ Routing
    -Il Router si occupa di tutto ciò che riguarda gli indirizzi URL, così non dovrai più pensarci tu. Vedremo: +Il router si occupa di tutto ciò che riguarda gli indirizzi URL, così non dovete pensarci voi. Vi mostreremo: -- come impostare il router affinché gli URL siano come desiderato -- parleremo di SEO e redirect +- come configurare il router perché gli URL appaiano come volete +- parleremo di SEO e di redirect - e mostreremo come scrivere un router personalizzato
    -URL più umani (o anche cool o pretty URL) sono più usabili, memorizzabili e contribuiscono positivamente al SEO. Nette ci pensa e va incontro pienamente agli sviluppatori. Puoi progettare per la tua applicazione esattamente la struttura degli indirizzi URL che desideri. Puoi progettarla persino quando l'applicazione è già finita, perché si può fare senza interventi nel codice o nei template. Si definisce infatti in modo elegante in un [unico posto |#Integrazione nell applicazione], nel router, e non è quindi disseminata sotto forma di annotazioni in tutti i presenter. +Gli URL più amichevoli (noti anche come cool o pretty URL) sono più usabili, più facili da ricordare e contribuiscono positivamente alla SEO. Nette ne tiene conto e va pienamente incontro alle esigenze degli sviluppatori. Potete progettare per la vostra applicazione esattamente la struttura di URL che volete. Potete perfino progettarla quando l'applicazione è già finita, perché non richiede alcuna modifica al codice o ai template. Si definisce elegantemente in [un unico punto |#Integrazione], il router, invece di essere sparsa come annotazioni in tutti i presenter. -Il router in Nette è eccezionale perché è **bidirezionale.** Sa sia decodificare gli URL nella richiesta HTTP, sia creare i link. Svolge quindi un ruolo fondamentale in [Nette Application |how-it-works#Nette Application], perché decide quale presenter e azione eseguirà la richiesta corrente, ma viene anche utilizzato per la [generazione di URL |creating-links] nel template, ecc. +Il router in Nette è eccezionale perché è **bidirezionale**. Sa sia decodificare gli URL delle richieste HTTP sia creare link. Ha quindi un ruolo essenziale in [Nette Application |how-it-works#Nette Application], perché non solo decide quale presenter e quale azione eseguiranno la richiesta corrente, ma serve anche a [generare gli URL |creating-links] nei template e altrove. -Tuttavia, il router non è limitato solo a questo utilizzo, puoi usarlo in applicazioni dove i presenter non vengono affatto utilizzati, per API REST, ecc. Maggiori informazioni nella sezione [#Utilizzo indipendente]. +Il router non è però limitato a questo uso: potete usarlo in applicazioni in cui i presenter non vengono usati affatto, per API REST e così via. Maggiori dettagli nella sezione [#Uso autonomo]. Collezione di route =================== -Il modo più piacevole per definire la forma degli indirizzi URL nell'applicazione è offerto dalla classe [api:Nette\Application\Routers\RouteList]. La definizione è costituita da un elenco delle cosiddette route, cioè maschere di indirizzi URL e presenter e azioni associati ad esse tramite una semplice API. Non è necessario dare un nome alle route. +Il modo più piacevole di definire la struttura degli indirizzi URL di un'applicazione lo offre la classe [api:Nette\Application\Routers\RouteList]. La definizione consiste in un elenco di cosiddette route, cioè di maschere di indirizzi URL e dei presenter e delle azioni a esse associati, tramite un'API semplice. Non abbiamo bisogno di dare un nome alle route. ```php $router = new Nette\Application\Routers\RouteList; @@ -31,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -L'esempio dice che se apriamo `https://domain.com/rss.xml` nel browser, verrà visualizzato il presenter `Feed` con l'azione `rss`, se `https://domain.com/article/12`, verrà visualizzato il presenter `Article` con l'azione `view`, ecc. In caso di mancata corrispondenza di una route adatta, Nette Application reagisce lanciando un'eccezione [BadRequestException |api:Nette\Application\BadRequestException], che viene mostrata all'utente come una pagina di errore 404 Not Found. +L'esempio mostra che, se apriamo nel browser `https://domain.com/rss.xml`, verrà mostrato il presenter `Feed` con l'azione `rss`. Se apriamo `https://domain.com/article/12`, verrà mostrato il presenter `Article` con l'azione `view` e così via. Se non viene trovata alcuna route adatta, Nette Application risponde sollevando una [BadRequestException |api:Nette\Application\BadRequestException], che viene mostrata all'utente come pagina di errore 404 Not Found. Ordine delle route ------------------ -L'**ordine** in cui sono elencate le singole route è **assolutamente cruciale**, perché vengono valutate sequenzialmente dall'alto verso il basso. Vale la regola che dichiariamo le route **dalle specifiche alle generali**: +L'**ordine** in cui sono elencate le singole route è assolutamente **essenziale**, perché vengono valutate in sequenza dall'alto verso il basso. La regola è dichiarare le route **dalla più specifica alla più generica**: ```php -// SBAGLIATO: 'rss.xml' viene catturato dalla prima route e interpreta questa stringa come +// SBAGLIATO: 'rss.xml' viene catturato dalla prima route, che intende questa stringa come $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -49,10 +49,10 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Le route vengono valutate dall'alto verso il basso anche durante la generazione dei link: +Anche nella generazione dei link le route vengono valutate dall'alto verso il basso: ```php -// SBAGLIATO: il link a 'Feed:rss' genera come 'admin/feed/rss' +// SBAGLIATO: il link a 'Feed:rss' viene generato come 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -61,57 +61,57 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Non ti nasconderemo che la corretta composizione delle route richiede una certa abilità. Prima di padroneggiarla, ti sarà utile il [pannello di routing |#Debug del router]. +Non vi nasconderemo che comporre correttamente le route richiede una certa abilità. Finché non l'avrete padroneggiata, il [pannello del routing |#Debugging del router] vi sarà uno strumento utile. Maschera e parametri -------------------- -La maschera descrive il percorso relativo dalla directory principale del sito web. La maschera più semplice è un URL statico: +La maschera descrive il percorso relativo alla directory radice del sito. La maschera più semplice è un URL statico: ```php $router->addRoute('products', 'Products:default'); ``` -Spesso le maschere contengono i cosiddetti **parametri**. Questi sono indicati tra parentesi angolari (es. ``) e vengono passati al presenter di destinazione, ad esempio al metodo `renderShow(int $year)` o al parametro persistente `$year`: +Spesso le maschere contengono i cosiddetti **parametri**. Sono racchiusi tra parentesi angolari (per esempio ``) e vengono passati al presenter di destinazione, per esempio al metodo `renderShow(int $year)` oppure al parametro persistente `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -L'esempio dice che se apriamo `https://example.com/chronicle/2020` nel browser, verrà visualizzato il presenter `History` con l'azione `show` e il parametro `year: 2020`. +L'esempio mostra che, se apriamo nel browser `https://example.com/chronicle/2020`, verrà mostrato il presenter `History` con l'azione `show` e il parametro `year: 2020`. -Possiamo specificare un valore predefinito per i parametri direttamente nella maschera, rendendoli così opzionali: +Possiamo indicare un valore predefinito per i parametri direttamente nella maschera, rendendoli così facoltativi: ```php $router->addRoute('chronicle/', 'History:show'); ``` -La route accetterà ora anche l'URL `https://example.com/chronicle/`, che visualizzerà nuovamente `History:show` con il parametro `year: 2020`. +La route accetterà ora anche l'URL `https://example.com/chronicle/`, che mostrerà di nuovo `History:show` con il parametro `year: 2020`. -Un parametro può ovviamente essere anche il nome del presenter e dell'azione. Ad esempio così: +Naturalmente anche i nomi del presenter e dell'azione possono essere parametri. Per esempio: ```php $router->addRoute('/', 'Home:default'); ``` -La route specificata accetta ad es. URL nella forma `/article/edit` o anche `/catalog/list` e li interpreta come presenter e azioni `Article:edit` e `Catalog:list`. +La route indicata accetta, per esempio, URL nella forma `/article/edit` o `/catalog/list` e li interpreta rispettivamente come i presenter e le azioni `Article:edit` e `Catalog:list`. -Allo stesso tempo, assegna ai parametri `presenter` e `action` i valori predefiniti `Home` e `default` e sono quindi anche opzionali. Quindi la route accetta anche URL nella forma `/article` e la interpreta come `Article:default`. O viceversa, un link a `Product:default` genererà il percorso `/product`, un link al predefinito `Home:default` il percorso `/`. +Allo stesso tempo assegna ai parametri `presenter` e `action` i valori predefiniti `Home` e `default`, rendendoli così anch'essi facoltativi. La route accetta quindi anche un URL come `/article` e lo interpreta come `Article:default`. Oppure, al contrario, un link a `Product:default` genera il percorso `/product` e un link al `Home:default` predefinito genera il percorso `/`. -La maschera può descrivere non solo il percorso relativo dalla directory principale del sito web, ma anche il percorso assoluto, se inizia con uno slash, o persino l'intero URL assoluto, se inizia con due slash: +La maschera può descrivere non solo il percorso relativo alla directory radice del sito, ma anche un percorso assoluto se inizia con una barra, oppure perfino l'intero URL assoluto se inizia con due barre: ```php -// relativamente alla document root +// relativo al document root $router->addRoute('/', /* ... */); // percorso assoluto (relativo al dominio) $router->addRoute('//', /* ... */); -// URL assoluto incluso il dominio (relativo allo schema) +// URL assoluto, dominio compreso (relativo allo schema) $router->addRoute('//.example.com//', /* ... */); -// URL assoluto incluso lo schema +// URL assoluto, schema compreso $router->addRoute('https://.example.com//', /* ... */); ``` @@ -119,42 +119,42 @@ $router->addRoute('https://.example.com//', /* ... */); Espressioni di validazione -------------------------- -Per ogni parametro è possibile stabilire una condizione di validazione tramite un'[espressione regolare|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Ad esempio, per il parametro `id` specifichiamo che può assumere solo cifre tramite l'espressione regolare `\d+`: +Per ogni parametro si può indicare una condizione di validazione tramite un'[espressione regolare|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Per esempio, per il parametro `id` indichiamo che può contenere solo cifre, con la regex `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -L'espressione regolare predefinita per tutti i parametri è `[^/]+`, cioè tutto tranne lo slash. Se un parametro deve accettare anche gli slash, specifichiamo l'espressione `.+`: +L'espressione regolare predefinita di tutti i parametri è `[^/]+`, cioè tutto tranne la barra. Se un parametro deve accettare anche le barre, impostiamo l'espressione a `.+`: ```php -// accetta https://example.com/a/b/c, path sarà 'a/b/c' +// accetta https://example.com/a/b/c, il percorso sarà 'a/b/c' $router->addRoute('', /* ... */); ``` -Sequenze opzionali ------------------- +Sequenze facoltative +-------------------- -Nella maschera è possibile contrassegnare parti opzionali tramite parentesi quadre. Qualsiasi parte della maschera può essere opzionale, possono esserci anche parametri al suo interno: +Nella maschera le parti facoltative si possono contrassegnare con le parentesi quadre. Qualsiasi parte della maschera può essere facoltativa e può contenere parametri: ```php $router->addRoute('[/]', /* ... */); -// Accetta percorsi: -// /cs/download => lang => cs, name => download +// accetta i percorsi: +// /en/download => lang => en, name => download // /download => lang => null, name => download ``` -Quando un parametro fa parte di una sequenza opzionale, diventa ovviamente anche opzionale. Se non ha un valore predefinito specificato, sarà null. +Quando un parametro fa parte di una sequenza facoltativa, diventa naturalmente facoltativo anch'esso. Se non ha un valore predefinito indicato, sarà null. -Le parti opzionali possono essere anche nel dominio: +Le parti facoltative possono trovarsi anche nel dominio: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Le sequenze possono essere nidificate e combinate liberamente: +Le sequenze si possono annidare e combinare a piacere: ```php $router->addRoute( @@ -162,14 +162,14 @@ $router->addRoute( 'Home:default', ); -// Accetta percorsi: -// /cs/hello +// accetta i percorsi: +// /en/hello // /en-us/hello // /hello // /hello/page-12 ``` -Durante la generazione dell'URL, si cerca la variante più corta, quindi tutto ciò che può essere omesso viene omesso. Per questo, ad esempio, la route `index[.html]` genera il percorso `/index`. È possibile invertire il comportamento specificando un punto esclamativo dopo la parentesi quadra sinistra: +Nella generazione degli URL si preferisce la variante più breve, quindi tutto ciò che si può omettere viene omesso. Perciò, per esempio, la route `index[.html]` genera il percorso `/index`. Questo comportamento si può invertire mettendo un punto esclamativo dopo la parentesi quadra sinistra: ```php // accetta /hello e /hello.html, genera /hello @@ -179,7 +179,7 @@ $router->addRoute('[.html]', /* ... */); $router->addRoute('[!.html]', /* ... */); ``` -I parametri opzionali (cioè i parametri con un valore predefinito) senza parentesi quadre si comportano essenzialmente come se fossero racchiusi tra parentesi nel modo seguente: +I parametri facoltativi (cioè i parametri con un valore predefinito) senza parentesi quadre si comportano in sostanza come se fossero racchiusi nel modo seguente: ```php $router->addRoute('//', /* ... */); @@ -188,7 +188,7 @@ $router->addRoute('//', /* ... */); $router->addRoute('[/[/[]]]', /* ... */); ``` -Se volessimo influenzare il comportamento dello slash finale, in modo che ad esempio invece di `/home/` venga generato solo `/home`, si può ottenere così: +Se vogliamo influire sul comportamento della barra finale, in modo che per esempio venga generato `/home` invece di `/home/`, lo si ottiene così: ```php $router->addRoute('[[/[/]]]', /* ... */); @@ -198,24 +198,24 @@ $router->addRoute('[[/[/]]]', /* ... */); Caratteri jolly --------------- -Nella maschera del percorso assoluto possiamo utilizzare i seguenti caratteri jolly ed evitare così, ad esempio, la necessità di scrivere nella maschera il dominio, che può differire nell'ambiente di sviluppo e produzione: +Nella maschera di un URL assoluto possiamo usare i caratteri jolly seguenti per evitare, per esempio, di dover scrivere nella maschera il dominio, che potrebbe differire tra l'ambiente di sviluppo e quello di produzione: -- `%tld%` = top level domain, es. `com` o `org` -- `%sld%` = second level domain, es. `example` -- `%domain%` = dominio senza sottodomini, es. `example.com` -- `%host%` = intero host, es. `www.example.com` -- `%basePath%` = percorso alla directory principale +- `%tld%` = dominio di primo livello, per esempio `com` o `org` +- `%sld%` = dominio di secondo livello, per esempio `example` +- `%domain%` = dominio senza sottodomini, per esempio `example.com` +- `%host%` = host intero, per esempio `www.example.com` +- `%basePath%` = percorso della directory radice ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); -$router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('//www.%sld%.%tld%/%basePath%//', /* ... */); ``` -Notazione estesa ----------------- +Notazione avanzata +------------------ -La destinazione della route, solitamente scritta nella forma `Presenter:action`, può anche essere scritta utilizzando un array che definisce i singoli parametri e i loro valori predefiniti: +La destinazione della route, di norma scritta nel formato `Presenter:azione`, si può scrivere anche con un array che definisce i singoli parametri e i loro valori predefiniti: ```php $router->addRoute('/[/]', [ @@ -224,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Per una specifica più dettagliata, è possibile utilizzare una forma ancora più estesa, dove oltre ai valori predefiniti possiamo impostare altre proprietà dei parametri, come ad esempio l'espressione regolare di validazione (vedi parametro `id`): +Per una specifica più dettagliata si può usare una forma ancora più estesa, in cui, oltre ai valori predefiniti, possiamo impostare altre proprietà del parametro, come un'espressione regolare di validazione (vedi il parametro `id`): ```php use Nette\Routing\Route; @@ -242,19 +242,29 @@ $router->addRoute('/[/]', [ ]); ``` -È importante notare che se i parametri definiti nell'array non sono specificati nella maschera del percorso, i loro valori non possono essere modificati, nemmeno tramite i parametri query specificati dopo il punto interrogativo nell'URL. +È importante notare che, se i parametri definiti nell'array non sono elencati nella maschera del percorso, i loro valori non si possono cambiare, nemmeno con i parametri di query indicati dopo il punto interrogativo nell'URL. + +Questo è utile per i **parametri fissi**: dare a una pagina specifica un URL breve e facile da ricordare. Per esempio, per far sì che `/tos` apra sempre `Article:view` con `id: 123`: + +```php +$router->addRoute('tos', [ + 'presenter' => 'Article', + 'action' => 'view', + 'id' => 123, +]); +``` Filtri e traduzioni ------------------- -Scriviamo il codice sorgente dell'applicazione in inglese, ma se il sito web deve avere URL in italiano, allora un semplice routing del tipo: +Il codice sorgente dell'applicazione lo scriviamo in inglese, ma se il sito deve avere URL in ceco, un routing semplice come: ```php $router->addRoute('/', 'Home:default'); ``` -genererà URL in inglese, come `/product/123` o `/cart`. Se vogliamo che i presenter e le azioni nell'URL siano rappresentati da parole italiane (es. `/prodotto/123` o `/carrello`), possiamo utilizzare un dizionario di traduzione. Per la sua scrittura abbiamo già bisogno della variante "più verbosa" del secondo parametro: +genererà URL inglesi, come `/product/123` o `/cart`. Se vogliamo che nell'URL i presenter e le azioni siano rappresentati da parole ceche (per esempio `/produkt/123` o `/kosik`), possiamo usare un dizionario di traduzione. Per scriverlo ci serve già la variante "più prolissa" del secondo parametro: ```php use Nette\Routing\Route; @@ -264,25 +274,25 @@ $router->addRoute('/', [ Route::Value => 'Home', Route::FilterTable => [ // stringa nell'URL => presenter - 'prodotto' => 'Product', - 'carrello' => 'Cart', - 'catalogo' => 'Catalog', + 'produkt' => 'Product', + 'kosik' => 'Cart', + 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'elenco' => 'list', + 'seznam' => 'list', ], ], ]); ``` -Più chiavi del dizionario di traduzione possono portare allo stesso presenter. In questo modo si creano diversi alias per esso. La variante canonica (cioè quella che sarà nell'URL generato) è considerata l'ultima chiave. +Più chiavi del dizionario di traduzione possono portare allo stesso presenter. Si creano così vari alias per esso. L'ultima chiave è considerata la variante canonica (cioè quella che comparirà nell'URL generato). -La tabella di traduzione può essere utilizzata in questo modo per qualsiasi parametro. Se la traduzione non esiste, viene preso il valore originale. Possiamo cambiare questo comportamento aggiungendo `Route::FilterStrict => true` e la route rifiuterà quindi l'URL se il valore non è nel dizionario. +La tabella di traduzione si può usare in questo modo per qualsiasi parametro. Se una traduzione non esiste, viene preso il valore originale. Possiamo cambiare questo comportamento aggiungendo `Route::FilterStrict => true`: la route rifiuterà allora l'URL se il valore non è nel dizionario. -Oltre al dizionario di traduzione sotto forma di array, è possibile implementare anche funzioni di traduzione personalizzate. +Oltre al dizionario di traduzione sotto forma di array, si possono usare funzioni di traduzione personalizzate. ```php use Nette\Routing\Route; @@ -298,15 +308,15 @@ $router->addRoute('//', [ ]); ``` -La funzione `Route::FilterIn` converte tra il parametro nell'URL e la stringa, che viene poi passata al presenter, la funzione `FilterOut` assicura la conversione nella direzione opposta. +La funzione `Route::FilterIn` converte tra il parametro presente nell'URL e la stringa che viene poi passata al presenter; la funzione `FilterOut` garantisce la conversione nella direzione opposta. -I parametri `presenter`, `action` e `module` hanno già filtri predefiniti che convertono tra lo stile PascalCase o camelCase e kebab-case utilizzato nell'URL. Il valore predefinito dei parametri viene scritto già nella forma trasformata, quindi ad esempio nel caso del presenter scriviamo ``, non ``. +I parametri `presenter`, `action` e `module` hanno già filtri predefiniti che convertono tra lo stile PascalCase o camelCase e il kebab-case usato negli URL. Il valore predefinito dei parametri si scrive nella forma in cui viene passato all'applicazione (PascalCase per presenter e module, camelCase per action), quindi per esempio, nel caso di un presenter, scriviamo ``, non ``. Filtri generali --------------- -Oltre ai filtri destinati a parametri specifici, possiamo definire anche filtri generali, che ricevono un array associativo di tutti i parametri, che possono modificare in qualsiasi modo e poi restituirli. I filtri generali li definiamo sotto la chiave `null`. +Oltre ai filtri destinati a parametri specifici, possiamo definire anche filtri generali, che ricevono un array associativo di tutti i parametri, che possono modificare a piacere e poi restituire. I filtri generali si definiscono sotto la chiave vuota. ```php use Nette\Routing\Route; @@ -321,78 +331,94 @@ $router->addRoute('/', [ ]); ``` -I filtri generali danno la possibilità di modificare il comportamento della route in modo assolutamente qualsiasi. Possiamo usarli ad esempio per modificare i parametri in base ad altri parametri. Ad esempio, la traduzione di `` e `` in base al valore corrente del parametro ``. +I filtri generali offrono la possibilità di modificare il comportamento della route in assolutamente qualsiasi modo. Possiamo usarli, per esempio, per modificare parametri in base ad altri parametri. Per esempio per tradurre `` e `` in base al valore corrente del parametro ``. -Se un parametro ha definito un filtro personalizzato e contemporaneamente esiste un filtro generale, viene eseguito il `FilterIn` personalizzato prima di quello generale e viceversa il `FilterOut` generale prima di quello personalizzato. Quindi all'interno del filtro generale i valori dei parametri `presenter` o `action` sono scritti nello stile PascalCase o camelCase. +Se un parametro ha un proprio filtro definito ed esiste anche un filtro generale, il `FilterIn` personalizzato viene eseguito prima di quello generale e, al contrario, il `FilterOut` generale viene eseguito prima di quello personalizzato. Dentro il filtro generale i valori dei parametri `presenter` e `action` sono quindi scritti rispettivamente nello stile PascalCase e camelCase. +Vedi [URL leggibili con gli slug |best-practices:pretty-urls] per un uso pratico di questi filtri: generare URL adatti alla SEO come `/article/123-how-to-bake-bread` senza modificare alcun template. -Sensi unici OneWay ------------------- -Le route a senso unico vengono utilizzate per mantenere la funzionalità dei vecchi URL, che l'applicazione non genera più, ma accetta ancora. Le contrassegniamo con il flag `OneWay`: +Flag OneWay +----------- + +Le route a senso unico servono a mantenere il funzionamento dei vecchi URL che l'applicazione non genera più, ma che accetta ancora. Le contrassegniamo con il flag `OneWay`: ```php // vecchio URL /product-info?id=123 -$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); +$router->addRoute('product-info', 'Product:detail', oneWay: true); // nuovo URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -Accedendo al vecchio URL, il presenter reindirizza automaticamente al nuovo URL, così i motori di ricerca non indicizzeranno due volte queste pagine (vedi [#SEO e canonizzazione]). +All'accesso al vecchio URL, il presenter reindirizza automaticamente al nuovo, così i motori di ricerca non indicizzeranno queste pagine due volte (vedi [#SEO e canonizzazione]). -Routing dinamico con callback ------------------------------ +Routing dinamico con le callback +-------------------------------- -Il routing dinamico con callback ti consente di assegnare direttamente alle route funzioni (callback) che vengono eseguite quando viene visitato il percorso dato. Questa funzionalità flessibile ti consente di creare rapidamente ed efficacemente vari endpoint per la tua applicazione: +Il routing dinamico con le callback vi permette di assegnare direttamente alle route delle funzioni (callback), che vengono eseguite quando si visita il percorso indicato. Questa funzionalità flessibile vi permette di creare rapidamente ed efficacemente vari endpoint per la vostra applicazione: ```php $router->addRoute('test', function () { - echo 'sei all\'indirizzo /test'; + echo 'Vi trovate all\'indirizzo /test'; }); ``` -Puoi anche definire parametri nella maschera, che verranno automaticamente passati al tuo callback: +Nella maschera potete definire anche parametri, che vengono passati automaticamente alla vostra callback: ```php $router->addRoute('', function (string $lang) { echo match ($lang) { - 'cs' => 'Vítejte na české verzi našeho webu!', // Welcome to the Czech version of our website! - 'en' => 'Welcome to the English version of our website!', + 'cs' => 'Benvenuti nella versione ceca del nostro sito!', + 'en' => 'Benvenuti nella versione inglese del nostro sito!', }; }); ``` +Oltre ai parametri della maschera, la callback può ricevere anche servizi dal container DI. Vengono passati in base al tipo del parametro. Inoltre il parametro `$presenter` riceve un'istanza di [MicroPresenter |api:NetteModule\MicroPresenter], che elabora la route: + +```php +$router->addRoute('', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) { + // ... +}); +``` + Moduli ------ -Se abbiamo più route che rientrano in un [modulo |directory-structure#Presenter e template] comune, utilizziamo `withModule()`: +Se abbiamo più route che appartengono a un [modulo |directory-structure#Presenter e template] comune, usiamo `withModule()`. Il modulo indicato viene anteposto automaticamente al presenter di ogni route del gruppo e sparisce del tutto dall'URL: ```php $router = new RouteList; -$router->withModule('Forum') // le seguenti route fanno parte del modulo Forum +$router->withModule('Forum') // le route seguenti fanno parte del modulo Forum ->addRoute('rss', 'Feed:rss') // il presenter sarà Forum:Feed ->addRoute('/') - ->withModule('Admin') // le seguenti route fanno parte del modulo Forum:Admin + ->withModule('Admin') // le route seguenti fanno parte del modulo Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Un'alternativa è l'uso del parametro `module`: +Un'alternativa è il parametro `module`, che allo stesso modo imposta un modulo fisso e lo tiene fuori dall'URL: ```php -// L'URL manage/dashboard/default si mappa sul presenter Admin:Dashboard +// l'URL manage/dashboard/default è mappato sul presenter Admin:Dashboard $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` +Ogni nome di presenter è completo solo insieme al proprio modulo, per esempio `Front:Admin:ProductList`. Ogni volta che un nome completo del genere finisce in un parametro dell'URL, il router lo codifica con due semplici regole: ogni due punti `:` (il separatore dei moduli) diventa un **punto** e ogni confine di parola in un nome PascalCase diventa un **trattino**. Così `Front:Admin:ProductList` compare nell'URL come `front.admin.product-list` e viene decodificato allo stesso modo. Ecco perché un'applicazione modulare, senza nessuno degli strumenti visti sopra, produce URL pieni di punti. + +Sia `withModule()` sia il parametro `module` lo evitano proprio perché tolgono dal nome del presenter un prefisso di modulo noto prima che arrivi all'URL: poiché il modulo è una costante, non ha bisogno di essere codificato affatto. + +A volte vogliamo che il modulo stesso vari e compaia nell'URL, quindi ricorriamo direttamente a `` nella maschera. Attenzione a un dettaglio essenziale: **`` cattura l'intero percorso del modulo**, cioè tutto ciò che precede l'ultimo due punti del nome del presenter. Per il presenter `Shop:Admin:Product` questo significa il modulo `Shop:Admin` e il presenter `Product` e, poiché i due punti diventano punti, otteniamo: + Sottodomini ----------- -Possiamo suddividere le collezioni di route per sottodomini: +Le collezioni di route si possono suddividere per sottodomini: ```php $router = new RouteList; @@ -401,7 +427,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Nel nome del dominio è possibile utilizzare anche [#Caratteri jolly]: +Nel nome del dominio si possono usare anche i [caratteri jolly |#Caratteri jolly]: ```php $router = new RouteList; @@ -413,20 +439,20 @@ $router->withDomain('example.%tld%') Prefisso del percorso --------------------- -Possiamo suddividere le collezioni di route per percorso nell'URL: +Le collezioni di route si possono suddividere in base al percorso nell'URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // cattura l'URL /eshop/rss - ->addRoute('/'); // cattura l'URL /eshop// + ->addRoute('rss', 'Feed:rss') // corrisponde all'URL /eshop/rss + ->addRoute('/'); // corrisponde all'URL /eshop// ``` Combinazioni ------------ -Le suddivisioni sopra menzionate possono essere combinate tra loro: +I raggruppamenti visti sopra si possono combinare tra loro: ```php $router = (new RouteList) @@ -446,37 +472,37 @@ $router = (new RouteList) ``` -Parametri query ---------------- +Parametri di query +------------------ -Le maschere possono anche contenere parametri query (parametri dopo il punto interrogativo nell'URL). A questi non è possibile definire un'espressione di validazione, ma è possibile cambiare il nome con cui vengono passati al presenter: +Le maschere possono contenere anche parametri di query (i parametri che seguono il punto interrogativo nell'URL). Per essi non si può definire un'espressione di validazione, ma si può cambiare il nome con cui vengono passati al presenter: ```php -// il parametro query 'cat' vogliamo usarlo nell'applicazione con il nome 'categoryId' +// vogliamo usare il parametro di query 'cat' con il nome 'categoryId' nell'applicazione $router->addRoute('product ? id= & cat=', /* ... */); ``` -Parametri Foo +Parametri foo ------------- -Ora andiamo più a fondo. I parametri Foo sono essenzialmente parametri senza nome che consentono di abbinare un'espressione regolare. Un esempio è una route che accetta `/index`, `/index.html`, `/index.htm` e `/index.php`: +Ora andiamo più a fondo. I parametri foo sono in sostanza parametri senza nome, che permettono di far corrispondere un'espressione regolare. Un esempio è una route che accetta `/index`, `/index.html`, `/index.htm` e `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -È anche possibile definire esplicitamente la stringa che verrà utilizzata durante la generazione dell'URL. La stringa deve essere posizionata direttamente dopo il punto interrogativo. La seguente route è simile alla precedente, ma genera `/index.html` invece di `/index`, perché la stringa `.html` è impostata come valore di generazione: +È anche possibile definire esplicitamente la stringa che verrà usata nella generazione dell'URL. La stringa va collocata subito dopo il punto interrogativo. La route seguente è simile alla precedente, ma genera `/index.html` invece di `/index`, perché come valore di generazione è impostata la stringa `.html`: ```php $router->addRoute('index', /* ... */); ``` -Integrazione nell'applicazione -============================== +Integrazione +============ -Per integrare il router creato nell'applicazione, dobbiamo informarne il container DI. Il modo più semplice è preparare una factory che produca l'oggetto router e comunicare nella configurazione del container che deve usarla. Supponiamo di scrivere a tale scopo il metodo `App\Core\RouterFactory::createRouter()`: +Per integrare il router creato nell'applicazione dobbiamo comunicarlo al container DI. Il modo più semplice è preparare una factory che crei l'oggetto router e dire al container, nella configurazione, di usarla. Supponiamo di scrivere a questo scopo il metodo `App\Core\RouterFactory::createRouter()`: ```php namespace App\Core; @@ -494,14 +520,14 @@ class RouterFactory } ``` -Nella [configurazione |dependency-injection:services] scriveremo quindi: +Scriviamo poi nella [configurazione |dependency-injection:services]: ```neon services: - App\Core\RouterFactory::createRouter ``` -Qualsiasi dipendenza, ad esempio dal database ecc., viene passata al metodo factory come suoi parametri tramite [autowiring|dependency-injection:autowiring]: +Le eventuali dipendenze, per esempio da un database, vengono passate al metodo factory come suoi parametri tramite l'[autowiring|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -514,15 +540,15 @@ public static function createRouter(Nette\Database\Connection $db): RouteList SimpleRouter ============ -Un router molto più semplice della collezione di route è [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Lo useremo quando non abbiamo particolari esigenze sulla forma dell'URL, se non è disponibile `mod_rewrite` (o le sue alternative) o se per ora non vogliamo occuparci di URL leggibili. +Un router molto più semplice della collezione di route è [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Lo usiamo quando non abbiamo esigenze particolari sul formato degli URL, se `mod_rewrite` (o le sue alternative) non è disponibile, oppure se non vogliamo ancora occuparci degli URL leggibili. -Genera indirizzi approssimativamente in questa forma: +Genera indirizzi più o meno in questa forma: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Il parametro del costruttore di SimpleRouter è il presenter & azione predefinito, a cui si deve puntare se apriamo la pagina senza parametri, ad es. `http://example.com/`. +Il parametro del costruttore di `SimpleRouter` è il presenter e l'azione predefiniti, cioè l'azione da eseguire se apriamo per esempio `http://example.com/` senza altri parametri. ```php // il presenter predefinito sarà 'Home' e l'azione 'default' @@ -540,9 +566,9 @@ services: SEO e canonizzazione ==================== -Il framework contribuisce al SEO (ottimizzazione della reperibilità su Internet) impedendo la duplicazione di contenuti su URL diversi. Se a una determinata destinazione portano più indirizzi, ad es. `/index` e `/index.html`, il framework determina il primo come primario (canonico) e reindirizza gli altri ad esso tramite il codice HTTP 301. Grazie a ciò, i motori di ricerca non indicizzano le pagine due volte e non diluiscono il loro page rank. +Il framework contribuisce alla SEO (Search Engine Optimization) impedendo l'esistenza di contenuto duplicato su URL diversi. Se più indirizzi portano a una certa destinazione, per esempio `/index` e `/index.html`, il framework designa il primo come principale (canonico) e vi reindirizza gli altri con il codice HTTP 301. Grazie a questo i motori di ricerca non indicizzano le pagine due volte e non ne diluiscono il page rank. -Questo processo si chiama canonizzazione. L'URL canonico è quello generato dal router, cioè la prima route corrispondente nella collezione senza il flag OneWay. Pertanto, nella collezione elenchiamo **le route primarie per prime**. +Questo processo si chiama canonizzazione. L'URL canonico è quello generato dal router, cioè dalla prima route corrispondente della collezione priva del flag OneWay. Nella collezione elenchiamo quindi **prima le route principali**. La canonizzazione viene eseguita dal presenter, maggiori informazioni nel capitolo [canonizzazione |presenters#Canonizzazione]. @@ -550,9 +576,9 @@ La canonizzazione viene eseguita dal presenter, maggiori informazioni nel capito HTTPS ===== -Per poter utilizzare il protocollo HTTPS, è necessario abilitarlo sull'hosting e configurare correttamente il server. +Per usare il protocollo HTTPS è necessario attivarlo sull'hosting e configurare correttamente il server. -Il redirect dell'intero sito a HTTPS deve essere impostato a livello di server, ad esempio tramite il file .htaccess nella directory principale della nostra applicazione, e con il codice HTTP 301. L'impostazione può variare a seconda dell'hosting e assomiglia circa a questo: +Il redirect dell'intero sito verso HTTPS va impostato a livello di server, per esempio con il file `.htaccess` nella directory radice della nostra applicazione, con il codice HTTP 301. Le impostazioni possono variare a seconda dell'hosting e hanno più o meno questo aspetto: ``` @@ -564,40 +590,40 @@ Il redirect dell'intero sito a HTTPS deve essere impostato a livello di server, ``` -Il router genera URL con lo stesso protocollo con cui è stata caricata la pagina, quindi non è necessario impostare nient'altro. +Il router genera gli URL con lo stesso protocollo con cui è stata caricata la pagina, quindi non serve impostare altro. -Se però eccezionalmente abbiamo bisogno che diverse route vengano eseguite sotto protocolli diversi, lo specifichiamo nella maschera della route: +Se però, in via eccezionale, abbiamo bisogno che route diverse girino con protocolli diversi, lo indichiamo nella maschera della route: ```php -// Genererà un indirizzo con HTTP +// genererà un indirizzo HTTP $router->addRoute('http://%host%//', /* ... */); -// Genererà un indirizzo con HTTPs +// genererà un indirizzo HTTPS $router->addRoute('https://%host%//', /* ... */); ``` -Debug del router -================ +Debugging del router +==================== -Il pannello di routing visualizzato nella [Tracy Bar |tracy:] è un aiuto utile che mostra l'elenco delle route e anche i parametri che il router ha ottenuto dall'URL. +Il pannello del routing mostrato nella [Tracy Bar |tracy:] è un aiuto utile: mostra l'elenco delle route e anche i parametri che il router ha ricavato dall'URL. -La barra verde con il simbolo ✓ rappresenta la route che ha elaborato l'URL corrente, con il colore blu e il simbolo ≈ sono contrassegnate le route che avrebbero elaborato anche l'URL se la verde non le avesse precedute. Vediamo inoltre il presenter & azione corrente. +La barra verde con il simbolo ✓ rappresenta la route che ha elaborato l'URL corrente; il colore blu e il simbolo ≈ indicano le route che avrebbero elaborato anch'esse l'URL, se quella verde non le avesse precedute. Vediamo inoltre il presenter e l'azione correnti. [* routing-debugger.webp *] -Allo stesso tempo, se si verifica un redirect inaspettato a causa della [canonizzazione |#SEO e canonizzazione], è utile guardare il pannello nella barra *redirect*, dove scoprirete come il router ha originariamente compreso l'URL e perché ha reindirizzato. +Allo stesso tempo, se avviene un redirect inatteso a causa della [canonizzazione |#SEO e canonizzazione], è utile guardare nel pannello la barra *redirect*, dove potete scoprire come il router aveva interpretato in origine l'URL e perché ha reindirizzato. .[note] -Durante il debug del router, consigliamo di aprire gli Strumenti per sviluppatori nel browser (Ctrl+Shift+I o Cmd+Option+I) e nel pannello Network disattivare la cache, in modo che non vi vengano salvati i redirect. +Durante il debugging del router consigliamo di aprire gli strumenti per sviluppatori del browser (Ctrl+Shift+I o Cmd+Option+I) e di disattivare la cache nel pannello Network, così che i redirect non vi vengano salvati. Prestazioni =========== -Il numero di route influisce sulla velocità del router. Il loro numero non dovrebbe assolutamente superare alcune decine. Se il tuo sito web ha una struttura URL troppo complicata, puoi scrivere un [#Router personalizzato] su misura. +Il numero di route influisce sulla velocità del router. Il loro numero non dovrebbe assolutamente superare qualche decina. Se il vostro sito ha una struttura di URL troppo complicata, potete scrivere un [router personalizzato |#Router personalizzato]. -Se il router non ha dipendenze, ad esempio dal database, e la sua factory non accetta argomenti, possiamo serializzare la sua forma compilata direttamente nel container DI e accelerare così leggermente l'applicazione. +Se il router non ha dipendenze, per esempio da un database, e la sua factory non accetta argomenti, possiamo serializzarne la forma compilata direttamente nel container DI e velocizzare così leggermente l'applicazione. ```neon routing: @@ -608,7 +634,7 @@ routing: Router personalizzato ===================== -Le righe seguenti sono destinate a utenti molto avanzati. Puoi creare un tuo router personalizzato e integrarlo in modo del tutto naturale nella collezione di route. Il router è un'implementazione dell'interfaccia [api:Nette\Routing\Router] con due metodi: +Le righe seguenti sono destinate agli utenti molto avanzati. Potete creare un vostro router e integrarlo naturalmente nella collezione di route. Il router è un'implementazione dell'interfaccia [api:Nette\Routing\Router], con due metodi: ```php use Nette\Http\IRequest as HttpRequest; @@ -628,7 +654,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Il metodo `match` elabora la richiesta corrente [$httpRequest |http:request], dalla quale è possibile ottenere non solo l'URL, ma anche gli header, ecc., in un array contenente il nome del presenter e i suoi parametri. Se non sa elaborare la richiesta, restituisce null. Durante l'elaborazione della richiesta, dobbiamo restituire almeno il presenter e l'azione. Il nome del presenter è completo e contiene anche eventuali moduli: +Il metodo `match` elabora la richiesta corrente [$httpRequest |http:request], dalla quale si possono ottenere non solo l'URL ma anche gli header e altro, trasformandola in un array che contiene il nome del presenter e i suoi parametri. Se non riesce a elaborare la richiesta, restituisce null. Elaborando la richiesta dobbiamo restituire almeno il presenter; l'azione è facoltativa e, se non indicata, vale `default`. Il nome del presenter è completo e comprende gli eventuali moduli: ```php [ @@ -637,9 +663,9 @@ Il metodo `match` elabora la richiesta corrente [$httpRequest |http:request], da ] ``` -Il metodo `constructUrl` al contrario costruisce dall'array di parametri l'URL assoluto risultante. A tal fine può utilizzare le informazioni dal parametro [`$refUrl`|api:Nette\Http\UrlScript], che è l'URL corrente. +Il metodo `constructUrl`, al contrario, costruisce l'URL assoluto risultante a partire dall'array di parametri. Può usare le informazioni del parametro [`$refUrl`|api:Nette\Http\UrlScript], che è l'URL corrente. -Lo aggiungi alla collezione di route usando `add()`: +Lo aggiungete alla collezione di route con `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -649,16 +675,16 @@ $router->addRoute(/* ... */); ``` -Utilizzo indipendente -===================== +Uso autonomo +============ -Per utilizzo indipendente intendiamo l'utilizzo delle capacità del router in un'applicazione che non utilizza Nette Application e i presenter. Vale per esso quasi tutto ciò che abbiamo mostrato in questo capitolo, con queste differenze: +Per uso autonomo intendiamo sfruttare le capacità del router in un'applicazione che non usa Nette Application e i presenter. Vale per essa quasi tutto ciò che abbiamo mostrato in questo capitolo, con queste differenze: -- per le collezioni di route utilizziamo la classe [api:Nette\Routing\RouteList] -- come simple router la classe [api:Nette\Routing\SimpleRouter] -- poiché non esiste la coppia `Presenter:action`, utilizziamo la [#Notazione estesa] +- per le collezioni di route usiamo la classe [api:Nette\Routing\RouteList] +- come router semplice, la classe [api:Nette\Routing\SimpleRouter] +- poiché la coppia `Presenter:azione` non esiste, usiamo la [notazione avanzata |#Notazione avanzata] -Quindi di nuovo creiamo un metodo che ci costruisca il router, ad es.: +Creiamo quindi di nuovo un metodo che ci componga il router, per esempio: ```php namespace App\Core; @@ -682,35 +708,35 @@ class RouterFactory } ``` -Se usi un container DI, cosa che consigliamo, aggiungiamo di nuovo il metodo alla configurazione e poi otteniamo il router insieme alla richiesta HTTP dal container: +Se usate un container DI, cosa che consigliamo, aggiungete di nuovo il metodo alla configurazione e ottenete poi dal container il router insieme alla richiesta HTTP: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Oppure creiamo direttamente gli oggetti: +Oppure create direttamente gli oggetti: ```php $router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Ora resta solo da mettere al lavoro il router: +Ora non resta che lasciare al router il suo lavoro: ```php $params = $router->match($httpRequest); if ($params === null) { - // non è stata trovata una route corrispondente, inviamo errore 404 + // nessuna route corrispondente trovata, invia un errore 404 exit; } -// elaboriamo i parametri ottenuti +// elabora i parametri ottenuti $controller = $params['controller']; // ... ``` -E viceversa usiamo il router per costruire un link: +E, al contrario, usate il router per costruire un link: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; @@ -718,4 +744,6 @@ $url = $router->constructUrl($params, $httpRequest->getUrl()); ``` -{{composer: nette/router}} +{{composer: nette/routing}} +{{repo: nette/routing}} +{{api: https://api.nette.org/routing/}} diff --git a/application/it/templates.texy b/application/it/templates.texy index 2080f7cd54..55b7110349 100644 --- a/application/it/templates.texy +++ b/application/it/templates.texy @@ -2,15 +2,15 @@ Template ******** .[perex] -Nette utilizza il sistema di templating [Latte |latte:]. Questo perché è il sistema di templating più sicuro per PHP, e allo stesso tempo il sistema più intuitivo. Non devi imparare molto di nuovo, ti basta la conoscenza di PHP e alcuni tag. +Nette usa il sistema di template [Latte |latte:]. Latte viene usato perché è il sistema di template più sicuro per PHP e allo stesso tempo il più intuitivo. Non dovete imparare molto di nuovo: bastano la conoscenza di PHP e qualche tag. -È comune che una pagina sia composta da un template di layout + il template dell'azione specifica. Ecco come potrebbe apparire un template di layout, nota i blocchi `{block}` e il tag `{include}`: +È consuetudine che una pagina sia composta dal template del layout più il template dell'azione specifica. Ecco che aspetto potrebbe avere un template di layout; notate i blocchi `{block}` e il tag `{include}`: ```latte - {block title}La mia App{/block} + {block title}My App{/block}
    ...
    @@ -20,7 +20,7 @@ Nette utilizza il sistema di templating [Latte |latte:]. Questo perché è il si ``` -E questo sarà il template dell'azione: +E questo sarebbe il template dell'azione: ```latte {block title}Homepage{/block} @@ -31,15 +31,15 @@ E questo sarà il template dell'azione: {/block} ``` -Definisce il blocco `content`, che verrà inserito al posto di `{include content}` nel layout, e ridefinisce anche il blocco `title`, che sovrascriverà `{block title}` nel layout. Prova a immaginare il risultato. +Definisce il blocco `content`, che viene inserito al posto di `{include content}` nel layout, e ridefinisce inoltre il blocco `title`, che sovrascrive `{block title}` del layout. Provate a immaginare il risultato. Ricerca dei template -------------------- -Non devi specificare nei presenter quale template deve essere renderizzato, il framework deduce il percorso da solo e ti risparmia la scrittura. +Nei presenter non dovete indicare quale template va disegnato: il framework ne deduce automaticamente il percorso, risparmiandovi di scriverlo. -Se utilizzi una struttura di directory in cui ogni presenter ha la propria directory, posiziona semplicemente il template in questa directory con il nome dell'azione (o view), cioè per l'azione `default` usa il template `default.latte`: +Se usate una struttura di directory in cui ogni presenter ha la propria directory, basta collocare il template in questa directory con il nome dell'azione (cioè della vista). Per esempio, per l'azione `default` usate il template `default.latte`: /--pre app/ @@ -49,34 +49,34 @@ app/ └── default.latte \-- -Se utilizzi una struttura in cui i presenter sono insieme in una directory e i template nella cartella `templates`, salvalo o nel file `..latte` o `/.latte`: +Se usate una struttura in cui i presenter stanno insieme in un'unica directory e i template in una cartella `templates`, salvatelo o nel file `..latte` oppure in `/.latte`: /--pre app/ └── Presenters/ ├── HomePresenter.php └── templates/ - ├── Home.default.latte ← 1a variante - └── Home/ - └── default.latte ← 2a variante + ├── Home/ + │ └── default.latte ← 1ª variante + └── Home.default.latte ← 2ª variante \-- -La directory `templates` può trovarsi anche un livello sopra, cioè allo stesso livello della directory con le classi dei presenter. +La directory `templates` si può collocare anche un livello più in alto, cioè allo stesso livello della directory con le classi dei presenter. -Se il template non viene trovato, il presenter risponde con un [errore 404 - page not found |presenters#Errore 404 e simili]. +Se il template non viene trovato, il presenter risponde con un [errore 404 - pagina non trovata |presenters#Errore 404 e simili]. -La view viene cambiata usando `$this->setView('altraView')`. È anche possibile specificare direttamente il file del template usando `$this->template->setFile('/path/to/template.latte')`. +Potete cambiare la vista con `$this->setView('otherView')`. È anche possibile indicare direttamente il file del template con `$this->template->setFile('/path/to/template.latte')`. .[note] -I file in cui vengono cercati i template possono essere modificati sovrascrivendo il metodo [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], che restituisce un array di possibili nomi di file. +I file in cui vengono cercati i template si possono cambiare sovrascrivendo il metodo [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], che restituisce un array dei possibili nomi di file. Ricerca del template di layout ------------------------------ -Nette cerca automaticamente anche il file di layout. +Nette cerca automaticamente anche il file del layout. -Se utilizzi una struttura di directory in cui ogni presenter ha la propria directory, posiziona il layout o nella cartella con il presenter, se è specifico solo per esso, o un livello sopra, se è comune a più presenter: +Se usate una struttura di directory in cui ogni presenter ha la propria directory, collocate il layout o nella cartella del presenter, se è specifico solo di quello, oppure un livello più in alto, se è comune a più presenter: /--pre app/ @@ -88,7 +88,7 @@ app/ └── default.latte \-- -Se utilizzi una struttura in cui i presenter sono insieme in una directory e i template nella cartella `templates`, il layout sarà atteso in queste posizioni: +Se usate una struttura in cui i presenter sono raggruppati in un'unica directory e i template stanno in una cartella `templates`, il layout sarà atteso in queste posizioni: /--pre app/ @@ -96,33 +96,66 @@ app/ ├── HomePresenter.php └── templates/ ├── @layout.latte ← layout comune - ├── Home.@layout.latte ← solo per Home, 1a variante - └── Home/ - └── @layout.latte ← solo per Home, 2a variante + ├── Home/ + │ └── @layout.latte ← solo per Home, 1ª variante + └── Home.@layout.latte ← solo per Home, 2ª variante \-- -Se il presenter si trova in un modulo, la ricerca avverrà anche ai livelli di directory superiori, in base alla nidificazione del modulo. +Se il presenter si trova in un modulo, la ricerca prosegue anche verso i livelli superiori di directory, secondo l'annidamento dei moduli. -Il nome del layout può essere cambiato usando `$this->setLayout('layoutAdmin')` e quindi sarà atteso nel file `@layoutAdmin.latte`. È anche possibile specificare direttamente il file del template di layout usando `$this->setLayout('/path/to/template.latte')`. +Il nome del layout si può cambiare con `$this->setLayout('layoutAdmin')`, e allora sarà atteso nel file `@layoutAdmin.latte`. Potete anche indicare direttamente il file del template di layout con `$this->setLayout('/path/to/template.latte')`. -Usando `$this->setLayout(false)` o il tag `{layout none}` all'interno del template, la ricerca del layout viene disattivata. +Con `$this->setLayout(false)` oppure con il tag `{layout none}` dentro il template si disattiva la ricerca del layout. .[note] -I file in cui vengono cercati i template di layout possono essere modificati sovrascrivendo il metodo [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], che restituisce un array di possibili nomi di file. +I file in cui vengono cercati i template di layout si possono cambiare sovrascrivendo il metodo [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], che restituisce un array dei possibili nomi di file. -Variabili nel template +Variabili del template ---------------------- -Passiamo le variabili al template scrivendole in `$this->template` e poi le abbiamo disponibili nel template come variabili locali: +Le variabili si passano ai template scrivendole in `$this->template`. Diventano poi disponibili nel template come variabili locali: ```php $this->template->article = $this->articles->getById($id); ``` -In questo modo semplice possiamo passare qualsiasi variabile ai template. Tuttavia, nello sviluppo di applicazioni robuste, è più utile limitarsi. Ad esempio, definendo esplicitamente l'elenco delle variabili che il template si aspetta e i loro tipi. Grazie a ciò, PHP potrà controllare i tipi, l'IDE suggerirà correttamente e l'analisi statica rivelerà errori. +Per passare automaticamente al template il valore di una proprietà come variabile, contrassegnatela con l'attributo `#[TemplateVariable]` e con visibilità pubblica: .{data-version:3.2.9} -E come definiamo tale elenco? Semplicemente sotto forma di una classe e delle sue properties. La nominiamo in modo simile al presenter, ma con `Template` alla fine: +```php +use Nette\Application\Attributes\TemplateVariable; + +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + #[TemplateVariable] + public string $siteName = 'My blog'; +} +``` + +Se passate al template una variabile con lo stesso nome, `#[TemplateVariable]` non la sovrascriverà. + + +Variabili predefinite +--------------------- + +I presenter e i componenti passano automaticamente ai template alcune variabili utili: + +- `$basePath` è il percorso URL assoluto della directory radice (per esempio `/eshop`) +- `$baseUrl` è l'URL assoluto della directory radice (per esempio `http://localhost/eshop`) +- `$user` è un oggetto che [rappresenta l'utente |security:authentication] +- `$presenter` è il presenter corrente +- `$control` è il componente o il presenter corrente +- `$flashes` è un array dei [messaggi |presenters#Messaggi flash] inviati dalla funzione `flashMessage()` + +Se usate una classe di template personalizzata, queste variabili vengono passate se create per esse una proprietà. + + +Template con tipi sicuri +------------------------ + +Sviluppando applicazioni solide, è utile definire esplicitamente quali variabili il template si aspetta e di che tipo sono. Questo offre il controllo dei tipi in PHP, suggerimenti intelligenti nell'IDE e permette all'analisi statica di individuare gli errori. + +Come si definisce un elenco del genere? Semplicemente come una classe con proprietà che rappresentano le variabili del template. Chiamatela come il presenter, aggiungendo alla fine `Template`: ```php /** @@ -141,22 +174,24 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -L'oggetto `$this->template` nel presenter sarà ora un'istanza della classe `ArticleTemplate`. Quindi PHP controllerà i tipi dichiarati durante la scrittura. E a partire dalla versione PHP 8.2 avviserà anche sulla scrittura in una variabile inesistente, nelle versioni precedenti si può ottenere lo stesso risultato usando il trait [Nette\SmartObject |utils:smartobject]. +L'oggetto `$this->template` nel presenter sarà ora un'istanza della classe `ArticleTemplate`. PHP controllerà quindi i tipi dichiarati durante la scrittura. + +Nette sceglie automaticamente la classe del template. Cerca prima una classe chiamata `Template`, per esempio `ArticleEditTemplate` per l'azione `edit`, e solo se non esiste ripiega su `Template`. -L'annotazione `@property-read` è destinata all'IDE e all'analisi statica, grazie ad essa funzionerà il suggerimento, vedi [PhpStorm and code completion for $this⁠-⁠>⁠template |https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template]. +L'annotazione `@property-read` è destinata all'IDE e all'analisi statica e abilita il completamento del codice, vedi "PhpStorm e il completamento del codice per $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Puoi goderti il lusso del suggerimento anche nei template, basta installare il plugin per Latte in PhpStorm e specificare all'inizio del template il nome della classe, maggiori informazioni nell'articolo [Latte: jak na typový systém|https://blog.nette.org/it/latte-how-to-use-type-system]: +Potete usare il completamento del codice anche direttamente nei template. Basta installare il plugin Latte per PhpStorm e indicare all'inizio del template il nome della classe dei parametri, maggiori informazioni nel capitolo [Latte: sistema di tipi |latte:type-system]: ```latte {templateType App\Presentation\Article\ArticleTemplate} ... ``` -Così funzionano anche i template nei componenti, basta solo rispettare la convenzione di denominazione e per un componente ad es. `FifteenControl` creare una classe di template `FifteenTemplate`. +Lo stesso vale per i componenti. Basta seguire la convenzione di denominazione e creare una classe dei parametri `FifteenTemplate` per un componente come `FifteenControl`. -Se hai bisogno di creare `$template` come istanza di un'altra classe, utilizza il metodo `createTemplate()`: +Se vi serve usare una classe dei parametri diversa, usate il metodo `createTemplate()`: ```php public function renderDefault(): void @@ -168,58 +203,100 @@ public function renderDefault(): void } ``` +.{data-version:3.3.0} +Se vi serve influire su come il template viene completato prima del rendering, per esempio per aggiungere variabili condivise da tutte le azioni, potete sovrascrivere nel presenter il metodo `completeTemplate()`. Viene chiamato subito prima che il template venga disegnato: -Variabili predefinite ---------------------- - -I presenter e i componenti passano automaticamente diverse variabili utili ai template: - -- `$basePath` è il percorso URL assoluto alla directory principale (es. `/eshop`) -- `$baseUrl` è l'URL assoluto alla directory principale (es. `http://localhost/eshop`) -- `$user` è l'oggetto [che rappresenta l'utente |security:authentication] -- `$presenter` è il presenter corrente -- `$control` è il componente o presenter corrente -- `$flashes` è l'array di [messaggi |presenters#Messaggi flash] inviati dalla funzione `flashMessage()` - -Se utilizzi una classe di template personalizzata, queste variabili vengono passate se crei una property per esse. +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My blog'; +} +``` -Creazione di link ------------------ +Creare i link +------------- -Nel template, i link ad altri presenter & azioni vengono creati in questo modo: +Nel template i link verso altri presenter e azioni si creano così: ```latte -dettaglio prodotto +dettaglio del prodotto ``` -L'attributo `n:href` è molto utile per i tag HTML ``. Se vogliamo stampare il link altrove, ad esempio nel testo, usiamo `{link}`: +L'attributo `n:href` è molto comodo per i tag HTML ``. Se vogliamo stampare il link altrove, per esempio nel testo, usiamo `{link}`: ```latte -L'indirizzo è: {link Home:default} +L'URL è: {link Home:default} ``` Maggiori informazioni si trovano nel capitolo [Creazione di link URL|creating-links]. -Filtri personalizzati, tag, ecc. --------------------------------- +Filtri, tag e altro personalizzati +---------------------------------- + +Il sistema di template Latte si può estendere con filtri, funzioni, tag e altri elementi personalizzati. Ci sono tre approcci disponibili, che vanno dalle soluzioni rapide ad hoc ai modelli architetturali per intere applicazioni. + +**Ad hoc nei metodi del presenter** -Il sistema di templating Latte può essere esteso con filtri, funzioni, tag personalizzati, ecc. Ciò può essere fatto direttamente nel metodo `render` o `beforeRender()`: +L'approccio più rapido è aggiungere filtri o funzioni direttamente nel codice del presenter o del componente. Nei presenter si prestano bene i metodi `beforeRender()` o `render()`: ```php -public function beforeRender(): void +protected function beforeRender(): void { // aggiunta di un filtro - $this->template->addFilter('foo', /* ... */); + $this->template->addFilter('money', fn($val) => '$' . number_format($val, 2)); + + // aggiunta di una funzione + $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); +} +``` - // o configuriamo direttamente l'oggetto Latte\Engine +Nel template: + +```latte +

    Prezzo: {$price|money}

    + +{if isWeekend($now)} ... {/if} +``` + +Per una logica più complessa potete configurare direttamente l'oggetto `Latte\Engine`: + +```php +protected function beforeRender(): void +{ $latte = $this->template->getLatte(); - $latte->addFilterLoader(/* ... */); + $latte->setFeature(Latte\Feature::MigrationWarnings); } ``` -Latte nella versione 3 offre un modo più avanzato, ovvero creare un'[extension |latte:extending-latte#Latte Extension] per ogni progetto web. Esempio parziale di tale classe: +**Con gli attributi** + +Un approccio più elegante è definire filtri e funzioni come metodi direttamente nella [classe dei parametri del template|#Template con tipi sicuri] del presenter o del componente, contrassegnandoli con degli attributi: + +```php +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + #[Latte\Attributes\TemplateFilter] + public function money(float $val): string + { + return '$' . number_format($val, 2); + } + + #[Latte\Attributes\TemplateFunction] + public function isWeekend(DateTimeInterface $date): bool + { + return $date->format('N') >= 6; + } +} +``` + +Latte individua e registra automaticamente i metodi contrassegnati con questi attributi. Il nome del filtro o della funzione nei template coincide con il nome del metodo. Questi metodi devono essere pubblici. + +**Globalmente, con le estensioni** + +Gli approcci precedenti si prestano a filtri e funzioni necessari solo in determinati presenter o componenti, non nell'intera applicazione. Per tutta l'applicazione la soluzione migliore è creare un'[estensione |latte:extending-latte#Estensione di Latte]. Questa classe centralizza tutte le estensioni di Latte del vostro progetto. Un breve esempio: ```php namespace App\Presentation\Accessory; @@ -251,11 +328,16 @@ final class LatteExtension extends Latte\Extension ]; } + private function filterTimeAgoInWords(DateTimeInterface $time): string + { + // ... + } + // ... } ``` -La registriamo tramite la [configurazione |configuration#Template Latte]: +Registrate l'estensione tramite la [configurazione |configuration#Template Latte]: ```neon latte: @@ -263,13 +345,27 @@ latte: - App\Presentation\Accessory\LatteExtension ``` +Le estensioni offrono diversi vantaggi: il supporto della dependency injection, l'accesso allo strato del modello della vostra applicazione e la gestione centralizzata di tutte le estensioni. Supportano inoltre tag personalizzati, provider, compiler pass e altro ancora. + + +Configurare tutti i template +---------------------------- + +Il servizio `TemplateFactory`, che crea tutti i template, offre un array pubblico di callback `$onCreate`. Vengono chiamate ogni volta che viene creato un template qualsiasi, così potete impostare filtri, funzioni o variabili per tutti i template dell'applicazione da un unico punto. Ogni callback riceve il template appena creato. Fatevi [iniettare |dependency-injection:passing-dependencies] il servizio `TemplateFactory` e registrate le callback, per esempio all'avvio dell'applicazione: + +```php +$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void { + $template->addFilter('money', fn($val) => '$' . number_format($val, 2)); +}; +``` + Traduzione ---------- -Se programmi un'applicazione multilingue, probabilmente avrai bisogno di stampare alcuni testi nel template in diverse lingue. Nette Framework definisce a tale scopo un'interfaccia per la traduzione [api:Nette\Localization\Translator], che ha un unico metodo `translate()`. Questo accetta il messaggio `$message`, che di solito è una stringa, e qualsiasi altro parametro. Il compito è restituire la stringa tradotta. In Nette non c'è un'implementazione predefinita, puoi scegliere in base alle tue esigenze tra diverse soluzioni pronte, che trovi su [Componette |https://componette.org/search/localization]. Nella loro documentazione imparerai come configurare il translator. +Se programmate un'applicazione multilingue, probabilmente avrete bisogno di stampare nel template alcuni testi in lingue diverse. Nette Framework definisce a questo scopo l'interfaccia di traduzione [api:Nette\Localization\Translator], che ha un unico metodo `translate()`. Accetta il messaggio `$message`, di norma una stringa, e qualsiasi altro parametro. Il compito è restituire la stringa tradotta. Nette non ha un'implementazione predefinita; potete scegliere tra diverse soluzioni già pronte disponibili su [Componette |https://componette.org/search/localization], secondo le vostre esigenze. La loro documentazione spiega come configurare il traduttore. -Ai template è possibile impostare un traduttore, che ci [facciamo passare |dependency-injection:passing-dependencies], con il metodo `setTranslator()`: +Ai template si può impostare un traduttore, che ci facciamo [passare |dependency-injection:passing-dependencies], con il metodo `setTranslator()`: ```php protected function beforeRender(): void @@ -279,7 +375,7 @@ protected function beforeRender(): void } ``` -Il translator può alternativamente essere impostato tramite la [configurazione |configuration#Template Latte]: +In alternativa il traduttore si può impostare tramite la [configurazione |configuration#Template Latte]: ```neon latte: @@ -287,7 +383,7 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Successivamente, il traduttore può essere utilizzato ad esempio come filtro `|translate`, inclusi parametri aggiuntivi che vengono passati al metodo `translate()` (vedi `foo, bar`): +Il traduttore si può poi usare, per esempio, come filtro `|translate`, compresi i parametri aggiuntivi che vengono passati al metodo `translate()` (vedi `foo, bar`): ```latte
    {='Carrello'|translate} @@ -295,7 +391,7 @@ Successivamente, il traduttore può essere utilizzato ad esempio come filtro `|t {$item|translate, foo, bar} ``` -O come tag con trattino basso: +Oppure come tag con il trattino basso: ```latte {_'Carrello'} @@ -303,14 +399,14 @@ O come tag con trattino basso: {_$item, foo, bar} ``` -Per la traduzione di una sezione del template esiste un tag di coppia `{translate}` (da Latte 2.11, prima si usava il tag `{_}`): +Per tradurre una porzione di template esiste il tag di tipo pari `{translate}` (da Latte 2.11, prima si usava il tag `{_}`): ```latte {translate}Ordine{/translate} {translate foo, bar}Ordine{/translate} ``` -Il translator viene chiamato standardmente durante l'esecuzione al rendering del template. Latte versione 3, tuttavia, può tradurre tutti i testi statici già durante la compilazione del template. Ciò consente di risparmiare prestazioni, poiché ogni stringa viene tradotta solo una volta e la traduzione risultante viene scritta nella forma compilata. Nella directory della cache vengono così create più versioni compilate del template, una per ogni lingua. Per fare ciò, basta solo specificare la lingua come secondo parametro: +Il traduttore viene normalmente chiamato in fase di esecuzione, durante il rendering del template. Latte versione 3 è però in grado di tradurre tutti i testi statici già durante la compilazione del template. Questo fa risparmiare prestazioni, perché ogni stringa viene tradotta una sola volta e la traduzione risultante viene scritta nella forma compilata. Nella directory della cache nascono così più versioni compilate del template, una per ogni lingua. Per ottenerlo basta indicare la lingua come secondo parametro: ```php protected function beforeRender(): void @@ -320,4 +416,4 @@ protected function beforeRender(): void } ``` -Per testo statico si intende ad esempio `{_'ciao'}` o `{translate}ciao{/translate}`. I testi non statici, come ad esempio `{_$foo}`, continueranno ad essere tradotti durante l'esecuzione. +Per testo statico si intende, per esempio, `{_'hello'}` oppure `{translate}hello{/translate}`. I testi non statici, come `{_$foo}`, continueranno a essere tradotti in fase di esecuzione. diff --git a/application/it/upgrading.texy b/application/it/upgrading.texy new file mode 100644 index 0000000000..7cfec2644a --- /dev/null +++ b/application/it/upgrading.texy @@ -0,0 +1,47 @@ +Aggiornamento +************* + + +Aggiornamento alla versione 3.0 +=============================== + +Nette 3.0 aggiunge le dichiarazioni di tipo ai parametri e ai valori di ritorno dei metodi. Se sovrascrivete un metodo del genere in una classe che eredita da Nette (per esempio in un presenter o in un componente), dovete aggiungere le stesse dichiarazioni di tipo, altrimenti PHP emette l'errore "Declaration must be compatible". + +L'interfaccia `Nette\Application\IRouter` è cambiata. Il metodo `match()` ora restituisce, e `constructUrl()` accetta, un array di parametri invece di un oggetto `Nette\Application\Request`. + +Nette controlla ora che ogni segnale venga inviato dalla stessa origine (cioè dallo stesso dominio e sottodominio). Questa same-origin policy è un meccanismo di sicurezza essenziale, che aiuta a ridurre i possibili vettori di attacco. Se volete permettere altre origini, aggiungete al metodo handler l'annotazione `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXy(): void +{ +} +``` + +Lo stesso vale per l'invio dei form. Se volete permetterne l'invio da altre origini, procedete così: + +```php +$form = new Nette\Application\UI\Form; +$form->allowCrossOrigin(); +``` + +Il costruttore di `Nette\ComponentModel\Component` non veniva usato da anni ed è stato rimosso nella versione 3.0. È una rottura di compatibilità: se in un componente o in un presenter che eredita da `Nette\Application\UI\Presenter` chiamate il costruttore genitore, dovete rimuovere la chiamata. + + +Aggiornamento alla versione 2.4 +=============================== + +- `Route` e `SimpleRouter` generano ora lo stesso schema HTTP/HTTPS con cui si è acceduto al sito. Una route che richiede un protocollo specifico si può definire con lo schema, per esempio `Route('http://domain.cz/')`. +- Per i parametri di tipo bool dei metodi render/action (cioè con valore predefinito true o false) e per i parametri persistenti si distinguono ora `false` e `null`. Se il parametro non è presente nell'URL, il suo valore è ora `null` (prima era `false`). +- La classe restituita da `Presenter::getReflection()` non è più discendente di `Nette\Reflection\ClassType` e `getReflection()->getMethod()` non è più discendente di `Nette\Reflection\Method`. +- Il flag `SECURED` e `Route::$defaultFlags` sono deprecati. + + +Aggiornamento alla versione 2.3 +=============================== + +- le route e i nomi dei presenter fanno **distinzione tra maiuscole e minuscole**. Nette vi avvisa se usate le maiuscole sbagliate nel nome di un presenter; per motivi di prestazioni la maschera della Route non viene controllata, quindi verificatela manualmente. +- `Route::addStyle()` e `Route::setStyleProperty()` sono deprecati ed emettono ora `E_USER_DEPRECATED`. +- l'estensione dei template `.phtml` e la vecchia sintassi dei link non sono più supportate. diff --git a/application/ja/@home.texy b/application/ja/@home.texy index 51000ea766..bf788071fd 100644 --- a/application/ja/@home.texy +++ b/application/ja/@home.texy @@ -2,84 +2,84 @@ Nette Application ***************** .[perex] -Nette ApplicationはNetteフレームワークの中核であり、最新のWebアプリケーションを作成するための強力なツールを提供します。開発を大幅に容易にし、コードのセキュリティと保守性を向上させる多くの優れた機能を提供します。 +Nette Application は Nette フレームワークの中核で、現代的なウェブアプリケーションを作るための強力な道具を提供します。開発を大きく楽にし、コードの安全性と保守性を高める、他にはない機能が数多く揃っています。 インストール ------ -[Composer|best-practices:composer]を使用してライブラリをダウンロードし、インストールします: +ライブラリは [Composer|best-practices:composer]でダウンロードしてインストールします。 ```shell composer require nette/application ``` -なぜNette Applicationを選ぶのか? -------------------------- +なぜ Nette Application を選ぶのか +-------------------------- -Netteは常にWeb技術分野のパイオニアでした。 +Nette は常にウェブ技術の先駆けであり続けてきました。 -**双方向ルーター:** Netteは高度なルーティングシステムを備えており、その双方向性でユニークです - URLをアプリケーションのアクションに変換するだけでなく、逆にURLアドレスを生成することもできます。これは次のことを意味します: -- テンプレートを編集することなく、いつでもアプリケーション全体のURL構造を変更できます -- URLは自動的に正規化され、SEOが向上します -- ルーティングはアノテーションに散在するのではなく、一箇所で定義されます +**双方向のルーター:** Nette には双方向性という点で他にない高度なルーティングのしくみがあります。URL をアプリケーションのアクションに変換するだけでなく、逆に URL を生成することもできます。つまり、 +- アプリケーション全体の URL の構造を、テンプレートを直さずにいつでも変えられます +- URL が自動的に正規化され、SEO が良くなります +- ルーティングが 1 か所で定義され、アノテーションに散らばりません -**コンポーネントとシグナル:** DelphiとReact.jsに触発された組み込みコンポーネントシステムは、PHPフレームワークの中で完全にユニークです: -- 再利用可能なUI要素の作成を可能にします -- コンポーネントの階層的な構成をサポートします -- シグナルを使用してAJAXリクエストをエレガントに処理します -- [Componette](https://componette.org)には豊富な既製コンポーネントライブラリがあります +**コンポーネントとシグナル:** Delphi と React.js に着想を得た組み込みのコンポーネントのしくみは、PHP のフレームワークの中でも他に例がありません。 +- 再利用できる UI 要素を作れます +- 階層的なコンポーネントの組み立てに対応しています +- シグナルを使った優雅な AJAX リクエストの処理を提供します +- [Componette](https://componette.org)に既製のコンポーネントの豊富なライブラリがあります -**AJAXとスニペット:** Netteは、Ruby on RailsのHotwireやSymfony UX Turboのような同様のソリューションが登場するずっと前の2009年に、AJAXを扱う革新的な方法を導入しました: -- スニペットを使用すると、JavaScriptを記述することなくページの一部のみを更新できます -- コンポーネントシステムとの自動統合 -- ページの一部を賢く無効化 -- 転送されるデータの最小量 +**AJAX とスニペット:** Nette は 2009 年に、AJAX を扱う画期的な方法を導入しました。Ruby on Rails の Hotwire や Symfony UX Turbo のような似た解よりずっと前のことです。 +- スニペットを使うと、JavaScript を書かずにページの一部だけを更新できます +- コンポーネントのしくみと自動的に統合されます +- ページの部分を賢く無効化します +- データの転送は最小限です -**直感的なテンプレート [Latte|latte:]:** PHP用の最も安全なテンプレートシステムで、高度な機能を備えています: -- コンテキストに応じたエスケープによるXSSからの自動保護 -- カスタムフィルタ、関数、タグによる拡張性 -- AJAX用のテンプレート継承とスニペット -- 型システムを備えたPHP 8.xの優れたサポート +**直感的な [Latte|latte:]テンプレート:** 高度な機能を備えた、PHP で最も安全なテンプレートシステムです。 +- コンテキストに応じたエスケープによる自動的な XSS 対策 +- 独自のフィルタ、関数、タグで拡張できます +- テンプレートの継承と AJAX 用のスニペット +- 型システムを備えた優れた PHP 8.x サポート -**Dependency Injection:** NetteはDependency Injectionを完全に活用しています: -- 依存関係の自動受け渡し(autowiring) -- わかりやすいNEON形式による設定 -- コンポーネントファクトリのサポート +**Dependency Injection:** Nette は依存性注入を全面的に活用します。 +- 依存関係の自動的な受け渡し(オートワイヤリング) +- 分かりやすい NEON 形式による設定 +- コンポーネントのファクトリのサポート 主な利点 ---- -- **セキュリティ**: XSS、CSRFなどの[脆弱性|nette:vulnerability-protection]に対する自動防御 -- **生産性**: スマートな設計により、少ない記述でより多くの機能を実現 -- **デバッグ**: ルーティングパネルを備えた[Tracyデバッガー|tracy:] -- **パフォーマンス**: スマートキャッシュ、コンポーネントの遅延読み込み -- **柔軟性**: アプリケーション完成後でもURLを簡単に変更可能 -- **コンポーネント**: 再利用可能なUI要素のユニークなシステム -- **モダン**: PHP 8.4+と型システムを完全にサポート +- **安全性**: XSS、CSRF などの[脆弱性|nette:vulnerability-protection]に対する自動的な保護 +- **生産性**: 賢い設計のおかげで、書く量は少なく機能は多く +- **デバッグ**: ルーティングのパネルを備えた [Tracy デバッガ|tracy:] +- **性能**: 賢いキャッシュ、コンポーネントの遅延読み込み +- **柔軟さ**: アプリケーションが完成したあとでも URL を簡単に変えられます +- **コンポーネント**: 再利用できる UI 要素の他にないしくみ +- **現代的**: PHP 8.3+ と型システムの完全なサポート はじめに ---- -1. [アプリケーションはどのように動作しますか? |how-it-works] - 基本的なアーキテクチャの理解 -2. [Presenter |presenters] - Presenterとアクションの操作 -3. [テンプレート |templates] - Latteでのテンプレート作成 -4. [ルーティング |routing] - URLアドレスの設定 -5. [インタラクティブコンポーネント |components] - コンポーネントシステムの活用 +1. [アプリケーションはどう動くのか |how-it-works] - 基本のアーキテクチャの理解 +2. [プレゼンター |presenters] - プレゼンターとアクションの扱い +3. [テンプレート |templates] - Latte でのテンプレートの作成 +4. [ルーティング |routing] - URL アドレスの設定 +5. [インタラクティブなコンポーネント |components] - コンポーネントのしくみの利用 -PHPとの互換性 +PHP の互換性 -------- -| バージョン | PHPとの互換性 -|-----------|------------------- -| Nette Application 4.0 | PHP 8.1 – 8.4 -| Nette Application 3.2 | PHP 8.1 – 8.4 -| Nette Application 3.1 | PHP 7.2 – 8.3 -| Nette Application 3.0 | PHP 7.1 – 8.0 -| Nette Application 2.4 | PHP 5.6 – 8.0 +| バージョン | 対応する PHP +|-----------------------|------------------- +| Nette Application 3.3 | PHP 8.3 - 8.5 +| Nette Application 3.2 | PHP 8.1 - 8.5 +| Nette Application 3.1 | PHP 7.2 - 8.3 +| Nette Application 3.0 | PHP 7.1 - 8.0 +| Nette Application 2.4 | PHP 5.6 - 8.0 最新のパッチバージョンに適用されます。 diff --git a/application/ja/@left-menu.texy b/application/ja/@left-menu.texy index d08783d23f..54cd9501d3 100644 --- a/application/ja/@left-menu.texy +++ b/application/ja/@left-menu.texy @@ -1,22 +1,24 @@ Nette Application ***************** -- [アプリケーションはどのように動作しますか? |how-it-works] -- [Bootstrapping] -- [Presenter |presenters] -- [テンプレート |templates] -- [ディレクトリ構造 |directory-structure] -- [ルーティング |routing] -- [URLリンクの作成 |creating-links] -- [インタラクティブコンポーネント |components] -- [AJAX & スニペット |ajax] +- [概要 |@home] +- [アプリケーションはどう動くのか |how-it-works] +- [ブートストラップ|bootstrapping] +- [プレゼンター|presenters] +- [テンプレート|templates] +- [ディレクトリ構成 |directory-structure] +- [ルーティング|routing] +- [URL リンクの作成 |creating-links] +- [インタラクティブなコンポーネント |components] +- [AJAX とスニペット |ajax] - [Multiplier |multiplier] -- [設定 |configuration] +- [設定|configuration] +- [アップグレード|upgrading] -参考文献 +関連情報 **** -- [なぜNetteを使うのか? |www:10-reasons-why-nette] +- [なぜ Nette を使うのか|www:10-reasons-why-nette] - [インストール |nette:installation] -- [最初のアプリケーションを作成しましょう! |quickstart:] -- [ガイドとベストプラクティス |best-practices:] -- [問題解決 |nette:troubleshooting] +- [最初のアプリケーションを作ろう |quickstart:] +- [ベストプラクティス |best-practices:] +- [トラブルシューティング |nette:troubleshooting] diff --git a/application/ja/@meta.texy b/application/ja/@meta.texy index d3c41dc3d7..43b85f3cac 100644 --- a/application/ja/@meta.texy +++ b/application/ja/@meta.texy @@ -1 +1 @@ -{{sitename: Nette ドキュメンテーション}} +{{sitename: Nette ドキュメント}} diff --git a/application/ja/ajax.texy b/application/ja/ajax.texy index 9b07928011..473d163c99 100644 --- a/application/ja/ajax.texy +++ b/application/ja/ajax.texy @@ -3,20 +3,20 @@ AJAX とスニペット
    -最新のWebアプリケーションの時代では、機能がサーバーとブラウザの間で分割されることが多いため、AJAXは不可欠な接続要素です。Nette Frameworkはこの分野でどのような可能性を提供しているでしょうか? +機能がサーバーとブラウザに分かれることの多い現代のウェブアプリケーションにおいて、AJAX は欠かせないつなぎ役です。この領域で Nette Framework は何を提供するのでしょうか。 - テンプレートの一部、いわゆるスニペットの送信 -- PHPとJavaScript間の変数渡し -- AJAXリクエストのデバッグツール +- PHP と JavaScript のあいだの変数の受け渡し +- AJAX リクエストをデバッグするための道具
    -AJAXリクエスト -========= +AJAX リクエスト +========== -AJAXリクエストは、基本的に通常のHTTPリクエストと変わりません。特定のパラメータでPresenterが呼び出されます。そして、Presenterがリクエストにどのように応答するかは、Presenter次第です。JSON形式のデータを返す、HTMLコードの一部を送信する、XMLドキュメントを送信するなど、さまざまな方法があります。 +AJAX のリクエストは、本質的には通常の HTTP リクエストと変わりません。特定のパラメータでプレゼンターが呼ばれます。リクエストにどう応えるかはプレゼンター次第で、JSON 形式のデータを返すことも、HTML コードの一部や XML のドキュメントを送ることもできます。 -ブラウザ側では、`fetch()` 関数を使用してAJAXリクエストを初期化します。 +ブラウザ側では、`fetch()` 関数で AJAX のリクエストを始めます。 ```js fetch(url, { @@ -24,22 +24,22 @@ fetch(url, { }) .then(response => response.json()) .then(payload => { - // レスポンスの処理 + // レスポンスを処理します }); ``` -サーバー側では、[HTTPリクエストをカプセル化するサービス |http:request] の `$httpRequest->isAjax()` メソッドでAJAXリクエストを認識します。検出には `X-Requested-With` HTTPヘッダーを使用するため、これを送信することが重要です。Presenter内では `$this->isAjax()` メソッドを使用できます。 +サーバー側では、[HTTP リクエストを包む |http:request]サービスの `$httpRequest->isAjax()` メソッドで AJAX のリクエストを見分けます。検出には `X-Requested-With` の HTTP ヘッダーを使うので、それを送ることが決定的に重要です。プレゼンターの中では `$this->isAjax()` メソッドが使えます。 -JSON形式でデータを送信したい場合は、[`sendJson()` |presenters#応答の送信] メソッドを使用します。このメソッドはPresenterの動作も終了させます。 +JSON 形式でデータを送りたい場合は [`sendJson()` |presenters#レスポンスの送信]メソッドを使います。このメソッドはプレゼンターの動作も終わらせます。 ```php public function actionExport(): void { - $this->sendJson($this->model->getData); + $this->sendJson($this->model->getData()); } ``` -AJAX用に特別なテンプレートで応答する予定がある場合は、次のように行うことができます。 +AJAX 用に用意した特別なテンプレートで応えるつもりなら、次のようにできます。 ```php public function handleClick($param): void @@ -55,35 +55,35 @@ public function handleClick($param): void スニペット ===== -Netteがサーバーとクライアントを接続するために提供する最も強力な手段は、スニペットです。これらのおかげで、最小限の労力と数行のコードで、通常のアプリケーションをAJAXアプリケーションに変えることができます。これがどのように機能するかは、Fifteenの例で示されています。そのコードは[GitHub |https://github.com/nette-examples/fifteen]にあります。 +サーバーとクライアントをつなぐために Nette が提供する最も強力な道具がスニペットです。これを使えば、ごくわずかな手間と数行のコードで、ふつうのアプリケーションを AJAX のアプリケーションに変えられます。Fifteen の例が全体の動きを示していて、そのコードは [GitHub |https://github.com/nette-examples/fifteen]にあります。 -スニペット、つまり切り抜きは、ページ全体を再読み込みする代わりに、ページの一部だけを更新することを可能にします。これはより速く、より効率的であるだけでなく、より快適なユーザーエクスペリエンスも提供します。スニペットは、Ruby on RailsのHotwireやSymfony UX Turboを思い出させるかもしれません。興味深いことに、Netteはスニペットを14年も前に導入しました。 +スニペットを使うと、ページ全体を読み込み直す代わりに、一部だけを更新できます。速く効率がよいだけでなく、より快適な利用体験も提供します。スニペットは Ruby on Rails の Hotwire や Symfony UX Turbo を思い起こさせるかもしれません。面白いことに、Nette はスニペットをその 14 年前に導入していました。 -スニペットはどのように機能しますか?ページの最初の読み込み(非AJAXリクエスト)では、すべてのスニペットを含むページ全体が読み込まれます。ユーザーがページと対話する(例えば、ボタンをクリックする、フォームを送信するなど)と、ページ全体を読み込む代わりにAJAXリクエストが発行されます。Presenterのコードはアクションを実行し、どのスニペットを更新する必要があるかを決定します。Netteはこれらのスニペットをレンダリングし、JSON形式の配列として送信します。ブラウザの処理コードは、受信したスニペットをページに挿入します。したがって、変更されたスニペットのコードのみが転送され、帯域幅を節約し、ページ全体のコンテンツを転送するよりも読み込みを高速化します。 +スニペットはどう動くのでしょうか。ページが最初に読み込まれるとき(AJAX でないリクエスト)は、すべてのスニペットを含むページ全体が読み込まれます。ユーザーがページを操作すると(ボタンをクリックする、フォームを送信するなど)、ページ全体を読み込み直す代わりに AJAX のリクエストが始まります。プレゼンターのコードが処理を行い、どのスニペットを更新すべきかを決めます。Nette はそのスニペットを描き、スニペットの配列を含む JSON のペイロードとして送ります。ブラウザ側の処理コードが、受け取ったスニペットをページに戻します。こうして変わったスニペットのコードだけが転送されるので、ページの内容全体を転送する場合と比べて帯域を節約でき、読み込みも速くなります。`redrawControl()` でスニペットが無効化されなければ、Nette は AJAX のリクエストでもページ全体を返します。スニペットが送られるのは、何かが無効化されたときだけです。 Naja ---- -ブラウザ側でスニペットを処理するために、[Najaライブラリ |https://naja.js.org]が使用されます。これをnode.jsパッケージとして[インストール |https://naja.js.org/#/guide/01-install-setup-naja]します(Webpack、Rollup、Vite、Parcelなどのアプリケーションで使用するため)。 +ブラウザ側でスニペットを扱うには [Naja ライブラリ |https://naja.js.org]を使います。Node.js のパッケージとして[インストール |https://naja.js.org/#/guide/01-install-setup-naja]してください(Webpack、Rollup、Vite、Parcel などのバンドラーと使う場合)。 ```shell npm install naja ``` -…または、ページテンプレートに直接挿入します。 +…あるいはページのテンプレートに直接書き込みます。 ```latte - + ``` -まず、ライブラリを[初期化 |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]する必要があります。 +まずライブラリを[初期化する |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]必要があります。 ```js naja.initialize(); ``` -通常のリンク(シグナル)やフォーム送信からAJAXリクエストを作成するには、関連するリンク、フォーム、またはボタンに `ajax` クラスを付けるだけです。 +ふつうのリンク(シグナル)やフォームの送信を AJAX のリクエストに変えるには、対象のリンク、フォーム、ボタンに `ajax` クラスを付けるだけです。 ```latte Go @@ -103,29 +103,31 @@ naja.initialize(); スニペットの再描画 --------- -[Control |components]クラスの各オブジェクト(Presenter自体を含む)は、再描画が必要な変更が発生したかどうかを記録します。これには `redrawControl()` メソッドが使用されます。 +[Control |components]クラスのオブジェクト(プレゼンター自身も含みます)はすべて、再描画が必要な変更が起きたかどうかを覚えています。そのために `redrawControl()` メソッドを使います。 ```php public function handleLogin(string $user): void { - // ログイン後、関連部分を再描画する必要がある + // ログイン後に該当する部分を再描画する必要があります $this->redrawControl(); // ... } ``` -Netteは、再描画する内容をさらに細かく制御できます。このメソッドは、引数としてスニペット名を受け取ることができます。したがって、テンプレートの一部のレベルで無効化(つまり、再描画を強制)できます。コンポーネント全体が無効化されると、そのすべてのスニペットも再描画されます。 +Nette は、何を再描画すべきかをさらに細かく制御できます。このメソッドはスニペットの名前を引数に取れます。つまりテンプレートの部分の水準で無効化(すなわち再描画の強制)ができます。コンポーネント全体が無効化されると、その中のすべてのスニペットも再描画されます。 ```php -// 'header' スニペットを無効化 +// 'header' スニペットを無効化します $this->redrawControl('header'); ``` +第 2 パラメータ `$redraw` を使えば、保留中の無効化を取り消すこともできます。`$this->redrawControl('header', redraw: false)` を呼ぶと、そのスニペットは再描画不要と印を付けられます。完全なシグネチャは `redrawControl(?string $snippet = null, bool $redraw = true)` です。 + -Latteのスニペット ------------ +Latte でのスニペット +------------- -Latteでスニペットを使用するのは非常に簡単です。テンプレートの一部をスニペットとして定義するには、単に `{snippet}` と `{/snippet}` タグで囲みます。 +Latte でスニペットを使うのはきわめて簡単です。テンプレートの一部をスニペットとして定義するには、`{snippet}` と `{/snippet}` のタグで囲むだけです。 ```latte {snippet header} @@ -133,9 +135,9 @@ Latteでスニペットを使用するのは非常に簡単です。テンプレ {/snippet} ``` -スニペットは、特別な生成された `id` を持つ `
    ` 要素をHTMLページに作成します。スニペットが再描画されると、この要素のコンテンツが更新されます。したがって、ページの初期レンダリング時に、たとえ最初は空であっても、すべてのスニペットもレンダリングする必要があります。 +スニペットは、生成された特別な `id` を持つ `
    ` 要素を HTML のページに作ります。スニペットが再描画されると、この要素の内容が更新されます。ですから、ページが最初に描かれるときには、たとえ最初は空でも、すべてのスニペットが描かれている必要があります。 -`
    ` 以外の要素でスニペットを作成することもできます。`n:attribute` を使用します。 +n:属性を使えば、`
    ` 以外の要素でスニペットを作ることもできます。 ```latte
    @@ -147,7 +149,7 @@ Latteでスニペットを使用するのは非常に簡単です。テンプレ スニペット領域 ------- -スニペット名は式にすることもできます。 +スニペットの名前は式にもできます。 ```latte {foreach $items as $id => $item} @@ -155,7 +157,9 @@ Latteでスニペットを使用するのは非常に簡単です。テンプレ {/foreach} ``` -これにより、`item-0`、`item-1`などの複数のスニペットが作成されます。動的スニペット(例えば `item-1`)を直接無効化した場合、何も再描画されません。理由は、スニペットは本当に切り抜きとして機能し、それ自体だけが直接レンダリングされるためです。しかし、テンプレートには実際には `item-1` という名前のスニペットはありません。それは、スニペットの周りのコード、つまりforeachループを実行することによってのみ作成されます。したがって、実行されるべきテンプレートの部分を `{snippetArea}` タグでマークします。 +これだけでは動かない途中の段階です。静的な `{snippet}` や `{snippetArea}` の外で描かれた動的スニペットは、*Dynamic snippets are allowed only inside static snippet/snippetArea.* というメッセージとともに `E_USER_WARNING` を出します。これは以下で直します。 + +これは `item-0`、`item-1` といった複数のスニペットを作ります。動的スニペット(たとえば `item-1`)を直接無効化しても、何も再描画されません。理由は、スニペットが本当に抜粋として働き、それ自身だけが直接描かれるからです。しかしテンプレートの中には、技術的には `item-1` という名前のスニペットは存在しません。それはスニペットを囲むコード、つまり foreach ループが実行されてはじめて生まれます。ですから、実行が必要なテンプレートの部分に `{snippetArea}` タグで印を付けます。 ```latte
      @@ -165,16 +169,16 @@ Latteでスニペットを使用するのは非常に簡単です。テンプレ
    ``` -そして、スニペット自体と親領域全体の両方を再描画させます。 +そして個々のスニペットと親の領域の両方の再描画を要求します。 ```php $this->redrawControl('itemsContainer'); $this->redrawControl('item-1'); ``` -同時に、`$items` 配列には再描画されるべき項目のみが含まれるようにすることが望ましいです。 +あわせて、`$items` 配列には再描画すべき項目だけが入るようにするとよいでしょう。 -`{include}` タグを使用して、スニペットを含む別のテンプレートをテンプレートに挿入する場合、テンプレートの挿入を再度 `snippetArea` に含め、それをスニペットと一緒に無効化する必要があります。 +`{include}` タグを使って、スニペットを含む別のテンプレートを主テンプレートに取り込む場合は、そのテンプレートの取り込みをもう一度 `snippetArea` で包み、スニペットとともに無効化する必要があります。 ```latte {snippetArea include} @@ -195,25 +199,25 @@ $this->redrawControl('item'); ``` -コンポーネントのスニペット -------------- +コンポーネントの中のスニペット +--------------- -[コンポーネント|components] 内にスニペットを作成することもでき、Netteはそれらを自動的に再描画します。ただし、制限があります。スニペットを再描画するために、パラメータなしで `render()` メソッドを呼び出します。したがって、テンプレートでパラメータを渡すことは機能しません。 +[コンポーネント|components]の中にもスニペットを作れ、Nette が自動的に再描画します。ただし制限があります。スニペットを再描画するために、Nette は `render()` メソッドをパラメータなしで呼びます。ですからテンプレートでパラメータを渡しても働きません。 ```latte OK {control productGrid} -動作しません: +動きません: {control productGrid $arg, $arg} {control productGrid:paginator} ``` -ユーザーデータの送信 ----------- +独自データの送信 +-------- -スニペットと一緒に、任意の追加データをクライアントに送信できます。それらを `payload` オブジェクトに書き込むだけです。 +スニペットとあわせて、任意の追加データをクライアントに送れます。`payload` オブジェクトに書き込むだけです。 ```php public function actionDelete(int $id): void @@ -226,10 +230,16 @@ public function actionDelete(int $id): void ``` +リダイレクト +------ + +AJAX のリクエスト中、`redirect()` と `redirectUrl()` メソッドは HTTP のリダイレクトを送りません。代わりに、AJAX のレスポンスで送られるデータオブジェクトであるペイロードの `payload.redirect` プロパティに宛先の URL を書き込んで送ります。実際のリダイレクトはクライアント側のライブラリ(Naja)が行います。 + + パラメータの受け渡し ========== -AJAXリクエストを使用してコンポーネントにパラメータを送信する場合、それがシグナルパラメータであろうと永続パラメータであろうと、リクエストでコンポーネント名を含むグローバル名を指定する必要があります。パラメータの完全な名前は `getParameterId()` メソッドによって返されます。 +AJAX のリクエストでコンポーネントにパラメータを送るとき、それがシグナルのパラメータであれ永続パラメータであれ、コンポーネント名を含むグローバルな名前をリクエストで指定しなければなりません。`getParameterId()` メソッドが完全なパラメータ名を返します。 ```js let url = new URL({link //foo!}); @@ -240,10 +250,16 @@ fetch(url, { }) ``` -そして、コンポーネント内の対応するパラメータを持つハンドルメソッド: +そしてコンポーネント側の、対応するパラメータを持つ handle メソッドです。 ```php public function handleFoo(int $bar): void { } ``` + + +関連情報 +==== + +- [動的スニペット |best-practices:dynamic-snippets] diff --git a/application/ja/bootstrapping.texy b/application/ja/bootstrapping.texy index 602daba9c8..53830bd4ea 100644 --- a/application/ja/bootstrapping.texy +++ b/application/ja/bootstrapping.texy @@ -3,19 +3,22 @@
    -ブートストラップは、アプリケーション環境の初期化、依存性注入(DI)コンテナの作成、およびアプリケーションの開始プロセスです。以下について説明します: +ブートストラップとは、アプリケーションの環境を初期化し、依存性注入(DI)コンテナを作り、アプリケーションを起動する過程のことです。ここでは次のことを扱います。 -- Bootstrapクラスが環境を初期化する方法 -- NEONファイルを使用してアプリケーションを設定する方法 -- 本番モードと開発モードを区別する方法 -- DIコンテナを作成および設定する方法 +- Bootstrap クラスが環境をどう初期化するか +- NEON ファイルでアプリケーションをどう設定するか +- 本番モードと開発モードをどう見分けるか +- DI コンテナをどう作り、どう設定するか
    -Webアプリケーションであれ、コマンドラインから実行されるスクリプトであれ、アプリケーションはその実行を開始する際に何らかの形で環境を初期化します。昔は、例えば `include.inc.php` のようなファイルがこれを担当し、最初のファイルがインクルードしていました。 最新のNetteアプリケーションでは、これは `Bootstrap` クラスに置き換えられ、アプリケーションの一部として `app/Bootstrap.php` ファイルにあります。例えば、次のようになります。 +ウェブのアプリケーションであれ、コマンドラインから実行するスクリプトであれ、その実行は何らかの環境の初期化から始まります。昔は `include.inc.php` のような名前のファイルがそれを担い、最初のファイルから読み込まれていました。現代の Nette のアプリケーションでは、それが `Bootstrap` クラスに置き換わり、アプリケーションの一部として `app/Bootstrap.php` ファイルにあります。たとえば次のような形になります。 ```php +namespace App; + +use Nette; use Nette\Bootstrap\Configurator; class Bootstrap @@ -26,9 +29,9 @@ class Bootstrap public function __construct() { $this->rootDir = dirname(__DIR__); - // Configuratorはアプリケーション環境とサービスの設定を担当します。 + // Configurator はアプリケーションの環境とサービスの設定を担当します。 $this->configurator = new Configurator; - // Netteによって生成される一時ファイル(コンパイルされたテンプレートなど)のディレクトリを設定します。 + // Nette が生成する一時ファイル(コンパイル済みテンプレートなど)のディレクトリを設定します $this->configurator->setTempDirectory($this->rootDir . '/temp'); } @@ -41,14 +44,14 @@ class Bootstrap private function initializeEnvironment(): void { - // Netteは賢く、開発モードは自動的に有効になります。 - // または、次の行のコメントを解除して特定のIPアドレスに対して有効にすることもできます。 + // Nette は賢いので開発モードは自動的に有効になります。 + // あるいは次の行のコメントを外して、特定の IP アドレスに対して有効にできます: // $this->configurator->setDebugMode('secret@23.75.345.200'); - // Tracyを有効にします:デバッグのための究極の「スイスアーミーナイフ」。 + // Tracy を有効にします。究極の「万能ナイフ」的デバッグツールです。 $this->configurator->enableTracy($this->rootDir . '/log'); - // RobotLoader: 選択したディレクトリ内のすべてのクラスを自動的にロードします。 + // RobotLoader: 選んだディレクトリのすべてのクラスを自動的に読み込みます $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); @@ -56,7 +59,7 @@ class Bootstrap private function setupContainer(): void { - // 設定ファイルをロードします。 + // 設定ファイルを読み込みます $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } @@ -66,69 +69,78 @@ class Bootstrap index.php ========= -Webアプリケーションの場合、最初のファイルは `www/` [公開ディレクトリ |directory-structure#公開ディレクトリ www] にある `index.php` です。これは `Bootstrap` クラスに環境を初期化させ、DIコンテナを作成させます。その後、そこから `Application` サービスを取得し、Webアプリケーションを起動します。 +ウェブアプリケーションの場合、最初のファイルは[公開ディレクトリ |directory-structure#公開ディレクトリ www/] `www/` にある `index.php` です。これは Bootstrap クラスに、環境を初期化して DI コンテナを作るよう指示します。そしてコンテナから `Application` サービスを取り出し、それがウェブアプリケーションを実行します。 ```php $bootstrap = new App\Bootstrap; -// 環境の初期化 + DIコンテナの作成 +// 環境を初期化し、DI コンテナを作ります $container = $bootstrap->bootWebApplication(); -// DIコンテナは Nette\Application\Application オブジェクトを作成します +// DI コンテナが Nette\Application\Application オブジェクトを作ります $application = $container->getByType(Nette\Application\Application::class); -// Netteアプリケーションを起動し、受信リクエストを処理します +// Nette のアプリケーションを起動し、届いたリクエストを処理します $application->run(); ``` -ご覧のとおり、環境の設定と依存性注入(DI)コンテナの作成は、[api:Nette\Bootstrap\Configurator] クラスによって支援されます。これについて詳しく見ていきましょう。 +.[note] +`$application` オブジェクトは、リクエストを処理しながら[イベント |nette:glossary#イベント]を発します。`onStartup`、`onRequest`、`onPresenter`、`onResponse`、`onShutdown`、そして(処理されなかった例外に対する)`onError` です。ハンドラを結びつけられるので、ログ記録やアプリケーション全体の監視に便利です。 + +ご覧のとおり、[api:Nette\Bootstrap\Configurator]クラスが環境の設定と依存性注入(DI)コンテナの生成を助けます。ここからそれを詳しく紹介します。 -開発環境 vs 本番環境 -============ +開発モードと本番モード +=========== -Netteは、開発サーバーで実行されているか、本番サーバーで実行されているかによって動作が異なります。 +Nette は、開発サーバーで動いているか本番サーバーで動いているかによって振る舞いを変えます。 -🛠️ 開発環境 (Development): - - 役立つ情報(SQLクエリ、実行時間、使用メモリ)を含むTracyデバッグバーを表示します。 - - エラーが発生した場合、関数呼び出しと変数内容を含む詳細なエラーページを表示します。 - - Latteテンプレートの変更、設定ファイルの編集などがあった場合にキャッシュを自動的に更新します。 +🛠️ 開発モード: + - 役立つ情報(SQL クエリ、実行時間、使用メモリ)を載せた Tracy のデバッグバーを表示します + - エラーのときは、関数の呼び出しと変数の内容を含む詳しいエラーページを表示します + - Latte のテンプレートや設定ファイルなどが変わると、キャッシュを自動的に更新します -🚀 本番環境 (Production): - - デバッグ情報は表示せず、すべてのエラーをログに記録します。 - - エラーが発生した場合、ErrorPresenterまたは一般的な「Server Error」ページを表示します。 - - キャッシュは自動的に更新されません! - - 速度とセキュリティのために最適化されています。 +🚀 本番モード: + - デバッグ情報は一切表示せず、すべてのエラーをログに書きます + - エラーのときは ErrorPresenter か、一般的な「Server Error」のページを表示します + - キャッシュは決して自動更新されません + - 速度と安全性のために最適化されています -モードの選択は自動検出によって行われるため、通常は何も設定したり手動で切り替えたりする必要はありません。 +モードの選択は自動検出で行われるので、ふつうは何も設定したりモードを手で切り替えたりする必要はありません。 -- 開発環境: localhost(IPアドレス `127.0.0.1` または `::1`)で、プロキシが存在しない場合(つまり、そのHTTPヘッダーがない場合)。 -- 本番環境: それ以外のすべての場合。 +- 開発モード: localhost(IP アドレス `127.0.0.1` または `::1`)で、プロキシがない場合(つまりその HTTP ヘッダーが検出されない場合) +- 本番モード: それ以外のすべての場所 -他の場合、例えば特定のIPアドレスからアクセスするプログラマーに対して開発環境を有効にしたい場合は、`setDebugMode()` を使用します。 +たとえば特定の IP アドレスからアクセスするプログラマーのために、ほかの場合にも開発モードを有効にしたいなら、`setDebugMode()` を使います。 ```php -$this->configurator->setDebugMode('23.75.345.200'); // IPアドレスの配列も指定できます +$this->configurator->setDebugMode('23.75.345.200'); // IP アドレスの配列も渡せます ``` -IPアドレスとCookieを組み合わせることを強くお勧めします。`nette-debug` Cookieに秘密のトークン、例えば `secret1234` を保存し、この方法で特定のIPアドレスからアクセスし、かつCookieに言及されたトークンを持つプログラマーに対して開発環境を有効にします。 +IP アドレスと cookie の組み合わせを強くおすすめします。`nette-debug` の cookie に秘密のトークン、たとえば `secret1234` を保存し、特定の IP アドレスからアクセスし、かつその cookie にそのトークンを持つプログラマーに対して開発モードを有効にします。 ```php $this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` -localhostに対しても、開発環境を完全に無効にすることもできます。 +localhost も含めて、開発モードを完全に無効にすることもできます。 ```php $this->configurator->setDebugMode(false); ``` -注意:値 `true` は開発環境を強制的に有効にします。これは本番サーバーでは絶対に行ってはいけません。 +値 `true` は開発モードを強制することに注意してください。本番サーバーでは**決して**あってはなりません。 + +自動検出は内部で静的メソッド `Configurator::detectDebugMode()` が行います。これは自分で呼ぶこともでき、たとえば configurator の外で開発モードを検出するのに使えます。IP アドレスやコンピュータ名の許可リストを任意で受け取り、現在のリクエストを開発モードで動かすべきかを返します。 + +```php +$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200'); +``` デバッグツール Tracy ============= -簡単なデバッグのために、優れたツール[Tracy |tracy:]を有効にします。開発環境ではエラーを視覚化し、本番環境では指定されたディレクトリにエラーをログ記録します。 +デバッグを楽にするために、素晴らしい道具 [Tracy |tracy:]を有効にします。開発モードではエラーを可視化し、本番モードでは指定したディレクトリにエラーを記録します。 ```php $this->configurator->enableTracy($this->rootDir . '/log'); @@ -138,19 +150,19 @@ $this->configurator->enableTracy($this->rootDir . '/log'); 一時ファイル ====== -NetteはDIコンテナ、RobotLoader、テンプレートなどにキャッシュを使用します。したがって、キャッシュが保存されるディレクトリへのパスを設定する必要があります。 +Nette は DI コンテナ、RobotLoader、テンプレートなどにキャッシュを使います。ですからキャッシュを保存するディレクトリのパスを設定する必要があります。 ```php $this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` -LinuxまたはmacOSでは、`log/` および `temp/` ディレクトリに[書き込み権限を設定 |nette:troubleshooting#ディレクトリ権限の設定]してください。 +Linux や macOS では、`log/` と `temp/` ディレクトリに[書き込みの権限 |nette:troubleshooting#ディレクトリの権限の設定]を設定してください。 RobotLoader =========== -通常、[RobotLoader |robot-loader:]を使用してクラスを自動的にロードしたいので、それを起動し、`Bootstrap.php` が配置されているディレクトリ(つまり `__DIR__`)とそのすべてのサブディレクトリからクラスをロードさせます。 +ふつうはクラスを [RobotLoader |robot-loader:]で自動的に読み込みたいので、それを起動し、`Bootstrap.php` があるディレクトリ(つまり `__DIR__`)とそのすべてのサブディレクトリからクラスを読み込ませます。 ```php $this->configurator->createRobotLoader() @@ -158,36 +170,38 @@ $this->configurator->createRobotLoader() ->register(); ``` -代替アプローチは、PSR-4に準拠しながら[Composer |best-practices:composer]経由でのみクラスをロードさせることです。 +別の方法として、PSR-4 に従って [Composer |best-practices:composer]だけでクラスを読み込むこともできます。 タイムゾーン ====== -Configuratorを使用して、デフォルトのタイムゾーンを設定できます。 +既定のタイムゾーンは configurator で設定できます。 ```php $this->configurator->setTimeZone('Europe/Prague'); ``` -DIコンテナの設定 -========= +DI コンテナの設定 +========== -ブートプロセスの一部は、アプリケーション全体の心臓部であるDIコンテナ、つまりオブジェクトのファクトリを作成することです。これは実際にはNetteによって生成され、キャッシュディレクトリに保存されるPHPクラスです。ファクトリはアプリケーションの主要なオブジェクトを生成し、設定ファイルを使用してそれらをどのように作成および設定するかを指示することで、アプリケーション全体の動作に影響を与えます。 +起動の過程の一部が DI コンテナ、つまりオブジェクトのファクトリの生成で、これはアプリケーション全体の心臓です。実際には Nette が生成してキャッシュディレクトリに保存する PHP のクラスです。このファクトリがアプリケーションの主要なオブジェクトを作り、設定ファイルでその作り方と設定の仕方を指示することで、アプリケーション全体の振る舞いに影響を与えます。 -設定ファイルは通常、[NEON |neon:format]形式で記述されます。別の章で、[設定できるすべてのこと |nette:configuring]について学びます。 +設定ファイルはふつう [NEON 形式 |neon:format]で書きます。[何を設定できるか |nette:configuring]は別の章で読めます。 .[tip] -開発環境では、コードまたは設定ファイルが変更されるたびにコンテナが自動的に更新されます。本番環境では、一度だけ生成され、パフォーマンスを最大化するために変更はチェックされません。 +開発モードでは、コードや設定ファイルが変わるたびにコンテナが自動的に更新されます。本番モードでは一度だけ生成され、性能を最大にするために変更は確認されません。 + +`createContainer()` がコンテナを構築してそのインスタンスを返すのに対し、`loadContainer()` メソッドは生成されたコンテナのクラス名だけを返すので、自分でインスタンス化できます。高度な場面で役立ちます。 -`addConfig()` を使用して設定ファイルをロードします。 +設定ファイルは `addConfig()` で読み込みます。 ```php $this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` -複数の設定ファイルを追加したい場合は、`addConfig()` 関数を複数回呼び出すことができます。 +設定ファイルをさらに足したい場合は、`addConfig()` 関数を何度も呼べます。 ```php $configDir = $this->rootDir . '/config'; @@ -198,17 +212,17 @@ if (PHP_SAPI === 'cli') { } ``` -`cli.php` という名前はタイプミスではありません。設定はPHPファイルに記述することもでき、そのファイルが配列として返します。 +`cli.php` という名前は誤りではありません。設定は、それを配列として返す PHP ファイルで書くこともできます。 -[`includes` セクション |dependency-injection:configuration#ファイルのインクルード]で他の設定ファイルを追加することもできます。 +ほかの設定ファイルは [`includes` セクション |dependency-injection:configuration#ファイルの読み込み]でも足せます。 -設定ファイルに同じキーを持つ要素が表示された場合、それらは上書きされるか、[配列の場合はマージ |dependency-injection:configuration#マージ]されます。後でインクルードされたファイルは、前のファイルよりも優先度が高くなります。`includes` セクションが記載されているファイルは、それにインクルードされているファイルよりも優先度が高くなります。 +設定ファイルに同じキーの項目が現れた場合、それらは上書きされるか、[配列なら統合されます |dependency-injection:configuration#統合]。あとで読み込まれたファイルのほうが、前のものより優先度が高くなります。`includes` セクションを持つファイルは、そこで読み込まれるファイルより優先度が高くなります。 静的パラメータ ------- -設定ファイルで使用されるパラメータは、[`parameters` セクション |dependency-injection:configuration#パラメータ]で定義でき、`addStaticParameters()` メソッド(エイリアス `addParameters()` を持つ)で渡す(または上書きする)こともできます。重要なのは、パラメータの値が異なると、追加のDIコンテナ、つまり追加のクラスが生成されることです。 +設定ファイルで使うパラメータは [`parameters` セクション |dependency-injection:configuration#パラメータ]で定義でき、`addStaticParameters()` メソッド(古い、いまは非推奨の別名は `addParameters()`)で渡す(または上書きする)こともできます。大事なのは、パラメータの値が違えば別の DI コンテナ、つまり別のクラスが生成されるという点です。 ```php $this->configurator->addStaticParameters([ @@ -216,13 +230,13 @@ $this->configurator->addStaticParameters([ ]); ``` -`projectId` パラメータは、設定で通常の `%projectId%` 表記で参照できます。 +`projectId` パラメータは、設定の中で標準の書き方 `%projectId%` で参照できます。 動的パラメータ ------- -コンテナに動的パラメータを追加することもできます。静的パラメータとは異なり、それらの異なる値は新しいDIコンテナの生成を引き起こしません。 +コンテナには動的パラメータも足せます。静的パラメータと違い、その値が違っても新しい DI コンテナは生成されません。 ```php $this->configurator->addDynamicParameters([ @@ -230,7 +244,7 @@ $this->configurator->addDynamicParameters([ ]); ``` -このようにして、例えば環境変数を簡単に追加でき、それらは設定で `%env.variable%` 表記で参照できます。 +こうすれば、たとえば環境変数を簡単に足せます。設定の中では `%env.variable%` の書き方で参照できます。 ```php $this->configurator->addDynamicParameters([ @@ -239,24 +253,25 @@ $this->configurator->addDynamicParameters([ ``` -デフォルトパラメータ ----------- +既定のパラメータ +-------- -設定ファイルでは、これらの静的パラメータを使用できます。 +設定ファイルでは次のパラメータが使えます。 -- `%appDir%` は `Bootstrap.php` ファイルを含むディレクトリへの絶対パスです。 -- `%wwwDir%` はエントリファイル `index.php` を含むディレクトリへの絶対パスです。 -- `%tempDir%` は一時ファイル用のディレクトリへの絶対パスです。 -- `%vendorDir%` はComposerがライブラリをインストールするディレクトリへの絶対パスです。 -- `%rootDir%` はプロジェクトのルートディレクトリへの絶対パスです。 -- `%debugMode%` はアプリケーションがデバッグモードであるかどうかを示します。 -- `%consoleMode%` はリクエストがコマンドライン経由で来たかどうかを示します。 +- `%appDir%` は `Bootstrap.php` ファイルがあるディレクトリへの絶対パスです +- `%wwwDir%` は入口のファイル `index.php` があるディレクトリへの絶対パスです +- `%tempDir%` は一時ファイル用のディレクトリへの絶対パスです +- `%vendorDir%` は Composer がライブラリをインストールするディレクトリへの絶対パスです +- `%rootDir%` はプロジェクトのルートディレクトリへの絶対パスです +- `%baseUrl%` はルートディレクトリへの絶対 URL です(実行時に解決される動的パラメータです) +- `%debugMode%` はアプリケーションがデバッグモードかどうかを示します +- `%consoleMode%` はリクエストがコマンドラインから来たかどうかを示します -インポートされたサービス ------------- +取り込まれるサービス +---------- -ここではさらに深く掘り下げます。DIコンテナの目的はオブジェクトを作成することですが、例外的に既存のオブジェクトをコンテナに挿入する必要がある場合があります。これを行うには、`imported: true` フラグを使用してサービスを定義します。 +さらに深く踏み込みましょう。DI コンテナの目的はオブジェクトを作ることですが、ときには既存のオブジェクトをコンテナに入れる必要が出てきます。そのためには `imported: true` フラグを付けてサービスを定義します。 ```neon services: @@ -265,7 +280,7 @@ services: imported: true ``` -そして、ブートストラップでオブジェクトをコンテナに挿入します。 +そして bootstrap でオブジェクトをコンテナに入れます。 ```php $this->configurator->addServices([ @@ -274,15 +289,15 @@ $this->configurator->addServices([ ``` -異なる環境 -===== +さまざまな環境 +======= -必要に応じて `Bootstrap` クラスを自由に変更してください。`bootWebApplication()` メソッドにパラメータを追加して、Webプロジェクトを区別することができます。または、他のメソッドを追加することもできます。例えば、単体テスト用の環境を初期化する `bootTestEnvironment()`、コマンドラインから呼び出されるスクリプト用の `bootConsoleApplication()` などです。 +`Bootstrap` クラスは必要に応じて自由に書き換えてください。ウェブのプロジェクトを見分けるために `bootWebApplication()` メソッドにパラメータを足せます。あるいはメソッドを増やしてもよいでしょう。ユニットテスト用に環境を初期化する `bootTestEnvironment()`、コマンドラインから呼ばれるスクリプト用の `bootConsoleApplication()` などです。 ```php public function bootTestEnvironment(): Nette\DI\Container { - Tester\Environment::setup(); // Nette Testerの初期化 + Tester\Environment::setup(); // Nette Tester の初期化 $this->setupContainer(); return $this->configurator->createContainer(); } diff --git a/application/ja/components.texy b/application/ja/components.texy index e8f6b5e4f1..faf42836b1 100644 --- a/application/ja/components.texy +++ b/application/ja/components.texy @@ -1,29 +1,29 @@ -インタラクティブコンポーネント -*************** +インタラクティブなコンポーネント +****************
    -コンポーネントは、ページに挿入する独立した再利用可能なオブジェクトです。フォーム、データグリッド、投票など、繰り返し使用する意味のあるものであれば何でもかまいません。ここでは以下について説明します。 +コンポーネントは、ページに埋め込む独立した再利用できるオブジェクトです。フォーム、データグリッド、アンケートなど、繰り返し使う意味のあるものなら何でもかまいません。ここでは次のことを扱います。 -- コンポーネントの使用方法 -- コンポーネントの作成方法 +- コンポーネントの使い方 +- その書き方 - シグナルとは何か
    -Netteには組み込みのコンポーネントシステムがあります。DelphiやASP.NET Web Formsを知っている古い世代の方々には馴染みがあるかもしれません。ReactやVue.jsも、遠いながらも似たようなものに基づいています。しかし、PHPフレームワークの世界では、これはユニークな機能です。 +Nette には組み込みのコンポーネントのしくみがあります。Delphi や ASP.NET Web Forms の熟練者には似たものが馴染み深いかもしれません。React や Vue.js も、遠く似た考えの上に築かれています。とはいえ PHP のフレームワークの世界では、これは他にない機能です。 -一方、コンポーネントはアプリケーション開発へのアプローチに根本的な影響を与えます。事前に準備されたユニットからページを組み立てることができます。管理画面にデータグリッドが必要ですか?Nette用のオープンソースアドオン(コンポーネントだけではありません)のリポジトリである[Componette |https://componette.org/search/component]で見つけて、Presenterに簡単に追加できます。 +同時にコンポーネントは、アプリケーション開発への向き合い方を根本から変えます。あらかじめ用意された部品からページを組み立てられます。管理画面にデータグリッドが必要ですか。Nette 向けのオープンソースのアドオン(コンポーネントに限りません)を集めた [Componette |https://componette.org/search/component]で見つけて、プレゼンターに差し込むだけです。 -Presenterには任意の数のコンポーネントを含めることができます。そして、一部のコンポーネントには他のコンポーネントを挿入できます。これにより、Presenterをルートとするコンポーネントツリーが作成されます。 +プレゼンターにはいくつでもコンポーネントを組み込めます。そしてコンポーネントの中にほかのコンポーネントを埋め込めます。こうしてプレゼンターを根とするコンポーネントの木ができます。 ファクトリメソッド ========= -コンポーネントはどのようにPresenterに挿入され、その後使用されるのでしょうか?通常はファクトリメソッドを使用します。 +コンポーネントはどうプレゼンターに差し込まれ、どう使われるのでしょうか。ふつうはファクトリメソッドを通じてです。 -コンポーネントファクトリは、コンポーネントが実際に必要になったときにのみ作成する(遅延/オンデマンド)エレガントな方法です。全体の魔法は、`createComponent()` という名前のメソッドを実装することにあります。ここで `` は作成されるコンポーネントの名前であり、このメソッドがコンポーネントを作成して返します。 +コンポーネントのファクトリは、本当に必要になったときにだけコンポーネントを作る(遅延、オンデマンド)優雅な方法です。魔法のすべては `createComponent()` という名前のメソッドを実装することにあります。`` は作られるコンポーネントの名前で、このメソッドがそれを作って返します。 ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -31,27 +31,27 @@ class DefaultPresenter extends Nette\Application\UI\Presenter protected function createComponentPoll(): PollControl { $poll = new PollControl; - $poll->items = $this->item; + $poll->items = $this->items; return $poll; } } ``` -すべてのコンポーネントが個別のメソッドで作成されるため、コードがより明確になります。 +すべてのコンポーネントが別々のメソッドで作られるので、コードが分かりやすくなります。 .[note] -コンポーネント名は常に小文字で始まりますが、メソッド名では大文字で記述されます。 +コンポーネントの名前は、メソッド名では大文字で始まっていても、常に小文字で始まります。 -ファクトリは直接呼び出すことはありません。コンポーネントを初めて使用するときに自動的に呼び出されます。これにより、コンポーネントは適切なタイミングで、実際に必要な場合にのみ作成されます。コンポーネントを使用しない場合(例えば、ページの一部のみが転送されるAJAXリクエストの場合や、テンプレートのキャッシュの場合)、コンポーネントはまったく作成されず、サーバーのパフォーマンスを節約できます。 +ファクトリを直接呼ぶことは決してありません。コンポーネントを最初に使ったときに自動的に呼ばれます。おかげでコンポーネントは適切な瞬間に、しかも本当に必要な場合にだけ作られます。コンポーネントを使わなければ(ページの一部だけを転送する AJAX のリクエストや、テンプレートをキャッシュする場合など)まったく作られないので、サーバーの性能を節約できます。 ```php .{file:DefaultPresenter.php} -// コンポーネントにアクセスし、初めての場合は -// それを作成する createComponentPoll() が呼び出されます +// コンポーネントにアクセスします。それが最初なら +// createComponentPoll() が呼ばれて作られます $poll = $this->getComponent('poll'); -// 代替構文: $poll = $this['poll']; +// 別の書き方: $poll = $this['poll']; ``` -テンプレートでは、[{control} |#レンダリング] タグを使用してコンポーネントを描画できます。したがって、コンポーネントを手動でテンプレートに渡す必要はありません。 +テンプレートでは [{control} |#描画]タグでコンポーネントを描けます。ですからコンポーネントを手でテンプレートに渡す必要はありません。 ```latte

    投票してください

    @@ -59,21 +59,26 @@ $poll = $this->getComponent('poll'); {control poll} ``` +.[tip] +数が変わるコンポーネントを動的に作るには [Multiplier |multiplier]を使います。 -ハリウッドスタイル -========= +`createComponent()` のファクトリメソッドはプレゼンターだけのものではありません。同じやり方でコンポーネントの中にコンポーネントを入れ子にし、木に組み立てられます。たとえばコンポーネントの中で別々に描かれるフォームに便利です。 + + +ハリウッド流 +====== -コンポーネントは通常、私たちがハリウッドスタイルと呼ぶのが好きな新鮮なテクニックを使用します。映画のオーディション参加者がよく聞く決まり文句をきっとご存知でしょう:「こちらから連絡しますので、電話しないでください」。まさにそれです。 +コンポーネントはふつう、私たちがハリウッド流と呼びたくなる新鮮な手法を使います。映画のオーディションの参加者がよく耳にする決まり文句をご存じでしょう。「こちらから連絡します、あなたからはしないでください。」まさにそういうことです。 -Netteでは、常に何かを尋ねる(「フォームは送信されましたか?」、「有効でしたか?」または「ユーザーはこのボタンを押しましたか?」)代わりに、フレームワークに「それが起こったら、このメソッドを呼び出して」と伝え、残りの作業を任せます。JavaScriptでプログラミングしている場合、このプログラミングスタイルには精通しているでしょう。特定のイベントが発生したときに呼び出される関数を記述します。そして、言語は適切なパラメータを渡します。 +Nette では、絶えず問い続ける(「フォームは送信されたか」「それは正しかったか」「ユーザーはこのボタンを押したか」)代わりに、フレームワークに「これが起きたら、このメソッドを呼んで」と伝えて、あとは任せます。JavaScript でプログラムしているなら、この書き方はよくご存じでしょう。ある出来事が起きたときに呼ばれる関数を書き、言語が適切なパラメータをそこに渡してくれます。 -これはアプリケーションの作成方法を完全に変えます。フレームワークに任せられるタスクが多ければ多いほど、あなたの作業は少なくなります。そして、見落とす可能性のあることも少なくなります。 +これはアプリケーションを書くときの見方をすっかり変えます。フレームワークに任せられる仕事が多いほど、あなたの手間は減ります。そして見落としも減ります。 -コンポーネントの作成 +コンポーネントを書く ========== -コンポーネントという用語は、通常、[api:Nette\Application\UI\Control] クラスの子孫を意味します。(したがって、「コントロール」という用語を使用する方が正確ですが、日本語では「コントロール」は他の意味合いを持つことがあり、「コンポーネント」の方が一般的になりました。)Presenter自体 [api:Nette\Application\UI\Presenter] も、ちなみに `Control` クラスの子孫です。 +コンポーネントという語は、ふつう [api:Nette\Application\UI\Control]クラスの子孫を指します。(「control」という語のほうが正確ですが、言語によっては別の意味を持つので、「component」のほうが定着しました。)プレゼンター [api:Nette\Application\UI\Presenter]自身も `Control` クラスの子孫です。 ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +89,22 @@ class PollControl extends Control ``` -レンダリング -====== +描画 +=== -コンポーネントをレンダリングするために `{control componentName}` タグが使用されることはすでに知っています。これは実際にはコンポーネントの `render()` メソッドを呼び出し、そこでレンダリングを処理します。Presenterとまったく同じように、`$this->template` 変数に [Latteテンプレート|templates] があり、それにパラメータを渡します。Presenterとは異なり、テンプレートファイルを指定してレンダリングさせる必要があります。 +コンポーネントを描くのに `{control componentName}` タグを使うことはすでに見ました。これは実際にはコンポーネントの `render()` メソッドを呼び、そこで描画の面倒を見ます。プレゼンターと同じく `$this->template` 変数に [Latte のテンプレート|templates]があり、そこにパラメータを渡します。プレゼンターと違うのは、テンプレートのファイルを指定して描かせる必要がある点です。 ```php .{file:PollControl.php} public function render(): void { - // テンプレートにいくつかのパラメータを挿入します + // テンプレートにいくつかのパラメータを入れます $this->template->param = $value; - // そしてそれをレンダリングします + // そして描きます $this->template->render(__DIR__ . '/poll.latte'); } ``` -`{control}` タグを使用すると、`render()` メソッドにパラメータを渡すことができます。 +`{control}` タグは `render()` メソッドにパラメータを渡せます。 ```latte {control poll $id, $message} @@ -112,7 +117,7 @@ public function render(int $id, string $message): void } ``` -コンポーネントがいくつかの部分で構成され、それらを別々にレンダリングしたい場合があります。それぞれについて、独自のレンダリングメソッドを作成します。ここでは例として `renderPaginator()` を作成します。 +コンポーネントが、別々に描きたいいくつかの部分から成ることもあります。それぞれについて独自の描画メソッドを作ります。この例では `renderPaginator()` です。 ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +126,69 @@ public function renderPaginator(): void } ``` -そして、テンプレートで次のように呼び出します。 +テンプレートでは次のように呼び出します。 ```latte {control poll:paginator} ``` -よりよく理解するために、このタグがどのようにPHPに変換されるかを知っておくと良いでしょう。 +理解を深めるために、このタグが PHP のコードにどう変換されるかを知っておくとよいでしょう。 ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -は次のように変換されます。 +は次に変換されます。 ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` メソッドは `poll` コンポーネントを返し、このコンポーネントに対して `render()` メソッド、またはタグのコロンの後に異なるレンダリング方法が指定されている場合は `renderPaginator()` メソッドを呼び出します。 +`getComponent()` メソッドが `poll` コンポーネントを返し、そのコンポーネントで `render()` メソッド、あるいはタグのコロンのあとに別の描画メソッドが指定されていれば `renderPaginator()` が呼ばれます。 .[caution] -注意:パラメータのどこかに **`=>`** が現れると、すべてのパラメータが配列にラップされ、最初の引数として渡されます。 +注意してください。パラメータの中で角かっこの外に **`=>`** が現れると、すべてのパラメータが配列に包まれ、第 1 引数として渡されます。 ```latte {control poll, id: 123, message: 'hello'} ``` -は次のように変換されます。 +は次に変換されます。 ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -サブコンポーネントのレンダリング: +サブコンポーネントの描画: ```latte {control cartControl-someForm} ``` -は次のように変換されます。 +は次に変換されます。 ```php $control->getComponent("cartControl-someForm")->render(); ``` -コンポーネントは、Presenterと同様に、いくつかの便利な変数を自動的にテンプレートに渡します。 +コンポーネントもプレゼンターと同じく、いくつかの役立つ変数をテンプレートに自動的に渡します。 -- `$basePath` はルートディレクトリへの絶対URLパスです(例:`/eshop`) -- `$baseUrl` はルートディレクトリへの絶対URLです(例:`http://localhost/eshop`) +- `$basePath` はルートディレクトリへの絶対 URL パスです(たとえば `/eshop`) +- `$baseUrl` はルートディレクトリへの絶対 URL です(たとえば `http://localhost/eshop`) - `$user` は[ユーザーを表す |security:authentication]オブジェクトです -- `$presenter` は現在のPresenterです +- `$presenter` は現在のプレゼンターです - `$control` は現在のコンポーネントです -- `$flashes` は `flashMessage()` 関数によって送信された[メッセージ |#フラッシュメッセージ]の配列です +- `$flashes` は `flashMessage()` 関数で送られた[メッセージ |#フラッシュメッセージ]の配列です シグナル ==== -Netteアプリケーションのナビゲーションは、`Presenter:action` のペアへのリンクまたはリダイレクトに基づいていることはすでに知っています。しかし、**現在のページ**でアクションを実行したいだけの場合はどうでしょうか?例えば、テーブルの列の並び替えを変更する、項目を削除する、ライト/ダークモードを切り替える、フォームを送信する、投票するなどです。 +Nette のアプリケーションでの移動が、`Presenter:action` の組へのリンクやリダイレクトから成ることはすでに見ました。しかし**現在のページ**で何か処理をしたいだけの場合はどうでしょうか。たとえば表の列の並べ替えを変える、項目を削除する、ライト/ダークモードを切り替える、フォームを送信する、アンケートに投票する、などです。 -この種のリクエストはシグナルと呼ばれます。そして、アクションが `action()` または `render()` メソッドを呼び出すのと同様に、シグナルは `handle()` メソッドを呼び出します。アクション(またはビュー)という概念は純粋にPresenterに関連していますが、シグナルはすべてのコンポーネントに関係します。したがって、`UI\Presenter` は `UI\Control` の子孫であるため、Presenterにも関係します。 +この種のリクエストをシグナルと呼びます。アクションが `action()` や `render()` メソッドを呼ぶのと同じように、シグナルは `handle()` メソッドを呼びます。アクション(やビュー)の考え方が純粋にプレゼンターに関わるのに対し、シグナルはすべてのコンポーネントに関わります。`UI\Presenter` は `UI\Control` の子孫なので、プレゼンターにも関わります。 ```php public function handleClick(int $x, int $y): void @@ -192,36 +197,36 @@ public function handleClick(int $x, int $y): void } ``` -シグナルを呼び出すリンクは、通常の方法で作成します。つまり、テンプレートでは `n:href` 属性または `{link}` タグを使用し、コードでは `link()` メソッドを使用します。詳細については、[URLリンクの作成 |creating-links#シグナルへのリンク]の章を参照してください。 +シグナルを呼ぶリンクはいつもどおりに作ります。つまりテンプレートでは `n:href` 属性か `{link}` タグ、コードでは `link()` メソッドです。詳しくは [URL リンクの作成 |creating-links#シグナルへのリンク]の章をご覧ください。 ```latte ここをクリック ``` -シグナルは常に現在のPresenterとアクションで呼び出され、別のPresenterや別のアクションで呼び出すことはできません。 +シグナルは常に現在のプレゼンターとアクションで呼ばれます。別のプレゼンターやアクションで呼ぶことはできません。 -したがって、シグナルは元のリクエストとまったく同じようにページの再読み込みを引き起こしますが、さらに適切なパラメータを持つシグナル処理メソッドを呼び出します。メソッドが存在しない場合、[api:Nette\Application\UI\BadSignalException] 例外がスローされ、ユーザーには403 Forbiddenエラーページとして表示されます。 +ですからシグナルは、もとのリクエストと同じようにページを読み込み直しつつ、加えてシグナルを処理するメソッドを適切なパラメータで呼びます。そのメソッドがなければ [api:Nette\Application\UI\BadSignalException]例外が投げられ、ユーザーには 403 Forbidden のエラーページとして表示されます。 -スニペットとAJAX -========== +スニペットと AJAX +=========== -シグナルはAJAXを少し思い出させるかもしれません:現在のページで呼び出されるハンドラです。そして、その通りです。シグナルは実際にはAJAXを使用して呼び出されることが多く、その後、変更されたページの部分のみがブラウザに転送されます。つまり、いわゆるスニペットです。詳細については、[AJAX専用ページ |ajax]を参照してください。 +シグナルは AJAX を少し思い起こさせるかもしれません。現在のページで呼ばれるハンドラだからです。そのとおりで、シグナルは実際に AJAX で呼ばれることが多く、そのあとページの変わった部分だけがブラウザに転送されます。これをスニペットと呼びます。詳しくは [AJAX のページ |ajax]をご覧ください。 フラッシュメッセージ ========== -コンポーネントには、Presenterとは独立した独自のフラッシュメッセージストレージがあります。これらは、例えば操作の結果を通知するメッセージです。フラッシュメッセージの重要な特徴は、リダイレクト後もテンプレートで利用できることです。表示後もさらに30秒間有効です。例えば、転送エラーのためにユーザーがページを更新した場合でも、メッセージはすぐには消えません。 +コンポーネントは、プレゼンターとは独立した自分のフラッシュメッセージの保管場所を持ちます。これはたとえば操作の結果を知らせるメッセージです。フラッシュメッセージの大事な性質は、リダイレクト後もテンプレートで使えることです。一度表示されたあとも、さらに 30 秒は有効なままです。たとえば通信のエラーでユーザーがページを再読み込みしても、メッセージがすぐ消えることはありません。 -送信は [flashMessage |api:Nette\Application\UI\Control::flashMessage()] メソッドによって処理されます。最初のパラメータはメッセージのテキストまたはメッセージを表す `stdClass` オブジェクトです。オプションの2番目のパラメータはそのタイプ(error、warning、infoなど)です。`flashMessage()` メソッドは、フラッシュメッセージのインスタンスを `stdClass` オブジェクトとして返し、これに追加情報を追加できます。 +送信は [flashMessage |api:Nette\Application\UI\Control::flashMessage()]メソッドが担当します。第 1 パラメータはメッセージの本文(`string`、`Stringable`)か、メッセージを表す `stdClass` オブジェクトです。省略可能な第 2 パラメータはその種類(error、warning、info など)です。`flashMessage()` メソッドはフラッシュメッセージのインスタンスを `stdClass` オブジェクトとして返すので、さらに情報を足せます。 ```php -$this->flashMessage('項目が削除されました。'); -$this->redirect(/* ... */); // そしてリダイレクトします +$this->flashMessage('項目を削除しました。'); +$this->redirect(/* ... */); // そしてリダイレクト ``` -これらのメッセージは、テンプレートでは `$flashes` 変数で `stdClass` オブジェクトとして利用できます。これらには `message`(メッセージテキスト)、`type`(メッセージタイプ)プロパティが含まれ、前述のユーザー情報を含むこともできます。例えば、次のようにレンダリングします。 +これらのメッセージは、テンプレートの `$flashes` 変数に `stdClass` オブジェクトとして入っていて、`message`(メッセージの本文)、`type`(メッセージの種類)のプロパティを持ち、先ほど触れたユーザーの情報を含むこともあります。たとえば次のように描きます。 ```latte {foreach $flashes as $flash} @@ -230,84 +235,84 @@ $this->redirect(/* ... */); // そしてリダイレクトします ``` -シグナル後のリダイレクト -============ +シグナルの処理後のリダイレクト +=============== -コンポーネントのシグナル処理後には、しばしばリダイレクトが続きます。これはフォームの場合と似ています。フォーム送信後もリダイレクトして、ブラウザでページを更新したときにデータが再送信されないようにします。 +コンポーネントのシグナルの処理のあとには、リダイレクトが続くことがよくあります。フォームと同じで、送信後にはリダイレクトして、ブラウザでページを再読み込みしてもデータが再送信されないようにします。 ```php -$this->redirect('this') // 現在のPresenterとアクションにリダイレクトします +$this->redirect('this'); // 現在のプレゼンターとアクションにリダイレクトします ``` -コンポーネントは再利用可能な要素であり、通常は特定のPresenterへの直接的な依存関係を持つべきではないため、`redirect()` および `link()` メソッドはパラメータを自動的にコンポーネントのシグナルとして解釈します。 +コンポーネントは再利用できる部品で、ふつう特定のプレゼンターへの直接のつながりを持つべきではないので、`redirect()` と `link()` メソッドはパラメータを自動的にコンポーネントのシグナルと解釈します。 ```php -$this->redirect('click') // 同じコンポーネントの 'click' シグナルにリダイレクトします +$this->redirect('click'); // 同じコンポーネントの 'click' シグナルにリダイレクトします ``` -別のPresenterやアクションにリダイレクトする必要がある場合は、Presenterを介して行うことができます。 +別のプレゼンターやアクションにリダイレクトする必要があるなら、プレゼンターを通して行えます。 ```php -$this->getPresenter()->redirect('Product:show'); // 別のPresenter/アクションにリダイレクトします +$this->getPresenter()->redirect('Product:show'); // 別のプレゼンター/アクションにリダイレクトします ``` -パーシステントパラメータ -============ +永続パラメータ +======= -パーシステントパラメータは、異なるリクエスト間でコンポーネントの状態を維持するために使用されます。その値は、リンクをクリックした後も同じままです。セッションデータとは異なり、URLで転送されます。そして、これは完全に自動的に行われ、同じページの他のコンポーネントで作成されたリンクも含みます。 +永続パラメータは、コンポーネントの状態をリクエストをまたいで保つために使います。その値はリンクをクリックしたあとも変わりません。セッションのデータと違い、URL で運ばれます。しかもそれは完全に自動的に起こり、同じページのほかのコンポーネントで作られたリンクにも及びます。 -例えば、コンテンツをページ分割するためのコンポーネントがあるとします。このようなコンポーネントはページ上に複数存在する可能性があります。そして、リンクをクリックした後、すべてのコンポーネントが現在のページにとどまるようにしたいとします。したがって、ページ番号(`page`)をパーシステントパラメータにします。 +たとえば内容をページ分けするコンポーネントがあるとします。そうしたコンポーネントがページに複数あるかもしれません。そしてリンクをクリックしたあとも、すべてのコンポーネントが今のページにとどまってほしいとします。ですからページ番号(`page`)を永続パラメータにします。 -Netteでパーシステントパラメータを作成するのは非常に簡単です。パブリックプロパティを作成し、属性でマークするだけです。(以前は `/** @persistent */` が使用されていました) +Nette で永続パラメータを作るのはきわめて簡単です。public のプロパティを作り、アトリビュートで印を付けるだけです(以前は `/** @persistent */` が使われていました)。 ```php -use Nette\Application\Attributes\Persistent; // この行は重要です +use Nette\Application\Attributes\Persistent; // この行が大事です class PaginatingControl extends Control { #[Persistent] - public int $page = 1; // publicである必要があります + public int $page = 1; // public でなければなりません } ``` -プロパティにはデータ型(例:`int`)を指定することをお勧めします。また、デフォルト値を指定することもできます。パラメータ値は[検証 |#パーシステントパラメータの検証]できます。 +プロパティにはデータ型(`int` など)を指定することをおすすめしますし、既定値も与えられます。パラメータの値は[検証できます |#永続パラメータの検証]。 -リンクを作成するときに、パーシステントパラメータの値を変更できます。 +リンクを作るとき、永続パラメータの値は変えられます。 ```latte 次へ ``` -または、*リセット*することもできます。つまり、URLから削除します。その後、デフォルト値を取ります。 +あるいは*リセット*して URL から取り除けます。その場合は既定値になります。 ```latte リセット ``` -パーシステントコンポーネント -============== +永続コンポーネント +========= -パラメータだけでなく、コンポーネントもパーシステントにすることができます。このようなコンポーネントでは、そのパーシステントパラメータはPresenterの異なるアクション間、または複数のPresenter間でも転送されます。パーシステントコンポーネントは、Presenterクラスのアノテーションでマークします。例えば、このようにして `calendar` および `poll` コンポーネントをマークします。 +パラメータだけでなく、コンポーネントも永続にできます。その永続パラメータは、プレゼンターの異なるアクションのあいだや、複数のプレゼンターのあいだでも引き継がれます。永続コンポーネントには、プレゼンターのクラスにアトリビュートで印を付けます。たとえば `calendar` と `poll` のコンポーネントには次のように印を付けます。 ```php -/** - * @persistent(calendar, poll) - */ +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] class DefaultPresenter extends Nette\Application\UI\Presenter { } ``` -これらのコンポーネント内のサブコンポーネントをマークする必要はありません。それらもパーシステントになります。 +これらのコンポーネントの中のサブコンポーネントには印を付ける必要がありません。それらも永続になります。 -PHP 8では、属性を使用してパーシステントコンポーネントをマークすることもできます。 +古いアノテーション `@persistent` もまだ動きますが、非推奨で警告を出します。 ```php -use Nette\Application\Attributes\Persistent; - -#[Persistent('calendar', 'poll')] +/** + * @persistent(calendar, poll) + */ class DefaultPresenter extends Nette\Application\UI\Presenter { } @@ -317,32 +322,32 @@ class DefaultPresenter extends Nette\Application\UI\Presenter 依存関係を持つコンポーネント ============== -それらを使用するPresenterを「汚す」ことなく、依存関係を持つコンポーネントを作成するにはどうすればよいでしょうか?NetteのDIコンテナの賢い機能のおかげで、従来のサービスを使用する場合と同様に、ほとんどの作業をフレームワークに任せることができます。 +依存関係を持つコンポーネントを、それを使うプレゼンターを「散らかさず」に作るにはどうすればよいでしょうか。Nette の DI コンテナの賢い機能のおかげで、通常のサービスと同じく、ほとんどの仕事をフレームワークに任せられます。 -例として、`PollFacade` サービスに依存するコンポーネントを取り上げましょう。 +`PollFacade` サービスに依存するコンポーネントを例に取りましょう。 ```php class PollControl extends Control { public function __construct( - private int $id, // コンポーネントを作成する投票のID + private int $id, // コンポーネントを作る対象のアンケートの ID private PollFacade $facade, ) { } public function handleVote(int $voteId): void { - $this->facade->vote($id, $voteId); + $this->facade->vote($this->id, $voteId); // ... } } ``` -従来のサービスを作成する場合、問題はありませんでした。すべての依存関係の受け渡しは、DIコンテナによって目に見えない形で処理されます。しかし、コンポーネントの場合、通常はPresenterの[#ファクトリメソッド] `createComponent…()` で直接新しいインスタンスを作成します。しかし、すべてのコンポーネントのすべての依存関係をPresenterに渡してからコンポーネントに渡すのは面倒です。そして、書かれたコードの量も… +通常のサービスを書いているなら、議論の余地はありません。DI コンテナがすべての依存関係の受け渡しを見えないところでこなしてくれます。しかしコンポーネントの場合、ふつうは[ファクトリメソッド |#ファクトリメソッド] `createComponent…()` の中で、プレゼンターの中に新しいインスタンスを作って扱います。とはいえ、すべてのコンポーネントのすべての依存関係を、コンポーネントに渡すためだけにプレゼンターへ渡すのは面倒です。しかも書くコードの量ときたら……。 -論理的な疑問は、なぜコンポーネントを従来のサービスとして登録し、Presenterに渡してから `createComponent…()` メソッドで返さないのかということです。しかし、このアプローチは不適切です。なぜなら、コンポーネントを複数回作成できるようにしたいからです。 +当然の疑問として、コンポーネントを通常のサービスとして登録し、プレゼンターに渡して `createComponent…()` メソッドで返せばよいのでは、と思うかもしれません。しかしこのやり方はふさわしくありません。必要ならコンポーネントを何度も作れるようにしたいからです。 -正しい解決策は、コンポーネントのファクトリ、つまりコンポーネントを作成するクラスを作成することです。 +正しい解は、コンポーネントのファクトリ、つまりコンポーネントを作ってくれるクラスを書くことです。 ```php class PollControlFactory @@ -359,14 +364,14 @@ class PollControlFactory } ``` -このようにして、ファクトリを構成内のコンテナに登録します。 +このファクトリを設定でコンテナに登録します。 ```neon services: - PollControlFactory ``` -そして最後に、Presenterで使用します。 +そして最後にプレゼンターで使います。 ```php class PollPresenter extends Nette\Application\UI\Presenter @@ -378,13 +383,13 @@ class PollPresenter extends Nette\Application\UI\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // パラメータを渡すことができます + $pollId = 1; // 自分のパラメータを渡せます return $this->pollControlFactory->create($pollId); } } ``` -素晴らしいことに、Nette DIはそのような単純なファクトリを[生成 |dependency-injection:factory]できるので、そのコード全体を書く代わりに、そのインターフェースを書くだけで済みます。 +素晴らしいのは、Nette DI がこうした単純なファクトリを[生成できる |dependency-injection:factory]ことです。ですからそのコード全体を書く代わりに、インターフェースを書くだけで済みます。 ```php interface PollControlFactory @@ -393,21 +398,21 @@ interface PollControlFactory } ``` -これで完了です。Netteは内部的にこのインターフェースを実装し、Presenterに渡します。そこで使用できます。魔法のように、パラメータ `$id` と `PollFacade` クラスのインスタンスをコンポーネントに追加します。 +これだけです。Nette が内部でこのインターフェースを実装し、プレゼンターに注入してくれるので、そこで使えます。`$id` パラメータと `PollFacade` クラスのインスタンスを、魔法のようにコンポーネントに足してくれます。 コンポーネントの詳細 ========== -Nette Applicationのコンポーネントは、Webアプリケーションの再利用可能な部分であり、ページに挿入され、この章全体で扱われています。そのようなコンポーネントには、具体的にどのような機能があるのでしょうか? +Nette Application のコンポーネントは、ページに埋め込むウェブアプリケーションの再利用できる部分で、この章はまるごとそれに捧げられてきました。そうしたコンポーネントには、正確には何ができるのでしょうか。 -1) テンプレートでレンダリング可能 -2) AJAXリクエスト時に[どの部分 |ajax#スニペット]をレンダリングするかを知っている(スニペット) -3) 状態をURLに保存する機能がある(パーシステントパラメータ) -4) ユーザーアクションに応答する機能がある(シグナル) -5) 階層構造を作成する(ルートはPresenter) +1) テンプレートに描ける +2) AJAX のリクエストのときに[自分のどの部分を |ajax#スニペット]描くべきかを知っている(スニペット) +3) 自分の状態を URL に保存できる(永続パラメータ) +4) ユーザーの操作に反応できる(シグナル) +5) 階層的な構造を作る(根はプレゼンター) -これらの各機能は、継承ラインのいずれかのクラスによって処理されます。レンダリング(1 + 2)は[api:Nette\Application\UI\Control]が担当し、[ライフサイクル |presenters#Presenterのライフサイクル]への統合(3, 4)は[api:Nette\Application\UI\Component]クラスが担当し、階層構造の作成(5)は[ContainerおよびComponent |component-model:]クラスが担当します。 +これらの機能は、それぞれ継承の系列の中のいずれかのクラスが担当します。描画(1 + 2)は [api:Nette\Application\UI\Control]が、[ライフサイクル |presenters#プレゼンターのライフサイクル]への統合(3、4)は [api:Nette\Application\UI\Component]クラスが、階層的な構造の生成(5)は [Container と Component |component-model:]のクラスが担当します。 ``` Nette\ComponentModel\Component { IComponent } @@ -428,12 +433,12 @@ Nette\ComponentModel\Component { IComponent } [* lifecycle-component.svg *] *** *コンポーネントのライフサイクル* .<> -パーシステントパラメータの検証 ---------------- +永続パラメータの検証 +---------- -URLから受け取った[#パーシステントパラメータ]の値は、`loadState()` メソッドによってプロパティに書き込まれます。また、プロパティで指定されたデータ型と一致するかどうかもチェックし、一致しない場合は404エラーで応答し、ページは表示されません。 +URL から受け取った[永続パラメータ |#永続パラメータ]の値は、`loadState()` メソッドがプロパティに書き込みます。あわせてプロパティに指定されたデータ型と合うかも確認し、合わなければ 404 のエラーで応え、ページは表示されません。 -パーシステントパラメータは、ユーザーがURLで簡単に上書きできるため、決して盲目的に信用しないでください。例えば、このようにしてページ番号 `$this->page` が0より大きいかどうかを検証します。適切な方法は、前述の `loadState()` メソッドをオーバーライドすることです。 +永続パラメータを決して盲信しないでください。ユーザーに URL で簡単に書き換えられます。たとえばページ番号 `$this->page` が 0 より大きいかを次のように確かめます。適切な方法が、先ほどの `loadState()` メソッドの上書きです。 ```php class PaginatingControl extends Control @@ -444,7 +449,7 @@ class PaginatingControl extends Control public function loadState(array $params): void { parent::loadState($params); // ここで $this->page が設定されます - // 値の独自のチェックが続きます: + // 続いて独自の値のチェック: if ($this->page < 1) { $this->error(); } @@ -452,29 +457,43 @@ class PaginatingControl extends Control } ``` -逆のプロセス、つまりパーシステントプロパティから値を収集するプロセスは、`saveState()` メソッドが担当します。 +逆の処理、つまり永続プロパティから値を集めるのは `saveState()` メソッドが担当します。 + + +プレゼンターへの接続 +---------- + +コンポーネントがプレゼンターの階層の一部になった瞬間、`$onAnchor` 配列に入っているコールバックが呼ばれます。その時点からコンポーネントはプレゼンターを使えるようになり、安全にリンクを作ったり、永続パラメータを読んだりできます。 + +```php +$control->onAnchor[] = function ($control): void { + // コンポーネントがプレゼンターを使えるようになりました +}; +``` シグナルの詳細 ------- -シグナルは、元のリクエストとまったく同じようにページの再読み込みを引き起こし(AJAXで呼び出された場合を除く)、`signalReceived($signal)` メソッドを呼び出します。`Nette\Application\UI\Component` クラスのデフォルト実装は、`handle{signal}` という単語で構成されるメソッドを呼び出そうとします。その後の処理は、特定のオブジェクト次第です。`Component` から継承するオブジェクト(つまり `Control` と `Presenter`)は、適切なパラメータを持つ `handle{signal}` メソッドを呼び出そうとすることで応答します。 +シグナルは(AJAX で呼ばれる場合を除き)もとのリクエストとまったく同じようにページを読み込み直し、`signalReceived($signal)` メソッドを呼びます。`Nette\Application\UI\Component` クラスでのその既定の実装は、`handle` という語を組み合わせたメソッドを呼ぼうとします。そのあとの処理はそのオブジェクト次第です。`Component` を継承したオブジェクト(つまり `Control` と `Presenter`)は、`handle` メソッドを適切なパラメータで呼ぼうとして反応します。 + +言い換えると、`handle` 関数の定義を取り、リクエストとともに来たすべてのパラメータと、URL のパラメータを名前で引数に割り当てて、メソッドを呼ぼうとします。たとえば URL の `id` パラメータの値は `$id` 引数として、URL の `something` は `$something` として渡されます。そしてメソッドがなければ、`signalReceived` メソッドが[例外 |api:Nette\Application\UI\BadSignalException]を投げます。 -言い換えれば、`handle{signal}` 関数の定義とリクエストで渡されたすべてのパラメータが取得され、URLのパラメータが名前に基づいて引数に割り当てられ、そのメソッドを呼び出そうとします。例えば、`$id` パラメータとしてURLの `id` パラメータの値が渡され、`$something` としてURLの `something` が渡されます。そして、メソッドが存在しない場合、`signalReceived` メソッドは[例外 |api:Nette\Application\UI\BadSignalException]をスローします。 +URL のパラメータのほかに、シグナルは**リクエストの POST 本体**で送られたパラメータも読みます。シグナルは JavaScript から呼ばれることが多く、そこでは POST メソッドでデータを送るのが自然なので、これは便利です。ただし同じ名前のパラメータが URL と POST 本体の両方から来た場合、**URL の値が優先されます**。ですから POST のフィールドに URL やルートのパラメータと同じ名前を付けるのは避けてください。さもないと URL の値が黙ってそれを上書きしてしまいます。シグナルのパラメータは、アクションのパラメータや永続パラメータと共通の空間を持ちます。[共有されるパラメータの空間 |presenters#共有されるパラメータの空間]をご覧ください。 -シグナルは、`SignalReceiver` インターフェースを実装し、コンポーネントツリーに接続されている任意のコンポーネント、Presenter、またはオブジェクトが受信できます。 +シグナルは、`SignalReceiver` インターフェースを実装してコンポーネントの木につながっている任意のコンポーネント、プレゼンター、オブジェクトが受け取れます。 -シグナルの主な受信者は、`Presenter` および `Control` から継承するビジュアルコンポーネントになります。シグナルは、オブジェクトに何かをするように指示する合図として機能することを目的としています。投票はユーザーからの投票をカウントする必要があり、ニュースブロックは展開して2倍のニュースを表示する必要があり、フォームは送信されてデータを処理する必要がある、などです。 +シグナルの主な受け手は `Presenter` と、`Control` を継承した画面のコンポーネントになるでしょう。シグナルは、オブジェクトに何かをすべきだと知らせる合図として働きます。アンケートはユーザーの票を数えるべき、ニュースの欄は広がって 2 倍のニュースを表示すべき、フォームが送信されたのでデータを処理すべき、といった具合です。 -シグナルのURLは、[Component::link() |api:Nette\Application\UI\Component::link()] メソッドを使用して作成します。`$destination` パラメータとして文字列 `{signal}!` を渡し、`$args` としてシグナルに渡したい引数の配列を渡します。シグナルは常に現在のPresenterとアクションで現在のパラメータとともに呼び出され、シグナルパラメータのみが追加されます。さらに、最初に**シグナルを指定するパラメータ `?do`** が追加されます。 +シグナルの URL は [Component::link() |api:Nette\Application\UI\Component::link()]メソッドで作ります。`$destination` パラメータには文字列 `{signal}!` を、`$args` にはシグナルに渡したい引数の配列を渡します。シグナルは常に現在のプレゼンターとアクションで、現在のパラメータとともに呼ばれ、シグナルのパラメータが足されるだけです。さらに**シグナルを指定するパラメータ `?do`** が足されます。 -その形式は `{signal}` または `{signalReceiver}-{signal}` のいずれかです。`{signalReceiver}` はPresenter内のコンポーネントの名前です。したがって、コンポーネント名にハイフンを使用することはできません。ハイフンはコンポーネント名とシグナルを区切るために使用されますが、このようにして複数のコンポーネントをネストすることが可能です。 +その形式は `{signal}` か `{signalReceiver}-{signal}` です。`{signalReceiver}` はプレゼンターの中のコンポーネントの名前です。ですからコンポーネント名にハイフンは使えません。コンポーネント名とシグナルを分けるのに使われるからです。とはいえ、この方法で複数のコンポーネントを入れ子にできます。 -[isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] メソッドは、コンポーネント(最初の引数)がシグナル(2番目の引数)の受信者であるかどうかを検証します。2番目の引数は省略できます。その場合、コンポーネントが任意のシグナルの受信者であるかどうかを判断します。2番目のパラメータとして `true` を指定すると、指定されたコンポーネントだけでなく、その子孫のいずれかが受信者であるかどうかも検証できます。 +[isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()]メソッドは、コンポーネント(第 1 引数)がシグナル(第 2 引数)の受け手かどうかを調べます。第 2 引数は省略でき、その場合はそのコンポーネントが何らかのシグナルの受け手かを調べます。第 2 パラメータを `true` にすると、指定したコンポーネントかその子孫のいずれかが受け手かどうかを確かめます。 -`handle{signal}` に先行する任意の段階で、[processSignal()|api:Nette\Application\UI\Presenter::processSignal()] メソッドを呼び出すことでシグナルを手動で実行できます。このメソッドはシグナルの処理を担当します。シグナルの受信者として指定されたコンポーネント(受信者が指定されていない場合はPresenter自体)を取得し、それにシグナルを送信します。 +`handle` より前のどの段階でも、[processSignal()|api:Nette\Application\UI\Presenter::processSignal()]メソッドを呼んでシグナルを手動で実行できます。このメソッドがシグナルの処理を引き受け、シグナルの受け手とされたコンポーネント(受け手が指定されていなければプレゼンター自身)を取って、そこへシグナルを送ります。 -例: +例を挙げます。 ```php if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, 'sorting')) { @@ -482,4 +501,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -これにより、シグナルは早期に実行され、再度呼び出されることはありません。 +これでシグナルが前倒しで実行され、もう一度呼ばれることはありません。 diff --git a/application/ja/configuration.texy b/application/ja/configuration.texy index cb0e58a8fe..28bb3a9af9 100644 --- a/application/ja/configuration.texy +++ b/application/ja/configuration.texy @@ -1,8 +1,8 @@ -アプリケーション設定 -********** +アプリケーションの設定 +*********** .[perex] -Netteアプリケーションの設定オプションの概要。 +Nette Application の設定オプションの概要です。 Application @@ -10,62 +10,64 @@ Application ```neon application: - # Tracy BlueScreenに「Nette Application」パネルを表示しますか? - debugger: ... # (bool) デフォルトは true + # Tracy の BlueScreen に「Nette Application」パネルを表示しますか? + debugger: ... # (bool) Tracy が使えれば有効 - # エラー時に error-presenter を呼び出しますか? - # 開発モードでのみ効果があります - catchExceptions: ... # (bool) デフォルトは true + # 本番では例外は常に error-presenter が扱います。 + # このオプションは開発モードでもその振る舞いを有効にするだけです + catchExceptions: ... # (bool) 既定は false。つまり開発では無効、本番では常に有効 # error-presenter の名前 - errorPresenter: Error # (string|array) デフォルトは 'Nette:Error' + errorPresenter: Error # (string|array) 既定は 'Nette:Error' - # Presenterとアクションのエイリアスを定義します + # プレゼンターとアクションの別名を定義します aliases: ... - # Presenter名をクラスに変換するルールを定義します + # プレゼンター名をクラスに変換する規則を定義します mapping: ... - # 不正なリンクは警告を生成しませんか? - # 開発モードでのみ効果があります - silentLinks: ... # (bool) デフォルトは false + # 不正なリンクの警告を抑制しますか? + # 開発モードでのみ有効です + silentLinks: ... # (bool) 既定は false ``` -`nette/application` バージョン 3.2 以降、エラープレゼンターのペアを定義できます。 +`nette/application` のバージョン 3.2 以降、エラー用のプレゼンターを 2 つ定義できます。 ```neon application: errorPresenter: - 4xx: Error4xx # Nette\Application\BadRequestException 例外用 - 5xx: Error5xx # その他の例外用 + 4xx: Error4xx # Nette\Application\BadRequestException 用 + 5xx: Error5xx # そのほかの例外用 ``` -`silentLinks` オプションは、リンク生成が失敗した場合(例えば、Presenterが存在しないためなど)に、開発モードでNetteがどのように動作するかを決定します。デフォルト値 `false` は、Netteが `E_USER_WARNING` エラーをスローすることを意味します。`true` に設定すると、このエラーメッセージが抑制されます。本番環境では、`E_USER_WARNING` は常に発生します。この動作は、Presenter変数 [$invalidLinkMode |creating-links#不正なリンク] を設定することでも制御できます。 +分けると便利なのは、この 2 つの状況が根本的に異なるからです。`BadRequestException`(4xx のコード)は、アプリケーションは正常で、訪問者が存在しないものを求めただけであることを意味します。ですからサイトのレイアウトの中で親切なメッセージを出す、機能の揃ったプレゼンターを使えます。逆に 5xx のエラーは、アプリケーションの何かが壊れていて、それが何かは分からないことを意味します。5xx のプレゼンターは、描画中にほかの何かが失敗しないよう、できるだけ最小限に保ってください。理想としては、データベースにもレイアウトにもログイン中のユーザーにも触れないことです。 -[エイリアスは、頻繁に使用されるPresenterへのリンクを簡略化 |creating-links#エイリアス]します。 +`silentLinks` オプションは、開発モードでリンクの生成が失敗したとき(たとえばプレゼンターが存在しないときなど)に Nette がどう振る舞うかを決めます。既定値の `false` は、Nette が `E_USER_WARNING` のエラーを出すことを意味します。`true` にするとこのエラーメッセージが抑制されます。本番環境では常に `E_USER_WARNING` が出ます。この振る舞いは、プレゼンターの変数 [$invalidLinkMode |creating-links#不正なリンク]の設定でも変えられます。 -[マッピングは、Presenter名からクラス名を導出するルールを定義 |directory-structure#Presenterのマッピング]します。 +[別名を使うと |creating-links#別名]、よく使うプレゼンターを簡単に参照できます。 +[mapping は規則を定義し |directory-structure#プレゼンターのマッピング]、プレゼンター名からクラス名が導かれます。 -Presenterの自動登録 --------------- -NetteはPresenterをサービスとしてDIコンテナに自動的に追加し、これによりPresenterの作成が大幅に高速化されます。NetteがPresenterをどのように検索するかは設定可能です。 +プレゼンターの自動登録 +----------- + +Nette はプレゼンターをサービスとして DI コンテナに自動的に追加し、その生成を大きく速くします。Nette がプレゼンターをどう探すかは設定できます。 ```neon application: - # ComposerクラスマップでPresenterを検索しますか? - scanComposer: ... # (bool) デフォルトは true + # Composer のクラスマップでプレゼンターを探しますか? + scanComposer: ... # (bool) 既定は true - # クラス名とファイル名が一致する必要があるマスク - scanFilter: ... # (string) デフォルトは '*Presenter' + # クラス名とファイル名が一致すべきマスク + scanFilter: ... # (string) 既定は '*Presenter' - # どのディレクトリでPresenterを検索しますか? - scanDirs: # (string[]|false) デフォルトは '%appDir%' + # どのディレクトリでプレゼンターを探しますか? + scanDirs: # (string[]|false) 既定は '%appDir%' - %vendorDir%/mymodule ``` -`scanDirs` にリストされたディレクトリは、デフォルト値 `%appDir%` を上書きするのではなく、補完します。したがって、`scanDirs` には `%appDir%` と `%vendorDir%/mymodule` の両方のパスが含まれます。デフォルトのディレクトリを除外したい場合は、値を上書きする[感嘆符 |dependency-injection:configuration#マージ]を使用します。 +`scanDirs` に並べたディレクトリは既定値 `%appDir%` を上書きせず、それを補うので、`scanDirs` には `%appDir%` と `%vendorDir%/mymodule` の両方のパスが入ります。既定のディレクトリを外したい場合は[感嘆符 |dependency-injection:configuration#統合]を使います。 ```neon application: @@ -73,36 +75,42 @@ application: - %vendorDir%/mymodule ``` -ディレクトリのスキャンは、false値を指定することで無効にできます。Presenterの自動追加を完全に抑制することはお勧めしません。そうしないと、アプリケーションのパフォーマンスが低下します。 +値を `false` にすると、ディレクトリの走査を無効にできます。するとプレゼンターはサービスとして登録されなくなるので、[decorator |dependency-injection:configuration#Decorator]セクションで調整できなくなり、生成も遅くなります。ですから自動登録を完全に抑えることはおすすめしません。アプリケーションの性能が落ちるからです。 Latte テンプレート ============ -この設定により、コンポーネントとPresenterにおけるLatteの動作をグローバルに影響させることができます。 +この設定は、コンポーネントとプレゼンターにおける Latte の振る舞いに全体として影響します。 ```neon latte: - # メインテンプレート(true)またはすべてのコンポーネント(all)に対してTracy BarにLatteパネルを表示しますか? - debugger: ... # (true|false|'all') デフォルトは true + # Tracy バーに Latte パネルを、主テンプレートについて表示(true)、それとも全コンポーネントについて(all)? + debugger: ... # (true|false|'all') Tracy が使えれば有効(デバッグモードのみ) + + # declare(strict_types=1) のヘッダー付きでテンプレートを生成します + strictTypes: ... # (bool) 既定は false + + # [厳格なパーサーモード |latte:develop#strict mode]を有効にします + strictParsing: ... # (bool) 既定は false - # declare(strict_types=1) ヘッダーを持つテンプレートを生成します - strictTypes: ... # (bool) デフォルトは false + # 変数のスコープをループ本体に限定します + scopedLoopVariables: ... # (bool) 既定は false - # [厳密なパーサーモード |latte:develop#striktní režim]を有効にします - strictParsing: ... # (bool) デフォルトは false + # ペアタグの入れ子によるインデントを取り除きます + dedent: ... # (bool) 既定は false - # [生成されたコードのチェック |latte:develop#Kontrola vygenerovaného kódu]を有効にします - phpLinter: ... # (string) デフォルトは null + # [生成されたコードのチェック |latte:develop#Checking Generated Code]を有効にします + phpLinter: ... # (string) 既定は null # ロケールを設定します - locale: cs_CZ # (string) デフォルトは null + locale: cs_CZ # (string) 既定は null # $this->template オブジェクトのクラス - templateClass: App\MyTemplateClass # デフォルトは Nette\Bridges\ApplicationLatte\DefaultTemplate + templateClass: App\MyTemplateClass # 既定は Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Latteバージョン3を使用している場合は、次のようにして新しい[拡張機能 |latte:extending-latte#Latte Extension]を追加できます。 +新しい[拡張 |latte:extending-latte#Latte Extension]は次のように追加できます。 ```neon latte: @@ -110,36 +118,22 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Latteバージョン2を使用している場合は、クラス名を指定するか、サービスへの参照を指定することで、新しいタグ(マクロ)を登録できます。デフォルトでは `install()` メソッドが呼び出されますが、別のメソッド名を指定することで変更できます。 - -```neon -latte: - # カスタムLatteタグの登録 - macros: - - App\MyLatteMacros::register # 静的メソッド、クラス名またはcallable - - @App\MyLatteMacrosFactory # install() メソッドを持つサービス - - @App\MyLatteMacrosFactory::register # register() メソッドを持つサービス - -services: - - App\MyLatteMacrosFactory -``` - ルーティング ====== -基本設定: +基本の設定です。 ```neon routing: - # Tracy Barにルーティングパネルを表示しますか? - debugger: ... # (bool) デフォルトは true + # Tracy バーにルーティングのパネルを表示しますか? + debugger: ... # (bool) Tracy が使えれば有効(デバッグモードのみ) - # ルーターをDIコンテナにシリアライズします - cache: ... # (bool) デフォルトは false + # ルーターを DI コンテナにシリアライズします + cache: ... # (bool) 既定は false ``` -ルーティングは通常、[RouterFactory |routing#ルートコレクション]クラスで定義します。あるいは、`maska: akce` のペアを使用して設定でルートを定義することもできますが、この方法では設定の柔軟性がそれほど高くありません。 +ルーティングはふつう [RouterFactory |routing#ルートのコレクション]クラスで定義します。あるいは `mask: action` の組を使って設定でルートを定義することもできますが、この方法はあまり柔軟ではありません。 ```neon routing: @@ -150,25 +144,25 @@ routing: 定数 -========= +=== -PHP定数の作成。 +PHP の定数を作ります。 ```neon constants: Foobar: 'baz' ``` -アプリケーション起動後、`Foobar` 定数が作成されます。 +`Foobar` 定数はアプリケーションの起動後に作られます。 .[note] -定数は、グローバルにアクセス可能な変数として使用すべきではありません。オブジェクトに値を渡すには、[依存関係注入 |dependency-injection:passing-dependencies]を使用してください。 +定数はグローバルに使える変数の代わりにすべきではありません。オブジェクトに値を渡すには[依存性注入 |dependency-injection:passing-dependencies]を使ってください。 PHP === -PHPディレクティブの設定。すべてのディレクティブの概要は[php.net |https://www.php.net/manual/en/ini.list.php]にあります。 +PHP のディレクティブの設定です。すべてのディレクティブの一覧は [php.net |https://www.php.net/manual/en/ini.list.php]にあります。 ```neon php: @@ -179,13 +173,14 @@ php: DI サービス ======= -これらのサービスはDIコンテナに追加されます。 - -| 名前 | 型 | 説明 -|---------------------------------------------------------- -| `application.application` | [api:Nette\Application\Application] | [アプリケーション全体の起動 |how-it-works#Nette Application] -| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] -| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | Presenter のファクトリ -| `application.###` | [api:Nette\Application\UI\Presenter] | 個々の Presenter -| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | `Latte\Engine` オブジェクトのファクトリ -| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | [`$this->template` |templates] のファクトリ +次のサービスが DI コンテナに追加されます。 + +| 名前 | 型 | 説明 +|----------------------------|---------------------------------------------------|----------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [アプリケーションの実行役 |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\IPresenterFactory] | プレゼンターのファクトリ +| `application.###` | [api:Nette\Application\UI\Presenter] | 個々のプレゼンター +| `routing.router` | [api:Nette\Routing\Router] | ルーター +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | `Latte\Engine` オブジェクトのファクトリ +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | [`$this->template` |templates]のファクトリ diff --git a/application/ja/creating-links.texy b/application/ja/creating-links.texy index 9b86073140..d08068f318 100644 --- a/application/ja/creating-links.texy +++ b/application/ja/creating-links.texy @@ -1,156 +1,173 @@ -URLリンクの作成 -********* +URL リンクの作成 +**********
    -Netteでリンクを作成するのは、指をさすのと同じくらい簡単です。指し示すだけで、フレームワークがすべての作業を代行します。ここでは以下について説明します。 +Nette でのリンクの作成は、指を差すのと同じくらい簡単です。狙いを定めるだけで、あとはフレームワークが全部やってくれます。ここでは次のことを扱います。 -- テンプレートやその他の場所でリンクを作成する方法 -- 現在のページへのリンクを区別する方法 -- 不正なリンクの対処法 +- テンプレートやそのほかの場所でリンクを作る方法 +- 現在のページへのリンクを見分ける方法 +- 不正なリンクへの対処
    -[双方向ルーティング |routing]のおかげで、後で変更される可能性のあるアプリケーションのURLをテンプレートやコードにハードコーディングしたり、複雑に組み立てたりする必要は決してありません。リンクでPresenterとアクションを指定し、必要に応じてパラメータを渡すだけで、フレームワークがURLを自動的に生成します。実際には、関数を呼び出すのと非常によく似ています。これは気に入るはずです。 +[双方向のルーティング |routing]のおかげで、あとで変わるかもしれない、あるいは組み立てが面倒なアプリケーションの URL を、テンプレートやコードに直接書き込む必要はもうありません。リンクにはプレゼンターとアクションを指定し、必要なパラメータを渡すだけで、URL はフレームワークが生成します。実のところ、関数を呼ぶのとよく似ています。きっと気に入るでしょう。 -Presenterテンプレート内 -================ +プレゼンターのテンプレートで +============== -最も頻繁にリンクを作成するのはテンプレートであり、`n:href` 属性は素晴らしいヘルパーです。 +リンクを作るのはたいていテンプレートの中で、そこでは `n:href` 属性が頼もしい助けになります。 ```latte 詳細 ``` -HTML属性 `href` の代わりに、[n:属性 |latte:syntax#n:属性] `n:href` を使用していることに注意してください。その値は、`href` 属性の場合のようにURLではなく、Presenterとアクションの名前です。 +HTML の属性 `href` の代わりに [n:属性 |latte:syntax#n:属性]の `n:href` を使っていることに注目してください。その値は、`href` 属性の場合のような URL ではなく、プレゼンターとアクションの名前です。 -リンクをクリックすることは、簡単に言えば、`ProductPresenter::renderShow()` メソッドを呼び出すようなものです。そして、そのシグネチャにパラメータがある場合は、引数を付けて呼び出すことができます。 +リンクをクリックすることは、簡単にいえば `ProductPresenter::renderShow()` メソッドを呼ぶようなものです。そしてそのシグネチャにパラメータがあれば、引数を付けて呼べます。 ```latte -製品詳細 +商品の詳細 ``` -名前付きパラメータを渡すことも可能です。次のリンクは、値 `cs` を持つ `lang` パラメータを渡します。 +名前付きパラメータも渡せます。次のリンクは、値 `en` を持つパラメータ `lang` を渡します。 ```latte -製品詳細 +商品の詳細 ``` -`ProductPresenter::renderShow()` メソッドがそのシグネチャに `$lang` を持っていない場合、`$lang = $this->getParameter('lang')` を使用してパラメータの値を取得するか、[プロパティ |presenters#リクエストパラメータ]から取得できます。 +`ProductPresenter::renderShow()` メソッドのシグネチャに `$lang` がなければ、`$lang = $this->getParameter('lang')` か[プロパティ |presenters#リクエストのパラメータ]でその値を取得できます。 -パラメータが配列に格納されている場合、`...` 演算子(Latte 2.xでは `(expand)` 演算子)を使用して展開できます。 +パラメータが配列に入っているなら、`...` 演算子で展開できます。 ```latte -{var $args = [$product->id, lang => cs]} -製品詳細 +{var $args = [$product->id, lang => en]} +商品の詳細 ``` -リンクでは、いわゆる[パーシステントパラメータ |presenters#パーシステントパラメータ]も自動的に渡されます。 +いわゆる[永続パラメータ |presenters#永続パラメータ]もリンクに自動的に渡されます。 -`n:href` 属性はHTMLタグ `` に非常に便利です。リンクを他の場所、例えばテキスト内に出力したい場合は、`{link}` を使用します。 +`n:href` 属性は HTML の `` タグにとても便利です。リンクをそれ以外の場所、たとえば文章の中に出したい場合は `{link}` を使います。 ```latte -アドレスは: {link Home:default} +URL は次のとおりです: {link Home:default} ``` -コード内 -==== +コードの中で +====== -Presenterでリンクを作成するには、`link()` メソッドを使用します。 +プレゼンターでリンクを作るには `link()` メソッドを使います。 ```php $url = $this->link('Product:show', $product->id); ``` -パラメータは、名前付きパラメータを含めることができる配列を使用して渡すこともできます。 +パラメータは配列としても渡せ、その中で名前付きパラメータも指定できます。 ```php -$url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$product->id, 'lang' => 'en']); ``` -リンクはPresenterなしでも作成できます。そのために[#LinkGenerator]とその `link()` メソッドがあります。 +リンクは [#LinkGenerator]とその `link()` メソッドを使えば、プレゼンターなしでも作れます。 +ときには、いまリンクを作りつつ、実際の URL の生成はあとにしたいことがあります。そのために `lazyLink()` メソッドがあり、`Nette\Application\UI\Link` オブジェクトを返します。利点は、このオブジェクトをたとえばテンプレートへ渡し、描かれる前に `setParameter()` メソッドでそのパラメータをまだ調整できることです。URL そのものは、オブジェクトが文字列に変換されるときにはじめて組み立てられます。 -Presenterへのリンク -============== +```php +$link = $this->lazyLink('Product:show', $id); +// ... +echo $link; // URL はここではじめて生成されます +``` -リンクのターゲットがPresenterとアクションの場合、構文は次のようになります。 + +プレゼンターへのリンク +=========== + +リンクの行き先がプレゼンターとアクションなら、次の構文になります。 ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -この形式は、すべてのLatteタグと、リンクを扱うすべてのPresenterメソッド(`n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`、および[#LinkGenerator])でサポートされています。したがって、例で `n:href` が使用されていても、これらの関数のいずれかを使用できます。 +この形式は、すべての Latte のタグと、リンクを扱うすべてのプレゼンターのメソッド、つまり `n:href`、`{link}`、`{plink}`、`link()`、`lazyLink()`、`isLinkCurrent()`、`redirect()`、`redirectPermanent()`、`forward()`、`canonicalize()`、そして [#LinkGenerator]で使えます。ですから例で `n:href` を使っていても、そこにはこれらのどの関数が来てもかまいません。 -したがって、基本形式は `Presenter:action` です。 +基本の形は `Presenter:action` です。 ```latte -ホームページ +トップページ ``` -現在のPresenterのアクションにリンクする場合、その名前を省略できます。 +現在のプレゼンターのアクションへリンクするなら、その名前は省けます。 ```latte -ホームページ +トップページ ``` -ターゲットが `default` アクションの場合、省略できますが、コロンは残す必要があります。 +行き先のアクションが `default` なら省けますが、コロンは残さなければなりません。 ```latte -ホームページ +トップページ ``` -リンクは他の[モジュール |directory-structure#Presenterとテンプレート]にも向かうことができます。ここでは、リンクはネストされたサブモジュールへの相対リンク、または絶対リンクに区別されます。原理はディスク上のパスに似ていますが、スラッシュの代わりにコロンが使用されます。現在のPresenterが `Front` モジュールの一部であると仮定すると、次のように記述します。 +リンクはほかの[モジュール |directory-structure#プレゼンターとテンプレート]も指せます。ここでは、入れ子のサブモジュールからの相対と、絶対とが区別されます。原理はディスクのパスと同じで、スラッシュの代わりにコロンを使うだけです。現在のプレゼンターが `Front` モジュールの一部だとすると、次のように書きます。 ```latte Front:Shop:Product:show へのリンク Admin:Product:show へのリンク ``` -特別なケースは、[自分自身へのリンク |#現在のページへのリンク]で、ターゲットとして `this` を指定します。 +特別な場合が[自分自身への |#現在のページへのリンク]リンクで、行き先に `this` を指定します。 ```latte 更新 ``` -ハッシュマーク `#` の後のいわゆるフラグメントを介して、ページの特定の部分にリンクできます。 +ハッシュ記号 `#` のあとのいわゆるフラグメントで、ページの特定の部分を指せます。 ```latte Home:default とフラグメント #main へのリンク ``` +.{data-version:3.3.0} +フラグメントは `#` のキーを持つ引数として動的に設定することもできます。その値は自動的にエンコードされ、行き先に指定されたフラグメントより優先されます。 + +```php +$this->link('Home:default', ['#' => $fragment]); +``` + 絶対パス ==== -`link()` または `n:href` を使用して生成されたリンクは常に絶対パス(つまり、`/` 文字で始まる)ですが、`https://domain` のようなプロトコルとドメインを持つ絶対URLではありません。 +`link()` や `n:href` で生成されるリンクは常に絶対パス(つまり `/` で始まるもの)ですが、`https://domain` のようにプロトコルとドメインを含む絶対 URL ではありません。 + +絶対 URL を生成するには、先頭にスラッシュを 2 つ足します(たとえば `n:href="//Home:"`)。あるいは `$this->absoluteUrls = true` を設定して、プレゼンターが絶対リンクだけを生成するように切り替えられます。 -絶対URLを生成するには、先頭に2つのスラッシュを追加します(例:`n:href="//Home:"`)。または、`$this->absoluteUrls = true` を設定して、Presenterが絶対リンクのみを生成するように切り替えることもできます。 +テンプレートでは `|absoluteUrl` フィルタを使って、相対パスを絶対パスに変換することもできます。 現在のページへのリンク =========== -ターゲット `this` は現在のページへのリンクを作成します。 +行き先 `this` は現在のページへのリンクを作ります。 ```latte 更新 ``` -同時に、`action()` または `render()` メソッドのシグネチャで指定されたすべてのパラメータも転送されます(`action()` が定義されていない場合)。したがって、`Product:show` ページで `id: 123` の場合、`this` へのリンクもこのパラメータを渡します。 +同時に、`action()` または(`action()` が定義されていなければ)`render()` メソッドのシグネチャに書かれたすべてのパラメータが引き継がれます。ですから `id: 123` で `Product:show` のページにいるなら、`this` へのリンクもこのパラメータを渡します。 -もちろん、パラメータを直接指定することも可能です。 +もちろん、パラメータを直接指定することもできます。 ```latte 更新 ``` -`isLinkCurrent()` 関数は、リンクのターゲットが現在のページと同じかどうかを判断します。これは、例えばテンプレートでリンクを区別するためなどに使用できます。 +`isLinkCurrent()` 関数は、リンクの行き先が現在のページと同じかどうかを調べます。たとえばテンプレートでリンクを見分けるのに使えます。 -パラメータは `link()` メソッドと同じですが、さらに特定のアクションの代わりにワイルドカード `*` を指定できます。これは、そのPresenterの任意のアクションを意味します。 +パラメータは `link()` メソッドと同じですが、具体的なアクションの代わりにワイルドカード `*` も使えます。これは指定したプレゼンターの任意のアクションを意味します。 ```latte {if !isLinkCurrent('Admin:login')} @@ -162,15 +179,15 @@ Presenterへのリンク
  • ``` -1つの要素で `n:href` と組み合わせる場合、短縮形を使用できます。 +ひとつの要素の中で `n:href` と組み合わせる場合は、短い形が使えます。 ```latte ... ``` -ワイルドカード `*` はアクションの代わりにのみ使用でき、Presenterの代わりには使用できません。 +ワイルドカード `*` はアクションの代わりにだけ使え、プレゼンターの代わりには使えません。 -特定のモジュールまたはそのサブモジュールにいるかどうかを判断するには、`isModuleCurrent(moduleName)` メソッドを使用します。 +特定のモジュールやそのサブモジュールにいるかどうかを判定するには、`isModuleCurrent(moduleName)` メソッドを使います。 ```latte
  • @@ -179,56 +196,72 @@ Presenterへのリンク ``` +リンクの基準の変更 .{data-version:3.2.7} +=============================== + +既定では、相対リンクは現在のプレゼンターから導かれます。これは `{linkBase}` で変えられます。 + +```latte +{linkBase Admin:Dashboard} +商品の詳細 +``` + +このリンクは `Admin:Dashboard:Product:show` に向かいます。影響を受けるのは相対リンクだけで、コロンで始まる絶対リンクと現在のプレゼンターへのリンク(`this`、`show`)は変わりません。 + +`{linkBase}` はテンプレート全体に適用され、とくにレイアウトのテンプレートで役立ちます。呼び出し元のプレゼンターに関係なく、一貫したリンクを保証してくれるからです。 +このタグはテンプレートの先頭に置かなければならず、さもないと `CompileException` を投げます。 + + シグナルへのリンク ========= -リンクのターゲットはPresenterとアクションだけでなく、[シグナル |components#シグナル](`handle()` メソッドを呼び出す)にすることもできます。その場合、構文は次のようになります。 +リンクの行き先はプレゼンターとアクションだけでなく、[シグナル |components#シグナル]でもかまいません(`handle()` メソッドを呼びます)。その場合の構文は次のとおりです。 ``` [//] [sub-component:]signal! [#fragment] ``` -したがって、シグナルは感嘆符で区別されます。 +シグナルは感嘆符で見分けられます。 ```latte シグナル ``` -サブコンポーネント(またはサブサブコンポーネント)のシグナルへのリンクを作成することもできます。 +サブコンポーネント(やそのサブコンポーネント)のシグナルへのリンクも作れます。 ```latte シグナル ``` -コンポーネント内のリンク -============ +コンポーネントの中のリンク +============= -[コンポーネント|components]は独立した再利用可能なユニットであり、周囲のPresenterへの依存関係を持つべきではないため、リンクはここで少し異なります。Latte属性 `n:href` とタグ `{link}`、および `link()` などのコンポーネントメソッドは、リンクのターゲットを**常にシグナル名と見なします**。したがって、感嘆符を指定する必要さえありません。 +[コンポーネント|components]は、周りのプレゼンターと何のつながりも持つべきでない独立した再利用可能な部品なので、ここではリンクの働きが少し違います。Latte の属性 `n:href` とタグ `{link}`、そしてコンポーネントの `link()` などのメソッドは、**リンクの行き先を常にシグナル名とみなします**。ですから感嘆符を付ける必要さえありません。 ```latte アクションではなくシグナル ``` -コンポーネントテンプレートでPresenterにリンクしたい場合は、`{plink}` タグを使用します。 +コンポーネントのテンプレートからプレゼンターへリンクしたい場合は、`{plink}` タグを使います。 ```latte -ホーム +トップ ``` -またはコードで +あるいはコードの中で ```php $this->getPresenter()->link('Home:default') ``` -エイリアス .{data-version:v3.2.2} -============================ +別名 .{data-version:3.2.3} +======================== -Presenter:アクションのペアに覚えやすいエイリアスを割り当てると便利な場合があります。例えば、ホームページ `Front:Home:default` を単に `home` と名付けたり、`Admin:Dashboard:default` を `admin` と名付けたりします。 +Presenter:action の組に、覚えやすい別名を割り当てると便利なことがあります。たとえばトップページの `Front:Home:default` を単に `home`、`Admin:Dashboard:default` を `admin` と呼ぶ、といった具合です。 -エイリアスは、[設定|configuration]の `application › aliases` キーの下で定義されます。 +別名は[設定|configuration]の `application › aliases` キーで定義します。 ```neon application: @@ -238,26 +271,26 @@ application: sign: Front:Sign:in ``` -リンクでは、アットマークを使用して記述されます。例えば: +リンクの中ではアットマークを付けて書きます。たとえば次のようにです。 ```latte -管理 +管理画面 ``` -これらは、`redirect()` などのリンクを扱うすべてのメソッドでもサポートされています。 +`redirect()` など、リンクを扱うすべてのメソッドでも使えます。 不正なリンク ====== -存在しないPresenterにつながる、ターゲットメソッドがシグネチャで受け入れるよりも多くのパラメータを渡す、またはターゲットアクションのURLを生成できないなどの理由で、不正なリンクを作成することがあります。不正なリンクをどのように処理するかは、静的変数 `Presenter::$invalidLinkMode` によって決定されます。これは、これらの値(定数)の組み合わせを取ることができます。 +不正なリンクを作ってしまうことがあります。存在しないプレゼンターを指している、行き先のメソッドのシグネチャが受け取るより多くのパラメータを渡している、あるいは行き先のアクションに対して URL を生成できない場合です。不正なリンクをどう扱うかは、プレゼンターの `$this->invalidLinkMode` で設定します。次の値(定数)の組み合わせを取れます。 -- `Presenter::InvalidLinkSilent` - サイレントモード、URLとして `#` 文字が返されます -- `Presenter::InvalidLinkWarning` - E_USER_WARNING 警告がスローされ、本番モードではログに記録されますが、スクリプトの実行は中断されません -- `Presenter::InvalidLinkTextual` - 視覚的な警告、エラーをリンクに直接出力します -- `Presenter::InvalidLinkException` - InvalidLinkException 例外がスローされます +- `Presenter::InvalidLinkSilent` - 静かなモード。URL として文字 # を返します +- `Presenter::InvalidLinkWarning` - E_USER_WARNING の警告が出ます。本番モードでは記録されますが、スクリプトの実行は止まりません +- `Presenter::InvalidLinkTextual` - 目に見える警告。エラーをリンクに直接書き出します +- `Presenter::InvalidLinkException` - InvalidLinkException を投げます -デフォルト設定は、本番モードでは `InvalidLinkWarning`、開発モードでは `InvalidLinkWarning | InvalidLinkTextual` です。本番環境での `InvalidLinkWarning` はスクリプトの実行を中断しませんが、警告はログに記録されます。開発環境では、[Tracy |tracy:]によってキャッチされ、ブルースクリーンが表示されます。`InvalidLinkTextual` は、`#error:` 文字で始まるエラーメッセージをURLとして返すように機能します。そのようなリンクを一目でわかるようにするには、CSSに以下を追加します。 +既定の設定は、本番モードでは `InvalidLinkWarning`、開発モードでは `InvalidLinkWarning | InvalidLinkTextual` です。本番環境の `InvalidLinkWarning` はスクリプトを止めませんが、警告は記録されます。開発環境では [Tracy |tracy:]がそれを捕まえてブルースクリーンを表示します。`InvalidLinkTextual` は、`#error:` の文字で始まるエラーメッセージを URL として返すことで働きます。そうしたリンクを一目で見つけられるよう、CSS に次を足してください。 ```css a[href^="#error:"] { @@ -266,7 +299,7 @@ a[href^="#error:"] { } ``` -開発環境で警告が生成されないようにしたい場合は、[設定|configuration]で直接サイレントモードを設定できます。 +開発環境で警告を出したくない場合は、[設定|configuration]で直接抑制できます。 ```neon application: @@ -277,10 +310,10 @@ application: LinkGenerator ============= -Presenterが存在しない場合に、`link()` メソッドと同様の快適さでリンクを作成するにはどうすればよいでしょうか?そのために[api:Nette\Application\LinkGenerator]があります。 +`link()` メソッドと同じ快適さで、しかしプレゼンターなしでリンクを作るにはどうすればよいでしょうか。そのためにあるのが [api:Nette\Application\LinkGenerator]です。 -LinkGeneratorは、コンストラクタ経由で渡してもらい、その後その `link()` メソッドを使用してリンクを作成できるサービスです。 +LinkGenerator はサービスで、コンストラクタで受け取り、その `link()` メソッドでリンクを作れます。 -Presenterとの違いがあります。LinkGeneratorはすべてのリンクを直接絶対URLとして作成します。さらに、「現在のPresenter」は存在しないため、ターゲットとしてアクション名 `link('default')` だけを指定したり、モジュールへの相対パスを指定したりすることはできません。 +プレゼンターとの違いがひとつあります。LinkGenerator はすべてのリンクを絶対 URL として直接作ります。さらに「現在のプレゼンター」がないので、行き先にアクション名だけを指定する `link('default')` はできませんし、モジュールへの相対パスも使えません。 -不正なリンクは常に `Nette\Application\UI\InvalidLinkException` をスローします。 +不正なリンクは常に `Nette\Application\UI\InvalidLinkException` を投げます。 diff --git a/application/ja/directory-structure.texy b/application/ja/directory-structure.texy index 5ad882a036..ad5c9b5e19 100644 --- a/application/ja/directory-structure.texy +++ b/application/ja/directory-structure.texy @@ -1,45 +1,45 @@ -アプリケーションのディレクトリ構造 +アプリケーションのディレクトリ構成 *****************
    -Nette Frameworkプロジェクトのために、明確でスケーラブルなディレクトリ構造をどのように設計すればよいでしょうか?コードの整理に役立つベストプラクティスを紹介します。以下について学びます。 +Nette Framework のプロジェクトで、見通しがよく拡張しやすいディレクトリ構成をどう設計すればよいでしょうか。コードの整理に役立つ実績ある作法を紹介します。次のことを学びます。 -- アプリケーションをディレクトリに**論理的に分割**する方法 -- プロジェクトの成長に合わせて**うまくスケール**するように構造を設計する方法 -- **可能な代替案**とその利点または欠点 +- アプリケーションをディレクトリに**論理的に構造化する**方法 +- プロジェクトの成長に**うまく追随する**構成の設計 +- **考えられる代替案**とその長所・短所
    -Nette Framework自体は特定の構造に固執しないことを言及することが重要です。あらゆるニーズや好みに簡単に適応できるように設計されています。 +まず大事なこととして、Nette Framework 自体は特定の構成を強制しません。どんな必要や好みにも簡単に合わせられるよう設計されています。 -プロジェクトの基本構造 +プロジェクトの基本構成 =========== -Nette Frameworkは固定のディレクトリ構造を指示しませんが、[Web Project|https://github.com/nette/web-project]の形で実証済みのデフォルトの配置があります。 +Nette Framework は決まったディレクトリ構成を押し付けませんが、[Web Project|https://github.com/nette/web-project]という形で実績のある既定の配置があります。 /--pre web-project/ -├── app/ ← アプリケーションディレクトリ -├── assets/ ← SCSS、JS、画像ファイルなど、代替として resources/ -├── bin/ ← コマンドラインスクリプト +├── app/ ← アプリケーションのディレクトリ +├── assets/ ← SCSS、JS ファイル、画像など。あるいは resources/ +├── bin/ ← コマンドライン用のスクリプト ├── config/ ← 設定 -├── log/ ← ログ記録されたエラー +├── log/ ← 記録されたエラー ├── temp/ ← 一時ファイル、キャッシュ ├── tests/ ← テスト -├── vendor/ ← Composerによってインストールされたライブラリ -└── www/ ← 公開ディレクトリ (document-root) +├── vendor/ ← Composer がインストールしたライブラリ +└── www/ ← 公開ディレクトリ(document-root) \-- -この構造は、ニーズに応じて自由に調整できます。フォルダの名前を変更したり、移動したりできます。その後、`Bootstrap.php` ファイルと、場合によっては `composer.json` のディレクトリへの相対パスを更新するだけです。それ以上のことは必要ありません。複雑な再設定や定数の変更は不要です。Netteは賢い自動検出機能を備えており、URLベースを含むアプリケーションの場所を自動的に認識します。 +この構成は必要に応じて自由に変えられます。フォルダの名前を変えたり移動したりできます。あとは `Bootstrap.php` と、必要なら `composer.json` のディレクトリへの相対パスを調整するだけです。それ以上は何も要りません。複雑な設定のやり直しも、定数の変更も不要です。Nette には賢い自動検出があり、アプリケーションの場所とその基準 URL を自動的に認識します。 -コード整理の原則 -======== +コードを整理する原則 +========== -新しいプロジェクトを初めて調べるときは、すぐに慣れることができるはずです。`app/Model/` ディレクトリを開いて、この構造を見ると想像してみてください。 +新しいプロジェクトをはじめて見るとき、素早く見当が付くべきです。`app/Model/` ディレクトリをクリックして、次の構成が現れたと想像してください。 /--pre app/Model/ @@ -48,9 +48,9 @@ Nette Frameworkは固定のディレクトリ構造を指示しませんが、[W └── Entities/ \-- -これから読み取れるのは、プロジェクトがいくつかのサービス、リポジトリ、エンティティを使用していることだけです。アプリケーションの実際の目的については何もわかりません。 +ここから分かるのは、このプロジェクトが何らかのサービス、リポジトリ、エンティティを使っているということだけです。アプリケーションが実際に何をするのかは何も分かりません。 -別のアプローチを見てみましょう - **ドメインによる整理**: +別のやり方、**ドメインによる整理**を見てみましょう。 /--pre app/Model/ @@ -60,76 +60,76 @@ Nette Frameworkは固定のディレクトリ構造を指示しませんが、[W └── Product/ \-- -ここでは違います - 一目でeコマースサイトであることがわかります。ディレクトリ名自体が、アプリケーションができること、つまり支払い、注文、製品を扱うことを示しています。 +こちらは違います。一目でネットショップだと分かります。ディレクトリの名前そのものが、アプリケーションに何ができるかを教えてくれます。支払い、注文、商品を扱うのだ、と。 -最初のアプローチ(クラスタイプによる整理)は、実際には多くの問題を引き起こします。論理的に関連するコードが異なるフォルダに分散され、それらの間を行き来する必要があります。したがって、ドメインごとに整理します。 +ひとつめのやり方(クラスの種類による整理)は、実務でいくつかの問題を生みます。論理的に関係するコードが別々のフォルダに散らばり、そのあいだを行き来しなければなりません。ですからドメインで整理します。 名前空間 ---- -ディレクトリ構造がアプリケーションの名前空間に対応するのが慣例です。つまり、ファイルの物理的な場所がその名前空間に対応します。例えば、`app/Model/Product/ProductRepository.php` に配置されたクラスは、`App\Model\Product` 名前空間を持つべきです。この原則は、コードの理解を助け、オートローディングを簡素化します。 +ディレクトリ構成をアプリケーションの名前空間に対応させるのが慣習です。つまりファイルの物理的な場所が、その名前空間と一致するということです。たとえば `app/Model/Product/ProductRepository.php` にあるクラスは、名前空間 `App\Model\Product` を持つべきです。この原則はコードを辿るのを助け、オートローディングを単純にします。 -名前の単数形 vs 複数形 -------------- +名前の単数形と複数形 +---------- -アプリケーションのメインディレクトリでは単数形を使用していることに注意してください:`app`, `config`, `log`, `temp`, `www`。同様に、アプリケーション内部でも:`Model`, `Core`, `Presentation`。これは、それぞれが1つのまとまった概念を表しているためです。 +アプリケーションの主要なディレクトリには単数形を使っていることに注目してください。`app`、`config`、`log`、`temp`、`www` です。アプリケーションの中でも同じで、`Model`、`Core`、`Presentation` です。それぞれがひとつのまとまった概念を表しているからです。 -同様に、例えば `app/Model/Product` は製品に関するすべてを表します。`Products` とは呼びません。なぜなら、それは製品でいっぱいのフォルダではないからです(そこには `nokia.php`, `samsung.php` のようなファイルがあるでしょう)。それは、製品を扱うクラス、つまり `ProductRepository.php`, `ProductService.php` を含む名前空間です。 +同じく `app/Model/Product` は、商品に関わるすべてを表します。`Products` と呼ばないのは、商品が詰まったフォルダ(それなら `nokia.php`、`samsung.php` のようなファイルが入るでしょう)ではないからです。商品を扱うクラス、`ProductRepository.php`、`ProductService.php` を含む名前空間なのです。 -`app/Tasks` フォルダは複数形です。なぜなら、それは独立した実行可能なスクリプトのセット、つまり `CleanupTask.php`, `ImportTask.php` を含んでいるからです。それぞれが独立したユニットです。 +`app/Tasks` フォルダが複数形なのは、独立した実行可能なスクリプトの集まり、`CleanupTask.php`、`ImportTask.php` を含むからです。そのひとつひとつが独立した単位です。 -一貫性のために、以下を使用することをお勧めします。 -- 機能的な全体を表す名前空間には単数形(複数のエンティティを扱う場合でも) -- 独立したユニットのコレクションには複数形 -- 不確かな場合、またはそれについて考えたくない場合は、単数形を選択してください +一貫性のために、次をおすすめします。 +- 機能のまとまりを表す名前空間には単数形(複数のエンティティを扱う場合でも) +- 独立した単位の集まりには複数形 +- 迷ったとき、あるいは考えたくないときは単数形 公開ディレクトリ `www/` =============== -このディレクトリは、Webからアクセスできる唯一のディレクトリ(いわゆるdocument-root)です。`www/` の代わりに `public/` という名前をよく見かけることもありますが、これは単なる慣例の問題であり、機能には影響しません。ディレクトリには以下が含まれます。 -- アプリケーションの[エントリポイント |bootstrapping#index.php] `index.php` -- mod_rewrite(Apacheの場合)のルールを含む `.htaccess` ファイル +このディレクトリだけがウェブからアクセスできます(document-root です)。`www/` の代わりに `public/` という名前もよく見かけますが、これは慣習の問題にすぎず、アプリケーションの動作には影響しません。このディレクトリには次のものが入ります。 +- アプリケーションの[入口 |bootstrapping#index.php] `index.php` +- mod_rewrite の規則を含む `.htaccess` ファイル(Apache 用) - 静的ファイル(CSS、JavaScript、画像) - アップロードされたファイル -アプリケーションの適切なセキュリティのためには、[document-rootを正しく設定 |nette:troubleshooting#URLから www ディレクトリを変更または削除する方法は]することが不可欠です。 +アプリケーションを適切に守るには、[document-root が正しく設定されている |nette:troubleshooting#URL から www のディレクトリを変えたり取り除いたりするには]ことが決定的に重要です。 .[note] -このディレクトリに `node_modules/` フォルダを決して配置しないでください。実行可能であり、公開すべきではない数千のファイルが含まれています。 +`node_modules/` フォルダをこのディレクトリに置いては決していけません。実行可能かもしれない何千ものファイルが入っていて、公開すべきではありません。 -アプリケーションディレクトリ `app/` -===================== +アプリケーションのディレクトリ `app/` +====================== -これはアプリケーションコードを含むメインディレクトリです。基本構造: +アプリケーションのコードを含む主要なディレクトリです。基本の構成は次のとおりです。 /--pre app/ -├── Core/ ← インフラストラクチャ関連 +├── Core/ ← インフラに関わる事柄 ├── Model/ ← ビジネスロジック -├── Presentation/ ← Presenterとテンプレート -├── Tasks/ ← コマンドスクリプト -└── Bootstrap.php ← アプリケーションのブートストラップクラス +├── Presentation/ ← プレゼンターとテンプレート +├── Tasks/ ← コマンドのスクリプト +└── Bootstrap.php ← アプリケーションの起動クラス \-- -`Bootstrap.php` は、環境を初期化し、設定をロードし、DIコンテナを作成する[アプリケーションの起動クラス|bootstrapping]です。 +`Bootstrap.php` は[アプリケーションの起動クラス|bootstrapping]で、環境を初期化し、設定を読み込み、DI コンテナを作ります。 -次に、個々のサブディレクトリについて詳しく見ていきましょう。 +では個々のサブディレクトリを詳しく見ていきましょう。 -Presenterとテンプレート -================ +プレゼンターとテンプレート +============= -アプリケーションのプレゼンテーション部分は `app/Presentation` ディレクトリにあります。代替案は短い `app/UI` です。これは、すべてのPresenter、そのテンプレート、および可能なヘルパークラスのための場所です。 +アプリケーションのプレゼンテーション層は `app/Presentation` ディレクトリにあります。短い `app/UI` でもかまいません。ここがすべてのプレゼンター、そのテンプレート、そして関連する補助クラスの置き場です。 -このレイヤーをドメインごとに整理します。eコマース、ブログ、APIを組み合わせた複雑なプロジェクトでは、構造は次のようになります。 +この層はドメインで整理します。ネットショップ、ブログ、API を組み合わせた複雑なプロジェクトなら、構成は次のようになります。 /--pre app/Presentation/ -├── Shop/ ← eコマースフロントエンド +├── Shop/ ← ネットショップのフロント │ ├── Product/ │ ├── Cart/ │ └── Order/ @@ -139,15 +139,15 @@ Presenterとテンプレート ├── Admin/ ← 管理画面 │ ├── Dashboard/ │ └── Products/ -└── Api/ ← APIエンドポイント +└── Api/ ← API のエンドポイント └── V1/ \-- -一方、単純なブログでは、次のような分割を使用します。 +逆に単純なブログなら、次の構成を使います。 /--pre app/Presentation/ -├── Front/ ← Webフロントエンド +├── Front/ ← サイトのフロント │ ├── Home/ │ └── Post/ ├── Admin/ ← 管理画面 @@ -157,17 +157,17 @@ Presenterとテンプレート └── Export/ ← RSS、サイトマップなど \-- -`Home/` や `Dashboard/` のようなフォルダには、Presenterとテンプレートが含まれます。`Front/`, `Admin/`, `Api/` のようなフォルダは**モジュール**と呼ばれます。技術的には、これらはアプリケーションを論理的に分割するために使用される通常のディレクトリです。 +`Home/` や `Dashboard/` のようなフォルダには、プレゼンターとテンプレートが入ります。`Front/`、`Admin/`、`Api/` のようなフォルダは**モジュール**と呼ばれます。技術的には、アプリケーションを論理的に分けるために使うふつうのディレクトリです。 -Presenterを含む各フォルダには、同じ名前のPresenterとそのテンプレートが含まれます。例えば、`Dashboard/` フォルダには以下が含まれます。 +プレゼンターを含む各フォルダには、プレゼンターのファイル自体とそのテンプレートが入ります。たとえば `Dashboard/` フォルダには次のものが入ります。 /--pre Dashboard/ -├── DashboardPresenter.php ← Presenter +├── DashboardPresenter.php ← プレゼンター └── default.latte ← テンプレート \-- -このディレクトリ構造は、クラスの名前空間に反映されます。例えば、`DashboardPresenter` は `App\Presentation\Admin\Dashboard` 名前空間に配置されます([#Presenterのマッピング]を参照)。 +このディレクトリ構成はクラスの名前空間に反映されます。たとえば `DashboardPresenter` は `App\Presentation\Admin\Dashboard` 名前空間にあります([#プレゼンターのマッピング]をご覧ください)。 ```php namespace App\Presentation\Admin\Dashboard; @@ -178,22 +178,22 @@ class DashboardPresenter extends Nette\Application\UI\Presenter } ``` -`Admin` モジュール内の `Dashboard` Presenterには、アプリケーション内でコロン表記を使用して `Admin:Dashboard` として参照します。その `default` アクションには `Admin:Dashboard:default` として参照します。ネストされたモジュールの場合、複数のコロンを使用します。例えば `Shop:Order:Detail:default` です。 +`Admin` モジュールの中の `Dashboard` プレゼンターは、アプリケーションの中でコロン記法を使って `Admin:Dashboard` と参照します。その `default` アクションは `Admin:Dashboard:default` です。入れ子のモジュールでは、コロンを複数使います。たとえば `Shop:Order:Detail:default` です。 -構造の柔軟な開発 +構成の柔軟な発展 -------- -この構造の大きな利点の1つは、プロジェクトの成長するニーズにエレガントに適応する方法です。例として、XMLフィードを生成する部分を取り上げましょう。最初は単純な形式です。 +この構成の大きな利点のひとつが、プロジェクトの膨らむ要求に優雅に追随できることです。例として XML のフィードを生成する部分を取り上げましょう。最初は単純な形です。 /--pre Export/ -├── ExportPresenter.php ← すべてのエクスポート用の単一Presenter -├── sitemap.latte ← サイトマップ用テンプレート -└── feed.latte ← RSSフィード用テンプレート +├── ExportPresenter.php ← すべてのエクスポート用のプレゼンター 1 つ +├── sitemap.latte ← サイトマップのテンプレート +└── feed.latte ← RSS フィードのテンプレート \-- -時間が経つにつれて、さらに多くのフィードタイプが追加され、それらに対してより多くのロジックが必要になります... 問題ありません!`Export/` フォルダは簡単にモジュールになります。 +やがてフィードの種類が増え、それらのためにもっとロジックが必要になります……。問題ありません。`Export/` フォルダをそのままモジュールにするだけです。 /--pre Export/ @@ -202,58 +202,58 @@ class DashboardPresenter extends Nette\Application\UI\Presenter │ └── sitemap.latte └── Feed/ ├── FeedPresenter.php - ├── zbozi.latte ← Zboží.cz用フィード - └── heureka.latte ← Heureka.cz用フィード + ├── amazon.latte ← Amazon 用のフィード + └── ebay.latte ← eBay 用のフィード \-- -この変換は完全にスムーズです - 新しいサブフォルダを作成し、コードをそれらに分割し、リンクを更新するだけです(例:`Export:feed` から `Export:Feed:zbozi` へ)。これにより、必要に応じて構造を徐々に拡張でき、ネストのレベルに制限はありません。 +この変身はまったく滑らかです。新しいサブフォルダを作り、コードをそこに分け、リンクを更新する(たとえば `Export:feed` から `Export:Feed:amazon` へ)だけです。おかげで必要に応じて構成を少しずつ広げられ、入れ子の深さにも制限はありません。 -例えば、管理画面で注文管理に関連する多くのPresenter(`OrderDetail`, `OrderEdit`, `OrderDispatch` など)がある場合、より良い整理のために、この場所に `Order` モジュール(フォルダ)を作成できます。そこにはPresenter `Detail`, `Edit`, `Dispatch` などの(フォルダ)が含まれます。 +たとえば管理画面に、`OrderDetail`、`OrderEdit`、`OrderDispatch` など注文管理に関わるプレゼンターがたくさんあるなら、整理のために `Order` という名前のモジュール(フォルダ)を作り、そこに `Detail`、`Edit`、`Dispatch` などのプレゼンター(のフォルダ)を入れられます。 -テンプレートの配置 ---------- +テンプレートの置き場 +---------- -前の例では、テンプレートがPresenterと同じフォルダに直接配置されていることを見ました。 +これまでの例では、テンプレートがプレゼンターと同じフォルダに置かれていました。 /--pre Dashboard/ -├── DashboardPresenter.php ← Presenter -├── DashboardTemplate.php ← テンプレート用のオプションクラス +├── DashboardPresenter.php ← プレゼンター +├── DashboardTemplate.php ← 任意のテンプレートクラス └── default.latte ← テンプレート \-- -この配置は、実際には最も便利であることが証明されています - すべての関連ファイルがすぐに手元にあります。 +実務ではこの置き場が最も便利だと分かっています。関係するファイルがすべてすぐ手元にあるからです。 -あるいは、テンプレートを `templates/` サブフォルダに配置することもできます。Netteは両方のバリアントをサポートしています。テンプレートを `Presentation/` フォルダの外に完全に配置することもできます。テンプレートの配置オプションに関するすべての情報は、[テンプレートの検索 |templates#テンプレートの検索]の章にあります。 +あるいはテンプレートを `templates/` のサブフォルダに置くこともできます。Nette はどちらの形にも対応しています。テンプレートを `Presentation/` フォルダの完全に外に置くことさえできます。テンプレートの置き場の選択肢については、[テンプレートの探索 |templates#テンプレートの探索]の章にすべてあります。 -ヘルパークラスとコンポーネント ---------------- +補助クラスとコンポーネント +------------- -Presenterとテンプレートには、しばしば他のヘルパーファイルも伴います。それらをその適用範囲に応じて論理的に配置します。 +プレゼンターとテンプレートには、ほかの補助ファイルが伴うことがよくあります。それらは適用範囲に応じて論理的に置きます。 -1. **Presenterのすぐ隣**、特定のPresenter用の特定のコンポーネントの場合: +1. そのプレゼンター固有のコンポーネントなら、**プレゼンターと同じ場所に**: /--pre Product/ ├── ProductPresenter.php -├── ProductGrid.php ← 製品リスト用コンポーネント -└── FilterForm.php ← フィルタリング用フォーム +├── ProductGrid.php ← 商品一覧のコンポーネント +└── FilterForm.php ← 絞り込みのフォーム \-- -2. **モジュール用** - アルファベット順の先頭に明確に配置される `Accessory` フォルダを使用することをお勧めします。 +2. **モジュール用** - `Accessory` フォルダの利用をおすすめします。アルファベット順で都合よく先頭に来ます。 /--pre Front/ ├── Accessory/ -│ ├── NavbarControl.php ← フロントエンド用コンポーネント +│ ├── NavbarControl.php ← フロント用のコンポーネント │ └── TemplateFilters.php ├── Product/ └── Cart/ \-- -3. **アプリケーション全体用** - `Presentation/Accessory/` 内: +3. **アプリケーション全体用** - `Presentation/Accessory/` に: /--pre app/Presentation/ ├── Accessory/ @@ -263,30 +263,30 @@ Presenterとテンプレートには、しばしば他のヘルパーファイ └── Admin/ \-- -または、`LatteExtension.php` や `TemplateFilters.php` のようなヘルパークラスをインフラストラクチャフォルダ `app/Core/Latte/` に配置することもできます。そして、コンポーネントを `app/Components` に配置します。選択はチームの慣習によります。 +あるいは `LatteExtension.php` や `TemplateFilters.php` のような補助クラスを、インフラのフォルダ `app/Core/Latte/` に置くこともできます。コンポーネントは `app/Components` に。選び方はチームの慣習によります。 -モデル - アプリケーションの心臓部 -================== +Model - アプリケーションの心臓 +=================== -モデルには、アプリケーションのすべてのビジネスロジックが含まれています。その整理には、再びルールが適用されます - ドメインごとに構造化します。 +モデルには、アプリケーションのビジネスロジックがすべて入ります。その整理の規則もやはり、ドメインによる構造化です。 /--pre app/Model/ -├── Payment/ ← 支払いに関するすべて -│ ├── PaymentFacade.php ← メインエントリポイント +├── Payment/ ← 支払いに関わるすべて +│ ├── PaymentFacade.php ← 主な入口 │ ├── PaymentRepository.php │ ├── Payment.php ← エンティティ -├── Order/ ← 注文に関するすべて +├── Order/ ← 注文に関わるすべて │ ├── OrderFacade.php │ ├── OrderRepository.php │ ├── Order.php -└── Shipping/ ← 配送に関するすべて +└── Shipping/ ← 配送に関わるすべて \-- -モデルでは、通常、これらのタイプのクラスに遭遇します。 +モデルでは、ふつう次の種類のクラスに出会います。 -**ファサード**: アプリケーション内の特定のドメインへのメインエントリポイントを表します。完全なユースケース(「注文を作成する」や「支払いを処理する」など)を実装するために、異なるサービス間の協力を調整するオーケストレーターとして機能します。オーケストレーションレイヤーの下で、ファサードは実装の詳細をアプリケーションの他の部分から隠し、特定のドメインを扱うためのクリーンなインターフェースを提供します。 +**ファサード**: アプリケーションの中の特定のドメインへの主な入口を表します。まとめ役として働き、さまざまなサービスの協調を取り持って、「注文を作る」「支払いを処理する」といったユースケースをまるごと実現します。ファサードはその取りまとめの層の下に実装の詳細を隠し、アプリケーションのほかの部分に対して、そのドメインを扱うためのきれいなインターフェースを提供します。 ```php class OrderFacade @@ -295,25 +295,25 @@ class OrderFacade { // 検証 // 注文の作成 - // 電子メールの送信 + // メールの送信 // 統計への書き込み } } ``` -**サービス**: ドメイン内の特定のビジネス操作に焦点を当てます。ユースケース全体をオーケストレーションするファサードとは異なり、サービスは特定のビジネスロジック(価格計算や支払い処理など)を実装します。サービスは通常ステートレスであり、より複雑な操作のための構成要素としてファサードによって使用されるか、より単純なタスクのためにアプリケーションの他の部分によって直接使用されることができます。 +**サービス**: ドメインの中の特定のビジネス上の操作に集中します。ユースケース全体を取りまとめるファサードと違い、サービスは具体的なビジネスロジック(価格の計算や支払いの処理など)を実装します。サービスはふつう状態を持たず、より複雑な操作の部品としてファサードから使われることも、もっと単純な用途でアプリケーションのほかの部分から直接使われることもあります。 ```php class PricingService { public function calculateTotal(Order $order): Money { - // 価格計算 + // 価格の計算 } } ``` -**リポジトリ**: データストレージ、通常はデータベースとのすべての通信を保証します。そのタスクは、エンティティのロードと保存、およびそれらを検索するためのメソッドの実装です。リポジトリは、アプリケーションの他の部分をデータベースの実装の詳細から分離し、データを扱うためのオブジェクト指向インターフェースを提供します。 +**リポジトリ**: データの保管場所、ふつうはデータベースとのやり取りをすべて担います。その役目はエンティティを読み書きし、それを探すメソッドを提供することです。リポジトリはアプリケーションのほかの部分をデータベースの実装の詳細から守り、データを扱うためのオブジェクト指向のインターフェースを提供します。 ```php class OrderRepository @@ -328,10 +328,10 @@ class OrderRepository } ``` -**エンティティ**: アプリケーションの主要なビジネスコンセプトを表すオブジェクトで、独自のアイデンティティを持ち、時間とともに変化します。通常、これらはORM(Nette Database ExplorerやDoctrineなど)を使用してデータベーステーブルにマッピングされるクラスです。エンティティは、そのデータに関するビジネスルールと検証ロジックを含むことができます。 +**エンティティ**: アプリケーションの主要なビジネス上の概念を表すオブジェクトで、自分の同一性を持ち、時とともに変化します。ふつうは ORM(Nette Database Explorer や Doctrine など)でデータベースのテーブルに対応づけられたクラスです。エンティティは、そのデータに関わるビジネスの規則や検証のロジックを持てます。 ```php -// orders データベーステーブルにマッピングされたエンティティ +// 'orders' データベーステーブルに対応づけられたエンティティ class Order extends Nette\Database\Table\ActiveRow { public function addItem(Product $product, int $quantity): void @@ -345,32 +345,32 @@ class Order extends Nette\Database\Table\ActiveRow } ``` -**値オブジェクト**: 独自のアイデンティティを持たない値を表す不変オブジェクト - 例えば、金額や電子メールアドレス。同じ値を持つ値オブジェクトの2つのインスタンスは同一と見なされます。 +**値オブジェクト**: 自分の同一性を持たない値、たとえば金額やメールアドレスを表す不変のオブジェクトです。同じ値を持つ 2 つの値オブジェクトのインスタンスは、同一とみなされます。 -インフラストラクチャコード -============= +インフラのコード +======== -`Core/` フォルダ(または `Infrastructure/`)は、アプリケーションの技術的な基盤のホームです。インフラストラクチャコードには通常、以下が含まれます。 +`Core/` フォルダ(あるいは `Infrastructure/`)は、アプリケーションの技術的な土台の置き場です。インフラのコードにはふつう次のものが含まれます。 /--pre app/Core/ -├── Router/ ← ルーティングとURL管理 +├── Router/ ← ルーティングと URL の管理 │ └── RouterFactory.php ├── Security/ ← 認証と認可 │ ├── Authenticator.php │ └── Authorizator.php -├── Logging/ ← ロギングと監視 +├── Logging/ ← ログ記録と監視 │ ├── SentryLogger.php │ └── FileLogger.php -├── Cache/ ← キャッシュレイヤー +├── Cache/ ← キャッシュの層 │ └── FullPageCache.php └── Integration/ ← 外部サービスとの統合 ├── Slack/ └── Stripe/ \-- -小規模なプロジェクトでは、もちろんフラットな分割で十分です。 +小さなプロジェクトなら、当然ながら平らな構成で十分です。 /--pre Core/ @@ -379,121 +379,121 @@ class Order extends Nette\Database\Table\ActiveRow └── QueueMailer.php \-- -これは次のようなコードです。 +ここに入るのは次のようなコードです。 -- 技術的なインフラストラクチャ(ルーティング、ロギング、キャッシュ)を扱います -- 外部サービス(Sentry、Elasticsearch、Redis)を統合します -- アプリケーション全体に基本的なサービス(メール、データベース)を提供します -- ほとんどの場合、特定のドメイン(製品、注文、記事)に依存しません - キャッシュやロガーはeコマースやブログで同じように機能します。 +- 技術的なインフラ(ルーティング、ログ記録、キャッシュ)を扱う +- 外部サービス(Sentry、Elasticsearch、Redis)と統合する +- アプリケーション全体に基本的なサービス(メール、データベース)を提供する +- 特定のドメインからはおおむね独立している。キャッシュやロガーは、ネットショップでもブログでも同じように働きます。 -特定のクラスがここに属するか、モデルに属するか迷っていますか?重要な違いは、`Core/` のコードは: +あるクラスがここに属するのか、モデルに属するのか迷いますか。決定的な違いは、`Core/` のコードが次の性質を持つことです。 -- ドメイン(製品、注文、記事)について何も知りません -- ほとんどの場合、別のプロジェクトに転送できます -- 「どのように機能するか」(メールを送信する方法)を扱い、「何をするか」(どのメールを送信するか)ではありません +- ドメイン(商品、注文、記事)について何も知らない +- たいてい別のプロジェクトに持っていける +- 「どう動くか」(メールをどう送るか)を解き、「何をするか」(どのメールを送るか)は解かない -よりよく理解するための例: +分かりやすくするための例です。 -- `App\Core\MailerFactory` - 電子メール送信用のクラスのインスタンスを作成し、SMTP設定を扱います -- `App\Model\OrderMailer` - `MailerFactory` を使用して注文に関する電子メールを送信し、そのテンプレートを知っており、いつ送信すべきかを知っています +- `App\Core\MailerFactory` - メールを送るクラスのインスタンスを作り、SMTP の設定を扱います +- `App\Model\OrderMailer` - `MailerFactory` を使って注文についてのメールを送り、そのテンプレートといつ送るべきかを知っています -コマンドスクリプト -========= +コマンドのスクリプト +========== -アプリケーションは、通常のHTTPリクエスト以外のアクティビティを実行する必要があることがよくあります - バックグラウンドでのデータ処理、メンテナンス、または定期的なタスクなどです。実行には `bin/` ディレクトリの単純なスクリプトが使用され、実装ロジック自体は `app/Tasks/`(または `app/Commands/`)に配置されます。 +アプリケーションは、通常の HTTP リクエストの外で処理を行う必要にしばしば迫られます。バックグラウンドのデータ処理、メンテナンス、定期的な仕事などです。実行には `bin/` ディレクトリの単純なスクリプトを使い、実装のロジック自体は `app/Tasks/`(あるいは `app/Commands/`)に置きます。 -例: +例を挙げます。 /--pre app/Tasks/ -├── Maintenance/ ← メンテナンススクリプト +├── Maintenance/ ← メンテナンスのスクリプト │ ├── CleanupCommand.php ← 古いデータの削除 │ └── DbOptimizeCommand.php ← データベースの最適化 ├── Integration/ ← 外部システムとの統合 -│ ├── ImportProducts.php ← サプライヤーシステムからのインポート +│ ├── ImportProducts.php ← 仕入先システムからの取り込み │ └── SyncOrders.php ← 注文の同期 -└── Scheduled/ ← 定期的なタスク +└── Scheduled/ ← 定期的な仕事 ├── NewsletterCommand.php ← ニュースレターの送信 └── ReminderCommand.php ← 顧客への通知 \-- -モデルに属するものとコマンドスクリプトに属するものは何ですか?例えば、1つの電子メールを送信するロジックはモデルの一部ですが、数千の電子メールの一括送信は `Tasks/` に属します。 +何がモデルに属し、何がコマンドのスクリプトに属するのでしょうか。たとえばメールを 1 通送るロジックはモデルの一部で、何千通ものメールの一括送信は `Tasks/` に属します。 -タスクは通常、[コマンドラインから実行 |https://blog.nette.org/en/cli-scripts-in-nette-application]されるか、cron経由で実行されます。HTTPリクエスト経由で実行することもできますが、セキュリティを考慮する必要があります。タスクを実行するPresenterは、例えばログインしたユーザーのみ、または強力なトークンと許可されたIPアドレスからのアクセスのみに保護する必要があります。長いタスクの場合、スクリプトのタイムアウト制限を増やし、セッションがロックされないように `session_write_close()` を使用する必要があります。 +タスクはふつうコマンドラインか cron から実行します。`bin/` のスクリプトが [bootConsoleApplication() |bootstrapping#さまざまな環境]メソッドで DI コンテナを作り、そこから必要なサービスを取り出します。HTTP のリクエストから実行することもできますが、安全性を考える必要があります。タスクを実行するプレゼンターは、たとえばログイン済みのユーザーだけに限る、あるいは強力なトークンと許可された IP アドレスからのアクセスに限る、といった形で守らなければなりません。長時間かかるタスクでは、スクリプトの時間制限を延ばし、セッションのロックを避けるために `session_write_close()` を使う必要があります。 -その他の可能なディレクトリ -============= +そのほかの考えられるディレクトリ +================ -前述の基本ディレクトリに加えて、プロジェクトのニーズに応じて他の特殊なフォルダを追加できます。最も一般的なものとその使用法を見てみましょう。 +ここまで挙げた基本のディレクトリのほかにも、プロジェクトの必要に応じて専用のフォルダを足せます。よくあるものとその用途を見てみましょう。 /--pre app/ -├── Api/ ← プレゼンテーションレイヤーに依存しないAPIロジック -├── Database/ ← テストデータ用のマイグレーションスクリプトとシーダー -├── Components/ ← アプリケーション全体で共有されるビジュアルコンポーネント -├── Event/ ← イベント駆動アーキテクチャを使用する場合に便利 -├── Mail/ ← 電子メールテンプレートと関連ロジック -└── Utils/ ← ヘルパークラス +├── Api/ ← プレゼンテーション層から独立した API のロジック +├── Database/ ← マイグレーションのスクリプトとテストデータの seeder +├── Components/ ← アプリケーション全体で共有する画面の部品 +├── Event/ ← イベント駆動のアーキテクチャを使うなら便利です +├── Mail/ ← メールのテンプレートと関連するロジック +└── Utils/ ← 補助クラス \-- -アプリケーション全体のPresenterで使用される共有ビジュアルコンポーネントには、`app/Components` または `app/Controls` フォルダを使用できます。 +アプリケーション全体のプレゼンターで使う共有の画面部品には、`app/Components` や `app/Controls` フォルダを使えます。 /--pre app/Components/ -├── Form/ ← 共有フォームコンポーネント +├── Form/ ← 共有のフォームの部品 │ ├── SignInForm.php │ └── UserForm.php -├── Grid/ ← データリスト用コンポーネント +├── Grid/ ← データ一覧の部品 │ └── DataGrid.php -└── Navigation/ ← ナビゲーション要素 +└── Navigation/ ← ナビゲーションの要素 ├── Breadcrumbs.php └── Menu.php \-- -ここには、より複雑なロジックを持つコンポーネントが属します。複数のプロジェクト間でコンポーネントを共有したい場合は、それらを別のComposerパッケージに分離することをお勧めします。 +ここには、より複雑なロジックを持つコンポーネントが属します。複数のプロジェクトでコンポーネントを共有したいなら、独立した Composer のパッケージに切り出すとよいでしょう。 -`app/Mail` ディレクトリに電子メール通信の管理を配置できます。 +`app/Mail` ディレクトリには、メールでのやり取りの管理を置けます。 /--pre app/Mail/ -├── templates/ ← 電子メールテンプレート +├── templates/ ← メールのテンプレート │ ├── order-confirmation.latte │ └── welcome.latte └── OrderMailer.php \-- -Presenterのマッピング -=============== +プレゼンターのマッピング +============ -マッピングは、Presenter名からクラス名を導出するためのルールを定義します。これらは[設定|configuration]の `application › mapping` キーの下で指定します。 +マッピングは、プレゼンター名からクラス名を導く規則を定めます。[設定|configuration]の `application › mapping` キーで指定します。 -このページでは、Presenterを `app/Presentation` フォルダ(または `app/UI`)に配置することを示しました。この慣例をNetteに設定ファイルで伝える必要があります。1行で十分です。 +このページでは、プレゼンターを `app/Presentation` フォルダ(または `app/UI`)に置いてきました。Nette Application 3.3 以降、これは設定しなくてよい既定の慣習です。別の構成を使う場合や、マッピングを明示的に指定したい場合、既定の設定は次の行に相当します。 ```neon application: mapping: App\Presentation\*\**Presenter ``` -マッピングはどのように機能しますか?よりよく理解するために、まずモジュールなしのアプリケーションを想像してみましょう。Presenterクラスが `App\Presentation` 名前空間に属するようにし、Presenter `Home` がクラス `App\Presentation\HomePresenter` にマッピングされるようにしたいとします。これは、この設定で実現できます。 +マッピングはどう働くのでしょうか。分かりやすくするために、まずモジュールのないアプリケーションを考えましょう。プレゼンターのクラスを `App\Presentation` 名前空間の下に置き、`Home` プレゼンターが `App\Presentation\HomePresenter` クラスに対応するようにしたいとします。それは次の設定で実現できます。 ```neon application: mapping: App\Presentation\*Presenter ``` -マッピングは、Presenter名 `Home` がマスク `App\Presentation\*Presenter` のアスタリスクを置き換え、結果としてクラス名 `App\Presentation\HomePresenter` を得るように機能します。簡単です! +マッピングは、マスク `App\Presentation\*Presenter` のアスタリスクをプレゼンター名 `Home` に置き換えて働き、最終的なクラス名 `App\Presentation\HomePresenter` になります。簡単ですね。 -しかし、この章や他の章の例でわかるように、Presenterクラスを同名のサブディレクトリに配置します。例えば、Presenter `Home` はクラス `App\Presentation\Home\HomePresenter` にマッピングされます。これは、コロンを2重にすることで実現できます(Nette Application 3.2が必要)。 +とはいえ、この章やほかの章の例で見たとおり、私たちはプレゼンターのクラスを同じ名前のサブディレクトリに置きます。たとえば `Home` プレゼンターは `App\Presentation\Home\HomePresenter` クラスに対応します。これは二重のアスタリスク `**` で実現します(Nette Application 3.2.3 以上が必要です)。 ```neon application: mapping: App\Presentation\**Presenter ``` -次に、Presenterをモジュールにマッピングします。各モジュールに対して特定のマッピングを定義できます。 +次はプレゼンターをモジュールに対応づける話に進みます。モジュールごとに個別のマッピングを定義できます。 ```neon application: @@ -503,9 +503,9 @@ application: Api: App\Api\*Presenter ``` -この設定によると、Presenter `Front:Home` はクラス `App\Presentation\Front\Home\HomePresenter` にマッピングされ、Presenter `Api:OAuth` はクラス `App\Api\OAuthPresenter` にマッピングされます。 +この設定に従うと、プレゼンター `Front:Home` は `App\Presentation\Front\Home\HomePresenter` クラスに、プレゼンター `Api:OAuth` は `App\Api\OAuthPresenter` クラスに対応します。 -モジュール `Front` と `Admin` は同様のマッピング方法を持ち、そのようなモジュールはおそらくもっと多いため、それらを置き換える一般的なルールを作成することが可能です。したがって、クラスマスクにモジュール用の新しいアスタリスクが追加されます。 +`Front` と `Admin` のモジュールは似たマッピングの形をしていて、そうしたモジュールはこれからも増えそうなので、それらをまとめる一般的な規則を作れます。クラスのマスクにモジュール用の新しいアスタリスクを足します。 ```neon application: @@ -514,9 +514,9 @@ application: Api: App\Api\*Presenter ``` -これは、例えばPresenter `Admin:User:Edit` のような、より深くネストされたディレクトリ構造でも機能します。アスタリスクを持つセグメントは各レベルで繰り返され、結果はクラス `App\Presentation\Admin\User\Edit\EditPresenter` になります。 +これはより深く入れ子になったディレクトリ構成でも働きます。たとえばプレゼンター `Admin:User:Edit` では、アスタリスクの部分がモジュールの階層ごとに繰り返され、クラス `App\Presentation\Admin\User\Edit\EditPresenter` になります。 -代替の表記法は、文字列の代わりに3つのセグメントからなる配列を使用することです。この表記法は前のものと同等です。 +別の書き方として、文字列の代わりに 3 つの要素から成る配列を使えます。上に示した例について、この書き方は前のものと等価です。 ```neon application: diff --git a/application/ja/how-it-works.texy b/application/ja/how-it-works.texy index 5ba6dd8368..d7d78af3f6 100644 --- a/application/ja/how-it-works.texy +++ b/application/ja/how-it-works.texy @@ -1,103 +1,103 @@ -アプリケーションはどのように動作しますか? -********************* +アプリケーションはどう動くのか +***************
    -あなたは今、Netteドキュメントの基本憲章を読んでいます。Webアプリケーションがどのように機能するかの全体像を学びます。AからZまで、誕生の瞬間からPHPスクリプトの最後の処理まで。読み終えた後、あなたは知っているでしょう: +あなたは今、Nette ドキュメントの土台となる章を読んでいます。リクエストが生まれた瞬間から PHP スクリプトの実行が終わるまで、ウェブアプリケーションがどう動くのかを A から Z まで学びます。読み終わると次のことが分かります。 -- 全体がどのように機能するか -- Bootstrap、Presenter、DIコンテナとは何か -- ディレクトリ構造はどのようになっているか +- 全体がどう動くのか +- Bootstrap、プレゼンター、DI コンテナとは何か +- ディレクトリ構成はどんな形か
    -ディレクトリ構造 +ディレクトリ構成 ======== -[WebProject|https://github.com/nette/web-project]と呼ばれるWebアプリケーションのスケルトンの例を開き、読みながら言及されているファイルを見ることができます。 +[WebProject|https://github.com/nette/web-project]というウェブアプリケーションの雛形の例を開いてください。読みながら、話に出てくるファイルを参照できます。 -ディレクトリ構造は次のようになります。 +ディレクトリ構成はおおよそ次のようになっています。 /--pre web-project/ -├── app/ ← アプリケーションディレクトリ -│ ├── Core/ ← 実行に必要な基本クラス -│ │ └── RouterFactory.php ← URLアドレスの設定 -│ ├── Presentation/ ← Presenter、テンプレートなど -│ │ ├── @layout.latte ← レイアウトテンプレート -│ │ └── Home/ ← Home Presenterのディレクトリ -│ │ ├── HomePresenter.php ← Home Presenterクラス -│ │ └── default.latte ← defaultアクションのテンプレート -│ └── Bootstrap.php ← ブートストラップクラス Bootstrap -├── assets/ ←リソース(SCSS、TypeScript、ソース画像) -├── bin/ ← コマンドラインから実行されるスクリプト +├── app/ ← アプリケーションのディレクトリ +│ ├── Core/ ← 動作に必要な中核のクラス +│ │ └── RouterFactory.php ← URL アドレスの設定 +│ ├── Presentation/ ← プレゼンター、テンプレートなど +│ │ ├── @layout.latte ← レイアウトのテンプレート +│ │ └── Home/ ← Home プレゼンターのディレクトリ +│ │ ├── HomePresenter.php ← Home プレゼンターのクラス +│ │ └── default.latte ← default アクションのテンプレート +│ └── Bootstrap.php ← 起動クラス Bootstrap +├── assets/ ← リソース(SCSS、TypeScript、元画像) +├── bin/ ← コマンドラインから実行するスクリプト ├── config/ ← 設定ファイル │ ├── common.neon │ └── services.neon -├── log/ ← ログ記録されたエラー +├── log/ ← 記録されたエラー ├── temp/ ← 一時ファイル、キャッシュなど -├── vendor/ ← Composerによってインストールされたライブラリ +├── vendor/ ← Composer がインストールしたライブラリ │ ├── ... -│ └── autoload.php ← インストールされたすべてのパッケージのオートローディング -├── www/ ← 公開ディレクトリまたはプロジェクトのドキュメントルート -│ ├── assets/ ←コンパイルされた静的ファイル(CSS、JS、画像、...) -│ ├── .htaccess ← mod_rewriteルール -│ └── index.php ← アプリケーションが起動する最初のファイル -└── .htaccess ← www以外のすべてのディレクトリへのアクセスを禁止 +│ └── autoload.php ← インストールされた全パッケージのオートローディング +├── www/ ← 公開ディレクトリ。プロジェクトの document-root +│ ├── assets/ ← コンパイル済みの静的ファイル(CSS、JS、画像など) +│ ├── .htaccess ← mod_rewrite の規則 +│ └── index.php ← アプリケーションを起動する最初のファイル +└── .htaccess ← www 以外のすべてのディレクトリへのアクセスを禁じます \-- -ディレクトリ構造は自由に変更でき、フォルダの名前を変更したり移動したりできます。完全に柔軟です。さらに、Netteは賢い自動検出機能を備えており、URLベースを含むアプリケーションの場所を自動的に認識します。 +ディレクトリ構成は好きなように変えられ、フォルダの名前を変えたり移動したりできます。完全に柔軟です。Nette には賢い自動検出もあり、アプリケーションの場所とその URL の基準を自動的に認識します。 -少し大きなアプリケーションでは、Presenterとテンプレートのフォルダを[サブディレクトリに分割 |directory-structure#Presenterとテンプレート]し、クラスをモジュールと呼ばれる名前空間に分割できます。 +少し大きなアプリケーションでは、プレゼンターとテンプレートのフォルダを[サブディレクトリ |directory-structure#プレゼンターとテンプレート]にまとめ、クラスを名前空間でまとめられます。これをモジュールと呼びます。 -`www/` ディレクトリは、プロジェクトのいわゆる公開ディレクトリまたはドキュメントルートを表します。アプリケーション側で何も設定を変更することなく名前を変更できます。ただし、ドキュメントルートがこのディレクトリを指すように[ホスティングを設定 |nette:troubleshooting#URLから www ディレクトリを変更または削除する方法は]する必要があります。 +`www/` ディレクトリは、プロジェクトの公開ディレクトリ、つまり document-root にあたります。アプリケーション側でほかに何も設定せずに名前を変えられます。document-root がこのディレクトリを指すよう[ホスティングを設定する |nette:troubleshooting#URL から www のディレクトリを変えたり取り除いたりするには]だけです。 -WebProjectは、[Composer |best-practices:composer]を使用してNetteを含めて直接ダウンロードすることもできます。 +WebProject は Nette ごと [Composer |best-practices:composer]で直接ダウンロードすることもできます。 ```shell composer create-project nette/web-project ``` -LinuxまたはmacOSでは、`log/` および `temp/` ディレクトリに[書き込み権限を設定 |nette:troubleshooting#ディレクトリ権限の設定]してください。 +Linux や macOS では、`log/` と `temp/` ディレクトリに[書き込みの権限 |nette:troubleshooting#ディレクトリの権限の設定]を設定してください。 -WebProjectアプリケーションは実行準備ができており、何も設定する必要はなく、`www/` フォルダにアクセスしてブラウザですぐに表示できます。 +WebProject アプリケーションはすぐに動く状態です。何も設定する必要はなく、`www/` フォルダにアクセスすればブラウザでそのまま見られます。 -HTTPリクエスト -========= +HTTP リクエスト +========== -すべては、ユーザーがブラウザでページを開いたときに始まります。つまり、ブラウザがHTTPリクエストでサーバーにノックするときです。リクエストは、公開ディレクトリ `www/` にある単一のPHPファイル、つまり `index.php` に向けられます。アドレス `https://example.com/product/123` へのリクエストであるとしましょう。適切な[サーバー設定 |nette:troubleshooting#きれいなURLのためにサーバーを設定する方法は]のおかげで、このURLも `index.php` ファイルにマッピングされ、実行されます。 +すべては、ユーザーがブラウザでページを開いたときに始まります。ブラウザはサーバーに HTTP リクエストを送ります。このリクエストは、公開ディレクトリ `www/` にあるただひとつの PHP ファイル、`index.php` に向かいます。リクエストがアドレス `https://example.com/product/123` に対するものだとしましょう。適切な[サーバーの設定 |nette:troubleshooting#きれいな URL のためにサーバーを設定するには]のおかげで、この URL も `index.php` ファイルに割り当てられ、それが実行されます。 -そのタスクは次のとおりです。 +その役目は次のとおりです。 1) 環境を初期化する -2) ファクトリを取得する -3) リクエストを処理するNetteアプリケーションを起動する +2) ファクトリを手に入れる +3) リクエストを処理する Nette アプリケーションを実行する -どのファクトリですか?私たちはトラクターではなく、Webページを作成しています!お待ちください、すぐに説明します。 +ファクトリ? 私たちはトラクターを作っているのではなく、ウェブサイトを作っているのですが。ご心配なく、すぐに説明します。 -「環境の初期化」という言葉は、例えば[Tracy|tracy:]を有効にすることを意味します。これは、エラーのログ記録や視覚化のための素晴らしいツールです。本番サーバーではエラーをログに記録し、開発サーバーでは直接表示します。したがって、初期化には、Webが本番モードで実行されているか開発モードで実行されているかを判断することも含まれます。これを行うために、Netteは[賢い自動検出 |bootstrapping#開発環境 vs 本番環境]を使用します。Webをlocalhostで実行すると、開発モードで実行されます。したがって、何も設定する必要はなく、アプリケーションは開発と本番展開の両方にすぐに準備ができています。[Bootstrapクラス|bootstrapping]に関する章で、これらの手順が実行され、詳細に説明されています。 +「環境の初期化」とは、たとえば [Tracy|tracy:]を有効にすることです。Tracy はエラーの記録と可視化のための素晴らしい道具です。本番サーバーではエラーを記録し、開発環境では直接表示します。ですから初期化には、サイトが本番モードで動いているのか開発モードで動いているのかの判定も含まれます。Nette はそのために[賢い自動検出 |bootstrapping#開発モードと本番モード]を使います。localhost でサイトを動かせば開発モードになります。何も設定する必要はなく、アプリケーションは開発にも本番の公開にもすぐ対応できます。これらの手順は [Bootstrap クラス|bootstrapping]の章で実行され、詳しく説明されています。 -3番目のポイント(はい、2番目はスキップしましたが、戻ってきます)は、アプリケーションの起動です。Netteでは、HTTPリクエストの処理は `Nette\Application\Application` クラス(以下 `Application`)が担当します。したがって、アプリケーションを起動すると言うとき、具体的にはこのクラスのオブジェクトで `run()` という適切な名前のメソッドを呼び出すことを意味します。 +3 つめ(2 つめは飛ばしましたが、あとで戻ります)はアプリケーションの実行です。Nette では HTTP リクエストの処理を `Nette\Application\Application` クラス(以下 `Application`)が担当します。ですからアプリケーションを実行するとは、具体的にはこのクラスのオブジェクトで、その名も `run()` というメソッドを呼ぶことを指します。 -Netteは、実証済みの方法論に従ってクリーンなアプリケーションを作成するように導くメンターです。そして、それらの完全に実証済みの方法論の1つは、**dependency injection**、略してDIと呼ばれます。現時点ではDIの説明で負担をかけたくありません。それについては[別の章|dependency-injection:introduction]があります。重要な結果は、主要なオブジェクトは通常、**DIコンテナ**(略してDIC)と呼ばれるオブジェクトのファクトリによって作成されることです。はい、それが少し前に話したファクトリです。そして、それは `Application` オブジェクトも作成します。そのため、最初にコンテナが必要です。`Configurator` クラスを使用してそれを取得し、`Application` オブジェクトを作成させ、その上で `run()` メソッドを呼び出すことで、Netteアプリケーションが起動します。これはまさに[index.php |bootstrapping#index.php]ファイルで行われていることです。 +Nette は指導役として、実績のある方法論に沿ってきれいなアプリケーションを書くよう導いてくれます。その中でも最も確立されたもののひとつが**依存性注入**、略して DI です。ここで DI の説明で負担をかけるつもりはありません。それには[専用の章|dependency-injection:introduction]があります。肝心の帰結は、重要なオブジェクトがふつう **DI コンテナ**(DIC)と呼ばれるオブジェクトのファクトリによって作られるということです。そう、これが先ほど触れたファクトリです。`Application` オブジェクトもここが作ってくれるので、まずコンテナが必要になります。コンテナは `Configurator` クラスで手に入れ、`Application` オブジェクトを作らせ、その `run()` メソッドを呼ぶ。こうして Nette のアプリケーションが始まります。これがまさに [index.php |bootstrapping#index.php]ファイルで起きていることです。 Nette Application ================= -Applicationクラスには1つのタスクしかありません:HTTPリクエストに応答することです。 +`Application` クラスの役目はひとつだけです。HTTP リクエストに応えることです。 -Netteで書かれたアプリケーションは、多数のいわゆるPresenter(他のフレームワークではコントローラーという用語に出会うかもしれませんが、同じものです)に分割されます。これらは、Webサイトの特定のページ(ホームページ、eコマースの製品、ログインフォーム、サイトマップフィードなど)を表すクラスです。アプリケーションは1つから数千のPresenterを持つことができます。 +Nette で書かれたアプリケーションは、いわゆるプレゼンターの集まりに分かれています(ほかのフレームワークでは「コントローラ」という言葉に出会うかもしれませんが、本質的には同じものです)。プレゼンターはクラスで、それぞれがウェブサイトの特定のページを表します。トップページ、ネットショップの商品、ログインフォーム、サイトマップのフィードなどです。アプリケーションはプレゼンターをひとつから何千まで持てます。 -Applicationは、まずいわゆるルーターに、現在のリクエストを処理するためにどのPresenterに渡すかを決定するように依頼します。ルーターは、誰の責任かを決定します。入力URL `https://example.com/product/123` を見て、設定方法に基づいて、これが例えば `id: 123` の製品を表示する(`show`)**アクション**を要求する**Presenter** `Product` の仕事であると判断します。Presenter + アクションのペアは、`Product:show` のようにコロンで区切って記述するのが良い習慣です。 +`Application` はまず、いわゆるルーターに、現在のリクエストをどのプレゼンターが処理すべきかを決めさせます。ルーターが責任の所在を定めるのです。入力の URL `https://example.com/product/123` を調べ、その設定にもとづいて、この仕事はたとえば `Product` **プレゼンター**のもので、`id: 123` の商品について `show` **アクション**を行うべきだと決めます。プレゼンターとアクションの組はコロンで区切って `Product:show` と書くのが良い習慣です。 -したがって、ルーターはURLを `Presenter:action` + パラメータのペア、この場合は `Product:show` + `id: 123` に変換しました。そのようなルーターがどのように見えるかは、`app/Core/RouterFactory.php` ファイルで確認でき、[ルーティング |Routing]の章で詳細に説明されています。 +こうしてルーターは URL を `Presenter:action` の組とパラメータに変換しました。この場合は `Product:show` と `id: 123` です。そうしたルーターがどんな形かは `app/Core/RouterFactory.php` ファイルで見られますし、[ルーティング |Routing]の章で詳しく説明します。 -続けましょう。ApplicationはPresenterの名前を知っているので、次に進むことができます。`ProductPresenter` クラスのオブジェクトを作成します。これはPresenter `Product` のコードです。より正確には、Presenterを作成するようにDIコンテナに依頼します。なぜなら、作成するのはDIコンテナの仕事だからです。 +先へ進みましょう。`Application` はプレゼンターの名前を知ったので、次に進めます。`Product` プレゼンターのコードを含む `ProductPresenter` クラスのインスタンスを作るのです。より正確には、オブジェクトの生成はその役目なので、DI コンテナにプレゼンターを作らせます。 -Presenterは次のようになります。 +プレゼンターはたとえば次のような形になります。 ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -109,92 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // モデルからデータを取得し、テンプレートに渡します + // モデルからデータを取得してテンプレートに渡します $this->template->product = $this->repository->getProduct($id); } } ``` -リクエストの処理はPresenterが引き継ぎます。そして、タスクは明確です:`id: 123` でアクション `show` を実行します。Presenterの言葉で言えば、これは `renderShow()` メソッドが呼び出され、パラメータ `$id` で `123` を受け取ることを意味します。 +プレゼンターがリクエストの処理を引き継ぎます。仕事ははっきりしています。`id: 123` で `show` アクションを実行することです。プレゼンターの用語でいえば、`renderShow()` メソッドが呼ばれ、`$id` パラメータに `123` を受け取る、ということです。 -Presenterは複数のアクションを処理できます。つまり、複数の `render()` メソッドを持つことができます。ただし、1つまたはできるだけ少ないアクションを持つPresenterを設計することをお勧めします。 +プレゼンターは複数のアクションを扱えます。つまり `render()` メソッドを複数持てます。とはいえ、プレゼンターはアクションひとつ、あるいはできるだけ少ない数で設計することをおすすめします。 -したがって、`renderShow(123)` メソッドが呼び出されました。そのコードは架空の例ですが、`$this->template` への書き込みによってデータがテンプレートにどのように渡されるかを見ることができます。 +というわけで `renderShow(123)` メソッドが呼ばれました。そのコードは架空の例ですが、`$this->template` に書き込むことでデータがテンプレートに渡される様子を示しています。 -その後、Presenterは応答を返します。これは、HTMLページ、画像、XMLドキュメント、ディスクからのファイルの送信、JSON、または別のページへのリダイレクトなどです。重要なのは、明示的に応答方法を指示しない場合(これは `ProductPresenter` の場合です)、応答はHTMLページを含むテンプレートのレンダリングになることです。なぜですか?なぜなら、99%の場合、テンプレートをレンダリングしたいので、Presenterはこの動作をデフォルトと見なし、作業を楽にしたいからです。それがNetteの目的です。 +続いてプレゼンターはレスポンスを返します。HTML のページ、画像、XML のドキュメント、ディスクからのファイルの送信、JSON、あるいは別のページへのリダイレクトかもしれません。大事なのは、どう応えるかを明示的に指定しなければ(`ProductPresenter` の場合がそうです)、テンプレートを HTML のページに描くのがレスポンスになる、という点です。なぜでしょうか。99 % の場合、私たちはテンプレートを描きたいからです。ですからプレゼンターは、私たちの手間を省くためにこの振る舞いを既定としています。それが Nette の本質です。 -どのテンプレートをレンダリングするかを指定する必要さえありません。パスは自動的に推測されます。`show` アクションの場合、単に `ProductPresenter` クラスと同じディレクトリにある `show.latte` テンプレートをロードしようとします。また、`@layout.latte` ファイルでレイアウトを見つけようとします([テンプレートの検索 |templates#テンプレートの検索]について詳しくはこちら)。 +描くテンプレートを指定する必要さえありません。フレームワークがパスを自動的に導きます。`show` アクションの場合は単に、`ProductPresenter` クラスと同じディレクトリにある `show.latte` テンプレートを読み込もうとします。レイアウトも `@layout.latte` ファイルから探そうとします(詳しくは[テンプレートの探索 |templates#テンプレートの探索]をご覧ください)。 -そして、テンプレートがレンダリングされます。これでPresenterとアプリケーション全体のタスクが完了し、作業は完了です。テンプレートが存在しない場合、404エラーページが返されます。Presenterについて詳しくは、[Presenter|presenters]ページをご覧ください。 +そしてテンプレートが描かれます。これでプレゼンターの、そしてアプリケーション全体の仕事が終わります。テンプレートが存在しなければ 404 のエラーページが返ります。プレゼンターについて詳しくは[プレゼンター|presenters]のページで学べます。 [* request-flow.svg *] -念のため、少し異なるURLでプロセス全体を要約してみましょう。 +念のため、少し違う URL で全体の流れをおさらいしましょう。 -1) URLは `https://example.com` になります -2) アプリケーションを起動し、コンテナを作成し、`Application::run()` を実行します -3) ルーターはURLを `Home:default` ペアとしてデコードします -4) `HomePresenter` クラスのオブジェクトが作成されます -5) `renderDefault()` メソッドが呼び出されます(存在する場合) -6) 例えば `@layout.latte` のレイアウトを持つ `default.latte` などのテンプレートがレンダリングされます +1) URL は `https://example.com` +2) アプリケーションが起動し、DI コンテナが作られ、`Application::run()` が実行されます。 +3) ルーターが URL を `Home:default` の組に読み解きます。 +4) `HomePresenter` クラスのインスタンスが作られます。 +5) (存在すれば)`renderDefault()` メソッドが呼ばれます。 +6) テンプレート、たとえば `default.latte` が、レイアウト、たとえば `@layout.latte` とともに描かれます。 -おそらく、今、多くの新しい概念に出会ったかもしれませんが、それらが意味をなすことを願っています。Netteでのアプリケーション開発は非常に快適です。 +ここまでで新しい概念にたくさん出会ったかもしれませんが、どれも筋が通っていると感じてもらえたはずです。Nette でのアプリケーション開発は、驚くほど素直です。 テンプレート ====== -テンプレートについて言えば、Netteでは[Latte |latte:]テンプレートシステムが使用されます。そのため、テンプレートの拡張子は `.latte` です。Latteが使用される理由は、PHPで最も安全なテンプレートシステムであると同時に、最も直感的なシステムでもあるためです。多くの新しいことを学ぶ必要はありません。PHPの知識といくつかのタグで十分です。すべては[ドキュメント |templates]で学ぶことができます。 +テンプレートといえば、Nette は [Latte |latte:]というテンプレートシステムを使います。テンプレートのファイルの拡張子が `.latte` なのはそのためです。Latte を使う第一の理由は、PHP で最も安全なテンプレートシステムであり、しかも最も直感的だからです。新しく覚えることは多くありません。PHP の知識といくつかのタグで十分です。必要なことはすべて[ドキュメント |templates]にあります。 -テンプレートでは、他のPresenterとアクションへの[リンクが作成 |creating-links]されます。 +テンプレートでは、ほかのプレゼンターやアクションへの[リンクを作ります |creating-links]。次のようにです。 ```latte -製品詳細 +商品の詳細 ``` -単に実際のURLの代わりに、既知の `Presenter:action` ペアを記述し、必要に応じてパラメータを指定します。トリックは `n:href` にあり、これはこの属性がNetteによって処理されることを示します。そして、次のように生成します。 +実際の URL の代わりに、見慣れた `Presenter:action` の組を書き、必要なパラメータを添えるだけです。仕掛けは `n:href` にあり、これがこの属性を処理するよう Nette に伝えます。すると次が生成されます。 ```latte -製品詳細 +商品の詳細 ``` -URLの生成は、前述のルーターが担当します。Netteのルーターが例外的なのは、URLからPresenter:アクションのペアへの変換だけでなく、逆方向、つまりPresenter名+アクション+パラメータからURLを生成することもできることです。 これにより、NetteではテンプレートやPresenterの文字を1つも変更することなく、完成したアプリケーション全体のURL形式を完全に変更できます。ルーターを編集するだけで。 また、これにより、いわゆるカノニカル化が可能になります。これはNetteのもう1つのユニークな機能であり、異なるURLで重複コンテンツが存在するのを自動的に防ぐことで、より良いSEO(インターネットでの検索エンジンの最適化)に貢献します。 多くのプログラマーはこれを驚くべきことだと考えています。 +URL の生成は、先ほどのルーターが担当します。Nette のルーターが並外れているのは、URL から `Presenter:action` の組への変換だけでなく、その逆、つまりプレゼンター名、アクション、パラメータから URL を生成することもできる点です。おかげで、完成した Nette のアプリケーション全体の URL の形式を、テンプレートやプレゼンターの文字を 1 つも変えずに、ルーターを直すだけで丸ごと変えられます。これはいわゆる正規化も可能にします。同じ内容が異なる URL に存在するのを自動的に防いで SEO を高める、Nette ならではのもうひとつの機能です。多くのプログラマーがこの能力に驚きます。 -インタラクティブコンポーネント -=============== +インタラクティブなコンポーネント +================ -Presenterについてもう1つお伝えしなければならないことがあります:それらには組み込みのコンポーネントシステムがあります。DelphiやASP.NET Web Formsを知っている古い世代の方々には馴染みがあるかもしれません。ReactやVue.jsも、遠いながらも似たようなものに基づいています。PHPフレームワークの世界では、これは完全にユニークな機能です。 +プレゼンターについてもうひとつお伝えすることがあります。プレゼンターには組み込みのコンポーネントのしくみがあります。経験のある方は Delphi や ASP.NET Web Forms の似たものを思い出すかもしれません。React や Vue.js も、いくらか近い考え方の上に築かれています。PHP のフレームワークの世界では、これはまったく他にない機能です。 -コンポーネントは、ページ(つまりPresenter)に挿入する独立した再利用可能なユニットです。[フォーム |forms:in-presenter]、[データグリッド |https://componette.org/contributte/datagrid/]、メニュー、投票など、繰り返し使用する意味のあるものであれば何でもかまいません。独自のコンポーネントを作成したり、[膨大な品揃え |https://componette.org]のオープンソースコンポーネントを使用したりできます。 +コンポーネントは独立した再利用できる部品で、ページ(つまりプレゼンター)に埋め込みます。[フォーム |forms:in-presenter]、[データグリッド |https://componette.org/contributte/datagrid/]、メニュー、アンケートなど、再利用する意味のあるものなら何でもです。自分でコンポーネントを作ることも、[豊富な品揃え |https://componette.org]のオープンソースのコンポーネントを使うこともできます。 -コンポーネントは、アプリケーション開発へのアプローチに根本的な影響を与えます。事前に準備されたユニットからページを組み立てる新しい可能性を開きます。そして、さらに[ハリウッド |components#ハリウッドスタイル]と共通点があります。 +コンポーネントはアプリケーション開発への向き合い方を根本から変えます。あらかじめ用意された部品からページを組み立てるという新しい可能性を開きます。そして[ハリウッド |components#ハリウッド流]とも通じるものがあります。 -DIコンテナと設定 -========= +DI コンテナと設定 +========== -DIコンテナ、またはオブジェクトファクトリは、アプリケーション全体の心臓部です。 +DI コンテナ、つまりオブジェクトのファクトリは、アプリケーション全体の心臓です。 -心配しないでください。前の行からそう思われるかもしれませんが、これは魔法のブラックボックスではありません。実際には、Netteによって生成され、キャッシュディレクトリに保存される、かなり退屈なPHPクラスです。`createServiceAbcd()` のような名前の多くのメソッドがあり、それぞれがオブジェクトを作成して返すことができます。はい、`createServiceApplication()` メソッドもあり、これは `index.php` ファイルでアプリケーションを起動するために必要だった `Nette\Application\Application` を作成します。そして、個々のPresenterを作成するメソッドもあります。などなど。 +ご心配なく。これまでの行から想像されるような魔法のブラックボックスではありません。実際には、Nette が生成してキャッシュディレクトリに保存する、かなり地味な PHP のクラスです。`createServiceAbcd()` のような名前のメソッドが数多く入っていて、それぞれが特定のオブジェクトを作って返せます。そう、`index.php` でアプリケーションを実行するのに必要だった `Nette\Application\Application` のインスタンスを作る `createServiceApplication__application()` メソッドもあります。個々のプレゼンターを作るメソッドなどもあります。 -DIコンテナが作成するオブジェクトは、何らかの理由でサービスと呼ばれます。 +DI コンテナが作るオブジェクトは、なぜかサービスと呼ばれます。 -このクラスの本当に特別な点は、あなたがそれをプログラミングするのではなく、フレームワークがプログラミングすることです。それは実際にPHPコードを生成し、ディスクに保存します。あなたは、コンテナがどのオブジェクトを作成できるようにすべきか、そして具体的にどのように作成すべきかの指示を与えるだけです。そして、これらの指示は[設定ファイル |bootstrapping#DIコンテナの設定]に書かれており、[NEON|neon:format]形式が使用されるため、拡張子も `.neon` です。 +このクラスが本当に特別なのは、あなたがそれをプログラムしないという点です。フレームワークがするのです。実際に PHP のコードを生成してディスクに保存します。あなたは、コンテナがどのオブジェクトを作れるべきか、そしてそれを正確にどう作るかの指示を与えるだけです。この指示は[設定ファイル |bootstrapping#DI コンテナの設定]に書きます。設定ファイルは [NEON|neon:format]形式を使うので、拡張子は `.neon` です。 -設定ファイルは、純粋にDIコンテナに指示するために使用されます。したがって、例えば[session |http:configuration#セッション]セクションで `expiration: 14 days` オプションを指定すると、DIコンテナはセッションを表す `Nette\Http\Session` オブジェクトを作成するときに、その `setExpiration('14 days')` メソッドを呼び出し、それによって設定が現実になります。 +設定ファイルは、純粋に DI コンテナへ指示を与えるためのものです。ですからたとえば [session |http:configuration#セッション]セクションで `expiration: 14 days` を指定すると、DI コンテナはセッションを表す `Nette\Http\Session` オブジェクトを作るときに `setExpiration('14 days')` メソッドを呼び、その設定を実現します。 -[設定できるすべてのこと |nette:configuring]と、[独自のサービスを定義する方法 |dependency-injection:services]を説明する章全体が用意されています。 +何を[設定できるか |nette:configuring]、そして[独自のサービスをどう定義するか |dependency-injection:services]を説明する章がまるごと用意されています。 -サービスの作成に少し慣れると、[autowiring |dependency-injection:autowiring]という言葉に出くわします。これは、信じられないほど人生を簡素化する機能です。何もする必要なく、必要な場所(例えばクラスのコンストラクタ)にオブジェクトを自動的に渡すことができます。NetteのDIコンテナが小さな奇跡であることに気づくでしょう。 +サービスの生成に少し踏み込むと、[オートワイヤリング |dependency-injection:autowiring]という言葉に出会います。これはあなたの生活を驚くほど楽にしてくれる機能です。あなたが何もしなくても、必要な場所(たとえばクラスのコンストラクタ)にオブジェクトを自動的に渡してくれます。Nette の DI コンテナが小さな奇跡であることに気づくでしょう。 -次へ進むには? -======= +次は何を? +===== -Netteのアプリケーションの基本原則を見てきました。まだ非常に表面的ですが、すぐに深く掘り下げ、やがて素晴らしいWebアプリケーションを作成できるようになるでしょう。次にどこへ進むべきですか?[最初のアプリケーションを作成する|quickstart:]チュートリアルを試しましたか? +Nette のアプリケーションの基本原則を見てきました。ここまでは表面をなぞる概観でしたが、まもなくもっと深く踏み込み、やがては素晴らしいウェブアプリケーションを作れるようになります。次はどこへ行きましょうか。[最初のアプリケーションを作ろう|quickstart:]のチュートリアルはもう試しましたか。 -上記に加えて、Netteには[便利なクラス|utils:]の武器庫全体、[データベースレイヤー|database:]などがあります。ドキュメントをざっと見てみてください。または[ブログ|https://blog.nette.org]をご覧ください。多くの興味深いことを見つけるでしょう。 +ここまで説明したもののほかにも、Nette には[便利なクラス|utils:]の一式や[データベース層|database:]などがあります。ドキュメントをあちこちクリックしてみてください。あるいは[ブログ|https://blog.nette.org]を訪ねてみてください。面白いものがたくさん見つかります。 -フレームワークがあなたに多くの喜びをもたらしますように 💙 +このフレームワークがあなたに大きな喜びをもたらしますように 💙 diff --git a/application/ja/multiplier.texy b/application/ja/multiplier.texy index 64f8a4ef3c..40d0258078 100644 --- a/application/ja/multiplier.texy +++ b/application/ja/multiplier.texy @@ -1,24 +1,24 @@ -Multiplier: 動的コンポーネント -********************* +Multiplier: 動的なコンポーネント +********************** .[perex] -インタラクティブコンポーネントを動的に作成するためのツール +インタラクティブなコンポーネントを動的に作るための道具です。 -典型的な例から始めましょう:eコマースサイトに商品のリストがあり、それぞれについてカートに商品を追加するためのフォームを表示したいとします。可能なバリアントの1つは、リスト全体を1つのフォームでラップすることです。しかし、[api:Nette\Application\UI\Multiplier]ははるかに便利な方法を提供します。 +典型的な例から始めましょう。ネットショップの商品一覧で、各商品に「カートに追加」のフォームを付けたいとします。ひとつのやり方は、一覧全体をひとつのフォームで包むことです。しかし [api:Nette\Application\UI\Multiplier]は、はるかに便利な方法を提供します。 -Multiplierを使用すると、複数のコンポーネントのファクトリを便利に定義できます。これはネストされたコンポーネントの原則に基づいて機能します - [api:Nette\ComponentModel\Container]から継承する各コンポーネントは、他のコンポーネントを含むことができます。 +Multiplier を使うと、複数のコンポーネントのファクトリを手軽に定義できます。原理は入れ子のコンポーネントで、[api:Nette\ComponentModel\Container]を継承したコンポーネントは、ほかのコンポーネントを含められます。 .[tip] -ドキュメントの[コンポーネントモデル |components#コンポーネントの詳細]に関する章、または[Honza Tvrdíkによる講演|https://www.youtube.com/watch?v=8y3LLexWu-I]を参照してください。 +ドキュメントの[コンポーネントモデル |components#コンポーネントの詳細]の章をご覧ください。 -Multiplierの本質は、コンストラクタで渡されたコールバックを使用して子を動的に作成できる親の役割を果たすことです。例を参照してください。 +Multiplier の本質は、コンストラクタに渡されたコールバックを使って子を動的に作れる親として働くことです。例を見てみましょう。 ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('count', '商品数:') + $form->addInteger('amount', '数量:') ->setRequired(); $form->addSubmit('send', 'カートに追加'); return $form; @@ -26,7 +26,7 @@ protected function createComponentShopForm(): Multiplier } ``` -これで、テンプレートで各商品についてフォームを簡単にレンダリングできます - そして、それぞれが本当にユニークなコンポーネントになります。 +これでテンプレートでは、商品ごとにフォームをそのまま描けます。しかもそのひとつひとつが本当に別々のコンポーネントになります。 ```latte {foreach $items as $item} @@ -37,23 +37,23 @@ protected function createComponentShopForm(): Multiplier {/foreach} ``` -`{control}` タグで渡される引数は、次のことを示す形式です。 +`{control}` タグに渡した引数は、次の意味を持つ形式に従っています。 -1. `shopForm` コンポーネントを取得する -2. そして、そこから子 `$item->id` を取得する +1. コンポーネント `shopForm` を取得する。 +2. そこから `$item->id` という名前の子を取得する。 -ポイント **1.** の最初の呼び出しでは、`shopForm` はまだ存在しないため、そのファクトリ `createComponentShopForm` が呼び出されます。取得されたコンポーネント(Multiplierのインスタンス)で、特定のフォームのファクトリが呼び出されます - これは、コンストラクタでMultiplierに渡した匿名関数です。 +**1** の最初の呼び出しでは `shopForm` コンポーネントがまだ存在しないので、そのファクトリ `createComponentShopForm` が呼ばれます。次に、得られたコンポーネント(Multiplier のインスタンス)に対して、個別のフォームのファクトリが呼ばれます。それが Multiplier のコンストラクタに渡した無名関数です。 -foreachの次の反復では、`createComponentShopForm` メソッドは呼び出されません(コンポーネントは存在します)が、異なる子を探しているため(`$item->id` は各反復で異なります)、匿名関数が再度呼び出され、新しいフォームが返されます。 +foreach ループの次の反復では、`createComponentShopForm` メソッドはもう呼ばれません(コンポーネントがすでに存在するからです)。しかし探している子が違う(反復ごとに `$item->id` が違う)ので、無名関数がもう一度呼ばれ、新しいフォームを返します。 -残っているのは、フォームが実際に意図した商品をカートに追加することを確認することだけです - 現在、各商品のフォームはまったく同じです。Multiplierのプロパティ(および一般的にNette Frameworkのコンポーネントファクトリ)が役立ちます。つまり、各ファクトリは最初の引数として作成されるコンポーネントの名前を受け取ります。この場合、それは `$item->id` であり、これはまさに必要なデータです。したがって、フォームの作成を少し変更するだけで十分です。 +あとはフォームが正しい商品をカートに追加するようにするだけです。今のところフォームはどの商品でも同じだからです。ここで Multiplier の(そして一般に Nette Framework のどのコンポーネントファクトリの)性質が役立ちます。どのファクトリも、作られるコンポーネントの名前を第 1 引数として受け取ります。さらに Multiplier のファクトリは、第 2 引数として Multiplier のインスタンス自身を受け取ります。この場合、第 1 引数は `$item->id` であり、まさに必要な情報です。ですからフォームの生成を少し書き換えるだけで済みます。 ```php protected function createComponentShopForm(): Multiplier { - return new Multiplier(function ($itemId) { + return new Multiplier(function (string $itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('count', '商品数:') + $form->addInteger('amount', '数量:') ->setRequired(); $form->addHidden('itemId', $itemId); $form->addSubmit('send', 'カートに追加'); diff --git a/application/ja/presenters.texy b/application/ja/presenters.texy index 6e7e8e2932..61138b6df4 100644 --- a/application/ja/presenters.texy +++ b/application/ja/presenters.texy @@ -1,39 +1,39 @@ -Presenter -********* +プレゼンター +******
    -NetteでPresenterとテンプレートを作成する方法について学びます。読み終えた後、あなたは知っているでしょう: +Nette でプレゼンターとテンプレートをどう書くのかを見ていきます。読み終えると次のことが分かります。 -- Presenterがどのように機能するか -- パーシステントパラメータとは何か -- テンプレートがどのようにレンダリングされるか +- プレゼンターがどう動くのか +- 永続パラメータとは何か +- テンプレートがどう描かれるのか
    -[すでに知っているように |how-it-works#Nette Application]、PresenterはWebアプリケーションの特定のページ(ホームページ、eコマースの製品、ログインフォーム、サイトマップフィードなど)を表すクラスです。アプリケーションは1つから数千のPresenterを持つことができます。他のフレームワークでは、コントローラーとも呼ばれます。 +プレゼンターがウェブアプリケーションの特定のページ、たとえばトップページ、ネットショップの商品、ログインフォーム、サイトマップのフィードなどを表すクラスであることは、[すでに見てきました |how-it-works#Nette Application]。アプリケーションはプレゼンターをひとつから何千まで持てます。ほかのフレームワークではコントローラとも呼ばれます。 -通常、Presenterという用語は、Webインターフェースの生成に適した[api:Nette\Application\UI\Presenter]クラスの子孫を意味し、この章の残りの部分で扱います。一般的な意味では、Presenterは[api:Nette\Application\IPresenter]インターフェースを実装する任意のオブジェクトです。 +ふつうプレゼンターという語は、ウェブのインターフェースを生成するのに向いた [api:Nette\Application\UI\Presenter]クラスの子孫を指し、この章の以降もそれを中心に扱います。より一般的な意味では、プレゼンターとは [api:Nette\Application\IPresenter]インターフェースを実装した任意のオブジェクトです。 -Presenterのライフサイクル -================= +プレゼンターのライフサイクル +============== -Presenterのタスクは、リクエストを処理し、応答(HTMLページ、画像、リダイレクトなど)を返すことです。 +プレゼンターの役目は、リクエストを処理してレスポンス(HTML のページ、画像、リダイレクトなど)を返すことです。 -したがって、最初にリクエストが渡されます。これは直接のHTTPリクエストではなく、ルーターの助けを借りてHTTPリクエストが変換された[api:Nette\Application\Request]オブジェクトです。Presenterはリクエストの処理をこれから示す他のメソッドに賢く委任するため、通常はこのオブジェクトに直接触れることはありません。 +ですからまずリクエストが渡されます。これは直接の HTTP リクエストではなく、ルーターの助けを借りて HTTP リクエストが変換された [api:Nette\Application\Request]オブジェクトです。このオブジェクトを直接扱うことはふつうありません。プレゼンターがリクエストの処理をほかのメソッドに巧みに委ねるからです。それをこれから見ていきます。 -[* lifecycle.svg *] *** *Presenterのライフサイクル* .<> +[* lifecycle.svg *] *** プレゼンターのライフサイクル .<> -この図は、存在する場合に上から下に順に呼び出されるメソッドのリストを表しています。これらのメソッドのいずれも存在する必要はなく、単一のメソッドを持たない完全に空のPresenterを持ち、それに基づいて単純な静的Webサイトを構築できます。 +図は、存在すれば上から下へ順に呼ばれるメソッドの一覧を示しています。どれも必須ではありません。メソッドがひとつもない、まったく空のプレゼンターを作り、その上に単純な静的サイトを築くこともできます。 `__construct()` --------------- -コンストラクタは、オブジェクト作成時に呼び出されるため、Presenterのライフサイクルには完全には属しません。しかし、その重要性のために記載しています。コンストラクタ([injectメソッド|best-practices:inject-method-attribute]とともに)は、依存関係を渡すために使用されます。 +コンストラクタは、オブジェクトが作られる瞬間に呼ばれるので、厳密にはプレゼンターのライフサイクルには属しません。それでもその重要さゆえに触れておきます。コンストラクタは([inject メソッド|best-practices:inject-method-attribute]とともに)依存関係を渡すために使います。 -Presenterは、アプリケーションのビジネスロジックを処理したり、データベースへの書き込みや読み取りを行ったり、計算を実行したりすべきではありません。これらは、モデルと呼ばれるレイヤーのクラスの仕事です。例えば、`ArticleRepository` クラスは記事の読み込みと保存を担当するかもしれません。Presenterがそれを使用できるようにするには、[依存性注入 |dependency-injection:passing-dependencies]を使用して渡してもらいます。 +プレゼンターは、アプリケーションのビジネスロジックを扱ったり、データベースに読み書きしたり、計算したりすべきではありません。それはモデルと呼ばれる層のクラスの責務です。たとえば `ArticleRepository` クラスが記事の読み込みと保存を担うでしょう。プレゼンターがそれを使うには、[依存性注入で渡してもらう |dependency-injection:passing-dependencies]必要があります。 ```php @@ -50,44 +50,47 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -リクエストを受信するとすぐに `startup()` メソッドが呼び出されます。プロパティの初期化、ユーザー権限の検証などに使用できます。メソッドは常に親 `parent::startup()` を呼び出す必要があります。 +リクエストを受け取った直後に `startup()` メソッドが呼ばれます。プロパティの初期化やユーザーの権限の確認などに使えます。このメソッドは必ず親を呼ぶ必要があります: `parent::startup()`。 `action(args...)` .{toc: action()} -------------------------------------------------- -`render()` メソッドに似ています。`render()` は後でレンダリングされる特定のテンプレートのデータを準備することを目的としていますが、`action()` ではテンプレートのレンダリングとは無関係にリクエストが処理されます。例えば、データが処理され、ユーザーがログインまたはログアウトされ、などが行われ、その後[別の場所にリダイレクト |#リダイレクト]されます。 +`render()` メソッドと似ています。`render()` がこのあと描かれる特定のテンプレートのためにデータを用意するものであるのに対し、`action()` はリクエストを処理するもので、そのあとにテンプレートを描くとは限りません。たとえばデータを処理したり、ユーザーをログイン・ログアウトさせたりしてから、[別の場所へリダイレクト |#リダイレクト]することもあります。 -重要なのは、`action()` が `render()` より前に呼び出されるため、そこで将来のイベントの流れを変更できることです。つまり、レンダリングされるテンプレートと呼び出される `render()` メソッドを `setView('jineView')` を使用して変更できます。 +大事なのは、`action()` が `render()` より*先に*呼ばれることです。おかげで、アクションのメソッドの中でリクエストの流れを変えられます。たとえば `setView('otherView')` を使って、描かれるテンプレートや、呼ばれる `render()` メソッドさえ変えられます。 -メソッドにはリクエストからのパラメータが渡されます。パラメータに型を指定することが可能であり、推奨されます。例えば `actionShow(int $id, ?string $slug = null)` - パラメータ `id` が欠落している場合、または整数でない場合、Presenterは[404エラー |#404エラーなど]を返し、動作を終了します。 +.{data-version:3.2.3} +`switch('otherAction')` メソッドを使えば、まったく別のアクションに切り替えることもできます。現在のメソッドを中断し、代わりに新しいアクションの `action()` と `render()` メソッドを実行します(そして自動的な[正規化|#正規化]を無効にします)。リクエスト自体は続き、いま走っているメソッドだけが中断されます。 + +リクエストのパラメータがメソッドに渡されます。これらのパラメータには型を指定でき、そうすることをおすすめします。たとえば `actionShow(int $id, ?string $slug = null)` です。`id` パラメータがない、あるいは整数でない場合、プレゼンターは [404 エラー |#404 などのエラー]を返して終わります。 `handle(args...)` .{toc: handle()} -------------------------------------------------- -このメソッドは、[コンポーネント |components#シグナル]に関する章で学ぶ、いわゆるシグナルを処理します。これは主にコンポーネントとAJAXリクエストの処理を目的としています。 +このメソッドは、いわゆるシグナルを処理します。シグナルについては[コンポーネント |components#シグナル]の章で学びます。主にコンポーネントと AJAX リクエストの処理のためのものです。 -メソッドには、`action()` の場合と同様に、型チェックを含むリクエストからのパラメータが渡されます。 +`action()` と同じく、型チェックも含めてリクエストのパラメータがメソッドに渡されます。 `beforeRender()` ---------------- -`beforeRender` メソッドは、その名前が示すように、各 `render()` メソッドの前に呼び出されます。共通のテンプレート設定、レイアウトへの変数の受け渡しなどに使用されます。 +`beforeRender` メソッドは、その名のとおり、すべての `render()` メソッドの前に呼ばれます。テンプレートの共通の設定、レイアウトへの変数の受け渡しなどに使います。 `render(args...)` .{toc: render()} ---------------------------------------------- -後続のレンダリングのためにテンプレートを準備し、データを渡すなどの場所です。 +ここでは、このあと描かれるテンプレートを準備し、データを渡したりします。 -メソッドには、`action()` の場合と同様に、型チェックを含むリクエストからのパラメータが渡されます。 +`action()` と同じく、型チェックも含めてリクエストのパラメータがメソッドに渡されます。 ```php public function renderShow(int $id): void { - // モデルからデータを取得し、テンプレートに渡します + // モデルからデータを取得してテンプレートに渡します $this->template->article = $this->articles->getById($id); } ``` @@ -96,104 +99,126 @@ public function renderShow(int $id): void `afterRender()` --------------- -`afterRender` メソッドは、名前が再び示すように、各 `render()` メソッドの後に呼び出されます。これはむしろ例外的に使用されます。 +`afterRender` メソッドは、これもまた名のとおり、すべての `render()` メソッドのあとに呼ばれます。使われることはむしろ稀です。 `shutdown()` ------------ -Presenterのライフサイクルの最後に呼び出されます。 +プレゼンターのライフサイクルの終わりに呼ばれます。 -**先に進む前に、良いアドバイス**。ご覧のとおり、Presenterは複数のアクション/ビューを処理できます。つまり、複数の `render()` メソッドを持つことができます。ただし、1つまたはできるだけ少ないアクションを持つPresenterを設計することをお勧めします。 +イベント +---- +プレゼンターのライフサイクルの一部として呼ばれる `startup()`、`beforeRender()`、`shutdown()` メソッドのほかに、自動的に呼ばれる関数を定義できます。プレゼンターはいわゆる[イベント |nette:glossary#イベント]を定義していて、そのハンドラを `$onStartup`、`$onRender`、`$onShutdown` の配列に足します。 + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` + +`$onStartup` 配列のハンドラは `startup()` メソッドの直前に、`$onRender` のハンドラは `beforeRender()` と `render()` のあいだに、そして `$onShutdown` のハンドラは `shutdown()` の直前に呼ばれます。 -応答の送信 -===== -Presenterの応答は通常、[HTMLページを含むテンプレートのレンダリング|templates]ですが、ファイルの送信、JSON、または別のページへのリダイレクトなども可能です。 +**先へ進む前にひとつ助言を。** ご覧のとおり、プレゼンターは複数のアクション/ビューを扱えます。つまり `render()` メソッドを複数持てます。とはいえ、プレゼンターはアクションひとつ、あるいはできるだけ少ない数で設計することをおすすめします。 + + +レスポンスの送信 +======== -ライフサイクルのいつでも、次のいずれかのメソッドを使用して応答を送信し、同時にPresenterを終了できます。 +プレゼンターのレスポンスはふつう[テンプレートを HTML のページに描くこと|templates]ですが、ファイルや JSON を送ることも、別のページへリダイレクトすることもできます。 -- `redirect()`, `redirectPermanent()`, `redirectUrl()`, `forward()` は[#リダイレクト]します -- `error()` は[エラーのため |#404エラーなど]にPresenterを終了します -- `sendJson($data)` はPresenterを終了し、JSON形式で[データを送信 |#JSONの送信]します -- `sendTemplate()` はPresenterを終了し、すぐに[テンプレートをレンダリング |templates]します -- `sendResponse($response)` はPresenterを終了し、[カスタム応答 |#応答]を送信します -- `terminate()` は応答なしでPresenterを終了します +ライフサイクルのどの時点でも、次のいずれかのメソッドでレスポンスを送り、同時にプレゼンターを終わらせられます。 -これらのメソッドのいずれも呼び出さない場合、Presenterは自動的にテンプレートのレンダリングに進みます。なぜですか?なぜなら、99%の場合、テンプレートをレンダリングしたいので、Presenterはこの動作をデフォルトと見なし、作業を楽にしたいからです。 +- `redirect()`、`redirectPermanent()`、`redirectUrl()`、`forward()` は[リダイレクト |#リダイレクト]を行います +- `error()` は[エラーのために |#404 などのエラー]プレゼンターを終わらせます +- `sendJson($data)` はプレゼンターを終わらせ、JSON 形式で[データを送ります |#JSON の送信] +- `sendTemplate()` はプレゼンターを終わらせ、すぐに[テンプレートを描きます |templates] +- `sendResponse($response)` はプレゼンターを終わらせ、[独自のレスポンス |#レスポンス]を送ります +- `terminate()` はレスポンスなしでプレゼンターを終わらせます + +これらのメソッドはいずれも、静かな終了の例外 `Nette\Application\AbortException` を投げて、ただちにプレゼンターを終わらせます。 + +これらのメソッドをどれも呼ばなければ、プレゼンターは自動的にテンプレートの描画に進みます。なぜでしょうか。99 % の場合、私たちはテンプレートを描きたいからです。ですからプレゼンターは、私たちの手間を省くためにこの振る舞いを既定としています。 リンクの作成 ====== -Presenterには `link()` メソッドがあり、これを使用して他のPresenterへのURLリンクを作成できます。最初のパラメータはターゲットのPresenterとアクションであり、その後に渡される引数が続きます。引数は配列として指定できます。 +プレゼンターには、ほかのプレゼンターへの URL リンクを作る `link()` メソッドがあります。第 1 パラメータは行き先のプレゼンターとアクションで、そのあとに引数が続きます。引数は配列としても渡せます。 ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$id, 'lang' => 'en']); ``` -テンプレートでは、他のPresenterとアクションへのリンクは次のように作成されます。 +テンプレートでは、ほかのプレゼンターやアクションへのリンクを次のように作ります。 ```latte -製品詳細 +商品の詳細 ``` -単に実際のURLの代わりに、既知の `Presenter:action` ペアを記述し、必要に応じてパラメータを指定します。トリックは `n:href` にあり、これはこの属性がLatteによって処理され、実際のURLが生成されることを示します。Netteでは、URLについて考える必要はまったくなく、Presenterとアクションについて考えるだけです。 +実際の URL の代わりに、見慣れた `Presenter:action` の組を書き、必要なパラメータを添えるだけです。仕掛けは `n:href` にあり、これがこの属性を処理して本当の URL を生成するよう Latte に伝えます。Nette では URL のことを考える必要はまったくなく、プレゼンターとアクションのことだけを考えればよいのです。 -詳細については、[URLリンクの作成|creating-links]の章を参照してください。 +詳しくは [URL リンクの作成|creating-links]の章をご覧ください。 リダイレクト ====== -別のPresenterに移動するには、`redirect()` および `forward()` メソッドを使用します。これらは[link() |#リンクの作成]メソッドと非常によく似た構文を持っています。 +別のプレゼンターに切り替えるには `redirect()` と `forward()` メソッドを使います。[link() |#リンクの作成]メソッドとよく似た構文です。 -`forward()` メソッドは、HTTPリダイレクトなしで即座に新しいPresenterに移動します。 +`forward()` メソッドは、HTTP のリダイレクトなしにただちに新しいプレゼンターへ切り替えます。 ```php $this->forward('Product:show'); ``` -HTTPコード302(または現在のリクエストメソッドがPOSTの場合は303)を持ついわゆる一時的なリダイレクトの例: +HTTP コード 302(現在のリクエストのメソッドが POST なら 303)の一時的なリダイレクトの例です。 ```php $this->redirect('Product:show', $id); ``` -HTTPコード301を持つ永続的なリダイレクトは、次のように実現できます。 +HTTP コード 301 の恒久的なリダイレクトには、次を使います。 ```php $this->redirectPermanent('Product:show', $id); ``` -アプリケーション外の別のURLにリダイレクトするには、`redirectUrl()` メソッドを使用します。2番目のパラメータとしてHTTPコードを指定できます。デフォルトは302(または現在のリクエストメソッドがPOSTの場合は303)です。 +アプリケーションの外の別の URL へは `redirectUrl()` メソッドでリダイレクトできます。HTTP コードは第 2 パラメータで指定でき、既定は 302(現在のリクエストのメソッドが POST なら 303)です。 ```php $this->redirectUrl('https://nette.org'); ``` -リダイレクトは、いわゆるサイレント終了例外 `Nette\Application\AbortException` をスローすることで、Presenterの動作を即座に終了します。 +リダイレクトは、いわゆる静かな終了の例外 `Nette\Application\AbortException` を投げて、ただちにプレゼンターの動作を終わらせます。 -リダイレクトの前に、[#フラッシュメッセージ]、つまりリダイレクト後にテンプレートに表示されるメッセージを送信できます。 +リダイレクトの前に、[フラッシュメッセージ |#フラッシュメッセージ]、つまりリダイレクト後のテンプレートに表示されるメッセージを送れます。 フラッシュメッセージ ========== -これらは通常、何らかの操作の結果を通知するメッセージです。フラッシュメッセージの重要な特徴は、リダイレクト後もテンプレートで利用できることです。表示後もさらに30秒間有効です。例えば、転送エラーのためにユーザーがページを更新した場合でも、メッセージはすぐには消えません。 +これはふつう、何らかの操作の結果を知らせるメッセージです。フラッシュメッセージの大事な性質は、リダイレクト後もテンプレートで使えることです。一度表示されたあとも、さらに 30 秒は有効なままです。たとえば通信のエラーでユーザーがページを再読み込みしても、メッセージがすぐ消えることはありません。 -[flashMessage() |api:Nette\Application\UI\Control::flashMessage()] メソッドを呼び出すだけで、Presenterがテンプレートへの受け渡しを処理します。最初のパラメータはメッセージのテキストであり、オプションの2番目のパラメータはそのタイプ(error、warning、infoなど)です。`flashMessage()` メソッドは、フラッシュメッセージのインスタンスを返し、これに追加情報を追加できます。 +[flashMessage() |api:Nette\Application\UI\Control::flashMessage()]メソッドを呼ぶだけで、テンプレートへの受け渡しはプレゼンターが担当します。第 1 パラメータはメッセージの本文、省略可能な第 2 パラメータはその種類(error、warning、info など)です。`flashMessage()` メソッドはフラッシュメッセージのインスタンスを返すので、追加の情報を足せます。 ```php -$this->flashMessage('項目が削除されました。'); -$this->redirect(/* ... */); // そしてリダイレクトします +$this->flashMessage('項目を削除しました。'); +$this->redirect(/* ... */); // そしてリダイレクト ``` -これらのメッセージは、テンプレートでは `$flashes` 変数で `stdClass` オブジェクトとして利用できます。これらには `message`(メッセージテキスト)、`type`(メッセージタイプ)プロパティが含まれ、前述のユーザー情報を含むこともできます。例えば、次のようにレンダリングします。 +テンプレートでは、これらのメッセージが `$flashes` 変数に `stdClass` オブジェクトとして入っていて、`message`(メッセージの本文)、`type`(メッセージの種類)、そして先ほど触れたユーザーが足した情報のプロパティを持ちます。次のように描きます。 ```latte {foreach $flashes as $flash} @@ -202,10 +227,10 @@ $this->redirect(/* ... */); // そしてリダイレクトします ``` -404エラーなど -======== +404 などのエラー +========== -リクエストを満たせない場合、例えば表示したい記事がデータベースに存在しないなどの理由で、`error(?string $message = null, int $httpCode = 404)` メソッドを使用して404エラーをスローします。 +たとえば表示したい記事がデータベースにないなど、リクエストに応えられない場合は、`error(string $message = '', int $httpCode = 404)` メソッドで 404 のエラーを投げます。 ```php public function renderShow(int $id): void @@ -218,13 +243,13 @@ public function renderShow(int $id): void } ``` -エラーのHTTPコードは2番目のパラメータとして渡すことができます。デフォルトは404です。メソッドは `Nette\Application\BadRequestException` 例外をスローするように機能し、その後 `Application` は制御をエラーPresenterに渡します。これは、発生したエラーを通知するページを表示するタスクを持つPresenterです。 エラーPresenterの設定は、[application設定|configuration]で行われます。 +HTTP のエラーコードは第 2 パラメータで渡せ、既定は 404 です。このメソッドは `Nette\Application\BadRequestException` を投げることで働き、そのあと `Application` が制御をエラー用のプレゼンターに渡します。これは、起きたエラーを知らせるページを表示する役目のプレゼンターです。エラー用のプレゼンターは[アプリケーションの設定|configuration]で指定します。 -JSONの送信 -======= +JSON の送信 +======== -JSON形式でデータを送信し、Presenterを終了するアクションメソッドの例: +`sendJson($data)` メソッドは、渡されたデータを JSON にエンコードして HTTP のレスポンスとして送り、プレゼンターを終わらせます。例を挙げます。 ```php public function actionData(): void @@ -235,59 +260,59 @@ public function actionData(): void ``` -リクエストパラメータ .{data-version:3.1.14} -================================= +リクエストのパラメータ .{data-version:3.1.14} +================================== -Presenterおよび各コンポーネントは、HTTPリクエストからパラメータを取得します。その値は `getParameter($name)` または `getParameters()` メソッドで取得できます。値は文字列または文字列の配列であり、基本的にはURLから直接取得された生のデータです。 +プレゼンターも、各コンポーネントも、そのパラメータを HTTP のリクエストから得ます。値は `getParameter($name)` や `getParameters()` メソッドで取り出せます。値は文字列か文字列の配列で、要するに URL から直接得た生のデータです。 -より便利にするために、プロパティを介してパラメータにアクセスすることをお勧めします。`#[Parameter]` 属性でマークするだけです。 +もっと便利にするために、プロパティ経由でパラメータにアクセスすることをおすすめします。`#[Parameter]` アトリビュートで印を付けるだけです。 ```php -use Nette\Application\Attributes\Parameter; // この行は重要です +use Nette\Application\Attributes\Parameter; // この行が大事です class HomePresenter extends Nette\Application\UI\Presenter { #[Parameter] - public string $theme; // publicである必要があります + public string $theme; // public でなければなりません } ``` -プロパティにはデータ型(例:`string`)を指定することをお勧めします。Netteはそれに基づいて値を自動的にキャストします。パラメータ値は[検証 |#パラメータの検証]することもできます。 +プロパティにはデータ型(`string` など)を指定することをおすすめします。そうすれば Nette が値を自動的にキャストします。パラメータの値は[検証する |#パラメータの検証]こともできます。 -リンクを作成するときに、パラメータの値を直接設定できます。 +リンクを作るとき、パラメータの値を直接設定できます。 ```latte クリック ``` -パーシステントパラメータ -============ +永続パラメータ +======= -パーシステントパラメータは、異なるリクエスト間で状態を維持するために使用されます。その値は、リンクをクリックした後も同じままです。セッションデータとは異なり、URLで転送されます。そして、これは完全に自動的に行われるため、`link()` や `n:href` で明示的に指定する必要はありません。 +永続パラメータは、リクエストをまたいで状態を保つために使います。その値はリンクをクリックしたあとも変わりません。セッションのデータと違い、URL で運ばれます。しかもそれは完全に自動的に起こるので、`link()` や `n:href` で明示的に書く必要はありません。 -使用例は?多言語アプリケーションがあるとします。現在の言語は、常にURLの一部である必要があるパラメータです。しかし、すべてのリンクでそれを指定するのは非常に面倒です。そこで、それをパーシステントパラメータ `lang` にし、自動的に転送されるようにします。素晴らしい! +使いどころの例を挙げましょう。多言語のアプリケーションがあるとします。現在の言語は、常に URL の一部でなければならないパラメータです。しかしそれをすべてのリンクに書くのは、途方もなく面倒です。ですからそれを永続パラメータ `lang` にすれば、自動的に運ばれていきます。素敵ですね。 -Netteでパーシステントパラメータを作成するのは非常に簡単です。パブリックプロパティを作成し、属性でマークするだけです。(以前は `/** @persistent */` が使用されていました) +Nette で永続パラメータを作るのはきわめて簡単です。public のプロパティを作り、アトリビュートで印を付けるだけです(以前は `/** @persistent */` が使われていました)。 ```php -use Nette\Application\Attributes\Persistent; // この行は重要です +use Nette\Application\Attributes\Persistent; // この行が大事です class ProductPresenter extends Nette\Application\UI\Presenter { #[Persistent] - public string $lang; // publicである必要があります + public string $lang; // public でなければなりません } ``` -`$this->lang` が例えば `'en'` の値を持つ場合、`link()` または `n:href` を使用して作成されたリンクも `lang=en` パラメータを含みます。そして、リンクをクリックした後も、再び `$this->lang = 'en'` になります。 +`$this->lang` が `'en'` のような値を持っていれば、`link()` や `n:href` で作られたリンクにもパラメータ `lang=en` が入ります。そしてリンクをクリックしたあと、`$this->lang` はまた `'en'` になります。 -プロパティにはデータ型(例:`string`)を指定することをお勧めします。また、デフォルト値を指定することもできます。パラメータ値は[検証 |#パラメータの検証]できます。 +プロパティにはデータ型(`string` など)を指定することをおすすめしますし、既定値も与えられます。パラメータの値は[検証できます |#パラメータの検証]。 -パーシステントパラメータは、通常、特定のPresenterのすべてのアクション間で転送されます。複数のPresenter間で転送するには、次のいずれかで定義する必要があります。 +永続パラメータはふつう、あるプレゼンターのすべてのアクションのあいだで引き継がれます。複数のプレゼンターをまたいで引き継ぐには、次のいずれかで定義する必要があります。 -- Presenterが継承する共通の祖先で -- Presenterが使用するトレイトで: +- プレゼンターが継承する共通の祖先で +- あるいはプレゼンターが使うトレイトで: ```php trait LanguageAware @@ -302,42 +327,62 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -リンクを作成するときに、パーシステントパラメータの値を変更できます。 +リンクを作るとき、永続パラメータの値は変えられます。 ```latte -チェコ語の詳細 +チェコ語での詳細 ``` -または、*リセット*することもできます。つまり、URLから削除します。その後、デフォルト値を取ります。 +あるいは*リセット*して URL から取り除けます。その場合は既定値になります。 ```latte クリック ``` -インタラクティブコンポーネント -=============== +共有されるパラメータの空間 +============= -Presenterには組み込みのコンポーネントシステムがあります。コンポーネントは、Presenterに挿入する独立した再利用可能なユニットです。[フォーム |forms:in-presenter]、データグリッド、メニューなど、繰り返し使用する意味のあるものであれば何でもかまいません。 +リクエストのパラメータ、[永続パラメータ |#永続パラメータ]、そして `action`、`render`、`handle`(シグナル)メソッドのパラメータは、ひとつの空間を共有していて、それぞれが名前で識別されます。同じ名前が複数に現れれば、それらはまったく同じ値を指します。 -コンポーネントがどのようにPresenterに挿入され、その後使用されるのでしょうか?それは[コンポーネント |components]の章で学びます。ハリウッドと共通点があることさえ発見するでしょう。 +これはしばしば都合よく使われます。たとえば永続パラメータ `lang` とアクションやシグナルのメソッドの引数 `$lang` は同じものなので、メソッドのシグネチャに並べるだけで永続パラメータの現在の値を読めます。 -そして、どこでコンポーネントを入手できますか?[Componette |https://componette.org/search/component]ページでは、オープンソースコンポーネントや、フレームワーク周辺のコミュニティのボランティアによってここに配置されたNette用の他の多くのアドオンを見つけることができます。 +```php +#[Persistent] +public string $lang; +public function handleSearch(string $query, string $lang): void +{ + // $lang には永続パラメータ lang の現在の値が入ります +} +``` + +この空間は共有されているので、意図的に値を共有したい場合を除き、パラメータの名前は一意に保ってください。これはシグナルにも当てはまり、シグナルはさらにリクエストの POST 本体からもパラメータを読みます。[シグナルの詳細 |components#シグナルの詳細]をご覧ください。 -深く掘り下げる -======= + +インタラクティブなコンポーネント +================ + +プレゼンターには組み込みのコンポーネントのしくみがあります。コンポーネントは、プレゼンターに埋め込む独立した再利用できる部品です。[フォーム |forms:in-presenter]、データグリッド、メニューなど、繰り返し使う意味のあるものなら何でもかまいません。 + +コンポーネントはどうプレゼンターに埋め込まれ、どう使われるのでしょうか。それは[コンポーネント |components]の章で学べます。ハリウッドとの共通点まで見つかります。 + +コンポーネントはどこで手に入るのでしょうか。[Componette |https://componette.org/search/component]には、フレームワークのコミュニティの有志が寄せたオープンソースのコンポーネントと、Nette のためのそのほか多くのアドオンがあります。 + + +さらに深く +===== .[tip] -この章でこれまで見てきたことで、おそらく完全に十分でしょう。以下の行は、Presenterについて深く掘り下げ、すべてを知りたい人のためのものです。 +この章でここまで扱った内容で、たいていの用途には十分でしょう。以降の節は、プレゼンターをもっと深く知りたい、何もかも知りたいという方のためのものです。 パラメータの検証 -------- -URLから受け取った[#リクエストパラメータ]と[#パーシステントパラメータ]の値は、`loadState()` メソッドによってプロパティに書き込まれます。また、プロパティで指定されたデータ型と一致するかどうかもチェックし、一致しない場合は404エラーで応答し、ページは表示されません。 +URL から受け取った[リクエストのパラメータ |#リクエストのパラメータ]と[永続パラメータ |#永続パラメータ]の値は、`loadState()` メソッドがプロパティに書き込みます。あわせてプロパティに指定されたデータ型と合うかも確認し、合わなければ 404 のエラーで応え、ページは表示されません。 -パラメータは、ユーザーがURLで簡単に上書きできるため、決して盲目的に信用しないでください。例えば、このようにして言語 `$this->lang` がサポートされている言語の中にあるかどうかを検証します。適切な方法は、前述の `loadState()` メソッドをオーバーライドすることです。 +URL から受け取ったパラメータを決して盲信しないでください。ユーザーに簡単に書き換えられます。たとえば言語 `$this->lang` が対応しているものの中にあるかを、次のように確かめます。そのための適切な方法が、先ほどの `loadState()` メソッドの上書きです。 ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -348,7 +393,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { parent::loadState($params); // ここで $this->lang が設定されます - // 値の独自のチェックが続きます: + // 続いて独自の値のチェック: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -360,83 +405,65 @@ class ProductPresenter extends Nette\Application\UI\Presenter リクエストの保存と復元 ----------- -Presenterが処理するリクエストは[api:Nette\Application\Request]オブジェクトであり、Presenterの `getRequest()` メソッドによって返されます。 +プレゼンターが処理するリクエストは [api:Nette\Application\Request]オブジェクトで、プレゼンターの `getRequest()` メソッドが返します。 -現在のリクエストはセッションに保存したり、逆にそこから復元してPresenterに再度実行させたりすることができます。これは、例えばユーザーがフォームに入力していてログインが期限切れになった場合に便利です。データを失わないように、ログインページにリダイレクトする前に現在のリクエストを `$reqId = $this->storeRequest()` を使用してセッションに保存します。これは短い文字列の形式でその識別子を返し、それをログインPresenterにパラメータとして渡します。 +現在のリクエストはセッションに保存でき、逆にそこから復元してプレゼンターにもう一度実行させられます。これは、たとえばユーザーがフォームに入力している最中にログインのセッションが切れたときに役立ちます。データを失わないよう、ログインページにリダイレクトする前に `$reqId = $this->storeRequest()` で現在のリクエストをセッションに保存します。これは短い文字列の識別子を返すので、それをパラメータとしてログインのプレゼンターに渡します。 -ログイン後、`$this->restoreRequest($reqId)` メソッドを呼び出します。これはセッションからリクエストを取得し、それにフォワードします。メソッドは、リクエストが現在ログインしているユーザーと同じユーザーによって作成されたことを検証します。別のユーザーがログインした場合、またはキーが無効な場合、何もしませんでプログラムは続行します。 +ログイン後に `$this->restoreRequest($reqId)` メソッドを呼ぶと、セッションからリクエストを取り出します。POST のリクエストはそこへ forward され、それ以外(GET)はリクエストの URL にリダイレクトされます。このメソッドは、そのリクエストがいまログインしているのと同じユーザーによって作られたかを確認します。別のユーザーがログインした場合やキーが正しくない場合は何もせず、プログラムはいつもどおり続きます。 -[以前のページに戻る方法 |best-practices:restore-request]のガイドをご覧ください。 +ガイド[前のページに戻るには |best-practices:restore-request]をご覧ください。 -カノニカル化 ------- +正規化 +--- -Presenterには、より良いSEO(インターネットでの検索エンジンの最適化)に貢献する本当に素晴らしい機能が1つあります。異なるURLで重複コンテンツが存在するのを自動的に防ぎます。特定のターゲットに複数のURLアドレス(例:`/index` と `/index?page=1`)がある場合、フレームワークはそのうちの1つをプライマリ(カノニカル)として決定し、HTTPコード301を使用して他のアドレスをそれにリダイレクトします。これにより、検索エンジンはページを2回インデックス付けせず、ページランクを希釈しません。 +プレゼンターには、SEO(検索エンジン最適化)の向上に寄与する本当に優れた機能があります。異なる URL に同じ内容が存在するのを自動的に防ぐのです。たとえば `/index` と `/index?page=1` のように複数の URL が特定の行き先に通じている場合、フレームワークはそのひとつを主要(正規)なものと定め、ほかを HTTP コード 301 でそこへリダイレクトします。おかげで検索エンジンがページを二重に登録して、そのページランクを薄めることがなくなります。 -このプロセスはカノニカル化と呼ばれます。カノニカルURLは、[ルーター|routing]によって生成されるURLであり、通常はコレクション内の最初の一致するルートです。 +この過程を正規化と呼びます。正規の URL は[ルーター|routing]が生成するもので、ふつうはコレクションの中で最初に一致するルートのものです。 -カノニカル化はデフォルトで有効になっており、`$this->autoCanonicalize = false` を介して無効にできます。 +正規化は既定で有効で、`$this->autoCanonicalize = false` で無効にできます。 -AJAXまたはPOSTリクエストの場合、データが失われたり、SEOの観点から付加価値がなかったりするため、リダイレクトは発生しません。 +AJAX や POST のリクエストではリダイレクトは起こりません。データを失いかねませんし、SEO 上の利点もないからです。 -カノニカル化は、`canonicalize()` メソッドを使用して手動で呼び出すこともできます。このメソッドには、`link()` メソッドと同様に、Presenter、アクション、およびパラメータが渡されます。リンクを作成し、現在のURLアドレスと比較します。異なる場合は、生成されたリンクにリダイレクトします。 +`canonicalize()` メソッドを使えば、正規化を手動で起こすこともできます。`link()` メソッドと同じように、プレゼンター、アクション、パラメータを渡します。リンクを生成して現在の URL アドレスと比べ、違っていれば生成したリンクへリダイレクトします。 ```php public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); - // $slug が $realSlug と異なる場合にリダイレクトします + // $slug が $realSlug と違えばリダイレクトします $this->canonicalize('Product:show', [$id, $realSlug]); } ``` +ルートのフィルタと `canonicalize()` を組み合わせて SEO に強い URL を作る完全なパターンは、[スラッグを使った読みやすい URL |best-practices:pretty-urls]をご覧ください。 -イベント ----- -Presenterのライフサイクルの一部として呼び出される `startup()`、`beforeRender()`、`shutdown()` メソッドに加えて、自動的に呼び出されるように他の関数を定義することもできます。Presenterはいわゆる[イベント |nette:glossary#イベント]を定義し、そのハンドラを `$onStartup`、`$onRender`、`$onShutdown` 配列に追加します。 +レスポンス +----- -```php -class ArticlePresenter extends Nette\Application\UI\Presenter -{ - public function __construct() - { - $this->onStartup[] = function () { - // ... - }; - } -} -``` - -`$onStartup` 配列のハンドラは `startup()` メソッドの直前に呼び出され、次に `$onRender` は `beforeRender()` と `render()` の間に呼び出され、最後に `$onShutdown` は `shutdown()` の直前に呼び出されます。 - - -応答 --------- - -Presenterが返す応答は、[api:Nette\Application\Response]インターフェースを実装するオブジェクトです。多くの準備された応答が利用可能です。 +プレゼンターが返すレスポンスは、[api:Nette\Application\Response]インターフェースを実装したオブジェクトです。あらかじめ用意されたレスポンスがいくつかあります。 -- [api:Nette\Application\Responses\CallbackResponse] - コールバックを送信します -- [api:Nette\Application\Responses\FileResponse] - ファイルを送信します +- [api:Nette\Application\Responses\CallbackResponse] - コールバックを送ります +- [api:Nette\Application\Responses\FileResponse] - ファイルを送ります - [api:Nette\Application\Responses\ForwardResponse] - forward() -- [api:Nette\Application\Responses\JsonResponse] - JSONを送信します +- [api:Nette\Application\Responses\JsonResponse] - JSON を送ります - [api:Nette\Application\Responses\RedirectResponse] - リダイレクト -- [api:Nette\Application\Responses\TextResponse] - テキストを送信します -- [api:Nette\Application\Responses\VoidResponse] - 空の応答 +- [api:Nette\Application\Responses\TextResponse] - テキストを送ります +- [api:Nette\Application\Responses\VoidResponse] - 空のレスポンス -応答は `sendResponse()` メソッドを使用して送信されます。 +レスポンスは `sendResponse()` メソッドで送ります。 ```php use Nette\Application\Responses; -// プレーンテキスト +// 素のテキスト $this->sendResponse(new Responses\TextResponse('Hello Nette!')); -// ファイルを送信します +// ファイルを送ります $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// 応答はコールバックになります +// コールバックを送ります $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -445,28 +472,88 @@ $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $ht $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +独自のレスポンスを書くこともできます。`Nette\Application\Response` インターフェースを実装するだけです。このインターフェースには、HTTP のリクエストとレスポンスを受け取る `send()` メソッドがひとつだけあります。たとえばメモリに保持したくないデータをストリームで送るときに役立ちます。 + +```php +class CsvResponse implements Nette\Application\Response +{ + public function __construct( + private string $fileName, + private iterable $rows, + ) { + } + + public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void + { + $response->setContentType('text/csv', 'utf-8'); + $response->sendAsFile($this->fileName); + + $handle = fopen('php://output', 'w'); + foreach ($this->rows as $row) { + fputcsv($handle, $row); + } + + fclose($handle); + } +} +``` + +あとはプレゼンターでいつもどおり送ります: `$this->sendResponse(new CsvResponse('export.csv', $rows));` + + +HTTP キャッシュ +---------- + +`lastModified()` メソッドを使うと、HTTP のキャッシュを簡単に活用できます。内容が最後に変更された日時(タイムスタンプ、文字列、`DateTimeInterface` オブジェクト)を渡し、必要なら ETag の検証子(内容の現在の版を表す短い文字列。そのハッシュなど)と有効期限も渡します。ブラウザがすでに一致する版を持っていれば、プレゼンターは `304 Not Modified` のレスポンスを送って終わるので、ページが無駄に描かれたり転送されたりしません。 + +```php +public function renderArticle(int $id): void +{ + $article = $this->articles->getById($id); + $this->lastModified($article->updatedAt); + // ... +} +``` + -`#[Requires]` を使用したアクセス制限 .{data-version:3.2.2} ------------------------------------------------ +テンプレートの仕上げ .{data-version:3.3.0} +-------------------------------- -`#[Requires]` 属性は、Presenterとそのメソッドへのアクセスを制限するための高度なオプションを提供します。HTTPメソッドの指定、AJAXリクエストの要求、同一オリジン(same origin)への制限、およびフォワーディング経由のアクセスのみに使用できます。属性は、Presenterクラスと個々の `action()`、`render()`、`handle()`、`createComponent()` メソッドの両方に適用できます。 +プレゼンターがテンプレートを描くとき、`sendTemplate()` メソッドは描画の直前に `completeTemplate()` を呼びます。このメソッドは `#[TemplateVariable]` アトリビュートで印の付いた変数を埋め、テンプレートのファイルを探します(既定の変数は、テンプレートが作られるときに `TemplateFactory` がすでに設定しています)。この protected のメソッドを上書きすれば、すべてのビューで共通の変数を足したり、別のファイルを指定したりできます。 -これらの制限を指定できます。 -- HTTPメソッドについて:`#[Requires(methods: ['GET', 'POST'])]` -- AJAXリクエストの要求:`#[Requires(ajax: true)]` -- 同一オリジンからのアクセスのみ:`#[Requires(sameOrigin: true)]` -- フォワード経由のアクセスのみ:`#[Requires(forward: true)]` -- 特定のアクションへの制限:`#[Requires(actions: 'default')]` +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My App'; +} +``` -詳細については、[Requires属性の使用方法 |best-practices:attribute-requires]のガイドをご覧ください。 +`#[Requires]` によるアクセスの制限 .{data-version:3.2.3} +---------------------------------------------- -HTTPメソッドのチェック -------------- +`#[Requires]` アトリビュートは、プレゼンターとそのメソッドへのアクセスを制限する高度な選択肢を提供します。HTTP のメソッドを指定する、AJAX のリクエストを要求する、同一オリジンに限る、forward 経由のアクセスだけを許すといったことができます。このアトリビュートは、プレゼンターのクラスにも、`action()`、`render()`、`handle()`、`createComponent()` といった個々のメソッドにも付けられます。 -NetteのPresenterは、各受信リクエストのHTTPメソッドを自動的に検証します。このチェックの理由は主にセキュリティです。標準では、`GET`、`POST`、`HEAD`、`PUT`、`DELETE`、`PATCH` メソッドが許可されています。 +次の制限を指定できます。 +- HTTP のメソッドについて: `#[Requires(methods: ['GET', 'POST'])]` +- AJAX のリクエストを要求する: `#[Requires(ajax: true)]` +- 同一オリジンからのアクセスだけ: `#[Requires(sameOrigin: true)]` +- forward 経由のアクセスだけ: `#[Requires(forward: true)]` +- 特定のアクションへの制限: `#[Requires(actions: 'default')]` -例えば `OPTIONS` メソッドを追加で許可したい場合は、`#[Requires]` 属性を使用します(Nette Application v3.2以降)。 +.[note] +バージョン 3.3 以降、同一オリジンの判定はブラウザの `Sec-Fetch-Site` ヘッダーで行われます(以前は SameSite の cookie 経由でした)。こちらのほうが確実で、スキーム、ドメイン、ポートの厳密な一致を確認します。 + +詳しくはガイド [Requires アトリビュートの使い方 |best-practices:attribute-requires]をご覧ください。 + + +HTTP メソッドのチェック +-------------- + +Nette のプレゼンターは、主に安全のために、届いたすべてのリクエストの HTTP メソッドを自動的に確認します。既定では `GET`、`POST`、`HEAD`、`PUT`、`DELETE`、`PATCH` のメソッドが許されます。 + +たとえば `OPTIONS` メソッドも追加で許したい場合は、`#[Requires]` アトリビュートを使います(Nette Application v3.2.3 以降)。 ```php #[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] @@ -475,26 +562,34 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -バージョン3.1では、検証は `checkHttpMethod()` で行われます。これは、リクエストで指定されたメソッドが `$presenter->allowedMethods` 配列に含まれているかどうかを判断します。メソッドを追加するには、次のようにします。 +バージョン 3.1.13 以降、確認は `checkHttpMethod()` で行われ、リクエストで指定されたメソッドが `$presenter->allowedMethods` 配列に含まれるかを調べます。バージョン 3.2.3 以降、この方法は非推奨で、`#[Requires]` が推奨されます。このメソッドは次のように上書きできます。 ```php class MyPresenter extends Nette\Application\UI\Presenter { - protected function checkHttpMethod(): void - { - $this->allowedMethods[] = 'OPTIONS'; - parent::checkHttpMethod(); - } + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } } ``` -`OPTIONS` メソッドを許可する場合、その後Presenter内で適切に処理する必要があることを強調することが重要です。このメソッドは、いわゆるプリフライトリクエストとしてよく使用されます。これは、CORS(Cross-Origin Resource Sharing)ポリシーの観点からリクエストが許可されているかどうかを判断する必要がある場合に、ブラウザが実際のリクエストの前に自動的に送信します。メソッドを許可しても正しい応答を実装しない場合、不整合や潜在的なセキュリティ問題につながる可能性があります。 +強調しておくべきなのは、`OPTIONS` メソッドを有効にしたら、プレゼンターの中でそれを適切に扱わなければならない、ということです。このメソッドはいわゆるプリフライトのリクエストとしてよく使われ、CORS(Cross-Origin Resource Sharing)のポリシーに照らしてリクエストが許されるかを判断する必要があるとき、ブラウザが実際のリクエストの前に自動的に送ります。メソッドを有効にしながら正しい応答を実装しないと、食い違いや潜在的なセキュリティの問題につながりかねません。 -その他の読み物 -======= +非推奨のアクションへの印 .{data-version:3.2.3} +---------------------------------- + +`#[Deprecated]` アトリビュートは、アクション、シグナル、あるいはプレゼンター全体を非推奨で将来削除予定と印を付けます。アプリケーションの非推奨の部分へのリンクを生成すると、Nette は警告を出して開発者に知らせます。 + +このアトリビュートは、プレゼンターのクラス全体にも、個々の `action()`、`render()`、`handle()` メソッドにも付けられます。 + + +関連情報 +==== -- [injectメソッドと属性 |best-practices:inject-method-attribute] -- [トレイトからのPresenterの構成 |best-practices:presenter-traits] -- [Presenterへの設定の受け渡し |best-practices:passing-settings-to-presenters] -- [以前のページに戻る方法 |best-practices:restore-request] +- [inject メソッドとアトリビュート |best-practices:inject-method-attribute] +- [トレイトによるプレゼンターの構成 |best-practices:presenter-traits] +- [プレゼンターへの設定の受け渡し |best-practices:passing-settings-to-presenters] +- [前のページに戻るには |best-practices:restore-request] diff --git a/application/ja/routing.texy b/application/ja/routing.texy index 4dd36be16c..b0cabf02a8 100644 --- a/application/ja/routing.texy +++ b/application/ja/routing.texy @@ -3,26 +3,26 @@
    -ルータはURLアドレスに関するすべてを担当するため、もはやそれについて考える必要はありません。以下に示します: +ルーターは URL アドレスにまつわるすべてを引き受けるので、あなたが URL のことを考える必要はありません。ここでは次のことを扱います。 -- URLが期待通りになるようにルータを設定する方法 -- SEOとリダイレクトについて説明します -- そして、独自のルータを作成する方法を示します +- URL を思いどおりの見た目にするためのルーターの設定 +- SEO とリダイレクト +- そして独自のルーターの書き方
    -より人間的なURL(クールまたはプリティURLとも呼ばれます)は、より使いやすく、覚えやすく、SEOに積極的に貢献します。Netteはこれを考慮し、開発者の要望に完全に応えます。アプリケーション用に、まさにあなたが望むURLアドレス構造を設計できます。 コードやテンプレートへの介入なしに行えるため、アプリケーションがすでに完成している時点でも設計できます。それは、ルータ内の[1つの場所 |#アプリケーションへの統合]でエレガントな方法で定義され、すべてのPresenterのアノテーションに散らばることはありません。 +人にやさしい URL(かっこいい URL、きれいな URL とも呼ばれます)は使いやすく、覚えやすく、SEO にもよい影響を与えます。Nette はそれを心得ていて、開発者の求めるものにしっかり応えます。アプリケーションの URL の構造を、思いどおりに設計できます。しかもアプリケーションが完成したあとでも設計できます。コードやテンプレートの変更が要らないからです。それはすべてのプレゼンターにアノテーションとして散らばるのではなく、[ただ一か所 |#組み込み]、つまりルーターで優雅に定義されます。 -Netteのルータは、**双方向**であるという点で特別です。HTTPリクエスト内のURLをデコードするだけでなく、リンクを作成することもできます。したがって、[Nette Application |how-it-works#Nette Application]において重要な役割を果たします。なぜなら、現在のリクエストを実行するPresenterとアクションを決定するだけでなく、テンプレートなどで[URLを生成 |creating-links]するためにも使用されるからです。 +Nette のルーターが並外れているのは、**双方向**だからです。HTTP のリクエストから URL を読み解くことも、リンクを作ることもできます。ですから [Nette Application |how-it-works#Nette Application]で欠かせない役割を果たします。現在のリクエストをどのプレゼンターとアクションが処理するかを決めるだけでなく、テンプレートなどで [URL を生成する |creating-links]のにも使われるからです。 -ただし、ルータはこの用途に限定されません。Presenterをまったく使用しないアプリケーション、REST APIなどで使用できます。詳細は[#スタンドアロンでの使用]セクションを参照してください。 +とはいえルーターの用途はそれだけではありません。プレゼンターをまったく使わないアプリケーションや REST API などでも使えます。詳しくは [#単独での利用]の節をご覧ください。 -ルートコレクション -========= +ルートのコレクション +========== -アプリケーションのURLアドレスの形式を定義する最も快適な方法は、[api:Nette\Application\Routers\RouteList]クラスを使用することです。定義は、いわゆるルートのリスト、つまりURLアドレスのマスクと、それに関連付けられたPresenterおよびアクションで構成され、シンプルなAPIを使用して行われます。ルートに名前を付ける必要はありません。 +アプリケーションの URL アドレスの構造を定義するもっとも心地よい方法は、[api:Nette\Application\Routers\RouteList]クラスが提供します。その定義は、いわゆるルートの一覧、つまり URL アドレスのマスクと、それに結びつくプレゼンターやアクションから成り、単純な API で書けます。ルートに名前を付ける必要はまったくありません。 ```php $router = new Nette\Application\Routers\RouteList; @@ -31,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -この例は、ブラウザで`https://domain.com/rss.xml`を開くと、Presenter `Feed`とアクション `rss`が表示され、`https://domain.com/article/12`を開くと、Presenter `Article`とアクション `view`が表示されることを示しています。適切なルートが見つからない場合、Nette Applicationは[BadRequestException |api:Nette\Application\BadRequestException]例外をスローし、これはユーザーにエラーページ404 Not Foundとして表示されます。 +この例からわかるように、ブラウザで `https://domain.com/rss.xml` を開くと `Feed` プレゼンターの `rss` アクションが表示されます。`https://domain.com/article/12` なら `Article` プレゼンターの `view` アクションが表示される、といった具合です。ふさわしいルートが見つからなければ、Nette Application は [BadRequestException |api:Nette\Application\BadRequestException]を投げて応え、ユーザーには 404 Not Found のエラーページとして表示されます。 ルートの順序 ------ -個々のルートがリストされる**順序は非常に重要**です。なぜなら、それらは上から下に順番に評価されるからです。ルールは、ルートを**特定のルートから一般的なルートへ**宣言することです: +個々のルートを並べる**順序**はきわめて**重要**です。上から下へ順に評価されるからです。原則は、ルートを**具体的なものから一般的なものへ**と宣言することです。 ```php -// 間違い: 'rss.xml' は最初のルートにキャッチされ、この文字列は として解釈されます +// 誤り: 'rss.xml' は最初のルートに捕まえられ、その文字列が と解釈されます $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -49,10 +49,10 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -ルートは、リンクを生成する際にも上から下に評価されます: +リンクを生成するときも、ルートは上から下へ評価されます。 ```php -// 間違い: 'Feed:rss' へのリンクは 'admin/feed/rss' として生成されます +// 誤り: 'Feed:rss' へのリンクが 'admin/feed/rss' として生成されます $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -61,100 +61,100 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -ルートを正しく組み立てるには、ある程度のスキルが必要であることを隠すつもりはありません。それを習得するまでは、[ルーティングパネル |#ルータのデバッグ]が便利なツールになります。 +ルートを正しく組み立てるにはいくらか慣れが要ることは、隠さずに申し上げます。身につくまでは [ルーティングのパネル |#ルーターのデバッグ]が役に立つ道具になるでしょう。 マスクとパラメータ --------- -マスクは、Webサイトのルートディレクトリからの相対パスを記述します。最も単純なマスクは静的なURLです: +マスクはウェブサイトのルートディレクトリからの相対パスを表します。もっとも単純なマスクは静的な URL です。 ```php $router->addRoute('products', 'Products:default'); ``` -マスクには、いわゆる**パラメータ**が含まれることがよくあります。これらは山括弧(例:``)で示され、ターゲットPresenterに渡されます。たとえば、メソッド `renderShow(int $year)` や永続パラメータ `$year` に渡されます: +マスクには**パラメータ**が入ることがよくあります。これは山かっこで囲まれ(たとえば ``)、対象のプレゼンターに、たとえば `renderShow(int $year)` メソッドや永続パラメータ `$year` に渡されます。 ```php $router->addRoute('chronicle/', 'History:show'); ``` -この例は、ブラウザで `https://example.com/chronicle/2020` を開くと、Presenter `History` とアクション `show` がパラメータ `year: 2020` とともに表示されることを示しています。 +この例からわかるように、ブラウザで `https://example.com/chronicle/2020` を開くと、`History` プレゼンターの `show` アクションがパラメータ `year: 2020` とともに表示されます。 -パラメータには、マスク内で直接デフォルト値を指定でき、これによりオプションになります: +パラメータの既定値はマスクの中で直接指定でき、そうするとそのパラメータは省略できるようになります。 ```php $router->addRoute('chronicle/', 'History:show'); ``` -これで、ルートはURL `https://example.com/chronicle/` も受け入れ、これも `History:show` をパラメータ `year: 2020` とともに表示します。 +これでこのルートは `https://example.com/chronicle/` という URL も受け付け、やはり `History:show` をパラメータ `year: 2020` とともに表示します。 -パラメータは、もちろんPresenter名やアクション名にもなり得ます。たとえば、このように: +もちろんプレゼンターやアクションの名前もパラメータにできます。たとえば次のようにです。 ```php $router->addRoute('/', 'Home:default'); ``` -指定されたルートは、たとえば `/article/edit` や `/catalog/list` の形式のURLを受け入れ、それらをPresenterとアクション `Article:edit` および `Catalog:list` として解釈します。 +このルートはたとえば `/article/edit` や `/catalog/list` の形の URL を受け付け、それぞれプレゼンターとアクション `Article:edit`、`Catalog:list` と解釈します。 -同時に、パラメータ `presenter` と `action` にデフォルト値 `Home` と `default` を与え、それらもオプションになります。したがって、ルートは `/article` の形式のURLも受け入れ、それを `Article:default` として解釈します。または逆に、`Product:default` へのリンクはパス `/product` を生成し、デフォルトの `Home:default` へのリンクはパス `/` を生成します。 +同時に `presenter` と `action` のパラメータに既定値 `Home` と `default` を与えるので、これらも省略できるようになります。ですからこのルートは `/article` という URL も受け付け、`Article:default` と解釈します。逆に `Product:default` へのリンクはパス `/product` を生成し、既定の `Home:default` へのリンクはパス `/` を生成します。 -マスクは、Webサイトのルートディレクトリからの相対パスだけでなく、スラッシュで始まる場合は絶対パス、または2つのスラッシュで始まる場合は完全な絶対URLも記述できます: +マスクはウェブサイトのルートディレクトリからの相対パスだけでなく、スラッシュで始めれば絶対パスを、スラッシュ 2 つで始めれば絶対 URL 全体を表せます。 ```php -// ドキュメントルートからの相対パス +// ドキュメントルートからの相対 $router->addRoute('/', /* ... */); -// 絶対パス(ドメインからの相対パス) +// 絶対パス(ドメインからの相対) $router->addRoute('//', /* ... */); -// ドメインを含む絶対URL(スキーマからの相対パス) +// ドメインを含む絶対 URL(スキームからの相対) $router->addRoute('//.example.com//', /* ... */); -// スキーマを含む絶対URL +// スキームを含む絶対 URL $router->addRoute('https://.example.com//', /* ... */); ``` -検証式 ---- +検証の正規表現 +------- -各パラメータには、[正規表現|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]を使用して検証条件を設定できます。たとえば、パラメータ `id` には、正規表現 `\d+` を使用して数字のみを受け入れるように指定します: +パラメータごとに [正規表現|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]で検証の条件を指定できます。たとえば `id` パラメータには、正規表現 `\d+` で数字しか入れられないと指定します。 ```php $router->addRoute('/[/]', /* ... */); ``` -すべてのパラメータのデフォルトの正規表現は `[^/]+` です。つまり、スラッシュ以外のすべてです。パラメータがスラッシュも受け入れる必要がある場合は、式 `.+` を指定します: +すべてのパラメータの既定の正規表現は `[^/]+`、つまりスラッシュ以外のすべてです。パラメータにスラッシュも受け付けさせたいなら、正規表現を `.+` にします。 ```php -// https://example.com/a/b/c を受け入れ、path は 'a/b/c' になります +// https://example.com/a/b/c を受け付け、path は 'a/b/c' になります $router->addRoute('', /* ... */); ``` -オプションシーケンス ----------- +省略できる部分 +------- -マスクでは、角括弧を使用してオプションの部分をマークできます。マスクの任意の部分をオプションにでき、パラメータを含めることもできます: +マスクの中では、角かっこで省略できる部分に印を付けられます。マスクのどの部分も省略できるようにでき、その中にパラメータを入れられます。 ```php $router->addRoute('[/]', /* ... */); -// 受け入れるパス: -// /cs/download => lang => cs, name => download +// 次のパスを受け付けます: +// /en/download => lang => en, name => download // /download => lang => null, name => download ``` -パラメータがオプションシーケンスの一部である場合、当然ながらオプションにもなります。デフォルト値が指定されていない場合は、nullになります。 +パラメータが省略できる部分の中にあると、当然そのパラメータも省略できるようになります。既定値が指定されていなければ null になります。 -オプションの部分はドメインにも含めることができます: +省略できる部分はドメインにも置けます。 ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -シーケンスは任意にネストおよび組み合わせることができます: +省略できる部分は自由に入れ子にしたり組み合わせたりできます。 ```php $router->addRoute( @@ -162,33 +162,33 @@ $router->addRoute( 'Home:default', ); -// 受け入れるパス: -// /cs/hello +// 次のパスを受け付けます: +// /en/hello // /en-us/hello // /hello // /hello/page-12 ``` -URLを生成する際には、最短のバリアントが試みられるため、省略できるものはすべて省略されます。したがって、たとえばルート `index[.html]` はパス `/index` を生成します。左角括弧の後に感嘆符を付けることで、動作を逆にすることができます: +URL を生成するときは、もっとも短い形が優先されるので、省ける部分はすべて省かれます。ですからたとえばルート `index[.html]` はパス `/index` を生成します。この振る舞いは、開き角かっこのうしろに感嘆符を置くと逆にできます。 ```php -// /hello と /hello.html を受け入れ、/hello を生成します +// /hello と /hello.html を受け付け、/hello を生成します $router->addRoute('[.html]', /* ... */); -// /hello と /hello.html を受け入れ、/hello.html を生成します +// /hello と /hello.html を受け付け、/hello.html を生成します $router->addRoute('[!.html]', /* ... */); ``` -角括弧なしのオプションパラメータ(つまり、デフォルト値を持つパラメータ)は、基本的に次のように括弧で囲まれているかのように動作します: +角かっこのない、省略できるパラメータ(つまり既定値を持つパラメータ)は、実質的に次のように囲まれているかのように振る舞います。 ```php $router->addRoute('//', /* ... */); -// これに対応します: +// これは次と同じです: $router->addRoute('[/[/[]]]', /* ... */); ``` -末尾のスラッシュの動作に影響を与えたい場合、たとえば `/home/` の代わりに `/home` だけを生成するようにするには、次のようにします: +末尾のスラッシュの振る舞いに手を入れて、たとえば `/home/` の代わりに `/home` を生成させたいなら、次のようにできます。 ```php $router->addRoute('[[/[/]]]', /* ... */); @@ -198,24 +198,24 @@ $router->addRoute('[[/[/]]]', /* ... */); ワイルドカード ------- -絶対パスのマスクでは、次のワイルドカードを使用して、たとえば開発環境と本番環境で異なる可能性のあるドメインをマスクに書き込む必要性を回避できます: +絶対 URL のマスクでは次のワイルドカードを使えます。たとえば開発環境と本番環境で違うかもしれないドメインを、マスクに書かずに済ませられます。 -- `%tld%` = トップレベルドメイン、例:`com` または `org` -- `%sld%` = セカンドレベルドメイン、例:`example` -- `%domain%` = サブドメインなしのドメイン、例:`example.com` -- `%host%` = 完全なホスト、例:`www.example.com` +- `%tld%` = トップレベルドメイン、たとえば `com` や `org` +- `%sld%` = セカンドレベルドメイン、たとえば `example` +- `%domain%` = サブドメインを除いたドメイン、たとえば `example.com` +- `%host%` = ホスト全体、たとえば `www.example.com` - `%basePath%` = ルートディレクトリへのパス ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); -$router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('//www.%sld%.%tld%/%basePath%//', /* ... */); ``` -拡張表記 ----- +詳しい書き方 +------ -通常 `Presenter:action` の形式で記述されるルートのターゲットは、個々のパラメータとそのデフォルト値を定義する配列を使用して記述することもできます: +ふつう `Presenter:action` の形で書くルートの行き先は、個々のパラメータとその既定値を定義する配列でも書けます。 ```php $router->addRoute('/[/]', [ @@ -224,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -より詳細な指定には、さらに拡張された形式を使用できます。ここでは、デフォルト値に加えて、パラメータの他のプロパティ(たとえば、検証正規表現(`id` パラメータを参照))も設定できます: +より細かく指定するには、さらに広げた形も使えます。そこでは既定値のほかに、検証の正規表現などパラメータのほかの性質も設定できます(`id` パラメータをご覧ください)。 ```php use Nette\Routing\Route; @@ -242,19 +242,29 @@ $router->addRoute('/[/]', [ ]); ``` -配列で定義されたパラメータがパスのマスクに含まれていない場合、その値はURLの疑問符の後に指定されたクエリパラメータを使用しても変更できないことに注意することが重要です。 +大事なのは、配列で定義したパラメータがパスのマスクに現れていない場合、その値は変えられないという点です。URL の疑問符のうしろに書くクエリパラメータでも変えられません。 + +これは**固定のパラメータ**、つまりあるページに短く覚えやすい URL を与えるのに役立ちます。たとえば `/tos` がいつも `Article:view` を `id: 123` で開くようにするには次のようにします。 + +```php +$router->addRoute('tos', [ + 'presenter' => 'Article', + 'action' => 'view', + 'id' => 123, +]); +``` -フィルタと翻訳 +フィルタと変換 ------- -アプリケーションのソースコードは英語で記述しますが、Webサイトにチェコ語のURLが必要な場合は、次のような単純なルーティングでは: +アプリケーションのソースコードは英語で書きますが、ウェブサイトの URL をチェコ語にしたい場合、次のような単純なルーティングでは、 ```php $router->addRoute('/', 'Home:default'); ``` -`/product/123` や `/cart` のような英語のURLが生成されます。URL内のPresenterとアクションをチェコ語の単語(例:`/produkt/123` や `/kosik`)で表現したい場合は、翻訳辞書を使用できます。その記述には、2番目のパラメータのより「冗長な」バリアントが必要です: +`/product/123` や `/cart` のような英語の URL が生成されます。URL の中のプレゼンターとアクションをチェコ語(たとえば `/produkt/123` や `/kosik`)で表したいなら、変換の辞書を使えます。それを書くには、第 2 パラメータの「詳しい」書き方がもう必要です。 ```php use Nette\Routing\Route; @@ -263,7 +273,7 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // URL内の文字列 => presenter + // URL の中の文字列 => プレゼンター 'produkt' => 'Product', 'kosik' => 'Cart', 'katalog' => 'Catalog', @@ -278,11 +288,11 @@ $router->addRoute('/', [ ]); ``` -翻訳辞書の複数のキーが同じPresenterにつながる可能性があります。これにより、異なるエイリアスが作成されます。正規のバリアント(つまり、生成されたURLに含まれるバリアント)は、最後のキーと見なされます。 +変換の辞書では、複数のキーが同じプレゼンターを指しても構いません。そうするとそのプレゼンターにいろいろな別名ができます。最後のキーが正式な形(つまり生成される URL に入る形)と見なされます。 -翻訳テーブルはこの方法で任意のパラメータに使用できます。翻訳が存在しない場合は、元の値が使用されます。この動作は、`Route::FilterStrict => true` を追加することで変更でき、値が辞書にない場合、ルートはURLを拒否します。 +変換の表はこのやり方でどのパラメータにも使えます。変換が存在しなければ、もとの値がそのまま使われます。この振る舞いは `Route::FilterStrict => true` を足すと変えられ、値が辞書にない場合、そのルートは URL を受け付けなくなります。 -配列形式の翻訳辞書に加えて、独自の翻訳関数をデプロイすることもできます。 +配列の形の変換の辞書のほかに、独自の変換の関数も使えます。 ```php use Nette\Routing\Route; @@ -298,15 +308,15 @@ $router->addRoute('//', [ ]); ``` -関数 `Route::FilterIn` は、URL内のパラメータとPresenterに渡される文字列の間で変換を行い、関数 `FilterOut` は逆方向の変換を保証します。 +`Route::FilterIn` の関数は、URL の中のパラメータと、そのあとプレゼンターに渡される文字列とのあいだの変換を行い、`FilterOut` の関数は逆向きの変換を受け持ちます。 -パラメータ `presenter`、`action`、`module` には、PascalCaseまたはcamelCaseスタイルとURLで使用されるkebab-caseの間で変換を行う事前定義されたフィルタがすでにあります。パラメータのデフォルト値はすでに変換された形式で記述されるため、たとえばPresenterの場合は `` と記述し、`` とは記述しません。 +`presenter`、`action`、`module` のパラメータには、PascalCase や camelCase の書き方と、URL で使われる kebab-case とのあいだを変換するフィルタがあらかじめ用意されています。パラメータの既定値は、アプリケーションに渡される形(プレゼンターとモジュールは PascalCase、アクションは camelCase)で書くので、たとえばプレゼンターなら `` と書き、`` とは書きません。 -一般フィルタ ------- +一般のフィルタ +------- -特定のパラメータ向けのフィルタに加えて、すべてのパラメータの連想配列を受け取り、それらを任意に変更して返すことができる一般フィルタも定義できます。一般フィルタはキー `null` の下に定義します。 +特定のパラメータ向けのフィルタのほかに、一般のフィルタも定義できます。これはすべてのパラメータの連想配列を受け取り、好きなように変えて返せます。一般のフィルタは空のキーの下に定義します。 ```php use Nette\Routing\Route; @@ -321,78 +331,94 @@ $router->addRoute('/', [ ]); ``` -一般フィルタを使用すると、ルートの動作を完全に任意の方法で変更できます。たとえば、他のパラメータに基づいてパラメータを変更するために使用できます。たとえば、パラメータ `` の現在の値に基づいて `` と `` を翻訳するなどです。 +一般のフィルタを使えば、ルートの振る舞いを本当に好きなように変えられます。たとえばほかのパラメータをもとにパラメータを変えるのに使えます。たとえば `` パラメータの今の値をもとに `` と `` を変換する、といった具合です。 -パラメータに独自のフィルタが定義されており、同時に一般フィルタが存在する場合、独自の `FilterIn` が一般フィルタの前に実行され、逆に一般フィルタの `FilterOut` が独自のフィルタの前に実行されます。したがって、一般フィルタ内では、パラメータ `presenter` および `action` の値はPascalCaseおよびcamelCaseスタイルで記述されます。 +パラメータに独自のフィルタが定義されていて、一般のフィルタもある場合、独自の `FilterIn` が一般のものより先に実行され、逆に一般の `FilterOut` が独自のものより先に実行されます。ですから一般のフィルタの中では、`presenter` と `action` のパラメータの値はそれぞれ PascalCase と camelCase の書き方になっています。 +これらのフィルタの実用的な使い方は [スラッグ付きのきれいな URL |best-practices:pretty-urls]をご覧ください。テンプレートを一切変えずに `/article/123-how-to-bake-bread` のような SEO にやさしい URL を生成します。 -一方向OneWay ---------- -一方向ルートは、アプリケーションがもはや生成しないが、まだ受け入れている古いURLの機能を維持するために使用されます。それらを `OneWay` フラグでマークします: +OneWay フラグ +---------- + +一方通行のルートは、アプリケーションがもう生成しないけれど受け付けはする古い URL を生かしておくのに使います。`OneWay` フラグで印を付けます。 ```php -// 古いURL /product-info?id=123 -$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// 新しいURL /product/123 +// 古い URL /product-info?id=123 +$router->addRoute('product-info', 'Product:detail', oneWay: true); +// 新しい URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -古いURLにアクセスすると、Presenterは自動的に新しいURLにリダイレクトするため、検索エンジンはこれらのページを2回インデックス付けしません([#SEOとカノニカル化]を参照)。 +古い URL にアクセスすると、プレゼンターが自動的に新しい URL へリダイレクトするので、検索エンジンがこれらのページを二重に登録することはありません([#SEO と正規化]をご覧ください)。 -コールバックによる動的ルーティング ------------------ +コールバックによる動的なルーティング +------------------ -コールバックによる動的ルーティングを使用すると、ルートに直接関数(コールバック)を割り当てることができ、特定のパスが訪問されたときに実行されます。この柔軟な機能により、アプリケーションのさまざまなエンドポイントを迅速かつ効率的に作成できます: +コールバックによる動的なルーティングを使うと、ルートに関数(コールバック)を直接割り当てられ、そのパスが訪れられたときに実行されます。この柔軟な機能のおかげで、アプリケーションのさまざまなエンドポイントを手早く効率よく作れます。 ```php $router->addRoute('test', function () { - echo 'あなたは /test アドレスにいます'; + echo 'You are at the /test address'; }); ``` -マスクにパラメータを定義することもでき、それらは自動的にコールバックに渡されます: +マスクの中にパラメータを定義することもでき、それは自動的にコールバックに渡されます。 ```php $router->addRoute('', function (string $lang) { echo match ($lang) { - 'cs' => '私たちのウェブサイトのチェコ語版へようこそ!', + 'cs' => 'Welcome to the Czech version of our website!', 'en' => 'Welcome to the English version of our website!', }; }); ``` +マスクからのパラメータのほかに、コールバックは DI コンテナのサービスも受け取れます。それはパラメータの型をもとに渡されます。さらに `$presenter` パラメータには、そのルートを処理する [MicroPresenter |api:NetteModule\MicroPresenter]のインスタンスが渡されます。 + +```php +$router->addRoute('', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) { + // ... +}); +``` + モジュール ----- -共通の[モジュール |directory-structure#Presenterとテンプレート]に属する複数のルートがある場合は、`withModule()` を使用します: +共通の[モジュール |directory-structure#プレゼンターとテンプレート]に属するルートが複数あるなら、`withModule()` を使います。指定したモジュールは、そのグループのすべてのルートのプレゼンターに自動的に前置され、URL からはすっかり消えます。 ```php $router = new RouteList; $router->withModule('Forum') // 以下のルートは Forum モジュールの一部です - ->addRoute('rss', 'Feed:rss') // presenter は Forum:Feed になります + ->addRoute('rss', 'Feed:rss') // プレゼンターは Forum:Feed になります ->addRoute('/') ->withModule('Admin') // 以下のルートは Forum:Admin モジュールの一部です ->addRoute('sign:in', 'Sign:in'); ``` -代替案は、パラメータ `module` を使用することです: +代わりに `module` パラメータも使えます。これも同じように固定のモジュールを設定し、URL には現れないようにします。 ```php -// URL manage/dashboard/default は Admin:Dashboard presenter にマッピングされます +// URL manage/dashboard/default はプレゼンター Admin:Dashboard に対応します $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` +プレゼンターの名前は、そのモジュールと合わせてはじめて完全になります。たとえば `Front:Admin:ProductList` です。こうした完全な名前が URL のパラメータに入るとき、ルーターは 2 つの単純な規則でそれを符号化します。コロン `:`(モジュールの区切り)はすべて**ドット**になり、PascalCase の名前の語の切れ目はすべて**ハイフン**になります。ですから `Front:Admin:ProductList` は URL の中で `front.admin.product-list` として現れ、同じやり方で読み戻されます。モジュールに分かれたアプリケーションが、上のような道具を使わないとドットだらけの URL を作るのは、これが理由です。 + +`withModule()` も `module` パラメータも、まさにこれを避けます。プレゼンターの名前が URL に届く前に、分かっているモジュールの接頭辞を取り除くからです。モジュールが決まりきったものである以上、そもそも符号化する必要はありません。 + +ときにはモジュール自体を変えられるようにして、URL に現れてほしいこともあります。そこでマスクの中で `` を直接使います。ただし大事な点にご注意ください。**`` はモジュールのパス全体**、つまりプレゼンターの名前の最後のコロンまでを取り込みます。プレゼンター `Shop:Admin:Product` ならモジュールは `Shop:Admin`、プレゼンターは `Product` ということになり、コロンはドットになるので、次のようになります。 + サブドメイン ------ -ルートコレクションをサブドメインごとに分割できます: +ルートのコレクションはサブドメインごとに分けられます。 ```php $router = new RouteList; @@ -401,7 +427,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -ドメイン名には[#ワイルドカード]を使用することもできます: +ドメイン名には [#ワイルドカード]も使えます。 ```php $router = new RouteList; @@ -410,23 +436,23 @@ $router->withDomain('example.%tld%') ``` -パスプレフィックス ---------- +パスの接頭辞 +------ -ルートコレクションをURLのパスごとに分割できます: +ルートのコレクションは URL のパスごとに分けられます。 ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // URL /eshop/rss をキャッチします - ->addRoute('/'); // URL /eshop// をキャッチします + ->addRoute('rss', 'Feed:rss') // URL /eshop/rss に一致します + ->addRoute('/'); // URL /eshop// に一致します ``` 組み合わせ ----- -上記の分割は相互に組み合わせることができます: +上のグループ分けは互いに組み合わせられます。 ```php $router = (new RouteList) @@ -449,34 +475,34 @@ $router = (new RouteList) クエリパラメータ -------- -マスクにはクエリパラメータ(URLの疑問符の後のパラメータ)も含めることができます。これらには検証式を定義できませんが、Presenterに渡される名前を変更できます: +マスクにはクエリパラメータ(URL の疑問符のうしろのパラメータ)も入れられます。これには検証の正規表現を定義できませんが、プレゼンターに渡される名前は変えられます。 ```php -// クエリパラメータ 'cat' をアプリケーションで 'categoryId' という名前で使用したい +// クエリパラメータ 'cat' をアプリケーションでは 'categoryId' という名前で使いたい $router->addRoute('product ? id= & cat=', /* ... */); ``` -Fooパラメータ --------- +Foo パラメータ +--------- -さて、さらに深く掘り下げます。Fooパラメータは、基本的に名前のないパラメータであり、正規表現をマッチさせることができます。例としては、`/index`、`/index.html`、`/index.htm`、`/index.php` を受け入れるルートがあります: +さらに深いところへ進みます。Foo パラメータは要するに名前のないパラメータで、正規表現に一致させられます。例として `/index`、`/index.html`、`/index.htm`、`/index.php` を受け付けるルートを挙げます。 ```php $router->addRoute('index', /* ... */); ``` -URLを生成する際に使用される文字列を明示的に定義することもできます。文字列は疑問符の直後に配置する必要があります。次のルートは前のルートに似ていますが、文字列 `.html` が生成値として設定されているため、`/index` の代わりに `/index.html` を生成します: +URL を生成するときに使われる文字列を、はっきり定義することもできます。その文字列は疑問符のすぐうしろに置かなければなりません。次のルートは前のものと似ていますが、`/index` の代わりに `/index.html` を生成します。生成に使う値として文字列 `.html` が設定されているからです。 ```php $router->addRoute('index', /* ... */); ``` -アプリケーションへの統合 -============ +組み込み +==== -作成したルータをアプリケーションに組み込むには、DIコンテナにそれについて伝える必要があります。最も簡単な方法は、ルータオブジェクトを作成するファクトリを準備し、コンテナの設定でそれを使用するように指示することです。その目的のために、メソッド `App\Core\RouterFactory::createRouter()` を記述するとしましょう: +作ったルーターをアプリケーションに組み込むには、DI コンテナにそれを教える必要があります。もっとも簡単なのは、ルーターのオブジェクトを作るファクトリを用意して、設定でそれを使うようにコンテナに伝えることです。そのために `App\Core\RouterFactory::createRouter()` メソッドを書くとしましょう。 ```php namespace App\Core; @@ -494,14 +520,14 @@ class RouterFactory } ``` -次に、[設定 |dependency-injection:services]に次のように記述します: +そして[設定 |dependency-injection:services]に次のように書きます。 ```neon services: - App\Core\RouterFactory::createRouter ``` -データベースなどへの依存関係は、[autowiring|dependency-injection:autowiring]を使用してファクトリメソッドにそのパラメータとして渡されます: +データベースなどへの依存関係があれば、[オートワイヤリング|dependency-injection:autowiring]によってファクトリメソッドのパラメータとして渡されます。 ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -514,22 +540,22 @@ public static function createRouter(Nette\Database\Connection $db): RouteList SimpleRouter ============ -ルートコレクションよりもはるかに単純なルータは、[SimpleRouter |api:Nette\Application\Routers\SimpleRouter]です。URLの形式に特別な要件がない場合、`mod_rewrite`(またはその代替)が利用できない場合、またはまだきれいなURLを扱いたくない場合に使用します。 +ルートのコレクションよりずっと単純なルーターが [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]です。URL の形に特別な要求がないとき、`mod_rewrite`(やその代わりになるもの)が使えないとき、あるいはまだきれいな URL に手を出したくないときに使います。 -おおよそ次のような形式のアドレスを生成します: +だいたい次のような形のアドレスを生成します。 ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -SimpleRouterのコンストラクタのパラメータは、パラメータなしでページを開いた場合(例:`http://example.com/`)にリダイレクトされるデフォルトのPresenterとアクションです。 +`SimpleRouter` のコンストラクタのパラメータは既定のプレゼンターとアクション、つまり `http://example.com/` をほかのパラメータなしで開いたときに実行されるアクションです。 ```php -// デフォルトのpresenterは 'Home'、アクションは 'default' になります +// 既定のプレゼンターは 'Home'、アクションは 'default' になります $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -SimpleRouterを[設定 |dependency-injection:services]で直接定義することをお勧めします: +SimpleRouter は[設定 |dependency-injection:services]で直接定義することをおすすめします。 ```neon services: @@ -537,22 +563,22 @@ services: ``` -SEOとカノニカル化 -========== +SEO と正規化 +======== -フレームワークは、異なるURLでコンテンツが重複するのを防ぐことで、SEO(検索エンジン最適化)に貢献します。特定のターゲットに複数のアドレス(例:`/index` と `/index.html`)がある場合、フレームワークは最初のものをプライマリ(カノニカル)として指定し、その他をHTTPコード301でリダイレクトします。これにより、検索エンジンはページを2回インデックス付けせず、ページランクを希釈しません。 +フレームワークは、異なる URL に同じ内容が現れるのを防ぐことで SEO(検索エンジン最適化)に貢献します。ある行き先に複数のアドレス、たとえば `/index` と `/index.html` が通じている場合、フレームワークは最初のものを主要なもの(正式なもの)と定め、ほかはそこへ HTTP コード 301 でリダイレクトします。おかげで検索エンジンがページを二重に登録して、そのページランクを薄めることがありません。 -このプロセスはカノニカル化と呼ばれます。カノニカルURLは、ルータによって生成されるURL、つまりOneWayフラグのないコレクション内の最初の適合するルートです。したがって、コレクションでは**プライマリルートを最初に**リストします。 +この処理を正規化と呼びます。正式な URL は、ルーターが生成するもの、つまりコレクションの中で最初に一致した OneWay フラグのないルートが生成するものです。ですからコレクションでは**主要なルートを先に**並べます。 -カノニカル化はPresenterによって実行されます。詳細は[カノニカル化 |presenters#カノニカル化]の章を参照してください。 +正規化はプレゼンターが行います。詳しくは [正規化 |presenters#正規化]の章をご覧ください。 HTTPS ===== -HTTPSプロトコルを使用するには、ホスティングで有効にし、サーバーを正しく設定する必要があります。 +HTTPS プロトコルを使うには、ホスティングでそれを有効にし、サーバーを正しく設定する必要があります。 -Webサイト全体をHTTPSにリダイレクトするには、サーバーレベルで設定する必要があります。たとえば、アプリケーションのルートディレクトリにある.htaccessファイルを使用して、HTTPコード301で設定します。設定はホスティングによって異なる場合があり、おおよそ次のようになります: +ウェブサイト全体の HTTPS へのリダイレクトは、サーバーの水準で、たとえばアプリケーションのルートディレクトリの `.htaccess` ファイルで HTTP コード 301 を使って設定しなければなりません。設定はホスティングによって違うことがあり、だいたい次のようになります。 ``` @@ -564,40 +590,40 @@ Webサイト全体をHTTPSにリダイレクトするには、サーバーレベ ``` -ルータはページが読み込まれたのと同じプロトコルでURLを生成するため、他に設定する必要はありません。 +ルーターはページが読み込まれたのと同じプロトコルで URL を生成するので、これ以上の設定は要りません。 -ただし、例外的に異なるルートを異なるプロトコルで実行する必要がある場合は、ルートのマスクで指定します: +とはいえ、例外的にルートごとに違うプロトコルで動かす必要があるなら、ルートのマスクで指定します。 ```php -// HTTPでアドレスを生成します +// HTTP のアドレスを生成します $router->addRoute('http://%host%//', /* ... */); -// HTTPSでアドレスを生成します +// HTTPS のアドレスを生成します $router->addRoute('https://%host%//', /* ... */); ``` -ルータのデバッグ -======== +ルーターのデバッグ +========= -[Tracy Bar |tracy:]に表示されるルーティングパネルは、ルートのリストと、ルータがURLから取得したパラメータを表示する便利なツールです。 +[Tracy Bar |tracy:]に表示されるルーティングのパネルは役に立つ助っ人で、ルートの一覧と、ルーターが URL から取り出したパラメータを見せてくれます。 -緑色のバーと✓記号は、現在のURLを処理したルートを表し、青色と≈記号は、緑色のルートが先行しなかった場合にURLを処理したであろうルートを示します。次に、現在のPresenterとアクションが表示されます。 +✓ の印が付いた緑の帯は、現在の URL を処理したルートを表します。青色と ≈ の印は、緑のルートに先を越されなければやはりその URL を処理したであろうルートを示します。さらに現在のプレゼンターとアクションも見られます。 [* routing-debugger.webp *] -同時に、[カノニカル化 |#SEOとカノニカル化]による予期しないリダイレクトが発生した場合、*redirect*バーのパネルを見て、ルータが最初にURLをどのように理解し、なぜリダイレクトしたかを確認すると便利です。 +同時に、[正規化 |#SEO と正規化]によって思いがけないリダイレクトが起きたときは、パネルの *redirect* の帯を見るとよいでしょう。そこでルーターがもともとその URL をどう解釈し、なぜリダイレクトしたのかがわかります。 .[note] -ルータをデバッグする際には、ブラウザで開発者ツール(Ctrl+Shift+IまたはCmd+Option+I)を開き、ネットワークパネルでキャッシュを無効にして、リダイレクトがキャッシュされないようにすることをお勧めします。 +ルーターをデバッグするときは、ブラウザで開発者ツールを開き(Ctrl+Shift+I または Cmd+Option+I)、Network のパネルでキャッシュを無効にして、リダイレクトが保存されないようにすることをおすすめします。 -パフォーマンス -======= +性能 +=== -ルートの数はルータの速度に影響します。その数は数十を超えるべきではありません。WebサイトのURL構造が複雑すぎる場合は、カスタム[#カスタムルータ]を作成できます。 +ルートの数はルーターの速さに影響します。その数は数十を超えないようにすべきです。ウェブサイトの URL の構造が複雑すぎるなら、独自の[ルーター |#独自のルーター]を書けます。 -ルータにデータベースなどの依存関係がなく、そのファクトリが引数を受け取らない場合は、そのコンパイル済み形式をDIコンテナに直接シリアライズして、アプリケーションをわずかに高速化できます。 +ルーターがデータベースなどへの依存関係を持たず、そのファクトリが引数を取らないなら、その組み立て済みの形を DI コンテナに直接シリアライズして、アプリケーションをわずかに速くできます。 ```neon routing: @@ -605,10 +631,10 @@ routing: ``` -カスタムルータ +独自のルーター ======= -以下の行は、非常に上級のユーザー向けです。独自のルータを作成し、それを自然にルートコレクションに統合できます。ルータは、2つのメソッドを持つ[api:Nette\Routing\Router]インターフェースの実装です: +以下の記述はとても進んだ利用者向けです。独自のルーターを作って、ルートのコレクションに自然に組み込めます。ルーターは 2 つのメソッドを持つ [api:Nette\Routing\Router]インターフェースの実装です。 ```php use Nette\Http\IRequest as HttpRequest; @@ -628,7 +654,7 @@ class MyRouter implements Nette\Routing\Router } ``` -メソッド `match` は、現在のリクエスト [$httpRequest |http:request] を処理し、そこからURLだけでなくヘッダーなども取得して、Presenter名とそのパラメータを含む配列に変換します。リクエストを処理できない場合は、nullを返します。 リクエストを処理する際には、少なくともPresenterとアクションを返す必要があります。Presenter名は完全であり、存在する可能性のあるモジュールも含まれます: +`match` メソッドは現在のリクエスト [$httpRequest |http:request](そこからは URL だけでなくヘッダーなども取り出せます)を、プレゼンターの名前とそのパラメータを含む配列に変えます。リクエストを処理できなければ null を返します。リクエストを処理するときは、少なくともプレゼンターを返さなければなりません。アクションは省略でき、指定がなければ `default` になります。プレゼンターの名前は完全で、モジュールも含みます。 ```php [ @@ -637,9 +663,9 @@ class MyRouter implements Nette\Routing\Router ] ``` -メソッド `constructUrl` は、逆にパラメータの配列から結果の絶対URLを構築します。そのために、パラメータ [`$refUrl`|api:Nette\Http\UrlScript](現在のURL)からの情報を使用できます。 +一方 `constructUrl` メソッドは、パラメータの配列から結果となる絶対 URL を組み立てます。そのとき現在の URL である [`$refUrl`|api:Nette\Http\UrlScript]パラメータの情報を使えます。 -`add()` を使用してルートコレクションに追加します: +`add()` でルートのコレクションに足します。 ```php $router = new Nette\Application\Routers\RouteList; @@ -649,16 +675,16 @@ $router->addRoute(/* ... */); ``` -スタンドアロンでの使用 -=========== +単独での利用 +====== -スタンドアロンでの使用とは、Nette ApplicationやPresenterを使用しないアプリケーションでルータの機能を利用することを意味します。この章で示したことのほとんどすべてが適用されますが、以下の違いがあります: +単独での利用とは、Nette Application とプレゼンターを使わないアプリケーションでルーターの機能を活かすことです。この章で見てきたことのほとんどはそこでも当てはまり、違いは次の点だけです。 -- ルートコレクションには[api:Nette\Routing\RouteList]クラスを使用します -- シンプルルータとして[api:Nette\Routing\SimpleRouter]クラスを使用します -- `Presenter:action` のペアが存在しないため、[#拡張表記]を使用します +- ルートのコレクションには [api:Nette\Routing\RouteList]クラスを使います +- 単純なルーターには [api:Nette\Routing\SimpleRouter]クラスを使います +- `Presenter:action` の組が存在しないので、[#詳しい書き方]を使います -したがって、再びルータを構築するメソッドを作成します。例: +ですからここでもルーターを組み立てるメソッドを作ります。たとえば次のようにです。 ```php namespace App\Core; @@ -682,35 +708,35 @@ class RouterFactory } ``` -DIコンテナを使用している場合(推奨)、再びメソッドを設定に追加し、その後ルータとHTTPリクエストをコンテナから取得します: +おすすめの DI コンテナを使っているなら、このメソッドをやはり設定に足して、コンテナからルーターを HTTP のリクエストとともに受け取ります。 ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -または、オブジェクトを直接作成します: +あるいはオブジェクトを直接作ります。 ```php $router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -これで、ルータを動作させるだけです: +あとはルーターに仕事をさせるだけです。 ```php $params = $router->match($httpRequest); if ($params === null) { - // 適合するルートが見つかりませんでした。エラー404を送信します + // 一致するルートが見つからなかったので 404 のエラーを送ります exit; } -// 取得したパラメータを処理します +// 得られたパラメータを処理します $controller = $params['controller']; // ... ``` -そして逆に、ルータを使用してリンクを構築します: +逆にルーターを使ってリンクを組み立てます。 ```php $params = ['controller' => 'ArticleController', 'id' => 123]; @@ -718,4 +744,6 @@ $url = $router->constructUrl($params, $httpRequest->getUrl()); ``` -{{composer: nette/router}} +{{composer: nette/routing}} +{{repo: nette/routing}} +{{api: https://api.nette.org/routing/}} diff --git a/application/ja/templates.texy b/application/ja/templates.texy index 2492f2325a..5e38d60b86 100644 --- a/application/ja/templates.texy +++ b/application/ja/templates.texy @@ -2,9 +2,9 @@ ****** .[perex] -Netteは[Latte |latte:]テンプレートエンジンを使用しています。これはPHPで最も安全なテンプレートエンジンであり、同時に最も直感的なシステムでもあるためです。多くの新しいことを学ぶ必要はなく、PHPの知識といくつかのタグで十分です。 +Nette は [Latte |latte:]というテンプレートシステムを使います。Latte を使うのは、PHP で最も安全なテンプレートシステムであり、同時に最も直感的なシステムだからです。新しく覚えることは多くありません。PHP の知識といくつかのタグで十分です。 -ページは通常、レイアウトテンプレートと特定のアクションのテンプレートから構成されます。これはレイアウトテンプレートの例です。`{block}`ブロックと`{include}`タグに注目してください: +ページはたいてい、レイアウトのテンプレートと個別のアクションのテンプレートから組み立てられます。レイアウトのテンプレートはたとえば次のような形です。`{block}` のブロックと `{include}` タグに注目してください。 ```latte @@ -20,7 +20,7 @@ Netteは[Latte |latte:]テンプレートエンジンを使用しています。 ``` -そして、これはアクションテンプレートになります: +そしてこれがアクションのテンプレートです。 ```latte {block title}Homepage{/block} @@ -31,15 +31,15 @@ Netteは[Latte |latte:]テンプレートエンジンを使用しています。 {/block} ``` -これは、レイアウトの`{include content}`の場所に挿入される`content`ブロックを定義し、レイアウトの`{block title}`を上書きする`title`ブロックも再定義します。結果を想像してみてください。 +ここでは `content` ブロックを定義していて、レイアウトの `{include content}` の場所に挿入されます。あわせて `title` ブロックを定義し直していて、レイアウトの `{block title}` を上書きします。結果を思い浮かべてみてください。 -テンプレートの検索 +テンプレートの探索 --------- -Presenterでどのテンプレートをレンダリングするかを指定する必要はありません。フレームワークはパスを自動的に推測し、記述の手間を省きます。 +プレゼンターでは、どのテンプレートを描くべきかを指定する必要はありません。フレームワークが自動的にパスを導くので、書く手間が省けます。 -各Presenterが独自のディレクトリを持つディレクトリ構造を使用している場合は、アクション(またはビュー)の名前でこのディレクトリにテンプレートを配置するだけです。つまり、アクション`default`にはテンプレート`default.latte`を使用します: +プレゼンターごとにディレクトリを持つ構成を使っているなら、そのディレクトリにアクション(つまりビュー)の名前でテンプレートを置くだけです。たとえば `default` アクションには `default.latte` テンプレートを使います。 /--pre app/ @@ -49,80 +49,113 @@ app/ └── default.latte \-- -Presenterが1つのディレクトリにまとめられ、テンプレートが`templates`フォルダにある構造を使用している場合は、ファイルを`..latte`または`/.latte`に保存します: +プレゼンターをひとつのディレクトリにまとめ、テンプレートを `templates` フォルダに置く構成を使っているなら、`..latte` か `/.latte` のファイルに保存します。 /--pre app/ └── Presenters/ ├── HomePresenter.php └── templates/ - ├── Home.default.latte ← 1番目のバリアント - └── Home/ - └── default.latte ← 2番目のバリアント + ├── Home/ + │ └── default.latte ← 1 つめの形 + └── Home.default.latte ← 2 つめの形 \-- -`templates`ディレクトリは、Presenterクラスを含むディレクトリと同じレベル、つまり1つ上のレベルに配置することもできます。 +`templates` ディレクトリは 1 階層上、つまりプレゼンターのクラスがあるディレクトリと同じ階層に置くこともできます。 -テンプレートが見つからない場合、Presenterは[エラー404 - ページが見つかりません |presenters#404エラーなど]で応答します。 +テンプレートが見つからない場合、プレゼンターは [404 - ページが見つかりません |presenters#404 などのエラー]で応えます。 -`$this->setView('jineView')`を使用してビューを変更します。`$this->template->setFile('/path/to/template.latte')`を使用してテンプレートファイルを直接指定することもできます。 +ビューは `$this->setView('otherView')` で変えられます。`$this->template->setFile('/path/to/template.latte')` でテンプレートのファイルを直接指定することもできます。 .[note] -テンプレートが検索されるファイルは、可能なファイル名の配列を返すメソッド[formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()]をオーバーライドすることで変更できます。 +テンプレートを探すファイルは、可能なファイル名の配列を返す [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()]メソッドを上書きすれば変えられます。 -レイアウトテンプレートの検索 --------------- +レイアウトのテンプレートの探索 +--------------- -Netteはレイアウトファイルも自動的に検索します。 +Nette はレイアウトのファイルも自動的に探します。 -各Presenterが独自のディレクトリを持つディレクトリ構造を使用している場合は、レイアウトをPresenterのフォルダに配置します(そのPresenterに固有の場合)。または、複数のPresenterで共有されている場合は1つ上のレベルに配置します: +プレゼンターごとにディレクトリを持つ構成を使っているなら、そのプレゼンター専用のレイアウトはプレゼンターのフォルダに、複数のプレゼンターで共通のレイアウトは 1 階層上に置きます。 /--pre app/ └── Presentation/ - ├── @layout.latte ← 共通レイアウト + ├── @layout.latte ← 共通のレイアウト └── Home/ - ├── @layout.latte ← Home presenter 専用 + ├── @layout.latte ← Home プレゼンター専用 ├── HomePresenter.php └── default.latte \-- -Presenterが1つのディレクトリにまとめられ、テンプレートが`templates`フォルダにある構造を使用している場合、レイアウトは次の場所にあると想定されます: +プレゼンターをひとつのディレクトリにまとめ、テンプレートを `templates` フォルダに置く構成なら、レイアウトは次の場所で探されます。 /--pre app/ └── Presenters/ ├── HomePresenter.php └── templates/ - ├── @layout.latte ← 共通レイアウト - ├── Home.@layout.latte ← Home 専用、1番目のバリアント - └── Home/ - └── @layout.latte ← Home 専用、2番目のバリアント + ├── @layout.latte ← 共通のレイアウト + ├── Home/ + │ └── @layout.latte ← Home 専用、1 つめの形 + └── Home.@layout.latte ← Home 専用、2 つめの形 \-- -Presenterがモジュール内にある場合、モジュールのネストに応じて、さらに上のディレクトリレベルでも検索されます。 +プレゼンターがモジュールの中にある場合は、モジュールの入れ子に応じてディレクトリの階層をさらに上へ探していきます。 -レイアウト名は`$this->setLayout('layoutAdmin')`を使用して変更でき、その場合、ファイル`@layoutAdmin.latte`にあると想定されます。`$this->setLayout('/path/to/template.latte')`を使用してレイアウトテンプレートファイルを直接指定することもできます。 +レイアウトの名前は `$this->setLayout('layoutAdmin')` で変えられ、その場合は `@layoutAdmin.latte` ファイルが期待されます。`$this->setLayout('/path/to/template.latte')` でレイアウトのテンプレートファイルを直接指定することもできます。 -`$this->setLayout(false)`またはテンプレート内の`{layout none}`タグを使用すると、レイアウト検索が無効になります。 +`$this->setLayout(false)` を使うか、テンプレートの中で `{layout none}` タグを使うと、レイアウトの探索を無効にできます。 .[note] -レイアウトテンプレートが検索されるファイルは、可能なファイル名の配列を返すメソッド[formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()]をオーバーライドすることで変更できます。 +レイアウトのテンプレートを探すファイルは、可能なファイル名の配列を返す [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()]メソッドを上書きすれば変えられます。 -テンプレート内の変数 ----------- +テンプレートの変数 +--------- -テンプレートに変数を渡すには、それらを`$this->template`に書き込みます。その後、テンプレート内でローカル変数として利用できます: +変数は `$this->template` に書き込むことでテンプレートに渡します。テンプレートの中ではローカル変数として使えるようになります。 ```php $this->template->article = $this->articles->getById($id); ``` -このようにして、任意の変数をテンプレートに簡単に渡すことができます。ただし、堅牢なアプリケーションを開発する場合、制限を設ける方が役立つ場合があります。たとえば、テンプレートが期待する変数のリストとその型を明示的に定義するなどです。これにより、PHPは型をチェックでき、IDEは正しく提案でき、静的解析はエラーを検出できます。 +プロパティの値を変数として自動的にテンプレートへ渡すには、`#[TemplateVariable]` アトリビュートを付け、public にします。 .{data-version:3.2.9} + +```php +use Nette\Application\Attributes\TemplateVariable; + +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + #[TemplateVariable] + public string $siteName = 'My blog'; +} +``` + +同じ名前の変数をテンプレートに渡した場合、`#[TemplateVariable]` はそれを上書きしません。 + + +既定の変数 +----- + +プレゼンターとコンポーネントは、いくつかの役立つ変数をテンプレートに自動的に渡します。 + +- `$basePath` はルートディレクトリへの絶対 URL パスです(たとえば `/eshop`) +- `$baseUrl` はルートディレクトリへの絶対 URL です(たとえば `http://localhost/eshop`) +- `$user` は[ユーザーを表す |security:authentication]オブジェクトです +- `$presenter` は現在のプレゼンターです +- `$control` は現在のコンポーネントまたはプレゼンターです +- `$flashes` は `flashMessage()` 関数で送られた[メッセージ |presenters#フラッシュメッセージ]の配列です + +独自のテンプレートクラスを使っている場合、これらの変数はそのためのプロパティを作れば渡されます。 + + +型安全なテンプレート +---------- + +堅牢なアプリケーションを開発するときは、テンプレートがどの変数をどんな型で期待するかを明示的に定義しておくと役立ちます。PHP による型チェック、IDE の賢い補完が得られ、静的解析でエラーを捕まえられるようになります。 -そして、そのようなリストをどのように定義するのでしょうか? 単純にクラスとそのプロパティの形式で定義します。Presenterと同様に名前を付けますが、最後に`Template`を付けます: +そうした一覧はどう定義するのでしょうか。テンプレートの変数を表すプロパティを持つクラスとして書くだけです。名前はプレゼンターに似せ、末尾に `Template` を付けます。 ```php /** @@ -137,26 +170,28 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template public Model\Article $article; public Nette\Security\User $user; - // その他の変数 + // そしてほかの変数 } ``` -Presenterの`$this->template`オブジェクトは、`ArticleTemplate`クラスのインスタンスになります。したがって、PHPは書き込み時に宣言された型をチェックします。そして、PHP 8.2以降では、存在しない変数への書き込みについても警告します。以前のバージョンでは、トレイト[Nette\SmartObject |utils:smartobject]を使用することで同じことが達成できます。 +これでプレゼンターの `$this->template` オブジェクトは `ArticleTemplate` クラスのインスタンスになります。書き込むとき、PHP が宣言された型を確認してくれます。 + +Nette はテンプレートのクラスを自動的に選びます。まず `Template` という名前のクラス、たとえば `edit` アクションなら `ArticleEditTemplate` を探し、それがない場合にだけ `Template` に戻ります。 -`@property-read`アノテーションはIDEと静的解析向けであり、これにより提案が機能します。「PhpStorm and code completion for $this⁠-⁠>⁠template」:https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template を参照してください。 +`@property-read` アノテーションは IDE と静的解析のためのもので、コード補完を可能にします。"PhpStorm と $this⁠-⁠>⁠template のコード補完":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template をご覧ください。 [* phpstorm-completion.webp *] -テンプレートでも提案の贅沢を楽しむことができます。PhpStormにLatteプラグインをインストールし、テンプレートの先頭にクラス名を指定するだけです。詳細は「Latte: 型システムの使い方」:https://blog.nette.org/en/latte-how-to-use-type-system の記事を参照してください: +テンプレートの中でも直接コード補完を使えます。PhpStorm 用の Latte プラグインをインストールし、テンプレートの先頭でテンプレートパラメータのクラス名を指定するだけです。詳しくは [Latte: 型システム |latte:type-system]の章をご覧ください。 ```latte {templateType App\Presentation\Article\ArticleTemplate} ... ``` -これはコンポーネントのテンプレートでも機能します。命名規則に従い、たとえばコンポーネント`FifteenControl`に対してテンプレートクラス`FifteenTemplate`を作成するだけです。 +同じことがコンポーネントにも当てはまります。命名の決まりに従って、`FifteenControl` のようなコンポーネントには `FifteenTemplate` というパラメータのクラスを作るだけです。 -`$template`を別のクラスのインスタンスとして作成する必要がある場合は、`createTemplate()`メソッドを使用します: +別のパラメータのクラスを使う必要があるなら、`createTemplate()` メソッドを使います。 ```php public function renderDefault(): void @@ -168,58 +203,100 @@ public function renderDefault(): void } ``` +.{data-version:3.3.0} +描画の前にテンプレートがどう仕上げられるかに手を入れたい場合、たとえばすべてのアクションで共通の変数を足したい場合は、プレゼンターの `completeTemplate()` メソッドを上書きできます。これはテンプレートが描かれる直前に呼ばれます。 -デフォルト変数 -------- - -Presenterとコンポーネントは、いくつかの便利な変数を自動的にテンプレートに渡します: - -- `$basePath` はルートディレクトリへの絶対URLパスです(例:`/eshop`) -- `$baseUrl` はルートディレクトリへの絶対URLです(例:`http://localhost/eshop`) -- `$user` は[ユーザーを表す |security:authentication]オブジェクトです -- `$presenter` は現在のPresenterです -- `$control` は現在のコンポーネントまたはPresenterです -- `$flashes` は関数 `flashMessage()` によって送信された[メッセージ |presenters#フラッシュメッセージ]の配列です - -独自のテンプレートクラスを使用している場合、これらの変数はプロパティを作成すれば渡されます。 +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My blog'; +} +``` リンクの作成 ------ -テンプレートでは、他のPresenterとアクションへのリンクは次のように作成されます: +テンプレートでは、ほかのプレゼンターやアクションへのリンクを次のように作ります。 ```latte -製品詳細 +商品の詳細 ``` -属性 `n:href` はHTMLタグ `` に非常に便利です。リンクを他の場所、たとえばテキスト内に出力したい場合は、`{link}` を使用します: +`n:href` 属性は HTML の `` タグにとても便利です。リンクをそれ以外の場所、たとえば文章の中に出したい場合は `{link}` を使います。 ```latte -アドレスは: {link Home:default} +URL は次のとおりです: {link Home:default} ``` -詳細については、[URLリンクの作成|creating-links]の章を参照してください。 +詳しくは [URL リンクの作成|creating-links]の章をご覧ください。 カスタムフィルタ、タグなど ------------- -Latteテンプレートシステムは、カスタムフィルタ、関数、タグなどで拡張できます。これは、`render`または`beforeRender()`メソッドで直接行うことができます: +Latte のテンプレートシステムは、独自のフィルタ、関数、タグなどで拡張できます。手早いその場しのぎの解から、アプリケーション全体のためのアーキテクチャ上のパターンまで、3 つの方法があります。 + +**プレゼンターのメソッドでその場しのぎに** + +最も手早いのは、プレゼンターやコンポーネントのコードで直接フィルタや関数を足す方法です。プレゼンターでは `beforeRender()` や `render()` メソッドがこれに向いています。 ```php -public function beforeRender(): void +protected function beforeRender(): void { // フィルタの追加 - $this->template->addFilter('foo', /* ... */); + $this->template->addFilter('money', fn($val) => '$' . number_format($val, 2)); + + // 関数の追加 + $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); +} +``` + +テンプレートでは次のようにします。 - // または Latte\Engine オブジェクトを直接設定 +```latte +

    価格: {$price|money}

    + +{if isWeekend($now)} ... {/if} +``` + +もっと複雑なロジックが必要なら、`Latte\Engine` オブジェクトを直接設定できます。 + +```php +protected function beforeRender(): void +{ $latte = $this->template->getLatte(); - $latte->addFilterLoader(/* ... */); + $latte->setFeature(Latte\Feature::MigrationWarnings); +} +``` + +**アトリビュートを使う** + +より優雅なのは、フィルタと関数をプレゼンターやコンポーネントの[テンプレートパラメータのクラス|#型安全なテンプレート]のメソッドとして定義し、アトリビュートで印を付ける方法です。 + +```php +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + #[Latte\Attributes\TemplateFilter] + public function money(float $val): string + { + return '$' . number_format($val, 2); + } + + #[Latte\Attributes\TemplateFunction] + public function isWeekend(DateTimeInterface $date): bool + { + return $date->format('N') >= 6; + } } ``` -Latteバージョン3では、より高度な方法として、各Webプロジェクト用に[extension |latte:extending-latte#Latte Extension]を作成する方法が提供されています。そのようなクラスの簡単な例: +Latte はこれらのアトリビュートが付いたメソッドを自動的に見つけて登録します。テンプレートでのフィルタや関数の名前はメソッド名と同じです。これらのメソッドは public でなければなりません。 + +**拡張を使って全体に** + +ここまでの方法は、特定のプレゼンターやコンポーネントでだけ必要なフィルタや関数に向いていて、アプリケーション全体には向きません。アプリケーション全体には[拡張 |latte:extending-latte#Latte Extension]を作るのが最適です。このクラスが、プロジェクトのすべての Latte の拡張をひとつにまとめます。短い例を挙げます。 ```php namespace App\Presentation\Accessory; @@ -251,11 +328,16 @@ final class LatteExtension extends Latte\Extension ]; } + private function filterTimeAgoInWords(DateTimeInterface $time): string + { + // ... + } + // ... } ``` -[設定 |configuration#Latte テンプレート]を使用して登録します: +拡張は[設定 |configuration#Latte テンプレート]で登録します。 ```neon latte: @@ -263,13 +345,27 @@ latte: - App\Presentation\Accessory\LatteExtension ``` +拡張にはいくつもの利点があります。依存性注入のサポート、アプリケーションのモデル層へのアクセス、そしてすべての拡張を一元的に管理できることです。独自のタグ、プロバイダ、コンパイラパスなどにも対応しています。 + + +すべてのテンプレートの設定 +------------- + +すべてのテンプレートを作る `TemplateFactory` サービスには、コールバックの公開配列 `$onCreate` があります。テンプレートが作られるたびに呼ばれるので、アプリケーションのすべてのテンプレートに対するフィルタ、関数、変数を 1 か所から設定できます。各コールバックは、新しく作られたテンプレートを受け取ります。`TemplateFactory` サービスを[注入してもらい |dependency-injection:passing-dependencies]、たとえばアプリケーションの起動時にコールバックを登録します。 + +```php +$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void { + $template->addFilter('money', fn($val) => '$' . number_format($val, 2)); +}; +``` + 翻訳 ----------- +--- -多言語アプリケーションをプログラミングしている場合、テンプレート内の一部のテキストを異なる言語で出力する必要があるでしょう。Nette Frameworkはこの目的のために、翻訳インターフェース[api:Nette\Localization\Translator]を定義しています。これには`translate()`という1つのメソッドがあります。これはメッセージ`$message`(通常は文字列)と任意の追加パラメータを受け取ります。タスクは翻訳された文字列を返すことです。 Netteにはデフォルトの実装はありません。[Componette |https://componette.org/search/localization]で見つけることができるいくつかの既製のソリューションから、ニーズに合わせて選択できます。それらのドキュメントで、トランスレータの設定方法を学びます。 +多言語のアプリケーションを作っているなら、テンプレートのテキストを言語ごとに出し分ける必要が出てくるでしょう。そのために Nette Framework は翻訳のインターフェース [api:Nette\Localization\Translator]を定義していて、`translate()` というメソッドをひとつだけ持ちます。メッセージ `$message`(ふつうは文字列です)とそのほかのパラメータを受け取ります。役目は翻訳された文字列を返すことです。Nette には既定の実装がありません。[Componette |https://componette.org/search/localization]にある既製の解決策から、必要に合うものを選べます。トランスレーターの設定方法は、それぞれのドキュメントで説明されています。 -テンプレートには、[渡してもらう |dependency-injection:passing-dependencies]トランスレータを`setTranslator()`メソッドで設定できます: +テンプレートには、[注入してもらった |dependency-injection:passing-dependencies]トランスレーターを `setTranslator()` メソッドで設定できます。 ```php protected function beforeRender(): void @@ -279,7 +375,7 @@ protected function beforeRender(): void } ``` -あるいは、トランスレータは[設定 |configuration#Latte テンプレート]を使用して設定することもできます: +あるいはトランスレーターを[設定 |configuration#Latte テンプレート]で指定することもできます。 ```neon latte: @@ -287,7 +383,7 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -その後、トランスレータは、たとえばフィルタ`|translate`として使用でき、`translate()`メソッドに渡される追加パラメータ(`foo, bar`を参照)も含みます: +そうすればトランスレーターを、たとえばフィルタ `|translate` として使えます。`translate()` メソッドに渡される追加のパラメータも書けます(`foo, bar` を参照)。 ```latte
    {='カート'|translate} @@ -295,7 +391,7 @@ latte: {$item|translate, foo, bar} ``` -またはアンダースコアタグとして: +あるいはアンダースコアのタグとして。 ```latte {_'カート'} @@ -303,14 +399,14 @@ latte: {_$item, foo, bar} ``` -テンプレートの一部を翻訳するには、ペアタグ`{translate}`があります(Latte 2.11以降、以前は`{_}`タグが使用されていました): +テンプレートの一部を翻訳するには、ペアタグ `{translate}` があります(Latte 2.11 以降。以前は `{_}` タグが使われていました)。 ```latte {translate}注文{/translate} {translate foo, bar}注文{/translate} ``` -トランスレータは通常、テンプレートのレンダリング中に実行時に呼び出されます。ただし、Latteバージョン3では、テンプレートのコンパイル中にすべての静的テキストを翻訳できます。これにより、各文字列が一度だけ翻訳され、結果の翻訳がコンパイル済み形式に書き込まれるため、パフォーマンスが節約されます。キャッシュディレクトリには、言語ごとに複数のコンパイル済みバージョンのテンプレートが作成されます。これを行うには、言語を2番目のパラメータとして指定するだけです: +トランスレーターはふつう、テンプレートが描かれる実行時に呼ばれます。ただし Latte のバージョン 3 は、すべての静的なテキストをテンプレートのコンパイル時に翻訳できます。各文字列が一度だけ翻訳され、その結果がコンパイル済みの形に書き込まれるので、性能が上がります。この場合、キャッシュディレクトリには言語ごとに複数のコンパイル済みテンプレートができます。そのためには、第 2 パラメータに言語を指定するだけです。 ```php protected function beforeRender(): void @@ -320,4 +416,4 @@ protected function beforeRender(): void } ``` -静的テキストとは、たとえば `{_'hello'}` や `{translate}hello{/translate}` のようなものを意味します。`{_$foo}` のような非静的テキストは、引き続き実行時に翻訳されます。 +静的なテキストとは、たとえば `{_'hello'}` や `{translate}hello{/translate}` のことです。`{_$foo}` のような静的でないテキストは、引き続き実行時に翻訳されます。 diff --git a/application/ja/upgrading.texy b/application/ja/upgrading.texy new file mode 100644 index 0000000000..e69196b350 --- /dev/null +++ b/application/ja/upgrading.texy @@ -0,0 +1,47 @@ +アップグレード +******* + + +バージョン 3.0 へのアップグレード +=================== + +Nette 3.0 では、メソッドのパラメータと戻り値に型宣言が加わりました。Nette を継承したクラス(プレゼンターやコンポーネントなど)でそうしたメソッドを上書きしている場合は、同じ型宣言を足さなければなりません。さもないと PHP が「Declaration must be compatible」のエラーを出します。 + +`Nette\Application\IRouter` インターフェースが変わりました。`match()` メソッドは `Nette\Application\Request` オブジェクトの代わりにパラメータの配列を返し、`constructUrl()` はそれを受け取るようになりました。 + +Nette は、各シグナルが同じオリジン(つまり同じドメインとサブドメイン)から送られたかを確認するようになりました。この同一オリジンポリシーは、起こり得る攻撃の経路を減らす助けになる決定的なセキュリティのしくみです。ほかのオリジンを許したい場合は、ハンドラのメソッドに `@crossOrigin` アノテーションを足してください。 + +```php +/** + * @crossOrigin + */ +public function handleXy(): void +{ +} +``` + +これはフォームの送信にも当てはまります。ほかのオリジンからの送信を許したい場合は、次のようにします。 + +```php +$form = new Nette\Application\UI\Form; +$form->allowCrossOrigin(); +``` + +`Nette\ComponentModel\Component` のコンストラクタは何年も使われておらず、バージョン 3.0 で削除されました。これは BC break です。`Nette\Application\UI\Presenter` を継承したコンポーネントやプレゼンターで親のコンストラクタを呼んでいる場合は、その呼び出しを削除しなければなりません。 + + +バージョン 2.4 へのアップグレード +=================== + +- `Route` と `SimpleRouter` は、サイトへのアクセスに使われたのと同じ HTTP/HTTPS のスキームを生成するようになりました。特定のプロトコルを要求するルートは、スキームを付けて定義できます。たとえば `Route('http://domain.cz/')` です。 +- render/action メソッドの bool 型のパラメータ(つまり既定値が true または false のもの)と永続パラメータについて、`false` と `null` が区別されるようになりました。パラメータが URL にない場合、その値は(以前の `false` ではなく)`null` になります。 +- `Presenter::getReflection()` が返すクラスは、もう `Nette\Reflection\ClassType` の子孫ではなく、`getReflection()->getMethod()` も `Nette\Reflection\Method` の子孫ではありません。 +- `SECURED` フラグと `Route::$defaultFlags` は非推奨です。 + + +バージョン 2.3 へのアップグレード +=================== + +- ルートとプレゼンター名は**大文字小文字を区別します**。プレゼンター名で誤った大文字小文字を使うと Nette が警告しますが、性能上の理由から Route のマスクは確認されないので、手で確かめてください。 +- `Route::addStyle()` と `Route::setStyleProperty()` は非推奨で、`E_USER_DEPRECATED` を出すようになりました。 +- テンプレートの拡張子 `.phtml` と古いリンクの構文はサポートされなくなりました。 diff --git a/application/meta.json b/application/meta.json index da1901a760..725762aab4 100644 --- a/application/meta.json +++ b/application/meta.json @@ -1,5 +1,6 @@ { - "version": "4.0", + "version": "4.x", "repo": "nette/application", - "composer": "nette/application" + "composer": "nette/application", + "api": "https://api.nette.org/application/" } diff --git a/application/pl/@home.texy b/application/pl/@home.texy index e7a87853d3..5f8b714738 100644 --- a/application/pl/@home.texy +++ b/application/pl/@home.texy @@ -2,13 +2,13 @@ Nette Application ***************** .[perex] -Nette Application jest rdzeniem frameworka Nette, który dostarcza potężne narzędzia do tworzenia nowoczesnych aplikacji internetowych. Oferuje szereg wyjątkowych funkcji, które znacząco ułatwiają rozwój oraz poprawiają bezpieczeństwo i utrzymywalność kodu. +Nette Application to rdzeń frameworka Nette, dostarczający potężne narzędzia do tworzenia nowoczesnych aplikacji webowych. Oferuje szereg wyjątkowych możliwości, które znacząco upraszczają tworzenie aplikacji oraz poprawiają bezpieczeństwo kodu i łatwość jego utrzymania. Instalacja ---------- -Bibliotekę pobierzesz i zainstalujesz za pomocą narzędzia [Composer|best-practices:composer]: +Bibliotekę pobierzesz i zainstalujesz za pomocą [Composera|best-practices:composer]: ```shell composer require nette/application @@ -18,68 +18,68 @@ composer require nette/application Dlaczego wybrać Nette Application? ---------------------------------- -Nette zawsze było pionierem w dziedzinie technologii internetowych. +Nette od zawsze było pionierem technologii webowych. -**Dwukierunkowy router:** Nette dysponuje zaawansowanym systemem routingu, który jest unikalny dzięki swojej dwukierunkowości - nie tylko tłumaczy URL na akcje aplikacji, ale także potrafi generować adresy URL wstecz. Oznacza to, że: -- Możesz w dowolnym momencie zmienić strukturę URL całej aplikacji bez konieczności modyfikowania szablonów +**Dwukierunkowy router:** Nette dysponuje zaawansowanym systemem routingu, unikalnym dzięki swojej dwukierunkowości - nie tylko tłumaczy URL na akcje aplikacji, ale potrafi też generować URL w drugą stronę. Oznacza to, że: +- strukturę URL całej aplikacji możesz zmienić w dowolnej chwili bez konieczności modyfikowania szablonów - URL są automatycznie kanonizowane, co poprawia SEO -- Routing jest definiowany w jednym miejscu, a nie rozproszony w adnotacjach - -**Komponenty i sygnały:** Wbudowany system komponentów inspirowany Delphi i React.js jest całkowicie wyjątkowy wśród frameworków PHP: -- Umożliwia tworzenie reużywalnych elementów UI -- Obsługuje hierarchiczne składanie komponentów -- Oferuje eleganckie przetwarzanie żądań AJAX za pomocą sygnałów -- Bogata biblioteka gotowych komponentów na [Componette](https://componette.org) - -**AJAX i snippety:** Nette wprowadziło rewolucyjny sposób pracy z AJAXem już w 2009 roku, długo przed podobnymi rozwiązaniami jak Hotwire dla Ruby on Rails czy Symfony UX Turbo: -- Snippety umożliwiają aktualizację tylko części strony bez konieczności pisania JavaScriptu -- Automatyczna integracja z systemem komponentów -- Inteligentna inwalidacja części stron -- Minimalna ilość przesyłanych danych - -**Intuicyjne szablony [Latte|latte:]:** Najbezpieczniejszy system szablonów dla PHP z zaawansowanymi funkcjami: -- Automatyczna ochrona przed XSS z kontekstowym escapowaniem -- Rozszerzalność za pomocą własnych filtrów, funkcji i znaczników -- Dziedziczenie szablonów i snippety dla AJAX -- Doskonałe wsparcie PHP 8.x z systemem typów - -**Dependency Injection:** Nette w pełni wykorzystuje Dependency Injection: -- Automatyczne przekazywanie zależności (autowiring) -- Konfiguracja za pomocą przejrzystego formatu NEON -- Wsparcie dla fabryk komponentów - - -Główne zalety -------------- - -- **Bezpieczeństwo**: Automatyczna obrona przed [podatnościami|nette:vulnerability-protection] takimi jak XSS, CSRF, itd. -- **Produktywność**: Mniej pisania, więcej funkcji dzięki inteligentnemu projektowi -- **Debugowanie**: [Debugger Tracy|tracy:] z panelem routingu -- **Wydajność**: Inteligentny cache, leniwe ładowanie komponentów -- **Elastyczność**: Łatwa modyfikacja URL nawet po zakończeniu aplikacji -- **Komponenty**: Unikalny system reużywalnych elementów UI -- **Nowoczesność**: Pełne wsparcie PHP 8.4+ i systemu typów - - -Zaczynamy ---------- - -1. [Jak działają aplikacje? |how-it-works] - Zrozumienie podstawowej architektury -2. [Presentery |presenters] - Praca z presenterami i akcjami -3. [Szablony |templates] - Tworzenie szablonów w Latte -4. [Routing |routing] - Konfiguracja adresów URL -5. [Komponenty interaktywne |components] - Wykorzystanie systemu komponentów - - -Kompatybilność z PHP --------------------- - -| wersja | kompatybilna z PHP -|-----------|------------------- -| Nette Application 4.0 | PHP 8.1 – 8.4 -| Nette Application 3.2 | PHP 8.1 – 8.4 -| Nette Application 3.1 | PHP 7.2 – 8.3 -| Nette Application 3.0 | PHP 7.1 – 8.0 -| Nette Application 2.4 | PHP 5.6 – 8.0 - -Dotyczy ostatniej wersji patch. +- routing definiowany jest w jednym miejscu, a nie rozproszony w adnotacjach + +**Komponenty i sygnały:** Wbudowany system komponentów zainspirowany Delphi i React.js jest unikalny wśród frameworków PHP: +- pozwala tworzyć elementy UI wielokrotnego użytku +- wspiera hierarchiczne składanie komponentów +- oferuje eleganckie obsługiwanie żądań AJAX za pomocą sygnałów +- bogata biblioteka gotowych komponentów na [Componette](https://componette.org) + +**AJAX i snippety:** Nette wprowadziło rewolucyjny sposób pracy z AJAX-em już w 2009 roku, na długo przed podobnymi rozwiązaniami, jak Hotwire dla Ruby on Rails czy Symfony UX Turbo: +- snippety pozwalają aktualizować tylko części strony bez konieczności pisania JavaScriptu +- automatyczna integracja z systemem komponentów +- inteligentne unieważnianie sekcji strony +- minimalny transfer danych + +**Intuicyjne szablony [Latte|latte:]:** Najbezpieczniejszy system szablonów dla PHP z zaawansowanymi możliwościami: +- automatyczna ochrona przed XSS z escapowaniem zależnym od kontekstu +- rozszerzalny o własne filtry, funkcje i tagi +- dziedziczenie szablonów i snippety dla AJAX-a +- doskonałe wsparcie dla PHP 8.x wraz z systemem typów + +**Wstrzykiwanie zależności:** Nette w pełni korzysta z wstrzykiwania zależności: +- automatyczne przekazywanie zależności (autowiring) +- konfiguracja w przejrzystym formacie NEON +- wsparcie dla fabryk komponentów + + +Główne korzyści +--------------- + +- **Bezpieczeństwo**: automatyczna ochrona przed [podatnościami|nette:vulnerability-protection], takimi jak XSS, CSRF itd. +- **Produktywność**: mniej pisania, więcej możliwości dzięki przemyślanej konstrukcji +- **Debugowanie**: [debugger Tracy|tracy:] z panelem routingu +- **Wydajność**: inteligentny cache, leniwe ładowanie komponentów +- **Elastyczność**: łatwa zmiana URL nawet po ukończeniu aplikacji +- **Komponenty**: unikalny system elementów UI wielokrotnego użytku +- **Nowoczesność**: pełne wsparcie dla PHP 8.3+ i systemu typów + + +Pierwsze kroki +-------------- + +1. [Jak działają aplikacje? |how-it-works] - zrozumienie podstawowej architektury +2. [Presentery |presenters] - praca z presenterami i akcjami +3. [Szablony |templates] - tworzenie szablonów w Latte +4. [Routing |routing] - konfigurowanie adresów URL +5. [Komponenty interaktywne |components] - korzystanie z systemu komponentów + + +Zgodność z PHP +-------------- + +| wersja | zgodna z PHP +|-----------------------|------------------- +| Nette Application 3.3 | PHP 8.3 - 8.5 +| Nette Application 3.2 | PHP 8.1 - 8.5 +| Nette Application 3.1 | PHP 7.2 - 8.3 +| Nette Application 3.0 | PHP 7.1 - 8.0 +| Nette Application 2.4 | PHP 5.6 - 8.0 + +Dotyczy najnowszych wersji patch. diff --git a/application/pl/@left-menu.texy b/application/pl/@left-menu.texy index 8600cb220d..ed22b0f063 100644 --- a/application/pl/@left-menu.texy +++ b/application/pl/@left-menu.texy @@ -1,22 +1,24 @@ Nette Application ***************** +- [Przegląd |@home] - [Jak działają aplikacje? |how-it-works] -- [Bootstrapping] -- [Presentery |presenters] -- [Szablony |templates] +- [Bootstrapping|bootstrapping] +- [Presentery|presenters] +- [Szablony|templates] - [Struktura katalogów |directory-structure] -- [Routing |routing] -- [Tworzenie linków URL |creating-links] +- [Routing|routing] +- [Tworzenie odnośników URL |creating-links] - [Komponenty interaktywne |components] -- [AJAX & snippety |ajax] -- [Multiplier |Multiplier] -- [Konfiguracja |configuration] +- [AJAX i snippety |ajax] +- [Multiplier |multiplier] +- [Konfiguracja|configuration] +- [Aktualizacja|upgrading] Dalsza lektura ************** -- [Dlaczego używać Nette? |www:10-reasons-why-nette] +- [Dlaczego warto używać Nette?|www:10-reasons-why-nette] - [Instalacja |nette:installation] -- [Pisanie pierwszej aplikacji! |quickstart:] -- [Przewodniki i dobre praktyki |best-practices:] +- [Stwórz swoją pierwszą aplikację! |quickstart:] +- [Dobre praktyki |best-practices:] - [Rozwiązywanie problemów |nette:troubleshooting] diff --git a/application/pl/ajax.texy b/application/pl/ajax.texy index d50269c03f..b7e7b78f9e 100644 --- a/application/pl/ajax.texy +++ b/application/pl/ajax.texy @@ -1,10 +1,10 @@ -AJAX & snippety +AJAX i snippety ***************
    -W erze nowoczesnych aplikacji internetowych, gdzie funkcjonalność jest często rozdzielona między serwerem a przeglądarką, AJAX jest niezbędnym elementem łączącym. Jakie możliwości oferuje nam Nette Framework w tej dziedzinie? -- wysyłanie fragmentów szablonu, tzw. snippetów +W erze nowoczesnych aplikacji webowych, w których funkcjonalność często rozłożona jest między serwer a przeglądarkę, AJAX jest niezbędnym elementem łączącym. Jakie możliwości oferuje w tym obszarze Nette Framework? +- wysyłanie części szablonu, zwanych snippetami - przekazywanie zmiennych między PHP a JavaScriptem - narzędzia do debugowania żądań AJAX @@ -14,9 +14,9 @@ W erze nowoczesnych aplikacji internetowych, gdzie funkcjonalność jest często Żądanie AJAX ============ -Żądanie AJAX zasadniczo nie różni się od klasycznego żądania HTTP. Wywoływany jest presenter z określonymi parametrami. Od presentera zależy, w jaki sposób zareaguje na żądanie - może zwrócić dane w formacie JSON, wysłać fragment kodu HTML, dokument XML itp. +Żądanie AJAX zasadniczo nie różni się od klasycznego żądania HTTP. Wywoływany jest presenter z określonymi parametrami. To do presentera należy decyzja, jak odpowiedzieć na żądanie - może zwrócić dane w formacie JSON, wysłać fragment kodu HTML, dokument XML itd. -Po stronie przeglądarki inicjujemy żądanie AJAX za pomocą funkcji `fetch()`: +Po stronie przeglądarki inicjujemy żądanie AJAX funkcją `fetch()`: ```js fetch(url, { @@ -24,22 +24,22 @@ fetch(url, { }) .then(response => response.json()) .then(payload => { - // przetwarzanie odpowiedzi + // przetwarzamy odpowiedź }); ``` -Po stronie serwera rozpoznajemy żądanie AJAX za pomocą metody `$httpRequest->isAjax()` usługi [enkapsulującej żądanie HTTP |http:request]. Do detekcji używa nagłówka HTTP `X-Requested-With`, dlatego ważne jest, aby go wysyłać. W ramach presentera można użyć metody `$this->isAjax()`. +Po stronie serwera żądanie AJAX rozpoznaje metoda `$httpRequest->isAjax()` usługi [enkapsulującej żądanie HTTP |http:request]. Do wykrycia używa nagłówka HTTP `X-Requested-With`, dlatego kluczowe jest jego wysłanie. Wewnątrz presentera możesz użyć metody `$this->isAjax()`. -Jeśli chcesz wysłać dane w formacie JSON, użyj metody [`sendJson()` |presenters#Wysłanie odpowiedzi]. Metoda ta również kończy działanie presentera. +Jeśli chcesz wysłać dane w formacie JSON, użyj metody [`sendJson()` |presenters#Wysyłanie odpowiedzi]. Metoda ta kończy również działanie presentera. ```php public function actionExport(): void { - $this->sendJson($this->model->getData); + $this->sendJson($this->model->getData()); } ``` -Jeśli planujesz odpowiedzieć za pomocą specjalnego szablonu przeznaczonego dla AJAX, możesz to zrobić w następujący sposób: +Jeśli planujesz odpowiedzieć specjalnym szablonem przeznaczonym dla AJAX-a, możesz zrobić to tak: ```php public function handleClick($param): void @@ -55,44 +55,44 @@ public function handleClick($param): void Snippety ======== -Najpotężniejszym narzędziem oferowanym przez Nette do łączenia serwera z klientem są snippety. Dzięki nim można przekształcić zwykłą aplikację w aplikację AJAXową przy minimalnym wysiłku i kilku linijkach kodu. Jak to wszystko działa, demonstruje przykład Fifteen, którego kod znajdziesz na [GitHubie |https://github.com/nette-examples/fifteen]. +Najpotężniejszym narzędziem, jakie Nette oferuje do łączenia serwera z klientem, są snippety. Dzięki nim zamienisz zwykłą aplikację w aplikację AJAX-ową minimalnym wysiłkiem i kilkoma wierszami kodu. Jak to wszystko działa, pokazuje przykład Fifteen, którego kod znajdziesz na [GitHubie |https://github.com/nette-examples/fifteen]. -Snippety, czyli fragmenty, umożliwiają aktualizację tylko części strony, zamiast ponownego ładowania całej strony. Jest to nie tylko szybsze i bardziej efektywne, ale także zapewnia bardziej komfortowe doświadczenie użytkownika. Snippety mogą przypominać Hotwire dla Ruby on Rails lub Symfony UX Turbo. Co ciekawe, Nette wprowadziło snippety już 14 lat wcześniej. +Snippety pozwalają aktualizować tylko części strony zamiast przeładowywać ją całą. Jest to nie tylko szybsze i wydajniejsze, ale daje też wygodniejsze doświadczenie użytkownika. Snippety mogą przypominać Ci Hotwire dla Ruby on Rails albo Symfony UX Turbo. Co ciekawe, Nette wprowadziło snippety 14 lat wcześniej. -Jak działają snippety? Przy pierwszym załadowaniu strony (żądanie nie-AJAXowe) ładowana jest cała strona wraz ze wszystkimi snippetami. Kiedy użytkownik wchodzi w interakcję ze stroną (np. klika przycisk, wysyła formularz itp.), zamiast ładowania całej strony wywoływane jest żądanie AJAX. Kod w presenterze wykonuje akcję i decyduje, które snippety należy zaktualizować. Nette renderuje te snippety i wysyła je w formie tablicy w formacie JSON. Kod obsługujący w przeglądarce wstawia otrzymane snippety z powrotem na stronę. Przesyłany jest więc tylko kod zmienionych snippetów, co oszczędza przepustowość i przyspiesza ładowanie w porównaniu do przesyłania całej zawartości strony. +Jak działają snippety? Przy pierwszym wczytaniu strony (żądanie nie-AJAX) wczytywana jest cała strona wraz ze wszystkimi snippetami. Gdy użytkownik wejdzie w interakcję ze stroną (np. kliknie przycisk, wyśle formularz itd.), zamiast przeładowania całej strony inicjowane jest żądanie AJAX. Kod w presenterze wykonuje akcję i decyduje, które snippety trzeba zaktualizować. Nette renderuje te snippety i wysyła je jako payload JSON zawierający tablicę ze snippetami. Kod obsługujący w przeglądarce wstawia następnie otrzymane snippety z powrotem na stronę. Przesyłany jest więc tylko kod zmienionych snippetów, co oszczędza pasmo i przyspiesza ładowanie w porównaniu z przesyłaniem treści całej strony. Jeśli żaden snippet nie zostanie unieważniony przez `redrawControl()`, Nette zwraca całą stronę nawet przy żądaniu AJAX - snippety wysyłane są dopiero wtedy, gdy coś zostanie unieważnione. Naja ---- -Do obsługi snippetów po stronie przeglądarki służy [biblioteka Naja |https://naja.js.org]. [Zainstaluj |https://naja.js.org/#/guide/01-install-setup-naja] ją jako pakiet node.js (do użytku z aplikacjami Webpack, Rollup, Vite, Parcel i innymi): +Do obsługi snippetów po stronie przeglądarki służy [biblioteka Naja |https://naja.js.org]. [Zainstaluj ją |https://naja.js.org/#/guide/01-install-setup-naja] jako pakiet Node.js (do użycia z bundlerami, takimi jak Webpack, Rollup, Vite, Parcel i inne): ```shell npm install naja ``` -…lub bezpośrednio wstaw do szablonu strony: +…albo wstaw ją bezpośrednio do szablonu strony: ```latte - + ``` -Najpierw należy [zainicjować |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] bibliotekę: +Najpierw trzeba bibliotekę [zainicjować |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: ```js naja.initialize(); ``` -Aby zwykły link (sygnał) lub wysłanie formularza przekształcić w żądanie AJAX, wystarczy oznaczyć odpowiedni link, formularz lub przycisk klasą `ajax`: +Aby zamienić zwykły odnośnik (sygnał) albo wysyłanie formularza w żądanie AJAX, wystarczy oznaczyć odpowiedni odnośnik, formularz albo przycisk klasą `ajax`: ```latte -Go +Idź -lub +albo
    @@ -100,46 +100,48 @@ lub ``` -Przerysowanie snippetów ------------------------ +Przerysowywanie snippetów +------------------------- -Każdy obiekt klasy [Control |components] (w tym sam Presenter) śledzi, czy nastąpiły zmiany wymagające jego przerysowania. Służy do tego metoda `redrawControl()`: +Każdy obiekt klasy [Control |components] (w tym sam Presenter) pilnuje, czy zaszły zmiany wymagające jego przerysowania. Służy do tego metoda `redrawControl()`: ```php public function handleLogin(string $user): void { - // po zalogowaniu należy przerysować odpowiednią część + // po zalogowaniu trzeba przerysować odpowiednią część $this->redrawControl(); // ... } ``` -Nette pozwala na jeszcze dokładniejszą kontrolę tego, co ma zostać przerysowane. Wspomniana metoda może bowiem przyjmować jako argument nazwę snippetu. Można więc unieważnić (czytaj: wymusić przerysowanie) na poziomie części szablonu. Jeśli unieważniony zostanie cały komponent, przerysowany zostanie również każdy jego snippet: +Nette pozwala na jeszcze precyzyjniejszą kontrolę tego, co trzeba przerysować. Metoda może przyjąć jako argument nazwę snippetu. Można więc unieważniać (czyli wymuszać przerysowanie) na poziomie części szablonu. Jeśli unieważniony zostanie cały komponent, przerysowany zostanie również każdy snippet w jego wnętrzu: ```php // unieważnia snippet 'header' $this->redrawControl('header'); ``` +Oczekujące unieważnienie możesz też anulować drugim parametrem `$redraw`: wywołanie `$this->redrawControl('header', redraw: false)` oznacza snippet jako niewymagający przerysowania. Pełna sygnatura to `redrawControl(?string $snippet = null, bool $redraw = true)`. + Snippety w Latte ---------------- -Używanie snippetów w Latte jest niezwykle proste. Aby zdefiniować część szablonu jako snippet, wystarczy otoczyć ją znacznikami `{snippet}` i `{/snippet}`: +Używanie snippetów w Latte jest wyjątkowo łatwe. Aby zdefiniować część szablonu jako snippet, wystarczy opakować ją tagami `{snippet}` i `{/snippet}`: ```latte {snippet header} -

    Witaj ...

    +

    Cześć ...

    {/snippet} ``` -Snippet tworzy w stronie HTML element `
    ` ze specjalnym wygenerowanym `id`. Podczas przerysowywania snippeta aktualizowana jest zawartość tego elementu. Dlatego konieczne jest, aby podczas pierwszego renderowania strony renderowane były również wszystkie snippety, nawet jeśli na początku mogą być puste. +Snippet tworzy na stronie HTML element `
    ` ze specjalnym wygenerowanym `id`. Przy przerysowaniu snippetu aktualizowana jest zawartość tego elementu. Dlatego konieczne jest, aby przy początkowym renderowaniu strony wyrenderowane zostały również wszystkie snippety, nawet jeśli na początku bywają puste. -Możesz również utworzyć snippet z innym elementem niż `
    ` za pomocą n:atrybutu: +Snippet możesz też utworzyć na elemencie innym niż `
    `, używając n:atrybutu: ```latte
    -

    Witaj ...

    +

    Cześć ...

    ``` @@ -155,7 +157,9 @@ Nazwy snippetów mogą być również wyrażeniami: {/foreach} ``` -W ten sposób powstanie kilka snippetów `item-0`, `item-1` itd. Gdybyśmy bezpośrednio unieważnili dynamiczny snippet (na przykład `item-1`), nic by się nie przerysowało. Powodem jest to, że snippety naprawdę działają jak wycinki i renderowane są tylko one same. Jednak w szablonie faktycznie nie ma żadnego snippeta o nazwie `item-1`. Powstaje on dopiero podczas wykonywania kodu wokół snippeta, czyli pętli foreach. Dlatego oznaczamy część szablonu, która ma zostać wykonana, za pomocą znacznika `{snippetArea}`: +Samo w sobie jest to niedziałający krok pośredni: wyrenderowany poza statycznym `{snippet}` albo `{snippetArea}` dynamiczny snippet wywołuje `E_USER_WARNING` z komunikatem *Dynamic snippets are allowed only inside static snippet/snippetArea.* Poprawimy to poniżej. + +Powstaje w ten sposób kilka snippetów w rodzaju `item-0`, `item-1` itd. Gdybyśmy unieważnili bezpośrednio dynamiczny snippet (np. `item-1`), nic nie zostałoby przerysowane. Powodem jest to, że snippety naprawdę działają jak wycinki i renderowane są bezpośrednio tylko one same. W szablonie technicznie nie ma jednak snippetu o nazwie `item-1`. Powstaje on dopiero wtedy, gdy wykona się kod otaczający snippet, czyli pętla foreach. Dlatego część szablonu, która ma zostać wykonana, oznaczamy tagiem `{snippetArea}`: ```latte
      @@ -165,7 +169,7 @@ W ten sposób powstanie kilka snippetów `item-0`, `item-1` itd. Gdybyśmy bezpo
    ``` -I zlecamy przerysowanie zarówno samego snippeta, jak i całego nadrzędnego obszaru: +I żądamy przerysowania zarówno pojedynczego snippetu, jak i całego obszaru nadrzędnego: ```php $this->redrawControl('itemsContainer'); @@ -174,7 +178,7 @@ $this->redrawControl('item-1'); Jednocześnie warto zadbać o to, aby tablica `$items` zawierała tylko te elementy, które mają zostać przerysowane. -Jeśli do szablonu za pomocą znacznika `{include}` wstawiamy inny szablon zawierający snippety, konieczne jest ponowne umieszczenie wstawionego szablonu w `snippetArea` i unieważnienie go razem ze snippetem: +Jeśli do głównego szablonu dołączymy tagiem `{include}` inny szablon zawierający snippety, trzeba dołączenie szablonu ponownie opakować w `snippetArea` i unieważnić je razem ze snippetem: ```latte {snippetArea include} @@ -198,22 +202,22 @@ $this->redrawControl('item'); Snippety w komponentach ----------------------- -Snippety można tworzyć również w [komponentach|components], a Nette będzie je automatycznie przerysowywać. Istnieje jednak pewne ograniczenie: do przerysowania snippetów wywoływana jest metoda `render()` bez parametrów. Oznacza to, że przekazywanie parametrów w szablonie nie będzie działać: +Snippety możesz tworzyć wewnątrz [komponentów|components], a Nette automatycznie je przerysuje. Jest jednak pewne ograniczenie: aby przerysować snippety, Nette wywołuje metodę `render()` bez żadnych parametrów. Przekazywanie parametrów w szablonie nie zadziała więc: ```latte OK {control productGrid} -nie będzie działać: +nie zadziała: {control productGrid $arg, $arg} {control productGrid:paginator} ``` -Wysyłanie danych użytkownika ----------------------------- +Wysyłanie własnych danych +------------------------- -Wraz ze snippetami możesz wysłać klientowi dowolne inne dane. Wystarczy je zapisać w obiekcie `payload`: +Razem ze snippetami możesz wysłać do klienta dowolne dodatkowe dane. Wystarczy zapisać je do obiektu `payload`: ```php public function actionDelete(int $id): void @@ -226,10 +230,16 @@ public function actionDelete(int $id): void ``` +Przekierowywanie +---------------- + +Podczas żądania AJAX metody `redirect()` i `redirectUrl()` nie wysyłają przekierowania HTTP. Zamiast tego zapisują docelowy URL do payloadu (obiektu danych wysyłanego w odpowiedzi AJAX), konkretnie do jego właściwości `payload.redirect`, i wysyłają go; samo przekierowanie wykonuje następnie biblioteka po stronie klienta (Naja). + + Przekazywanie parametrów ======================== -Jeśli za pomocą żądania AJAX wysyłamy parametry do komponentu, czy to parametry sygnału, czy parametry persistentne, musimy w żądaniu podać ich globalną nazwę, która zawiera również nazwę komponentu. Pełną nazwę parametru zwraca metoda `getParameterId()`. +Wysyłając do komponentu parametry przez żądanie AJAX, niezależnie od tego, czy są to parametry sygnału, czy parametry trwałe, musimy podać w żądaniu ich globalną nazwę, która zawiera również nazwę komponentu. Pełną nazwę parametru zwraca metoda `getParameterId()`. ```js let url = new URL({link //foo!}); @@ -240,10 +250,16 @@ fetch(url, { }) ``` -A metoda handle z odpowiednimi parametrami w komponencie: +A metoda handle z odpowiadającymi parametrami w komponencie: ```php public function handleFoo(int $bar): void { } ``` + + +Dalsza lektura +============== + +- [Dynamiczne snippety |best-practices:dynamic-snippets] diff --git a/application/pl/bootstrapping.texy b/application/pl/bootstrapping.texy index 4cc015bed0..13b0288e7b 100644 --- a/application/pl/bootstrapping.texy +++ b/application/pl/bootstrapping.texy @@ -3,19 +3,22 @@ Bootstrapping
    -Bootstrapping to proces inicjalizacji środowiska aplikacji, tworzenia kontenera dependency injection (DI) i uruchamiania aplikacji. Omówimy: +Bootstrapping to proces inicjalizacji środowiska aplikacji, tworzenia kontenera wstrzykiwania zależności (DI) i uruchamiania aplikacji. Omówimy: - jak klasa Bootstrap inicjalizuje środowisko -- jak aplikacje są konfigurowane przy użyciu plików NEON -- jak rozróżnić tryb produkcyjny i deweloperski +- jak konfiguruje się aplikacje za pomocą plików NEON +- jak odróżnić tryb produkcyjny od deweloperskiego - jak utworzyć i skonfigurować kontener DI
    -Aplikacje, czy to webowe, czy skrypty uruchamiane z wiersza poleceń, rozpoczynają swoje działanie od pewnej formy inicjalizacji środowiska. W dawnych czasach odpowiadał za to plik o nazwie np. `include.inc.php`, który był dołączany przez plik początkowy. W nowoczesnych aplikacjach Nette zastąpiła go klasa `Bootstrap`, którą jako część aplikacji znajdziesz w pliku `app/Bootstrap.php`. Może wyglądać na przykład tak: +Aplikacje, zarówno webowe, jak i skrypty uruchamiane z wiersza poleceń, zaczynają swoje działanie od jakiejś formy inicjalizacji środowiska. Dawniej odpowiadał za to plik nazywany może `include.inc.php`, dołączany przez plik początkowy. W nowoczesnych aplikacjach Nette zastąpiła go klasa `Bootstrap`, którą jako część aplikacji znajdziesz w pliku `app/Bootstrap.php`. Może wyglądać na przykład tak: ```php +namespace App; + +use Nette; use Nette\Bootstrap\Configurator; class Bootstrap @@ -26,9 +29,9 @@ class Bootstrap public function __construct() { $this->rootDir = dirname(__DIR__); - // Konfigurator jest odpowiedzialny za ustawienie środowiska aplikacji i usług. + // konfigurator odpowiada za ustawienie środowiska aplikacji i usług $this->configurator = new Configurator; - // Ustawia katalog dla plików tymczasowych generowanych przez Nette (np. skompilowane szablony) + // ustawiamy katalog na pliki tymczasowe generowane przez Nette (np. skompilowane szablony) $this->configurator->setTempDirectory($this->rootDir . '/temp'); } @@ -42,13 +45,13 @@ class Bootstrap private function initializeEnvironment(): void { // Nette jest sprytne i tryb deweloperski włącza się automatycznie, - // lub możesz go włączyć dla konkretnego adresu IP, odkomentowując poniższą linię: + // albo możesz włączyć go dla konkretnego adresu IP, odkomentowując poniższy wiersz: // $this->configurator->setDebugMode('secret@23.75.345.200'); - // Aktywuje Tracy: ostateczny "szwajcarski scyzoryk" do debugowania. + // włącza Tracy: najlepszy "scyzoryk szwajcarski" do debugowania $this->configurator->enableTracy($this->rootDir . '/log'); - // RobotLoader: automatycznie ładuje wszystkie klasy w wybranym katalogu + // RobotLoader: automatycznie wczytuje wszystkie klasy z wybranego katalogu $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); @@ -56,7 +59,7 @@ class Bootstrap private function setupContainer(): void { - // Ładuje pliki konfiguracyjne + // wczytujemy pliki konfiguracyjne $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } @@ -66,69 +69,78 @@ class Bootstrap index.php ========= -Plikiem początkowym w przypadku aplikacji webowych jest `index.php`, który znajduje się w [katalogu publicznym |directory-structure#Katalog publiczny www] `www/`. Ten plik zleca klasie Bootstrap inicjalizację środowiska i utworzenie kontenera DI. Następnie pobiera z niego usługę `Application`, która uruchamia aplikację webową: +W przypadku aplikacji webowych plikiem początkowym jest `index.php`, leżący w [katalogu publicznym |directory-structure#Katalog publiczny www/] `www/`. Zleca on klasie Bootstrap zainicjowanie środowiska i utworzenie kontenera DI. Następnie pobiera z kontenera usługę `Application`, która uruchamia aplikację webową: ```php $bootstrap = new App\Bootstrap; -// Inicjalizacja środowiska + utworzenie kontenera DI +// inicjalizacja środowiska + utworzenie kontenera DI $container = $bootstrap->bootWebApplication(); -// Kontener DI tworzy obiekt Nette\Application\Application +// kontener DI tworzy obiekt Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// Uruchomienie aplikacji Nette i przetworzenie przychodzącego żądania +// uruchamiamy aplikację Nette i obsługujemy przychodzące żądanie $application->run(); ``` -Jak widać, w ustawieniu środowiska i tworzeniu kontenera dependency injection (DI) pomaga klasa [api:Nette\Bootstrap\Configurator], którą teraz bliżej przedstawimy. +.[note] +Obiekt `$application` podczas obsługi żądania emituje [zdarzenia |nette:glossary#Zdarzenia]: `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown` i `onError` (przy nieobsłużonym wyjątku). Możesz podpiąć do nich handlery, co przydaje się do logowania albo monitorowania całej aplikacji. + +Jak widać, w ustawieniu środowiska i utworzeniu kontenera wstrzykiwania zależności (DI) pomaga klasa [api:Nette\Bootstrap\Configurator]. Przedstawimy ją teraz szczegółowo. -Tryb deweloperski vs produkcyjny -================================ +Tryb deweloperski kontra produkcyjny +==================================== -Nette zachowuje się różnie w zależności od tego, czy działa na serwerze deweloperskim czy produkcyjnym: +Nette zachowuje się różnie w zależności od tego, czy działa na serwerze deweloperskim, czy produkcyjnym: -🛠️ Tryb deweloperski (Development): - - Wyświetla pasek debugowania Tracy z użytecznymi informacjami (zapytania SQL, czas wykonania, użyta pamięć) - - W przypadku błędu wyświetla szczegółową stronę błędu z wywołaniami funkcji i zawartością zmiennych - - Automatycznie odświeża cache przy zmianie szablonów Latte, modyfikacji plików konfiguracyjnych itp. +🛠️ Tryb deweloperski: + - wyświetla pasek debugowania Tracy z przydatnymi informacjami (zapytania SQL, czas wykonania, zużyta pamięć) + - przy błędzie wyświetla szczegółową stronę błędu z wywołaniami funkcji i zawartością zmiennych + - automatycznie odświeża cache przy zmianie szablonów Latte, plików konfiguracyjnych itd. -🚀 Tryb produkcyjny (Production): - - Nie wyświetla żadnych informacji debugowania, wszystkie błędy zapisuje do logu - - W przypadku błędu wyświetla ErrorPresenter lub ogólną stronę "Server Error" - - Cache nigdy nie jest automatycznie odświeżany! - - Zoptymalizowany pod kątem szybkości i bezpieczeństwa +🚀 Tryb produkcyjny: + - nie wyświetla żadnych informacji debugowych, wszystkie błędy zapisuje do logu + - przy błędzie wyświetla ErrorPresenter albo ogólną stronę "Server Error" + - cache nigdy nie odświeża się automatycznie! + - zoptymalizowany pod kątem szybkości i bezpieczeństwa -Wybór trybu odbywa się przez autodetekcję, więc zazwyczaj nie trzeba niczego konfigurować ani ręcznie przełączać: +Tryb wybierany jest przez autodetekcję, więc zwykle nie trzeba niczego konfigurować ani przełączać ręcznie: -- tryb deweloperski: na localhost (adres IP `127.0.0.1` lub `::1`) jeśli nie ma proxy (tj. jego nagłówka HTTP) +- tryb deweloperski: na localhoście (adres IP `127.0.0.1` albo `::1`), o ile nie ma proxy (czyli nie wykryto jego nagłówka HTTP) - tryb produkcyjny: wszędzie indziej Jeśli chcemy włączyć tryb deweloperski również w innych przypadkach, na przykład dla programistów łączących się z konkretnego adresu IP, użyjemy `setDebugMode()`: ```php -$this->configurator->setDebugMode('23.75.345.200'); // można podać również tablicę adresów IP +$this->configurator->setDebugMode('23.75.345.200'); // można podać także tablicę adresów IP ``` -Zdecydowanie zalecamy łączenie adresu IP z ciasteczkiem (cookie). W ciasteczku `nette-debug` zapisujemy tajny token, np. `secret1234`, i w ten sposób aktywujemy tryb deweloperski dla programistów łączących się z konkretnego adresu IP i jednocześnie posiadających w ciasteczku wspomniany token: +Zdecydowanie zalecamy połączenie adresu IP z ciasteczkiem. W ciasteczku `nette-debug` zapisz tajny token, np. `secret1234`, i w ten sposób aktywuj tryb deweloperski dla programistów łączących się z konkretnego adresu IP, którzy mają w ciasteczku również wspomniany token: ```php $this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Tryb deweloperski możemy również całkowicie wyłączyć, nawet dla localhost: +Tryb deweloperski możemy też całkowicie wyłączyć, nawet dla localhosta: ```php $this->configurator->setDebugMode(false); ``` -Uwaga, wartość `true` włącza tryb deweloperski na stałe, co nigdy nie powinno mieć miejsca na serwerze produkcyjnym. +Zwróć uwagę, że wartość `true` wymusza włączenie trybu deweloperskiego, co **nigdy** nie powinno wydarzyć się na serwerze produkcyjnym. + +Autodetekcją zajmuje się wewnętrznie statyczna metoda `Configurator::detectDebugMode()`, którą możesz wywołać także samodzielnie, na przykład aby wykryć tryb deweloperski poza konfiguratorem. Przyjmuje opcjonalną białą listę adresów IP albo nazw komputerów i zwraca, czy bieżące żądanie ma działać w trybie deweloperskim: + +```php +$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200'); +``` -Narzędzie debugujące Tracy -========================== +Narzędzie do debugowania Tracy +============================== -Dla łatwego debugowania włączymy jeszcze świetne narzędzie [Tracy |tracy:]. W trybie deweloperskim wizualizuje błędy, a w trybie produkcyjnym loguje błędy do podanego katalogu: +Dla łatwego debugowania włączymy znakomite narzędzie [Tracy |tracy:]. W trybie deweloperskim wizualizuje błędy, a w trybie produkcyjnym loguje je do wskazanego katalogu: ```php $this->configurator->enableTracy($this->rootDir . '/log'); @@ -138,19 +150,19 @@ $this->configurator->enableTracy($this->rootDir . '/log'); Pliki tymczasowe ================ -Nette wykorzystuje cache dla kontenera DI, RobotLoadera, szablonów itp. Dlatego konieczne jest ustawienie ścieżki do katalogu, w którym będzie przechowywany cache: +Nette używa cache dla kontenera DI, RobotLoadera, szablonów itd. Dlatego trzeba ustawić ścieżkę do katalogu, w którym cache będzie przechowywany: ```php $this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` -Na Linuksie lub macOS ustaw katalogom `log/` i `temp/` [uprawnienia do zapisu |nette:troubleshooting#Ustawianie uprawnień do katalogów]. +Na Linuksie albo macOS ustaw katalogom `log/` i `temp/` [prawa do zapisu |nette:troubleshooting#Ustawienie uprawnień do katalogów]. RobotLoader =========== -Zazwyczaj będziemy chcieli automatycznie ładować klasy za pomocą [RobotLoadera |robot-loader:], musimy go więc uruchomić i pozwolić mu ładować klasy z katalogu, w którym znajduje się `Bootstrap.php` (tj. `__DIR__`), oraz wszystkich podkatalogów: +Zwykle będziemy chcieli automatycznie wczytywać klasy za pomocą [RobotLoadera |robot-loader:], musimy więc go uruchomić i pozwolić mu wczytywać klasy z katalogu, w którym leży `Bootstrap.php` (czyli `__DIR__`), oraz ze wszystkich jego podkatalogów: ```php $this->configurator->createRobotLoader() @@ -158,13 +170,13 @@ $this->configurator->createRobotLoader() ->register(); ``` -Alternatywnym podejściem jest ładowanie klas wyłącznie przez [Composer |best-practices:composer] przy zachowaniu PSR-4. +Alternatywnym podejściem jest wczytywanie klas wyłącznie przez [Composera |best-practices:composer] zgodnie z PSR-4. Strefa czasowa ============== -Za pomocą konfiguratora można ustawić domyślną strefę czasową. +Za pomocą konfiguratora możesz ustawić domyślną strefę czasową. ```php $this->configurator->setTimeZone('Europe/Prague'); @@ -174,14 +186,16 @@ $this->configurator->setTimeZone('Europe/Prague'); Konfiguracja kontenera DI ========================= -Częścią procesu startowego jest utworzenie kontenera DI, czyli fabryki obiektów, która jest sercem całej aplikacji. Jest to właściwie klasa PHP, którą generuje Nette i zapisuje w katalogu z cache. Fabryka tworzy kluczowe obiekty aplikacji, a za pomocą plików konfiguracyjnych instruujemy ją, jak ma je tworzyć i ustawiać, co wpływa na zachowanie całej aplikacji. +Częścią procesu startowego jest utworzenie kontenera DI, czyli fabryki obiektów, która jest sercem całej aplikacji. To w rzeczywistości klasa PHP generowana przez Nette i zapisywana w katalogu cache. Fabryka produkuje kluczowe obiekty aplikacji, a za pomocą plików konfiguracyjnych instruujemy ją, jak ma je tworzyć i ustawiać, wpływając tym samym na zachowanie całej aplikacji. -Pliki konfiguracyjne zazwyczaj zapisuje się w formacie [NEON |neon:format]. W osobnym rozdziale dowiesz się, [co można skonfigurować |nette:configuring]. +Pliki konfiguracyjne zapisywane są zwykle w [formacie NEON |neon:format]. W osobnym rozdziale przeczytasz, [co można skonfigurować |nette:configuring]. .[tip] -W trybie deweloperskim kontener automatycznie aktualizuje się przy każdej zmianie kodu lub plików konfiguracyjnych. W trybie produkcyjnym generowany jest tylko raz, a zmiany nie są sprawdzane w celu maksymalizacji wydajności. +W trybie deweloperskim kontener aktualizuje się automatycznie przy każdej zmianie kodu albo plików konfiguracyjnych. W trybie produkcyjnym generowany jest tylko raz, a zmiany nie są sprawdzane, aby zmaksymalizować wydajność. -Pliki konfiguracyjne ładujemy za pomocą `addConfig()`: +Podczas gdy `createContainer()` buduje kontener i zwraca jego instancję, metoda `loadContainer()` zwraca tylko nazwę wygenerowanej klasy kontenera, której instancję możesz potem utworzyć samodzielnie. Przydaje się to w zaawansowanych scenariuszach. + +Pliki konfiguracyjne wczytuje się metodą `addConfig()`: ```php $this->configurator->addConfig($this->rootDir . '/config/common.neon'); @@ -198,17 +212,17 @@ if (PHP_SAPI === 'cli') { } ``` -Nazwa `cli.php` nie jest pomyłką, konfiguracja może być zapisana również w pliku PHP, który zwraca ją jako tablicę. +Nazwa `cli.php` nie jest literówką; konfigurację można zapisać także w pliku PHP, który zwraca ją jako tablicę. -Możemy również dodać inne pliki konfiguracyjne w [sekcji `includes` |dependency-injection:configuration#Dołączanie plików]. +Kolejne pliki konfiguracyjne możemy dodać również [w sekcji `includes` |dependency-injection:configuration#Dołączanie plików]. -Jeśli w plikach konfiguracyjnych pojawią się elementy o tych samych kluczach, zostaną one nadpisane lub w przypadku [tablic połączone |dependency-injection:configuration#Łączenie]. Później dołączony plik ma wyższy priorytet niż poprzedni. Plik, w którym znajduje się sekcja `includes`, ma wyższy priorytet niż pliki w nim zawarte. +Jeśli w plikach konfiguracyjnych pojawią się elementy o tych samych kluczach, zostaną nadpisane albo, w przypadku [tablic, scalone |dependency-injection:configuration#Scalanie]. Plik dołączony później ma wyższy priorytet niż poprzedni. Plik, w którym wymieniona jest sekcja `includes`, ma wyższy priorytet niż pliki w nim dołączone. Parametry statyczne ------------------- -Parametry używane w plikach konfiguracyjnych możemy zdefiniować [w sekcji `parameters` |dependency-injection:configuration#Parametry], a także przekazywać (lub nadpisywać) metodą `addStaticParameters()` (ma alias `addParameters()`). Ważne jest, że różne wartości parametrów spowodują wygenerowanie kolejnych kontenerów DI, czyli kolejnych klas. +Parametry używane w plikach konfiguracyjnych można definiować [w sekcji `parameters` |dependency-injection:configuration#Parametry], a także przekazywać (albo nadpisywać) metodą `addStaticParameters()` (której starszy, obecnie przestarzały alias to `addParameters()`). Ważne jest, że różne wartości parametrów spowodują wygenerowanie kolejnych kontenerów DI, czyli kolejnych klas. ```php $this->configurator->addStaticParameters([ @@ -216,13 +230,13 @@ $this->configurator->addStaticParameters([ ]); ``` -Do parametru `projectId` można odwołać się w konfiguracji za pomocą zwykłego zapisu `%projectId%`. +Do parametru `projectId` można odwołać się w konfiguracji standardowym zapisem `%projectId%`. Parametry dynamiczne -------------------- -Do kontenera możemy dodać również parametry dynamiczne, których różne wartości, w przeciwieństwie do parametrów statycznych, nie spowodują generowania nowych kontenerów DI. +Do kontenera możemy dodać również parametry dynamiczne, których różne wartości, w odróżnieniu od parametrów statycznych, nie spowodują wygenerowania nowych kontenerów DI. ```php $this->configurator->addDynamicParameters([ @@ -230,7 +244,7 @@ $this->configurator->addDynamicParameters([ ]); ``` -W prosty sposób możemy dodać np. zmienne środowiskowe, do których można się następnie odwołać w konfiguracji za pomocą zapisu `%env.variable%`. +W ten sposób łatwo dodamy na przykład zmienne środowiskowe, do których w konfiguracji można potem odwołać się zapisem `%env.zmienna%`. ```php $this->configurator->addDynamicParameters([ @@ -242,21 +256,22 @@ $this->configurator->addDynamicParameters([ Parametry domyślne ------------------ -W plikach konfiguracyjnych można używać następujących parametrów statycznych: +W plikach konfiguracyjnych możesz używać tych parametrów: -- `%appDir%` to ścieżka absolutna do katalogu z plikiem `Bootstrap.php` -- `%wwwDir%` to ścieżka absolutna do katalogu z plikiem wejściowym `index.php` -- `%tempDir%` to ścieżka absolutna do katalogu dla plików tymczasowych -- `%vendorDir%` to ścieżka absolutna do katalogu, w którym Composer instaluje biblioteki -- `%rootDir%` to ścieżka absolutna do katalogu głównego projektu -- `%debugMode%` wskazuje, czy aplikacja jest w trybie debugowania -- `%consoleMode%` wskazuje, czy żądanie przyszło przez wiersz poleceń +- `%appDir%` to bezwzględna ścieżka do katalogu zawierającego plik `Bootstrap.php` +- `%wwwDir%` to bezwzględna ścieżka do katalogu zawierającego plik wejściowy `index.php` +- `%tempDir%` to bezwzględna ścieżka do katalogu na pliki tymczasowe +- `%vendorDir%` to bezwzględna ścieżka do katalogu, w którym Composer instaluje biblioteki +- `%rootDir%` to bezwzględna ścieżka do katalogu głównego projektu +- `%baseUrl%` to bezwzględny URL katalogu głównego (parametr dynamiczny wyliczany w czasie działania) +- `%debugMode%` wskazuje, czy aplikacja działa w trybie debug +- `%consoleMode%` wskazuje, czy żądanie przyszło z wiersza poleceń Usługi importowane ------------------ -Teraz zagłębiamy się bardziej. Chociaż celem kontenera DI jest tworzenie obiektów, wyjątkowo może zaistnieć potrzeba wstawienia istniejącego obiektu do kontenera. Robimy to, definiując usługę z flagą `imported: true`. +Teraz zejdziemy głębiej. Choć zadaniem kontenera DI jest tworzenie obiektów, czasem może pojawić się potrzeba wstawienia do kontenera istniejącego obiektu. Robimy to, definiując usługę z flagą `imported: true`. ```neon services: @@ -274,10 +289,10 @@ $this->configurator->addServices([ ``` -Odmienne środowisko -=================== +Różne środowiska +================ -Nie bój się modyfikować klasy Bootstrap według własnych potrzeb. Do metody `bootWebApplication()` możesz dodać parametry do rozróżniania projektów webowych. Możemy też dodać inne metody, na przykład `bootTestEnvironment()`, która inicjalizuje środowisko dla testów jednostkowych, `bootConsoleApplication()` dla skryptów wywoływanych z wiersza poleceń itp. +Śmiało modyfikuj klasę `Bootstrap` według swoich potrzeb. Do metody `bootWebApplication()` możesz dodać parametry odróżniające projekty webowe. Albo możemy dodać inne metody, na przykład `bootTestEnvironment()`, która inicjalizuje środowisko dla testów jednostkowych, `bootConsoleApplication()` dla skryptów wywoływanych z wiersza poleceń itd. ```php public function bootTestEnvironment(): Nette\DI\Container diff --git a/application/pl/components.texy b/application/pl/components.texy index 0ddc2e7e66..189db24a74 100644 --- a/application/pl/components.texy +++ b/application/pl/components.texy @@ -3,7 +3,7 @@ Komponenty interaktywne
    -Komponenty to samodzielne obiekty wielokrotnego użytku, które wstawiamy na strony. Mogą to być formularze, datagridy, ankiety, właściwie wszystko, co ma sens używać wielokrotnie. Pokażemy: +Komponenty to osobne obiekty wielokrotnego użytku, które osadzamy w stronach. Mogą to być formularze, datagridy, ankiety, w zasadzie wszystko, co warto wykorzystywać wielokrotnie. Pokażemy: - jak używać komponentów? - jak je pisać? @@ -11,19 +11,19 @@ Komponenty to samodzielne obiekty wielokrotnego użytku, które wstawiamy na str
    -Nette ma wbudowany system komponentów. Coś podobnego mogą pamiętać weterani z Delphi lub ASP.NET Web Forms, na czymś zdalnie podobnym opiera się React czy Vue.js. Jednak w świecie frameworków PHP jest to unikalna sprawa. +Nette ma wbudowany system komponentów. Coś podobnego mogą kojarzyć weterani z Delphi albo ASP.NET Web Forms; React czy Vue.js zbudowane są na czymś odlegle podobnym. W świecie frameworków PHP jest to jednak funkcja unikalna. -Przy tym komponenty zasadniczo wpływają na podejście do tworzenia aplikacji. Możesz bowiem składać strony z gotowych jednostek. Potrzebujesz w administracji datagrid? Znajdziesz go na [Componette |https://componette.org/search/component], repozytorium open-source dodatków (czyli nie tylko komponentów) dla Nette i po prostu wstawisz do presentera. +Jednocześnie komponenty zasadniczo wpływają na podejście do tworzenia aplikacji. Możesz składać strony z wcześniej przygotowanych jednostek. Potrzebujesz datagrida w swojej administracji? Znajdź go na [Componette |https://componette.org/search/component], repozytorium dodatków open source (nie tylko komponentów) do Nette, i po prostu wstaw do presentera. -Do presentera możesz włączyć dowolną liczbę komponentów. A do niektórych komponentów możesz wstawiać kolejne komponenty. Powstaje w ten sposób drzewo komponentów, którego korzeniem jest presenter. +Do presentera możesz włączyć dowolną liczbę komponentów. A w niektórych komponentach możesz osadzić kolejne. Powstaje w ten sposób drzewo komponentów, którego korzeniem jest presenter. -Metody fabrykujące -================== +Metody fabryczne +================ -Jak wstawiać komponenty do presentera i następnie ich używać? Zazwyczaj za pomocą metod fabrykujących. +Jak komponenty trafiają do presentera i jak się ich potem używa? Zwykle za pomocą metod fabrycznych. -Fabryka komponentów stanowi elegancki sposób na tworzenie komponentów dopiero w chwili, gdy są rzeczywiście potrzebne (lazy / on demand). Cały urok polega na implementacji metody o nazwie `createComponent()`, gdzie `` to nazwa tworzonego komponentu, która tworzy i zwraca komponent. +Fabryka komponentów to elegancki sposób tworzenia komponentów dopiero wtedy, gdy są rzeczywiście potrzebne (lazy / on demand). Cała magia polega na zaimplementowaniu metody o nazwie `createComponent()`, gdzie `` to nazwa tworzonego komponentu, która komponent tworzy i zwraca. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -31,49 +31,54 @@ class DefaultPresenter extends Nette\Application\UI\Presenter protected function createComponentPoll(): PollControl { $poll = new PollControl; - $poll->items = $this->item; + $poll->items = $this->items; return $poll; } } ``` -Dzięki temu, że wszystkie komponenty są tworzone w osobnych metodach, kod zyskuje na przejrzystości. +Ponieważ wszystkie komponenty tworzone są w osobnych metodach, kod staje się przejrzystszy. .[note] -Nazwy komponentów zawsze zaczynają się małą literą, mimo że w nazwie metody pisane są z dużej. +Nazwy komponentów zawsze zaczynają się małą literą, mimo że w nazwie metody pisane są wielką. -Fabryk nigdy nie wywołujemy bezpośrednio, wywołują się same w chwili, gdy komponent użyjemy po raz pierwszy. Dzięki temu komponent jest tworzony we właściwym momencie i tylko wtedy, gdy jest rzeczywiście potrzebny. Jeśli komponentu nie użyjemy (np. przy żądaniu AJAX, gdy przesyłana jest tylko część strony, lub przy cachowaniu szablonu), nie zostanie on w ogóle utworzony i oszczędzimy wydajność serwera. +Fabryk nigdy nie wywołujemy bezpośrednio; wywoływane są automatycznie przy pierwszym użyciu komponentu. Dzięki temu komponent tworzony jest we właściwym momencie i tylko wtedy, gdy naprawdę jest potrzebny. Jeśli komponentu nie użyjemy (np. przy żądaniu AJAX, gdzie przesyłana jest tylko część strony, albo przy cachowaniu szablonu), w ogóle nie zostanie utworzony, co oszczędza wydajność serwera. ```php .{file:DefaultPresenter.php} -// uzyskujemy dostęp do komponentu i jeśli to było po raz pierwszy, -// wywołuje się createComponentPoll(), która go tworzy +// sięgamy po komponent i jeśli był to pierwszy raz, +// wywoła się createComponentPoll(), które go utworzy $poll = $this->getComponent('poll'); -// alternatywna składnia: $poll = $this['poll']; +// alternatywny zapis: $poll = $this['poll']; ``` -W szablonie można wyrenderować komponent za pomocą znacznika [{control} |#Renderowanie]. Nie ma więc potrzeby ręcznego przekazywania komponentów do szablonu. +W szablonie komponent można wyrenderować tagiem [{control} |#Renderowanie]. Nie ma więc potrzeby ręcznego przekazywania komponentów do szablonu. ```latte -

    Głosuj

    +

    Zagłosuj

    {control poll} ``` +.[tip] +Do dynamicznego tworzenia zmiennej liczby komponentów użyj [Multipliera |multiplier]. + +Metody fabryczne `createComponent()` działają nie tylko w presenterach. W ten sam sposób możesz zagnieździć komponent w innym komponencie, składając je w drzewo - przydaje się to na przykład przy osobno renderowanym formularzu wewnątrz komponentu. -Styl Hollywood -============== -Komponenty często używają jednej świeżej techniki, którą lubimy nazywać stylem Hollywood. Na pewno znasz skrzydlate zdanie, które tak często słyszą uczestnicy castingów filmowych: „Nie dzwońcie do nas, my zadzwonimy do was”. I właśnie o to chodzi. +Styl hollywoodzki +================= -W Nette bowiem, zamiast ciągle pytać („czy formularz został wysłany?”, „czy był poprawny?” lub „czy użytkownik nacisnął ten przycisk?”), mówisz frameworkowi „kiedy to się stanie, wywołaj tę metodę” i zostawiasz dalszą pracę jemu. Jeśli programujesz w JavaScript, ten styl programowania jest Ci dobrze znany. Piszesz funkcje, które są wywoływane, gdy nastąpi określone zdarzenie. A język przekazuje im odpowiednie parametry. +Komponenty powszechnie korzystają ze świeżej techniki, którą lubimy nazywać stylem hollywoodzkim. Na pewno znasz frazes, który często słyszą uczestnicy castingów filmowych: "Nie dzwoń do nas, my zadzwonimy do ciebie". I dokładnie o to chodzi. -To całkowicie zmienia spojrzenie na pisanie aplikacji. Im więcej zadań możesz zostawić frameworkowi, tym mniej masz pracy. I tym mniej możesz czegoś np. pominąć. +W Nette, zamiast nieustannie zadawać pytania ("czy formularz został wysłany?", "czy był poprawny?", "czy użytkownik nacisnął ten przycisk?"), mówisz frameworkowi "gdy to się stanie, wywołaj tę metodę" i zostawiasz mu dalszą pracę. Jeśli programujesz w JavaScripcie, dobrze znasz ten styl programowania. Piszesz funkcje, które wywoływane są, gdy nastąpi określone zdarzenie. A język przekazuje im odpowiednie parametry. + +Całkowicie zmienia to perspektywę pisania aplikacji. Im więcej zadań możesz zostawić frameworkowi, tym mniej masz pracy. I tym mniej możesz przeoczyć. Pisanie komponentu ================== -Pod pojęciem komponent zazwyczaj rozumiemy potomka klasy [api:Nette\Application\UI\Control]. (Dokładniej byłoby więc używać terminu „controls”, ale „kontrolki” mają w języku polskim zupełnie inne znaczenie i raczej przyjęły się „komponenty”.) Sam presenter [api:Nette\Application\UI\Presenter] jest zresztą również potomkiem klasy `Control`. +Przez pojęcie komponent rozumiemy zwykle potomka klasy [api:Nette\Application\UI\Control]. (Trafniej byłoby używać terminu "controls", ale w niektórych językach ma on inne znaczenie, a "komponenty" bardziej się przyjęły.) Sam presenter [api:Nette\Application\UI\Presenter] również jest potomkiem klasy `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -87,7 +92,7 @@ class PollControl extends Control Renderowanie ============ -Już wiemy, że do renderowania komponentu używa się znacznika `{control componentName}`. Ten znacznik właściwie wywołuje metodę `render()` komponentu, w której dbamy o renderowanie. Do dyspozycji mamy, tak samo jak w presenterze, [szablon Latte|templates] w zmiennej `$this->template`, do której przekazujemy parametry. W przeciwieństwie do presentera musimy podać plik z szablonem i zlecić jego wyrenderowanie: +Wiemy już, że do wyrenderowania komponentu służy tag `{control nazwaKomponentu}`. Wywołuje on w rzeczywistości metodę `render()` komponentu, w której zajmujemy się renderowaniem. Mamy do dyspozycji, tak jak w presenterze, [szablon Latte|templates] w zmiennej `$this->template`, do której przekazujemy parametry. W odróżnieniu od presentera musimy podać plik szablonu i kazać go wyrenderować: ```php .{file:PollControl.php} public function render(): void @@ -99,7 +104,7 @@ public function render(): void } ``` -Znacznik `{control}` umożliwia przekazanie parametrów do metody `render()`: +Tag `{control}` pozwala przekazać metodzie `render()` parametry: ```latte {control poll $id, $message} @@ -112,7 +117,7 @@ public function render(int $id, string $message): void } ``` -Czasami komponent może składać się z kilku części, które chcemy renderować oddzielnie. Dla każdej z nich tworzymy własną metodę renderującą, tutaj w przykładzie np. `renderPaginator()`: +Czasem komponent może składać się z kilku części, które chcemy renderować osobno. Dla każdej z nich tworzymy własną metodę renderującą, tutaj w przykładzie `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +126,69 @@ public function renderPaginator(): void } ``` -A w szablonie wywołujemy ją za pomocą: +A w szablonie wywołujemy ją potem tak: ```latte {control poll:paginator} ``` -Dla lepszego zrozumienia warto wiedzieć, jak ten znacznik jest tłumaczony na PHP. +Dla lepszego zrozumienia warto wiedzieć, jak ten tag przekłada się na kod PHP. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -zostanie przetłumaczone jako: +przekłada się na: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -Metoda `getComponent()` zwraca komponent `poll` i na tym komponencie wywołuje metodę `render()`, lub `renderPaginator()`, jeśli inny sposób renderowania jest podany w znaczniku za dwukropkiem. +Metoda `getComponent()` zwraca komponent `poll`, a na tym komponencie wywoływana jest metoda `render()` albo `renderPaginator()`, jeśli w tagu po dwukropku podano inną metodę renderującą. .[caution] -Uwaga, jeśli gdziekolwiek w parametrach pojawi się **`=>`**, wszystkie parametry zostaną zapakowane do tablicy i przekazane jako pierwszy argument: +Uwaga, jeśli w parametrach poza nawiasami kwadratowymi pojawi się **`=>`**, wszystkie parametry zostaną opakowane w tablicę i przekazane jako pierwszy argument: ```latte {control poll, id: 123, message: 'hello'} ``` -zostanie przetłumaczone jako: +przekłada się na: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Renderowanie podkomponentu: +Renderowanie subkomponentu: ```latte {control cartControl-someForm} ``` -zostanie przetłumaczone jako: +przekłada się na: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Komponenty, podobnie jak presentery, automatycznie przekazują do szablonów kilka użytecznych zmiennych: +Komponenty, podobnie jak presentery, automatycznie przekazują do szablonów kilka przydatnych zmiennych: -- `$basePath` to absolutna ścieżka URL do katalogu głównego (np. `/eshop`) -- `$baseUrl` to absolutny URL do katalogu głównego (np. `http://localhost/eshop`) +- `$basePath` to bezwzględna ścieżka URL do katalogu głównego (np. `/eshop`) +- `$baseUrl` to bezwzględny URL katalogu głównego (np. `http://localhost/eshop`) - `$user` to obiekt [reprezentujący użytkownika |security:authentication] -- `$presenter` to aktualny presenter -- `$control` to aktualny komponent -- `$flashes` to tablica [wiadomości |#Wiadomości flash] wysłanych przez funkcję `flashMessage()` +- `$presenter` to bieżący presenter +- `$control` to bieżący komponent +- `$flashes` to tablica [wiadomości |#Wiadomości flash] wysłanych funkcją `flashMessage()` Sygnał ====== -Już wiemy, że nawigacja w aplikacji Nette polega na linkowaniu lub przekierowywaniu do par `Presenter:action`. Ale co, jeśli chcemy tylko wykonać akcję na **aktualnej stronie**? Na przykład zmienić sortowanie kolumn w tabeli; usunąć pozycję; przełączyć tryb jasny/ciemny; wysłać formularz; zagłosować w ankiecie; itp. +Wiemy już, że nawigacja w aplikacji Nette polega na linkowaniu albo przekierowywaniu do par `Presenter:akcja`. A co, jeśli chcemy tylko wykonać akcję na **bieżącej stronie**? Na przykład zmienić sortowanie kolumn w tabeli; usunąć element; przełączyć tryb jasny/ciemny; wysłać formularz; zagłosować w ankiecie itd. -Ten rodzaj żądań nazywa się sygnałami. I podobnie jak akcje wywołują metody `action()` lub `render()`, sygnały wywołują metody `handle()`. Podczas gdy pojęcie akcji (lub widoku) jest związane wyłącznie z presenterami, sygnały dotyczą wszystkich komponentów. A więc także presenterów, ponieważ `UI\Presenter` jest potomkiem `UI\Control`. +Ten typ żądania nazywa się sygnałem. I tak jak akcje wywołują metody `action()` albo `render()`, sygnały wywołują metody `handle()`. Podczas gdy pojęcie akcji (albo widoku) dotyczy wyłącznie presenterów, sygnały dotyczą wszystkich komponentów. A więc i presenterów, bo `UI\Presenter` jest potomkiem `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +197,36 @@ public function handleClick(int $x, int $y): void } ``` -Link, który wywoła sygnał, tworzymy w zwykły sposób, czyli w szablonie za pomocą atrybutu `n:href` lub znacznika `{link}`, w kodzie za pomocą metody `link()`. Więcej w rozdziale [Tworzenie linków URL |creating-links#Linki do sygnału]. +Odnośnik wywołujący sygnał tworzy się w zwykły sposób, czyli w szablonie atrybutem `n:href` albo tagiem `{link}`, a w kodzie metodą `link()`. Więcej w rozdziale [Tworzenie odnośników URL |creating-links#Odnośniki do sygnału]. ```latte kliknij tutaj ``` -Sygnał zawsze jest wywoływany na aktualnym presenterze i akcji, nie można go wywołać na innym presenterze lub innej akcji. +Sygnał wywoływany jest zawsze na bieżącym presenterze i akcji; nie da się wywołać go na innym presenterze albo innej akcji. -Sygnał powoduje więc ponowne załadowanie strony dokładnie tak samo, jak przy pierwotnym żądaniu, tylko dodatkowo wywołuje metodę obsługującą sygnał z odpowiednimi parametrami. Jeśli metoda nie istnieje, rzucany jest wyjątek [api:Nette\Application\UI\BadSignalException], który użytkownikowi wyświetla się jako strona błędu 403 Forbidden. +Sygnał powoduje więc przeładowanie strony dokładnie jak pierwotne żądanie, ale dodatkowo wywołuje metodę obsługującą sygnał z odpowiednimi parametrami. Jeśli metoda nie istnieje, zgłaszany jest wyjątek [api:Nette\Application\UI\BadSignalException], który wyświetlany jest użytkownikowi jako strona błędu 403 Forbidden. Snippety i AJAX =============== -Sygnały mogą trochę przypominać AJAX: handlery, które są wywoływane na aktualnej stronie. I masz rację, sygnały naprawdę często są wywoływane za pomocą AJAXu, a następnie przesyłamy do przeglądarki tylko zmienione części strony. Czyli tzw. snippety. Więcej informacji znajdziesz na [stronie poświęconej AJAX |ajax]. +Sygnały mogą trochę przypominać Ci AJAX: handlery wywoływane na bieżącej stronie. I masz rację, sygnały rzeczywiście często wywoływane są przez AJAX, a następnie do przeglądarki przesyłane są tylko zmienione części strony. Nazywamy je snippetami. Więcej informacji znajdziesz na [stronie poświęconej AJAX-owi |ajax]. Wiadomości flash ================ -Komponent ma własny magazyn wiadomości flash, niezależny od presentera. Są to wiadomości, które np. informują o wyniku operacji. Ważną cechą wiadomości flash jest to, że są dostępne w szablonie nawet po przekierowaniu. Nawet po wyświetleniu pozostają aktywne przez kolejne 30 sekund – na przykład na wypadek, gdyby z powodu błędnego transferu użytkownik odświeżył stronę - wiadomość mu więc od razu nie zniknie. +Komponent ma własny magazyn wiadomości flash, niezależny od presentera. To wiadomości informujące na przykład o wyniku operacji. Ważną cechą wiadomości flash jest to, że są dostępne w szablonie również po przekierowaniu. Nawet po wyświetleniu pozostają aktywne przez kolejne 30 sekund, na przykład na wypadek, gdyby użytkownik odświeżył stronę z powodu błędu transmisji - wiadomość nie zniknie od razu. -Wysyłanie zapewnia metoda [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Pierwszym parametrem jest tekst wiadomości lub obiekt `stdClass` reprezentujący wiadomość. Opcjonalnym drugim parametrem jest jej typ (error, warning, info itp.). Metoda `flashMessage()` zwraca instancję wiadomości flash jako obiekt `stdClass`, do którego można dodawać kolejne informacje. +Wysyłaniem zajmuje się metoda [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Pierwszym parametrem jest tekst wiadomości (`string`, `Stringable`) albo obiekt `stdClass` reprezentujący wiadomość. Opcjonalnym drugim parametrem jest jej typ (error, warning, info itd.). Metoda `flashMessage()` zwraca instancję wiadomości flash jako obiekt `stdClass`, do którego można dodać dalsze informacje. ```php -$this->flashMessage('Pozycja została usunięta.'); +$this->flashMessage('Element został usunięty.'); $this->redirect(/* ... */); // i przekierowujemy ``` -W szablonie te wiadomości są dostępne w zmiennej `$flashes` jako obiekty `stdClass`, które zawierają właściwości `message` (tekst wiadomości), `type` (typ wiadomości) i mogą zawierać już wspomniane informacje użytkownika. Wyrenderujemy je na przykład tak: +Wiadomości te dostępne są w szablonie w zmiennej `$flashes` jako obiekty `stdClass`, które zawierają właściwości `message` (tekst wiadomości), `type` (typ wiadomości) i mogą zawierać wspomniane informacje użytkownika. Renderujemy je na przykład tak: ```latte {foreach $flashes as $flash} @@ -230,39 +235,39 @@ W szablonie te wiadomości są dostępne w zmiennej `$flashes` jako obiekty `std ``` -Przekierowanie po sygnale -========================= +Przekierowanie po przetworzeniu sygnału +======================================= -Po przetworzeniu sygnału komponentu często następuje przekierowanie. Jest to podobna sytuacja jak w przypadku formularzy - po ich wysłaniu również przekierowujemy, aby przy odświeżeniu strony w przeglądarce nie doszło do ponownego wysłania danych. +Po przetworzeniu sygnału komponentu często następuje przekierowanie. Przypomina to formularze - po ich wysłaniu również przekierowujemy, aby zapobiec ponownemu wysłaniu danych przy odświeżeniu strony w przeglądarce. ```php -$this->redirect('this') // przekierowuje na aktualny presenter i akcję +$this->redirect('this'); // przekierowuje na bieżący presenter i akcję ``` -Ponieważ komponent jest elementem wielokrotnego użytku i zazwyczaj nie powinien mieć bezpośredniego powiązania z konkretnymi presenterami, metody `redirect()` i `link()` automatycznie interpretują parametr jako sygnał komponentu: +Ponieważ komponent jest elementem wielokrotnego użytku i zwykle nie powinien mieć bezpośredniego powiązania z konkretnymi presenterami, metody `redirect()` i `link()` automatycznie interpretują parametr jako sygnał komponentu: ```php -$this->redirect('click') // przekierowuje na sygnał 'click' tego samego komponentu +$this->redirect('click'); // przekierowuje na sygnał 'click' tego samego komponentu ``` -Jeśli potrzebujesz przekierować na inny presenter lub akcję, możesz to zrobić za pośrednictwem presentera: +Jeśli potrzebujesz przekierować na inny presenter albo inną akcję, możesz zrobić to przez presenter: ```php $this->getPresenter()->redirect('Product:show'); // przekierowuje na inny presenter/akcję ``` -Parametry persistentne -====================== +Parametry trwałe +================ -Parametry persistentne służą do utrzymywania stanu w komponentach między różnymi żądaniami. Ich wartość pozostaje taka sama nawet po kliknięciu na link. W przeciwieństwie do danych w sesji, są one przesyłane w URL. I to całkowicie automatycznie, w tym w linkach tworzonych w innych komponentach na tej samej stronie. +Parametry trwałe służą do utrzymywania stanu w komponentach między różnymi żądaniami. Ich wartość pozostaje taka sama również po kliknięciu w odnośnik. W odróżnieniu od danych w sesji przesyłane są w URL. I dzieje się to całkowicie automatycznie, łącznie z odnośnikami tworzonymi w innych komponentach na tej samej stronie. -Masz np. komponent do paginacji treści. Takich komponentów może być na stronie kilka. I chcemy, aby po kliknięciu na link wszystkie komponenty pozostały na swojej aktualnej stronie. Dlatego z numeru strony (`page`) zrobimy parametr persistentny. +Masz na przykład komponent do stronicowania treści. Takich komponentów może być na stronie kilka. I chcemy, aby po kliknięciu w odnośnik wszystkie komponenty pozostały na swojej bieżącej stronie. Dlatego numer strony (`page`) czynimy parametrem trwałym. -Tworzenie parametru persistentnego w Nette jest niezwykle proste. Wystarczy utworzyć publiczną właściwość i oznaczyć ją atrybutem: (wcześniej używano `/** @persistent */`) +Utworzenie parametru trwałego w Nette jest wyjątkowo proste. Wystarczy utworzyć właściwość publiczną i oznaczyć ją atrybutem: (wcześniej używano `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // ta linia jest ważna +use Nette\Application\Attributes\Persistent; // ten wiersz jest ważny class PaginatingControl extends Control { @@ -271,43 +276,43 @@ class PaginatingControl extends Control } ``` -Przy właściwości zalecamy podanie również typu danych (np. `int`) i można podać również wartość domyślną. Wartości parametrów można [walidować |#Walidacja parametrów persistentnych]. +Zalecamy podanie typu danych właściwości (np. `int`), możesz też podać wartość domyślną. Wartości parametrów można [walidować |#Walidacja parametrów trwałych]. -Podczas tworzenia linku można zmienić wartość parametru persistentnego: +Przy tworzeniu odnośnika wartość parametru trwałego można zmienić: ```latte -następna +dalej ``` -Lub można go *zresetować*, tj. usunąć z URL. Wtedy przyjmie swoją wartość domyślną: +Albo *zresetować*, czyli usunąć z URL. Przyjmie wtedy swoją wartość domyślną: ```latte resetuj ``` -Komponenty persistentne -======================= +Komponenty trwałe +================= -Nie tylko parametry, ale także komponenty mogą być persistentne. W przypadku takiego komponentu jego parametry persistentne są przenoszone również między różnymi akcjami presentera lub między wieloma presenterami. Komponenty persistentne oznaczamy adnotacją przy klasie presentera. Na przykład tak oznaczymy komponenty `calendar` i `poll`: +Trwałe mogą być nie tylko parametry, ale też komponenty. Ich parametry trwałe przenoszone są wtedy również między różnymi akcjami presentera albo między wieloma presenterami. Komponenty trwałe oznaczamy atrybutem na klasie presentera. Na przykład komponenty `calendar` i `poll` oznaczymy tak: ```php -/** - * @persistent(calendar, poll) - */ +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] class DefaultPresenter extends Nette\Application\UI\Presenter { } ``` -Podkomponentów wewnątrz tych komponentów nie trzeba oznaczać, staną się również persistentne. +Subkomponentów wewnątrz tych komponentów nie trzeba oznaczać; również stają się trwałe. -W PHP 8 można również użyć atrybutów do oznaczenia komponentów persistentnych: +Starsza adnotacja `@persistent` nadal działa, ale jest przestarzała i wywołuje ostrzeżenie: ```php -use Nette\Application\Attributes\Persistent; - -#[Persistent('calendar', 'poll')] +/** + * @persistent(calendar, poll) + */ class DefaultPresenter extends Nette\Application\UI\Presenter { } @@ -317,15 +322,15 @@ class DefaultPresenter extends Nette\Application\UI\Presenter Komponenty z zależnościami ========================== -Jak tworzyć komponenty z zależnościami, nie „zaśmiecając” sobie presenterów, które będą ich używać? Dzięki sprytnym właściwościom kontenera DI w Nette, podobnie jak przy używaniu klasycznych usług, można pozostawić większość pracy frameworkowi. +Jak tworzyć komponenty z zależnościami, nie "zaśmiecając" presenterów, które będą ich używać? Dzięki sprytnym możliwościom kontenera DI w Nette, podobnie jak przy klasycznych usługach, większość pracy można zostawić frameworkowi. -Weźmy jako przykład komponent, który ma zależność od usługi `PollFacade`: +Weźmy przykład komponentu, który ma zależność od usługi `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id ankiety dla której tworzymy komponent + private int $id, // ID ankiety, dla której tworzymy komponent private PollFacade $facade, ) { } @@ -338,11 +343,11 @@ class PollControl extends Control } ``` -Gdybyśmy pisali klasyczną usługę, nie byłoby problemu. O przekazanie wszystkich zależności zadbałby niewidocznie kontener DI. Jednak z komponentami zazwyczaj postępujemy tak, że ich nową instancję tworzymy bezpośrednio w presenterze w [metodach fabrykujących |#Metody fabrykujące] `createComponent…()`. Ale przekazywanie wszystkich zależności wszystkich komponentów do presentera, aby je następnie przekazać komponentom, jest uciążliwe. I tyle napisanego kodu… +Gdybyśmy pisali klasyczną usługę, nie byłoby o czym mówić. Kontener DI niewidocznie zająłby się przekazaniem wszystkich zależności. Przy komponentach zwykle jednak radzimy sobie tak, że tworzymy nową instancję bezpośrednio w presenterze, w [metodach fabrycznych |#Metody fabryczne] `createComponent…()`. Ale przekazywanie wszystkich zależności wszystkich komponentów do presentera tylko po to, aby przekazać je dalej do komponentów, jest uciążliwe. A ile kodu do napisania... -Logicznym pytaniem jest, dlaczego po prostu nie zarejestrujemy komponentu jako klasycznej usługi, nie przekażemy go do presentera, a następnie w metodzie `createComponent…()` nie zwrócimy? Takie podejście jest jednak nieodpowiednie, ponieważ chcemy mieć możliwość tworzenia komponentu nawet wielokrotnie. +Logiczne pytanie brzmi: dlaczego po prostu nie zarejestrujemy komponentu jako klasycznej usługi, nie przekażemy go do presentera i nie zwrócimy w metodzie `createComponent…()`? To podejście jest jednak niewłaściwe, bo chcemy mieć możliwość tworzenia komponentu w razie potrzeby wielokrotnie. -Prawidłowym rozwiązaniem jest napisanie dla komponentu fabryki, czyli klasy, która nam komponent utworzy: +Poprawnym rozwiązaniem jest napisanie fabryki komponentu, czyli klasy, która tworzy komponent za nas: ```php class PollControlFactory @@ -359,14 +364,14 @@ class PollControlFactory } ``` -Taką fabrykę zarejestrujemy w naszym kontenerze w konfiguracji: +Fabrykę tę rejestrujemy w kontenerze w konfiguracji: ```neon services: - PollControlFactory ``` -a na koniec użyjemy jej w naszym presenterze: +i wreszcie używamy jej w naszym presenterze: ```php class PollPresenter extends Nette\Application\UI\Presenter @@ -378,13 +383,13 @@ class PollPresenter extends Nette\Application\UI\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // możemy przekazać nasz parametr + $pollId = 1; // możemy przekazać własny parametr return $this->pollControlFactory->create($pollId); } } ``` -Świetne jest to, że Nette DI potrafi takie proste fabryki [generować |dependency-injection:factory], więc zamiast całego jej kodu wystarczy napisać tylko jej interfejs: +Świetne jest to, że Nette DI potrafi takie proste fabryki [wygenerować |dependency-injection:factory], więc zamiast pisać cały jej kod, wystarczy napisać jej interfejs: ```php interface PollControlFactory @@ -393,21 +398,21 @@ interface PollControlFactory } ``` -I to wszystko. Nette wewnętrznie zaimplementuje ten interfejs i przekaże go do presentera, gdzie już możemy go używać. Magicznie doda nam do naszego komponentu również parametr `$id` i instancję klasy `PollFacade`. +I to wszystko. Nette wewnętrznie implementuje ten interfejs i wstrzykuje go do presentera, gdzie możemy go użyć. Magicznie dodaje do naszego komponentu parametr `$id` i instancję klasy `PollFacade`. -Komponenty dogłębnie -==================== +Komponenty w głąb +================= -Komponenty w Nette Application stanowią części aplikacji internetowej wielokrotnego użytku, które wstawiamy na strony i którym poświęcony jest cały ten rozdział. Jakie dokładnie możliwości ma taki komponent? +Komponenty w Nette Application reprezentują części aplikacji webowej wielokrotnego użytku, które osadzamy w stronach i którym poświęcony jest cały ten rozdział. Jakie dokładnie są możliwości takiego komponentu? -1) jest renderowalny w szablonie -2) wie, [którą swoją część |ajax#Snippety] ma wyrenderować przy żądaniu AJAX (snippety) -3) ma możliwość zapisywania swojego stanu w URL (parametry persistentne) -4) ma możliwość reagowania na akcje użytkownika (sygnały) -5) tworzy strukturę hierarchiczną (gdzie korzeniem jest presenter) +1) da się go wyrenderować w szablonie +2) wie, [którą swoją część |ajax#Snippety] wyrenderować przy żądaniu AJAX (snippety) +3) ma możliwość przechowywania swojego stanu w URL (parametry trwałe) +4) ma możliwość reagowania na działania użytkownika (sygnały) +5) tworzy strukturę hierarchiczną (której korzeniem jest presenter) -Każdą z tych funkcji obsługuje któraś z klas linii dziedziczenia. Renderowanie (1 + 2) obsługuje [api:Nette\Application\UI\Control], włączenie do [cyklu życia |presenters#Cykl życia presentera] (3, 4) klasa [api:Nette\Application\UI\Component], a tworzenie struktury hierarchicznej (5) klasy [Container i Component |component-model:]. +Za każdą z tych funkcji odpowiada jedna z klas w linii dziedziczenia. Za renderowanie (1 + 2) odpowiada [api:Nette\Application\UI\Control], za włączenie w [cykl życia |presenters#Cykl życia presentera] (3, 4) klasa [api:Nette\Application\UI\Component], a za utworzenie struktury hierarchicznej (5) klasy [Container i Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -428,12 +433,12 @@ Cykl życia komponentu [* lifecycle-component.svg *] *** *Cykl życia komponentu* .<> -Walidacja parametrów persistentnych ------------------------------------ +Walidacja parametrów trwałych +----------------------------- -Wartości [parametrów persistentnych |#Parametry persistentne] otrzymanych z URL zapisuje do właściwości metoda `loadState()`. Sprawdza ona również, czy odpowiada typ danych podany przy właściwości, w przeciwnym razie odpowiada błędem 404 i strona się nie wyświetla. +Wartości [parametrów trwałych |#Parametry trwałe] otrzymane z URL zapisywane są do właściwości metodą `loadState()`. Sprawdza ona również, czy typ danych podany dla właściwości się zgadza; w przeciwnym razie odpowiada błędem 404 i strona nie zostaje wyświetlona. -Nigdy ślepo nie wierz parametrom persistentnym, ponieważ mogą być łatwo nadpisane przez użytkownika w URL. W ten sposób na przykład sprawdzimy, czy numer strony `$this->page` jest większy niż 0. Odpowiednią drogą jest nadpisanie wspomnianej metody `loadState()`: +Nigdy nie ufaj ślepo parametrom trwałym, bo użytkownik może je łatwo nadpisać w URL. Tak sprawdzimy na przykład, czy numer strony `$this->page` jest większy od 0. Odpowiednim sposobem jest nadpisanie wspomnianej metody `loadState()`: ```php class PaginatingControl extends Control @@ -443,8 +448,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // tutaj ustawia się $this->page - // następuje własna kontrola wartości: + parent::loadState($params); // tutaj ustawiane jest $this->page + // następuje własne sprawdzenie wartości: if ($this->page < 1) { $this->error(); } @@ -452,27 +457,41 @@ class PaginatingControl extends Control } ``` -Proces odwrotny, czyli zebranie wartości z właściwości persistentnych, obsługuje metoda `saveState()`. +Odwrotnym procesem, czyli zebraniem wartości z właściwości trwałych, zajmuje się metoda `saveState()`. + + +Podłączenie do presentera +------------------------- + +W chwili, gdy komponent staje się częścią hierarchii presentera, wywoływane są jego callbacki zapisane w tablicy `$onAnchor`. Od tego momentu komponent ma dostępny presenter, może bezpiecznie tworzyć odnośniki, odczytywać parametry trwałe itd. + +```php +$control->onAnchor[] = function ($control): void { + // komponent ma teraz dostępny presenter +}; +``` + +Sygnały w głąb +-------------- -Sygnały dogłębnie ------------------ +Sygnał powoduje przeładowanie strony dokładnie jak pierwotne żądanie (poza wywołaniem przez AJAX) i wywołuje metodę `signalReceived($signal)`, której domyślna implementacja w klasie `Nette\Application\UI\Component` próbuje wywołać metodę złożoną ze słów `handle`. Dalsze przetwarzanie zależy od danego obiektu. Obiekty dziedziczące po `Component` (czyli `Control` i `Presenter`) reagują próbą wywołania metody `handle` z odpowiednimi parametrami. -Sygnał powoduje ponowne załadowanie strony dokładnie tak samo, jak przy pierwotnym żądaniu (z wyjątkiem przypadku, gdy jest wywoływany przez AJAX) i wywołuje metodę `signalReceived($signal)`, której domyślna implementacja w klasie `Nette\Application\UI\Component` próbuje wywołać metodę złożoną ze słów `handle{signal}`. Dalsze przetwarzanie zależy od danego obiektu. Obiekty dziedziczące po `Component` (tj. `Control` i `Presenter`) reagują tak, że próbują wywołać metodę `handle{signal}` z odpowiednimi parametrami. +Innymi słowy: brana jest definicja funkcji `handle` wraz ze wszystkimi parametrami, które przyszły z żądaniem, a parametry z URL przypisywane są do argumentów po nazwie, po czym następuje próba wywołania metody. Na przykład wartość parametru `id` z URL przekazywana jest jako argument `$id`, `something` z URL jako `$something` itd. A jeśli metoda nie istnieje, metoda `signalReceived` zgłasza [wyjątek |api:Nette\Application\UI\BadSignalException]. -Innymi słowy: bierze się definicję funkcji `handle{signal}` i wszystkie parametry, które przyszły z żądaniem, a do argumentów według nazwy dopasowuje się parametry z URL i próbuje wywołać daną metodę. Np. jako parametr `$id` przekazuje się wartość z parametru `id` w URL, jako `$something` przekazuje się `something` z URL, itd. A jeśli metoda nie istnieje, metoda `signalReceived` rzuca [wyjątek |api:Nette\Application\UI\BadSignalException]. +Poza parametrami z URL sygnał odczytuje również parametry wysłane w **ciele POST żądania**. Przydaje się to, bo sygnały często wywoływane są przez JavaScript, w którym naturalne jest wysyłanie danych metodą POST. Jeśli jednak parametr o tej samej nazwie przyjdzie zarówno z URL, jak i z ciała POST, pierwszeństwo ma wartość **z URL**. Unikaj więc nadawania polu POST tej samej nazwy co parametrowi URL albo trasy, bo wartość z URL po cichu by je nadpisała. Parametry sygnału dzielą wspólną przestrzeń z parametrami akcji i parametrami trwałymi, zobacz [Wspólna przestrzeń parametrów |presenters#Wspólna przestrzeń parametrów]. -Sygnał może odbierać dowolny komponent, presenter lub obiekt, który implementuje interfejs `SignalReceiver` i jest podłączony do drzewa komponentów. +Sygnał może odebrać dowolny komponent, presenter albo obiekt implementujący interfejs `SignalReceiver` i podłączony do drzewa komponentów. -Głównymi odbiorcami sygnałów będą `Presentery` i komponenty wizualne dziedziczące po `Control`. Sygnał ma służyć jako znak dla obiektu, że ma coś zrobić – ankieta ma zliczyć głos od użytkownika, blok z nowościami ma się rozwinąć i wyświetlić dwa razy więcej nowości, formularz został wysłany i ma przetworzyć dane itp. +Głównymi odbiorcami sygnałów będą `Presentery` i komponenty wizualne dziedziczące po `Control`. Sygnał ma służyć jako znak dla obiektu, że powinien coś zrobić: ankieta ma policzyć głos użytkownika, blok z newsami ma się rozwinąć i wyświetlić dwa razy więcej newsów, formularz został wysłany i ma przetworzyć dane itd. -URL dla sygnału tworzymy za pomocą metody [Component::link() |api:Nette\Application\UI\Component::link()]. Jako parametr `$destination` przekazujemy ciąg `{signal}!` a jako `$args` tablicę argumentów, które chcemy przekazać sygnałowi. Sygnał zawsze jest wywoływany na aktualnym presenterze i akcji z aktualnymi parametrami, parametry sygnału są tylko dodawane. Dodatkowo na początku dodawany jest **parametr `?do`, który określa sygnał**. +URL sygnału tworzy się metodą [Component::link() |api:Nette\Application\UI\Component::link()]. Jako parametr `$destination` przekazujemy string `{sygnał}!`, a jako `$args` tablicę argumentów, które chcemy przekazać sygnałowi. Sygnał wywoływany jest zawsze na bieżącym presenterze i akcji z bieżącymi parametrami; parametry sygnału są tylko dodawane. Ponadto dodawany jest **parametr `?do`, który określa sygnał**. -Jego format to albo `{signal}`, albo `{signalReceiver}-{signal}`. `{signalReceiver}` to nazwa komponentu w presenterze. Dlatego w nazwie komponentu nie może być myślnika – używa się go do oddzielenia nazwy komponentu i sygnału, jednak można w ten sposób zagnieździć kilka komponentów. +Jego format to albo `{sygnał}`, albo `{odbiorcaSygnału}-{sygnał}`. `{odbiorcaSygnału}` to nazwa komponentu w presenterze. Dlatego w nazwie komponentu nie można użyć myślnika - służy on do oddzielenia nazwy komponentu i sygnału, choć w ten sposób można zagnieżdżać wiele komponentów. -Metoda [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] sprawdza, czy komponent (pierwszy argument) jest odbiorcą sygnału (drugi argument). Drugi argument możemy pominąć – wtedy sprawdza, czy komponent jest odbiorcą jakiegokolwiek sygnału. Jako drugi parametr można podać `true` i tym samym sprawdzić, czy odbiorcą jest nie tylko podany komponent, ale także którykolwiek jego potomek. +Metoda [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] sprawdza, czy komponent (pierwszy argument) jest odbiorcą sygnału (drugi argument). Drugi argument można pominąć - sprawdzane jest wtedy, czy komponent jest odbiorcą jakiegokolwiek sygnału. Jeśli drugi parametr ustawimy na `true`, sprawdzane jest, czy odbiorcą jest podany komponent albo któryś z jego potomków. -W dowolnej fazie poprzedzającej `handle{signal}` możemy wykonać sygnał ręcznie, wywołując metodę [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], która zajmuje się obsługą sygnału – bierze komponent, który został określony jako odbiorca sygnału (jeśli nie jest określony odbiorca sygnału, jest to sam presenter) i wysyła mu sygnał. +Na dowolnym etapie poprzedzającym `handle` możemy wykonać sygnał ręcznie, wywołując metodę [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], która zajmuje się obsługą sygnału - bierze komponent zidentyfikowany jako odbiorca sygnału (jeśli odbiorcy nie podano, jest nim sam presenter) i wysyła mu sygnał. Przykład: @@ -482,4 +501,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Tym samym sygnał jest wykonany przedwcześnie i nie będzie już ponownie wywoływany. +Sygnał zostanie w ten sposób wykonany przedwcześnie i nie zostanie wywołany ponownie. diff --git a/application/pl/configuration.texy b/application/pl/configuration.texy index 01f37a6477..0281e17a4b 100644 --- a/application/pl/configuration.texy +++ b/application/pl/configuration.texy @@ -2,7 +2,7 @@ Konfiguracja aplikacji ********************** .[perex] -Przegląd opcji konfiguracyjnych dla aplikacji Nette. +Przegląd opcji konfiguracyjnych Nette Application. Application @@ -10,39 +10,41 @@ Application ```neon application: - # wyświetlić panel "Nette Application" w Tracy BlueScreen? - debugger: ... # (bool) domyślnie true + # pokazywać panel "Nette Application" w Tracy BlueScreen? + debugger: ... # (bool) włączone, jeśli Tracy jest dostępne - # czy przy błędzie będzie wywoływany error-presenter? - # ma efekt tylko w trybie deweloperskim - catchExceptions: ... # (bool) domyślnie true + # na produkcji wyjątki zawsze obsługuje error-presenter; + # ta opcja włącza to zachowanie również w trybie deweloperskim + catchExceptions: ... # (bool) domyślnie false - czyli wyłączone w dev, na produkcji zawsze włączone # nazwa error-presentera errorPresenter: Error # (string|array) domyślnie 'Nette:Error' - # definiuje aliasy dla presenterów i akcji + # definiuje aliasy presenterów i akcji aliases: ... # definiuje reguły tłumaczenia nazwy presentera na klasę mapping: ... - # nieprawidłowe linki nie generują ostrzeżeń? - # ma efekt tylko w trybie deweloperskim + # wyciszać ostrzeżenia o nieprawidłowych odnośnikach? + # działa tylko w trybie deweloperskim silentLinks: ... # (bool) domyślnie false ``` -Od wersji `nette/application` 3.2 można zdefiniować parę error-presenterów: +Od wersji 3.2 pakietu `nette/application` można zdefiniować parę error-presenterów: ```neon application: errorPresenter: - 4xx: Error4xx # dla wyjątku Nette\Application\BadRequestException + 4xx: Error4xx # dla Nette\Application\BadRequestException 5xx: Error5xx # dla pozostałych wyjątków ``` -Opcja `silentLinks` określa, jak Nette zachowa się w trybie deweloperskim, gdy generowanie linku nie powiodło się (np. dlatego, że presenter nie istnieje itp.). Domyślna wartość `false` oznacza, że Nette zgłosi błąd `E_USER_WARNING`. Ustawienie na `true` spowoduje stłumienie tego komunikatu błędu. W środowisku produkcyjnym `E_USER_WARNING` jest zawsze zgłaszany. To zachowanie możemy również kontrolować, ustawiając zmienną presentera [$invalidLinkMode |creating-links#Nieprawidłowe linki]. +Rozdzielenie ich przydaje się, bo obie sytuacje są zasadniczo różne. `BadRequestException` (kody 4xx) oznacza, że z aplikacją wszystko w porządku, a jedynie odwiedzający poprosił o coś, co nie istnieje. Możesz więc użyć pełnoprawnego presentera, który wyświetli przyjazny komunikat w layoucie Twojej witryny. Odwrotnie, błąd 5xx oznacza, że w aplikacji coś się zepsuło i nie wiadomo co. Presenter dla 5xx trzymaj możliwie minimalny, aby przy jego renderowaniu nie mogło zawieść nic więcej - najlepiej, aby nie dotykał bazy danych, layoutu ani zalogowanego użytkownika. -[Aliasy upraszczają linkowanie |creating-links#Aliasy] do często używanych presenterów. +Opcja `silentLinks` określa, jak Nette zachowuje się w trybie deweloperskim, gdy generowanie odnośnika się nie powiedzie (na przykład dlatego, że presenter nie istnieje itd.). Wartość domyślna `false` oznacza, że Nette zgłasza błąd `E_USER_WARNING`. Ustawienie na `true` wycisza ten komunikat. W środowisku produkcyjnym `E_USER_WARNING` zgłaszany jest zawsze. Na to zachowanie można wpłynąć również ustawieniem zmiennej presentera [$invalidLinkMode |creating-links#Nieprawidłowe odnośniki]. + +[Aliasy upraszczają odwoływanie się |creating-links#Aliasy] do często używanych presenterów. [Mapowanie definiuje reguły |directory-structure#Mapowanie presenterów], według których z nazwy presentera wyprowadzana jest nazwa klasy. @@ -50,14 +52,14 @@ Opcja `silentLinks` określa, jak Nette zachowa się w trybie deweloperskim, gdy Automatyczna rejestracja presenterów ------------------------------------ -Nette automatycznie dodaje presentery jako usługi do kontenera DI, co znacząco przyspiesza ich tworzenie. Sposób, w jaki Nette wyszukuje presentery, można skonfigurować: +Nette automatycznie dodaje presentery jako usługi do kontenera DI, co znacząco przyspiesza ich tworzenie. To, jak Nette odnajduje presentery, można skonfigurować: ```neon application: - # szukać presenterów w Composer class map? + # szukać presenterów w class mapie Composera? scanComposer: ... # (bool) domyślnie true - # maska, której musi odpowiadać nazwa klasy i pliku + # maska, do której musi pasować nazwa klasy i pliku scanFilter: ... # (string) domyślnie '*Presenter' # w których katalogach szukać presenterów? @@ -65,7 +67,7 @@ application: - %vendorDir%/mymodule ``` -Katalogi podane w `scanDirs` nie nadpisują wartości domyślnej `%appDir%`, ale uzupełniają ją, więc `scanDirs` będzie zawierać obie ścieżki `%appDir%` i `%vendorDir%/mymodule`. Jeśli chcielibyśmy pominąć katalog domyślny, użyjemy [wykrzyknika |dependency-injection:configuration#Łączenie], który nadpisze wartość: +Katalogi wymienione w `scanDirs` nie nadpisują wartości domyślnej `%appDir%`, lecz ją uzupełniają, więc `scanDirs` będzie zawierać obie ścieżki: `%appDir%` i `%vendorDir%/mymodule`. Jeśli chcemy pominąć katalog domyślny, użyjemy [wykrzyknika |dependency-injection:configuration#Scalanie]: ```neon application: @@ -73,26 +75,32 @@ application: - %vendorDir%/mymodule ``` -Skanowanie katalogów można wyłączyć, podając wartość `false`. Nie zalecamy całkowitego wyłączania automatycznego dodawania presenterów, ponieważ w przeciwnym razie dojdzie do obniżenia wydajności aplikacji. +Skanowanie katalogów można wyłączyć, ustawiając wartość na `false`. Presentery nie są wtedy rejestrowane jako usługi, więc nie da się ich dostosować przez sekcję [decorator |dependency-injection:configuration#Dekorator], a ich tworzenie jest wolniejsze. Nie zalecamy więc całkowitego wyłączania automatycznej rejestracji, bo obniży to wydajność aplikacji. Szablony Latte ============== -Tym ustawieniem można globalnie wpłynąć na zachowanie Latte w komponentach i presenterach. +To ustawienie globalnie wpływa na zachowanie Latte w komponentach i presenterach. ```neon latte: - # wyświetlić panel Latte w Tracy Bar dla głównego szablonu (true) lub wszystkich komponentów (all)? - debugger: ... # (true|false|'all') domyślnie true + # pokazywać panel Latte w pasku Tracy dla głównego szablonu (true), czy dla wszystkich komponentów (all)? + debugger: ... # (true|false|'all') włączone, jeśli Tracy jest dostępne (tylko w trybie debug) - # generuje szablony z nagłówkiem declare(strict_types=1) + # generować szablony z nagłówkiem declare(strict_types=1) strictTypes: ... # (bool) domyślnie false - # włącza tryb [ścisłego parsera |latte:develop#striktní režim] + # włącza [tryb ścisłego parsera |latte:develop#strict mode] strictParsing: ... # (bool) domyślnie false - # aktywuje [kontrolę wygenerowanego kodu |latte:develop#Kontrola vygenerovaného kódu] + # ogranicza zasięg zmiennych do ciała pętli + scopedLoopVariables: ... # (bool) domyślnie false + + # usuwa wcięcia powstałe z zagnieżdżenia w tagach parzystych + dedent: ... # (bool) domyślnie false + + # włącza [kontrolę wygenerowanego kodu |latte:develop#Checking Generated Code] phpLinter: ... # (string) domyślnie null # ustawia locale @@ -102,7 +110,7 @@ latte: templateClass: App\MyTemplateClass # domyślnie Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Jeśli używasz Latte w wersji 3, możesz dodawać nowe [rozszerzenia |latte:extending-latte#Latte Extension] za pomocą: +Nowe [rozszerzenia |latte:extending-latte#Latte Extension] dodasz tak: ```neon latte: @@ -110,20 +118,6 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Jeśli używasz Latte w wersji 2, możesz rejestrować nowe tagi, podając nazwę klasy lub referencję do usługi. Domyślnie wywoływana jest metoda `install()`, ale można to zmienić, podając nazwę innej metody: - -```neon -latte: - # rejestracja niestandardowych znaczników Latte - macros: - - App\MyLatteMacros::register # metoda statyczna, nazwa klasy lub callable - - @App\MyLatteMacrosFactory # usługa z metodą install() - - @App\MyLatteMacrosFactory::register # usługa z metodą register() - -services: - - App\MyLatteMacrosFactory -``` - Routing ======= @@ -132,14 +126,14 @@ Podstawowe ustawienia: ```neon routing: - # wyświetlić panel routingu w Tracy Bar? - debugger: ... # (bool) domyślnie true + # pokazywać panel routingu w pasku Tracy? + debugger: ... # (bool) włączone, jeśli Tracy jest dostępne (tylko w trybie debug) - # serializuje router do kontenera DI + # serializować router do kontenera DI cache: ... # (bool) domyślnie false ``` -Routing zazwyczaj definiujemy w klasie [RouterFactory |routing#Kolekcja tras]. Alternatywnie trasy można definiować również w konfiguracji za pomocą par `maska: akcja`, ale ten sposób nie oferuje tak szerokiej zmienności w ustawieniach: +Routing definiuje się zwykle w klasie [RouterFactory |routing#Kolekcja tras]. Alternatywnie trasy można definiować również w konfiguracji za pomocą par `maska: akcja`, ale ten sposób nie daje zbyt dużej elastyczności: ```neon routing: @@ -159,16 +153,16 @@ constants: Foobar: 'baz' ``` -Po uruchomieniu aplikacji zostanie utworzona stała `Foobar`. +Stała `Foobar` zostanie utworzona po starcie aplikacji. .[note] -Stałe nie powinny służyć jako swego rodzaju globalnie dostępne zmienne. Do przekazywania wartości do obiektów wykorzystaj [dependency injection |dependency-injection:passing-dependencies]. +Stałe nie powinny służyć jako globalnie dostępne zmienne. Do przekazywania wartości do obiektów używaj [wstrzykiwania zależności |dependency-injection:passing-dependencies]. PHP === -Ustawienia dyrektyw PHP. Przegląd wszystkich dyrektyw znajdziesz na [php.net |https://www.php.net/manual/en/ini.list.php]. +Ustawianie dyrektyw PHP. Przegląd wszystkich dyrektyw znajdziesz na [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: @@ -179,13 +173,14 @@ php: Usługi DI ========= -Te usługi są dodawane do kontenera DI: - -| Nazwa | Typ | Opis -|---------------------------------------------------------- -| `application.application` | [api:Nette\Application\Application] | [uruchamiacz całej aplikacji |how-it-works#Nette Application] -| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] -| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | fabryka presenterów -| `application.###` | [api:Nette\Application\UI\Presenter] | poszczególne presentery -| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | fabryka obiektu `Latte\Engine` -| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | fabryka dla [`$this->template` |templates] +Do kontenera DI dodawane są te usługi: + +| Nazwa | Typ | Opis +|----------------------------|---------------------------------------------------|----------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [uruchamiacz aplikacji |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\IPresenterFactory] | fabryka presenterów +| `application.###` | [api:Nette\Application\UI\Presenter] | poszczególne presentery +| `routing.router` | [api:Nette\Routing\Router] | router +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | fabryka obiektu `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | fabryka [`$this->template` |templates] diff --git a/application/pl/creating-links.texy b/application/pl/creating-links.texy index 40b1445c2c..b8abbf5d05 100644 --- a/application/pl/creating-links.texy +++ b/application/pl/creating-links.texy @@ -1,156 +1,173 @@ -Tworzenie linków URL -******************** +Tworzenie odnośników URL +************************
    -Tworzenie linków w Nette jest proste jak wskazywanie palcem. Wystarczy tylko wycelować, a framework już za Ciebie wykona całą pracę. Pokażemy: +Tworzenie odnośników w Nette jest tak proste jak wskazanie palcem. Wystarczy wycelować, a framework wykona za Ciebie całą pracę. Pokażemy: -- jak tworzyć linki w szablonach i gdzie indziej -- jak odróżnić link do aktualnej strony -- co z nieprawidłowymi linkami +- jak tworzyć odnośniki w szablonach i gdzie indziej +- jak odróżnić odnośnik do bieżącej strony +- co zrobić z nieprawidłowymi odnośnikami
    -Dzięki [dwukierunkowemu routingowi |routing] nigdy nie będziesz musiał w szablonach czy kodzie zapisywać na sztywno adresów URL Twojej aplikacji, które mogą się później zmienić, lub skomplikowanie je składać. W linku wystarczy podać presenter i akcję, przekazać ewentualne parametry, a framework już sam wygeneruje URL. Właściwie jest to bardzo podobne do wywoływania funkcji. Spodoba Ci się to. +Dzięki [dwukierunkowemu routingowi |routing] nigdy nie będziesz musiał zapisywać na sztywno w szablonach ani w kodzie URL swojej aplikacji, które mogą się później zmienić albo których składanie bywa skomplikowane. W odnośniku podajesz jedynie presenter i akcję, przekazujesz ewentualne parametry, a framework sam wygeneruje URL. Właściwie przypomina to bardzo wywołanie funkcji. Spodoba Ci się to. W szablonie presentera ====================== -Najczęściej tworzymy linki w szablonach, a świetnym pomocnikiem jest atrybut `n:href`: +Najczęściej tworzymy odnośniki w szablonach i świetnym pomocnikiem jest atrybut `n:href`: ```latte szczegóły ``` -Zauważ, że zamiast atrybutu HTML `href` użyliśmy [n:atrybutu |latte:syntax#n:atrybuty] `n:href`. Jego wartością nie jest URL, jak by to było w przypadku atrybutu `href`, ale nazwa presentera i akcji. +Zwróć uwagę, że zamiast atrybutu HTML `href` użyliśmy [n:atrybutu |latte:syntax#n:atrybuty] `n:href`. Jego wartością nie jest URL, jak byłoby w przypadku atrybutu `href`, lecz nazwa presentera i akcji. -Kliknięcie na link jest, upraszczając, czymś w rodzaju wywołania metody `ProductPresenter::renderShow()`. A jeśli ma w swojej sygnaturze parametry, możemy ją wywołać z argumentami: +Kliknięcie w odnośnik to, mówiąc prosto, coś w rodzaju wywołania metody `ProductPresenter::renderShow()`. A jeśli ma ona w sygnaturze parametry, możemy wywołać ją z argumentami: ```latte szczegóły produktu ``` -Możliwe jest również przekazywanie parametrów nazwanych. Poniższy link przekazuje parametr `lang` o wartości `cs`: +Można też przekazywać parametry nazwane. Poniższy odnośnik przekazuje parametr `lang` o wartości `en`: ```latte -szczegóły produktu +szczegóły produktu ``` -Jeśli metoda `ProductPresenter::renderShow()` nie ma `$lang` w swojej sygnaturze, może uzyskać wartość parametru za pomocą `$lang = $this->getParameter('lang')` lub z [właściwości |presenters#Parametry żądania]. +Jeśli metoda `ProductPresenter::renderShow()` nie ma w sygnaturze `$lang`, może pobrać wartość parametru przez `$lang = $this->getParameter('lang')` albo z [właściwości |presenters#Parametry żądania]. -Jeśli parametry są przechowywane w tablicy, można je rozwinąć operatorem `...` (w Latte 2.x operatorem `(expand)`): +Jeśli parametry są w tablicy, można je rozwinąć operatorem `...`: ```latte -{var $args = [$product->id, lang => cs]} -szczegóły produktu +{var $args = [$product->id, lang => en]} +szczegóły produktu ``` -W linkach automatycznie przekazywane są również tzw. [parametry persistentne |presenters#Parametry trwałe]. +W odnośnikach automatycznie przekazywane są również tak zwane [parametry trwałe |presenters#Parametry trwałe]. -Atrybut `n:href` jest bardzo przydatny dla znaczników HTML ``. Jeśli chcemy wypisać link gdzie indziej, na przykład w tekście, użyjemy `{link}`: +Atrybut `n:href` jest bardzo poręczny dla tagów HTML ``. Jeśli chcemy wypisać odnośnik gdzie indziej, na przykład w tekście, użyjemy `{link}`: ```latte -Adres to: {link Home:default} +URL to: {link Home:default} ``` W kodzie ======== -Do tworzenia linku w presenterze służy metoda `link()`: +Do utworzenia odnośnika w presenterze służy metoda `link()`: ```php $url = $this->link('Product:show', $product->id); ``` -Parametry można przekazać również za pomocą tablicy, gdzie można podać również parametry nazwane: +Parametry można przekazać także jako tablicę, w której można podać również parametry nazwane: ```php -$url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$product->id, 'lang' => 'en']); ``` -Linki można tworzyć również bez presentera, do tego służy [#LinkGenerator] i jego metoda `link()`. +Odnośniki można tworzyć również bez presentera, za pomocą [#LinkGenerator] i jego metody `link()`. +Czasem potrzebujesz utworzyć odnośnik teraz, ale wygenerować rzeczywisty URL dopiero później. Służy do tego metoda `lazyLink()`, która zwraca obiekt `Nette\Application\UI\Link`. Zaletą jest to, że możesz ten obiekt przekazać dalej, na przykład do szablonu, i przed jego wyrenderowaniem nadal dostosowywać jego parametry metodą `setParameter()`. Sam URL składany jest dopiero wtedy, gdy obiekt zostanie skonwertowany na string: -Linki do presentera -=================== +```php +$link = $this->lazyLink('Product:show', $id); +// ... +echo $link; // URL generowany jest dopiero tutaj +``` -Jeśli celem linku jest presenter i akcja, ma on następującą składnię: + +Odnośniki do presentera +======================= + +Jeśli celem odnośnika jest presenter i akcja, ma on taką składnię: ``` -[//] [[[[:]module:]presenter:]action | this] [#fragment] +[//] [[[[:]moduł:]presenter:]akcja | this] [#fragment] ``` -Format ten obsługują wszystkie znaczniki Latte i wszystkie metody presentera, które pracują z linkami, czyli `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` oraz [#LinkGenerator]. Więc nawet jeśli w przykładach użyto `n:href`, mogłaby tam być dowolna z tych funkcji. +Format ten obsługują wszystkie tagi Latte i wszystkie metody presentera pracujące z odnośnikami, czyli `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`, a także [#LinkGenerator]. Choć więc w przykładach używane jest `n:href`, mogłaby tam stać dowolna z tych funkcji. -Podstawową formą jest więc `Presenter:action`: +Podstawową postacią jest więc `Presenter:akcja`: ```latte strona główna ``` -Jeśli linkujemy do akcji aktualnego presentera, możemy pominąć jego nazwę: +Jeśli linkujemy do akcji bieżącego presentera, możemy pominąć jego nazwę: ```latte strona główna ``` -Jeśli celem jest akcja `default`, możemy ją pominąć, ale dwukropek musi pozostać: +Jeśli akcją docelową jest `default`, możemy ją pominąć, ale dwukropek musi zostać: ```latte strona główna ``` -Linki mogą również prowadzić do innych [modułów |directory-structure#Presentery i szablony]. Tutaj linki dzielą się na względne do zagnieżdżonego podmodułu lub absolutne. Zasada jest analogiczna do ścieżek na dysku, tylko zamiast ukośników są dwukropki. Załóżmy, że aktualny presenter jest częścią modułu `Front`, wtedy zapiszemy: +Odnośniki mogą wskazywać także na inne [moduły |directory-structure#Presentery i szablony]. Rozróżniamy tu odnośniki względne wobec zagnieżdżonego submodułu albo bezwzględne. Zasada jest analogiczna do ścieżek dyskowych, tyle że zamiast ukośników używa się dwukropków. Zakładając, że bieżący presenter jest częścią modułu `Front`, zapisalibyśmy: ```latte -link do Front:Shop:Product:show -link do Admin:Product:show +odnośnik do Front:Shop:Product:show +odnośnik do Admin:Product:show ``` -Specjalnym przypadkiem jest link [do siebie samego |#Link do aktualnej strony], gdy jako cel podamy `this`. +Szczególnym przypadkiem jest odnośnik [do samego siebie |#Odnośnik do bieżącej strony], gdzie jako cel podajemy `this`. ```latte odśwież ``` -Możemy linkować do określonej części strony za pomocą tzw. fragmentu za znakiem kratki `#`: +Do konkretnej części strony możemy linkować przez tak zwany fragment po znaku kratki `#`: ```latte -link do Home:default i fragmentu #main +odnośnik do Home:default i fragmentu #main ``` +.{data-version:3.3.0} +Fragment można też ustawić dynamicznie jako argument z kluczem `#`. Jego wartość jest automatycznie kodowana i ma pierwszeństwo przed fragmentem podanym w celu: + +```php +$this->link('Home:default', ['#' => $fragment]); +``` + + +Ścieżki bezwzględne +=================== -Ścieżki absolutne -================= +Odnośniki generowane przez `link()` albo `n:href` są zawsze ścieżkami bezwzględnymi (czyli zaczynają się od `/`), ale nie bezwzględnymi URL z protokołem i domeną, jak `https://domain`. -Linki generowane za pomocą `link()` lub `n:href` są zawsze ścieżkami absolutnymi (tj. zaczynają się znakiem `/`), ale nie są absolutnymi URL z protokołem i domeną, jak `https://domain`. +Aby wygenerować bezwzględny URL, dodaj na początku dwa ukośniki (np. `n:href="//Home:"`). Alternatywnie możesz przełączyć presenter tak, aby generował wyłącznie odnośniki bezwzględne, ustawiając `$this->absoluteUrls = true`. -Aby wygenerować absolutny URL, dodaj na początku dwie ukośniki (np. `n:href="//Home:"`). Lub można przełączyć presenter, aby generował tylko linki absolutne, ustawiając `$this->absoluteUrls = true`. +Do zamiany ścieżki względnej na bezwzględną można też użyć w szablonie filtra `|absoluteUrl`. -Link do aktualnej strony -======================== +Odnośnik do bieżącej strony +=========================== -Cel `this` tworzy link do aktualnej strony: +Cel `this` tworzy odnośnik do bieżącej strony: ```latte odśwież ``` -Jednocześnie przekazywane są wszystkie parametry podane w sygnaturze metody `action()` lub `render()`, jeśli `action()` nie jest zdefiniowana. Więc jeśli jesteśmy na stronie `Product:show` i `id: 123`, link do `this` przekaże również ten parametr. +Jednocześnie przekazywane są wszystkie parametry podane w sygnaturze metody `action()` albo `render()` (jeśli `action()` nie jest zdefiniowana). Jeśli więc jesteśmy na stronie `Product:show` z `id: 123`, odnośnik do `this` przekaże również ten parametr. -Oczywiście można parametry specyfikować bezpośrednio: +Oczywiście parametry można podać bezpośrednio: ```latte odśwież ``` -Funkcja `isLinkCurrent()` sprawdza, czy cel linku jest zgodny z aktualną stroną. Można tego użyć na przykład w szablonie do odróżnienia linków itp. +Funkcja `isLinkCurrent()` sprawdza, czy cel odnośnika jest identyczny z bieżącą stroną. Można tego użyć na przykład w szablonie do odróżniania odnośników itd. -Parametry są takie same jak w metodzie `link()`, dodatkowo jednak można zamiast konkretnej akcji podać symbol wieloznaczny `*`, który oznacza dowolną akcję danego presentera. +Parametry są takie same jak dla metody `link()`, ale zamiast konkretnej akcji można użyć symbolu wieloznacznego `*`, który oznacza dowolną akcję danego presentera. ```latte {if !isLinkCurrent('Admin:login')} @@ -162,15 +179,15 @@ Parametry są takie same jak w metodzie `link()`, dodatkowo jednak można zamias
  • ``` -W połączeniu z `n:href` w jednym elemencie można użyć skróconej formy: +W połączeniu z `n:href` w jednym elemencie można użyć postaci skróconej: ```latte ... ``` -Symbol wieloznaczny `*` można użyć tylko zamiast akcji, a nie presentera. +Symbolu wieloznacznego `*` można użyć tylko zamiast akcji, nie presentera. -Aby sprawdzić, czy jesteśmy w określonym module lub jego podmodule, użyjemy metody `isModuleCurrent(moduleName)`. +Aby ustalić, czy jesteśmy w konkretnym module albo jego submodule, użyj metody `isModuleCurrent(moduleName)`. ```latte
  • @@ -179,13 +196,29 @@ Aby sprawdzić, czy jesteśmy w określonym module lub jego podmodule, użyjemy ``` -Linki do sygnału -================ +Zmiana bazy odnośników .{data-version:3.2.7} +============================================ -Celem linku nie musi być tylko presenter i akcja, ale także [sygnał |components#Sygnał] (wywołują metodę `handle()`). Wtedy składnia jest następująca: +Domyślnie odnośniki względne wyprowadzane są z bieżącego presentera. Można to zmienić za pomocą `{linkBase}`: + +```latte +{linkBase Admin:Dashboard} +szczegóły produktu +``` + +Odnośnik poprowadzi do `Admin:Dashboard:Product:show`. Dotyczy to wyłącznie odnośników względnych - odnośniki bezwzględne zaczynające się dwukropkiem oraz odnośniki do bieżącego presentera (`this`, `show`) pozostają bez zmian. + +`{linkBase}` obowiązuje w całym szablonie i przydaje się zwłaszcza w szablonach layoutu, gdzie zapewnia spójne odnośniki niezależnie od wywołującego presentera. +Tag musi znaleźć się na początku szablonu, w przeciwnym razie zgłasza `CompileException`. + + +Odnośniki do sygnału +==================== + +Celem odnośnika nie musi być tylko presenter i akcja, ale też [sygnał |components#Sygnał] (wywołuje wtedy metodę `handle()`). Składnia wygląda wtedy tak: ``` -[//] [sub-component:]signal! [#fragment] +[//] [subkomponent:]sygnał! [#fragment] ``` Sygnał odróżnia więc wykrzyknik: @@ -194,39 +227,39 @@ Sygnał odróżnia więc wykrzyknik: sygnał ``` -Można również utworzyć link do sygnału podkomponentu (lub pod-podkomponentu): +Możesz też utworzyć odnośnik do sygnału subkomponentu (albo sub-subkomponentu): ```latte sygnał ``` -Linki w komponencie -=================== +Odnośniki w komponencie +======================= -Ponieważ [komponenty|components] są samodzielnymi jednostkami wielokrotnego użytku, które nie powinny mieć żadnych powiązań z otaczającymi presenterami, linki działają tu trochę inaczej. Atrybut Latte `n:href` i znacznik `{link}` oraz metody komponentu takie jak `link()` i inne uważają cel linku **zawsze za nazwę sygnału**. Dlatego nie jest nawet konieczne podawanie wykrzyknika: +Ponieważ [komponenty|components] są osobnymi jednostkami wielokrotnego użytku, które nie powinny mieć żadnych powiązań z otaczającymi presenterami, odnośniki działają tu nieco inaczej. Atrybut Latte `n:href` i tag `{link}`, a także metody komponentu, takie jak `link()` i inne, **zawsze traktują cel odnośnika jako nazwę sygnału**. Nie trzeba więc nawet dodawać wykrzyknika: ```latte -sygnał, a nie akcja +sygnał, nie akcja ``` -Jeśli chcielibyśmy w szablonie komponentu linkować do presenterów, użyjemy do tego znacznika `{plink}`: +Gdybyśmy chcieli w szablonie komponentu linkować do presenterów, użyjemy tagu `{plink}`: ```latte -główna +strona główna ``` -lub w kodzie +albo w kodzie ```php $this->getPresenter()->link('Home:default') ``` -Aliasy .{data-version:v3.2.2} -============================= +Aliasy .{data-version:3.2.3} +============================ -Czasami może się przydać przypisanie parze Presenter:akcja łatwo zapamiętywalnego aliasu. Na przykład stronę główną `Front:Home:default` nazwać po prostu `home` lub `Admin:Dashboard:default` jako `admin`. +Czasem przydatne bywa przypisanie parze Presenter:akcja łatwego do zapamiętania aliasu. Na przykład nazwanie strony głównej `Front:Home:default` po prostu `home`, a `Admin:Dashboard:default` jako `admin`. Aliasy definiuje się w [konfiguracji|configuration] pod kluczem `application › aliases`: @@ -238,26 +271,26 @@ application: sign: Front:Sign:in ``` -W linkach zapisuje się je za pomocą znaku @, na przykład: +W odnośnikach zapisuje się je potem za pomocą małpy, na przykład: ```latte administracja ``` -Są one obsługiwane również we wszystkich metodach pracujących z linkami, takich jak `redirect()` i podobnych. +Obsługiwane są też we wszystkich metodach pracujących z odnośnikami, takich jak `redirect()` i podobne. -Nieprawidłowe linki -=================== +Nieprawidłowe odnośniki +======================= -Może się zdarzyć, że utworzymy nieprawidłowy link - albo dlatego, że prowadzi do nieistniejącego presentera, albo dlatego, że przekazuje więcej parametrów, niż akceptuje metoda docelowa w swojej sygnaturze, albo gdy dla akcji docelowej nie można wygenerować URL. Sposób postępowania z nieprawidłowymi linkami określa zmienna statyczna `Presenter::$invalidLinkMode`. Może ona przyjmować kombinację tych wartości (stałych): +Może się zdarzyć, że utworzymy nieprawidłowy odnośnik: albo dlatego, że prowadzi do nieistniejącego presentera, albo dlatego, że przekazuje więcej parametrów, niż przyjmuje w sygnaturze metoda docelowa, albo gdy dla docelowej akcji nie da się wygenerować URL. Sposób obsługi nieprawidłowych odnośników ustawia się w presenterze przez `$this->invalidLinkMode`. Może przyjmować kombinację tych wartości (stałych): -- `Presenter::InvalidLinkSilent` - tryb cichy, jako URL zwracany jest znak # -- `Presenter::InvalidLinkWarning` - zgłaszane jest ostrzeżenie E_USER_WARNING, które w trybie produkcyjnym zostanie zalogowane, ale nie spowoduje przerwania działania skryptu -- `Presenter::InvalidLinkTextual` - ostrzeżenie wizualne, wypisuje błąd bezpośrednio w linku -- `Presenter::InvalidLinkException` - rzucany jest wyjątek InvalidLinkException +- `Presenter::InvalidLinkSilent` - tryb cichy, jako URL zwraca znak # +- `Presenter::InvalidLinkWarning` - zgłaszane jest ostrzeżenie E_USER_WARNING, które w trybie produkcyjnym zostanie zalogowane, ale nie przerwie wykonywania skryptu +- `Presenter::InvalidLinkTextual` - ostrzeżenie wizualne, wypisuje błąd bezpośrednio w odnośniku +- `Presenter::InvalidLinkException` - zgłasza InvalidLinkException -Domyślne ustawienie to `InvalidLinkWarning` w trybie produkcyjnym i `InvalidLinkWarning | InvalidLinkTextual` w trybie deweloperskim. `InvalidLinkWarning` w środowisku produkcyjnym nie powoduje przerwania skryptu, ale ostrzeżenie zostanie zalogowane. W środowisku deweloperskim zostanie przechwycone przez [Tracy |tracy:] i wyświetli bluescreen. `InvalidLinkTextual` działa tak, że jako URL zwraca komunikat błędu zaczynający się od znaków `#error:`. Aby takie linki były od razu widoczne, dodajmy do CSS: +Ustawieniem domyślnym jest `InvalidLinkWarning` w trybie produkcyjnym i `InvalidLinkWarning | InvalidLinkTextual` w trybie deweloperskim. `InvalidLinkWarning` w środowisku produkcyjnym nie powoduje przerwania skryptu, ale ostrzeżenie zostanie zalogowane. W środowisku deweloperskim przechwytuje je [Tracy |tracy:] i wyświetla bluescreen. `InvalidLinkTextual` działa tak, że jako URL zwraca komunikat o błędzie zaczynający się od znaków `#error:`. Aby takie odnośniki rzucały się w oczy na pierwszy rzut oka, dodaj do swojego CSS: ```css a[href^="#error:"] { @@ -266,7 +299,7 @@ a[href^="#error:"] { } ``` -Jeśli nie chcemy, aby w środowisku deweloperskim generowane były ostrzeżenia, możemy ustawić tryb cichy bezpośrednio w [konfiguracji|configuration]. +Jeśli nie chcemy, aby w środowisku deweloperskim powstawały ostrzeżenia, możemy wyciszyć je bezpośrednio w [konfiguracji|configuration]. ```neon application: @@ -277,10 +310,10 @@ application: LinkGenerator ============= -Jak tworzyć linki z podobnym komfortem jak metoda `link()`, ale bez obecności presentera? Do tego służy [api:Nette\Application\LinkGenerator]. +Jak tworzyć odnośniki z podobną wygodą jak metodą `link()`, ale bez obecności presentera? Od tego jest [api:Nette\Application\LinkGenerator]. -LinkGenerator to usługa, którą można sobie przekazać przez konstruktor, a następnie tworzyć linki za pomocą jej metody `link()`. +LinkGenerator to usługa, którą możesz otrzymać przez konstruktor, a następnie tworzyć odnośniki jej metodą `link()`. -W porównaniu do presenterów jest tu różnica. LinkGenerator tworzy wszystkie linki od razu jako absolutne URL. Ponadto nie istnieje żaden "aktualny presenter", więc nie można jako celu podać tylko nazwy akcji `link('default')` ani podawać ścieżek względnych do modułów. +W porównaniu z presenterami jest różnica. LinkGenerator tworzy wszystkie odnośniki bezpośrednio jako bezwzględne URL. Ponadto nie istnieje "bieżący presenter", nie da się więc podać jako celu samej nazwy akcji `link('default')` ani używać ścieżek względnych do modułów. -Nieprawidłowe linki zawsze rzucają `Nette\Application\UI\InvalidLinkException`. +Nieprawidłowe odnośniki zawsze zgłaszają `Nette\Application\UI\InvalidLinkException`. diff --git a/application/pl/directory-structure.texy b/application/pl/directory-structure.texy index f3a84e2f67..4df3dbebdd 100644 --- a/application/pl/directory-structure.texy +++ b/application/pl/directory-structure.texy @@ -3,43 +3,43 @@ Struktura katalogów aplikacji
    -Jak zaprojektować przejrzystą i skalowalną strukturę katalogów dla projektów w Nette Framework? Pokażemy sprawdzone praktyki, które pomogą w organizacji kodu. Dowiesz się: +Jak zaprojektować przejrzystą i skalowalną strukturę katalogów dla projektów w Nette Framework? Pokażemy Ci sprawdzone praktyki, które pomogą uporządkować kod. Dowiesz się: - jak **logicznie podzielić** aplikację na katalogi -- jak zaprojektować strukturę tak, aby **dobrze skalowała się** wraz ze wzrostem projektu -- jakie są **możliwe alternatywy** i ich zalety czy wady +- jak zaprojektować strukturę, aby **dobrze się skalowała** wraz z rozwojem projektu +- jakie są **możliwe alternatywy** oraz ich zalety i wady
    -Ważne jest, aby wspomnieć, że sam Nette Framework nie narzuca żadnej konkretnej struktury. Jest zaprojektowany tak, aby można go było łatwo dostosować do wszelkich potrzeb i preferencji. +Warto wspomnieć, że sam Nette Framework nie wymusza żadnej konkretnej struktury. Zaprojektowano go tak, aby dawał się łatwo dostosować do dowolnych potrzeb i preferencji. Podstawowa struktura projektu ============================= -Chociaż Nette Framework nie dyktuje żadnej sztywnej struktury katalogów, istnieje sprawdzony domyślny układ w postaci [Web Project|https://github.com/nette/web-project]: +Choć Nette Framework nie narzuca żadnej sztywnej struktury katalogów, istnieje sprawdzony układ domyślny w postaci [Web Project|https://github.com/nette/web-project]: /--pre web-project/ -├── app/ ← katalog z aplikacją +├── app/ ← katalog aplikacji ├── assets/ ← pliki SCSS, JS, obrazy..., alternatywnie resources/ -├── bin/ ← skrypty dla wiersza poleceń +├── bin/ ← skrypty do wiersza poleceń ├── config/ ← konfiguracja ├── log/ ← zalogowane błędy ├── temp/ ← pliki tymczasowe, cache ├── tests/ ← testy -├── vendor/ ← biblioteki zainstalowane przez Composer +├── vendor/ ← biblioteki zainstalowane przez Composera └── www/ ← katalog publiczny (document-root) \-- -Tę strukturę można dowolnie modyfikować zgodnie z własnymi potrzebami - zmieniać nazwy lub przenosić foldery. Następnie wystarczy tylko zmodyfikować ścieżki względne do katalogów w pliku `Bootstrap.php` i ewentualnie `composer.json`. Nic więcej nie jest potrzebne, żadna skomplikowana rekonfiguracja, żadne zmiany stałych. Nette dysponuje inteligentną autodetekcją i automatycznie rozpozna lokalizację aplikacji, w tym jej bazę URL. +Strukturę tę możesz swobodnie modyfikować według swoich potrzeb: zmieniać nazwy folderów albo je przenosić. Wystarczy wtedy dostosować ścieżki względne do katalogów w `Bootstrap.php` i ewentualnie `composer.json`. Nic więcej nie jest potrzebne, żadnej skomplikowanej rekonfiguracji, żadnych zmian stałych. Nette ma sprytną autodetekcję i samo rozpoznaje położenie aplikacji wraz z jej bazowym URL. Zasady organizacji kodu ======================= -Kiedy po raz pierwszy eksplorujesz nowy projekt, powinieneś szybko się w nim zorientować. Wyobraź sobie, że rozwijasz katalog `app/Model/` i widzisz taką strukturę: +Gdy po raz pierwszy poznajesz nowy projekt, powinieneś móc szybko się w nim odnaleźć. Wyobraź sobie, że klikasz w katalog `app/Model/` i widzisz taką strukturę: /--pre app/Model/ @@ -48,7 +48,7 @@ Kiedy po raz pierwszy eksplorujesz nowy projekt, powinieneś szybko się w nim z └── Entities/ \-- -Z niej wyczytasz tylko to, że projekt używa jakichś usług, repozytoriów i encji. O rzeczywistym celu aplikacji nie dowiesz się absolutnie nic. +Dowiadujesz się z tego tylko tyle, że projekt używa jakichś usług, repozytoriów i encji. O rzeczywistym przeznaczeniu aplikacji nie dowiadujesz się niczego. Spójrzmy na inne podejście - **organizację według domen**: @@ -60,42 +60,42 @@ Spójrzmy na inne podejście - **organizację według domen**: └── Product/ \-- -Tutaj jest inaczej - na pierwszy rzut oka widać, że chodzi o e-sklep. Już same nazwy katalogów zdradzają, co aplikacja potrafi - pracuje z płatnościami, zamówieniami i produktami. +Tutaj jest inaczej - na pierwszy rzut oka widać, że chodzi o sklep internetowy. Same nazwy katalogów zdradzają, co aplikacja potrafi: pracuje z płatnościami, zamówieniami i produktami. -Pierwsze podejście (organizacja według typu klas) przynosi w praktyce szereg problemów: kod, który jest ze sobą logicznie powiązany, jest rozproszony w różnych folderach i trzeba między nimi przeskakiwać. Dlatego będziemy organizować według domen. +Pierwsze podejście (organizacja według typu klas) przynosi w praktyce kilka problemów: kod logicznie powiązany jest rozproszony po różnych folderach i trzeba między nimi skakać. Dlatego będziemy organizować według domen. Przestrzenie nazw ----------------- -Jest zwyczajem, że struktura katalogów odpowiada przestrzeniom nazw w aplikacji. Oznacza to, że fizyczna lokalizacja plików odpowiada ich namespace. Na przykład klasa umieszczona w `app/Model/Product/ProductRepository.php` powinna mieć namespace `App\Model\Product`. Ta zasada pomaga w orientacji w kodzie i upraszcza autoloading. +Zwyczajowo struktura katalogów odpowiada przestrzeniom nazw w aplikacji. Oznacza to, że fizyczne położenie plików zgadza się z ich przestrzenią nazw. Na przykład klasa leżąca w `app/Model/Product/ProductRepository.php` powinna mieć przestrzeń nazw `App\Model\Product`. Zasada ta pomaga w poruszaniu się po kodzie i upraszcza autoloading. -Liczba pojedyncza vs mnoga w nazwach ------------------------------------- +Liczba pojedyncza kontra mnoga w nazwach +---------------------------------------- -Zauważ, że w głównych katalogach aplikacji używamy liczby pojedynczej: `app`, `config`, `log`, `temp`, `www`. Podobnie wewnątrz aplikacji: `Model`, `Core`, `Presentation`. Dzieje się tak dlatego, że każdy z nich reprezentuje jeden spójny koncept. +Zauważ, że dla głównych katalogów aplikacji używamy liczby pojedynczej: `app`, `config`, `log`, `temp`, `www`. To samo dotyczy wnętrza aplikacji: `Model`, `Core`, `Presentation`. Dzieje się tak dlatego, że każdy z nich reprezentuje jedno spójne pojęcie. -Podobnie np. `app/Model/Product` reprezentuje wszystko związane z produktami. Nie nazwiemy tego `Products`, ponieważ nie jest to folder pełen produktów (byłyby tam pliki `nokia.php`, `samsung.php`). Jest to namespace zawierający klasy do pracy z produktami - `ProductRepository.php`, `ProductService.php`. +Podobnie `app/Model/Product` reprezentuje wszystko, co dotyczy produktów. Nie nazywamy go `Products`, bo to nie jest folder pełen produktów (zawierałby pliki w rodzaju `nokia.php`, `samsung.php`). To przestrzeń nazw zawierająca klasy do pracy z produktami: `ProductRepository.php`, `ProductService.php`. -Folder `app/Tasks` jest w liczbie mnogiej, ponieważ zawiera zestaw samodzielnych skryptów wykonywalnych - `CleanupTask.php`, `ImportTask.php`. Każdy z nich jest samodzielną jednostką. +Folder `app/Tasks` jest w liczbie mnogiej, bo zawiera zestaw osobnych wykonywalnych skryptów: `CleanupTask.php`, `ImportTask.php`. Każdy z nich jest niezależną jednostką. -Dla spójności zalecamy używanie: -- Liczby pojedynczej dla namespace reprezentującego funkcjonalną całość (nawet jeśli pracuje z wieloma encjami) -- Liczby mnogiej dla kolekcji samodzielnych jednostek -- W przypadku niepewności lub jeśli nie chcesz się nad tym zastanawiać, wybierz liczbę pojedynczą +Dla spójności zalecamy używać: +- liczby pojedynczej dla przestrzeni nazw reprezentujących jednostkę funkcjonalną (nawet jeśli pracuje z wieloma encjami) +- liczby mnogiej dla zbiorów niezależnych jednostek +- w razie wątpliwości albo gdy nie chcesz się nad tym zastanawiać, wybierz liczbę pojedynczą Katalog publiczny `www/` ======================== -Ten katalog jest jedynym dostępnym z sieci (tzw. document-root). Często można spotkać się również z nazwą `public/` zamiast `www/` - jest to tylko kwestia konwencji i nie ma wpływu na funkcjonalność. Katalog zawiera: -- [Punkt wejściowy |bootstrapping#index.php] aplikacji `index.php` -- Plik `.htaccess` z regułami dla mod_rewrite (w Apache) -- Pliki statyczne (CSS, JavaScript, obrazy) -- Przesłane pliki +Ten katalog jako jedyny jest dostępny z sieci (document-root). Często możesz spotkać się z nazwą `public/` zamiast `www/` - to tylko kwestia konwencji i nie wpływa na działanie aplikacji. Katalog zawiera: +- [punkt wejścia |bootstrapping#index.php] aplikacji `index.php` +- plik `.htaccess` z regułami mod_rewrite (dla Apache) +- pliki statyczne (CSS, JavaScript, obrazy) +- przesłane pliki -Dla prawidłowego zabezpieczenia aplikacji kluczowe jest posiadanie poprawnie [skonfigurowanego document-root |nette:troubleshooting#Jak zmienić lub usunąć katalog www z adresu URL]. +Dla właściwego bezpieczeństwa aplikacji kluczowe jest [poprawne skonfigurowanie document-root |nette:troubleshooting#Jak zmienić albo usunąć katalog www z URL?]. .[note] Nigdy nie umieszczaj w tym katalogu folderu `node_modules/` - zawiera tysiące plików, które mogą być wykonywalne i nie powinny być publicznie dostępne. @@ -104,32 +104,32 @@ Nigdy nie umieszczaj w tym katalogu folderu `node_modules/` - zawiera tysiące p Katalog aplikacji `app/` ======================== -To jest główny katalog z kodem aplikacji. Podstawowa struktura: +To główny katalog zawierający kod aplikacji. Podstawowa struktura: /--pre app/ -├── Core/ ← kwestie infrastrukturalne +├── Core/ ← sprawy infrastrukturalne ├── Model/ ← logika biznesowa ├── Presentation/ ← presentery i szablony ├── Tasks/ ← skrypty poleceń └── Bootstrap.php ← klasa startowa aplikacji \-- -`Bootstrap.php` to [klasa startowa aplikacji|bootstrapping], która inicjalizuje środowisko, ładuje konfigurację i tworzy kontener DI. +`Bootstrap.php` to [klasa startowa aplikacji|bootstrapping], która inicjalizuje środowisko, wczytuje konfigurację i tworzy kontener DI. -Przyjrzyjmy się teraz poszczególnym podkatalogom bardziej szczegółowo. +Przyjrzyjmy się teraz poszczególnym podkatalogom szczegółowo. Presentery i szablony ===================== -Część prezentacyjną aplikacji mamy w katalogu `app/Presentation`. Alternatywą jest krótkie `app/UI`. Jest to miejsce dla wszystkich presenterów, ich szablonów i ewentualnych klas pomocniczych. +Warstwa prezentacji aplikacji leży w katalogu `app/Presentation`. Alternatywą jest krótsze `app/UI`. To miejsce na wszystkie presentery, ich szablony i ewentualne powiązane klasy pomocnicze. -Tę warstwę organizujemy według domen. W złożonym projekcie, który łączy e-sklep, blog i API, struktura wyglądałaby tak: +Warstwę tę organizujemy według domen. W złożonym projekcie łączącym sklep internetowy, blog i API struktura wyglądałaby tak: /--pre app/Presentation/ -├── Shop/ ← frontend e-sklepu +├── Shop/ ← frontend sklepu │ ├── Product/ │ ├── Cart/ │ └── Order/ @@ -143,23 +143,23 @@ Tę warstwę organizujemy według domen. W złożonym projekcie, który łączy └── V1/ \-- -Natomiast w prostym blogu użylibyśmy podziału: +Odwrotnie, dla prostego bloga użylibyśmy takiej struktury: /--pre app/Presentation/ -├── Front/ ← frontend strony +├── Front/ ← frontend witryny │ ├── Home/ │ └── Post/ ├── Admin/ ← administracja │ ├── Dashboard/ │ └── Posts/ ├── Error/ -└── Export/ ← RSS, mapy strony itp. +└── Export/ ← RSS, mapy strony itd. \-- -Foldery takie jak `Home/` czy `Dashboard/` zawierają presentery i szablony. Foldery takie jak `Front/`, `Admin/` czy `Api/` nazywamy **modułami**. Technicznie są to zwykłe katalogi, które służą do logicznego podziału aplikacji. +Foldery w rodzaju `Home/` czy `Dashboard/` zawierają presentery i szablony. Foldery w rodzaju `Front/`, `Admin/` czy `Api/` nazywamy **modułami**. Technicznie to zwykłe katalogi służące do logicznego podziału aplikacji. -Każdy folder z presenterem zawiera tak samo nazwany presenter i jego szablony. Na przykład folder `Dashboard/` zawiera: +Każdy folder zawierający presenter obejmuje sam plik presentera i jego szablony. Na przykład folder `Dashboard/` zawiera: /--pre Dashboard/ @@ -167,7 +167,7 @@ Każdy folder z presenterem zawiera tak samo nazwany presenter i jego szablony. └── default.latte ← szablon \-- -Ta struktura katalogów odzwierciedla się w przestrzeniach nazw klas. Na przykład `DashboardPresenter` znajduje się w przestrzeni nazw `App\Presentation\Admin\Dashboard` (zobacz [#Mapowanie presenterów]): +Ta struktura katalogów odzwierciedla się w przestrzeniach nazw klas. Na przykład `DashboardPresenter` leży w przestrzeni nazw `App\Presentation\Admin\Dashboard` (zobacz [#Mapowanie presenterów]): ```php namespace App\Presentation\Admin\Dashboard; @@ -178,22 +178,22 @@ class DashboardPresenter extends Nette\Application\UI\Presenter } ``` -Do presentera `Dashboard` wewnątrz modułu `Admin` odwołujemy się w aplikacji za pomocą notacji dwukropkowej jako `Admin:Dashboard`. Do jego akcji `default` następnie jako `Admin:Dashboard:default`. W przypadku zagnieżdżonych modułów używamy więcej dwukropków, na przykład `Shop:Order:Detail:default`. +Do presentera `Dashboard` w module `Admin` odwołujemy się w aplikacji zapisem z dwukropkiem jako `Admin:Dashboard`. Do jego akcji `default` odwołujemy się potem jako `Admin:Dashboard:default`. Przy zagnieżdżonych modułach używamy większej liczby dwukropków, na przykład `Shop:Order:Detail:default`. Elastyczny rozwój struktury --------------------------- -Jedną z wielkich zalet tej struktury jest to, jak elegancko dostosowuje się do rosnących potrzeb projektu. Jako przykład weźmy część generującą kanały XML. Na początku mamy prostą postać: +Jedną z wielkich zalet tej struktury jest to, jak elegancko dostosowuje się do rosnących potrzeb projektu. Jako przykład weźmy część generującą feedy XML. Na początku mamy prostą postać: /--pre Export/ ├── ExportPresenter.php ← jeden presenter dla wszystkich eksportów -├── sitemap.latte ← szablon dla mapy strony -└── feed.latte ← szablon dla kanału RSS +├── sitemap.latte ← szablon mapy strony +└── feed.latte ← szablon feedu RSS \-- -Z czasem pojawią się kolejne typy kanałów i będziemy potrzebować dla nich więcej logiki... Żaden problem! Folder `Export/` po prostu stanie się modułem: +Z czasem dochodzą kolejne typy feedów i potrzebujemy dla nich więcej logiki... Żaden problem! Folder `Export/` po prostu staje się modułem: /--pre Export/ @@ -202,38 +202,38 @@ Z czasem pojawią się kolejne typy kanałów i będziemy potrzebować dla nich │ └── sitemap.latte └── Feed/ ├── FeedPresenter.php - ├── zbozi.latte ← kanał dla Zboží.cz - └── heureka.latte ← kanał dla Heureka.cz + ├── amazon.latte ← feed dla Amazona + └── ebay.latte ← feed dla eBaya \-- -Ta transformacja jest całkowicie płynna - wystarczy utworzyć nowe podfoldery, podzielić do nich kod i zaktualizować linki (np. z `Export:feed` na `Export:Feed:zbozi`). Dzięki temu możemy strukturę stopniowo rozszerzać w miarę potrzeb, poziom zagnieżdżenia nie jest w żaden sposób ograniczony. +Ta przemiana jest całkowicie płynna - wystarczy utworzyć nowe podfoldery, podzielić między nie kod i zaktualizować odnośniki (np. z `Export:feed` na `Export:Feed:amazon`). Dzięki temu możemy stopniowo rozbudowywać strukturę według potrzeb, a poziom zagnieżdżenia nie jest w żaden sposób ograniczony. -Jeśli na przykład w administracji masz wiele presenterów dotyczących zarządzania zamówieniami, takich jak `OrderDetail`, `OrderEdit`, `OrderDispatch` itp., możesz dla lepszej organizacji w tym miejscu utworzyć moduł (folder) `Order`, w którym będą (foldery dla) presenterów `Detail`, `Edit`, `Dispatch` i inne. +Jeśli na przykład w administracji masz wiele presenterów związanych z zarządzaniem zamówieniami, takich jak `OrderDetail`, `OrderEdit`, `OrderDispatch` itd., możesz dla lepszej organizacji utworzyć moduł (folder) o nazwie `Order`, który będzie zawierać (foldery dla) presentery `Detail`, `Edit`, `Dispatch` i inne. -Lokalizacja szablonów ---------------------- +Położenie szablonów +------------------- -W poprzednich przykładach widzieliśmy, że szablony są umieszczone bezpośrednio w folderze z presenterem: +W poprzednich przykładach widzieliśmy, że szablony leżą bezpośrednio w folderze z presenterem: /--pre Dashboard/ ├── DashboardPresenter.php ← presenter -├── DashboardTemplate.php ← opcjonalna klasa dla szablonu +├── DashboardTemplate.php ← opcjonalna klasa szablonu └── default.latte ← szablon \-- -Ta lokalizacja w praktyce okazuje się najwygodniejsza - wszystkie powiązane pliki masz od razu pod ręką. +To położenie okazuje się w praktyce najwygodniejsze - masz wszystkie powiązane pliki pod ręką. -Alternatywnie możesz umieścić szablony w podfolderze `templates/`. Nette wspiera obie warianty. Możesz nawet umieścić szablony całkowicie poza folderem `Presentation/`. Wszystko o możliwościach umieszczania szablonów znajdziesz w rozdziale [Wyszukiwanie szablonów |templates#Wyszukiwanie szablonów]. +Alternatywnie możesz umieścić szablony w podfolderze `templates/`. Nette obsługuje oba warianty. Możesz nawet umieścić szablony całkowicie poza folderem `Presentation/`. Wszystko o możliwościach położenia szablonów znajdziesz w rozdziale [Wyszukiwanie szablonów |templates#Wyszukiwanie szablonów]. Klasy pomocnicze i komponenty ----------------------------- -Do presenterów i szablonów często należą również inne pliki pomocnicze. Umieszczamy je logicznie według ich zakresu: +Presenterom i szablonom często towarzyszą inne pliki pomocnicze. Umieszczamy je logicznie według ich zasięgu: -1. **Bezpośrednio przy presenterze** w przypadku specyficznych komponentów dla danego presentera: +1. **Bezpośrednio przy presenterze** w przypadku komponentów specyficznych dla tego presentera: /--pre Product/ @@ -242,7 +242,7 @@ Do presenterów i szablonów często należą również inne pliki pomocnicze. U └── FilterForm.php ← formularz do filtrowania \-- -2. **Dla modułu** - zalecamy wykorzystanie folderu `Accessory`, który umieści się przejrzyście na początku alfabetu: +2. **Dla modułu** - zalecamy folder `Accessory`, który wygodnie ląduje na początku alfabetu: /--pre Front/ @@ -263,30 +263,30 @@ Do presenterów i szablonów często należą również inne pliki pomocnicze. U └── Admin/ \-- -Lub możesz umieścić klasy pomocnicze takie jak `LatteExtension.php` czy `TemplateFilters.php` w folderze infrastruktury `app/Core/Latte/`. A komponenty w `app/Components`. Wybór zależy od zwyczajów zespołu. +Alternatywnie klasy pomocnicze, takie jak `LatteExtension.php` czy `TemplateFilters.php`, możesz umieścić w folderze infrastrukturalnym `app/Core/Latte/`. A komponenty w `app/Components`. Wybór zależy od konwencji zespołu. Model - serce aplikacji ======================= -Model zawiera całą logikę biznesową aplikacji. Dla jego organizacji obowiązuje ponownie zasada - strukturyzujemy według domen: +Model zawiera całą logikę biznesową aplikacji. Zasada jego organizacji jest znów taka sama: strukturyzuj według domen: /--pre app/Model/ -├── Payment/ ← wszystko związane z płatnościami -│ ├── PaymentFacade.php ← główny punkt wejściowy +├── Payment/ ← wszystko o płatnościach +│ ├── PaymentFacade.php ← główny punkt wejścia │ ├── PaymentRepository.php │ ├── Payment.php ← encja -├── Order/ ← wszystko związane z zamówieniami +├── Order/ ← wszystko o zamówieniach │ ├── OrderFacade.php │ ├── OrderRepository.php │ ├── Order.php -└── Shipping/ ← wszystko związane z wysyłką +└── Shipping/ ← wszystko o wysyłce \-- -W modelu typowo spotkasz się z tymi typami klas: +W modelu typowo spotkasz te rodzaje klas: -**Fasady**: reprezentują główny punkt wejściowy do konkretnej domeny w aplikacji. Działają jako orkiestrator, który koordynuje współpracę między różnymi usługami w celu implementacji kompletnych przypadków użycia (jak "utwórz zamówienie" lub "przetwórz płatność"). Pod swoją warstwą orkiestracji fasada ukrywa szczegóły implementacyjne przed resztą aplikacji, dostarczając czysty interfejs do pracy z daną domeną. +**Fasady**: reprezentują główny punkt wejścia do konkretnej domeny w aplikacji. Działają jak orkiestrator, koordynując współpracę różnych usług w celu zrealizowania kompletnych przypadków użycia (jak "utwórz zamówienie" czy "przetwórz płatność"). Pod swoją warstwą orkiestracji fasada ukrywa przed resztą aplikacji szczegóły implementacyjne, dostarczając tym samym czysty interfejs do pracy z daną domeną. ```php class OrderFacade @@ -296,12 +296,12 @@ class OrderFacade // walidacja // utworzenie zamówienia // wysłanie e-maila - // zapisanie do statystyk + // zapis do statystyk } } ``` -**Usługi**: koncentrują się na specyficznej operacji biznesowej w ramach domeny. W przeciwieństwie do fasady, która orkiestruje całe przypadki użycia, usługa implementuje konkretną logikę biznesową (jak kalkulacje cen lub przetwarzanie płatności). Usługi są typowo bezstanowe i mogą być używane albo przez fasady jako bloki budulcowe dla bardziej złożonych operacji, albo bezpośrednio przez inne części aplikacji dla prostszych zadań. +**Usługi**: skupiają się na konkretnych operacjach biznesowych w obrębie domeny. W odróżnieniu od fasad, które orkiestrują całe przypadki użycia, usługa implementuje konkretną logikę biznesową (jak obliczanie cen czy przetwarzanie płatności). Usługi są zwykle bezstanowe i mogą być używane albo przez fasady jako klocki do bardziej złożonych operacji, albo bezpośrednio przez inne części aplikacji do prostszych zadań. ```php class PricingService @@ -313,7 +313,7 @@ class PricingService } ``` -**Repozytoria**: zapewniają całą komunikację z magazynem danych, typowo bazą danych. Jego zadaniem jest wczytywanie i zapisywanie encji oraz implementacja metod do ich wyszukiwania. Repozytorium odizolowuje resztę aplikacji od szczegółów implementacyjnych bazy danych i dostarcza interfejs zorientowany obiektowo do pracy z danymi. +**Repozytoria**: zajmują się całą komunikacją z magazynem danych, zwykle z bazą danych. Ich zadaniem jest wczytywanie i zapisywanie encji oraz implementowanie metod do ich wyszukiwania. Repozytorium osłania resztę aplikacji przed szczegółami implementacyjnymi bazy danych i dostarcza obiektowy interfejs do pracy z danymi. ```php class OrderRepository @@ -328,10 +328,10 @@ class OrderRepository } ``` -**Encje**: obiekty reprezentujące główne koncepty biznesowe w aplikacji, które mają swoją tożsamość i zmieniają się w czasie. Typowo są to klasy mapowane na tabele bazy danych za pomocą ORM (jak Nette Database Explorer lub Doctrine). Encje mogą zawierać reguły biznesowe dotyczące ich danych oraz logikę walidacji. +**Encje**: obiekty reprezentujące główne pojęcia biznesowe w aplikacji, które mają własną tożsamość i zmieniają się w czasie. Zwykle są to klasy mapowane na tabele bazy danych za pomocą ORM (jak Nette Database Explorer albo Doctrine). Encje mogą zawierać reguły biznesowe związane ze swoimi danymi i logikę walidacji. ```php -// Encja mapowana na tabelę bazy danych orders +// encja mapowana na tabelę bazy danych 'orders' class Order extends Nette\Database\Table\ActiveRow { public function addItem(Product $product, int $quantity): void @@ -345,13 +345,13 @@ class Order extends Nette\Database\Table\ActiveRow } ``` -**Obiekty wartości**: niemutowalne obiekty reprezentujące wartości bez własnej tożsamości - na przykład kwota pieniężna lub adres e-mail. Dwie instancje obiektu wartości z tymi samymi wartościami są uważane za identyczne. +**Value Objects**: niezmienne obiekty reprezentujące wartości bez własnej tożsamości, na przykład kwotę pieniężną albo adres e-mail. Dwie instancje value objectu o tych samych wartościach uznawane są za identyczne. -Kod infrastruktury -================== +Kod infrastrukturalny +===================== -Folder `Core/` (lub także `Infrastructure/`) jest domem dla technicznego fundamentu aplikacji. Kod infrastruktury typowo obejmuje: +Folder `Core/` (albo alternatywnie `Infrastructure/`) jest domem technicznego fundamentu aplikacji. Kod infrastrukturalny obejmuje zwykle: /--pre app/Core/ @@ -370,7 +370,7 @@ Folder `Core/` (lub także `Infrastructure/`) jest domem dla technicznego fundam └── Stripe/ \-- -W mniejszych projektach oczywiście wystarczy płaska struktura: +Przy mniejszych projektach naturalnie wystarczy struktura płaska: /--pre Core/ @@ -379,29 +379,29 @@ W mniejszych projektach oczywiście wystarczy płaska struktura: └── QueueMailer.php \-- -Chodzi o kod, który: +To kod, który: -- Rozwiązuje problemy techniczne infrastruktury (routing, logowanie, cachowanie) -- Integruje usługi zewnętrzne (Sentry, Elasticsearch, Redis) -- Dostarcza podstawowe usługi dla całej aplikacji (mail, baza danych) -- Jest zazwyczaj niezależny od konkretnej domeny - cache lub logger działa tak samo dla e-sklepu czy bloga. +- zajmuje się infrastrukturą techniczną (routing, logowanie, cache) +- integruje usługi zewnętrzne (Sentry, Elasticsearch, Redis) +- dostarcza podstawowe usługi dla całej aplikacji (poczta, baza danych) +- jest w większości niezależny od konkretnej domeny - cache albo logger działa tak samo dla sklepu internetowego i dla bloga -Wahasz się, czy dana klasa należy tutaj, czy do modelu? Kluczowa różnica polega na tym, że kod w `Core/`: +Zastanawiasz się, czy dana klasa należy tutaj, czy do modelu? Kluczowa różnica jest taka, że kod w `Core/`: -- Nie wie nic o domenie (produkty, zamówienia, artykuły) -- Jest zazwyczaj możliwe przeniesienie go do innego projektu -- Rozwiązuje "jak to działa" (jak wysłać maila), a nie "co to robi" (jakiego maila wysłać) +- nie wie nic o domenie (produkty, zamówienia, artykuły) +- zwykle da się przenieść do innego projektu +- rozwiązuje "jak to działa" (jak wysłać e-mail), a nie "co robi" (jaki e-mail wysłać) Przykład dla lepszego zrozumienia: -- `App\Core\MailerFactory` - tworzy instancje klasy do wysyłania e-maili, obsługuje ustawienia SMTP -- `App\Model\OrderMailer` - używa `MailerFactory` do wysyłania e-maili o zamówieniach, zna ich szablony i wie, kiedy mają być wysłane +- `App\Core\MailerFactory` - tworzy instancje klasy do wysyłania e-maili, zajmuje się ustawieniami SMTP +- `App\Model\OrderMailer` - używa `MailerFactory` do wysyłania e-maili o zamówieniach, zna ich szablony i wie, kiedy mają być wysyłane Skrypty poleceń =============== -Aplikacje często potrzebują wykonywać czynności poza zwykłymi żądaniami HTTP - czy to chodzi o przetwarzanie danych w tle, konserwację, czy zadania okresowe. Do uruchamiania służą proste skrypty w katalogu `bin/`, samą logikę implementacyjną umieszczamy w `app/Tasks/` (ewentualnie `app/Commands/`). +Aplikacje często potrzebują wykonywać czynności poza zwykłymi żądaniami HTTP, czy to przetwarzanie danych w tle, czy konserwację, czy zadania okresowe. Do uruchamiania służą proste skrypty w katalogu `bin/`, a właściwa logika implementacji umieszczana jest w `app/Tasks/` (albo `app/Commands/`). Przykład: @@ -418,27 +418,27 @@ Przykład: └── ReminderCommand.php ← powiadomienia dla klientów \-- -Co należy do modelu, a co do skryptów poleceń? Na przykład logika do wysłania jednego e-maila jest częścią modelu, masowa wysyłka tysięcy e-maili już należy do `Tasks/`. +Co należy do modelu, a co do skryptów poleceń? Na przykład logika wysyłania jednego e-maila jest częścią modelu, natomiast masowe wysyłanie tysięcy e-maili należy do `Tasks/`. -Zadania zazwyczaj [uruchamiamy z wiersza poleceń |https://blog.nette.org/en/cli-scripts-in-nette-application] lub przez cron. Można je uruchamiać również przez żądanie HTTP, ale trzeba pamiętać o bezpieczeństwie. Presenter, który uruchomi zadanie, trzeba zabezpieczyć, na przykład tylko dla zalogowanych użytkowników lub silnym tokenem i dostępem z dozwolonych adresów IP. W przypadku długich zadań trzeba zwiększyć limit czasu skryptu i użyć `session_write_close()`, aby nie blokować sesji. +Zadania uruchamiane są zwykle z wiersza poleceń albo przez cron: skrypt w `bin/` tworzy kontener DI metodą [bootConsoleApplication() |bootstrapping#Różne środowiska] i pobiera z niego potrzebną usługę. Można je uruchamiać również przez żądanie HTTP, ale trzeba pomyśleć o bezpieczeństwie. Presenter uruchamiający zadanie trzeba zabezpieczyć, na przykład tylko dla zalogowanych użytkowników albo silnym tokenem i dostępem z dozwolonych adresów IP. Przy długo działających zadaniach trzeba zwiększyć limit czasu skryptu i użyć `session_write_close()`, aby nie blokować sesji. Inne możliwe katalogi ===================== -Oprócz wspomnianych podstawowych katalogów można w zależności od potrzeb projektu dodać inne specjalistyczne foldery. Spójrzmy na najczęstsze z nich i ich zastosowanie: +Poza wymienionymi podstawowymi katalogami możesz dodać kolejne, wyspecjalizowane foldery według potrzeb projektu. Spójrzmy na najczęstsze i ich zastosowanie: /--pre app/ -├── Api/ ← logika dla API niezależna od warstwy prezentacji -├── Database/ ← skrypty migracyjne i seedery dla danych testowych +├── Api/ ← logika API niezależna od warstwy prezentacji +├── Database/ ← skrypty migracyjne i seedery danych testowych ├── Components/ ← współdzielone komponenty wizualne w całej aplikacji -├── Event/ ← przydatne jeśli używasz architektury sterowanej zdarzeniami -├── Mail/ ← szablony e-mail i powiązana logika +├── Event/ ← przydatne przy architekturze zdarzeniowej +├── Mail/ ← szablony e-maili i powiązana logika └── Utils/ ← klasy pomocnicze \-- -Dla współdzielonych komponentów wizualnych używanych w presenterach w całej aplikacji można użyć folderu `app/Components` lub `app/Controls`: +Na współdzielone komponenty wizualne używane w presenterach w całej aplikacji możesz przeznaczyć folder `app/Components` albo `app/Controls`: /--pre app/Components/ @@ -447,18 +447,18 @@ Dla współdzielonych komponentów wizualnych używanych w presenterach w całej │ └── UserForm.php ├── Grid/ ← komponenty do listowania danych │ └── DataGrid.php -└── Navigation/ ← elementy nawigacyjne +└── Navigation/ ← elementy nawigacji ├── Breadcrumbs.php └── Menu.php \-- -Tutaj należą komponenty, które mają bardziej złożoną logikę. Jeśli chcesz współdzielić komponenty między wieloma projektami, wskazane jest wydzielenie ich do osobnego pakietu composera. +To miejsce dla komponentów o bardziej złożonej logice. Jeśli chcesz współdzielić komponenty między wieloma projektami, warto wydzielić je do osobnego pakietu Composera. -Do katalogu `app/Mail` można umieścić zarządzanie komunikacją e-mail: +W katalogu `app/Mail` możesz umieścić zarządzanie komunikacją e-mailową: /--pre app/Mail/ -├── templates/ ← szablony e-mail +├── templates/ ← szablony e-maili │ ├── order-confirmation.latte │ └── welcome.latte └── OrderMailer.php @@ -468,32 +468,32 @@ Do katalogu `app/Mail` można umieścić zarządzanie komunikacją e-mail: Mapowanie presenterów ===================== -Mapowanie definiuje reguły wnioskowania nazwy klasy z nazwy presentera. Specyfikujemy je w [konfiguracji|configuration] pod kluczem `application › mapping`. +Mapowanie definiuje reguły wyprowadzania nazwy klasy z nazwy presentera. Podajemy je w [konfiguracji|configuration] pod kluczem `application › mapping`. -Na tej stronie pokazaliśmy, że presentery umieszczamy w folderze `app/Presentation` (ewentualnie `app/UI`). Tę konwencję musimy przekazać Nette w pliku konfiguracyjnym. Wystarczy jedna linia: +Na tej stronie pokazaliśmy, że presentery umieszczamy w folderze `app/Presentation` (albo `app/UI`). Od Nette Application 3.3 jest to konwencja domyślna, której nie trzeba konfigurować. Jeśli używasz innej struktury albo chcesz podać mapowanie jawnie, ustawienie domyślne odpowiada temu wierszowi: ```neon application: mapping: App\Presentation\*\**Presenter ``` -Jak działa mapowanie? Dla lepszego zrozumienia najpierw wyobraźmy sobie aplikację bez modułów. Chcemy, aby klasy presenterów należały do przestrzeni nazw `App\Presentation`, aby presenter `Home` mapował się na klasę `App\Presentation\HomePresenter`. Co osiągniemy tą konfiguracją: +Jak działa mapowanie? Dla lepszego zrozumienia wyobraźmy sobie najpierw aplikację bez modułów. Chcemy, aby klasy presenterów należały do przestrzeni nazw `App\Presentation`, tak aby presenter `Home` mapował się na klasę `App\Presentation\HomePresenter`. Osiągniemy to taką konfiguracją: ```neon application: mapping: App\Presentation\*Presenter ``` -Mapowanie działa tak, że nazwa presentera `Home` zastępuje gwiazdkę w masce `App\Presentation\*Presenter`, przez co uzyskujemy wynikową nazwę klasy `App\Presentation\HomePresenter`. Proste! +Mapowanie działa tak, że gwiazdka w masce `App\Presentation\*Presenter` zastępowana jest nazwą presentera `Home`, co daje ostateczną nazwę klasy `App\Presentation\HomePresenter`. Proste! -Jak jednak widać w przykładach w tym i innych rozdziałach, klasy presenterów umieszczamy w eponimicznych podkatalogach, na przykład presenter `Home` mapuje się na klasę `App\Presentation\Home\HomePresenter`. Osiągniemy to przez podwojenie dwukropka (wymaga Nette Application 3.2): +Jak jednak widzisz w przykładach w tym i innych rozdziałach, klasy presenterów umieszczamy w jednoimiennych podkatalogach, na przykład presenter `Home` mapuje się na klasę `App\Presentation\Home\HomePresenter`. Osiągniemy to, używając podwójnej gwiazdki `**` (wymaga Nette Application 3.2.3): ```neon application: mapping: App\Presentation\**Presenter ``` -Teraz przystąpimy do mapowania presenterów do modułów. Dla każdego modułu możemy zdefiniować specyficzne mapowanie: +Przejdźmy teraz do mapowania presenterów w modułach. Dla każdego modułu możemy zdefiniować własne mapowanie: ```neon application: @@ -503,9 +503,9 @@ application: Api: App\Api\*Presenter ``` -Zgodnie z tą konfiguracją presenter `Front:Home` mapuje się na klasę `App\Presentation\Front\Home\HomePresenter`, podczas gdy presenter `Api:OAuth` na klasę `App\Api\OAuthPresenter`. +Zgodnie z tą konfiguracją presenter `Front:Home` mapuje się na klasę `App\Presentation\Front\Home\HomePresenter`, a presenter `Api:OAuth` na klasę `App\Api\OAuthPresenter`. -Ponieważ moduły `Front` i `Admin` mają podobny sposób mapowania i takich modułów będzie prawdopodobnie więcej, możliwe jest utworzenie ogólnej reguły, która je zastąpi. Do maski klasy dojdzie więc nowa gwiazdka dla modułu: +Ponieważ moduły `Front` i `Admin` mają podobny wzorzec mapowania, a takich modułów będzie zapewne więcej, można utworzyć ogólną regułę, która je zastąpi. Do maski klasy dochodzi nowa gwiazdka dla modułu: ```neon application: @@ -514,9 +514,9 @@ application: Api: App\Api\*Presenter ``` -Działa to również dla głębiej zagnieżdżonych struktur katalogów, jak na przykład presenter `Admin:User:Edit`, segment z gwiazdką powtarza się dla każdego poziomu, a wynikiem jest klasa `App\Presentation\Admin\User\Edit\EditPresenter`. +Działa to również dla głębiej zagnieżdżonych struktur katalogów, jak presenter `Admin:User:Edit`, gdzie segment z gwiazdką powtarza się dla każdego poziomu modułu, co daje klasę `App\Presentation\Admin\User\Edit\EditPresenter`. -Alternatywnym zapisem jest użycie zamiast stringa tablicy składającej się z trzech segmentów. Ten zapis jest ekwiwalentny z poprzednim: +Alternatywnym zapisem jest użycie zamiast stringa tablicy złożonej z trzech segmentów. Dla pokazanych wyżej przykładów zapis ten jest równoważny poprzedniemu: ```neon application: diff --git a/application/pl/how-it-works.texy b/application/pl/how-it-works.texy index b0a9e60584..08541766b4 100644 --- a/application/pl/how-it-works.texy +++ b/application/pl/how-it-works.texy @@ -3,10 +3,10 @@ Jak działają aplikacje?
    -Właśnie czytasz podstawowy dokument dokumentacji Nette. Poznasz całą zasadę działania aplikacji internetowych. Krok po kroku, od A do Z, od momentu powstania aż do ostatniego tchnienia skryptu PHP. Po przeczytaniu będziesz wiedzieć: +Czytasz właśnie fundamentalny rozdział dokumentacji Nette. Poznasz kompletne zasady działania aplikacji webowych, od A do Z, od chwili narodzin żądania aż po zakończenie skryptu PHP. Po lekturze będziesz wiedzieć: - jak to wszystko działa -- co to jest Bootstrap, Presenter i kontener DI +- czym są Bootstrap, Presenter i kontener DI - jak wygląda struktura katalogów
    @@ -15,16 +15,16 @@ Właśnie czytasz podstawowy dokument dokumentacji Nette. Poznasz całą zasadę Struktura katalogów =================== -Otwórz przykład szkieletu aplikacji internetowej o nazwie [WebProject|https://github.com/nette/web-project] i podczas czytania możesz patrzeć na pliki, o których jest mowa. +Otwórz przykładowy szkielet aplikacji webowej o nazwie [WebProject|https://github.com/nette/web-project]. Czytając, możesz zaglądać do omawianych plików. Struktura katalogów wygląda mniej więcej tak: /--pre web-project/ -├── app/ ← katalog z aplikacją -│ ├── Core/ ← podstawowe klasy niezbędne do działania +├── app/ ← katalog aplikacji +│ ├── Core/ ← klasy rdzenia niezbędne do działania │ │ └── RouterFactory.php ← konfiguracja adresów URL -│ ├── Presentation/ ← presentery, szablony & spółka. +│ ├── Presentation/ ← presentery, szablony i spółka │ │ ├── @layout.latte ← szablon layoutu │ │ └── Home/ ← katalog presentera Home │ │ ├── HomePresenter.php ← klasa presentera Home @@ -35,53 +35,53 @@ Struktura katalogów wygląda mniej więcej tak: ├── config/ ← pliki konfiguracyjne │ ├── common.neon │ └── services.neon -├── log/ ← logowane błędy +├── log/ ← zalogowane błędy ├── temp/ ← pliki tymczasowe, cache, … -├── vendor/ ← biblioteki zainstalowane przez Composer +├── vendor/ ← biblioteki zainstalowane przez Composera │ ├── ... │ └── autoload.php ← autoloading wszystkich zainstalowanych pakietów -├── www/ ← katalog publiczny czyli document-root projektu +├── www/ ← katalog publiczny, document root projektu │ ├── assets/ ← skompilowane pliki statyczne (CSS, JS, obrazy, ...) │ ├── .htaccess ← reguły mod_rewrite -│ └── index.php ← pierwszy plik, którym uruchamia się aplikacja -└── .htaccess ← zabrania dostępu do wszystkich katalogów oprócz www +│ └── index.php ← plik początkowy uruchamiający aplikację +└── .htaccess ← zabrania dostępu do wszystkich katalogów poza www \-- -Strukturę katalogów można dowolnie zmieniać, foldery przemianować lub przenieść, jest całkowicie elastyczna. Nette dodatkowo dysponuje inteligentną autodetekcją i automatycznie rozpozna lokalizację aplikacji, w tym jej bazę URL. +Strukturę katalogów możesz dowolnie zmieniać, zmieniać nazwy folderów albo je przenosić; jest całkowicie elastyczna. Nette dysponuje też sprytną autodetekcją i samo rozpoznaje położenie aplikacji wraz z jej bazowym URL. -W nieco większych aplikacjach możemy foldery z presenterami i szablonami [podzielić na podkatalogi |directory-structure#Presentery i szablony] i klasy na przestrzenie nazw, które nazywamy modułami. +W nieco większych aplikacjach możemy uporządkować foldery presenterów i szablonów w [podkatalogi |directory-structure#Presentery i szablony], a klasy pogrupować w przestrzenie nazw, które nazywamy modułami. -Katalog `www/` reprezentuje tzw. katalog publiczny czyli document-root projektu. Można go przemianować bez konieczności ustawiania czegokolwiek dodatkowego po stronie aplikacji. Trzeba tylko [skonfigurować hosting |nette:troubleshooting#Jak zmienić lub usunąć katalog www z adresu URL] tak, aby document-root wskazywał na ten katalog. +Katalog `www/` reprezentuje katalog publiczny, czyli document-root projektu. Możesz zmienić jego nazwę bez konieczności konfigurowania czegokolwiek po stronie aplikacji. Wystarczy [skonfigurować hosting |nette:troubleshooting#Jak zmienić albo usunąć katalog www z URL?] tak, aby document-root wskazywał na ten katalog. -WebProject można również od razu pobrać wraz z Nette za pomocą [Composera |best-practices:composer]: +WebProject możesz też pobrać bezpośrednio, wraz z Nette, za pomocą [Composera |best-practices:composer]: ```shell composer create-project nette/web-project ``` -Na Linuksie lub macOS ustaw katalogom `log/` i `temp/` [prawa do zapisu |nette:troubleshooting#Ustawianie uprawnień do katalogów]. +Na Linuksie albo macOS ustaw katalogom `log/` i `temp/` [prawa do zapisu |nette:troubleshooting#Ustawienie uprawnień do katalogów]. -Aplikacja WebProject jest gotowa do uruchomienia, nie trzeba niczego konfigurować i można ją od razu wyświetlić w przeglądarce, uzyskując dostęp do folderu `www/`. +Aplikacja WebProject jest gotowa do uruchomienia; nie trzeba konfigurować absolutnie niczego i możesz obejrzeć ją od razu w przeglądarce, wchodząc do folderu `www/`. Żądanie HTTP ============ -Wszystko zaczyna się w chwili, gdy użytkownik w przeglądarce otwiera stronę. Czyli gdy przeglądarka puka do serwera z żądaniem HTTP. Żądanie kieruje się na jeden plik PHP, który znajduje się w publicznym katalogu `www/`, a jest nim `index.php`. Powiedzmy, że chodzi o żądanie na adres `https://example.com/product/123`. Dzięki odpowiedniemu [ustawieniu serwera |nette:troubleshooting#Jak skonfigurować serwer dla przyjaznych adresów URL] również ten URL mapuje się na plik `index.php` i ten się wykonuje. +Wszystko zaczyna się, gdy użytkownik otworzy w przeglądarce stronę. Przeglądarka wysyła do serwera żądanie HTTP. Żądanie to trafia do jednego pliku PHP leżącego w katalogu publicznym `www/`, czyli do `index.php`. Załóżmy, że żądanie dotyczy adresu `https://example.com/product/123`. Dzięki odpowiedniej [konfiguracji serwera |nette:troubleshooting#Jak skonfigurować serwer dla przyjaznych URL-i?] również ten URL mapowany jest na plik `index.php`, który następnie się wykonuje. Jego zadaniem jest: -1) zainicjalizowanie środowiska -2) uzyskanie fabryki -3) uruchomienie aplikacji Nette, która obsłuży żądanie +1) zainicjować środowisko +2) pozyskać fabrykę +3) uruchomić aplikację Nette, która obsłuży żądanie -Jaką fabrykę? Przecież nie produkujemy traktorów, ale strony internetowe! Poczekajcie, zaraz się to wyjaśni. +Jaką fabrykę? Przecież nie produkujemy traktorów, tylko robimy strony! Zaraz, zaraz, zaraz to wyjaśnimy. -Słowami „inicjalizacja środowiska” rozumiemy na przykład to, że aktywuje się [Tracy|tracy:], co jest niesamowitym narzędziem do logowania lub wizualizacji błędów. Na serwerze produkcyjnym błędy loguje, na deweloperskim od razu wyświetla. Dlatego do inicjalizacji należy również decyzja, czy strona działa w trybie produkcyjnym czy deweloperskim. Do tego Nette używa [inteligentnej autodetekcji |bootstrapping#Tryb deweloperski vs produkcyjny]: jeśli stronę uruchamiasz na localhost, działa w trybie deweloperskim. Nie musisz więc nic konfigurować, a aplikacja jest od razu gotowa zarówno do rozwoju, jak i ostrego wdrożenia. Te kroki są wykonywane i szczegółowo opisane w rozdziale o [klasie Bootstrap|bootstrapping]. +Przez "inicjalizację środowiska" rozumiemy na przykład aktywowanie [Tracy|tracy:], czyli znakomitego narzędzia do logowania i wizualizowania błędów. Na serwerze produkcyjnym błędy loguje, a w środowisku deweloperskim wyświetla je bezpośrednio. Inicjalizacja obejmuje więc również ustalenie, czy witryna działa w trybie produkcyjnym, czy deweloperskim. Nette używa do tego [sprytnej autodetekcji |bootstrapping#Tryb deweloperski kontra produkcyjny]: jeśli uruchomisz witrynę na localhoście, działa ona w trybie deweloperskim. Nie musisz niczego konfigurować, a aplikacja jest od razu gotowa zarówno do tworzenia, jak i do wdrożenia na żywo. Te kroki wykonywane są i szczegółowo opisane w rozdziale o [klasie Bootstrap|bootstrapping]. -Trzecim punktem (tak, drugi pominęliśmy, ale wrócimy do niego) jest uruchomienie aplikacji. Obsługą żądań HTTP w Nette zajmuje się klasa `Nette\Application\Application` (dalej `Application`), więc gdy mówimy uruchomić aplikację, mamy na myśli konkretnie wywołanie metody o wymownej nazwie `run()` na obiekcie tej klasy. +Punkt trzeci (tak, drugi pominęliśmy, ale do niego wrócimy) to uruchomienie aplikacji. Za obsługę żądań HTTP w Nette odpowiada klasa `Nette\Application\Application` (dalej `Application`). Gdy więc mówimy "uruchom aplikację", mamy konkretnie na myśli wywołanie na obiekcie tej klasy trafnie nazwanej metody `run()`. -Nette jest mentorem, który prowadzi Cię do pisania czystych aplikacji według sprawdzonych metodyk. A jedna z tych absolutnie najsprawdzonych nazywa się **dependency injection**, w skrócie DI. W tej chwili nie chcemy Cię obciążać wyjaśnianiem DI, od tego jest [osobny rozdział|dependency-injection:introduction], istotny jest skutek, że kluczowe obiekty będzie nam zazwyczaj tworzyć fabryka obiektów, której mówi się **kontener DI** (w skrócie DIC). Tak, to ta fabryka, o której była przed chwilą mowa. I wyprodukuje nam również obiekt `Application`, dlatego potrzebujemy najpierw kontenera. Uzyskamy go za pomocą klasy `Configurator` i pozwolimy mu wyprodukować obiekt `Application`, wywołamy na nim metodę `run()` i tym samym uruchomi się aplikacja Nette. Dokładnie to dzieje się w pliku [index.php |bootstrapping#index.php]. +Nette jest mentorem, który prowadzi Cię do pisania czystych aplikacji według sprawdzonych metodologii. Jedną z najbardziej ugruntowanych jest **wstrzykiwanie zależności**, w skrócie DI. Nie chcemy Cię teraz obciążać wyjaśnianiem DI, jest od tego [osobny rozdział|dependency-injection:introduction]. Istotną konsekwencją jest to, że kluczowe obiekty tworzy zwykle fabryka obiektów zwana **kontenerem DI** (albo DIC). Tak, to właśnie ta fabryka, o której była mowa wcześniej. Produkuje ona również obiekt `Application`, dlatego najpierw potrzebujemy kontenera. Pozyskujemy go za pomocą klasy `Configurator`, każemy mu utworzyć obiekt `Application`, wywołujemy na nim metodę `run()` i tym samym aplikacja Nette rusza. Dokładnie to dzieje się w pliku [index.php |bootstrapping#index.php]. Nette Application @@ -89,15 +89,15 @@ Nette Application Klasa `Application` ma jedno zadanie: odpowiedzieć na żądanie HTTP. -Aplikacje pisane w Nette dzielą się na mnóstwo tzw. presenterów (w innych frameworkach można spotkać się z terminem controller, chodzi o to samo), które są klasami, z których każda reprezentuje jakąś konkretną stronę internetową: np. stronę główną; produkt w e-sklepie; formularz logowania; kanał sitemap itp. Aplikacja może mieć od jednego do tysięcy presenterów. +Aplikacje pisane w Nette dzielą się na wiele tak zwanych presenterów (w innych frameworkach możesz spotkać się z terminem "kontroler", który w istocie oznacza to samo). To klasy, z których każda reprezentuje konkretną stronę witryny: np. stronę główną, produkt w sklepie internetowym, formularz logowania, feed z mapą strony itd. Aplikacja może mieć od jednego do tysięcy presenterów. -`Application` zaczyna od tego, że prosi tzw. router, aby zdecydował, któremu z presenterów przekazać aktualne żądanie do obsłużenia. Router decyduje, czyja to odpowiedzialność. Spogląda na wejściowy URL `https://example.com/product/123` i na podstawie tego, jak jest ustawiony, decyduje, że to praca np. dla **presentera** `Product`, od którego będzie chciał jako **akcję** wyświetlenie (`show`) produktu o `id: 123`. Parę presenter + akcja jest dobrym zwyczajem zapisywać oddzieloną dwukropkiem jako `Product:show`. +`Application` zaczyna od zapytania tak zwanego routera, który presenter ma obsłużyć bieżące żądanie. Router rozstrzyga o odpowiedzialności. Analizuje wejściowy URL `https://example.com/product/123` i na podstawie swojej konfiguracji decyduje, że to zadanie należy na przykład do **presentera** `Product`, który ma wykonać **akcję** `show` dla produktu o `id: 123`. Dobrą praktyką jest zapisywanie pary presenter + akcja oddzielonych dwukropkiem, czyli `Product:show`. -Zatem router przekształcił URL na parę `Presenter:action` + parametry, w naszym przypadku `Product:show` + `id: 123`. Jak taki router wygląda, można zobaczyć w pliku `app/Core/RouterFactory.php` i szczegółowo opisujemy go w rozdziale [Routingu |Routing]. +Router zamienił więc URL na parę `Presenter:akcja` + parametry, w naszym przypadku `Product:show` + `id: 123`. Jak taki router wygląda, zobaczysz w pliku `app/Core/RouterFactory.php`, a szczegółowo opisujemy go w rozdziale [Routing |Routing]. -Idźmy dalej. `Application` już zna nazwę presentera i może kontynuować. Tworząc obiekt klasy `ProductPresenter`, który jest kodem presentera `Product`. Dokładniej mówiąc, prosi kontener DI, aby wyprodukował presenter, ponieważ od produkowania jest on. +Idźmy dalej. `Application` zna już nazwę presentera i może działać dalej. Robi to, tworząc instancję klasy `ProductPresenter`, która zawiera kod presentera `Product`. Ściślej mówiąc, prosi o utworzenie presentera kontener DI, bo tworzenie obiektów to jego odpowiedzialność. -Presenter może wyglądać na przykład tak: +Presenter może wyglądać tak: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -115,86 +115,86 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Obsługę żądania przejmuje presenter. A zadanie brzmi jasno: wykonaj akcję `show` z `id: 123`. Co w języku presenterów oznacza, że wywołana zostanie metoda `renderShow()` i w parametrze `$id` otrzyma `123`. +Presenter przejmuje obsługę żądania. Zadanie jest jasne: wykonać akcję `show` z `id: 123`. W terminologii presenterów oznacza to, że wywoływana jest metoda `renderShow()`, otrzymująca `123` w parametrze `$id`. -Presenter może obsługiwać więcej akcji, czyli mieć więcej metod `render()`. Ale zalecamy projektowanie presenterów z jedną lub jak najmniejszą liczbą akcji. +Presenter może obsługiwać wiele akcji, czyli mieć wiele metod `render()`. Zalecamy jednak projektować presentery z jedną albo możliwie niewieloma akcjami. -Zatem, wywołano metodę `renderShow(123)`, której kod jest wprawdzie wymyślonym przykładem, ale można na nim zobaczyć, jak przekazuje się dane do szablonu, czyli zapisem do `$this->template`. +Wywołana została więc metoda `renderShow(123)`. Jej kod to fikcyjny przykład, ale pokazuje, jak dane przekazywane są do szablonu, konkretnie przez zapis do `$this->template`. -Następnie presenter zwraca odpowiedź. Może to być strona HTML, obrazek, dokument XML, wysłanie pliku z dysku, JSON lub na przykład przekierowanie na inną stronę. Ważne jest, że jeśli jawnie nie powiemy, jak ma odpowiedzieć (co jest przypadkiem `ProductPresenter`), odpowiedzią będzie wyrenderowanie szablonu ze stroną HTML. Dlaczego? Ponieważ w 99% przypadków chcemy wyrenderować szablon, dlatego presenter to zachowanie traktuje jako domyślne i chce nam ułatwić pracę. To jest sens Nette. +Następnie presenter zwraca odpowiedź. Może nią być strona HTML, obraz, dokument XML, wysłanie pliku z dysku, JSON albo choćby przekierowanie na inną stronę. Co ważne, jeśli nie wskażemy jawnie, jak odpowiedzieć (a tak jest w przypadku `ProductPresenter`), odpowiedzią będzie wyrenderowanie szablonu do strony HTML. Dlaczego? Bo w 99% przypadków chcemy wyrenderować szablon. Presenter przyjmuje więc to zachowanie jako domyślne, aby ułatwić nam pracę. Na tym polega istota Nette. -Nie musimy nawet podawać, jaki szablon wyrenderować, ścieżkę do niego wywnioskuje sam. W przypadku akcji `show` po prostu spróbuje załadować szablon `show.latte` w katalogu z klasą `ProductPresenter`. Podobnie spróbuje odnaleźć layout w pliku `@layout.latte` (więcej o [wyszukiwaniu szablonów |templates#Wyszukiwanie szablonów]). +Nie musimy nawet wskazywać, który szablon wyrenderować; framework sam wywnioskuje ścieżkę. W przypadku akcji `show` po prostu próbuje wczytać szablon `show.latte` leżący w tym samym katalogu co klasa `ProductPresenter`. Próbuje też odnaleźć layout w pliku `@layout.latte` (więcej szczegółów w [wyszukiwaniu szablonów |templates#Wyszukiwanie szablonów]). -A następnie szablony wyrenderuje. Tym samym zadanie presentera i całej aplikacji jest zakończone, a dzieło jest ukończone. Jeśli szablon by nie istniał, zwrócona zostanie strona z błędem 404. Więcej o presenterach przeczytasz na stronie [Presentery |presenters]. +Następnie szablony są renderowane. To kończy zadanie presentera i całej aplikacji. Jeśli szablon nie istnieje, zwracana jest strona błędu 404. Więcej o presenterach dowiesz się na stronie [Presentery|presenters]. [* request-flow.svg *] -Dla pewności, spróbujmy podsumować cały proces z nieco innym URL: +Dla pewności podsumujmy cały proces na nieco innym URL: -1) URL będzie `https://example.com` -2) uruchamiamy aplikację, tworzy się kontener i uruchamia `Application::run()` -3) router dekoduje URL jako parę `Home:default` -4) tworzy się obiekt klasy `HomePresenter` +1) URL to `https://example.com` +2) aplikacja startuje, tworzony jest kontener DI i wykonywane `Application::run()` +3) router dekoduje URL na parę `Home:default` +4) tworzona jest instancja klasy `HomePresenter` 5) wywoływana jest metoda `renderDefault()` (jeśli istnieje) -6) renderowany jest szablon np. `default.latte` z layoutem np. `@layout.latte` +6) renderowany jest szablon, np. `default.latte`, wraz z layoutem, np. `@layout.latte` -Być może spotkałeś się teraz z dużą ilością nowych pojęć, ale wierzymy, że mają sens. Tworzenie aplikacji w Nette to ogromna przyjemność. +Mogłeś właśnie spotkać się z wieloma nowymi pojęciami, ale wierzymy, że mają sens. Tworzenie aplikacji w Nette jest zdumiewająco proste. Szablony ======== -Skoro już mowa o szablonach, w Nette używa się systemu szablonów [Latte |latte:]. Dlatego też te końcówki `.latte` przy szablonach. Latte używa się po pierwsze dlatego, że jest to najlepiej zabezpieczony system szablonów dla PHP, a jednocześnie system najbardziej intuicyjny. Nie musisz uczyć się wielu nowych rzeczy, wystarczy znajomość PHP i kilku znaczników. Wszystko dowiesz się [w dokumentacji |templates]. +Skoro mowa o szablonach, Nette używa systemu szablonów [Latte |latte:]. Dlatego pliki szablonów mają rozszerzenie `.latte`. Latte używane jest przede wszystkim dlatego, że to najbezpieczniejszy system szablonów dla PHP, a zarazem najbardziej intuicyjny. Nie musisz uczyć się wiele nowego; wystarczy znajomość PHP i kilku tagów. Wszystko, czego potrzebujesz, znajdziesz [w dokumentacji |templates]. -W szablonie [tworzy się linki |creating-links] do innych presenterów i akcji w ten sposób: +W szablonie [tworzysz odnośniki |creating-links] do innych presenterów i akcji tak: ```latte szczegóły produktu ``` -Po prostu zamiast rzeczywistego URL wpisujesz znaną parę `Presenter:action` i podajesz ewentualne parametry. Sztuczka tkwi w `n:href`, które mówi, że ten atrybut przetworzy Nette. I wygeneruje: +Zamiast prawdziwego URL po prostu zapisujesz znajomą parę `Presenter:akcja` i dodajesz ewentualne parametry. Sztuczka tkwi w `n:href`, które mówi Nette, aby ten atrybut przetworzyło. Wygeneruje wtedy: ```latte szczegóły produktu ``` -Generowaniem URL zajmuje się już wcześniej wspomniany router. Otóż routery w Nette są wyjątkowe tym, że potrafią wykonywać nie tylko transformacje z URL na parę presenter:action, ale także odwrotnie, czyli z nazwy presentera + akcji + parametrów wygenerować URL. Dzięki temu w Nette możesz całkowicie zmienić kształty URL w całej gotowej aplikacji, nie zmieniając ani jednego znaku w szablonie czy presenterze. Tylko przez modyfikację routera. Również dzięki temu działa tzw. kanonizacja, co jest kolejną unikalną cechą Nette, która przyczynia się do lepszego SEO (optymalizacji dla wyszukiwarek internetowych) przez automatyczne zapobieganie istnieniu duplikatów treści pod różnymi URL. Wielu programistów uważa to za niesamowite. +Generowaniem URL zajmuje się wspomniany wcześniej router. Routery w Nette są wyjątkowe, bo potrafią wykonać nie tylko przekształcenie z URL na parę `Presenter:akcja`, ale też odwrotne: wygenerować URL z nazwy presentera, akcji i parametrów. Dzięki temu w Nette możesz całkowicie zmienić postać URL w całej gotowej aplikacji bez zmieniania jednego znaku w szablonach czy presenterach, po prostu modyfikując router. Umożliwia to również tak zwaną kanonizację, kolejną unikalną cechę Nette, która poprawia SEO (Search Engine Optimization) przez automatyczne zapobieganie istnieniu zduplikowanej treści pod różnymi URL. Wielu programistów uznaje tę możliwość za zdumiewającą. Komponenty interaktywne ======================= -O presenterach musimy Ci jeszcze zdradzić jedną rzecz: mają w sobie wbudowany system komponentów. Coś podobnego mogą pamiętać weterani z Delphi lub ASP.NET Web Forms, na czymś zdalnie podobnym opiera się React czy Vue.js. W świecie frameworków PHP jest to absolutnie unikalna sprawa. +Musimy powiedzieć Ci o presenterach jeszcze jedno: mają wbudowany system komponentów. Osoby z większym doświadczeniem mogą przypomnieć sobie coś podobnego z Delphi albo ASP.NET Web Forms; React czy Vue.js zbudowane są na nieco pokrewnych koncepcjach. W świecie frameworków PHP jest to funkcja całkowicie unikalna. -Komponenty to samodzielne, wielokrotnego użytku całości, które wstawiamy do stron (czyli presenterów). Mogą to być [formularze |forms:in-presenter], [siatki danych |https://componette.org/contributte/datagrid/], menu, ankiety, właściwie cokolwiek, co ma sens używać wielokrotnie. Możemy tworzyć własne komponenty lub używać niektórych z [ogromnej oferty |https://componette.org] komponentów open source. +Komponenty to niezależne jednostki wielokrotnego użytku, które osadzamy w stronach (czyli w presenterach). Mogą to być [formularze |forms:in-presenter], [datagridy |https://componette.org/contributte/datagrid/], menu, ankiety, w zasadzie wszystko, co warto wykorzystywać wielokrotnie. Możemy tworzyć własne komponenty albo skorzystać z [ogromnego wyboru |https://componette.org] komponentów open source. -Komponenty zasadniczo wpływają na podejście do tworzenia aplikacji. Otworzą Ci nowe możliwości składania stron z gotowych jednostek. A ponadto mają coś wspólnego z [Hollywoodem |components#Styl Hollywood]. +Komponenty zasadniczo wpływają na podejście do tworzenia aplikacji. Otwierają nowe możliwości składania stron z wcześniej przygotowanych jednostek. I mają też coś wspólnego z [Hollywood |components#Styl hollywoodzki]. Kontener DI i konfiguracja ========================== -Kontener DI czyli fabryka obiektów jest sercem całej aplikacji. +Kontener DI, czyli fabryka obiektów, jest sercem całej aplikacji. -Nie obawiaj się, nie jest to żaden magiczny black box, jak mogłoby się wydawać z poprzednich linijek. Właściwie jest to jedna dość nudna klasa PHP, którą generuje Nette i zapisuje w katalogu z cache. Ma mnóstwo metod nazwanych jak `createServiceAbcd()` i każda z nich potrafi wyprodukować i zwrócić jakiś obiekt. Tak, jest tam również metoda `createServiceApplication()`, która wyprodukuje `Nette\Application\Application`, którego potrzebowaliśmy w pliku `index.php` do uruchomienia aplikacji. I są tam metody produkujące poszczególne presentery. I tak dalej. +Bez obaw, nie jest to żadna magiczna czarna skrzynka, choć poprzednie wiersze mogłyby tak sugerować. W rzeczywistości to całkiem przyziemna klasa PHP, którą generuje Nette i zapisuje w katalogu cache. Zawiera wiele metod nazwanych w rodzaju `createServiceAbcd()`, z których każda potrafi utworzyć i zwrócić konkretny obiekt. Tak, jest tam również metoda `createServiceApplication__application()`, produkująca instancję `Nette\Application\Application`, której potrzebowaliśmy w `index.php` do uruchomienia aplikacji. Są też metody tworzące poszczególne presentery itd. -Obiektom, które tworzy kontener DI, z jakiegoś powodu mówi się usługi. +Obiekty tworzone przez kontener DI z jakiegoś powodu nazywane są usługami. -Co jest w tej klasie naprawdę specjalnego, to że nie programujesz jej Ty, ale framework. On rzeczywiście generuje kod PHP i zapisuje go na dysku. Ty tylko dajesz instrukcje, jakie obiekty ma umieć kontener produkować i jak dokładnie. A te instrukcje są zapisane w [plikach konfiguracyjnych |bootstrapping#Konfiguracja kontenera DI], dla których używa się formatu [NEON|neon:format] i dlatego mają też rozszerzenie `.neon`. +Prawdziwie szczególne w tej klasie jest to, że jej nie programujesz - robi to framework. Faktycznie generuje kod PHP i zapisuje go na dysk. Ty jedynie podajesz instrukcje, jakie obiekty kontener ma umieć tworzyć i dokładnie jak. Instrukcje te zapisywane są w [plikach konfiguracyjnych |bootstrapping#Konfiguracja kontenera DI], które używają formatu [NEON|neon:format] i dlatego mają rozszerzenie `.neon`. -Pliki konfiguracyjne służą czysto do instruowania kontenera DI. Więc gdy na przykład podam w sekcji [sesji |http:configuration#Sesja] opcję `expiration: 14 days`, to kontener DI przy tworzeniu obiektu `Nette\Http\Session` reprezentującego sesję wywoła jego metodę `setExpiration('14 days')` i tym samym konfiguracja stanie się rzeczywistością. +Pliki konfiguracyjne służą wyłącznie do instruowania kontenera DI. Jeśli więc na przykład podasz w sekcji [session |http:configuration#Sesja] opcję `expiration: 14 days`, kontener DI przy tworzeniu obiektu `Nette\Http\Session` reprezentującego sesję wywoła jego metodę `setExpiration('14 days')`, urzeczywistniając tym samym konfigurację. -Jest tu dla Ciebie przygotowany cały rozdział opisujący, co wszystko można [konfigurować |nette:configuring] i jak [definiować własne usługi |dependency-injection:services]. +Czeka na Ciebie cały rozdział opisujący, co można [skonfigurować |nette:configuring] i jak [zdefiniować własne usługi |dependency-injection:services]. -Jak tylko trochę zagłębisz się w tworzenie usług, natkniesz się na słowo [autowiring |dependency-injection:autowiring]. To jest bajer, który niesamowicie uprości Ci życie. Potrafi automatycznie przekazywać obiekty tam, gdzie ich potrzebujesz (na przykład w konstruktorach Twoich klas), bez konieczności robienia czegokolwiek. Odkryjesz, że kontener DI w Nette to mały cud. +Gdy tylko zagłębisz się nieco w tworzenie usług, natkniesz się na pojęcie [autowiring |dependency-injection:autowiring]. To funkcja, która niesamowicie uprości Ci życie. Potrafi automatycznie przekazywać obiekty tam, gdzie ich potrzebujesz (na przykład do konstruktorów Twoich klas), bez konieczności robienia czegokolwiek. Odkryjesz, że kontener DI w Nette to mały cud. -Dokąd dalej? -============ +Co dalej? +========= -Przeszliśmy przez podstawowe zasady aplikacji w Nette. Na razie bardzo powierzchownie, ale wkrótce zagłębisz się w temat i z czasem stworzysz wspaniałe aplikacje internetowe. Dokąd kontynuować dalej? Czy wypróbowałeś już tutorial [Pisanie pierwszej aplikacji|quickstart:]? +Omówiliśmy podstawowe zasady działania aplikacji Nette. Jak dotąd był to przegląd powierzchowny, ale wkrótce zagłębisz się bardziej i z czasem stworzysz wspaniałe aplikacje webowe. Gdzie dalej? Wypróbowałeś już samouczek [Stwórz swoją pierwszą aplikację|quickstart:]? -Oprócz wyżej opisanego, Nette dysponuje całym arsenałem [przydatnych klas|utils:], [warstwą bazy danych|database:], itd. Spróbuj sobie po prostu przeklikać dokumentację. Lub [blog|https://blog.nette.org]. Odkryjesz mnóstwo ciekawych rzeczy. +Poza tym, co opisano powyżej, Nette oferuje cały arsenał [przydatnych klas|utils:], [warstwę bazodanową|database:] itd. Poklikaj po dokumentacji. Albo odwiedź [blog|https://blog.nette.org]. Odkryjesz wiele ciekawych rzeczy. -Niech framework przynosi Ci mnóstwo radości 💙 +Niech framework przyniesie Ci wiele radości 💙 diff --git a/application/pl/multiplier.texy b/application/pl/multiplier.texy index 68e68e1852..dcb7e97ab9 100644 --- a/application/pl/multiplier.texy +++ b/application/pl/multiplier.texy @@ -2,31 +2,31 @@ Multiplier: dynamiczne komponenty ********************************* .[perex] -Narzędzie do dynamicznego tworzenia interaktywnych komponentów +Narzędzie do dynamicznego tworzenia komponentów interaktywnych. -Wyjdźmy od typowego przykładu: mamy listę towarów w sklepie internetowym, przy czym przy każdym będziemy chcieli wyświetlić formularz do dodania towaru do koszyka. Jedną z możliwych opcji jest opakowanie całego listingu w jeden formularz. Znacznie wygodniejszy sposób oferuje nam jednak [api:Nette\Application\UI\Multiplier]. +Zacznijmy od typowego przykładu: wyobraź sobie listę produktów w sklepie internetowym, przy której chcesz wyświetlić dla każdego artykułu formularz "Do koszyka". Jedną z możliwości jest opakowanie całej listy w jeden formularz. O wiele wygodniejszy sposób oferuje jednak [api:Nette\Application\UI\Multiplier]. -Multiplier umożliwia wygodne definiowanie fabryczki dla wielu komponentów. Działa na zasadzie zagnieżdżonych komponentów - każdy komponent dziedziczący po [api:Nette\ComponentModel\Container] może zawierać kolejne komponenty. +Multiplier pozwala wygodnie zdefiniować fabrykę dla wielu komponentów. Działa na zasadzie komponentów zagnieżdżonych - każdy komponent dziedziczący po [api:Nette\ComponentModel\Container] może zawierać inne komponenty. .[tip] -Zobacz rozdział o [modelu komponentowym |components#Komponenty dogłębnie] w dokumentacji lub [wykład Honzy Tvrdíka|https://www.youtube.com/watch?v=8y3LLexWu-I]. +Zobacz w dokumentacji rozdział o [modelu komponentów |components#Komponenty w głąb]. -Istotą Multipliera jest to, że występuje w pozycji rodzica, który potrafi dynamicznie tworzyć swoje potomstwo za pomocą callbacku przekazanego w konstruktorze. Zobacz przykład: +Istota Multipliera polega na tym, że działa jak rodzic, który potrafi dynamicznie tworzyć swoich potomków za pomocą callbacku przekazanego w konstruktorze. Zobacz przykład: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Ilość towaru:') + $form->addInteger('amount', 'Ilość:') ->setRequired(); - $form->addSubmit('send', 'Dodaj do koszyka'); + $form->addSubmit('send', 'Do koszyka'); return $form; }); } ``` -Teraz możemy w szablonie po prostu przy każdym towarze wyświetlić formularz - i każdy będzie rzeczywiście unikalnym komponentem. +Teraz w szablonie możemy po prostu wyrenderować formularz dla każdego produktu - i każdy naprawdę będzie osobnym komponentem. ```latte {foreach $items as $item} @@ -37,26 +37,26 @@ Teraz możemy w szablonie po prostu przy każdym towarze wyświetlić formularz {/foreach} ``` -Argument przekazany w znaczniku `{control}` jest w formacie, który mówi: +Argument przekazany w tagu `{control}` ma format, który oznacza: -1. pobierz komponent `shopForm` -2. a z niego pobierz potomka `$item->id` +1. Pobierz komponent `shopForm`. +2. Z niego pobierz potomka o nazwie `$item->id`. -Przy pierwszym wywołaniu punktu **1.** `shopForm` jeszcze nie istnieje, więc wywołana zostanie jego fabryka `createComponentShopForm`. Na uzyskanym komponencie (instancji Multipliera) jest następnie wywoływana fabryka konkretnego formularza - co jest anonimową funkcją, którą przekazaliśmy Multiplierowi w konstruktorze. +Przy pierwszym wywołaniu punktu **1** komponent `shopForm` jeszcze nie istnieje, więc wywoływana jest jego fabryka `createComponentShopForm`. Następnie na uzyskanym komponencie (instancji Multipliera) wywoływana jest fabryka konkretnego formularza, czyli anonimowa funkcja, którą przekazaliśmy do konstruktora Multipliera. -W kolejnej iteracji foreache już metoda `createComponentShopForm` nie będzie wywoływana (komponent istnieje), ale ponieważ szukamy jego innego potomka (`$item->id` będzie w każdej iteracji inne), ponownie zostanie wywołana anonimowa funkcja i zwróci nam nowy formularz. +W kolejnej iteracji pętli foreach metoda `createComponentShopForm` nie zostanie wywołana ponownie (bo komponent już istnieje). Ponieważ jednak szukamy innego potomka (`$item->id` będzie w każdej iteracji inne), anonimowa funkcja zostanie wywołana ponownie i zwróci nowy formularz. -Jedyne, co pozostaje, to zapewnić, aby formularz dodał do koszyka rzeczywiście ten towar, który ma - obecnie formularz przy każdym towarze jest całkowicie identyczny. Pomoże nam właściwość Multipliera (i ogólnie każdej fabryki komponentu w Nette Framework), a mianowicie ta, że każda fabryka jako swój pierwszy argument otrzymuje nazwę tworzonego komponentu. W naszym przypadku będzie to `$item->id`, co jest dokładnie tą informacją, której potrzebujemy. Wystarczy więc lekko zmodyfikować tworzenie formularza: +Pozostaje już tylko zadbać o to, aby formularz dodawał do koszyka właściwy produkt - obecnie formularz jest identyczny dla każdego produktu. Pomaga nam w tym cecha Multipliera (i ogólnie każdej fabryki komponentów w Nette Framework): każda fabryka otrzymuje jako pierwszy argument nazwę tworzonego komponentu. Ponadto fabryka Multipliera otrzymuje jako drugi argument samą instancję Multipliera. W naszym przypadku pierwszym argumentem będzie `$item->id`, czyli dokładnie ta informacja, której potrzebujemy. Wystarczy więc nieznacznie zmodyfikować tworzenie formularza: ```php protected function createComponentShopForm(): Multiplier { - return new Multiplier(function ($itemId) { + return new Multiplier(function (string $itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Ilość towaru:') + $form->addInteger('amount', 'Ilość:') ->setRequired(); $form->addHidden('itemId', $itemId); - $form->addSubmit('send', 'Dodaj do koszyka'); + $form->addSubmit('send', 'Do koszyka'); return $form; }); } diff --git a/application/pl/presenters.texy b/application/pl/presenters.texy index c450af53eb..ac3ce3dd2d 100644 --- a/application/pl/presenters.texy +++ b/application/pl/presenters.texy @@ -3,37 +3,37 @@ Presentery
    -Zapoznamy się z tym, jak w Nette pisze się presentery i szablony. Po przeczytaniu będziesz wiedzieć: +Przyjrzymy się temu, jak w Nette pisze się presentery i szablony. Po lekturze będziesz rozumieć: -- jak działa presenter -- co to są parametry trwałe -- jak rysuje się szablony +- jak działają presentery +- czym są parametry trwałe +- jak renderowane są szablony
    -[Już wiemy |how-it-works#Nette Application], że presenter to klasa, która reprezentuje jakąś konkretną stronę aplikacji internetowej, np. stronę główną; produkt w e-sklepie; formularz logowania; kanał sitemap itp. Aplikacja może mieć od jednego do tysięcy presenterów. W innych frameworkach nazywa się je również kontrolerami. +[Wiemy już |how-it-works#Nette Application], że presenter to klasa reprezentująca konkretną stronę aplikacji webowej, na przykład stronę główną, produkt w sklepie internetowym, formularz logowania, feed z mapą strony itd. Aplikacja może mieć od jednego do tysięcy presenterów. W innych frameworkach znane są też jako kontrolery. -Zazwyczaj pod pojęciem presenter rozumie się potomka klasy [api:Nette\Application\UI\Presenter], który jest odpowiedni do generowania interfejsów internetowych i któremu poświęcimy resztę tego rozdziału. W ogólnym sensie presenter to dowolny obiekt implementujący interfejs [api:Nette\Application\IPresenter]. +Zwykle terminem presenter określamy potomka klasy [api:Nette\Application\UI\Presenter], która nadaje się do generowania interfejsów webowych i której poświęcona będzie reszta tego rozdziału. W ogólnym sensie presenterem jest dowolny obiekt implementujący interfejs [api:Nette\Application\IPresenter]. Cykl życia presentera ===================== -Zadaniem presentera jest obsłużenie żądania i zwrócenie odpowiedzi (co może być stroną HTML, obrazkiem, przekierowaniem itp.). +Zadaniem presentera jest obsłużenie żądania i zwrócenie odpowiedzi (którą może być strona HTML, obraz, przekierowanie itd.). -Zatem na początku przekazywane jest mu żądanie. Nie jest to bezpośrednio żądanie HTTP, ale obiekt [api:Nette\Application\Request], na który zostało przekształcone żądanie HTTP za pomocą routera. Z tym obiektem zazwyczaj nie mamy do czynienia, ponieważ presenter inteligentnie deleguje przetwarzanie żądania do innych metod, które teraz pokażemy. +Na początku przekazywane jest mu więc żądanie. Nie jest to bezpośrednio żądanie HTTP, lecz obiekt [api:Nette\Application\Request], w który żądanie HTTP zostało przekształcone z pomocą routera. Zwykle nie pracujemy z tym obiektem bezpośrednio, bo presenter sprytnie deleguje obsługę żądania do innych metod, którym teraz się przyjrzymy. -[* lifecycle.svg *] *** *Cykl życia presentera* .<> +[* lifecycle.svg *] *** Cykl życia presentera .<> -Obrazek przedstawia listę metod, które są kolejno wywoływane od góry do dołu, jeśli istnieją. Żadna z nich nie musi istnieć, możemy mieć całkowicie pusty presenter bez ani jednej metody i zbudować na nim prostą statyczną stronę internetową. +Diagram pokazuje listę metod wywoływanych kolejno od góry do dołu, jeśli istnieją. Żadna z nich nie jest obowiązkowa; możesz mieć całkowicie pusty presenter bez jednej metody i zbudować na nim prostą statyczną witrynę. `__construct()` --------------- -Konstruktor nie należy tak do końca do cyklu życia presentera, ponieważ jest wywoływany w momencie tworzenia obiektu. Ale podajemy go ze względu na ważność. Konstruktor (wraz z [metodą inject|best-practices:inject-method-attribute]) służy do przekazywania zależności. +Konstruktor nie należy ściśle do cyklu życia presentera, bo wywoływany jest w chwili tworzenia obiektu. Wspominamy o nim jednak ze względu na jego znaczenie. Konstruktor (wraz z [metodą inject|best-practices:inject-method-attribute]) służy do przekazywania zależności. -Presenter nie powinien zajmować się logiką biznesową aplikacji, zapisywać i czytać z bazy danych, wykonywać obliczeń itp. Od tego są klasy z warstwy, którą określamy jako model. Na przykład klasa `ArticleRepository` może odpowiadać za ładowanie i zapisywanie artykułów. Aby presenter mógł z nią pracować, pozwoli sobie ją [przekazać za pomocą dependency injection |dependency-injection:passing-dependencies]: +Presenter nie powinien zajmować się logiką biznesową aplikacji, zapisywać do bazy danych ani z niej czytać, wykonywać obliczeń itd. To odpowiedzialność klas w warstwie, którą nazywamy modelem. Na przykład klasa `ArticleRepository` może odpowiadać za wczytywanie i zapisywanie artykułów. Aby presenter mógł z niej korzystać, musi mieć ją [przekazaną przez wstrzykiwanie zależności |dependency-injection:passing-dependencies]: ```php @@ -50,39 +50,42 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Natychmiast po otrzymaniu żądania wywoływana jest metoda `startup()`. Można jej użyć do inicjalizacji właściwości, weryfikacji uprawnień użytkownika itp. Wymagane jest, aby metoda zawsze wywoływała przodka `parent::startup()`. +Natychmiast po otrzymaniu żądania wywoływana jest metoda `startup()`. Możesz jej użyć do zainicjowania właściwości, sprawdzenia uprawnień użytkownika itd. Wymagane jest, aby metoda ta zawsze wywoływała swojego rodzica: `parent::startup()`. -`action(args...)` .{toc: action()} --------------------------------------------------- +`action(args...)` .{toc: action()} +------------------------------------------------- + +Podobna do metody `render()`. Podczas gdy `render()` ma przygotować dane dla konkretnego szablonu, który następnie zostanie wyrenderowany, `action()` przetwarza żądanie bez konieczności renderowania potem szablonu. Może na przykład przetworzyć dane, zalogować albo wylogować użytkownika itd., a następnie [przekierować gdzie indziej |#Przekierowanie]. -Odpowiednik metody `render()`. Podczas gdy `render()` jest przeznaczona do przygotowania danych dla konkretnego szablonu, który następnie zostanie wyrenderowany, to w `action()` przetwarza się żądanie bez związku z renderowaniem szablonu. Na przykład przetwarza się dane, loguje lub wylogowuje użytkownika i tak dalej, a następnie [przekierowuje gdzie indziej |#Przekierowanie]. +Ważne jest, że `action()` wywoływana jest *przed* `render()`. Pozwala nam to ewentualnie zmienić przebieg żądania w metodzie akcji, na przykład zmieniając szablon, który zostanie wyrenderowany, albo nawet metodę `render()`, która zostanie wywołana, za pomocą `setView('otherView')`. -Ważne jest, że `action()` jest wywoływana wcześniej niż `render()`, więc możemy w niej ewentualnie zmienić dalszy bieg wydarzeń, tj. zmienić szablon, który będzie rysowany, a także metodę `render()`, która będzie wywoływana. A to za pomocą `setView('innyView')`. +.{data-version:3.2.3} +Możesz nawet przełączyć się na zupełnie inną akcję metodą `switch('otherAction')`. Przerywa ona bieżącą metodę i zamiast niej uruchamia metody `action()` i `render()` nowej akcji (oraz wyłącza automatyczną [kanonizację|#Kanonizacja]). Samo żądanie trwa dalej; przerywana jest tylko aktualnie działająca metoda. -Metodzie przekazywane są parametry z żądania. Możliwe i zalecane jest podanie typów parametrów, np. `actionShow(int $id, ?string $slug = null)` - jeśli parametr `id` będzie brakował lub jeśli nie będzie liczbą całkowitą, presenter zwróci [błąd 404 |#Błąd 404 i spółka] i zakończy działanie. +Do metody przekazywane są parametry z żądania. Można i zalecamy podać dla nich typy, np. `actionShow(int $id, ?string $slug = null)`. Jeśli parametru `id` brakuje albo nie jest liczbą całkowitą, presenter zwraca [błąd 404 |#Błąd 404 itd.] i kończy działanie. -`handle(args...)` .{toc: handle()} +`handle(args...)` .{toc: handle()} -------------------------------------------------- -Metoda przetwarza tzw. sygnały, z którymi zapoznamy się w rozdziale poświęconym [komponentom |components#Sygnał]. Jest bowiem przeznaczona głównie dla komponentów i przetwarzania żądań AJAX. +Ta metoda przetwarza tak zwane sygnały, o których dowiemy się w rozdziale poświęconym [komponentom |components#Sygnał]. Przeznaczona jest przede wszystkim dla komponentów i obsługi żądań AJAX. -Metodzie przekazywane są parametry z żądania, jak w przypadku `action()`, w tym kontrola typów. +Do metody przekazywane są parametry z żądania, tak jak przy `action()`, wraz z kontrolą typów. `beforeRender()` ---------------- -Metoda `beforeRender`, jak sama nazwa wskazuje, jest wywoływana przed każdą metodą `render()`. Używa się jej do wspólnej konfiguracji szablonu, przekazania zmiennych dla layoutu i podobnych. +Metoda `beforeRender`, jak sama nazwa wskazuje, wywoływana jest przed każdą metodą `render()`. Służy do wspólnej konfiguracji szablonu, przekazywania zmiennych do layoutu i podobnych zadań. -`render(args...)` .{toc: render()} ----------------------------------------------- +`render(args...)` .{toc: render()} +----------------------------------------------- -Miejsce, gdzie przygotowujemy szablon do późniejszego wyrenderowania, przekazujemy mu dane itp. +Tutaj przygotowujemy szablon do późniejszego renderowania, przekazujemy mu dane itd. -Metodzie przekazywane są parametry z żądania, jak w przypadku `action()`, w tym kontrola typów. +Do metody przekazywane są parametry z żądania, tak jak przy `action()`, wraz z kontrolą typów. ```php public function renderShow(int $id): void @@ -96,7 +99,7 @@ public function renderShow(int $id): void `afterRender()` --------------- -Metoda `afterRender`, jak nazwa ponownie wskazuje, jest wywoływana po każdej metodzie `render()`. Używa się jej raczej wyjątkowo. +Metoda `afterRender`, jak znów wskazuje nazwa, wywoływana jest po każdej metodzie `render()`. Używana jest raczej rzadko. `shutdown()` @@ -105,95 +108,117 @@ Metoda `afterRender`, jak nazwa ponownie wskazuje, jest wywoływana po każdej m Wywoływana na końcu cyklu życia presentera. -**Dobra rada, zanim pójdziemy dalej**. Presenter, jak widać, może obsługiwać więcej akcji/view, czyli mieć więcej metod `render()`. Ale zalecamy projektowanie presenterów z jedną lub jak najmniejszą liczbą akcji. +Zdarzenia +--------- + +Poza metodami `startup()`, `beforeRender()` i `shutdown()`, wywoływanymi w ramach cyklu życia presentera, można zdefiniować inne funkcje, które będą wywoływane automatycznie. Presenter definiuje tak zwane [zdarzenia |nette:glossary#Zdarzenia], a ich handlery dodajesz do tablic `$onStartup`, `$onRender` i `$onShutdown`. + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` + +Handlery z tablicy `$onStartup` wywoływane są tuż przed metodą `startup()`, handlery `$onRender` między `beforeRender()` a `render()`, a wreszcie handlery `$onShutdown` tuż przed `shutdown()`. + + +**Rada, zanim ruszymy dalej:** Jak widzisz, presenter może obsługiwać wiele akcji/widoków, czyli mieć wiele metod `render()`. Zalecamy jednak projektować presentery z jedną albo możliwie niewieloma akcjami. -Wysłanie odpowiedzi -=================== +Wysyłanie odpowiedzi +==================== -Odpowiedzią presentera jest zazwyczaj [wyrenderowanie szablonu ze stroną HTML|templates], ale może nią być również wysłanie pliku, JSON lub na przykład przekierowanie na inną stronę. +Odpowiedzią presentera jest zwykle [wyrenderowanie szablonu do strony HTML|templates], ale może to być również wysłanie pliku, JSON-a albo choćby przekierowanie na inną stronę. -W dowolnym momencie cyklu życia możemy za pomocą jednej z poniższych metod wysłać odpowiedź i jednocześnie zakończyć działanie presentera: +W dowolnym momencie cyklu życia możemy użyć jednej z poniższych metod, aby wysłać odpowiedź i jednocześnie zakończyć działanie presentera: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` i `forward()` [przekierowuje |#Przekierowanie] -- `error()` kończy presenter [z powodu błędu |#Błąd 404 i spółka] -- `sendJson($data)` kończy presenter i [wysyła dane |#Wysłanie JSON] w formacie JSON +- `redirect()`, `redirectPermanent()`, `redirectUrl()` i `forward()` wykonują [przekierowanie |#Przekierowanie] +- `error()` kończy presenter [z powodu błędu |#Błąd 404 itd.] +- `sendJson($data)` kończy presenter i [wysyła dane |#Wysyłanie JSON] w formacie JSON - `sendTemplate()` kończy presenter i natychmiast [renderuje szablon |templates] - `sendResponse($response)` kończy presenter i wysyła [własną odpowiedź |#Odpowiedzi] - `terminate()` kończy presenter bez odpowiedzi -Jeśli nie wywołasz żadnej z tych metod, presenter automatycznie przystąpi do renderowania szablonu. Dlaczego? Ponieważ w 99% przypadków chcemy wyrenderować szablon, dlatego presenter to zachowanie traktuje jako domyślne i chce nam ułatwić pracę. +Każda z tych metod natychmiast kończy presenter, zgłaszając cichy wyjątek zakończenia `Nette\Application\AbortException`. +Jeśli nie wywołasz żadnej z tych metod, presenter automatycznie przechodzi do wyrenderowania szablonu. Dlaczego? Bo w 99% przypadków chcemy wyrenderować szablon, więc presenter przyjmuje to zachowanie jako domyślne, aby ułatwić nam pracę. -Tworzenie linków -================ -Presenter dysponuje metodą `link()`, za pomocą której można tworzyć linki URL do innych presenterów. Pierwszym parametrem jest docelowy presenter & akcja, następnie przekazywane argumenty, które mogą być podane jako tablica: +Tworzenie odnośników +==================== + +Presenter ma metodę `link()`, która służy do tworzenia odnośników URL do innych presenterów. Pierwszym parametrem jest docelowy presenter i akcja, po nim argumenty, które można przekazać jako tablicę: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$id, 'lang' => 'en']); ``` -W szablonie tworzy się linki do innych presenterów & akcji w ten sposób: +W szablonie odnośniki do innych presenterów i akcji tworzy się tak: ```latte szczegóły produktu ``` -Po prostu zamiast rzeczywistego URL wpisujesz znaną parę `Presenter:action` i podajesz ewentualne parametry. Sztuczka tkwi w `n:href`, które mówi, że ten atrybut przetworzy Latte i wygeneruje rzeczywisty URL. W Nette więc w ogóle nie musisz zastanawiać się nad URL, tylko nad presenterami i akcjami. +Zamiast prawdziwego URL po prostu zapisujesz znajomą parę `Presenter:akcja` i dodajesz ewentualne parametry. Sztuczka tkwi w `n:href`, które mówi Latte, aby ten atrybut przetworzyło i wygenerowało prawdziwy URL. W Nette w ogóle nie musisz myśleć o URL, tylko o presenterach i akcjach. -Więcej informacji znajdziesz w rozdziale [Tworzenie linków URL|creating-links]. +Więcej informacji znajdziesz w rozdziale [Tworzenie odnośników URL|creating-links]. Przekierowanie ============== -Do przejścia na inny presenter służą metody `redirect()` i `forward()`, które mają bardzo podobną składnię jak metoda [link() |#Tworzenie linków]. +Do przejścia na inny presenter służą metody `redirect()` i `forward()`. Mają bardzo podobną składnię do metody [link() |#Tworzenie odnośników]. -Metoda `forward()` przechodzi na nowy presenter natychmiast bez przekierowania HTTP: +Metoda `forward()` przechodzi na nowy presenter natychmiast, bez przekierowania HTTP: ```php $this->forward('Product:show'); ``` -Przykład tzw. tymczasowego przekierowania z kodem HTTP 302 (lub 303, jeśli metodą aktualnego żądania jest POST): +Przykład tymczasowego przekierowania z kodem HTTP 302 (albo 303, jeśli metodą bieżącego żądania jest POST): ```php $this->redirect('Product:show', $id); ``` -Stałe przekierowanie z kodem HTTP 301 osiągniesz w ten sposób: +Aby uzyskać trwałe przekierowanie z kodem HTTP 301, użyj tego: ```php $this->redirectPermanent('Product:show', $id); ``` -Na inny URL poza aplikacją można przekierować metodą `redirectUrl()`. Jako drugi parametr można podać kod HTTP, domyślny to 302 (lub 303, jeśli metodą aktualnego żądania jest POST): +Na inny URL poza aplikacją możesz przekierować metodą `redirectUrl()`. Kod HTTP można podać jako drugi parametr; domyślnie jest to 302 (albo 303, jeśli metodą bieżącego żądania jest POST): ```php $this->redirectUrl('https://nette.org'); ``` -Przekierowanie natychmiast kończy działanie presentera przez wyrzucenie tzw. cichego wyjątku kończącego `Nette\Application\AbortException`. +Przekierowanie natychmiast kończy działanie presentera, zgłaszając tak zwany cichy wyjątek zakończenia `Nette\Application\AbortException`. -Przed przekierowaniem można wysłać [flash message |#Wiadomości flash], czyli wiadomości, które zostaną po przekierowaniu wyświetlone w szablonie. +Przed przekierowaniem można wysłać [wiadomości flash |#Wiadomości flash], czyli wiadomości, które wyświetlą się w szablonie po przekierowaniu. Wiadomości flash ================ -Są to wiadomości zazwyczaj informujące o wyniku jakiejś operacji. Ważną cechą wiadomości flash jest to, że są dostępne w szablonie również po przekierowaniu. Nawet po wyświetleniu pozostają aktywne jeszcze przez 30 sekund – na przykład na wypadek, gdyby z powodu błędnego transferu użytkownik odświeżył stronę - wiadomość mu więc od razu nie zniknie. +To wiadomości informujące zwykle o wyniku jakiejś operacji. Ważną cechą wiadomości flash jest to, że pozostają dostępne w szablonie również po przekierowaniu. Po wyświetleniu pozostają aktywne przez kolejne 30 sekund, na przykład gdyby użytkownik odświeżył stronę z powodu błędu transmisji - wiadomość nie zniknie od razu. -Wystarczy wywołać metodę [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] a o przekazanie do szablonu zadba presenter. Pierwszym parametrem jest tekst wiadomości, a opcjonalnym drugim parametrem jej typ (error, warning, info itp.). Metoda `flashMessage()` zwraca instancję wiadomości flash, której można dodawać dalsze informacje. +Wystarczy wywołać metodę [flashMessage() |api:Nette\Application\UI\Control::flashMessage()], a presenter zajmie się przekazaniem jej do szablonu. Pierwszym parametrem jest tekst wiadomości, a opcjonalnym drugim jej typ (np. error, warning, info). Metoda `flashMessage()` zwraca instancję wiadomości flash, do której można dodać dalsze informacje. ```php -$this->flashMessage('Pozycja została usunięta.'); +$this->flashMessage('Element został usunięty.'); $this->redirect(/* ... */); // i przekierowujemy ``` -W szablonie te wiadomości są dostępne w zmiennej `$flashes` jako obiekty `stdClass`, które zawierają właściwości `message` (tekst wiadomości), `type` (typ wiadomości) i mogą zawierać już wspomniane informacje użytkownika. Wyrenderujemy je na przykład tak: +W szablonie wiadomości te dostępne są w zmiennej `$flashes` jako obiekty `stdClass` zawierające właściwości `message` (tekst wiadomości), `type` (typ wiadomości) i ewentualnie wspomniane wcześniej informacje dodane przez użytkownika. Renderujemy je tak: ```latte {foreach $flashes as $flash} @@ -202,10 +227,10 @@ W szablonie te wiadomości są dostępne w zmiennej `$flashes` jako obiekty `std ``` -Błąd 404 i spółka. -================== +Błąd 404 itd. +============= -Jeśli nie można spełnić żądania, na przykład z powodu, że artykuł, który chcemy wyświetlić, nie istnieje w bazie danych, wyrzucamy błąd 404 metodą `error(?string $message = null, int $httpCode = 404)`. +Jeśli żądania nie da się spełnić, na przykład dlatego, że artykuł, który chcemy wyświetlić, nie istnieje w bazie danych, zgłaszamy błąd 404 metodą `error(string $message = '', int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,13 +243,13 @@ public function renderShow(int $id): void } ``` -Kod HTTP błędu można przekazać jako drugi parametr, domyślny to 404. Metoda działa tak, że wyrzuca wyjątek `Nette\Application\BadRequestException`, po czym `Application` przekazuje sterowanie do error-presentera. Co jest presenterem, którego zadaniem jest wyświetlenie strony informującej o zaistniałym błędzie. Ustawienie error-preseteru dokonuje się w [konfiguracji application|configuration]. +Kod błędu HTTP można przekazać jako drugi parametr; domyślnie jest to 404. Metoda działa tak, że zgłasza `Nette\Application\BadRequestException`, po czym `Application` przekazuje sterowanie do error-presentera. To presenter, którego zadaniem jest wyświetlenie strony informującej o zaistniałym błędzie. Error-presenter konfiguruje się w [konfiguracji aplikacji|configuration]. -Wysłanie JSON -============= +Wysyłanie JSON +============== -Przykład metody action, która wysyła dane w formacie JSON i kończy presenter: +Metoda `sendJson($data)` koduje podane dane do JSON, wysyła je jako odpowiedź HTTP i kończy presenter. Przykład: ```php public function actionData(): void @@ -238,12 +263,12 @@ public function actionData(): void Parametry żądania .{data-version:3.1.14} ======================================== -Presenter, a także każdy komponent, uzyskuje z żądania HTTP swoje parametry. Ich wartość można uzyskać metodą `getParameter($name)` lub `getParameters()`. Wartości są ciągami znaków lub tablicami ciągów znaków, są to w zasadzie surowe dane uzyskane bezpośrednio z URL. +Presenter, a także każdy komponent, pozyskuje swoje parametry z żądania HTTP. Ich wartości możesz pobrać metodami `getParameter($name)` albo `getParameters()`. Wartościami są stringi albo tablice stringów, czyli w istocie surowe dane pozyskane bezpośrednio z URL. -Dla większej wygody zalecamy udostępnianie parametrów przez właściwości. Wystarczy oznaczyć je atrybutem `#[Parameter]`: +Dla większej wygody zalecamy sięganie po parametry przez właściwości. Wystarczy oznaczyć je atrybutem `#[Parameter]`: ```php -use Nette\Application\Attributes\Parameter; // ta linia jest ważna +use Nette\Application\Attributes\Parameter; // ten wiersz jest ważny class HomePresenter extends Nette\Application\UI\Presenter { @@ -252,9 +277,9 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Przy właściwości zalecamy podanie również typu danych (np. `string`), a Nette na jego podstawie automatycznie przeliczy wartość. Wartości parametrów można również [walidować |#Walidacja parametrów]. +Dla właściwości zalecamy podanie typu danych (np. `string`), a Nette automatycznie odpowiednio rzutuje wartość. Wartości parametrów można też [walidować |#Walidacja parametrów]. -Przy tworzeniu linku można parametrom wartość ustawić bezpośrednio: +Przy tworzeniu odnośnika możesz ustawić wartość parametru bezpośrednio: ```latte kliknij @@ -264,14 +289,14 @@ Przy tworzeniu linku można parametrom wartość ustawić bezpośrednio: Parametry trwałe ================ -Parametry trwałe służą do utrzymywania stanu między różnymi żądaniami. Ich wartość pozostaje taka sama nawet po kliknięciu na link. W przeciwieństwie do danych w sesji, są one przekazywane w URL. I to całkowicie automatycznie, nie trzeba ich więc jawnie podawać w `link()` lub `n:href`. +Parametry trwałe służą do utrzymywania stanu między różnymi żądaniami. Ich wartość pozostaje taka sama również po kliknięciu w odnośnik. W odróżnieniu od danych w sesji przesyłane są w URL. I dzieje się to całkowicie automatycznie, nie ma więc potrzeby podawania ich jawnie w `link()` czy `n:href`. -Przykład użycia? Masz aplikację wielojęzyczną. Aktualny język jest parametrem, który musi być stale częścią URL. Ale byłoby niesamowicie męczące podawanie go w każdym linku. Więc zrobisz z niego parametr trwały `lang` i będzie się przenosił sam. Parada! +Przykładowe zastosowanie? Wyobraź sobie, że masz aplikację wielojęzyczną. Bieżący język to parametr, który musi zawsze być częścią URL. Ale dodawanie go do każdego odnośnika byłoby niesamowicie żmudne. Czynisz więc z niego parametr trwały `lang` i będzie przenoszony automatycznie. Zgrabne! -Tworzenie parametru trwałego jest w Nette niezwykle proste. Wystarczy utworzyć publiczną właściwość i oznaczyć ją atrybutem: (wcześniej używano `/** @persistent */`) +Utworzenie parametru trwałego w Nette jest wyjątkowo proste. Wystarczy utworzyć właściwość publiczną i oznaczyć ją atrybutem: (wcześniej używano `/** @persistent */`) ```php -use Nette\Application\Attributes\Persistent; // ta linia jest ważna +use Nette\Application\Attributes\Persistent; // ten wiersz jest ważny class ProductPresenter extends Nette\Application\UI\Presenter { @@ -280,14 +305,14 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Jeśli `$this->lang` będzie miał wartość na przykład `'en'`, to również linki utworzone za pomocą `link()` lub `n:href` będą zawierać parametr `lang=en`. A po kliknięciu na link ponownie `$this->lang = 'en'`. +Jeśli `$this->lang` ma wartość w rodzaju `'en'`, to odnośniki tworzone przez `link()` albo `n:href` będą zawierać również parametr `lang=en`. A po kliknięciu w odnośnik `$this->lang` znów będzie `'en'`. -Przy właściwości zalecamy podanie również typu danych (np. `string`) i można podać również wartość domyślną. Wartości parametrów można [walidować |#Walidacja parametrów]. +Dla właściwości zalecamy podanie typu danych (np. `string`), możesz też podać wartość domyślną. Wartości parametrów można [walidować |#Walidacja parametrów]. -Parametry trwałe standardowo przenoszą się między wszystkimi akcjami danego presentera. Aby przenosiły się również między wieloma presenterami, trzeba je zdefiniować albo: +Parametry trwałe przenoszone są zwykle między wszystkimi akcjami danego presentera. Aby przenosić je również między wieloma presenterami, trzeba zdefiniować je albo: -- we wspólnym przodku, od którego dziedziczą presentery -- w trait, którego użyją presentery: +- we wspólnym przodku, po którym presentery dziedziczą +- albo w traicie, którego presentery używają: ```php trait LanguageAware @@ -302,42 +327,62 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Przy tworzeniu linku można parametrowi trwałemu zmienić wartość: +Przy tworzeniu odnośnika wartość parametru trwałego można zmienić: ```latte szczegóły po czesku ``` -Lub można go *zresetować*, tj. usunąć z URL. Wtedy przyjmie swoją wartość domyślną: +Albo *zresetować*, czyli usunąć z URL. Przyjmie wtedy swoją wartość domyślną: ```latte kliknij ``` +Wspólna przestrzeń parametrów +============================= + +Parametry żądania, [parametry trwałe |#Parametry trwałe] oraz parametry metod `action`, `render` i `handle` (sygnału) dzielą jedną przestrzeń, w której każdy identyfikowany jest swoją nazwą. Jeśli ta sama nazwa pojawi się w kilku z nich, odnoszą się do jednej i tej samej wartości. + +Często wykorzystuje się to z korzyścią. Na przykład parametr trwały `lang` i argument `$lang` metody akcji albo sygnału to jedno i to samo - bieżącą wartość parametru trwałego odczytasz, po prostu wymieniając go w sygnaturze metody: + +```php +#[Persistent] +public string $lang; + +public function handleSearch(string $query, string $lang): void +{ + // $lang zawiera bieżącą wartość parametru trwałego lang +} +``` + +Ponieważ przestrzeń ta jest wspólna, dbaj o unikalność nazw parametrów, chyba że celowo chcesz, aby dzieliły wartość. Dotyczy to również sygnałów, które dodatkowo odczytują parametry z ciała POST żądania, zobacz [Sygnały w głąb |components#Sygnały w głąb]. + + Komponenty interaktywne ======================= -Presentery mają wbudowany system komponentów. Komponenty to samodzielne, wielokrotnego użytku całości, które wstawiamy do presenterów. Mogą to być [formularze |forms:in-presenter], siatki danych, menu, właściwie cokolwiek, co ma sens używać wielokrotnie. +Presentery mają wbudowany system komponentów. Komponenty to osobne jednostki wielokrotnego użytku, które osadzamy w presenterach. Mogą to być [formularze |forms:in-presenter], datagridy, menu, w zasadzie wszystko, co warto wykorzystywać wielokrotnie. -Jak wstawia się komponenty do presentera i następnie używa? Dowiesz się tego w rozdziale [Komponenty |components]. Nawet dowiesz się, co mają wspólnego z Hollywoodem. +Jak komponenty trafiają do presenterów i jak się ich potem używa? Dowiesz się tego w rozdziale [Komponenty |components]. Dowiesz się nawet, co mają wspólnego z Hollywood. -A gdzie mogę zdobyć komponenty? Na stronie [Componette |https://componette.org/search/component] znajdziesz komponenty open-source oraz wiele innych dodatków do Nette, które umieścili tu wolontariusze ze społeczności wokół frameworka. +A skąd wziąć komponenty? Na [Componette |https://componette.org/search/component] znajdziesz komponenty open source i wiele innych dodatków do Nette, przekazanych przez ochotników ze społeczności frameworka. -Idziemy do hloubky -================== +Idziemy głębiej +=============== .[tip] -Z tym, co do tej pory pokazaliśmy w tym rozdziale, prawdopodobnie w zupełności sobie poradzisz. Poniższe linijki są przeznaczone dla tych, którzy interesują się presenterami dogłębnie i chcą wiedzieć absolutnie wszystko. +To, co omówiliśmy dotąd w tym rozdziale, wystarczy zapewne do większości zastosowań. Kolejne sekcje przeznaczone są dla tych, których interesuje głębsze zanurzenie w presentery i którzy chcą wiedzieć absolutnie wszystko. Walidacja parametrów -------------------- -Wartości [parametrów żądania |#Parametry żądania] i [parametrów trwałych |#Parametry trwałe] otrzymanych z URL zapisuje do właściwości metoda `loadState()`. Ta również kontroluje, czy odpowiada typ danych podany przy właściwości, w przeciwnym razie odpowie błędem 404 i strona się nie wyświetli. +Wartości [parametrów żądania |#Parametry żądania] i [parametrów trwałych |#Parametry trwałe] otrzymane z URL zapisywane są do właściwości metodą `loadState()`. Sprawdza ona również, czy typ danych podany we właściwości się zgadza, w przeciwnym razie odpowie błędem 404 i strona nie zostanie wyświetlona. -Nigdy ślepo nie wierz parametrom, ponieważ mogą być łatwo przez użytkownika nadpisane w URL. W ten sposób na przykład zweryfikujemy, czy język `$this->lang` jest wśród wspieranych. Odpowiednią drogą jest nadpisanie wspomnianej metody `loadState()`: +Nigdy nie ufaj ślepo parametrom otrzymanym z URL, bo użytkownik może je łatwo nadpisać. Tak na przykład sprawdzimy, czy język `$this->lang` należy do obsługiwanych. Odpowiednim sposobem jest nadpisanie wspomnianej metody `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -347,8 +392,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // tutaj ustawia się $this->lang - // następuje własna kontrola wartości: + parent::loadState($params); // tutaj ustawiane jest $this->lang + // następuje własne sprawdzenie wartości: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -357,30 +402,30 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -Zapisanie i odtworzenie żądania -------------------------------- +Zapis i przywrócenie żądania +---------------------------- -Żądanie, które obsługuje presenter, jest obiektem [api:Nette\Application\Request] i zwraca go metoda presentera `getRequest()`. +Żądanie obsługiwane przez presenter to obiekt [api:Nette\Application\Request], zwracany metodą presentera `getRequest()`. -Aktualne żądanie można zapisać do sesji lub odwrotnie, odtworzyć z niej i pozwolić presenterowi ponownie je wykonać. Przydaje się to na przykład w sytuacji, gdy użytkownik wypełnia formularz i wygaśnie mu sesja logowania. Aby nie stracił danych, przed przekierowaniem na stronę logowania aktualne żądanie zapisujemy do sesji za pomocą `$reqId = $this->storeRequest()`, które zwraca jego identyfikator w postaci krótkiego ciągu znaków, a ten przekazujemy jako parametr do presentera logowania. +Bieżące żądanie można zapisać do sesji albo, odwrotnie, przywrócić je z niej i kazać presenterowi wykonać je ponownie. Przydaje się to na przykład wtedy, gdy użytkownik wypełnia formularz, a jego sesja logowania wygasa. Aby nie utracić danych, przed przekierowaniem na stronę logowania zapisujemy bieżące żądanie do sesji przez `$reqId = $this->storeRequest()`. Zwraca to jego identyfikator w postaci krótkiego stringa, który następnie przekazujemy jako parametr do presentera logowania. -Po zalogowaniu wywołujemy metodę `$this->restoreRequest($reqId)`, która pobiera żądanie z sesji i forwarduje na nie. Metoda przy tym weryfikuje, czy żądanie utworzył ten sam użytkownik, który się teraz zalogował. Jeśli zalogowałby się inny użytkownik lub klucz byłby nieprawidłowy, nie zrobi nic i program kontynuuje dalej. +Po zalogowaniu wywołujemy metodę `$this->restoreRequest($reqId)`, która pobiera żądanie z sesji. Żądania POST są do niego przekazywane, a pozostałe (GET) przekierowywane na URL żądania. Metoda sprawdza, czy żądanie zostało utworzone przez tego samego użytkownika, który jest teraz zalogowany. Jeśli zaloguje się inny użytkownik albo klucz jest nieprawidłowy, nie robi nic, a program działa dalej jak zwykle. -Zobacz poradnik [Jak wrócić do poprzedniej strony |best-practices:restore-request]. +Zobacz przewodnik [Jak wrócić do poprzedniej strony |best-practices:restore-request]. Kanonizacja ----------- -Presentery mają jedną naprawdę świetną cechę, która przyczynia się do lepszego SEO (optymalizacji dla wyszukiwarek internetowych). Automatycznie zapobiegają istnieniu duplikatów treści pod różnymi URL. Jeśli do określonego celu prowadzi więcej adresów URL, np. `/index` i `/index?page=1`, framework określa jeden z nich jako podstawowy (kanoniczny) i pozostałe na niego przekierowuje za pomocą kodu HTTP 301. Dzięki temu wyszukiwarki nie indeksują stron dwukrotnie i nie rozdrabniają ich page rank. +Presentery mają naprawdę znakomitą funkcję, która przyczynia się do lepszego SEO (Search Engine Optimization). Automatycznie zapobiegają istnieniu zduplikowanej treści pod różnymi URL. Jeśli do konkretnego celu prowadzi kilka URL, np. `/index` i `/index?page=1`, framework wyznacza jeden z nich jako podstawowy (kanoniczny) i przekierowuje na niego pozostałe kodem HTTP 301. Dzięki temu wyszukiwarki nie indeksują Twoich stron dwa razy i nie rozmywają ich page ranku. -Ten proces nazywa się kanonizacją. Kanonicznym URL jest ten, który generuje [router|routing], zazwyczaj więc pierwsza pasująca trasa w kolekcji. +Proces ten nazywa się kanonizacją. Kanonicznym URL jest ten wygenerowany przez [router|routing], zwykle pierwsza pasująca trasa w kolekcji. Kanonizacja jest domyślnie włączona i można ją wyłączyć przez `$this->autoCanonicalize = false`. -Do przekierowania nie dochodzi przy żądaniu AJAX lub POST, ponieważ doszłoby do utraty danych lub nie miałoby to wartości dodanej z punktu widzenia SEO. +Przekierowanie nie następuje przy żądaniach AJAX ani POST, bo mogłoby to prowadzić do utraty danych albo nie dawałoby żadnej dodatkowej wartości dla SEO. -Kanonizację można wywołać również manualnie za pomocą metody `canonicalize()`, której podobnie jak metodzie `link()` przekazuje się presenter, akcję i parametry. Tworzy link i porównuje go z aktualnym adresem URL. Jeśli się różnią, przekierowuje na wygenerowany link. +Kanonizację możesz też wywołać ręcznie metodą `canonicalize()`. Podobnie jak metodzie `link()`, przekazujesz jej presenter, akcję i parametry. Generuje odnośnik i porównuje go z bieżącym adresem URL. Jeśli się różnią, przekierowuje na wygenerowany odnośnik. ```php public function actionShow(int $id, ?string $slug = null): void @@ -391,31 +436,13 @@ public function actionShow(int $id, ?string $slug = null): void } ``` - -Zdarzenia ---------- - -Oprócz metod `startup()`, `beforeRender()` i `shutdown()`, które są wywoływane jako część cyklu życia presentera, można zdefiniować jeszcze inne funkcje, które mają być automatycznie wywoływane. Presenter definiuje tzw. [zdarzenia |nette:glossary#Eventy zdarzenia], których handlery dodasz do tablic `$onStartup`, `$onRender` i `$onShutdown`. - -```php -class ArticlePresenter extends Nette\Application\UI\Presenter -{ - public function __construct() - { - $this->onStartup[] = function () { - // ... - }; - } -} -``` - -Handlery w tablicy `$onStartup` są wywoływane tuż przed metodą `startup()`, dalej `$onRender` między `beforeRender()` a `render()` i na końcu `$onShutdown` tuż przed `shutdown()`. +Kompletny wzorzec łączący filtry tras z `canonicalize()` w celu tworzenia przyjaznych SEO adresów URL znajdziesz w [Ładne URL ze slugami |best-practices:pretty-urls]. Odpowiedzi ---------- -Odpowiedź, którą zwraca presenter, jest obiektem implementującym interfejs [api:Nette\Application\Response]. Dostępnych jest szereg gotowych odpowiedzi: +Odpowiedź zwracana przez presenter to obiekt implementujący interfejs [api:Nette\Application\Response]. Dostępnych jest kilka gotowych odpowiedzi: - [api:Nette\Application\Responses\CallbackResponse] - wysyła callback - [api:Nette\Application\Responses\FileResponse] - wysyła plik @@ -430,43 +457,103 @@ Odpowiedzi wysyła się metodą `sendResponse()`: ```php use Nette\Application\Responses; -// Zwykły tekst +// zwykły tekst $this->sendResponse(new Responses\TextResponse('Hello Nette!')); -// Wysyła plik +// wysyła plik $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Odpowiedzią będzie callback +// wysyła callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { - echo '

    Hello

    '; + echo '

    Cześć

    '; } }; $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +Możesz też napisać własną odpowiedź. Wystarczy zaimplementować interfejs `Nette\Application\Response`, który ma jedną metodę `send()` otrzymującą żądanie i odpowiedź HTTP. Przydaje się to na przykład przy strumieniowaniu danych, których nie chcesz trzymać w pamięci: + +```php +class CsvResponse implements Nette\Application\Response +{ + public function __construct( + private string $fileName, + private iterable $rows, + ) { + } + + public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void + { + $response->setContentType('text/csv', 'utf-8'); + $response->sendAsFile($this->fileName); + + $handle = fopen('php://output', 'w'); + foreach ($this->rows as $row) { + fputcsv($handle, $row); + } + + fclose($handle); + } +} +``` + +Wysyłasz ją potem w presenterze jak zwykle: `$this->sendResponse(new CsvResponse('export.csv', $rows));` + -Ograniczenie dostępu za pomocą `#[Requires]` .{data-version:3.2.2} ------------------------------------------------------------------- +Cache HTTP +---------- -Atrybut `#[Requires]` zapewnia zaawansowane możliwości ograniczania dostępu do presenterów i ich metod. Można go użyć do specyfikacji metod HTTP, wymagania żądania AJAX, ograniczenia do tego samego pochodzenia (same origin) oraz dostępu tylko przez forwardowanie. Atrybut można stosować zarówno do klas presenterów, jak i do poszczególnych metod `action()`, `render()`, `handle()` i `createComponent()`. +Metoda `lastModified()` ułatwia korzystanie z cache HTTP. Przekazujesz jej datę i czas ostatniej modyfikacji treści (jako timestamp, string albo obiekt `DateTimeInterface`), a opcjonalnie walidator ETag (krótki string identyfikujący bieżącą wersję treści, na przykład jej hash) i czas wygaśnięcia. Jeśli przeglądarka ma już pasującą wersję, presenter wysyła odpowiedź `304 Not Modified` i kończy działanie, dzięki czemu strona nie jest niepotrzebnie renderowana ani przesyłana: -Można określić te ograniczenia: +```php +public function renderArticle(int $id): void +{ + $article = $this->articles->getById($id); + $this->lastModified($article->updatedAt); + // ... +} +``` + + +Finalizacja szablonu .{data-version:3.3.0} +------------------------------------------ + +Gdy presenter renderuje szablon, metoda `sendTemplate()` tuż przed renderowaniem wywołuje `completeTemplate()`. Metoda ta uzupełnia zmienne oznaczone atrybutem `#[TemplateVariable]` i odnajduje plik szablonu (zmienne domyślne ustawia już `TemplateFactory` przy tworzeniu szablonu). Możesz nadpisać tę metodę chronioną, aby dodać zmienne wspólne dla wszystkich widoków albo ustawić inny plik: + +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'Moja aplikacja'; +} +``` + + +Ograniczanie dostępu przez `#[Requires]` .{data-version:3.2.3} +-------------------------------------------------------------- + +Atrybut `#[Requires]` daje zaawansowane możliwości ograniczania dostępu do presenterów i ich metod. Można nim określić metody HTTP, wymagać żądania AJAX, ograniczyć do tego samego pochodzenia i dopuścić dostęp tylko przez forward. Atrybut można zastosować zarówno do klas presenterów, jak i do poszczególnych metod, takich jak `action()`, `render()`, `handle()` i `createComponent()`. + +Możesz określić te ograniczenia: - na metody HTTP: `#[Requires(methods: ['GET', 'POST'])]` - wymaganie żądania AJAX: `#[Requires(ajax: true)]` - dostęp tylko z tego samego pochodzenia: `#[Requires(sameOrigin: true)]` - dostęp tylko przez forward: `#[Requires(forward: true)]` -- ograniczenie do konkretnych akcji: `#[Requires(actions: 'default')]` +- ograniczenia na konkretne akcje: `#[Requires(actions: 'default')]` + +.[note] +Od wersji 3.3 zgodność pochodzenia weryfikowana jest nagłówkiem przeglądarki `Sec-Fetch-Site` (wcześniej przez ciasteczko SameSite), co jest bardziej niezawodne i sprawdza dokładną zgodność schematu, domeny i portu. -Szczegóły znajdziesz w poradniku [Jak używać atrybutu Requires |best-practices:attribute-requires]. +Szczegóły znajdziesz w przewodniku [Jak używać atrybutu Requires |best-practices:attribute-requires]. Kontrola metody HTTP -------------------- -Presentery w Nette automatycznie weryfikują metodę HTTP każdego przychodzącego żądania. Powodem tej kontroli jest przede wszystkim bezpieczeństwo. Standardowo dozwolone są metody `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. +Presentery w Nette automatycznie weryfikują metodę HTTP każdego przychodzącego żądania, przede wszystkim ze względów bezpieczeństwa. Domyślnie dozwolone są metody `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. -Jeśli chcesz dodatkowo zezwolić na przykład na metodę `OPTIONS`, użyj do tego atrybutu `#[Requires]` (od Nette Application v3.2): +Jeśli chcesz dodatkowo dopuścić na przykład metodę `OPTIONS`, użyj atrybutu `#[Requires]` (od Nette Application v3.2.3): ```php #[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] @@ -475,26 +562,34 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -W wersji 3.1 weryfikacja odbywa się w `checkHttpMethod()`, która sprawdza, czy metoda określona w żądaniu jest zawarta w tablicy `$presenter->allowedMethods`. Dodanie metody wykonaj w ten sposób: +Od wersji 3.1.13 weryfikacja wykonywana jest w `checkHttpMethod()`, która sprawdza, czy metoda podana w żądaniu znajduje się w tablicy `$presenter->allowedMethods`. Od wersji 3.2.3 podejście to jest przestarzałe na rzecz `#[Requires]`. Metodę możesz nadpisać tak: ```php class MyPresenter extends Nette\Application\UI\Presenter { - protected function checkHttpMethod(): void - { - $this->allowedMethods[] = 'OPTIONS'; - parent::checkHttpMethod(); - } + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } } ``` -Ważne jest podkreślenie, że jeśli zezwolisz na metodę `OPTIONS`, musisz ją następnie również odpowiednio obsłużyć w ramach swojego presentera. Metoda jest często używana jako tzw. preflight request, który przeglądarka automatycznie wysyła przed rzeczywistym żądaniem, gdy trzeba sprawdzić, czy żądanie jest dozwolone z punktu widzenia polityki CORS (Cross-Origin Resource Sharing). Jeśli zezwolisz na metodę, ale nie zaimplementujesz prawidłowej odpowiedzi, może to prowadzić do niespójności i potencjalnych problemów bezpieczeństwa. +Warto podkreślić, że jeśli włączysz metodę `OPTIONS`, musisz następnie odpowiednio ją obsłużyć w swoim presenterze. Metoda ta bywa używana jako tak zwane żądanie preflight, które przeglądarka wysyła automatycznie przed właściwym żądaniem, gdy trzeba ustalić, czy żądanie jest dopuszczalne według polityki CORS (Cross-Origin Resource Sharing). Jeśli włączysz metodę, ale nie zaimplementujesz poprawnej odpowiedzi, może to prowadzić do niespójności i potencjalnych problemów z bezpieczeństwem. + + +Oznaczanie przestarzałych akcji .{data-version:3.2.3} +----------------------------------------------------- + +Atrybut `#[Deprecated]` oznacza akcje, sygnały albo całe presentery jako przestarzałe i przeznaczone do usunięcia w przyszłości. Przy generowaniu odnośników do przestarzałych części aplikacji Nette zgłasza ostrzeżenie, aby zwrócić uwagę programistów. + +Atrybut możesz zastosować albo do całej klasy presentera, albo do poszczególnych metod `action()`, `render()` i `handle()`. Dalsza lektura ============== - [Metody i atrybuty inject |best-practices:inject-method-attribute] -- [Składanie presenterów z trait |best-practices:presenter-traits] +- [Składanie presenterów z traitów |best-practices:presenter-traits] - [Przekazywanie ustawień do presenterów |best-practices:passing-settings-to-presenters] - [Jak wrócić do poprzedniej strony |best-practices:restore-request] diff --git a/application/pl/routing.texy b/application/pl/routing.texy index 34577ae078..b93081a766 100644 --- a/application/pl/routing.texy +++ b/application/pl/routing.texy @@ -1,28 +1,28 @@ -Routowanie -********** +Routing +*******
    -Router odpowiada za wszystko związane z adresami URL, abyś Ty już nie musiał się nad nimi zastanawiać. Pokażemy: +Router zajmuje się wszystkim, co dotyczy adresów URL, dzięki czemu nie musisz o nich myśleć. Pokażemy Ci: -- jak ustawić router, aby URL były zgodne z oczekiwaniami -- powiemy o SEO i przekierowaniach +- jak skonfigurować router, aby URL wyglądały tak, jak chcesz +- omówimy SEO i przekierowania - i pokażemy, jak napisać własny router
    -Bardziej ludzkie URL (lub też cool czy pretty URL) są bardziej użyteczne, łatwiejsze do zapamiętania i pozytywnie wpływają na SEO. Nette o tym myśli i w pełni wychodzi naprzeciw deweloperom. Możesz dla swojej aplikacji zaprojektować dokładnie taką strukturę adresów URL, jaką będziesz chciał. Możesz ją zaprojektować nawet w chwili, gdy aplikacja jest już gotowa, ponieważ obejdzie się to bez ingerencji w kod czy szablony. Definiuje się ją bowiem w elegancki sposób w jednym [jedynym miejscu |#Włączenie do aplikacji], w routerze, i nie jest rozproszona w formie adnotacji we wszystkich presenterach. +Bardziej przyjazne dla ludzi URL (znane też jako cool albo pretty URL) są użyteczniejsze, łatwiejsze do zapamiętania i pozytywnie wpływają na SEO. Nette ma to na uwadze i w pełni wychodzi naprzeciw potrzebom programistów. Możesz zaprojektować dokładnie taką strukturę URL, jakiej chcesz dla swojej aplikacji. Możesz zaprojektować ją nawet wtedy, gdy aplikacja jest już gotowa, bo nie wymaga to zmian w kodzie ani szablonach. Definiuje się ją elegancko w [jednym miejscu |#Integracja], w routerze, zamiast rozpraszać ją jako adnotacje po wszystkich presenterach. -Router w Nette jest wyjątkowy tym, że jest **dwukierunkowy.** Potrafi zarówno dekodować URL w żądaniu HTTP, jak i tworzyć linki. Odgrywa więc kluczową rolę w [Nette Application |how-it-works#Nette Application], ponieważ nie tylko decyduje o tym, który presenter i akcja będzie wykonywać aktualne żądanie, ale także wykorzystuje się go do [generowania URL |creating-links] w szablonie itp. +Router w Nette jest wyjątkowy, bo jest **dwukierunkowy**. Potrafi zarówno dekodować URL z żądań HTTP, jak i tworzyć odnośniki. Odgrywa więc kluczową rolę w [Nette Application |how-it-works#Nette Application], bo nie tylko decyduje, który presenter i akcja obsłużą bieżące żądanie, ale służy też do [generowania URL |creating-links] w szablonach itd. -Jednak router nie jest ograniczony tylko do tego zastosowania, można go używać w aplikacjach, gdzie w ogóle nie używa się presenterów, dla REST API, itd. Więcej w części [#Samostatné použití]. +Router nie ogranicza się jednak tylko do tego zastosowania; możesz używać go w aplikacjach, w których presentery w ogóle nie występują, do REST API itd. Więcej szczegółów w sekcji [#Użycie samodzielne]. Kolekcja tras ============= -Najprzyjemniejszy sposób definiowania postaci adresów URL w aplikacji oferuje klasa [api:Nette\Application\Routers\RouteList]. Definicja składa się z listy tzw. tras (routes), czyli masek adresów URL i przypisanych do nich presenterów i akcji za pomocą prostego API. Tras nie musimy w żaden sposób nazywać. +Najprzyjemniejszy sposób definiowania struktury adresów URL w aplikacji oferuje klasa [api:Nette\Application\Routers\RouteList]. Definicja składa się z listy tak zwanych tras, czyli masek adresów URL oraz powiązanych z nimi presenterów i akcji, zapisanych prostym API. Tras nie musimy w żaden sposób nazywać. ```php $router = new Nette\Application\Routers\RouteList; @@ -31,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -Przykład mówi, że jeśli w przeglądarce otworzymy `https://domain.com/rss.xml`, wyświetli się presenter `Feed` z akcją `rss`, jeśli `https://domain.com/article/12`, wyświetli się presenter `Article` z akcją `view` itd. W przypadku nieznalezienia odpowiedniej trasy Nette Application reaguje wyrzuceniem wyjątku [BadRequestException |api:Nette\Application\BadRequestException], który użytkownikowi wyświetli się jako strona błędu 404 Not Found. +Przykład pokazuje, że jeśli otworzymy w przeglądarce `https://domain.com/rss.xml`, wyświetli się presenter `Feed` z akcją `rss`. Jeśli `https://domain.com/article/12`, wyświetli się presenter `Article` z akcją `view` itd. Jeśli nie znajdzie się odpowiednia trasa, Nette Application odpowiada, zgłaszając [BadRequestException |api:Nette\Application\BadRequestException], który wyświetlany jest użytkownikowi jako strona błędu 404 Not Found. Kolejność tras -------------- -Absolutnie **kluczowa jest kolejność**, w jakiej są wymienione poszczególne trasy, ponieważ są one ewaluowane kolejno od góry do dołu. Obowiązuje zasada, że trasy deklarujemy **od szczegółowych do ogólnych**: +**Kolejność**, w jakiej wymieniane są poszczególne trasy, jest absolutnie **kluczowa**, bo są one obliczane kolejno od góry do dołu. Zasada brzmi: deklarujemy trasy **od konkretnych do ogólnych**: ```php -// ŹLE: 'rss.xml' przechwyci pierwsza trasa i rozumie ten ciąg jako +// ŹLE: 'rss.xml' przechwycone zostanie przez pierwszą trasę i zrozumiane jako $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -49,10 +49,10 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Trasy są ewaluowane od góry do dołu również przy generowaniu linków: +Trasy obliczane są od góry do dołu również przy generowaniu odnośników: ```php -// ŹLE: link do 'Feed:rss' wygeneruje jako 'admin/feed/rss' +// ŹLE: odnośnik do 'Feed:rss' wygeneruje się jako 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -61,57 +61,57 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Nie będziemy przed Tobą ukrywać, że prawidłowe zestawienie tras wymaga pewnej wprawy. Zanim ją opanujesz, użytecznym pomocnikiem będzie [panel routingu |#Debugowanie routera]. +Nie będziemy ukrywać, że poprawne złożenie tras wymaga pewnej wprawy. Zanim ją opanujesz, przydatnym narzędziem będzie [panel routingu |#Debugowanie routera]. Maska i parametry ----------------- -Maska opisuje ścieżkę względną od katalogu głównego strony internetowej. Najprostszą maską jest statyczny URL: +Maska opisuje ścieżkę względną od katalogu głównego witryny. Najprostszą maską jest statyczny URL: ```php $router->addRoute('products', 'Products:default'); ``` -Często maski zawierają tzw. **parametry**. Są one podane w nawiasach ostrych (np. ``) i są przekazywane do docelowego presentera, na przykład do metody `renderShow(int $year)` lub do trwałego parametru `$year`: +Często maski zawierają tak zwane **parametry**. Ujmowane są one w nawiasy ostre (np. ``) i przekazywane do docelowego presentera, na przykład do metody `renderShow(int $year)` albo do parametru trwałego `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Przykład mówi, że jeśli w przeglądarce otworzymy `https://example.com/chronicle/2020`, wyświetli się presenter `History` z akcją `show` i parametrem `year: 2020`. +Przykład pokazuje, że jeśli otworzymy w przeglądarce `https://example.com/chronicle/2020`, wyświetli się presenter `History` z akcją `show` i parametrem `year: 2020`. -Parametrom możemy określić wartość domyślną bezpośrednio w masce i tym samym stają się one opcjonalne: +Wartość domyślną parametrów możemy podać bezpośrednio w masce, czyniąc je opcjonalnymi: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Trasa będzie teraz akceptować również URL `https://example.com/chronicle/`, które ponownie wyświetli `History:show` z parametrem `year: 2020`. +Trasa przyjmie teraz również URL `https://example.com/chronicle/`, który znów wyświetli `History:show` z parametrem `year: 2020`. -Parametrem może być oczywiście również nazwa presentera i akcji. Na przykład tak: +Parametrami mogą być oczywiście również nazwy presentera i akcji. Na przykład: ```php $router->addRoute('/', 'Home:default'); ``` -Podana trasa akceptuje np. URL w postaci `/article/edit` lub także `/catalog/list` i rozumie je jako presentery i akcje `Article:edit` i `Catalog:list`. +Podana trasa przyjmuje na przykład URL w postaci `/article/edit` albo `/catalog/list` i rozumie je odpowiednio jako presentery i akcje `Article:edit` oraz `Catalog:list`. -Jednocześnie nadaje parametrom `presenter` i `action` wartości domyślne `Home` i `default`, a zatem są one również opcjonalne. Tak więc trasa akceptuje również URL w postaci `/article` i rozumie go jako `Article:default`. Lub odwrotnie, link do `Product:default` wygeneruje ścieżkę `/product`, link do domyślnego `Home:default` ścieżkę `/`. +Jednocześnie nadaje parametrom `presenter` i `action` wartości domyślne `Home` i `default`, czyniąc również je opcjonalnymi. Trasa przyjmuje więc także URL `/article` i rozumie go jako `Article:default`. Albo odwrotnie, odnośnik do `Product:default` generuje ścieżkę `/product`, a odnośnik do domyślnego `Home:default` generuje ścieżkę `/`. -Maska może opisywać nie tylko ścieżkę względną od katalogu głównego strony internetowej, ale także ścieżkę absolutną, jeśli zaczyna się od ukośnika, lub nawet cały absolutny URL, jeśli zaczyna się od dwóch ukośników: +Maska może opisywać nie tylko ścieżkę względną od katalogu głównego witryny, ale też ścieżkę bezwzględną, jeśli zaczyna się ukośnikiem, albo nawet cały bezwzględny URL, jeśli zaczyna się dwoma ukośnikami: ```php -// względnie do document root +// względem document root $router->addRoute('/', /* ... */); -// ścieżka absolutna (względna do domeny) +// ścieżka bezwzględna (względem domeny) $router->addRoute('//', /* ... */); -// absolutny URL włącznie z domeną (względny do schematu) +// bezwzględny URL wraz z domeną (względem schematu) $router->addRoute('//.example.com//', /* ... */); -// absolutny URL włącznie ze schematem +// bezwzględny URL wraz ze schematem $router->addRoute('https://.example.com//', /* ... */); ``` @@ -119,16 +119,16 @@ $router->addRoute('https://.example.com//', /* ... */); Wyrażenia walidacyjne --------------------- -Dla każdego parametru można ustalić warunek walidacyjny za pomocą [wyrażenia regularnego|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Na przykład dla parametru `id` określimy, że może przyjmować tylko cyfry za pomocą wyrażenia regularnego `\d+`: +Dla każdego parametru można podać warunek walidacyjny za pomocą [wyrażenia regularnego|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Na przykład dla parametru `id` określamy wyrażeniem `\d+`, że może zawierać wyłącznie cyfry: ```php $router->addRoute('/[/]', /* ... */); ``` -Domyślnym wyrażeniem regularnym dla wszystkich parametrów jest `[^/]+`, tj. wszystko oprócz ukośnika. Jeśli parametr ma akceptować również ukośniki, podamy wyrażenie `.+`: +Domyślnym wyrażeniem regularnym dla wszystkich parametrów jest `[^/]+`, czyli wszystko poza ukośnikiem. Jeśli parametr ma przyjmować również ukośniki, ustawiamy wyrażenie na `.+`: ```php -// akceptuje https://example.com/a/b/c, path będzie 'a/b/c' +// przyjmuje https://example.com/a/b/c, path będzie 'a/b/c' $router->addRoute('', /* ... */); ``` @@ -136,25 +136,25 @@ $router->addRoute('', /* ... */); Sekwencje opcjonalne -------------------- -W masce można oznaczać opcjonalne części za pomocą nawiasów kwadratowych. Opcjonalna może być dowolna część maski, mogą się w niej znajdować również parametry: +W masce można oznaczyć części opcjonalne nawiasami kwadratowymi. Opcjonalna może być dowolna część maski i może zawierać parametry: ```php $router->addRoute('[/]', /* ... */); -// Akceptuje ścieżki: -// /cs/download => lang => cs, name => download +// przyjmuje ścieżki: +// /en/download => lang => en, name => download // /download => lang => null, name => download ``` -Gdy parametr jest częścią sekwencji opcjonalnej, staje się oczywiście również opcjonalny. Jeśli nie ma podanej wartości domyślnej, będzie miał wartość null. +Gdy parametr jest częścią sekwencji opcjonalnej, naturalnie również staje się opcjonalny. Jeśli nie ma podanej wartości domyślnej, będzie miał wartość null. -Opcjonalne części mogą być również w domenie: +Części opcjonalne mogą znajdować się także w domenie: ```php $router->addRoute('//[.]example.com//', /* ... */); ``` -Sekwencje można dowolnie zagnieżdżać i kombinować: +Sekwencje można dowolnie zagnieżdżać i łączyć: ```php $router->addRoute( @@ -162,24 +162,24 @@ $router->addRoute( 'Home:default', ); -// Akceptuje ścieżki: -// /cs/hello +// przyjmuje ścieżki: +// /en/hello // /en-us/hello // /hello // /hello/page-12 ``` -Przy generowaniu URL dąży się do najkrótszej warianty, więc wszystko, co można pominąć, jest pomijane. Dlatego na przykład trasa `index[.html]` generuje ścieżkę `/index`. Odwrócić zachowanie można przez podanie wykrzyknika za lewym nawiasem kwadratowym: +Przy generowaniu URL preferowany jest wariant najkrótszy, więc wszystko, co można pominąć, zostaje pominięte. Dlatego na przykład trasa `index[.html]` generuje ścieżkę `/index`. To zachowanie można odwrócić, umieszczając wykrzyknik za lewym nawiasem kwadratowym: ```php -// akceptuje /hello i /hello.html, generuje /hello +// przyjmuje /hello i /hello.html, generuje /hello $router->addRoute('[.html]', /* ... */); -// akceptuje /hello i /hello.html, generuje /hello.html +// przyjmuje /hello i /hello.html, generuje /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Parametry opcjonalne (tj. parametry mające wartość domyślną) bez nawiasów kwadratowych zachowują się w zasadzie tak, jakby były ujęte w nawiasy w następujący sposób: +Parametry opcjonalne (czyli parametry z wartością domyślną) bez nawiasów kwadratowych zachowują się w istocie tak, jakby były ujęte w następujący sposób: ```php $router->addRoute('//', /* ... */); @@ -188,7 +188,7 @@ $router->addRoute('//', /* ... */); $router->addRoute('[/[/[]]]', /* ... */); ``` -Jeśli chcielibyśmy wpłynąć na zachowanie końcowego ukośnika, aby np. zamiast `/home/` generowało się tylko `/home`, można to osiągnąć w ten sposób: +Jeśli chcemy wpłynąć na zachowanie końcowego ukośnika, aby na przykład zamiast `/home/` generowało się `/home`, można to osiągnąć tak: ```php $router->addRoute('[[/[/]]]', /* ... */); @@ -198,24 +198,24 @@ $router->addRoute('[[/[/]]]', /* ... */); Symbole wieloznaczne -------------------- -W masce ścieżki absolutnej możemy użyć następujących symboli wieloznacznych i uniknąć w ten sposób np. konieczności zapisywania w masce domeny, która może się różnić w środowisku deweloperskim i produkcyjnym: +W masce bezwzględnego URL możemy użyć poniższych symboli wieloznacznych, aby nie musieć na przykład wpisywać do maski domeny, która może różnić się między środowiskiem deweloperskim a produkcyjnym: -- `%tld%` = top level domain, np. `com` lub `org` -- `%sld%` = second level domain, np. `example` +- `%tld%` = domena najwyższego poziomu, np. `com` albo `org` +- `%sld%` = domena drugiego poziomu, np. `example` - `%domain%` = domena bez subdomen, np. `example.com` - `%host%` = cały host, np. `www.example.com` - `%basePath%` = ścieżka do katalogu głównego ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); -$router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('//www.%sld%.%tld%/%basePath%//', /* ... */); ``` -Zapis rozszerzony ------------------ +Zapis zaawansowany +------------------ -Cel trasy, zazwyczaj zapisywany w postaci `Presenter:action`, może być również zapisany za pomocą tablicy, która definiuje poszczególne parametry i ich wartości domyślne: +Cel trasy, zapisywany zwykle w formacie `Presenter:akcja`, można zapisać również za pomocą tablicy definiującej poszczególne parametry i ich wartości domyślne: ```php $router->addRoute('/[/]', [ @@ -224,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Dla bardziej szczegółowej specyfikacji można użyć jeszcze bardziej rozszerzonej formy, gdzie oprócz wartości domyślnych możemy ustawić również inne właściwości parametrów, takie jak na przykład walidacyjne wyrażenie regularne (zobacz parametr `id`): +Do dokładniejszego określenia można użyć jeszcze bardziej rozbudowanej postaci, w której poza wartościami domyślnymi możemy ustawić inne właściwości parametrów, na przykład walidacyjne wyrażenie regularne (zobacz parametr `id`): ```php use Nette\Routing\Route; @@ -242,19 +242,29 @@ $router->addRoute('/[/]', [ ]); ``` -Ważne jest zauważenie, że jeśli parametry zdefiniowane w tablicy nie są wymienione w masce ścieżki, ich wartości nie można zmienić, nawet za pomocą parametrów query podanych za znakiem zapytania w URL. +Ważne, aby zauważyć, że jeśli parametry zdefiniowane w tablicy nie są wymienione w masce ścieżki, ich wartości nie da się zmienić, nawet za pomocą parametrów zapytania podanych w URL po znaku zapytania. + +Przydaje się to przy **parametrach stałych** - aby nadać konkretnej stronie krótki, łatwy do zapamiętania URL. Na przykład aby `/tos` zawsze otwierało `Article:view` z `id: 123`: + +```php +$router->addRoute('tos', [ + 'presenter' => 'Article', + 'action' => 'view', + 'id' => 123, +]); +``` Filtry i tłumaczenia -------------------- -Kody źródłowe aplikacji piszemy w języku angielskim, ale jeśli strona ma mieć polskie URL, to proste routowanie typu: +Kod źródłowy aplikacji piszemy po angielsku, ale jeśli witryna ma mieć czeskie URL, to proste routowanie w rodzaju: ```php $router->addRoute('/', 'Home:default'); ``` -będzie generować angielskie URL, takie jak `/product/123` lub `/cart`. Jeśli chcemy mieć presentery i akcje w URL reprezentowane polskimi słowami (np. `/produkt/123` lub `/koszyk`), możemy wykorzystać słownik tłumaczeń. Do jego zapisu potrzebujemy już "bardziej gadatliwej" warianty drugiego parametru: +wygeneruje angielskie URL, takie jak `/product/123` czy `/cart`. Jeśli chcemy, aby presentery i akcje w URL reprezentowane były czeskimi słowami (np. `/produkt/123` albo `/kosik`), możemy użyć słownika tłumaczeń. Aby go zapisać, potrzebujemy już "gadatliwszego" wariantu drugiego parametru: ```php use Nette\Routing\Route; @@ -263,26 +273,26 @@ $router->addRoute('/', [ 'presenter' => [ Route::Value => 'Home', Route::FilterTable => [ - // ciąg w URL => presenter + // string w URL => presenter 'produkt' => 'Product', - 'koszyk' => 'Cart', + 'kosik' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'lista' => 'list', + 'seznam' => 'list', ], ], ]); ``` -Wiele kluczy słownika tłumaczeń może prowadzić do tego samego presentera. W ten sposób tworzy się dla niego różne aliasy. Za wariant kanoniczny (czyli ten, który będzie w wygenerowanym URL) uważa się ostatni klucz. +Kilka kluczy w słowniku tłumaczeń może prowadzić do tego samego presentera. Powstają w ten sposób jego rozmaite aliasy. Ostatni klucz uznawany jest za wariant kanoniczny (czyli ten, który znajdzie się w wygenerowanym URL). -Tabelę tłumaczeń można w ten sposób użyć dla dowolnego parametru. Przy czym jeśli tłumaczenie nie istnieje, bierze się pierwotną wartość. To zachowanie możemy zmienić dodając `Route::FilterStrict => true` i trasa wtedy odrzuci URL, jeśli wartość nie jest w słowniku. +Tablicy tłumaczeń można w ten sposób użyć dla dowolnego parametru. Jeśli tłumaczenie nie istnieje, brana jest wartość pierwotna. To zachowanie możemy zmienić, dodając `Route::FilterStrict => true`, a trasa odrzuci wtedy URL, jeśli wartości nie ma w słowniku. -Oprócz słownika tłumaczeń w postaci tablicy można zastosować również własne funkcje tłumaczące. +Poza słownikiem tłumaczeń w postaci tablicy można zastosować własne funkcje tłumaczące. ```php use Nette\Routing\Route; @@ -298,15 +308,15 @@ $router->addRoute('//', [ ]); ``` -Funkcja `Route::FilterIn` konwertuje między parametrem w URL a ciągiem, który następnie jest przekazywany do presentera, funkcja `FilterOut` zapewnia konwersję w przeciwnym kierunku. +Funkcja `Route::FilterIn` konwertuje między parametrem w URL a stringiem, który przekazywany jest następnie do presentera; funkcja `FilterOut` zapewnia konwersję w kierunku odwrotnym. -Parametry `presenter`, `action` i `module` już mają predefiniowane filtry, które konwertują między stylem PascalCase resp. camelCase a kebab-case używanym w URL. Wartość domyślna parametrów zapisuje się już w przekształconej postaci, więc na przykład w przypadku presentera piszemy ``, a nie ``. +Parametry `presenter`, `action` i `module` mają już predefiniowane filtry konwertujące między stylem PascalCase czy camelCase a kebab-case używanym w URL. Wartość domyślna parametrów zapisywana jest w postaci, w jakiej przekazywana jest do aplikacji (PascalCase dla presentera i modułu, camelCase dla akcji), więc na przykład w przypadku presentera piszemy ``, a nie ``. Filtry ogólne ------------- -Oprócz filtrów przeznaczonych dla konkretnych parametrów możemy zdefiniować również filtry ogólne, które otrzymają tablicę asocjacyjną wszystkich parametrów, które mogą dowolnie modyfikować, a następnie je zwrócą. Filtry ogólne definiujemy pod kluczem `null`. +Poza filtrami przeznaczonymi dla konkretnych parametrów możemy zdefiniować również filtry ogólne, które otrzymują tablicę asocjacyjną wszystkich parametrów, mogą ją dowolnie modyfikować i następnie zwrócić. Filtry ogólne definiuje się pod pustym kluczem. ```php use Nette\Routing\Route; @@ -321,65 +331,75 @@ $router->addRoute('/', [ ]); ``` -Filtry ogólne dają możliwość modyfikacji zachowania trasy w absolutnie dowolny sposób. Możemy je użyć na przykład do modyfikacji parametrów na podstawie innych parametrów. Na przykład tłumaczenie `` i `` na podstawie aktualnej wartości parametru ``. +Filtry ogólne dają możliwość modyfikowania zachowania trasy w absolutnie dowolny sposób. Możemy użyć ich na przykład do modyfikowania parametrów na podstawie innych parametrów. Choćby do tłumaczenia `` i `` na podstawie bieżącej wartości parametru ``. + +Jeśli parametr ma zdefiniowany własny filtr i istnieje również filtr ogólny, własny `FilterIn` wykonywany jest przed ogólnym, a odwrotnie, ogólny `FilterOut` wykonywany jest przed własnym. Wewnątrz filtra ogólnego wartości parametrów `presenter` i `action` zapisane są więc odpowiednio w stylu PascalCase i camelCase. -Jeśli parametr ma zdefiniowany własny filtr i jednocześnie istnieje filtr ogólny, wykonuje się własny `FilterIn` przed ogólnym i odwrotnie ogólny `FilterOut` przed własnym. Zatem wewnątrz filtra ogólnego wartości parametrów `presenter` resp. `action` są zapisane w stylu PascalCase resp. camelCase. +Praktyczne zastosowanie tych filtrów, czyli generowanie przyjaznych SEO adresów URL, takich jak `/article/123-how-to-bake-bread`, bez modyfikowania jakichkolwiek szablonów, znajdziesz w [Ładne URL ze slugami |best-practices:pretty-urls]. -Jednokierunkowe OneWay ----------------------- +Flaga OneWay +------------ -Trasy jednokierunkowe używa się do zachowania funkcjonalności starych URL, których aplikacja już nie generuje, ale nadal akceptuje. Oznaczamy je flagą `OneWay`: +Trasy jednokierunkowe służą do utrzymania działania starych URL, których aplikacja już nie generuje, ale nadal je przyjmuje. Oznaczamy je flagą `OneWay`: ```php -// stare URL /product-info?id=123 -$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); -// nowe URL /product/123 +// stary URL /product-info?id=123 +$router->addRoute('product-info', 'Product:detail', oneWay: true); +// nowy URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -Przy dostępie do starego URL presenter automatycznie przekierowuje na nowy URL, dzięki czemu wyszukiwarki nie zaindeksują tych stron dwukrotnie (zobacz [#SEO i kanonizacja]). +Przy wejściu na stary URL presenter automatycznie przekierowuje na nowy URL, dzięki czemu wyszukiwarki nie zaindeksują tych stron dwa razy (zobacz [#SEO i kanonizacja]). -Dynamiczne routowanie z callbackami ------------------------------------ +Routing dynamiczny z callbackami +-------------------------------- -Dynamiczne routowanie z callbackami pozwala przypisać trasom bezpośrednio funkcje (callbacki), które zostaną wykonane, gdy dana ścieżka zostanie odwiedzona. Ta elastyczna funkcjonalność pozwala szybko i efektywnie tworzyć różne punkty końcowe (endpoints) dla Twojej aplikacji: +Routing dynamiczny z callbackami pozwala przypisywać trasom bezpośrednio funkcje (callbacki), które wykonują się przy wejściu na daną ścieżkę. Ta elastyczna funkcjonalność pozwala szybko i sprawnie tworzyć rozmaite endpointy dla Twojej aplikacji: ```php $router->addRoute('test', function () { - echo 'jesteś pod adresem /test'; + echo 'Jesteś pod adresem /test'; }); ``` -Możesz również zdefiniować w masce parametry, które zostaną automatycznie przekazane do Twojego callbacku: +W masce możesz też zdefiniować parametry, które automatycznie przekazywane są do Twojego callbacku: ```php $router->addRoute('', function (string $lang) { echo match ($lang) { - 'cs' => 'Witaj na czeskiej wersji naszej strony!', - 'en' => 'Welcome to the English version of our website!', + 'cs' => 'Witamy w czeskiej wersji naszej witryny!', + 'en' => 'Witamy w angielskiej wersji naszej witryny!', }; }); ``` +Poza parametrami z maski callback może otrzymać również usługi z kontenera DI. Przekazywane są na podstawie typu parametru. Ponadto parametr `$presenter` otrzymuje instancję [MicroPresenter |api:NetteModule\MicroPresenter], która obsługuje trasę: + +```php +$router->addRoute('', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) { + // ... +}); +``` + Moduły ------ -Jeśli mamy więcej tras, które należą do wspólnego [modułu |directory-structure#Presentery i szablony], wykorzystamy `withModule()`: +Jeśli mamy kilka tras należących do wspólnego [modułu |directory-structure#Presentery i szablony], używamy `withModule()`. Podany moduł dodawany jest automatycznie przed presenterem każdej trasy w grupie i całkowicie znika z URL: ```php $router = new RouteList; -$router->withModule('Forum') // następujące trasy są częścią modułu Forum - ->addRoute('rss', 'Feed:rss') // presenter będzie Forum:Feed +$router->withModule('Forum') // kolejne trasy są częścią modułu Forum + ->addRoute('rss', 'Feed:rss') // presenterem będzie Forum:Feed ->addRoute('/') - ->withModule('Admin') // następujące trasy są częścią modułu Forum:Admin + ->withModule('Admin') // kolejne trasy są częścią modułu Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Alternatywą jest użycie parametru `module`: +Alternatywą jest parametr `module`, który podobnie ustawia stały moduł i trzyma go poza URL: ```php // URL manage/dashboard/default mapuje się na presenter Admin:Dashboard @@ -388,11 +408,17 @@ $router->addRoute('manage//', [ ]); ``` +Każda nazwa presentera jest kompletna dopiero wraz ze swoim modułem, np. `Front:Admin:ProductList`. Ilekroć taka pełna nazwa trafi do parametru URL, router koduje ją według dwóch prostych reguł: każdy dwukropek `:` (separator modułu) staje się **kropką**, a każda granica słowa w nazwie PascalCase staje się **myślnikiem**. `Front:Admin:ProductList` pojawia się więc w URL jako `front.admin.product-list` i w ten sam sposób jest dekodowana z powrotem. Dlatego aplikacja modułowa, bez żadnego z powyższych narzędzi, produkuje URL pełne kropek. + +Zarówno `withModule()`, jak i parametr `module` unikają tego właśnie dlatego, że odcinają znany prefiks modułu od nazwy presentera, zanim trafi ona do URL: skoro moduł jest stały, w ogóle nie trzeba go kodować. + +Czasem chcemy, aby sam moduł się zmieniał i pojawiał w URL, sięgamy więc po `` bezpośrednio w masce. Uwaga na jeden kluczowy szczegół: **`` przechwytuje całą ścieżkę modułu** - wszystko aż do ostatniego dwukropka w nazwie presentera. Dla presentera `Shop:Admin:Product` oznacza to moduł `Shop:Admin` i presenter `Product`, a ponieważ dwukropki stają się kropkami, otrzymamy: + Subdomeny --------- -Kolekcje tras możemy dzielić według subdomen: +Kolekcje tras można podzielić według subdomen: ```php $router = new RouteList; @@ -413,20 +439,20 @@ $router->withDomain('example.%tld%') Prefiks ścieżki --------------- -Kolekcje tras możemy dzielić według ścieżki w URL: +Kolekcje tras można podzielić według ścieżki w URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // łapie URL /eshop/rss - ->addRoute('/'); // łapie URL /eshop// + ->addRoute('rss', 'Feed:rss') // pasuje do URL /eshop/rss + ->addRoute('/'); // pasuje do URL /eshop// ``` Kombinacje ---------- -Powyższe podziały możemy wzajemnie kombinować: +Powyższe grupowania można ze sobą łączyć: ```php $router = (new RouteList) @@ -446,37 +472,37 @@ $router = (new RouteList) ``` -Parametry Query ---------------- +Parametry zapytania +------------------- -Maski mogą również zawierać parametry query (parametry za znakiem zapytania w URL). Nie można im zdefiniować wyrażenia walidacyjnego, ale można zmienić nazwę, pod którą zostaną przekazane do presentera: +Maski mogą zawierać również parametry zapytania (parametry po znaku zapytania w URL). Nie da się dla nich zdefiniować wyrażenia walidacyjnego, ale można zmienić nazwę, pod jaką przekazywane są do presentera: ```php -// parametr query 'cat' chcemy w aplikacji użyć pod nazwą 'categoryId' +// chcemy używać parametru zapytania 'cat' w aplikacji pod nazwą 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Parametry Foo +Parametry foo ------------- -Teraz już idziemy głębiej. Parametry Foo to w zasadzie nienazwane parametry, które umożliwiają dopasowanie wyrażenia regularnego. Przykładem jest trasa akceptująca `/index`, `/index.html`, `/index.htm` i `/index.php`: +Teraz schodzimy głębiej. Parametry foo to w istocie parametry nienazwane, pozwalające dopasować wyrażenie regularne. Przykładem jest trasa przyjmująca `/index`, `/index.html`, `/index.htm` i `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -Można również jawnie zdefiniować ciąg, który będzie użyty przy generowaniu URL. Ciąg musi być umieszczony bezpośrednio za znakiem zapytania. Następująca trasa jest podobna do poprzedniej, ale generuje `/index.html` zamiast `/index`, ponieważ ciąg `.html` jest ustawiony jako wartość generująca: +Można też jawnie zdefiniować string, który zostanie użyty przy generowaniu URL. String trzeba umieścić bezpośrednio za znakiem zapytania. Poniższa trasa jest podobna do poprzedniej, ale generuje `/index.html` zamiast `/index`, bo jako wartość do generowania ustawiono string `.html`: ```php $router->addRoute('index', /* ... */); ``` -Włączenie do aplikacji -====================== +Integracja +========== -Aby włączyć utworzony router do aplikacji, musimy o nim powiedzieć kontenerowi DI. Najłatwiejszą drogą jest przygotowanie fabryki, która wyprodukuje obiekt routera, i poinformowanie w konfiguracji kontenera, że ma jej użyć. Powiedzmy, że w tym celu napiszemy metodę `App\Core\RouterFactory::createRouter()`: +Aby zintegrować utworzony router z aplikacją, musimy powiedzieć o nim kontenerowi DI. Najprościej przygotować fabrykę, która utworzy obiekt routera, i powiedzieć kontenerowi w konfiguracji, aby jej użył. Powiedzmy, że napiszemy w tym celu metodę `App\Core\RouterFactory::createRouter()`: ```php namespace App\Core; @@ -494,14 +520,14 @@ class RouterFactory } ``` -Do [konfiguracji |dependency-injection:services] następnie zapiszemy: +Następnie zapisujemy w [konfiguracji |dependency-injection:services]: ```neon services: - App\Core\RouterFactory::createRouter ``` -Wszelkie zależności, na przykład od bazy danych itp., zostaną przekazane do metody fabrycznej jako jej parametry za pomocą [autowiringu|dependency-injection:autowiring]: +Wszelkie zależności, na przykład od bazy danych itd., przekazywane są metodzie fabrycznej jako jej parametry za pomocą [autowiringu|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -514,22 +540,22 @@ public static function createRouter(Nette\Database\Connection $db): RouteList SimpleRouter ============ -Znacznie prostszym routerem niż kolekcja tras jest [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Użyjemy go wtedy, gdy nie mamy szczególnych wymagań co do kształtu URL, gdy nie jest dostępny `mod_rewrite` (lub jego alternatywy) lub gdy na razie nie chcemy zajmować się ładnymi URL. +O wiele prostszym routerem niż kolekcja tras jest [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Używamy go, gdy nie mamy szczególnych wymagań co do postaci URL, gdy `mod_rewrite` (albo jego alternatywy) nie jest dostępny albo gdy nie chcemy jeszcze zajmować się ładnymi URL. -Generuje adresy mniej więcej w tym kształcie: +Generuje adresy mniej więcej w takiej postaci: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Parametrem konstruktora SimpleRoutera jest domyślny presenter & akcja, na który ma kierować, jeśli otworzymy stronę bez parametrów, np. `http://example.com/`. +Parametrem konstruktora `SimpleRouter` jest domyślny presenter i akcja, czyli akcja, która ma się wykonać, jeśli otworzymy np. `http://example.com/` bez dodatkowych parametrów. ```php -// domyślnym presenterem będzie 'Home' a akcja 'default' +// domyślnym presenterem będzie 'Home', a akcją 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Zalecamy SimpleRouter bezpośrednio zdefiniować w [konfiguracji |dependency-injection:services]: +Zalecamy definiowanie SimpleRoutera bezpośrednio w [konfiguracji |dependency-injection:services]: ```neon services: @@ -540,19 +566,19 @@ services: SEO i kanonizacja ================= -Framework przyczynia się do SEO (optymalizacji dla wyszukiwarek internetowych) przez zapobieganie duplikacji treści pod różnymi URL. Jeśli do określonego celu prowadzi więcej adresów, np. `/index` i `/index.html`, framework pierwszy z nich określa jako podstawowy (kanoniczny) i pozostałe na niego przekierowuje za pomocą kodu HTTP 301. Dzięki temu wyszukiwarki nie indeksują stron dwukrotnie i nie rozdrabniają ich page rank. +Framework przyczynia się do SEO (Search Engine Optimization), zapobiegając zduplikowanej treści pod różnymi URL. Jeśli do określonego celu prowadzi kilka adresów, np. `/index` i `/index.html`, framework wyznacza pierwszy z nich jako podstawowy (kanoniczny) i przekierowuje na niego pozostałe kodem HTTP 301. Dzięki temu wyszukiwarki nie indeksują stron dwa razy i nie rozmywają ich page ranku. -Ten proces nazywa się kanonizacją. Kanonicznym URL jest ten, który generuje router, tj. pierwsza pasująca trasa w kolekcji bez flagi OneWay. Dlatego w kolekcji podajemy **podstawowe trasy jako pierwsze**. +Proces ten nazywa się kanonizacją. Kanonicznym URL jest ten wygenerowany przez router, czyli przez pierwszą pasującą trasę w kolekcji bez flagi OneWay. Dlatego w kolekcji wymieniamy **najpierw trasy podstawowe**. -Kanonizację przeprowadza presenter, więcej w rozdziale [kanonizacja |presenters#Kanonizacja]. +Kanonizację wykonuje presenter, więcej w rozdziale [kanonizacja |presenters#Kanonizacja]. HTTPS ===== -Aby móc używać protokołu HTTPS, konieczne jest jego włączenie na hostingu i prawidłowe skonfigurowanie serwera. +Aby używać protokołu HTTPS, trzeba włączyć go na hostingu i poprawnie skonfigurować serwer. -Przekierowanie całej strony na HTTPS należy ustawić na poziomie serwera, na przykład za pomocą pliku .htaccess w katalogu głównym naszej aplikacji, i to z kodem HTTP 301. Ustawienie może się różnić w zależności od hostingu i wygląda mniej więcej tak: +Przekierowanie całej witryny na HTTPS trzeba ustawić na poziomie serwera, na przykład za pomocą pliku `.htaccess` w katalogu głównym naszej aplikacji, z kodem HTTP 301. Ustawienia mogą różnić się w zależności od hostingu i wyglądają mniej więcej tak: ``` @@ -564,15 +590,15 @@ Przekierowanie całej strony na HTTPS należy ustawić na poziomie serwera, na p ``` -Router generuje URL z tym samym protokołem, z jakim została załadowana strona, więc nic więcej nie trzeba ustawiać. +Router generuje URL z tym samym protokołem, z jakim wczytano stronę, więc nic więcej nie trzeba ustawiać. -Jeśli jednak wyjątkowo potrzebujemy, aby różne trasy działały pod różnymi protokołami, podamy go w masce trasy: +Jeśli jednak wyjątkowo potrzebujemy, aby różne trasy działały pod różnymi protokołami, podajemy to w masce trasy: ```php -// Będzie generować adres z HTTP +// wygeneruje adres HTTP $router->addRoute('http://%host%//', /* ... */); -// Będzie generować adres z HTTPS +// wygeneruje adres HTTPS $router->addRoute('https://%host%//', /* ... */); ``` @@ -580,24 +606,24 @@ $router->addRoute('https://%host%//', /* ... */); Debugowanie routera =================== -Panel routingu wyświetlający się w [Tracy Bar |tracy:] jest użytecznym pomocnikiem, który wyświetla listę tras oraz parametrów, które router uzyskał z URL. +Panel routingu wyświetlany w [pasku Tracy |tracy:] to przydatny pomocnik, który pokazuje listę tras, a także parametry, które router pozyskał z URL. -Zielony pasek z symbolem ✓ reprezentuje trasę, która przetworzyła aktualny URL, niebieskim kolorem i symbolem ≈ są oznaczone trasy, które również przetworzyłyby URL, gdyby zielona ich nie wyprzedziła. Dalej widzimy aktualny presenter & akcję. +Zielony pasek z symbolem ✓ reprezentuje trasę, która obsłużyła bieżący URL; kolor niebieski i symbol ≈ oznaczają trasy, które również obsłużyłyby URL, gdyby zielona ich nie wyprzedziła. Dalej widzimy bieżący presenter i akcję. [* routing-debugger.webp *] -Jednocześnie jeśli dojdzie do nieoczekiwanego przekierowania z powodu [kanonizacji |#SEO i kanonizacja], warto spojrzeć do panelu w pasku *redirect*, gdzie dowiesz się, jak router pierwotnie zrozumiał URL i dlaczego przekierował. +Jednocześnie, jeśli dojdzie do nieoczekiwanego przekierowania z powodu [kanonizacji |#SEO i kanonizacja], warto zajrzeć do panelu w pasek *redirect*, gdzie dowiesz się, jak router pierwotnie zrozumiał URL i dlaczego przekierował. .[note] -Podczas debugowania routera zalecamy otwarcie w przeglądarce Developer Tools (Ctrl+Shift+I lub Cmd+Option+I) i w panelu Network wyłączenie cache, aby nie zapisywały się w niej przekierowania. +Przy debugowaniu routera zalecamy otworzyć w przeglądarce Developer Tools (Ctrl+Shift+I albo Cmd+Option+I) i wyłączyć cache w panelu Network, aby przekierowania nie były w nim przechowywane. Wydajność ========= -Liczba tras ma wpływ na szybkość routera. Ich liczba zdecydowanie nie powinna przekraczać kilkudziesięciu. Jeśli Twoja strona ma zbyt skomplikowaną strukturę URL, możesz napisać na miarę [#Własny router]. +Liczba tras wpływa na szybkość routera. Ich liczba zdecydowanie nie powinna przekraczać kilkudziesięciu. Jeśli Twoja witryna ma zbyt skomplikowaną strukturę URL, możesz napisać [własny router |#Własny router]. -Jeśli router nie ma żadnych zależności, na przykład od bazy danych, a jego fabryka nie przyjmuje żadnych argumentów, możemy jego skompilowaną postać zserializować bezpośrednio do kontenera DI i tym samym nieznacznie przyspieszyć aplikację. +Jeśli router nie ma zależności, na przykład od bazy danych, a jego fabryka nie przyjmuje argumentów, możemy zserializować jego skompilowaną postać bezpośrednio do kontenera DI i tym samym nieco przyspieszyć aplikację. ```neon routing: @@ -608,7 +634,7 @@ routing: Własny router ============= -Poniższe linijki są przeznaczone dla bardzo zaawansowanych użytkowników. Możesz stworzyć własny router i całkowicie naturalnie włączyć go do kolekcji tras. Router jest implementacją interfejsu [api:Nette\Routing\Router] z dwiema metodami: +Poniższe wiersze przeznaczone są dla bardzo zaawansowanych użytkowników. Możesz utworzyć własny router i naturalnie włączyć go do kolekcji tras. Router to implementacja interfejsu [api:Nette\Routing\Router] z dwiema metodami: ```php use Nette\Http\IRequest as HttpRequest; @@ -628,7 +654,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Metoda `match` przetwarza aktualne żądanie [$httpRequest |http:request], z którego można uzyskać nie tylko URL, ale i nagłówki itp., do tablicy zawierającej nazwę presentera i jego parametry. Jeśli nie potrafi przetworzyć żądania, zwraca null. Przy przetwarzaniu żądania musimy zwrócić co najmniej presenter i akcję. Nazwa presentera jest pełna i zawiera również ewentualne moduły: +Metoda `match` przetwarza bieżące żądanie [$httpRequest |http:request], z którego można pozyskać nie tylko URL, ale też nagłówki itd., w tablicę zawierającą nazwę presentera i jego parametry. Jeśli nie potrafi obsłużyć żądania, zwraca null. Przy obsłudze żądania musimy zwrócić przynajmniej presenter; akcja jest opcjonalna i przy braku podania przyjmuje wartość `default`. Nazwa presentera jest kompletna i zawiera ewentualne moduły: ```php [ @@ -637,9 +663,9 @@ Metoda `match` przetwarza aktualne żądanie [$httpRequest |http:request], z kt ] ``` -Metoda `constructUrl` odwrotnie, składa z tablicy parametrów wynikowy absolutny URL. Do tego może wykorzystać informacje z parametru [`$refUrl`|api:Nette\Http\UrlScript], który jest aktualnym URL. +Metoda `constructUrl` odwrotnie składa z tablicy parametrów wynikowy bezwzględny URL. Może wykorzystać informacje z parametru [`$refUrl`|api:Nette\Http\UrlScript], którym jest bieżący URL. -Do kolekcji tras dodasz go za pomocą `add()`: +Dodasz go do kolekcji tras metodą `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -649,16 +675,16 @@ $router->addRoute(/* ... */); ``` -Samostatné použití +Użycie samodzielne ================== -Samodzielnym użyciem rozumiemy wykorzystanie możliwości routera w aplikacji, która nie wykorzystuje Nette Application i presenterów. Dotyczy go prawie wszystko, co pokazaliśmy w tym rozdziale, z tymi różnicami: +Przez użycie samodzielne rozumiemy wykorzystanie możliwości routera w aplikacji, która nie używa Nette Application ani presenterów. Dotyczy jej niemal wszystko, co pokazaliśmy w tym rozdziale, z tymi różnicami: - dla kolekcji tras używamy klasy [api:Nette\Routing\RouteList] -- jako simple router klasy [api:Nette\Routing\SimpleRouter] -- ponieważ nie istnieje para `Presenter:action`, używamy [#Zapis rozszerzony] +- jako prostego routera klasy [api:Nette\Routing\SimpleRouter] +- ponieważ para `Presenter:akcja` nie istnieje, używamy [#Zapis zaawansowany] -Więc ponownie tworzymy metodę, która nam zbuduje router, np.: +Znów tworzymy więc metodę, która złoży nam router, np.: ```php namespace App\Core; @@ -682,21 +708,21 @@ class RouterFactory } ``` -Jeśli używasz kontenera DI, co zalecamy, ponownie dodamy metodę do konfiguracji, a następnie router wraz z żądaniem HTTP uzyskamy z kontenera: +Jeśli używasz kontenera DI, co zalecamy, dodaj metodę ponownie do konfiguracji, a następnie pozyskaj router wraz z żądaniem HTTP z kontenera: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Albo obiekty bezpośrednio wyprodukujemy: +Albo utwórz obiekty bezpośrednio: ```php $router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Teraz już pozostaje puścić router do pracy: +Teraz pozostaje już tylko pozwolić routerowi wykonać swoją pracę: ```php $params = $router->match($httpRequest); @@ -705,12 +731,12 @@ if ($params === null) { exit; } -// przetwarzamy uzyskane parametry +// przetwarzamy pozyskane parametry $controller = $params['controller']; // ... ``` -I odwrotnie użyjemy routera do zbudowania linku: +I odwrotnie, użyj routera do złożenia odnośnika: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; @@ -718,4 +744,6 @@ $url = $router->constructUrl($params, $httpRequest->getUrl()); ``` -{{composer: nette/router}} +{{composer: nette/routing}} +{{repo: nette/routing}} +{{api: https://api.nette.org/routing/}} diff --git a/application/pl/templates.texy b/application/pl/templates.texy index 496242c1d0..790c08acc3 100644 --- a/application/pl/templates.texy +++ b/application/pl/templates.texy @@ -2,15 +2,15 @@ Szablony ******** .[perex] -Nette używa systemu szablonów [Latte |latte:]. Po pierwsze dlatego, że jest to najlepiej zabezpieczony system szablonów dla PHP, a jednocześnie system najbardziej intuicyjny. Nie musisz uczyć się wielu nowych rzeczy, wystarczy znajomość PHP i kilku znaczników. +Nette używa systemu szablonów [Latte |latte:]. Latte jest używane, bo to najbezpieczniejszy system szablonów dla PHP, a zarazem najbardziej intuicyjny. Nie musisz uczyć się wiele nowego; wystarczy znajomość PHP i kilku tagów. -Jest typowe, że strona składa się z szablonu layoutu + szablonu danej akcji. Tak na przykład może wyglądać szablon layoutu, zwróć uwagę na bloki `{block}` i znacznik `{include}`: +Powszechnie strona składa się z szablonu layoutu i szablonu konkretnej akcji. Tak może wyglądać szablon layoutu; zwróć uwagę na bloki `{block}` i tag `{include}`: ```latte - {block title}Moja Aplikacja{/block} + {block title}Moja aplikacja{/block}
    ...
    @@ -20,7 +20,7 @@ Jest typowe, że strona składa się z szablonu layoutu + szablonu danej akcji. ``` -A to będzie szablon akcji: +A tak wyglądałby szablon akcji: ```latte {block title}Strona główna{/block} @@ -31,15 +31,15 @@ A to będzie szablon akcji: {/block} ``` -Definiuje on blok `content`, który zostanie wstawiony w miejsce `{include content}` w layoucie, a także redefiniuje blok `title`, którym nadpisze `{block title}` w layoucie. Spróbuj sobie wyobrazić wynik. +Definiuje on blok `content`, który wstawiany jest w layoucie w miejsce `{include content}`, a także na nowo definiuje blok `title`, który nadpisuje `{block title}` w layoucie. Spróbuj wyobrazić sobie wynik. Wyszukiwanie szablonów ---------------------- -Nie musisz w presenterach podawać, jaki szablon ma być wyrenderowany, framework sam wywnioskuje ścieżkę i oszczędzi Ci pisania. +W presenterach nie musisz podawać, który szablon ma zostać wyrenderowany; framework sam wywnioskuje ścieżkę, oszczędzając Ci pisania. -Jeśli używasz struktury katalogów, gdzie każdy presenter ma własny katalog, po prostu umieść szablon w tym katalogu pod nazwą akcji (resp. view), tj. dla akcji `default` użyj szablonu `default.latte`: +Jeśli używasz struktury katalogów, w której każdy presenter ma własny katalog, po prostu umieść szablon w tym katalogu pod nazwą akcji (czyli widoku). Na przykład dla akcji `default` użyj szablonu `default.latte`: /--pre app/ @@ -49,34 +49,34 @@ app/ └── default.latte \-- -Jeśli używasz struktury, gdzie presentery są razem w jednym katalogu, a szablony w folderze `templates`, zapisz go albo w pliku `..latte` albo `/.latte`: +Jeśli używasz struktury, w której presentery leżą razem w jednym katalogu, a szablony w folderze `templates`, zapisz go albo w pliku `..latte`, albo `/.latte`: /--pre app/ └── Presenters/ ├── HomePresenter.php └── templates/ - ├── Home.default.latte ← 1. wariant - └── Home/ - └── default.latte ← 2. wariant + ├── Home/ + │ └── default.latte ← 1. wariant + └── Home.default.latte ← 2. wariant \-- -Katalog `templates` może być umieszczony również o poziom wyżej, tj. na tym samym poziomie, co katalog z klasami presenterów. +Katalog `templates` może leżeć też o poziom wyżej, czyli na tym samym poziomie co katalog z klasami presenterów. -Jeśli szablon nie zostanie znaleziony, presenter odpowie [błędem 404 - page not found |presenters#Błąd 404 i spółka]. +Jeśli szablon się nie znajdzie, presenter odpowiada [błędem 404 - strona nie znaleziona |presenters#Błąd 404 itd.]. -View zmienisz za pomocą `$this->setView('innyView')`. Można również bezpośrednio określić plik z szablonem za pomocą `$this->template->setFile('/path/to/template.latte')`. +Widok możesz zmienić przez `$this->setView('otherView')`. Można też bezpośrednio wskazać plik szablonu przez `$this->template->setFile('/path/to/template.latte')`. .[note] -Pliki, w których wyszukiwane są szablony, można zmienić przez nadpisanie metody [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], która zwraca tablicę możliwych nazw plików. +Pliki, w których wyszukiwane są szablony, można zmienić, nadpisując metodę [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], która zwraca tablicę możliwych nazw plików. Wyszukiwanie szablonu layoutu ----------------------------- -Nette również automatycznie wyszukuje plik z layoutem. +Nette automatycznie wyszukuje również plik layoutu. -Jeśli używasz struktury katalogów, gdzie każdy presenter ma własny katalog, umieść layout albo w folderze z presenterem, jeśli jest specyficzny tylko dla niego, albo o poziom wyżej, jeśli jest wspólny dla wielu presenterów: +Jeśli używasz struktury katalogów, w której każdy presenter ma własny katalog, umieść layout albo w folderze z presenterem, jeśli jest przeznaczony tylko dla niego, albo o poziom wyżej, jeśli jest wspólny dla kilku presenterów: /--pre app/ @@ -88,7 +88,7 @@ app/ └── default.latte \-- -Jeśli używasz struktury, gdzie presentery są razem w jednym katalogu, a szablony w folderze `templates`, layout będzie oczekiwany w tych miejscach: +Jeśli używasz struktury, w której presentery zgrupowane są w jednym katalogu, a szablony w folderze `templates`, layout będzie oczekiwany w tych miejscach: /--pre app/ @@ -96,33 +96,66 @@ app/ ├── HomePresenter.php └── templates/ ├── @layout.latte ← wspólny layout - ├── Home.@layout.latte ← tylko dla Home, 1. wariant - └── Home/ - └── @layout.latte ← tylko dla Home, 2. wariant + ├── Home/ + │ └── @layout.latte ← tylko dla Home, 1. wariant + └── Home.@layout.latte ← tylko dla Home, 2. wariant \-- -Jeśli presenter znajduje się w module, będzie wyszukiwany również o kolejne poziomy katalogów wyżej, zgodnie z zagnieżdżeniem modułu. +Jeśli presenter leży w module, wyszukiwanie postępuje też wyżej po poziomach katalogów, zgodnie z zagnieżdżeniem modułów. -Nazwę layoutu można zmienić za pomocą `$this->setLayout('layoutAdmin')`, a wtedy będzie oczekiwany w pliku `@layoutAdmin.latte`. Można również bezpośrednio określić plik z szablonem layoutu za pomocą `$this->setLayout('/path/to/template.latte')`. +Nazwę layoutu można zmienić przez `$this->setLayout('layoutAdmin')`, a wtedy będzie oczekiwany w pliku `@layoutAdmin.latte`. Możesz też bezpośrednio wskazać plik szablonu layoutu przez `$this->setLayout('/path/to/template.latte')`. -Za pomocą `$this->setLayout(false)` lub znacznika `{layout none}` wewnątrz szablonu wyszukiwanie layoutu zostanie wyłączone. +Użycie `$this->setLayout(false)` albo tagu `{layout none}` wewnątrz szablonu wyłącza wyszukiwanie layoutu. .[note] -Pliki, w których wyszukiwane są szablony layoutu, można zmienić przez nadpisanie metody [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], która zwraca tablicę możliwych nazw plików. +Pliki, w których wyszukiwane są szablony layoutu, można zmienić, nadpisując metodę [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], która zwraca tablicę możliwych nazw plików. -Zmienne w szablonie -------------------- +Zmienne szablonu +---------------- -Zmienne do szablonu przekazujemy tak, że zapisujemy je do `$this->template`, a potem mamy je dostępne w szablonie jako zmienne lokalne: +Zmienne przekazuje się do szablonów, zapisując je do `$this->template`. Stają się wtedy dostępne w szablonie jako zmienne lokalne: ```php $this->template->article = $this->articles->getById($id); ``` -W ten prosty sposób możemy przekazać do szablonów dowolne zmienne. Jednak przy tworzeniu solidnych aplikacji bywa bardziej użyteczne ograniczenie się. Na przykład tak, że jawnie zdefiniujemy wykaz zmiennych, których oczekuje szablon, oraz ich typów. Dzięki temu PHP będzie mógł kontrolować typy, IDE poprawnie podpowiadać, a analiza statyczna wykrywać błędy. +Aby wartość właściwości była automatycznie przekazywana do szablonu jako zmienna, oznacz ją atrybutem `#[TemplateVariable]` i widocznością public: .{data-version:3.2.9} + +```php +use Nette\Application\Attributes\TemplateVariable; + +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + #[TemplateVariable] + public string $siteName = 'Mój blog'; +} +``` + +Jeśli przekażesz do szablonu zmienną o tej samej nazwie, `#[TemplateVariable]` jej nie nadpisze. + + +Zmienne domyślne +---------------- + +Presentery i komponenty automatycznie przekazują do szablonów kilka przydatnych zmiennych: + +- `$basePath` to bezwzględna ścieżka URL do katalogu głównego (np. `/eshop`) +- `$baseUrl` to bezwzględny URL katalogu głównego (np. `http://localhost/eshop`) +- `$user` to obiekt [reprezentujący użytkownika |security:authentication] +- `$presenter` to bieżący presenter +- `$control` to bieżący komponent albo presenter +- `$flashes` to tablica [wiadomości |presenters#Wiadomości flash] wysłanych funkcją `flashMessage()` + +Jeśli używasz własnej klasy szablonu, zmienne te są przekazywane, o ile utworzysz dla nich właściwość. + + +Szablony bezpieczne typowo +-------------------------- -A jak taki wykaz zdefiniujemy? Po prostu w postaci klasy i jej właściwości. Nazwiemy ją podobnie jak presenter, tylko z `Template` na końcu: +Przy tworzeniu solidnych aplikacji przydaje się jawne określenie, jakich zmiennych oczekuje szablon i jakich są typów. Daje to kontrolę typów w PHP, inteligentne podpowiedzi w IDE i pozwala analizie statycznej wyłapywać błędy. + +Jak zdefiniować taką listę? Po prostu jako klasę z właściwościami reprezentującymi zmienne szablonu. Nazwij ją podobnie jak presenter, tylko z `Template` na końcu: ```php /** @@ -141,22 +174,24 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Obiekt `$this->template` w presenterze będzie teraz instancją klasy `ArticleTemplate`. Więc PHP podczas zapisu będzie kontrolował zadeklarowane typy. A począwszy od wersji PHP 8.2 powiadomi również o zapisie do nieistniejącej zmiennej, w poprzednich wersjach tego samego można osiągnąć używając traity [Nette\SmartObject |utils:smartobject]. +Obiekt `$this->template` w presenterze będzie teraz instancją klasy `ArticleTemplate`. PHP będzie więc przy zapisie sprawdzać zadeklarowane typy. + +Nette wybiera klasę szablonu automatycznie. Najpierw szuka klasy o nazwie `Template`, np. `ArticleEditTemplate` dla akcji `edit`, i dopiero gdy jej nie ma, sięga po `Template`. -Adnotacja `@property-read` jest przeznaczona dla IDE i analizy statycznej, dzięki niej będzie działać podpowiadanie, zobacz "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Adnotacja `@property-read` przeznaczona jest dla IDE i analizy statycznej, umożliwia uzupełnianie kodu, zobacz "PhpStorm i uzupełnianie kodu dla $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Luksusu podpowiadania możesz sobie pozwolić również w szablonach, wystarczy zainstalować w PhpStorm wtyczkę dla Latte i podać na początku szablonu nazwę klasy, więcej w artykule "Latte: jak na typowy system":https://blog.nette.org/pl/latte-how-to-use-type-system: +Uzupełniania kodu możesz używać również bezpośrednio w szablonach. Wystarczy zainstalować plugin Latte do PhpStorm i podać na początku szablonu nazwę klasy parametrów szablonu, więcej w rozdziale [Latte: system typów |latte:type-system]: ```latte {templateType App\Presentation\Article\ArticleTemplate} ... ``` -Tak działają również szablony w komponentach, wystarczy tylko przestrzegać konwencji nazewnictwa i dla komponentu np. `FifteenControl` utworzyć klasę szablonu `FifteenTemplate`. +To samo dotyczy komponentów. Wystarczy trzymać się konwencji nazewniczej i dla komponentu w rodzaju `FifteenControl` utworzyć klasę parametrów `FifteenTemplate`. -Jeśli potrzebujesz utworzyć `$template` jako instancję innej klasy, wykorzystaj metodę `createTemplate()`: +Jeśli potrzebujesz użyć innej klasy parametrów, skorzystaj z metody `createTemplate()`: ```php public function renderDefault(): void @@ -168,58 +203,100 @@ public function renderDefault(): void } ``` +.{data-version:3.3.0} +Jeśli potrzebujesz wpłynąć na to, jak szablon jest finalizowany przed renderowaniem, na przykład aby dodać zmienne wspólne dla wszystkich akcji, możesz nadpisać w presenterze metodę `completeTemplate()`. Wywoływana jest tuż przed wyrenderowaniem szablonu: -Zmienne domyślne ----------------- - -Presentery i komponenty przekazują do szablonów kilka użytecznych zmiennych automatycznie: - -- `$basePath` to absolutna ścieżka URL do katalogu głównego (np. `/eshop`) -- `$baseUrl` to absolutny URL do katalogu głównego (np. `http://localhost/eshop`) -- `$user` to obiekt [reprezentujący użytkownika |security:authentication] -- `$presenter` to aktualny presenter -- `$control` to aktualny komponent lub presenter -- `$flashes` tablica [wiadomości |presenters#Wiadomości flash] wysłanych funkcją `flashMessage()` - -Jeśli używasz własnej klasy szablonu, te zmienne zostaną przekazane, jeśli utworzysz dla nich właściwość. +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'Mój blog'; +} +``` -Tworzenie linków ----------------- +Tworzenie odnośników +-------------------- -W szablonie tworzy się linki do innych presenterów & akcji w ten sposób: +W szablonie odnośniki do innych presenterów i akcji tworzy się tak: ```latte szczegóły produktu ``` -Atrybut `n:href` jest bardzo przydatny dla znaczników HTML ``. Jeśli chcemy link wypisać gdzie indziej, na przykład w tekście, użyjemy `{link}`: +Atrybut `n:href` jest bardzo poręczny dla tagów HTML ``. Jeśli chcemy wypisać odnośnik gdzie indziej, na przykład w tekście, użyjemy `{link}`: ```latte -Adres to: {link Home:default} +URL to: {link Home:default} ``` -Więcej informacji znajdziesz w rozdziale [Tworzenie linków URL|creating-links]. +Więcej informacji znajdziesz w rozdziale [Tworzenie odnośników URL|creating-links]. -Własne filtry, znaczniki itp. ------------------------------ +Własne filtry, tagi itd. +------------------------ -System szablonów Latte można rozszerzyć o własne filtry, funkcje, znaczniki itp. Można to zrobić bezpośrednio w metodzie `render` lub `beforeRender()`: +System szablonów Latte można rozszerzać o własne filtry, funkcje, tagi i inne elementy. Dostępne są trzy podejścia, od szybkich rozwiązań doraźnych po wzorce architektoniczne dla całych aplikacji. + +**Doraźnie w metodach presentera** + +Najszybszym podejściem jest dodawanie filtrów albo funkcji bezpośrednio w kodzie presentera lub komponentu. W presenterach dobrze nadają się do tego metody `beforeRender()` albo `render()`: ```php -public function beforeRender(): void +protected function beforeRender(): void { // dodanie filtra - $this->template->addFilter('foo', /* ... */); + $this->template->addFilter('money', fn($val) => '$' . number_format($val, 2)); + + // dodanie funkcji + $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); +} +``` + +W szablonie: + +```latte +

    Cena: {$price|money}

    - // lub konfigurujemy bezpośrednio obiekt Latte\Engine +{if isWeekend($now)} ... {/if} +``` + +Przy bardziej złożonej logice możesz skonfigurować bezpośrednio obiekt `Latte\Engine`: + +```php +protected function beforeRender(): void +{ $latte = $this->template->getLatte(); - $latte->addFilterLoader(/* ... */); + $latte->setFeature(Latte\Feature::MigrationWarnings); } ``` -Latte w wersji 3 oferuje bardziej zaawansowany sposób, a mianowicie utworzenie sobie [extension |latte:extending-latte#Latte Extension] dla każdego projektu internetowego. Przykładowy fragment takiej klasy: +**Za pomocą atrybutów** + +Eleganckim podejściem jest zdefiniowanie filtrów i funkcji jako metod bezpośrednio w [klasie parametrów szablonu|#Szablony bezpieczne typowo] presentera albo komponentu, oznaczonych atrybutami: + +```php +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + #[Latte\Attributes\TemplateFilter] + public function money(float $val): string + { + return '$' . number_format($val, 2); + } + + #[Latte\Attributes\TemplateFunction] + public function isWeekend(DateTimeInterface $date): bool + { + return $date->format('N') >= 6; + } +} +``` + +Latte automatycznie odnajduje i rejestruje metody oznaczone tymi atrybutami. Nazwa filtra albo funkcji w szablonach odpowiada nazwie metody. Metody te muszą być publiczne. + +**Globalnie za pomocą rozszerzeń** + +Poprzednie podejścia nadają się do filtrów i funkcji potrzebnych tylko w konkretnych presenterach albo komponentach, a nie w całej aplikacji. Dla całej aplikacji najlepiej sprawdza się utworzenie [rozszerzenia |latte:extending-latte#Latte Extension]. Klasa ta centralizuje wszystkie rozszerzenia Latte w Twoim projekcie. Krótki przykład: ```php namespace App\Presentation\Accessory; @@ -251,11 +328,16 @@ final class LatteExtension extends Latte\Extension ]; } + private function filterTimeAgoInWords(DateTimeInterface $time): string + { + // ... + } + // ... } ``` -Zarejestrujemy ją za pomocą [konfiguracji |configuration#Szablony Latte]: +Rozszerzenie zarejestrujesz przez [konfigurację |configuration#Szablony Latte]: ```neon latte: @@ -263,13 +345,27 @@ latte: - App\Presentation\Accessory\LatteExtension ``` +Rozszerzenia dają kilka korzyści: wsparcie dla wstrzykiwania zależności, dostęp do warstwy modelu Twojej aplikacji i centralne zarządzanie wszystkimi rozszerzeniami. Obsługują też własne tagi, providery, compiler passy i inne rzeczy. + + +Ustawienie wszystkich szablonów +------------------------------- + +Usługa `TemplateFactory`, która tworzy wszystkie szablony, oferuje publiczną tablicę callbacków `$onCreate`. Wywoływane są one przy każdym utworzeniu dowolnego szablonu, dzięki czemu z jednego miejsca ustawisz filtry, funkcje albo zmienne dla wszystkich szablonów w aplikacji. Każdy callback otrzymuje nowo utworzony szablon. Każ sobie [wstrzyknąć |dependency-injection:passing-dependencies] usługę `TemplateFactory` i zarejestruj callbacki, np. przy starcie aplikacji: + +```php +$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void { + $template->addFilter('money', fn($val) => '$' . number_format($val, 2)); +}; +``` + Tłumaczenie ----------- -Jeśli programujesz aplikację wielojęzyczną, prawdopodobnie będziesz potrzebować niektóre teksty w szablonie wypisać w różnych językach. Nette Framework w tym celu definiuje interfejs do tłumaczenia [api:Nette\Localization\Translator], który ma jedyną metodę `translate()`. Przyjmuje ona wiadomość `$message`, co zazwyczaj jest ciągiem znaków, oraz dowolne inne parametry. Zadaniem jest zwrócenie przetłumaczonego ciągu. W Nette nie ma żadnej domyślnej implementacji, możesz wybrać według swoich potrzeb spośród kilku gotowych rozwiązań, które znajdziesz na [Componette |https://componette.org/search/localization]. W ich dokumentacji dowiesz się, jak konfigurować translator. +Jeśli programujesz aplikację wielojęzyczną, prawdopodobnie będziesz potrzebować wypisywać w szablonie niektóre teksty w różnych językach. Nette Framework definiuje w tym celu interfejs tłumaczenia [api:Nette\Localization\Translator], który ma jedną metodę `translate()`. Przyjmuje ona komunikat `$message`, którym zwykle jest string, oraz dowolne inne parametry. Zadaniem jest zwrócenie przetłumaczonego stringa. Nette nie ma domyślnej implementacji; możesz wybrać z kilku gotowych rozwiązań dostępnych na [Componette |https://componette.org/search/localization] według swoich potrzeb. Ich dokumentacja wyjaśnia, jak skonfigurować translator. -Szablonom można ustawić translator, który sobie [przekażemy |dependency-injection:passing-dependencies], metodą `setTranslator()`: +Szablonom można ustawić translator, który [otrzymamy wstrzyknięty |dependency-injection:passing-dependencies], metodą `setTranslator()`: ```php protected function beforeRender(): void @@ -279,7 +375,7 @@ protected function beforeRender(): void } ``` -Translator alternatywnie można ustawić za pomocą [konfiguracji |configuration#Szablony Latte]: +Alternatywnie translator można ustawić przez [konfigurację |configuration#Szablony Latte]: ```neon latte: @@ -287,7 +383,7 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Następnie można używać translatora na przykład jako filtra `|translate`, w tym z dodatkowymi parametrami, które zostaną przekazane metodzie `translate()` (zobacz `foo, bar`): +Translatora można potem używać na przykład jako filtra `|translate`, wraz z dodatkowymi parametrami przekazywanymi do metody `translate()` (zobacz `foo, bar`): ```latte
    {='Koszyk'|translate} @@ -295,7 +391,7 @@ Następnie można używać translatora na przykład jako filtra `|translate`, w {$item|translate, foo, bar} ``` -Lub jako znacznika z podkreśleniem: +Albo jako tagu z podkreśleniem: ```latte {_'Koszyk'} @@ -303,14 +399,14 @@ Lub jako znacznika z podkreśleniem: {_$item, foo, bar} ``` -Do tłumaczenia fragmentu szablonu istnieje parzysty znacznik `{translate}` (od Latte 2.11, wcześniej używano znacznika `{_}`): +Do przetłumaczenia fragmentu szablonu służy tag parzysty `{translate}` (od Latte 2.11, wcześniej używano tagu `{_}`): ```latte -{translate}Zamówienie{/translate} -{translate foo, bar}Zamówienie{/translate} +{translate}Zamów{/translate} +{translate foo, bar}Zamów{/translate} ``` -Translator standardowo jest wywoływany w czasie rzeczywistym podczas renderowania szablonu. Latte w wersji 3 jednak potrafi wszystkie statyczne teksty tłumaczyć już podczas kompilacji szablonu. Tym samym oszczędza się wydajność, ponieważ każdy ciąg jest tłumaczony tylko raz, a wynikowe tłumaczenie jest zapisywane w skompilowanej postaci. W katalogu z cache powstaje więc więcej skompilowanych wersji szablonu, jedna dla każdego języka. Do tego wystarczy tylko podać język jako drugi parametr: +Translator wywoływany jest normalnie w czasie działania, przy renderowaniu szablonu. Latte w wersji 3 potrafi jednak przetłumaczyć wszystkie teksty statyczne już podczas kompilacji szablonu. Oszczędza to wydajność, bo każdy string tłumaczony jest tylko raz, a powstałe tłumaczenie zapisywane jest do skompilowanej postaci. W katalogu cache powstaje wtedy kilka skompilowanych wersji szablonu, po jednej na język. Wystarczy do tego podać język jako drugi parametr: ```php protected function beforeRender(): void @@ -320,4 +416,4 @@ protected function beforeRender(): void } ``` -Tekstem statycznym jest na przykład `{_'hello'}` lub `{translate}hello{/translate}`. Teksty niestatyczne, jak na przykład `{_$foo}`, nadal będą tłumaczone w czasie rzeczywistym. +Przez tekst statyczny rozumiemy na przykład `{_'hello'}` albo `{translate}hello{/translate}`. Teksty niestatyczne, jak `{_$foo}`, nadal będą tłumaczone w czasie działania. diff --git a/application/pl/upgrading.texy b/application/pl/upgrading.texy new file mode 100644 index 0000000000..e7ea4851fa --- /dev/null +++ b/application/pl/upgrading.texy @@ -0,0 +1,47 @@ +Aktualizacja +************ + + +Aktualizacja do wersji 3.0 +========================== + +Nette 3.0 dodaje deklaracje typów do parametrów metod i wartości zwracanych. Jeśli nadpisujesz taką metodę w klasie dziedziczącej po Nette (na przykład w presenterze albo komponencie), musisz dodać te same deklaracje typów, w przeciwnym razie PHP zgłosi błąd "Declaration must be compatible". + +Interfejs `Nette\Application\IRouter` uległ zmianie. Metoda `match()` zwraca teraz tablicę parametrów zamiast obiektu `Nette\Application\Request`, a `constructUrl()` taką tablicę przyjmuje. + +Nette sprawdza teraz, czy każdy sygnał wysyłany jest z tego samego pochodzenia (czyli z tej samej domeny i subdomeny). Ta polityka same-origin to kluczowy mechanizm bezpieczeństwa, który pomaga ograniczyć możliwe wektory ataku. Jeśli chcesz dopuścić inne pochodzenie, dodaj do metody obsługującej adnotację `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXy(): void +{ +} +``` + +Dotyczy to również wysyłania formularzy. Jeśli chcesz dopuścić wysyłanie z innego pochodzenia, zrób to tak: + +```php +$form = new Nette\Application\UI\Form; +$form->allowCrossOrigin(); +``` + +Konstruktor `Nette\ComponentModel\Component` nie był używany od lat i w wersji 3.0 został usunięty. To złamanie zgodności wstecznej: jeśli w komponencie albo presenterze dziedziczącym po `Nette\Application\UI\Presenter` wywołujesz konstruktor rodzica, musisz to wywołanie usunąć. + + +Aktualizacja do wersji 2.4 +========================== + +- `Route` i `SimpleRouter` generują teraz ten sam schemat HTTP/HTTPS, którym otwarto stronę. Trasę wymagającą konkretnego protokołu można zdefiniować wraz ze schematem, np. `Route('http://domain.cz/')`. +- Dla parametrów metod render/action typu bool (czyli z wartością domyślną true albo false) oraz dla parametrów trwałych rozróżniane są teraz `false` i `null`. Jeśli parametru nie ma w URL, jego wartością jest teraz `null` (wcześniej `false`). +- Klasa zwracana przez `Presenter::getReflection()` nie jest już potomkiem `Nette\Reflection\ClassType`, a `getReflection()->getMethod()` nie jest już potomkiem `Nette\Reflection\Method`. +- Flaga `SECURED` i `Route::$defaultFlags` są przestarzałe. + + +Aktualizacja do wersji 2.3 +========================== + +- trasy i nazwy presenterów **rozróżniają wielkość liter**. Nette ostrzega, gdy użyjesz błędnej wielkości liter w nazwie presentera; ze względów wydajnościowych maska Route nie jest sprawdzana, więc zweryfikuj ją ręcznie. +- `Route::addStyle()` i `Route::setStyleProperty()` są przestarzałe i wywołują teraz `E_USER_DEPRECATED`. +- rozszerzenie szablonów `.phtml` i stara składnia odnośników nie są już obsługiwane. diff --git a/application/ru/@home.texy b/application/ru/@home.texy index eee8a946c1..701ddd7ee1 100644 --- a/application/ru/@home.texy +++ b/application/ru/@home.texy @@ -2,13 +2,13 @@ Nette Application ***************** .[perex] -Nette Application является ядром фреймворка Nette, предоставляя мощные инструменты для создания современных веб-приложений. Оно предлагает ряд исключительных возможностей, которые значительно упрощают разработку и повышают безопасность и поддерживаемость кода. +Nette Application - ядро фреймворка Nette, дающее мощные инструменты для создания современных веб-приложений. Оно предлагает ряд исключительных возможностей, которые заметно упрощают разработку и повышают безопасность и поддерживаемость кода. Установка --------- -Скачать и установить библиотеку можно с помощью [Composer|best-practices:composer]: +Скачайте и установите библиотеку с помощью [Composer|best-practices:composer]: ```shell composer require nette/application @@ -18,68 +18,68 @@ composer require nette/application Почему стоит выбрать Nette Application? --------------------------------------- -Nette всегда был пионером в области веб-технологий. +Nette всегда был первопроходцем в веб-технологиях. -**Двунаправленный маршрутизатор:** Nette обладает продвинутой системой маршрутизации, уникальной своей двунаправленностью — она не только преобразует URL в действия (actions) приложения, но и способна генерировать URL-адреса в обратную сторону. Это означает, что: -- Вы можете в любое время изменить структуру URL всего приложения без необходимости редактировать шаблоны +**Двунаправленный маршрутизатор:** в Nette есть продвинутая система маршрутизации, уникальная своей двунаправленностью: она не только преобразует URL в действия приложения, но и умеет порождать URL в обратную сторону. Это значит: +- Вы можете в любой момент изменить структуру URL всего приложения, не трогая шаблоны - URL автоматически канонизируются, что улучшает SEO -- Маршрутизация определяется в одном месте, а не разбросана по аннотациям +- Маршрутизация задаётся в одном месте, а не разбросана по аннотациям -**Компоненты и сигналы:** Встроенная компонентная система, вдохновленная Delphi и React.js, является совершенно уникальной среди PHP-фреймворков: -- Позволяет создавать повторно используемые элементы UI +**Компоненты и сигналы:** встроенная система компонентов, вдохновлённая Delphi и React.js, уникальна среди PHP-фреймворков: +- Позволяет создавать переиспользуемые элементы интерфейса - Поддерживает иерархическую композицию компонентов -- Предлагает элегантную обработку AJAX-запросов с помощью сигналов +- Предлагает изящную обработку AJAX-запросов через сигналы - Богатая библиотека готовых компонентов на [Componette](https://componette.org) -**AJAX и сниппеты:** Nette представил революционный способ работы с AJAX еще в 2009 году, задолго до появления аналогичных решений, таких как Hotwire для Ruby on Rails или Symfony UX Turbo: -- Сниппеты позволяют обновлять только части страницы без необходимости писать JavaScript -- Автоматическая интеграция с компонентной системой -- Умная инвалидация частей страниц -- Минимальное количество передаваемых данных +**AJAX и сниппеты:** Nette представил революционный способ работы с AJAX ещё в 2009 году, задолго до похожих решений вроде Hotwire для Ruby on Rails или Symfony UX Turbo: +- Сниппеты позволяют обновлять только части страницы, не требуя писать JavaScript +- Автоматическая интеграция с системой компонентов +- Умная инвалидация участков страницы +- Минимальный объём передаваемых данных -**Интуитивные шаблоны [Latte|latte:]:** Самая безопасная система шаблонов для PHP с расширенными функциями: +**Интуитивные шаблоны [Latte|latte:]:** самая безопасная система шаблонов для PHP с продвинутыми возможностями: - Автоматическая защита от XSS с контекстно-зависимым экранированием -- Расширяемость с помощью пользовательских фильтров, функций и тегов +- Расширяемость собственными фильтрами, функциями и тегами - Наследование шаблонов и сниппеты для AJAX - Отличная поддержка PHP 8.x с системой типов **Dependency Injection:** Nette полностью использует Dependency Injection: - Автоматическая передача зависимостей (autowiring) -- Конфигурация с помощью понятного формата NEON +- Настройка в наглядном формате NEON - Поддержка фабрик компонентов -Основные преимущества ---------------------- +Главные преимущества +-------------------- -- **Безопасность**: Автоматическая защита от [уязвимостей|nette:vulnerability-protection], таких как XSS, CSRF и т. д. -- **Продуктивность**: Меньше кода, больше функций благодаря умному дизайну +- **Безопасность**: автоматическая защита от [уязвимостей|nette:vulnerability-protection] вроде XSS, CSRF и других +- **Продуктивность**: меньше писать, больше возможностей благодаря продуманной архитектуре - **Отладка**: [отладчик Tracy|tracy:] с панелью маршрутизации -- **Производительность**: Умный кеш, ленивая загрузка компонентов -- **Гибкость**: Легкое изменение URL даже после завершения приложения -- **Компоненты**: Уникальная система повторно используемых элементов UI -- **Современность**: Полная поддержка PHP 8.4+ и системы типов +- **Производительность**: умный кеш, ленивая загрузка компонентов +- **Гибкость**: лёгкое изменение URL даже после завершения приложения +- **Компоненты**: уникальная система переиспользуемых элементов интерфейса +- **Современность**: полная поддержка PHP 8.3+ и системы типов -Начало работы -------------- +Первые шаги +----------- -1. [Как работают приложения? |how-it-works] - Понимание базовой архитектуры -2. [Презентеры |presenters] - Работа с презентерами и действиями -3. [Шаблоны |templates] - Создание шаблонов в Latte -4. [Маршрутизация |routing] - Конфигурация URL-адресов -5. [Интерактивные компоненты |components] - Использование компонентной системы +1. [Как работают приложения? |how-it-works] - понимание базовой архитектуры +2. [Презентеры |presenters] - работа с презентерами и действиями +3. [Шаблоны |templates] - создание шаблонов на Latte +4. [Маршрутизация |routing] - настройка URL-адресов +5. [Интерактивные компоненты |components] - использование системы компонентов Совместимость с PHP ------------------- -| версия | совместим с PHP -|-----------|------------------- -| Nette Application 4.0 | PHP 8.1 – 8.4 -| Nette Application 3.2 | PHP 8.1 – 8.4 -| Nette Application 3.1 | PHP 7.2 – 8.3 -| Nette Application 3.0 | PHP 7.1 – 8.0 -| Nette Application 2.4 | PHP 5.6 – 8.0 +| версия | совместимость с PHP +|-----------------------|------------------- +| Nette Application 3.3 | PHP 8.3 - 8.5 +| Nette Application 3.2 | PHP 8.1 - 8.5 +| Nette Application 3.1 | PHP 7.2 - 8.3 +| Nette Application 3.0 | PHP 7.1 - 8.0 +| Nette Application 2.4 | PHP 5.6 - 8.0 -Действительно для последних патч-версий. +Относится к последним патч-версиям. diff --git a/application/ru/@left-menu.texy b/application/ru/@left-menu.texy index bcf2cfd383..dadb55bbc3 100644 --- a/application/ru/@left-menu.texy +++ b/application/ru/@left-menu.texy @@ -1,22 +1,24 @@ Nette Application ***************** +- [Обзор |@home] - [Как работают приложения? |how-it-works] -- [Bootstrapping] -- [Презентеры |presenters] -- [Шаблоны |templates] +- [Bootstrapping|bootstrapping] +- [Презентеры|presenters] +- [Шаблоны|templates] - [Структура каталогов |directory-structure] -- [Маршрутизация |routing] +- [Маршрутизация|routing] - [Создание URL-ссылок |creating-links] - [Интерактивные компоненты |components] - [AJAX и сниппеты |ajax] - [Multiplier |multiplier] -- [Конфигурация |configuration] +- [Конфигурация|configuration] +- [Обновление|upgrading] -Дополнительное чтение -********************* -- [Зачем использовать Nette? |www:10-reasons-why-nette] +Дополнительные материалы +************************ +- [Зачем использовать Nette?|www:10-reasons-why-nette] - [Установка |nette:installation] -- [Пишем первое приложение! |quickstart:] -- [Руководства и лучшие практики |best-practices:] +- [Создайте своё первое приложение! |quickstart:] +- [Лучшие практики |best-practices:] - [Устранение неполадок |nette:troubleshooting] diff --git a/application/ru/ajax.texy b/application/ru/ajax.texy index 533d8ed638..a61b4aa5a7 100644 --- a/application/ru/ajax.texy +++ b/application/ru/ajax.texy @@ -3,9 +3,9 @@ AJAX и сниппеты
    -В эпоху современных веб-приложений, где функциональность часто распределяется между сервером и браузером, AJAX является необходимым связующим элементом. Какие возможности предлагает нам Nette Framework в этой области? -- отправка частей шаблона, так называемых сниппетов -- передача переменных между PHP и JavaScript +В эпоху современных веб-приложений, где функциональность часто распределена между сервером и браузером, AJAX служит незаменимым связующим звеном. Какие возможности предлагает в этой области Nette Framework? +- отправку частей шаблона, называемых сниппетами +- передачу переменных между PHP и JavaScript - инструменты для отладки AJAX-запросов
    @@ -14,9 +14,9 @@ AJAX и сниппеты AJAX-запрос =========== -AJAX-запрос в принципе не отличается от классического HTTP-запроса. Вызывается презентер (presenter) с определенными параметрами. И презентер решает, как реагировать на запрос — он может вернуть данные в формате JSON, отправить часть HTML-кода, XML-документ и т. д. +AJAX-запрос принципиально не отличается от обычного HTTP-запроса. Вызывается презентер с определёнными параметрами. Как ответить на запрос, решает сам презентер: он может вернуть данные в формате JSON, отправить часть HTML-кода, XML-документ и так далее. -На стороне браузера мы инициализируем AJAX-запрос с помощью функции `fetch()`: +На стороне браузера мы инициируем AJAX-запрос функцией `fetch()`: ```js fetch(url, { @@ -24,22 +24,22 @@ fetch(url, { }) .then(response => response.json()) .then(payload => { - // обработка ответа + // обрабатываем ответ }); ``` -На стороне сервера мы распознаем AJAX-запрос с помощью метода `$httpRequest->isAjax()` сервиса [инкапсулирующего HTTP-запрос |http:request]. Для обнаружения используется HTTP-заголовок `X-Requested-With`, поэтому важно его отправлять. В рамках презентера можно использовать метод `$this->isAjax()`. +На стороне сервера AJAX-запрос распознаётся методом `$httpRequest->isAjax()` сервиса, [упаковывающего HTTP-запрос |http:request]. Для определения он использует HTTP-заголовок `X-Requested-With`, поэтому принципиально важно его отправлять. Внутри презентера можно использовать метод `$this->isAjax()`. -Если вы хотите отправить данные в формате JSON, используйте метод [`sendJson()` |presenters#Отправка ответа]. Метод также завершает работу презентера. +Если вы хотите отправить данные в формате JSON, используйте метод [`sendJson()` |presenters#Отправка ответа]. Этот метод также завершает работу презентера. ```php public function actionExport(): void { - $this->sendJson($this->model->getData); + $this->sendJson($this->model->getData()); } ``` -Если вы планируете ответить с помощью специального шаблона, предназначенного для AJAX, вы можете сделать это следующим образом: +Если вы собираетесь ответить особым шаблоном, предназначенным для AJAX, это делается так: ```php public function handleClick($param): void @@ -55,29 +55,29 @@ public function handleClick($param): void Сниппеты ======== -Самый мощный инструмент, который предлагает Nette для связи сервера с клиентом, — это сниппеты. Благодаря им вы можете превратить обычное приложение в AJAX-приложение с минимальными усилиями и несколькими строками кода. Как все это работает, демонстрирует пример Fifteen, код которого вы найдете на [GitHub |https://github.com/nette-examples/fifteen]. +Самый мощный инструмент, который Nette предлагает для связи сервера с клиентом, - это сниппеты. С их помощью вы можете превратить обычное приложение в AJAX-приложение минимальными усилиями и несколькими строками кода. Пример Fifteen показывает, как всё это работает, а его код можно найти на [GitHub |https://github.com/nette-examples/fifteen]. -Сниппеты, или фрагменты, позволяют обновлять только части страницы, вместо того чтобы перезагружать всю страницу целиком. Это не только быстрее и эффективнее, но и обеспечивает более комфортный пользовательский опыт. Сниппеты могут напомнить вам Hotwire для Ruby on Rails или Symfony UX Turbo. Интересно, что Nette представила сниппеты на 14 лет раньше. +Сниппеты позволяют обновлять только части страницы вместо перезагрузки всей страницы. Это не только быстрее и эффективнее, но и удобнее для пользователя. Сниппеты могут напомнить вам Hotwire для Ruby on Rails или Symfony UX Turbo. Любопытно, что Nette представил сниппеты на 14 лет раньше. -Как работают сниппеты? При первой загрузке страницы (не-AJAX запросе) загружается вся страница, включая все сниппеты. Когда пользователь взаимодействует со страницей (например, нажимает кнопку, отправляет форму и т. д.), вместо загрузки всей страницы вызывается AJAX-запрос. Код в презентере выполняет действие (action) и решает, какие сниппеты необходимо обновить. Nette отрисовывает эти сниппеты и отправляет их в виде массива в формате JSON. Обслуживающий код в браузере вставляет полученные сниппеты обратно в страницу. Таким образом, передается только код измененных сниппетов, что экономит пропускную способность и ускоряет загрузку по сравнению с передачей всего содержимого страницы. +Как работают сниппеты? При первой загрузке страницы (не-AJAX-запрос) загружается вся страница, включая все сниппеты. Когда пользователь взаимодействует со страницей (например, нажимает кнопку, отправляет форму и так далее), вместо перезагрузки всей страницы инициируется AJAX-запрос. Код в презентере выполняет действие и решает, какие сниппеты нужно обновить. Nette отрисовывает эти сниппеты и отправляет их как payload в формате JSON, содержащий массив со сниппетами. Обрабатывающий код в браузере затем вставляет полученные сниппеты обратно в страницу. Таким образом передаётся только код изменившихся сниппетов, что экономит трафик и ускоряет загрузку по сравнению с передачей всего содержимого страницы. Если ни один сниппет не был инвалидирован через `redrawControl()`, Nette возвращает всю страницу даже для AJAX-запроса: сниппеты отправляются, только когда что-то инвалидировано. Naja ---- -Для обработки сниппетов на стороне браузера используется [библиотека Naja |https://naja.js.org]. Ее [установите |https://naja.js.org/#/guide/01-install-setup-naja] как пакет node.js (для использования с приложениями Webpack, Rollup, Vite, Parcel и другими): +Для работы со сниппетами на стороне браузера используется [библиотека Naja |https://naja.js.org]. [Установите её |https://naja.js.org/#/guide/01-install-setup-naja] как пакет Node.js (для использования со сборщиками вроде Webpack, Rollup, Vite, Parcel и других): ```shell npm install naja ``` -…или прямо вставьте в шаблон страницы: +…или вставьте прямо в шаблон страницы: ```latte - + ``` -Сначала необходимо [инициализировать |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] библиотеку: +Сначала библиотеку нужно [инициализировать |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: ```js naja.initialize(); @@ -103,43 +103,45 @@ naja.initialize(); Перерисовка сниппетов --------------------- -Каждый объект класса [Control |components] (включая сам Presenter) отслеживает, произошли ли изменения, требующие его перерисовки. Для этого используется метод `redrawControl()`: +Каждый объект класса [Control |components] (включая сам презентер) следит за тем, произошли ли изменения, требующие перерисовки. Для этого служит метод `redrawControl()`: ```php public function handleLogin(string $user): void { - // после входа необходимо перерисовать соответствующую часть + // после входа нужно перерисовать соответствующую часть $this->redrawControl(); // ... } ``` -Nette позволяет еще более тонко контролировать то, что нужно перерисовать. Указанный метод может принимать в качестве аргумента имя сниппета. Таким образом, можно инвалидировать (то есть: принудительно перерисовать) на уровне частей шаблона. Если инвалидируется весь компонент, то перерисовывается и каждый его сниппет: +Nette позволяет ещё точнее управлять тем, что нужно перерисовать. Метод может принять аргументом имя сниппета. Благодаря этому можно инвалидировать (то есть заставить перерисоваться) на уровне частей шаблона. Если инвалидирован весь компонент, будет перерисован и каждый сниппет внутри него: ```php // инвалидирует сниппет 'header' $this->redrawControl('header'); ``` +Можно и отменить ожидающую инвалидацию вторым параметром `$redraw`: вызов `$this->redrawControl('header', redraw: false)` помечает сниппет как не нуждающийся в перерисовке. Полная сигнатура - `redrawControl(?string $snippet = null, bool $redraw = true)`. + Сниппеты в Latte ---------------- -Использование сниппетов в Latte невероятно просто. Чтобы определить часть шаблона как сниппет, просто оберните ее тегами `{snippet}` и `{/snippet}`: +Использовать сниппеты в Latte исключительно просто. Чтобы объявить часть шаблона сниппетом, достаточно обернуть её тегами `{snippet}` и `{/snippet}`: ```latte {snippet header} -

    Привет ...

    +

    Hello ...

    {/snippet} ``` -Сниппет создает в HTML-странице элемент `
    ` со специальным сгенерированным `id`. При перерисовке сниппета обновляется содержимое этого элемента. Поэтому необходимо, чтобы при первоначальной отрисовке страницы отрисовывались также все сниппеты, даже если они могут быть пустыми вначале. +Сниппет создаёт в HTML-странице элемент `
    ` со специальным порождённым `id`. При перерисовке сниппета содержимое этого элемента обновляется. Поэтому необходимо, чтобы при первоначальной отрисовке страницы отрисовывались и все сниппеты, даже если поначалу они пусты. -Вы можете создать сниппет с другим элементом, отличным от `
    `, с помощью n:атрибута: +Сниппет можно создать и на другом элементе, не `
    `, с помощью n:атрибута: ```latte
    -

    Привет ...

    +

    Hello ...

    ``` @@ -147,7 +149,7 @@ $this->redrawControl('header'); Области сниппетов ----------------- -Имена сниппетов также могут быть выражениями: +Имена сниппетов могут быть и выражениями: ```latte {foreach $items as $id => $item} @@ -155,7 +157,9 @@ $this->redrawControl('header'); {/foreach} ``` -Таким образом, у нас получится несколько сниппетов `item-0`, `item-1` и т. д. Если бы мы напрямую инвалидировали динамический сниппет (например, `item-1`), ничего бы не перерисовалось. Причина в том, что сниппеты действительно работают как фрагменты и отрисовываются только они сами. Однако в шаблоне фактически нет сниппета с именем `item-1`. Он создается только при выполнении кода вокруг сниппета, то есть цикла foreach. Поэтому мы помечаем часть шаблона, которая должна быть выполнена, с помощью тега `{snippetArea}`: +Сам по себе это неработающий промежуточный шаг: отрисованный вне статического `{snippet}` или `{snippetArea}`, динамический сниппет выдаёт `E_USER_WARNING` с сообщением *Dynamic snippets are allowed only inside static snippet/snippetArea.* Ниже мы это исправим. + +Так создаётся несколько сниппетов вроде `item-0`, `item-1` и так далее. Если бы мы напрямую инвалидировали динамический сниппет (например, `item-1`), не перерисовалось бы ничего. Причина в том, что сниппеты действительно работают как выдержки и напрямую отрисовываются только они сами. Однако в шаблоне технически нет сниппета с именем `item-1`. Он возникает только тогда, когда выполняется код вокруг сниппета, то есть цикл foreach. Поэтому мы помечаем ту часть шаблона, которая должна выполниться, тегом `{snippetArea}`: ```latte
      @@ -165,16 +169,16 @@ $this->redrawControl('header');
    ``` -И заставляем перерисовать как сам сниппет, так и всю родительскую область: +И запрашиваем перерисовку как отдельного сниппета, так и всей родительской области: ```php $this->redrawControl('itemsContainer'); $this->redrawControl('item-1'); ``` -В то же время желательно убедиться, что массив `$items` содержит только те элементы, которые должны быть перерисованы. +Одновременно стоит позаботиться о том, чтобы в массиве `$items` были только те элементы, которые нужно перерисовать. -Если мы вставляем в шаблон с помощью тега `{include}` другой шаблон, содержащий сниппеты, необходимо включение шаблона снова обернуть в `snippetArea` и инвалидировать его вместе со сниппетом: +Если мы подключаем в главный шаблон другой шаблон со сниппетами через тег `{include}`, подключение шаблона нужно снова обернуть в `snippetArea` и инвалидировать её вместе со сниппетом: ```latte {snippetArea include} @@ -198,38 +202,44 @@ $this->redrawControl('item'); Сниппеты в компонентах ---------------------- -Вы можете создавать сниппеты и в [компонентах |components], и Nette будет автоматически их перерисовывать. Но есть определенное ограничение: для перерисовки сниппетов вызывается метод `render()` без параметров. То есть передача параметров в шаблоне не будет работать: +Сниппеты можно создавать и внутри [компонентов|components], и Nette будет автоматически их перерисовывать. Однако есть ограничение: для перерисовки сниппетов Nette вызывает метод `render()` без параметров. Поэтому передача параметров в шаблоне работать не будет: ```latte OK {control productGrid} -не будет работать: +работать не будет: {control productGrid $arg, $arg} {control productGrid:paginator} ``` -Отправка пользовательских данных --------------------------------- +Отправка собственных данных +--------------------------- -Вместе со сниппетами вы можете отправить клиенту любые другие данные. Достаточно записать их в объект `payload`: +Вместе со сниппетами вы можете отправить клиенту любые дополнительные данные. Достаточно записать их в объект `payload`: ```php public function actionDelete(int $id): void { // ... if ($this->isAjax()) { - $this->payload->message = 'Успешно'; + $this->payload->message = 'Success'; } } ``` +Перенаправление +--------------- + +Во время AJAX-запроса методы `redirect()` и `redirectUrl()` не отправляют HTTP-перенаправление. Вместо этого они записывают целевой URL в payload (объект данных, отправляемый в AJAX-ответе), а именно в его свойство `payload.redirect`, и отправляют его; само перенаправление затем выполняет клиентская библиотека (Naja). + + Передача параметров =================== -Если мы отправляем компоненту параметры с помощью AJAX-запроса, будь то параметры сигнала или персистентные параметры, мы должны указать в запросе их глобальное имя, которое также содержит имя компонента. Полное имя параметра возвращает метод `getParameterId()`. +Отправляя параметры компоненту через AJAX-запрос, будь то параметры сигнала или постоянные параметры, мы должны указать в запросе их глобальное имя, включающее имя компонента. Полное имя параметра возвращает метод `getParameterId()`. ```js let url = new URL({link //foo!}); @@ -247,3 +257,9 @@ public function handleFoo(int $bar): void { } ``` + + +Дополнительные материалы +======================== + +- [Динамические сниппеты |best-practices:dynamic-snippets] diff --git a/application/ru/bootstrapping.texy b/application/ru/bootstrapping.texy index 58aeb4f9b0..3842b68a2b 100644 --- a/application/ru/bootstrapping.texy +++ b/application/ru/bootstrapping.texy @@ -1,21 +1,24 @@ -Загрузка -******** +Bootstrapping +*************
    -Загрузка — это процесс инициализации среды приложения, создания контейнера внедрения зависимостей (DI) и запуска приложения. Мы обсудим: +Bootstrapping - это процесс инициализации окружения приложения, создания контейнера внедрения зависимостей (DI) и запуска приложения. Мы обсудим: -- как класс Bootstrap инициализирует среду -- как приложения настраиваются с помощью NEON файлов -- как различать режим производства и разработки -- как создать и настроить DI контейнер +- как класс Bootstrap инициализирует окружение +- как приложения настраиваются файлами NEON +- как различать производственный режим и режим разработки +- как создать и настроить DI-контейнер
    -Приложения, будь то веб-приложения или скрипты, запускаемые из командной строки, начинают свою работу с некоторой формы инициализации среды. В давние времена за это отвечал файл с именем, например, `include.inc.php`, который включался в первоначальный файл. В современных приложениях Nette его заменил класс `Bootstrap`, который как часть приложения находится в файле `app/Bootstrap.php`. Он может выглядеть, например, так: +Приложения, будь то веб-приложения или скрипты, запускаемые из командной строки, начинают своё выполнение с той или иной инициализации окружения. В былые времена за это отвечал файл с именем вроде `include.inc.php`, подключаемый начальным файлом. В современных приложениях Nette его заменил класс `Bootstrap`, который как часть приложения находится в файле `app/Bootstrap.php`. Он может выглядеть, например, так: ```php +namespace App; + +use Nette; use Nette\Bootstrap\Configurator; class Bootstrap @@ -26,9 +29,9 @@ class Bootstrap public function __construct() { $this->rootDir = dirname(__DIR__); - // Конфигуратор отвечает за настройку среды приложения и сервисов. + // Configurator отвечает за настройку окружения приложения и сервисов. $this->configurator = new Configurator; - // Устанавливает каталог для временных файлов, генерируемых Nette (например, скомпилированных шаблонов) + // Задаём каталог для временных файлов, порождаемых Nette (например, скомпилированных шаблонов) $this->configurator->setTempDirectory($this->rootDir . '/temp'); } @@ -41,11 +44,11 @@ class Bootstrap private function initializeEnvironment(): void { - // Nette умный, и режим разработки включается автоматически, - // или вы можете включить его для конкретного IP-адреса, раскомментировав следующую строку: + // Nette умён, и режим разработки включается автоматически, + // либо вы можете включить его для конкретного IP-адреса, раскомментировав следующую строку: // $this->configurator->setDebugMode('secret@23.75.345.200'); - // Активирует Tracy: ультимативный "швейцарский нож" для отладки. + // Включает Tracy - лучший "швейцарский нож" для отладки. $this->configurator->enableTracy($this->rootDir . '/log'); // RobotLoader: автоматически загружает все классы в выбранном каталоге @@ -56,7 +59,7 @@ class Bootstrap private function setupContainer(): void { - // Загружает конфигурационные файлы + // Загружаем конфигурационные файлы $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } @@ -66,69 +69,78 @@ class Bootstrap index.php ========= -Первоначальным файлом для веб-приложений является `index.php`, который находится в [публичном каталоге |directory-structure#Публичный каталог www] `www/`. Он запрашивает у класса Bootstrap инициализацию среды и создание DI-контейнера. Затем он получает из него сервис `Application`, который запускает веб-приложение: +У веб-приложений начальным файлом служит `index.php`, лежащий в [публичном каталоге |directory-structure#Публичный каталог www/] `www/`. Он поручает классу Bootstrap инициализировать окружение и создать DI-контейнер. Затем он получает из контейнера сервис `Application`, который и запускает веб-приложение: ```php $bootstrap = new App\Bootstrap; -// Инициализация среды + создание DI-контейнера +// Инициализируем окружение и создаём DI-контейнер $container = $bootstrap->bootWebApplication(); -// DI-контейнер создает объект Nette\Application\Application +// DI-контейнер создаёт объект Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// Запуск приложения Nette и обработка входящего запроса +// Запускаем приложение Nette и обрабатываем входящий запрос $application->run(); ``` -Как видно, с настройкой среды и созданием контейнера внедрения зависимостей (DI) помогает класс [api:Nette\Bootstrap\Configurator], который мы сейчас рассмотрим подробнее. +.[note] +Объект `$application` в ходе обработки запроса испускает [события |nette:glossary#События]: `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown` и `onError` (при необработанном исключении). Вы можете привязать к ним обработчики, что удобно для ведения лога или мониторинга всего приложения. + +Как видите, настроить окружение и создать контейнер внедрения зависимостей (DI) помогает класс [api:Nette\Bootstrap\Configurator]. Сейчас мы познакомим вас с ним подробнее. -Режим разработки vs режим production -==================================== +Режим разработки и производственный режим +========================================= -Nette ведет себя по-разному в зависимости от того, работает ли он на сервере разработки или production: +Nette ведёт себя по-разному в зависимости от того, работает он на сервере разработки или на производственном: -🛠️ Режим разработки (Development): - - Отображает панель отладки Tracy с полезной информацией (SQL-запросы, время выполнения, использованная память) - - При ошибке отображает подробную страницу ошибки с вызовами функций и содержимым переменных - - Автоматически обновляет кеш при изменении шаблонов Latte, редактировании конфигурационных файлов и т. д. +🛠️ Режим разработки: + - Показывает панель отладки Tracy с полезными сведениями (SQL-запросы, время выполнения, использованная память) + - При ошибке показывает подробную страницу ошибки с вызовами функций и содержимым переменных + - Автоматически обновляет кеш при изменении шаблонов Latte, конфигурационных файлов и прочего -🚀 Режим production (Production): - - Не отображает никакой отладочной информации, все ошибки записывает в лог - - При ошибке отображает ErrorPresenter или общую страницу "Server Error" - - Кеш никогда автоматически не обновляется! - - Оптимизирован для скорости и безопасности +🚀 Производственный режим: + - Не показывает никаких отладочных сведений, все ошибки записываются в лог + - При ошибке показывает ErrorPresenter или общую страницу "Server Error" + - Кеш никогда не обновляется автоматически! + - Оптимизирован ради скорости и безопасности -Выбор режима осуществляется автоопределением, поэтому обычно не требуется ничего настраивать или вручную переключать: +Режим выбирается автоопределением, поэтому обычно ничего настраивать и переключать вручную не нужно: -- режим разработки: на localhost (IP-адрес `127.0.0.1` или `::1`), если нет прокси (т. е. его HTTP-заголовка) -- режим production: везде в остальных случаях +- режим разработки: на localhost (IP-адрес `127.0.0.1` или `::1`), если нет прокси (то есть его HTTP-заголовок не обнаружен) +- производственный режим: везде остальном -Если мы хотим включить режим разработки и в других случаях, например, для программистов, обращающихся с конкретного IP-адреса, используем `setDebugMode()`: +Если мы хотим включить режим разработки и в других случаях, например для программистов, заходящих с определённого IP-адреса, мы используем `setDebugMode()`: ```php -$this->configurator->setDebugMode('23.75.345.200'); // можно указать и массив IP-адресов +$this->configurator->setDebugMode('23.75.345.200'); // можно передать и массив IP-адресов ``` -Настоятельно рекомендуем комбинировать IP-адрес с cookie. В cookie `nette-debug` сохраним секретный токен, например `secret1234`, и таким образом активируем режим разработки для программистов, обращающихся с конкретного IP-адреса и одновременно имеющих в cookie упомянутый токен: +Мы настоятельно рекомендуем сочетать IP-адрес с cookie. Сохраните в cookie `nette-debug` секретный токен, например `secret1234`, и тем самым включите режим разработки для программистов, заходящих с определённого IP-адреса и имеющих в cookie этот токен: ```php $this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Режим разработки можно также полностью отключить, даже для localhost: +Мы можем и полностью отключить режим разработки, даже на localhost: ```php $this->configurator->setDebugMode(false); ``` -Внимание, значение `true` включает режим разработки принудительно, что никогда не должно происходить на production-сервере. +Учтите, что значение `true` принудительно включает режим разработки, чего на производственном сервере быть **никогда** не должно. + +Автоопределением внутренне занимается статический метод `Configurator::detectDebugMode()`, который вы можете вызвать и сами, например чтобы определить режим разработки вне конфигуратора. Он принимает необязательный белый список IP-адресов или имён компьютеров и возвращает, должен ли текущий запрос выполняться в режиме разработки: + +```php +$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200'); +``` Инструмент отладки Tracy ======================== -Для легкой отладки мы также включим отличный инструмент [Tracy |tracy:]. В режиме разработки он визуализирует ошибки, а в режиме production записывает ошибки в указанный каталог: +Ради удобной отладки мы включим прекрасный инструмент [Tracy |tracy:]. В режиме разработки он наглядно показывает ошибки, а в производственном записывает их в указанный каталог: ```php $this->configurator->enableTracy($this->rootDir . '/log'); @@ -138,19 +150,19 @@ $this->configurator->enableTracy($this->rootDir . '/log'); Временные файлы =============== -Nette использует кеш для DI-контейнера, RobotLoader, шаблонов и т. д. Поэтому необходимо установить путь к каталогу, куда будет сохраняться кеш: +Nette использует кеш для DI-контейнера, RobotLoader, шаблонов и прочего. Поэтому нужно задать путь к каталогу, где будет храниться кеш: ```php $this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` -На Linux или macOS установите для каталогов `log/` и `temp/` [права на запись |nette:troubleshooting#Настройка прав доступа к каталогам]. +В Linux или macOS задайте каталогам `log/` и `temp/` [права на запись |nette:troubleshooting#Задание прав на каталоги]. RobotLoader =========== -Как правило, мы захотим автоматически загружать классы с помощью [RobotLoader |robot-loader:], поэтому мы должны его запустить и позволить ему загружать классы из каталога, где находится `Bootstrap.php` (т. е. `__DIR__`), и всех подкаталогов: +Обычно мы хотим автоматически загружать классы с помощью [RobotLoader |robot-loader:], поэтому нам нужно его запустить и дать ему загружать классы из каталога, где лежит `Bootstrap.php` (то есть `__DIR__`), и из всех его подкаталогов: ```php $this->configurator->createRobotLoader() @@ -158,13 +170,13 @@ $this->configurator->createRobotLoader() ->register(); ``` -Альтернативный подход — позволить загружать классы только через [Composer |best-practices:composer] при соблюдении PSR-4. +Альтернативный подход - загружать классы исключительно через [Composer |best-practices:composer] по PSR-4. Часовой пояс ============ -Через конфигуратор можно установить часовой пояс по умолчанию. +Часовой пояс по умолчанию можно задать через конфигуратор. ```php $this->configurator->setTimeZone('Europe/Prague'); @@ -174,20 +186,22 @@ $this->configurator->setTimeZone('Europe/Prague'); Конфигурация DI-контейнера ========================== -Частью процесса загрузки является создание DI-контейнера, или фабрики объектов, которая является сердцем всего приложения. Это фактически PHP-класс, который генерирует Nette и сохраняет в каталоге кеша. Фабрика производит ключевые объекты приложения, и с помощью конфигурационных файлов мы инструктируем ее, как их создавать и настраивать, тем самым влияя на поведение всего приложения. +Частью процесса запуска является создание DI-контейнера, то есть фабрики объектов, которая служит сердцем всего приложения. На деле это PHP-класс, порождённый Nette и сохранённый в каталоге кеша. Фабрика создаёт ключевые объекты приложения, а конфигурационными файлами мы указываем ей, как их создавать и настраивать, и тем самым влияем на поведение всего приложения. -Конфигурационные файлы обычно записываются в формате [NEON |neon:format]. В отдельной главе вы узнаете, [что все можно настроить |nette:configuring]. +Конфигурационные файлы обычно пишутся в [формате NEON |neon:format]. В отдельной главе вы можете прочитать, [что можно настраивать |nette:configuring]. .[tip] -В режиме разработки контейнер автоматически обновляется при каждом изменении кода или конфигурационных файлов. В режиме production он генерируется только один раз, и изменения не проверяются для максимальной производительности. +В режиме разработки контейнер автоматически обновляется при изменении кода или конфигурационных файлов. В производственном режиме он порождается только один раз, а изменения не проверяются ради максимальной производительности. -Конфигурационные файлы загружаем с помощью `addConfig()`: +Метод `createContainer()` собирает контейнер и возвращает его экземпляр, а метод `loadContainer()` возвращает только имя порождённого класса контейнера, который вы затем можете создать сами. Это полезно в продвинутых сценариях. + +Конфигурационные файлы загружаются методом `addConfig()`: ```php $this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` -Если мы хотим добавить несколько конфигурационных файлов, мы можем вызвать функцию `addConfig()` несколько раз. +Если мы хотим добавить больше конфигурационных файлов, мы можем вызвать функцию `addConfig()` несколько раз. ```php $configDir = $this->rootDir . '/config'; @@ -198,17 +212,17 @@ if (PHP_SAPI === 'cli') { } ``` -Имя `cli.php` — не опечатка, конфигурация может быть записана и в PHP-файле, который вернет ее в виде массива. +Имя `cli.php` - не опечатка: конфигурацию можно записать и в PHP-файле, который возвращает её массивом. -Также мы можем добавить другие конфигурационные файлы в [секции `includes` |dependency-injection:configuration#Включение файлов]. +Другие конфигурационные файлы мы можем добавить и в [секции `includes` |dependency-injection:configuration#Подключение файлов]. -Если в конфигурационных файлах появляются элементы с одинаковыми ключами, они будут перезаписаны или, в случае [массивов, объединены |dependency-injection:configuration#Слияние]. Позже включенный файл имеет более высокий приоритет, чем предыдущий. Файл, в котором указана секция `includes`, имеет более высокий приоритет, чем включенные в нем файлы. +Если в конфигурационных файлах встречаются элементы с одинаковыми ключами, они будут перезаписаны или, в случае [массивов, объединены |dependency-injection:configuration#Слияние]. Файл, подключённый позже, имеет более высокий приоритет, чем предыдущий. Файл, в котором указана секция `includes`, имеет более высокий приоритет, чем подключённые в нём файлы. Статические параметры --------------------- -Параметры, используемые в конфигурационных файлах, можно определить [в секции `parameters` |dependency-injection:configuration#Параметры], а также передавать (или перезаписывать) методом `addStaticParameters()` (имеет псевдоним `addParameters()`). Важно, что разные значения параметров приводят к генерации дополнительных DI-контейнеров, то есть дополнительных классов. +Параметры, используемые в конфигурационных файлах, можно определить [в секции `parameters` |dependency-injection:configuration#Параметры], а также передать (или переопределить) методом `addStaticParameters()` (его более старый, ныне устаревший псевдоним - `addParameters()`). Важно, что разные значения параметров вызовут порождение дополнительных DI-контейнеров, то есть дополнительных классов. ```php $this->configurator->addStaticParameters([ @@ -216,13 +230,13 @@ $this->configurator->addStaticParameters([ ]); ``` -На параметр `projectId` можно ссылаться в конфигурации обычным способом `%projectId%`. +На параметр `projectId` можно сослаться в конфигурации обычной записью `%projectId%`. Динамические параметры ---------------------- -В контейнер можно добавить и динамические параметры, различные значения которых, в отличие от статических параметров, не приводят к генерации новых DI-контейнеров. +Мы можем добавить в контейнер и динамические параметры, разные значения которых, в отличие от статических, не вызовут порождения новых DI-контейнеров. ```php $this->configurator->addDynamicParameters([ @@ -230,7 +244,7 @@ $this->configurator->addDynamicParameters([ ]); ``` -Таким образом, мы можем легко добавить, например, переменные среды, на которые затем можно ссылаться в конфигурации с помощью записи `%env.variable%`. +Так мы легко добавим, например, переменные окружения, на которые затем можно сослаться в конфигурации записью `%env.variable%`. ```php $this->configurator->addDynamicParameters([ @@ -242,21 +256,22 @@ $this->configurator->addDynamicParameters([ Параметры по умолчанию ---------------------- -В конфигурационных файлах вы можете использовать эти статические параметры: +В конфигурационных файлах вы можете использовать эти параметры: -- `%appDir%` — абсолютный путь к каталогу с файлом `Bootstrap.php` -- `%wwwDir%` — абсолютный путь к каталогу с входным файлом `index.php` -- `%tempDir%` — абсолютный путь к каталогу для временных файлов -- `%vendorDir%` — абсолютный путь к каталогу, куда Composer устанавливает библиотеки -- `%rootDir%` — абсолютный путь к корневому каталогу проекта -- `%debugMode%` — указывает, находится ли приложение в режиме отладки -- `%consoleMode%` — указывает, пришел ли запрос через командную строку +- `%appDir%` - абсолютный путь к каталогу с файлом `Bootstrap.php` +- `%wwwDir%` - абсолютный путь к каталогу с начальным файлом `index.php` +- `%tempDir%` - абсолютный путь к каталогу временных файлов +- `%vendorDir%` - абсолютный путь к каталогу, куда Composer устанавливает библиотеки +- `%rootDir%` - абсолютный путь к корневому каталогу проекта +- `%baseUrl%` - абсолютный URL корневого каталога (динамический параметр, вычисляемый во время выполнения) +- `%debugMode%` - находится ли приложение в режиме отладки +- `%consoleMode%` - пришёл ли запрос из командной строки Импортированные сервисы ----------------------- -Теперь мы углубляемся. Хотя смысл DI-контейнера заключается в создании объектов, в исключительных случаях может возникнуть необходимость вставить существующий объект в контейнер. Мы делаем это, определяя сервис с флагом `imported: true`. +Теперь копнём глубже. Хотя назначение DI-контейнера - создавать объекты, изредка может понадобиться вставить в контейнер уже существующий объект. Мы делаем это, определив сервис с флагом `imported: true`. ```neon services: @@ -265,7 +280,7 @@ services: imported: true ``` -И в bootstrap вставляем объект в контейнер: +А в bootstrap вставляем объект в контейнер: ```php $this->configurator->addServices([ @@ -274,10 +289,10 @@ $this->configurator->addServices([ ``` -Различные среды -=============== +Разные окружения +================ -Не бойтесь изменять класс Bootstrap в соответствии со своими потребностями. Методу `bootWebApplication()` можно добавить параметры для различения веб-проектов. Или мы можем добавить другие методы, например `bootTestEnvironment()`, который инициализирует среду для модульных тестов, `bootConsoleApplication()` для скриптов, вызываемых из командной строки, и т. д. +Смело меняйте класс `Bootstrap` под свои нужды. Вы можете добавить в метод `bootWebApplication()` параметры, чтобы различать веб-проекты. Или можно добавить другие методы, например `bootTestEnvironment()`, инициализирующий окружение для модульных тестов, `bootConsoleApplication()` для скриптов, вызываемых из командной строки, и так далее. ```php public function bootTestEnvironment(): Nette\DI\Container diff --git a/application/ru/components.texy b/application/ru/components.texy index be5ba317a5..a1942e5f9e 100644 --- a/application/ru/components.texy +++ b/application/ru/components.texy @@ -3,7 +3,7 @@
    -Компоненты — это отдельные повторно используемые объекты, которые мы вставляем на страницы. Это могут быть формы, датагриды, опросы, в общем, все, что имеет смысл использовать повторно. Мы покажем: +Компоненты - самостоятельные переиспользуемые объекты, которые мы встраиваем в страницы. Это могут быть формы, таблицы данных, опросы - в общем, всё, что имеет смысл использовать повторно. Мы покажем: - как использовать компоненты? - как их писать? @@ -11,19 +11,19 @@
    -Nette имеет встроенную систему компонентов. Что-то подобное могут помнить старожилы из Delphi или ASP.NET Web Forms, на чем-то отдаленно похожем построены React или Vue.js. Однако в мире PHP-фреймворков это уникальное явление. +В Nette встроена система компонентов. Нечто похожее может быть знакомо ветеранам Delphi или ASP.NET Web Forms; React или Vue.js построены на чём-то отдалённо похожем. Однако в мире PHP-фреймворков это уникальная возможность. -При этом компоненты кардинально влияют на подход к созданию приложений. Вы можете собирать страницы из готовых блоков. Нужен датагрид в админке? Найдите его на [Componette |https://componette.org/search/component], репозитории open-source дополнений (то есть не только компонентов) для Nette, и просто вставьте в презентер. +При этом компоненты принципиально меняют подход к разработке приложений. Вы можете собирать страницы из заранее подготовленных единиц. Нужна таблица данных в вашей администраторской части? Найдите её на [Componette |https://componette.org/search/component], в хранилище дополнений с открытым кодом (не только компонентов) для Nette, и просто вставьте в презентер. -В презентер можно включить любое количество компонентов. А в некоторые компоненты можно вставлять другие компоненты. Таким образом, создается дерево компонентов, корнем которого является презентер. +Вы можете встроить в презентер сколько угодно компонентов. А в некоторые компоненты можно встраивать другие компоненты. Так возникает дерево компонентов, корнем которого служит презентер. Фабричные методы ================ -Как компоненты вставляются в презентер и затем используются? Обычно с помощью фабричных методов. +Как компоненты вставляются в презентер и затем используются? Обычно через фабричные методы. -Фабрика компонентов представляет собой элегантный способ создания компонентов только тогда, когда они действительно необходимы (lazy / on demand). Все волшебство заключается в реализации метода с именем `createComponent()`, где `` — это имя создаваемого компонента, и который создает и возвращает компонент. +Фабрика компонента - изящный способ создавать компоненты только тогда, когда они действительно нужны (лениво, по требованию). Вся магия заключается в реализации метода с именем `createComponent()`, где `` - имя создаваемого компонента; этот метод создаёт и возвращает компонент. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -31,49 +31,54 @@ class DefaultPresenter extends Nette\Application\UI\Presenter protected function createComponentPoll(): PollControl { $poll = new PollControl; - $poll->items = $this->item; + $poll->items = $this->items; return $poll; } } ``` -Благодаря тому, что все компоненты создаются в отдельных методах, код становится более понятным. +Поскольку все компоненты создаются в отдельных методах, код становится нагляднее. .[note] -Имена компонентов всегда начинаются с маленькой буквы, хотя в названии метода они пишутся с большой. +Имена компонентов всегда начинаются со строчной буквы, хотя в имени метода они пишутся с заглавной. -Фабрики никогда не вызываются напрямую, они вызываются сами в тот момент, когда мы впервые используем компонент. Благодаря этому компонент создается в нужный момент и только в том случае, когда он действительно необходим. Если мы не используем компонент (например, при AJAX-запросе, когда передается только часть страницы, или при кешировании шаблона), он вообще не создается, и мы экономим ресурсы сервера. +Мы никогда не вызываем фабрики напрямую: они вызываются автоматически при первом использовании компонента. Благодаря этому компонент создаётся в нужный момент и только если он действительно нужен. Если мы компонент не используем (например, при AJAX-запросе, когда передаётся лишь часть страницы, или при кешировании шаблона), он вообще не создастся, что сэкономит производительность сервера. ```php .{file:DefaultPresenter.php} -// обращаемся к компоненту, и если это было впервые, -// вызывается createComponentPoll(), который его создает +// обращаемся к компоненту, и если это в первый раз, +// вызывается createComponentPoll(), который его создаёт $poll = $this->getComponent('poll'); -// альтернативный синтаксис: $poll = $this['poll']; +// альтернативная запись: $poll = $this['poll']; ``` -В шаблоне можно отрисовать компонент с помощью тега [{control} |#Отрисовка]. Поэтому нет необходимости вручную передавать компоненты в шаблон. +В шаблоне компонент можно отрисовать тегом [{control} |#Отрисовка]. Поэтому передавать компоненты в шаблон вручную не нужно. ```latte -

    Голосуйте

    +

    Please Vote

    {control poll} ``` +.[tip] +Для динамического создания переменного числа компонентов используйте [Multiplier |multiplier]. -Стиль Голливуда -=============== +Фабричные методы `createComponent()` работают не только в презентерах. Тем же способом вы можете вложить компонент в другой компонент и собрать их в дерево - это удобно, например, для отдельно отрисовываемой формы внутри компонента. -Компоненты обычно используют одну свежую технику, которую мы любим называть стилем Голливуда. Вы наверняка знаете крылатую фразу, которую так часто слышат участники кинопроб: «Не звоните нам, мы вам позвоним». Именно об этом и идет речь. -В Nette вместо того, чтобы постоянно спрашивать («была ли отправлена форма?», «было ли это валидно?» или «нажал ли пользователь эту кнопку?»), вы говорите фреймворку «когда это произойдет, вызови этот метод» и оставляете дальнейшую работу ему. Если вы программируете на JavaScript, этот стиль программирования вам хорошо знаком. Вы пишете функции, которые вызываются, когда происходит определенное событие. И язык передает им соответствующие параметры. +Голливудский стиль +================== -Это полностью меняет взгляд на написание приложений. Чем больше задач вы можете оставить фреймворку, тем меньше у вас работы. И тем меньше вы можете что-то упустить. +Компоненты обычно используют бодрый приём, который мы любим называть голливудским стилем. Вы наверняка знаете штамп, который часто слышат участники кинопроб: "Не звоните нам, мы позвоним вам". Именно об этом и речь. +В Nette вместо того, чтобы постоянно задавать вопросы ("была ли форма отправлена?", "была ли она корректна?", "нажал ли пользователь эту кнопку?"), вы говорите фреймворку: "когда произойдёт вот это, вызови этот метод", и оставляете дальнейшую работу ему. Если вы программируете на JavaScript, этот стиль вам близко знаком. Вы пишете функции, которые вызываются, когда происходит определённое событие. И язык передаёт им нужные параметры. -Пишем компонент -=============== +Это полностью меняет взгляд на написание приложений. Чем больше задач вы можете оставить фреймворку, тем меньше у вас работы. И тем меньше вы можете упустить. -Под понятием компонент обычно подразумевается потомок класса [api:Nette\Application\UI\Control]. (Точнее было бы использовать термин «controls», но «контролы» в русском языке имеют совершенно другое значение, и скорее прижились «компоненты».) Сам презентер [api:Nette\Application\UI\Presenter] также является потомком класса `Control`. + +Написание компонента +==================== + +Под словом "компонент" мы обычно понимаем потомка класса [api:Nette\Application\UI\Control]. (Точнее было бы говорить "controls", но в некоторых языках это слово имеет другое значение, и "компоненты" прижилось лучше.) Сам презентер [api:Nette\Application\UI\Presenter] тоже является потомком класса `Control`. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -87,7 +92,7 @@ class PollControl extends Control Отрисовка ========= -Мы уже знаем, что для отрисовки компонента используется тег `{control componentName}`. Он фактически вызывает метод `render()` компонента, в котором мы заботимся об отрисовке. В нашем распоряжении, точно так же, как и в презентере, есть [шаблон Latte |templates] в переменной `$this->template`, в который мы передаем параметры. В отличие от презентера, мы должны указать файл с шаблоном и заставить его отрисоваться: +Мы уже знаем, что для отрисовки компонента служит тег `{control componentName}`. На деле он вызывает метод `render()` компонента, в котором мы заботимся об отрисовке. У нас есть, как и в презентере, [шаблон Latte|templates] в переменной `$this->template`, куда мы передаём параметры. В отличие от презентера, мы обязаны указать файл шаблона и заставить его отрисоваться: ```php .{file:PollControl.php} public function render(): void @@ -112,7 +117,7 @@ public function render(int $id, string $message): void } ``` -Иногда компонент может состоять из нескольких частей, которые мы хотим отрисовывать отдельно. Для каждой из них мы создадим собственный метод отрисовки, здесь в примере, например, `renderPaginator()`: +Иногда компонент может состоять из нескольких частей, которые мы хотим отрисовывать по отдельности. Для каждой из них мы создаём собственный метод отрисовки, здесь в примере `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +126,69 @@ public function renderPaginator(): void } ``` -А в шаблоне мы затем вызовем его с помощью: +А в шаблоне вызываем его так: ```latte {control poll:paginator} ``` -Для лучшего понимания полезно знать, как этот тег переводится в PHP. +Для лучшего понимания полезно знать, как этот тег превращается в PHP-код. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -переводится как: +превращается в: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -Метод `getComponent()` возвращает компонент `poll`, и над этим компонентом вызывается метод `render()`, соответственно `renderPaginator()`, если в теге после двоеточия указан другой способ рендеринга. +Метод `getComponent()` возвращает компонент `poll`, а у этого компонента вызывается метод `render()` или `renderPaginator()`, если в теге после двоеточия указан другой метод отрисовки. .[caution] -Внимание, если где-либо в параметрах появится **`=>`**, все параметры будут упакованы в массив и переданы как первый аргумент: +Осторожно: если в параметрах вне квадратных скобок появляется **`=>`**, все параметры будут упакованы в массив и переданы как первый аргумент: ```latte {control poll, id: 123, message: 'hello'} ``` -переводится как: +превращается в: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Отрисовка суб-компонента: +Отрисовка подкомпонента: ```latte {control cartControl-someForm} ``` -переводится как: +превращается в: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Компоненты, так же как и презентеры, автоматически передают в шаблоны несколько полезных переменных: +Компоненты, как и презентеры, автоматически передают в шаблоны несколько полезных переменных: -- `$basePath` — абсолютный URL-путь к корневому каталогу (например, `/eshop`) -- `$baseUrl` — абсолютный URL к корневому каталогу (например, `http://localhost/eshop`) -- `$user` — объект, [представляющий пользователя |security:authentication] -- `$presenter` — текущий презентер -- `$control` — текущий компонент -- `$flashes` — массив [сообщений |#Flash-сообщения], отправленных функцией `flashMessage()` +- `$basePath` - абсолютный путь URL к корневому каталогу (например, `/eshop`) +- `$baseUrl` - абсолютный URL корневого каталога (например, `http://localhost/eshop`) +- `$user` - объект, [представляющий пользователя |security:authentication] +- `$presenter` - текущий презентер +- `$control` - текущий компонент +- `$flashes` - массив [сообщений |#Flash-сообщения], отправленных функцией `flashMessage()` Сигнал ====== -Мы уже знаем, что навигация в приложении Nette заключается в ссылках или перенаправлениях на пары `Presenter:action`. Но что, если мы просто хотим выполнить действие на **текущей странице**? Например, изменить сортировку столбцов в таблице; удалить элемент; переключить светлый/темный режим; отправить форму; проголосовать в опросе; и т. д. +Мы уже знаем, что навигация в приложении Nette состоит из ссылок или перенаправлений на пары `Презентер:действие`. Но что, если мы хотим просто выполнить действие на **текущей странице**? Например, изменить сортировку столбцов таблицы; удалить элемент; переключить светлый или тёмный режим; отправить форму; проголосовать в опросе и так далее. -Этот тип запросов называется сигналами. И подобно тому, как действия вызывают методы `action()` или `render()`, сигналы вызывают методы `handle()`. В то время как понятие действия (или view) связано исключительно с презентерами, сигналы относятся ко всем компонентам. И, следовательно, и к презентерам, потому что `UI\Presenter` является потомком `UI\Control`. +Такой вид запроса называется сигналом. И так же как действия вызывают методы `action()` или `render()`, сигналы вызывают методы `handle()`. Понятие действия (или представления) относится исключительно к презентерам, а сигналы касаются всех компонентов. А значит, и презентеров, потому что `UI\Presenter` - потомок `UI\Control`. ```php public function handleClick(int $x, int $y): void @@ -192,36 +197,36 @@ public function handleClick(int $x, int $y): void } ``` -Ссылку, которая вызывает сигнал, мы создаем обычным способом, то есть в шаблоне атрибутом `n:href` или тегом `{link}`, в коде методом `link()`. Подробнее в главе [Создание URL-ссылок |creating-links#Ссылки на сигнал]. +Ссылка, вызывающая сигнал, создаётся обычным способом, то есть в шаблоне атрибутом `n:href` или тегом `{link}`, а в коде методом `link()`. Подробнее в главе [Создание URL-ссылок |creating-links#Ссылки на сигнал]. ```latte -нажмите здесь +click here ``` -Сигнал всегда вызывается на текущем презентере и action, невозможно вызвать его на другом презентере или другом action. +Сигнал всегда вызывается на текущем презентере и действии; вызвать его на другом презентере или действии нельзя. -Таким образом, сигнал вызывает перезагрузку страницы точно так же, как при первоначальном запросе, только дополнительно вызывает метод обработки сигнала с соответствующими параметрами. Если метод не существует, выбрасывается исключение [api:Nette\Application\UI\BadSignalException], которое отображается пользователю как страница ошибки 403 Forbidden. +Таким образом, сигнал вызывает перезагрузку страницы точно так же, как исходный запрос, но дополнительно вызывает метод обработки сигнала с нужными параметрами. Если метода не существует, выбрасывается исключение [api:Nette\Application\UI\BadSignalException], которое показывается пользователю как страница ошибки 403 Forbidden. Сниппеты и AJAX =============== -Сигналы могут немного напомнить вам AJAX: обработчики, которые вызываются на текущей странице. И вы правы, сигналы действительно часто вызываются с помощью AJAX, и затем мы передаем в браузер только измененные части страницы. То есть так называемые сниппеты. Дополнительную информацию можно найти на [странице, посвященной AJAX |ajax]. +Сигналы могут немного напомнить вам AJAX: обработчики, вызываемые на текущей странице. И вы правы, сигналы действительно часто вызываются через AJAX, и затем в браузер передаются только изменившиеся части страницы. Они называются сниппетами. Подробнее на [странице, посвящённой AJAX |ajax]. Flash-сообщения =============== -Компонент имеет собственное хранилище flash-сообщений, независимое от презентера. Это сообщения, которые, например, информируют о результате операции. Важной особенностью flash-сообщений является то, что они доступны в шаблоне даже после перенаправления. Даже после отображения они остаются активными еще 30 секунд — например, на случай, если из-за ошибки передачи пользователь обновит страницу — сообщение не исчезнет сразу. +У компонента есть собственное хранилище flash-сообщений, независимое от презентера. Это сообщения, которые, например, извещают о результате операции. Важная особенность flash-сообщений в том, что они доступны в шаблоне и после перенаправления. Даже после показа они остаются активными ещё 30 секунд: например, на случай, если пользователь обновит страницу из-за ошибки передачи, сообщение не исчезнет сразу. -Отправку обеспечивает метод [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Первым параметром является текст сообщения или объект `stdClass`, представляющий сообщение. Необязательным вторым параметром является его тип (error, warning, info и т. д.). Метод `flashMessage()` возвращает экземпляр flash-сообщения как объект `stdClass`, к которому можно добавлять дополнительную информацию. +Отправкой занимается метод [flashMessage |api:Nette\Application\UI\Control::flashMessage()]. Первый параметр - текст сообщения (`string`, `Stringable`) или объект `stdClass`, представляющий сообщение. Необязательный второй параметр - его тип (error, warning, info и так далее). Метод `flashMessage()` возвращает экземпляр flash-сообщения как объект `stdClass`, в который можно добавить дополнительные сведения. ```php -$this->flashMessage('Элемент был удален.'); +$this->flashMessage('Item was deleted.'); $this->redirect(/* ... */); // и перенаправляем ``` -В шаблоне эти сообщения доступны в переменной `$flashes` как объекты `stdClass`, которые содержат свойства `message` (текст сообщения), `type` (тип сообщения) и могут содержать уже упомянутую пользовательскую информацию. Отрисуем их, например, так: +Эти сообщения доступны шаблону в переменной `$flashes` как объекты `stdClass`, содержащие свойства `message` (текст сообщения), `type` (тип сообщения) и, возможно, упомянутые пользовательские сведения. Отрисовываем мы их, например, так: ```latte {foreach $flashes as $flash} @@ -230,36 +235,36 @@ $this->redirect(/* ... */); // и перенаправляем ``` -Перенаправление после сигнала -============================= +Перенаправление после обработки сигнала +======================================= -После обработки сигнала компонента часто следует перенаправление. Это похожая ситуация, как с формами — после их отправки мы также перенаправляем, чтобы при обновлении страницы в браузере не произошло повторной отправки данных. +За обработкой сигнала компонента часто следует перенаправление. Это похоже на формы: после их отправки мы тоже перенаправляем, чтобы данные не отправились повторно при обновлении страницы в браузере. ```php -$this->redirect('this') // перенаправляет на текущий презентер и action +$this->redirect('this'); // перенаправляет на текущие презентер и действие ``` -Поскольку компонент является повторно используемым элементом и обычно не должен иметь прямой связи с конкретными презентерами, методы `redirect()` и `link()` автоматически интерпретируют параметр как сигнал компонента: +Поскольку компонент - переиспользуемый элемент, который обычно не должен быть напрямую связан с конкретными презентерами, методы `redirect()` и `link()` автоматически истолковывают параметр как сигнал компонента: ```php -$this->redirect('click') // перенаправляет на сигнал 'click' того же компонента +$this->redirect('click'); // перенаправляет на сигнал 'click' того же компонента ``` -Если вам нужно перенаправить на другой презентер или действие, вы можете сделать это через презентер: +Если вам нужно перенаправить на другой презентер или действие, это можно сделать через презентер: ```php -$this->getPresenter()->redirect('Product:show'); // перенаправляет на другой презентер/action +$this->getPresenter()->redirect('Product:show'); // перенаправляет на другой презентер или действие ``` -Персистентные параметры -======================= +Постоянные параметры +==================== -Персистентные параметры служат для поддержания состояния в компонентах между различными запросами. Их значение остается неизменным даже после нажатия на ссылку. В отличие от данных в сессии, они передаются в URL. И это происходит полностью автоматически, включая ссылки, созданные в других компонентах на той же странице. +Постоянные параметры служат для сохранения состояния компонентов между разными запросами. Их значение остаётся тем же и после щелчка по ссылке. В отличие от данных сессии, они передаются в URL. И происходит это полностью автоматически, включая ссылки, созданные в других компонентах на той же странице. -Например, у вас есть компонент для постраничной навигации контента. Таких компонентов на странице может быть несколько. И мы хотим, чтобы после нажатия на ссылку все компоненты остались на своей текущей странице. Поэтому мы сделаем номер страницы (`page`) персистентным параметром. +Например, у вас есть компонент постраничного вывода содержимого. Таких компонентов на странице может быть несколько. И мы хотим, чтобы все компоненты после щелчка по ссылке остались на своей текущей странице. Поэтому мы делаем номер страницы (`page`) постоянным параметром. -Создание персистентного параметра в Nette невероятно просто. Достаточно создать публичное свойство и пометить его атрибутом: (ранее использовалось `/** @persistent */`) +Создать постоянный параметр в Nette исключительно просто. Достаточно создать публичное свойство и пометить его атрибутом (раньше использовалось `/** @persistent */`): ```php use Nette\Application\Attributes\Persistent; // эта строка важна @@ -271,43 +276,43 @@ class PaginatingControl extends Control } ``` -У свойства рекомендуется указывать тип данных (например, `int`), и вы можете указать значение по умолчанию. Значения параметров можно [валидировать |#Валидация персистентных параметров]. +Мы рекомендуем указывать тип данных свойства (например, `int`), а также можно задать значение по умолчанию. Значения параметров можно [проверять |#Проверка постоянных параметров]. -При создании ссылки можно изменить значение персистентного параметра: +При создании ссылки значение постоянного параметра можно изменить: ```latte -следующая +next ``` -Или его можно *сбросить*, то есть удалить из URL. Тогда он примет свое значение по умолчанию: +Или его можно *сбросить*, то есть убрать из URL. Тогда он примет значение по умолчанию: ```latte -сбросить +reset ``` -Персистентные компоненты -======================== +Постоянные компоненты +===================== -Не только параметры, но и компоненты могут быть персистентными. У такого компонента его персистентные параметры передаются и между различными действиями презентера, или между несколькими презентерами. Персистентные компоненты помечаются аннотацией у класса презентера. Например, так мы пометим компоненты `calendar` и `poll`: +Постоянными могут быть не только параметры, но и компоненты. Их постоянные параметры тогда передаются даже между разными действиями презентера или между несколькими презентерами. Постоянные компоненты мы помечаем атрибутом на классе презентера. Например, компоненты `calendar` и `poll` мы помечаем так: ```php -/** - * @persistent(calendar, poll) - */ +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] class DefaultPresenter extends Nette\Application\UI\Presenter { } ``` -Подкомпоненты внутри этих компонентов помечать не нужно, они также станут персистентными. +Подкомпоненты внутри этих компонентов помечать не нужно, они тоже становятся постоянными. -В PHP 8 вы можете использовать атрибуты для обозначения персистентных компонентов: +Более старая аннотация `@persistent` ещё работает, но объявлена устаревшей и выдаёт предупреждение: ```php -use Nette\Application\Attributes\Persistent; - -#[Persistent('calendar', 'poll')] +/** + * @persistent(calendar, poll) + */ class DefaultPresenter extends Nette\Application\UI\Presenter { } @@ -317,15 +322,15 @@ class DefaultPresenter extends Nette\Application\UI\Presenter Компоненты с зависимостями ========================== -Как создавать компоненты с зависимостями, не «засоряя» презентеры, которые их будут использовать? Благодаря умным свойствам DI-контейнера в Nette, так же как при использовании классических сервисов, можно оставить большую часть работы фреймворку. +Как создавать компоненты с зависимостями, не "захламляя" презентеры, которые будут их использовать? Благодаря продуманным возможностям DI-контейнера Nette, как и в случае с обычными сервисами, большую часть работы можно оставить фреймворку. -Возьмем в качестве примера компонент, который имеет зависимость от сервиса `PollFacade`: +Возьмём для примера компонент, зависящий от сервиса `PollFacade`: ```php class PollControl extends Control { public function __construct( - private int $id, // Id опроса, для которого мы создаем компонент + private int $id, // ID опроса, для которого мы создаём компонент private PollFacade $facade, ) { } @@ -338,11 +343,11 @@ class PollControl extends Control } ``` -Если бы мы писали классический сервис, проблем бы не было. О передаче всех зависимостей невидимо позаботился бы DI-контейнер. Однако с компонентами мы обычно обращаемся так, что создаем их новый экземпляр прямо в презентере в [фабричных методах |#Фабричные методы] `createComponent…()`. Но передавать все зависимости всех компонентов в презентер, чтобы затем передать их компонентам, громоздко. И сколько написанного кода… +Если бы мы писали обычный сервис, обсуждать было бы нечего. DI-контейнер незаметно позаботился бы о передаче всех зависимостей. Однако с компонентами мы обычно обходимся созданием нового экземпляра прямо в презентере, в [фабричных методах |#Фабричные методы] `createComponent…()`. Но передавать в презентер все зависимости всех компонентов только для того, чтобы передать их дальше компонентам, утомительно. И сколько кода приходится писать... -Логичный вопрос: почему бы просто не зарегистрировать компонент как классический сервис, не передать его в презентер и затем в методе `createComponent…()` не возвращать? Такой подход, однако, неуместен, потому что мы хотим иметь возможность создавать компонент даже несколько раз. +Логично спросить: почему бы просто не зарегистрировать компонент как обычный сервис, передать его в презентер и затем возвращать в методе `createComponent…()`? Однако такой подход неуместен, потому что мы хотим иметь возможность при необходимости создавать компонент несколько раз. -Правильным решением является написание для компонента фабрики, то есть класса, который нам создаст компонент: +Правильное решение - написать для компонента фабрику, то есть класс, который создаёт компонент за нас: ```php class PollControlFactory @@ -359,14 +364,14 @@ class PollControlFactory } ``` -Так мы зарегистрируем фабрику в нашем контейнере в конфигурации: +Эту фабрику мы регистрируем в нашем контейнере в конфигурации: ```neon services: - PollControlFactory ``` -и, наконец, используем ее в нашем презентере: +и наконец используем её в своём презентере: ```php class PollPresenter extends Nette\Application\UI\Presenter @@ -378,13 +383,13 @@ class PollPresenter extends Nette\Application\UI\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // можем передать наш параметр + $pollId = 1; // мы можем передать свой параметр return $this->pollControlFactory->create($pollId); } } ``` -Замечательно то, что Nette DI умеет [генерировать |dependency-injection:factory] такие простые фабрики, поэтому вместо всего ее кода достаточно написать только ее интерфейс: +Прекрасно то, что Nette DI умеет [порождать |dependency-injection:factory] такие простые фабрики, поэтому вместо всего её кода вам достаточно написать её интерфейс: ```php interface PollControlFactory @@ -393,21 +398,21 @@ interface PollControlFactory } ``` -И это все. Nette внутренне реализует этот интерфейс и передаст его в презентер, где мы уже можем его использовать. Он волшебным образом добавит в наш компонент и параметр `$id`, и экземпляр класса `PollFacade`. +И это всё. Nette внутренне реализует этот интерфейс и внедрит его в презентер, где мы сможем им пользоваться. Он волшебным образом добавит в наш компонент параметр `$id` и экземпляр класса `PollFacade`. -Компоненты в глубину -==================== +Компоненты в подробностях +========================= -Компоненты в Nette Application представляют собой повторно используемые части веб-приложения, которые мы вставляем на страницы и которым, собственно, посвящена вся эта глава. Какие именно возможности имеет такой компонент? +Компоненты в Nette Application представляют переиспользуемые части веб-приложения, которые мы встраиваем в страницы и которым посвящена вся эта глава. Каковы же в точности возможности такого компонента? -1) он может быть отрисован в шаблоне -2) он знает, [какую свою часть |ajax#Сниппеты] нужно отрисовать при AJAX-запросе (сниппеты) -3) он имеет возможность сохранять свое состояние в URL (персистентные параметры) -4) он имеет возможность реагировать на действия пользователя (сигналы) -5) он создает иерархическую структуру (где корнем является презентер) +1) Он отрисовывается в шаблоне +2) Он знает, [какую свою часть |ajax#Сниппеты] отрисовать при AJAX-запросе (сниппеты) +3) Он умеет хранить своё состояние в URL (постоянные параметры) +4) Он умеет реагировать на действия пользователя (сигналы) +5) Он образует иерархическую структуру (корнем которой служит презентер) -Каждую из этих функций обеспечивает один из классов иерархии наследования. За отрисовку (1 + 2) отвечает [api:Nette\Application\UI\Control], за включение в [жизненный цикл |presenters#Жизненный цикл презентера] (3, 4) — класс [api:Nette\Application\UI\Component], а за создание иерархической структуры (5) — классы [Container и Component |component-model:]. +За каждую из этих функций отвечает один из классов в цепочке наследования. За отрисовку (1 + 2) отвечает [api:Nette\Application\UI\Control], за встраивание в [жизненный цикл |presenters#Жизненный цикл презентера] (3, 4) - класс [api:Nette\Application\UI\Component], а за создание иерархической структуры (5) - классы [Container и Component |component-model:]. ``` Nette\ComponentModel\Component { IComponent } @@ -428,12 +433,12 @@ Nette\ComponentModel\Component { IComponent } [* lifecycle-component.svg *] *** *Жизненный цикл компонента* .<> -Валидация персистентных параметров ----------------------------------- +Проверка постоянных параметров +------------------------------ -Значения [персистентных параметров |#Персистентные параметры], полученные из URL, записываются в свойства методом `loadState()`. Он также проверяет, соответствует ли тип данных, указанный у свойства, иначе отвечает ошибкой 404, и страница не отображается. +Значения [постоянных параметров |#Постоянные параметры], полученные из URL, записываются в свойства методом `loadState()`. Он также проверяет, соответствуют ли они типу данных, указанному у свойства; иначе он отвечает ошибкой 404, и страница не отображается. -Никогда слепо не доверяйте персистентным параметрам, потому что они могут быть легко изменены пользователем в URL. Так, например, мы проверим, что номер страницы `$this->page` больше 0. Подходящий способ — переопределить упомянутый метод `loadState()`: +Никогда не доверяйте постоянным параметрам вслепую, потому что пользователь легко может переписать их в URL. Вот так мы проверяем, например, что номер страницы `$this->page` больше 0. Подходящий способ - переопределить упомянутый метод `loadState()`: ```php class PaginatingControl extends Control @@ -443,8 +448,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // здесь устанавливается $this->page - // следует собственная проверка значения: + parent::loadState($params); // здесь задаётся $this->page + // далее идёт собственная проверка значения: if ($this->page < 1) { $this->error(); } @@ -452,27 +457,41 @@ class PaginatingControl extends Control } ``` -Обратный процесс, то есть сбор значений из персистентных свойств, отвечает метод `saveState()`. +Обратным процессом, то есть сбором значений из постоянных свойств, занимается метод `saveState()`. + + +Подключение к презентеру +------------------------ + +В момент, когда компонент становится частью иерархии презентера, вызываются его callback-функции, хранящиеся в массиве `$onAnchor`. С этого момента у компонента есть презентер, он может безопасно создавать ссылки, читать постоянные параметры и так далее. + +```php +$control->onAnchor[] = function ($control): void { + // у компонента теперь есть презентер +}; +``` + +Сигналы в подробностях +---------------------- -Сигналы в глубину ------------------ +Сигнал вызывает перезагрузку страницы ровно так же, как исходный запрос (кроме случая вызова через AJAX), и вызывает метод `signalReceived($signal)`, реализация которого по умолчанию в классе `Nette\Application\UI\Component` пытается вызвать метод, составленный из слов `handle`. Дальнейшая обработка зависит от объекта. Объекты, наследующие от `Component` (то есть `Control` и `Presenter`), реагируют попыткой вызвать метод `handle` с нужными параметрами. -Сигнал вызывает перезагрузку страницы точно так же, как при первоначальном запросе (кроме случая, когда он вызывается AJAX-ом), и вызывает метод `signalReceived($signal)`, чья реализация по умолчанию в классе `Nette\Application\UI\Component` пытается вызвать метод, составленный из слов `handle{signal}`. Дальнейшая обработка зависит от данного объекта. Объекты, наследующие от `Component` (т. е. `Control` и `Presenter`), реагируют так, что пытаются вызвать метод `handle{signal}` с соответствующими параметрами. +Иначе говоря: берётся определение функции `handle` вместе со всеми параметрами, пришедшими с запросом, параметры из URL сопоставляются аргументам по имени, и делается попытка вызвать метод. Например, значение параметра `id` из URL передаётся как аргумент `$id`, `something` из URL - как `$something` и так далее. А если метода не существует, метод `signalReceived` выбрасывает [исключение |api:Nette\Application\UI\BadSignalException]. -Другими словами: берется определение функции `handle{signal}` и все параметры, пришедшие с запросом, и к аргументам по имени подставляются параметры из URL, и делается попытка вызвать данный метод. Например, в качестве параметра `$id` передается значение из параметра `id` в URL, в качестве `$something` передается `something` из URL и т. д. И если метод не существует, метод `signalReceived` выбрасывает [исключение |api:Nette\Application\UI\BadSignalException]. +Помимо параметров из URL сигнал читает и параметры, отправленные в **теле POST-запроса**. Это удобно, потому что сигналы часто вызываются из JavaScript, где данные естественно отправлять методом POST. Однако если параметр с одним и тем же именем приходит и из URL, и из тела POST, приоритет имеет значение **из URL**. Поэтому не давайте полю POST такое же имя, как у параметра URL или маршрута, иначе значение из URL молча его перекроет. Параметры сигнала делят общее пространство с параметрами действия и постоянными параметрами, см. [Общее пространство параметров |presenters#Общее пространство параметров]. -Сигнал может принимать любой компонент, презентер или объект, который реализует интерфейс `SignalReceiver` и подключен к дереву компонентов. +Сигнал может принять любой компонент, презентер или объект, который реализует интерфейс `SignalReceiver` и подключён к дереву компонентов. -Основными получателями сигналов будут `Presenters` и визуальные компоненты, наследующие от `Control`. Сигнал должен служить знаком для объекта, что он должен что-то сделать — опрос должен засчитать голос пользователя, блок с новостями должен развернуться и показать в два раза больше новостей, форма была отправлена и должна обработать данные и т. п. +Главными получателями сигналов будут `Presenters` и визуальные компоненты, наследующие от `Control`. Сигнал предназначен служить объекту знаком, что нужно что-то сделать: опрос должен засчитать голос пользователя, блок новостей должен раскрыться и показать вдвое больше новостей, форма была отправлена и должна обработать данные и так далее. -URL для сигнала создаем с помощью метода [Component::link() |api:Nette\Application\UI\Component::link()]. В качестве параметра `$destination` передаем строку `{signal}!` и в качестве `$args` — массив аргументов, которые мы хотим передать сигналу. Сигнал всегда вызывается на текущем презентере и action с текущими параметрами, параметры сигнала просто добавляются. Кроме того, в самом начале добавляется **параметр `?do`, который определяет сигнал**. +URL для сигнала создаётся методом [Component::link() |api:Nette\Application\UI\Component::link()]. Как параметр `$destination` мы передаём строку `{signal}!`, а как `$args` - массив аргументов, которые хотим передать сигналу. Сигнал всегда вызывается на текущем презентере и действии с текущими параметрами; параметры сигнала лишь добавляются. Кроме того, добавляется **параметр `?do`, задающий сигнал**. -Его формат — либо `{signal}`, либо `{signalReceiver}-{signal}`. `{signalReceiver}` — это имя компонента в презентере. Поэтому в имени компонента не может быть дефиса — он используется для разделения имени компонента и сигнала, однако таким образом можно вложить несколько компонентов. +Его формат - либо `{signal}`, либо `{signalReceiver}-{signal}`. `{signalReceiver}` - имя компонента в презентере. Поэтому в имени компонента нельзя использовать дефис: он служит для разделения имени компонента и сигнала, хотя таким способом можно вкладывать несколько компонентов друг в друга. -Метод [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] проверяет, является ли компонент (первый аргумент) получателем сигнала (второй аргумент). Второй аргумент можно опустить — тогда проверяется, является ли компонент получателем любого сигнала. В качестве второго параметра можно указать `true`, чтобы проверить, является ли получателем не только указанный компонент, но и любой его потомок. +Метод [isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] проверяет, является ли компонент (первый аргумент) получателем сигнала (второй аргумент). Второй аргумент можно опустить, тогда проверяется, является ли компонент получателем какого-либо сигнала. Если второй параметр установлен в `true`, проверяется, является ли получателем указанный компонент или любой из его потомков. -На любом этапе, предшествующем `handle{signal}`, мы можем выполнить сигнал вручную, вызвав метод [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], который берет на себя обработку сигнала — берет компонент, который определен как получатель сигнала (если получатель сигнала не указан, это сам презентер), и отправляет ему сигнал. +На любом этапе, предшествующем `handle`, мы можем выполнить сигнал вручную, вызвав метод [processSignal()|api:Nette\Application\UI\Presenter::processSignal()], который заботится об обработке сигнала: он берёт компонент, определённый как получатель сигнала (если получатель не указан, это сам презентер), и отправляет ему сигнал. Пример: @@ -482,4 +501,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Таким образом, сигнал выполняется преждевременно и больше не будет вызываться. +Это выполняет сигнал досрочно, и повторно он вызван не будет. diff --git a/application/ru/configuration.texy b/application/ru/configuration.texy index 4f2aa91381..7b37255d05 100644 --- a/application/ru/configuration.texy +++ b/application/ru/configuration.texy @@ -1,8 +1,8 @@ -Конфигурация приложений +Конфигурация приложения *********************** .[perex] -Обзор опций конфигурации для приложений Nette. +Обзор параметров конфигурации Nette Application. Application @@ -10,54 +10,56 @@ Application ```neon application: - # отображать панель "Nette Application" в Tracy BlueScreen? - debugger: ... # (bool) по умолчанию true + # показывать панель "Nette Application" в Tracy BlueScreen? + debugger: ... # (bool) включено при наличии Tracy - # будет ли при ошибке вызываться error-presenter? - # имеет эффект только в режиме разработки - catchExceptions: ... # (bool) по умолчанию true + # в продакшене исключения всегда обрабатывает error-презентер; + # этот параметр лишь включает такое поведение и в режиме разработки + catchExceptions: ... # (bool) по умолчанию false, то есть выключено в dev, всегда включено в продакшене - # имя error-presenter + # имя error-презентера errorPresenter: Error # (string|array) по умолчанию 'Nette:Error' - # определяет псевдонимы для презентеров и действий + # задаёт псевдонимы презентеров и действий aliases: ... - # определяет правила для перевода имени презентера в класс + # задаёт правила преобразования имени презентера в класс mapping: ... - # неверные ссылки не генерируют предупреждения? - # имеет эффект только в режиме разработки + # подавлять предупреждения о некорректных ссылках? + # действует только в режиме разработки silentLinks: ... # (bool) по умолчанию false ``` -Начиная с версии `nette/application` 3.2, можно определить пару error-presenter'ов: +Начиная с версии `nette/application` 3.2 можно задать пару error-презентеров: ```neon application: errorPresenter: - 4xx: Error4xx # для исключения Nette\Application\BadRequestException + 4xx: Error4xx # для Nette\Application\BadRequestException 5xx: Error5xx # для остальных исключений ``` -Опция `silentLinks` определяет, как Nette поведет себя в режиме разработки, если генерация ссылки не удалась (например, потому что презентер не существует и т. д.). Значение по умолчанию `false` означает, что Nette выбросит ошибку `E_USER_WARNING`. Установка на `true` подавит это сообщение об ошибке. В production-среде `E_USER_WARNING` всегда будет вызываться. Это поведение также можно контролировать, установив переменную презентера [$invalidLinkMode |creating-links#Недействительные ссылки]. +Разделять их полезно, потому что эти две ситуации принципиально различны. `BadRequestException` (коды 4xx) означает, что с приложением всё в порядке и просто посетитель запросил то, чего не существует. Поэтому вы можете использовать полноценный презентер, показывающий дружелюбное сообщение в макете вашего сайта. Напротив, ошибка 5xx означает, что в приложении что-то сломалось и вы не знаете что. Держите презентер для 5xx максимально простым, чтобы при его отрисовке уже ничто не могло отказать: в идеале он не должен трогать ни базу данных, ни макет, ни вошедшего пользователя. -[Псевдонимы упрощают создание ссылок |creating-links#Псевдонимы] на часто используемые презентеры. +Параметр `silentLinks` определяет, как ведёт себя Nette в режиме разработки, когда порождение ссылки не удаётся (например, потому что презентера не существует). Значение по умолчанию `false` означает, что Nette выдаёт ошибку `E_USER_WARNING`. Значение `true` подавляет это сообщение. В производственной среде `E_USER_WARNING` выдаётся всегда. На это поведение можно повлиять и переменной презентера [$invalidLinkMode |creating-links#Некорректные ссылки]. -[Маппинг определяет правила |directory-structure#Маппинг презентеров], по которым из имени презентера выводится имя класса. +[Псевдонимы упрощают обращение |creating-links#Псевдонимы] к часто используемым презентерам. + +[Mapping задаёт правила |directory-structure#Отображение презентеров], по которым имя класса выводится из имени презентера. Автоматическая регистрация презентеров -------------------------------------- -Nette автоматически добавляет презентеры как сервисы в DI-контейнер, что значительно ускоряет их создание. Как Nette находит презентеры, можно настроить: +Nette автоматически добавляет презентеры как сервисы в DI-контейнер, что заметно ускоряет их создание. То, как Nette находит презентеры, можно настроить: ```neon application: - # искать презентеры в Composer class map? + # искать презентеры в class map Composer? scanComposer: ... # (bool) по умолчанию true - # маска, которой должно соответствовать имя класса и файла + # маска, которой должны соответствовать имя класса и имя файла scanFilter: ... # (string) по умолчанию '*Presenter' # в каких каталогах искать презентеры? @@ -65,7 +67,7 @@ application: - %vendorDir%/mymodule ``` -Каталоги, указанные в `scanDirs`, не перезаписывают значение по умолчанию `%appDir%`, а дополняют его, `scanDirs` таким образом будет содержать оба пути `%appDir%` и `%vendorDir%/mymodule`. Если мы хотим исключить каталог по умолчанию, используем [восклицательный знак |dependency-injection:configuration#Слияние], который перезапишет значение: +Каталоги, перечисленные в `scanDirs`, не заменяют значение по умолчанию `%appDir%`, а дополняют его, поэтому в `scanDirs` окажутся оба пути: `%appDir%` и `%vendorDir%/mymodule`. Если мы хотим обойтись без каталога по умолчанию, мы используем [восклицательный знак |dependency-injection:configuration#Слияние]: ```neon application: @@ -73,36 +75,42 @@ application: - %vendorDir%/mymodule ``` -Сканирование каталогов можно отключить, указав значение false. Не рекомендуем полностью подавлять автоматическое добавление презентеров, так как это приведет к снижению производительности приложения. +Сканирование каталогов можно отключить значением `false`. Тогда презентеры больше не регистрируются как сервисы, поэтому их нельзя настроить через секцию [decorator |dependency-injection:configuration#Decorator], а их создание идёт медленнее. Поэтому мы не рекомендуем полностью отключать автоматическую регистрацию: это снизит производительность приложения. Шаблоны Latte ============= -С помощью этой настройки можно глобально повлиять на поведение Latte в компонентах и презентерах. +Эта настройка глобально влияет на поведение Latte в компонентах и презентерах. ```neon latte: - # отображать панель Latte в Tracy Bar для главного шаблона (true) или всех компонентов (all)? - debugger: ... # (true|false|'all') по умолчанию true + # показывать панель Latte в Tracy Bar для главного шаблона (true) или для всех компонентов (all)? + debugger: ... # (true|false|'all') включено при наличии Tracy (только в режиме отладки) - # генерирует шаблоны с заголовком declare(strict_types=1) + # порождать шаблоны с заголовком declare(strict_types=1) strictTypes: ... # (bool) по умолчанию false - # включает режим [строгого парсера |latte:develop#strict-mode] + # включить [строгий режим разбора |latte:develop#strict mode] strictParsing: ... # (bool) по умолчанию false - # активирует [проверку сгенерированного кода |latte:develop#checking-generated-code] + # ограничивает область видимости переменных телом цикла + scopedLoopVariables: ... # (bool) по умолчанию false + + # убирает отступы, возникающие из-за вложенности в парные теги + dedent: ... # (bool) по умолчанию false + + # включить [проверку порождённого кода |latte:develop#Checking Generated Code] phpLinter: ... # (string) по умолчанию null - # устанавливает локаль - locale: ru_RU # (string) по умолчанию null + # задать локаль + locale: cs_CZ # (string) по умолчанию null # класс объекта $this->template templateClass: App\MyTemplateClass # по умолчанию Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Если вы используете Latte версии 3, вы можете добавлять новые [расширения |latte:extending-latte#Latte Extension] с помощью: +Новые [расширения |latte:extending-latte#Расширение Latte] добавляются так: ```neon latte: @@ -110,20 +118,6 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Если вы используете Latte версии 2, вы можете регистрировать новые теги, указав имя класса или ссылку на сервис. По умолчанию вызывается метод `install()`, но это можно изменить, указав имя другого метода: - -```neon -latte: - # регистрация пользовательских тегов Latte - macros: - - App\MyLatteMacros::register # статический метод, classname или callable - - @App\MyLatteMacrosFactory # сервис с методом install() - - @App\MyLatteMacrosFactory::register # сервис с методом register() - -services: - - App\MyLatteMacrosFactory -``` - Маршрутизация ============= @@ -132,14 +126,14 @@ services: ```neon routing: - # отображать панель маршрутизации в Tracy Bar? - debugger: ... # (bool) по умолчанию true + # показывать панель маршрутизации в Tracy Bar? + debugger: ... # (bool) включено при наличии Tracy (только в режиме отладки) - # сериализует маршрутизатор в DI-контейнер + # сериализовать маршрутизатор в DI-контейнер cache: ... # (bool) по умолчанию false ``` -Маршрутизацию обычно определяем в классе [RouterFactory |routing#Коллекция маршрутов]. Альтернативно, маршруты можно определить также в конфигурации с помощью пар `маска: действие`, но этот способ не предлагает такой широкой вариативности в настройке: +Маршрутизация обычно задаётся в классе [RouterFactory |routing#Набор маршрутов]. Как вариант, маршруты можно задать и в конфигурации парами `маска: действие`, но такой способ не даёт большой гибкости: ```neon routing: @@ -152,23 +146,23 @@ routing: Константы ========= -Создание PHP-констант. +Создание констант PHP. ```neon constants: Foobar: 'baz' ``` -После запуска приложения будет создана константа `Foobar`. +Константа `Foobar` будет создана после старта приложения. .[note] -Константы не должны служить некими глобально доступными переменными. Для передачи значений в объекты используйте [внедрение зависимостей |dependency-injection:passing-dependencies]. +Константы не должны служить глобально доступными переменными. Для передачи значений объектам используйте [внедрение зависимостей |dependency-injection:passing-dependencies]. PHP === -Настройка директив PHP. Обзор всех директив можно найти на [php.net |https://www.php.net/manual/en/ini.list.php]. +Настройка директив PHP. Обзор всех директив есть на [php.net |https://www.php.net/manual/en/ini.list.php]. ```neon php: @@ -181,11 +175,12 @@ php: Эти сервисы добавляются в DI-контейнер: -| Имя | Тип | Описание -|---------------------------------------------------------- -| `application.application` | [api:Nette\Application\Application] | [запускающий все приложение |how-it-works#Nette Application] -| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] -| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | фабрика презентеров -| `application.###` | [api:Nette\Application\UI\Presenter] | отдельные презентеры -| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | фабрика объекта `Latte\Engine` -| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | фабрика для [`$this->template` |templates] +| Имя | Тип | Описание +|----------------------------|---------------------------------------------------|----------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [запускающий модуль приложения |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\IPresenterFactory] | фабрика презентеров +| `application.###` | [api:Nette\Application\UI\Presenter] | отдельные презентеры +| `routing.router` | [api:Nette\Routing\Router] | маршрутизатор +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | фабрика объекта `Latte\Engine` +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | фабрика [`$this->template` |templates] diff --git a/application/ru/creating-links.texy b/application/ru/creating-links.texy index 99e54aa1ff..2643963f02 100644 --- a/application/ru/creating-links.texy +++ b/application/ru/creating-links.texy @@ -3,56 +3,56 @@
    -Создавать ссылки в Nette просто, как указывать пальцем. Достаточно просто направить, и фреймворк уже сделает всю работу за вас. Мы покажем: +Создавать ссылки в Nette так же просто, как показать пальцем. Достаточно прицелиться, а всю работу сделает фреймворк. Мы покажем: -- как создавать ссылки в шаблонах и в других местах +- как создавать ссылки в шаблонах и не только - как отличить ссылку на текущую страницу -- что делать с недействительными ссылками +- что делать с некорректными ссылками
    -Благодаря [двусторонней маршрутизации |routing] вам никогда не придется жестко прописывать URL-адреса вашего приложения в шаблонах или коде, которые могут позже измениться, или сложно их составлять. В ссылке достаточно указать презентер и действие, передать возможные параметры, и фреймворк сам сгенерирует URL. На самом деле, это очень похоже на вызов функции. Вам это понравится. +Благодаря [двунаправленной маршрутизации |routing] вам никогда не придётся жёстко прописывать URL вашего приложения в шаблонах или коде, ведь они могут позже измениться или оказаться сложными в сборке. В ссылке достаточно указать презентер и действие, передать параметры, а URL фреймворк породит сам. По сути это очень похоже на вызов функции. Вам понравится. В шаблоне презентера ==================== -Чаще всего мы создаем ссылки в шаблонах, и отличным помощником является атрибут `n:href`: +Чаще всего мы создаём ссылки в шаблонах, и отличный помощник здесь - атрибут `n:href`: ```latte -деталь +detail ``` -Обратите внимание, что вместо HTML-атрибута `href` мы использовали [n:атрибут |latte:syntax#n:атрибуты] `n:href`. Его значением является не URL, как это было бы в случае атрибута `href`, а имя презентера и действия. +Обратите внимание, что вместо HTML-атрибута `href` мы использовали [n:атрибут |latte:syntax#n:атрибуты] `n:href`. Его значением служит не URL, как было бы у атрибута `href`, а имя презентера и действия. -Нажатие на ссылку, упрощенно говоря, похоже на вызов метода `ProductPresenter::renderShow()`. И если у него в сигнатуре есть параметры, мы можем вызвать его с аргументами: +Щелчок по ссылке, попросту говоря, похож на вызов метода `ProductPresenter::renderShow()`. А если у него есть параметры в сигнатуре, мы можем вызвать его с аргументами: ```latte -деталь продукта +product detail ``` -Можно передавать и именованные параметры. Следующая ссылка передает параметр `lang` со значением `cs`: +Можно передавать и именованные параметры. Следующая ссылка передаёт параметр `lang` со значением `en`: ```latte -деталь продукта +product detail ``` -Если метод `ProductPresenter::renderShow()` не имеет `$lang` в своей сигнатуре, он может получить значение параметра с помощью `$lang = $this->getParameter('lang')` или из [свойства |presenters#Параметры запроса]. +Если у метода `ProductPresenter::renderShow()` нет `$lang` в сигнатуре, он может получить значение параметра через `$lang = $this->getParameter('lang')` или из [свойства |presenters#Параметры запроса]. -Если параметры хранятся в массиве, их можно развернуть с помощью оператора `...` (в Latte 2.x оператором `(expand)`): +Если параметры хранятся в массиве, их можно развернуть оператором `...`: ```latte -{var $args = [$product->id, lang => cs]} -деталь продукта +{var $args = [$product->id, lang => en]} +product detail ``` -В ссылках также автоматически передаются так называемые [персистентные параметры |presenters#Персистентные параметры]. +Так называемые [постоянные параметры |presenters#Постоянные параметры] тоже передаются в ссылках автоматически. -Атрибут `n:href` очень удобен для HTML-тегов ``. Если мы хотим вывести ссылку в другом месте, например, в тексте, используем `{link}`: +Атрибут `n:href` очень удобен для HTML-тегов ``. Если мы хотим вывести ссылку в другом месте, например в тексте, мы используем `{link}`: ```latte -Адрес: {link Home:default} +URL is: {link Home:default} ``` @@ -65,96 +65,113 @@ $url = $this->link('Product:show', $product->id); ``` -Параметры можно передать также с помощью массива, где можно указать и именованные параметры: +Параметры можно передать и массивом, где можно указать и именованные параметры: ```php -$url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$product->id, 'lang' => 'en']); ``` -Ссылки можно создавать и без презентера, для этого есть [#LinkGenerator] и его метод `link()`. +Ссылки можно создавать и без презентера, с помощью [#LinkGenerator] и его метода `link()`. + +Иногда нужно создать ссылку сейчас, а сам URL породить только позже. Для этого есть метод `lazyLink()`, возвращающий объект `Nette\Application\UI\Link`. Преимущество в том, что этот объект можно передать дальше, например в шаблон, и до его отрисовки ещё изменить его параметры методом `setParameter()`. Сам URL собирается только при преобразовании объекта в строку: + +```php +$link = $this->lazyLink('Product:show', $id); +// ... +echo $link; // URL порождается только здесь +``` Ссылки на презентер =================== -Если целью ссылки является презентер и действие, используется следующий синтаксис: +Если целью ссылки служит презентер и действие, синтаксис такой: ``` [//] [[[[:]module:]presenter:]action | this] [#fragment] ``` -Формат поддерживается всеми тегами Latte и всеми методами презентера, работающими со ссылками, то есть `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`, а также [#LinkGenerator]. Так что, хотя в примерах используется `n:href`, там могла бы быть любая из этих функций. +Этот формат поддерживают все теги Latte и все методы презентера, работающие со ссылками, то есть `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()`, а также [#LinkGenerator]. Так что, хотя в примерах используется `n:href`, на его месте могла бы быть любая из этих функций. -Основной формой является `Presenter:action`: +Основная форма, таким образом, - `Презентер:действие`: ```latte -главная страница +home page ``` -Если мы ссылаемся на действие текущего презентера, мы можем опустить его имя: +Если мы ссылаемся на действие текущего презентера, его имя можно опустить: ```latte -главная страница +home page ``` -Если целью является действие `default`, мы можем его опустить, но двоеточие должно остаться: +Если целевое действие - `default`, его можно опустить, но двоеточие должно остаться: ```latte -главная страница +home page ``` -Ссылки также могут указывать на другие [модули |directory-structure#Презентеры и шаблоны]. Здесь ссылки делятся на относительные к вложенному подмодулю или абсолютные. Принцип аналогичен путям на диске, только вместо слешей используются двоеточия. Предположим, что текущий презентер является частью модуля `Front`, тогда запишем: +Ссылки могут указывать и на другие [модули |directory-structure#Презентеры и шаблоны]. Здесь ссылки различаются как относительные к вложенному подмодулю или абсолютные. Принцип аналогичен путям на диске, только вместо слешей используются двоеточия. Если считать, что текущий презентер входит в модуль `Front`, мы написали бы: ```latte -ссылка на Front:Shop:Product:show -ссылка на Admin:Product:show +link to Front:Shop:Product:show +link to Admin:Product:show ``` -Особым случаем является ссылка [на себя |#Ссылка на текущую страницу], когда в качестве цели указываем `this`. +Особый случай - ссылка [на саму себя |#Ссылка на текущую страницу], где мы указываем целью `this`. ```latte -обновить +refresh ``` -Мы можем ссылаться на определенную часть страницы через так называемый фрагмент после символа решетки `#`: +Мы можем сослаться на определённую часть страницы через так называемый фрагмент после знака решётки `#`: ```latte -ссылка на Home:default и фрагмент #main +link to Home:default and fragment #main +``` + +.{data-version:3.3.0} +Фрагмент можно задать и динамически, аргументом с ключом `#`. Его значение автоматически кодируется и имеет приоритет над фрагментом, указанным в цели: + +```php +$this->link('Home:default', ['#' => $fragment]); ``` Абсолютные пути =============== -Ссылки, генерируемые с помощью `link()` или `n:href`, всегда являются абсолютными путями (т. е. начинаются с символа `/`), но не абсолютными URL с протоколом и доменом, как `https://domain`. +Ссылки, порождаемые через `link()` или `n:href`, всегда являются абсолютными путями (то есть начинаются с `/`), но не абсолютными URL с протоколом и доменом вроде `https://domain`. + +Чтобы породить абсолютный URL, добавьте в начале два слеша (например, `n:href="//Home:"`). Как вариант, вы можете переключить презентер на порождение только абсолютных ссылок, задав `$this->absoluteUrls = true`. -Для генерации абсолютного URL добавьте в начало два слеша (например, `n:href="//Home:"`). Или можно переключить презентер, чтобы он генерировал только абсолютные ссылки, установив `$this->absoluteUrls = true`. +В шаблоне для преобразования относительного пути в абсолютный можно использовать и фильтр `|absoluteUrl`. Ссылка на текущую страницу ========================== -Цель `this` создаст ссылку на текущую страницу: +Цель `this` создаёт ссылку на текущую страницу: ```latte -обновить +refresh ``` -При этом передаются все параметры, указанные в сигнатуре метода `action()` или `render()`, если `action()` не определена. Так что если мы находимся на странице `Product:show` и `id: 123`, ссылка на `this` передаст и этот параметр. +При этом передаются все параметры, указанные в сигнатуре метода `action()` или `render()` (если `action()` не определён). Так что если мы находимся на странице `Product:show` с `id: 123`, ссылка на `this` передаст и этот параметр. -Конечно, можно указать параметры напрямую: +Разумеется, параметры можно указать и напрямую: ```latte -обновить +refresh ``` -Функция `isLinkCurrent()` проверяет, совпадает ли цель ссылки с текущей страницей. Это можно использовать, например, в шаблоне для выделения ссылок и т. п. +Функция `isLinkCurrent()` проверяет, совпадает ли цель ссылки с текущей страницей. Это можно использовать, например, в шаблоне, чтобы выделять ссылки и подобное. -Параметры такие же, как у метода `link()`, но дополнительно можно вместо конкретного действия указать подстановочный знак `*`, который означает любое действие данного презентера. +Параметры те же, что и у метода `link()`, но вместо конкретного действия можно использовать подстановочный знак `*`, означающий любое действие данного презентера. ```latte {if !isLinkCurrent('Admin:login')} - Войдите + Login {/if}
  • @@ -162,15 +179,15 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']);
  • ``` -В сочетании с `n:href` в одном элементе можно использовать сокращенную форму: +В сочетании с `n:href` в одном элементе можно использовать сокращённую форму: ```latte ... ``` -Подстановочный знак `*` можно использовать только вместо действия, а не презентера. +Подстановочный знак `*` можно использовать только вместо действия, но не вместо презентера. -Для проверки, находимся ли мы в определенном модуле или его подмодуле, используем метод `isModuleCurrent(moduleName)`. +Чтобы определить, находимся ли мы в определённом модуле или его подмодуле, используйте метод `isModuleCurrent(moduleName)`. ```latte
  • @@ -179,41 +196,57 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); ``` +Изменение базы ссылок .{data-version:3.2.7} +=========================================== + +По умолчанию относительные ссылки отсчитываются от текущего презентера. Это можно изменить с помощью `{linkBase}`: + +```latte +{linkBase Admin:Dashboard} +product detail +``` + +Ссылка приведёт на `Admin:Dashboard:Product:show`. Затрагиваются только относительные ссылки: абсолютные, начинающиеся с двоеточия, и ссылки на текущий презентер (`this`, `show`) остаются без изменений. + +`{linkBase}` действует на весь шаблон и особенно полезен в шаблонах макетов, где обеспечивает единообразные ссылки независимо от вызывающего презентера. +Тег должен стоять в начале шаблона, иначе он выбросит `CompileException`. + + Ссылки на сигнал ================ -Целью ссылки может быть не только презентер и действие, но и [сигнал |components#Сигнал] (вызывают метод `handle()`). Тогда синтаксис следующий: +Целью ссылки может быть не только презентер и действие, но и [сигнал |components#Сигнал] (он вызывает метод `handle()`). Тогда синтаксис такой: ``` [//] [sub-component:]signal! [#fragment] ``` -Сигнал отличает восклицательный знак: +Сигнал, таким образом, отличается восклицательным знаком: ```latte -сигнал +signal ``` Можно создать и ссылку на сигнал подкомпонента (или под-подкомпонента): ```latte -сигнал +signal ``` Ссылки в компоненте =================== -Поскольку [компоненты |components] являются отдельными повторно используемыми единицами, которые не должны иметь никаких связей с окружающими презентерами, ссылки здесь работают немного иначе. Атрибут Latte `n:href` и тег `{link}`, а также методы компонента, такие как `link()` и другие, считают цель ссылки **всегда именем сигнала**. Поэтому не нужно даже указывать восклицательный знак: +Поскольку [компоненты|components] - самостоятельные переиспользуемые единицы, которые не должны быть связаны с окружающими презентерами, ссылки здесь работают немного иначе. Атрибут Latte `n:href` и тег `{link}`, а также методы компонента вроде `link()` и другие **всегда считают целью ссылки имя сигнала**. Поэтому восклицательный знак даже не нужен: ```latte -сигнал, а не действие +signal, not an action ``` -Если бы мы хотели в шаблоне компонента ссылаться на презентеры, мы бы использовали для этого тег `{plink}`: +Если бы мы хотели сослаться в шаблоне компонента на презентеры, мы использовали бы тег `{plink}`: ```latte -введение +home ``` или в коде @@ -223,12 +256,12 @@ $this->getPresenter()->link('Home:default') ``` -Псевдонимы .{data-version:v3.2.2} -================================= +Псевдонимы .{data-version:3.2.3} +================================ -Иногда может быть полезно присвоить паре Presenter:action легко запоминающийся псевдоним. Например, главную страницу `Front:Home:default` назвать просто `home` или `Admin:Dashboard:default` как `admin`. +Иногда бывает полезно назначить паре "презентер:действие" легко запоминающийся псевдоним. Например, назвать главную страницу `Front:Home:default` просто `home`, а `Admin:Dashboard:default` - `admin`. -Псевдонимы определяются в [конфигурации |configuration] под ключом `application › aliases`: +Псевдонимы задаются в [конфигурации|configuration] под ключом `application › aliases`: ```neon application: @@ -238,26 +271,26 @@ application: sign: Front:Sign:in ``` -В ссылках они затем записываются с помощью символа @, например: +В ссылках они затем записываются со знаком собаки, например: ```latte -администрирование +administration ``` -Они также поддерживаются во всех методах, работающих со ссылками, таких как `redirect()` и подобных. +Они поддерживаются и во всех методах, работающих со ссылками, таких как `redirect()` и подобные. -Недействительные ссылки -======================= +Некорректные ссылки +=================== -Может случиться, что мы создадим недействительную ссылку — либо потому, что она ведет на несуществующий презентер, либо потому, что передает больше параметров, чем принимает целевой метод в своей сигнатуре, либо когда для целевого действия невозможно сгенерировать URL. Как обращаться с недействительными ссылками, определяет статическая переменная `Presenter::$invalidLinkMode`. Она может принимать комбинацию следующих значений (констант): +Может случиться, что мы создадим некорректную ссылку: либо потому, что она ведёт на несуществующий презентер, либо потому, что передаёт больше параметров, чем принимает в сигнатуре целевой метод, либо когда URL для целевого действия породить нельзя. Как обходиться с некорректными ссылками, задаётся в презентере через `$this->invalidLinkMode`. Он может принимать сочетание этих значений (констант): -- `Presenter::InvalidLinkSilent` — тихий режим, в качестве URL возвращается символ # -- `Presenter::InvalidLinkWarning` — выбрасывается предупреждение E_USER_WARNING, которое в режиме production будет залогировано, но не вызовет прерывания выполнения скрипта -- `Presenter::InvalidLinkTextual` — визуальное предупреждение, выводит ошибку прямо в ссылку -- `Presenter::InvalidLinkException` — выбрасывается исключение InvalidLinkException +- `Presenter::InvalidLinkSilent` - молчаливый режим, возвращает в качестве URL символ # +- `Presenter::InvalidLinkWarning` - выдаётся предупреждение E_USER_WARNING, которое в производственном режиме попадёт в лог, но не прервёт выполнение скрипта +- `Presenter::InvalidLinkTextual` - наглядное предупреждение, выводит ошибку прямо в ссылку +- `Presenter::InvalidLinkException` - выбрасывает InvalidLinkException -Настройка по умолчанию — `InvalidLinkWarning` в режиме production и `InvalidLinkWarning | InvalidLinkTextual` в режиме разработки. `InvalidLinkWarning` в production-среде не вызывает прерывания скрипта, но предупреждение будет залогировано. В среде разработки его перехватывает [Tracy |tracy:] и отображает синий экран. `InvalidLinkTextual` работает так, что в качестве URL возвращает сообщение об ошибке, которое начинается символами `#error:`. Чтобы такие ссылки были заметны с первого взгляда, добавим в CSS: +По умолчанию задано `InvalidLinkWarning` в производственном режиме и `InvalidLinkWarning | InvalidLinkTextual` в режиме разработки. `InvalidLinkWarning` в производственной среде не прерывает скрипт, но предупреждение попадает в лог. В среде разработки его перехватывает [Tracy |tracy:] и показывает синий экран. `InvalidLinkTextual` работает так, что возвращает как URL сообщение об ошибке, начинающееся с символов `#error:`. Чтобы такие ссылки бросались в глаза с первого взгляда, добавьте в свой CSS: ```css a[href^="#error:"] { @@ -266,7 +299,7 @@ a[href^="#error:"] { } ``` -Если мы не хотим, чтобы в среде разработки генерировались предупреждения, мы можем установить тихий режим прямо в [конфигурации |configuration]. +Если мы не хотим, чтобы в среде разработки возникали предупреждения, мы можем подавить их прямо в [конфигурации|configuration]. ```neon application: @@ -277,10 +310,10 @@ application: LinkGenerator ============= -Как создавать ссылки с таким же удобством, как у метода `link()`, но без присутствия презентера? Для этого существует [api:Nette\Application\LinkGenerator]. +Как создавать ссылки с таким же удобством, как методом `link()`, но без презентера? Для этого и существует [api:Nette\Application\LinkGenerator]. -LinkGenerator — это сервис, который вы можете получить через конструктор и затем создавать ссылки его методом `link()`. +LinkGenerator - сервис, который вы можете получить через конструктор и затем создавать ссылки его методом `link()`. -По сравнению с презентерами есть разница. LinkGenerator создает все ссылки сразу как абсолютные URL. И далее не существует "текущего презентера", поэтому нельзя в качестве цели указать только имя действия `link('default')` или указывать относительные пути к модулям. +По сравнению с презентерами есть отличие. LinkGenerator создаёт все ссылки сразу как абсолютные URL. Кроме того, "текущего презентера" нет, поэтому нельзя указать целью только имя действия `link('default')` или использовать относительные пути к модулям. -Недействительные ссылки всегда выбрасывают `Nette\Application\UI\InvalidLinkException`. +Некорректные ссылки всегда выбрасывают `Nette\Application\UI\InvalidLinkException`. diff --git a/application/ru/directory-structure.texy b/application/ru/directory-structure.texy index 9c1abc41af..58cb3e5bba 100644 --- a/application/ru/directory-structure.texy +++ b/application/ru/directory-structure.texy @@ -3,43 +3,43 @@
    -Как спроектировать понятную и масштабируемую структуру каталогов для проектов на Nette Framework? Мы покажем проверенные практики, которые помогут вам организовать код. Вы узнаете: +Как спроектировать наглядную и масштабируемую структуру каталогов для проектов на Nette Framework? Мы покажем проверенные практики, которые помогут вам упорядочить код. Вы узнаете: -- как **логически разделить** приложение на каталоги -- как спроектировать структуру так, чтобы она **хорошо масштабировалась** с ростом проекта -- какие существуют **возможные альтернативы** и их преимущества или недостатки +- как **логично разложить** приложение по каталогам +- как спроектировать структуру так, чтобы она **хорошо масштабировалась** по мере роста проекта +- какие есть **возможные альтернативы** и в чём их плюсы и минусы
    -Важно отметить, что сам Nette Framework не привязан к какой-либо конкретной структуре. Он разработан так, чтобы его можно было легко адаптировать к любым потребностям и предпочтениям. +Важно упомянуть, что сам Nette Framework не навязывает никакой конкретной структуры. Он спроектирован так, чтобы легко подстраиваться под любые потребности и предпочтения. Базовая структура проекта ========================= -Хотя Nette Framework не диктует никакой жесткой структуры каталогов, существует проверенное стандартное расположение в виде [Web Project |https://github.com/nette/web-project]: +Хотя Nette Framework не диктует никакой фиксированной структуры каталогов, есть проверенное расположение по умолчанию в виде [Web Project|https://github.com/nette/web-project]: /--pre web-project/ -├── app/ ← каталог с приложением -├── assets/ ← файлы SCSS, JS, изображения..., альтернативно resources/ +├── app/ ← каталог приложения +├── assets/ ← файлы SCSS, JS, изображения..., как вариант resources/ ├── bin/ ← скрипты для командной строки ├── config/ ← конфигурация -├── log/ ← логируемые ошибки +├── log/ ← записанные ошибки ├── temp/ ← временные файлы, кеш ├── tests/ ← тесты ├── vendor/ ← библиотеки, установленные Composer └── www/ ← публичный каталог (document-root) \-- -Эту структуру можно произвольно изменять в соответствии с вашими потребностями - папки можно переименовывать или перемещать. Затем достаточно лишь изменить относительные пути к каталогам в файле `Bootstrap.php` и, возможно, `composer.json`. Больше ничего не требуется, никакой сложной реконфигурации, никаких изменений констант. Nette обладает умным автоопределением и автоматически распознает расположение приложения, включая его базовый URL. +Вы можете свободно менять эту структуру под свои нужды: переименовывать или переносить папки. Затем достаточно поправить относительные пути к каталогам в `Bootstrap.php` и, возможно, в `composer.json`. Больше ничего не нужно, никакой сложной перенастройки, никаких изменений констант. У Nette есть умное автоопределение, и он сам распознаёт расположение приложения, включая его базовый URL. Принципы организации кода ========================= -Когда вы впервые изучаете новый проект, вы должны быстро в нем сориентироваться. Представьте, что вы открываете каталог `app/Model/` и видите следующую структуру: +Когда вы впервые изучаете новый проект, вы должны быстро сориентироваться. Представьте, что вы заходите в каталог `app/Model/` и видите такую структуру: /--pre app/Model/ @@ -48,7 +48,7 @@ └── Entities/ \-- -Из нее вы узнаете только то, что проект использует какие-то сервисы, репозитории и сущности. О реальном назначении приложения вы не узнаете абсолютно ничего. +Отсюда вы узнаёте только то, что в проекте используются какие-то сервисы, репозитории и сущности. О настоящем назначении приложения вы не узнаёте ничего. Посмотрим на другой подход - **организацию по доменам**: @@ -60,51 +60,51 @@ └── Product/ \-- -Здесь все иначе - с первого взгляда ясно, что это интернет-магазин. Уже сами названия каталогов говорят о том, что умеет приложение - работает с платежами, заказами и продуктами. +Здесь всё иначе: с первого взгляда ясно, что это интернет-магазин. Сами имена каталогов раскрывают, что умеет приложение: оно работает с платежами, заказами и товарами. -Первый подход (организация по типу классов) на практике приносит ряд проблем: код, который логически связан, разбросан по разным папкам, и вам приходится переключаться между ними. Поэтому мы будем организовывать по доменам. +Первый подход (организация по типам классов) приносит на практике несколько проблем: логически связанный код раздроблен по разным папкам, и вам приходится прыгать между ними. Поэтому мы будем организовывать по доменам. -Пространства имен +Пространства имён ----------------- -Принято, чтобы структура каталогов соответствовала пространствам имен в приложении. Это означает, что физическое расположение файлов соответствует их пространству имен. Например, класс, расположенный в `app/Model/Product/ProductRepository.php`, должен иметь пространство имен `App\Model\Product`. Этот принцип помогает ориентироваться в коде и упрощает автозагрузку. +Принято, чтобы структура каталогов соответствовала пространствам имён приложения. Это значит, что физическое расположение файлов совпадает с их пространством имён. Например, у класса, лежащего в `app/Model/Product/ProductRepository.php`, должно быть пространство имён `App\Model\Product`. Этот принцип помогает ориентироваться в коде и упрощает автозагрузку. -Единственное vs множественное число в названиях ------------------------------------------------ +Единственное и множественное число в именах +------------------------------------------- -Обратите внимание, что для основных каталогов приложения мы используем единственное число: `app`, `config`, `log`, `temp`, `www`. Точно так же и внутри приложения: `Model`, `Core`, `Presentation`. Это потому, что каждый из них представляет собой единую целостную концепцию. +Обратите внимание, что для главных каталогов приложения мы используем единственное число: `app`, `config`, `log`, `temp`, `www`. То же и внутри приложения: `Model`, `Core`, `Presentation`. Это потому, что каждый из них представляет одно цельное понятие. -Аналогично, например, `app/Model/Product` представляет все, что связано с продуктами. Мы не назовем это `Products`, потому что это не папка, полная продуктов (там были бы файлы `nokia.php`, `samsung.php`). Это пространство имен, содержащее классы для работы с продуктами - `ProductRepository.php`, `ProductService.php`. +Точно так же `app/Model/Product` представляет всё, что связано с товарами. Мы не называем его `Products`, потому что это не папка, полная товаров (в ней лежали бы файлы вроде `nokia.php`, `samsung.php`). Это пространство имён с классами для работы с товарами - `ProductRepository.php`, `ProductService.php`. -Папка `app/Tasks` находится во множественном числе, потому что она содержит набор отдельных исполняемых скриптов - `CleanupTask.php`, `ImportTask.php`. Каждый из них является отдельной единицей. +Папка `app/Tasks` во множественном числе, потому что содержит набор отдельных исполняемых скриптов - `CleanupTask.php`, `ImportTask.php`. Каждый из них - самостоятельная единица. -Для согласованности рекомендуем использовать: -- Единственное число для пространства имен, представляющего функциональное целое (даже если оно работает с несколькими сущностями) -- Множественное число для коллекций отдельных единиц -- В случае неопределенности или если вы не хотите об этом думать, выберите единственное число +Ради единообразия мы рекомендуем использовать: +- единственное число для пространств имён, представляющих функциональную единицу (даже если она работает с несколькими сущностями) +- множественное число для наборов самостоятельных единиц +- в случае сомнений или если не хочется над этим думать, выбирайте единственное число Публичный каталог `www/` ======================== -Этот каталог является единственным доступным из веба (так называемый document-root). Часто можно встретить название `public/` вместо `www/` - это всего лишь вопрос соглашения и на функциональность не влияет. Каталог содержит: -- [Точку входа |bootstrapping#index.php] приложения `index.php` -- Файл `.htaccess` с правилами для mod_rewrite (для Apache) -- Статические файлы (CSS, JavaScript, изображения) -- Загруженные файлы +Этот каталог - единственный, доступный из веба (document-root). Часто вместо `www/` вам может встретиться имя `public/` - это лишь вопрос соглашения, на работу приложения оно не влияет. Каталог содержит: +- [точку входа |bootstrapping#index.php] приложения `index.php` +- файл `.htaccess` с правилами для mod_rewrite (для Apache) +- статические файлы (CSS, JavaScript, изображения) +- загруженные файлы -Для правильной защиты приложения крайне важно иметь правильно [настроенный document-root |nette:troubleshooting#Как изменить или удалить каталог www из URL]. +Для правильной безопасности приложения принципиально важно [правильно настроить document-root |nette:troubleshooting#Как изменить или убрать из URL каталог www?]. .[note] -Никогда не размещайте в этом каталоге папку `node_modules/` - она содержит тысячи файлов, которые могут быть исполняемыми и не должны быть общедоступными. +Никогда не помещайте в этот каталог папку `node_modules/`: в ней тысячи файлов, которые могут быть исполняемыми и не должны быть публично доступны. Каталог приложения `app/` ========================= -Это основной каталог с кодом приложения. Базовая структура: +Это главный каталог с кодом приложения. Базовая структура: /--pre app/ @@ -112,24 +112,24 @@ ├── Model/ ← бизнес-логика ├── Presentation/ ← презентеры и шаблоны ├── Tasks/ ← командные скрипты -└── Bootstrap.php ← загрузочный класс приложения +└── Bootstrap.php ← стартовый класс приложения \-- -`Bootstrap.php` — это [стартовый класс приложения |bootstrapping], который инициализирует среду, загружает конфигурацию и создает DI-контейнер. +`Bootstrap.php` - [стартовый класс приложения|bootstrapping], который инициализирует окружение, загружает конфигурацию и создаёт DI-контейнер. -Давайте теперь рассмотрим отдельные подкаталоги подробнее. +Теперь рассмотрим отдельные подкаталоги подробнее. Презентеры и шаблоны ==================== -Презентационная часть приложения находится в каталоге `app/Presentation`. Альтернативой является короткое `app/UI`. Это место для всех презентеров, их шаблонов и возможных вспомогательных классов. +Презентационная часть приложения находится в каталоге `app/Presentation`. Альтернатива - более короткий `app/UI`. Это место для всех презентеров, их шаблонов и любых связанных вспомогательных классов. -Этот слой мы организуем по доменам. В сложном проекте, который сочетает в себе интернет-магазин, блог и API, структура выглядела бы так: +Этот слой мы организуем по доменам. В сложном проекте, объединяющем интернет-магазин, блог и API, структура выглядела бы так: /--pre app/Presentation/ -├── Shop/ ← фронтенд интернет-магазина +├── Shop/ ← витрина интернет-магазина │ ├── Product/ │ ├── Cart/ │ └── Order/ @@ -139,27 +139,27 @@ ├── Admin/ ← администрирование │ ├── Dashboard/ │ └── Products/ -└── Api/ ← конечные точки API +└── Api/ ← точки входа API └── V1/ \-- -Напротив, для простого блога мы бы использовали разделение: +И наоборот, для простого блога мы использовали бы такую структуру: /--pre app/Presentation/ -├── Front/ ← фронтенд сайта +├── Front/ ← публичная часть сайта │ ├── Home/ │ └── Post/ ├── Admin/ ← администрирование │ ├── Dashboard/ │ └── Posts/ ├── Error/ -└── Export/ ← RSS, карты сайта и т. д. +└── Export/ ← RSS, карты сайта и прочее \-- -Папки, такие как `Home/` или `Dashboard/`, содержат презентеры и шаблоны. Папки, такие как `Front/`, `Admin/` или `Api/`, называются **модулями**. Технически это обычные каталоги, которые служат для логического разделения приложения. +Папки вроде `Home/` или `Dashboard/` содержат презентеры и шаблоны. Папки вроде `Front/`, `Admin/` или `Api/` называются **модулями**. Технически это обычные каталоги, служащие для логического разбиения приложения. -Каждая папка с презентером содержит одноименный презентер и его шаблоны. Например, папка `Dashboard/` содержит: +Каждая папка с презентером содержит сам файл презентера и его шаблоны. Например, папка `Dashboard/` содержит: /--pre Dashboard/ @@ -167,7 +167,7 @@ └── default.latte ← шаблон \-- -Эта структура каталогов отражается в пространствах имен классов. Например, `DashboardPresenter` находится в пространстве имен `App\Presentation\Admin\Dashboard` (см. [#маппинг презентеров]): +Эта структура каталогов отражается в пространствах имён классов. Например, `DashboardPresenter` находится в пространстве имён `App\Presentation\Admin\Dashboard` (см. [#Отображение презентеров]): ```php namespace App\Presentation\Admin\Dashboard; @@ -178,22 +178,22 @@ class DashboardPresenter extends Nette\Application\UI\Presenter } ``` -На презентер `Dashboard` внутри модуля `Admin` мы ссылаемся в приложении с помощью двоеточия как на `Admin:Dashboard`. На его действие `default` затем как на `Admin:Dashboard:default`. В случае вложенных модулей мы используем больше двоеточий, например `Shop:Order:Detail:default`. +К презентеру `Dashboard` внутри модуля `Admin` мы обращаемся в приложении записью через двоеточие как `Admin:Dashboard`. К его действию `default` - как `Admin:Dashboard:default`. Для вложенных модулей мы используем несколько двоеточий, например `Shop:Order:Detail:default`. Гибкое развитие структуры ------------------------- -Одним из больших преимуществ этой структуры является то, как элегантно она адаптируется к растущим потребностям проекта. В качестве примера возьмем часть, генерирующую XML-фиды. В начале у нас простая форма: +Одно из больших преимуществ этой структуры в том, как изящно она подстраивается под растущие потребности проекта. Возьмём для примера часть, порождающую XML-ленты. Поначалу у нас простой вид: /--pre Export/ -├── ExportPresenter.php ← один презентер для всех экспортов -├── sitemap.latte ← шаблон для карты сайта -└── feed.latte ← шаблон для RSS-фида +├── ExportPresenter.php ← один презентер для всех лент +├── sitemap.latte ← шаблон карты сайта +└── feed.latte ← шаблон RSS-ленты \-- -Со временем появляются новые типы фидов, и для них требуется больше логики... Нет проблем! Папка `Export/` просто становится модулем: +Со временем добавляются новые виды лент, и логики для них нужно больше... Не беда! Папка `Export/` просто становится модулем: /--pre Export/ @@ -202,52 +202,52 @@ class DashboardPresenter extends Nette\Application\UI\Presenter │ └── sitemap.latte └── Feed/ ├── FeedPresenter.php - ├── zbozi.latte ← фид для Zboží.cz - └── heureka.latte ← фид для Heureka.cz + ├── amazon.latte ← лента для Amazon + └── ebay.latte ← лента для eBay \-- -Эта трансформация абсолютно плавная - достаточно создать новые подпапки, распределить в них код и обновить ссылки (например, с `Export:feed` на `Export:Feed:zbozi`). Благодаря этому мы можем постепенно расширять структуру по мере необходимости, уровень вложенности никак не ограничен. +Это преобразование проходит совершенно гладко: достаточно создать новые подпапки, разложить по ним код и обновить ссылки (например, с `Export:feed` на `Export:Feed:amazon`). Благодаря этому мы можем постепенно расширять структуру по мере надобности, уровень вложенности ничем не ограничен. -Если, например, в администрировании у вас много презентеров, связанных с управлением заказами, таких как `OrderDetail`, `OrderEdit`, `OrderDispatch` и т. д., вы можете для лучшей организации в этом месте создать модуль (папку) `Order`, в котором будут (папки для) презентеров `Detail`, `Edit`, `Dispatch` и другие. +Например, если в администрировании у вас много презентеров, связанных с управлением заказами, таких как `OrderDetail`, `OrderEdit`, `OrderDispatch` и другие, для лучшего порядка вы можете создать модуль (папку) `Order`, которая будет содержать (папки для) презентеров `Detail`, `Edit`, `Dispatch` и прочих. Расположение шаблонов --------------------- -В предыдущих примерах мы видели, что шаблоны размещаются прямо в папке с презентером: +В предыдущих примерах мы видели, что шаблоны лежат прямо в папке с презентером: /--pre Dashboard/ ├── DashboardPresenter.php ← презентер -├── DashboardTemplate.php ← необязательный класс для шаблона +├── DashboardTemplate.php ← необязательный класс шаблона └── default.latte ← шаблон \-- -Это расположение на практике оказывается наиболее удобным - все связанные файлы у вас под рукой. +На практике такое расположение оказывается самым удобным: все связанные файлы у вас под рукой. -Альтернативно, вы можете разместить шаблоны в подпапке `templates/`. Nette поддерживает оба варианта. Вы даже можете разместить шаблоны совершенно вне папки `Presentation/`. Все о возможностях размещения шаблонов вы найдете в главе [Поиск шаблонов |templates#Поиск шаблонов]. +Как вариант, вы можете поместить шаблоны в подпапку `templates/`. Nette поддерживает оба варианта. Вы можете даже разместить шаблоны совсем вне папки `Presentation/`. Всё о возможностях размещения шаблонов можно найти в главе [Поиск шаблонов |templates#Поиск шаблона]. Вспомогательные классы и компоненты ----------------------------------- -К презентерам и шаблонам часто относятся и другие вспомогательные файлы. Мы разместим их логически в соответствии с их областью действия: +Презентерам и шаблонам часто сопутствуют другие вспомогательные файлы. Мы размещаем их логично, по области действия: -1. **Прямо у презентера** в случае специфических компонентов для данного презентера: +1. **Прямо рядом с презентером** в случае компонентов, относящихся только к нему: /--pre Product/ ├── ProductPresenter.php -├── ProductGrid.php ← компонент для вывода продуктов -└── FilterForm.php ← форма для фильтрации +├── ProductGrid.php ← компонент для вывода товаров +└── FilterForm.php ← форма фильтрации \-- -2. **Для модуля** - рекомендуем использовать папку `Accessory`, которая размещается удобно в начале алфавита: +2. **Для модуля** - мы рекомендуем использовать папку `Accessory`, которая удобно оказывается в начале по алфавиту: /--pre Front/ ├── Accessory/ -│ ├── NavbarControl.php ← компоненты для фронтенда +│ ├── NavbarControl.php ← компоненты публичной части │ └── TemplateFilters.php ├── Product/ └── Cart/ @@ -263,57 +263,57 @@ class DashboardPresenter extends Nette\Application\UI\Presenter └── Admin/ \-- -Или вы можете разместить вспомогательные классы, такие как `LatteExtension.php` или `TemplateFilters.php`, в инфраструктурной папке `app/Core/Latte/`. А компоненты в `app/Components`. Выбор зависит от привычек команды. +Как вариант, вспомогательные классы вроде `LatteExtension.php` или `TemplateFilters.php` можно поместить в инфраструктурную папку `app/Core/Latte/`. А компоненты в `app/Components`. Выбор зависит от соглашений команды. Модель - сердце приложения ========================== -Модель содержит всю бизнес-логику приложения. Для ее организации снова действует правило - структурируем по доменам: +Модель содержит всю бизнес-логику приложения. Правило её организации снова то же - структура по доменам: /--pre app/Model/ -├── Payment/ ← все, что связано с платежами +├── Payment/ ← всё о платежах │ ├── PaymentFacade.php ← главная точка входа │ ├── PaymentRepository.php │ ├── Payment.php ← сущность -├── Order/ ← все, что связано с заказами +├── Order/ ← всё о заказах │ ├── OrderFacade.php │ ├── OrderRepository.php │ ├── Order.php -└── Shipping/ ← все, что связано с доставкой +└── Shipping/ ← всё о доставке \-- -В модели обычно встречаются следующие типы классов: +В модели вам обычно встречаются такие типы классов: -**Фасады**: представляют собой главную точку входа в конкретный домен приложения. Они действуют как оркестратор, координирующий взаимодействие между различными сервисами для реализации полных сценариев использования (например, "создать заказ" или "обработать платеж"). Под своим оркестрационным слоем фасад скрывает детали реализации от остальной части приложения, предоставляя чистый интерфейс для работы с данным доменом. +**Фасады**: представляют главную точку входа в определённый домен приложения. Они выступают дирижёрами, согласующими взаимодействие разных сервисов ради выполнения полных сценариев (вроде "создать заказ" или "обработать платёж"). Под своим дирижёрским слоем фасад скрывает от остального приложения подробности реализации и тем самым даёт чистый интерфейс для работы с этим доменом. ```php class OrderFacade { public function createOrder(Cart $cart): Order { - // валидация + // проверка // создание заказа - // отправка электронной почты + // отправка письма // запись в статистику } } ``` -**Сервисы**: фокусируются на специфической бизнес-операции в рамках домена. В отличие от фасада, который оркеструет целые сценарии использования, сервис реализует конкретную бизнес-логику (например, расчет цен или обработку платежей). Сервисы обычно не имеют состояния и могут использоваться либо фасадами как строительные блоки для более сложных операций, либо напрямую другими частями приложения для более простых задач. +**Сервисы**: сосредоточены на конкретных бизнес-операциях внутри домена. В отличие от фасадов, дирижирующих целыми сценариями, сервис реализует конкретную бизнес-логику (вроде расчёта цен или обработки платежей). Сервисы обычно не хранят состояния и могут использоваться либо фасадами как строительные блоки более сложных операций, либо напрямую другими частями приложения для более простых задач. ```php class PricingService { public function calculateTotal(Order $order): Money { - // расчет цены + // расчёт цены } } ``` -**Репозитории**: обеспечивают все взаимодействие с хранилищем данных, обычно базой данных. Их задача - загрузка и сохранение сущностей и реализация методов для их поиска. Репозиторий изолирует остальную часть приложения от деталей реализации базы данных и предоставляет объектно-ориентированный интерфейс для работы с данными. +**Репозитории**: занимаются всем общением с хранилищем данных, обычно с базой данных. Их задача - загружать и сохранять сущности и реализовывать методы их поиска. Репозиторий заслоняет остальное приложение от подробностей реализации базы данных и даёт объектный интерфейс для работы с данными. ```php class OrderRepository @@ -328,10 +328,10 @@ class OrderRepository } ``` -**Сущности**: объекты, представляющие основные бизнес-концепции в приложении, которые имеют свою идентичность и изменяются со временем. Обычно это классы, отображаемые на таблицы базы данных с помощью ORM (например, Nette Database Explorer или Doctrine). Сущности могут содержать бизнес-правила, касающиеся их данных, и логику валидации. +**Сущности**: объекты, представляющие главные бизнес-понятия приложения, у которых есть собственная идентичность и которые меняются со временем. Обычно это классы, отображённые на таблицы базы данных через ORM (например, Nette Database Explorer или Doctrine). Сущности могут содержать бизнес-правила, связанные с их данными, и логику проверки. ```php -// Сущность, отображаемая на таблицу базы данных orders +// Сущность, отображённая на таблицу 'orders' в базе данных class Order extends Nette\Database\Table\ActiveRow { public function addItem(Product $product, int $quantity): void @@ -345,13 +345,13 @@ class Order extends Nette\Database\Table\ActiveRow } ``` -**Объекты-значения**: неизменяемые объекты, представляющие значения без собственной идентичности - например, денежная сумма или адрес электронной почты. Два экземпляра объекта-значения с одинаковыми значениями считаются идентичными. +**Объекты-значения**: неизменяемые объекты, представляющие значения без собственной идентичности, например денежную сумму или адрес электронной почты. Два экземпляра объекта-значения с одинаковыми значениями считаются одинаковыми. Инфраструктурный код ==================== -Папка `Core/` (или также `Infrastructure/`) является домом для технической основы приложения. Инфраструктурный код обычно включает: +Папка `Core/` (или, как вариант, `Infrastructure/`) - дом для технической основы приложения. Инфраструктурный код обычно включает: /--pre app/Core/ @@ -370,7 +370,7 @@ class Order extends Nette\Database\Table\ActiveRow └── Stripe/ \-- -Для небольших проектов, разумеется, достаточно плоского разделения: +Для небольших проектов, естественно, достаточно плоской структуры: /--pre Core/ @@ -381,27 +381,27 @@ class Order extends Nette\Database\Table\ActiveRow Это код, который: -- Решает техническую инфраструктуру (маршрутизация, логирование, кеширование) +- Занимается технической инфраструктурой (маршрутизация, логирование, кеширование) - Интегрирует внешние сервисы (Sentry, Elasticsearch, Redis) -- Предоставляет базовые сервисы для всего приложения (почта, база данных) -- В основном не зависит от конкретного домена - кеш или логгер работает одинаково для интернет-магазина или блога. +- Предоставляет базовые сервисы всему приложению (почта, база данных) +- Как правило, не зависит от конкретного домена: кеш или логгер работают одинаково и для интернет-магазина, и для блога. -Сомневаетесь, принадлежит ли определенный класс сюда или к модели? Ключевое различие в том, что код в `Core/`: +Не уверены, куда относится определённый класс - сюда или в модель? Ключевое отличие в том, что код в `Core/`: -- Ничего не знает о домене (продукты, заказы, статьи) -- В основном его можно перенести в другой проект -- Решает "как это работает" (как отправить письмо), а не "что это делает" (какое письмо отправить) +- Ничего не знает о домене (товарах, заказах, статьях) +- Обычно можно перенести в другой проект +- Решает "как это работает" (как отправить письмо), а не "что оно делает" (какое письмо отправить) Пример для лучшего понимания: -- `App\Core\MailerFactory` - создает экземпляры класса для отправки электронной почты, решает настройки SMTP -- `App\Model\OrderMailer` - использует `MailerFactory` для отправки электронных писем о заказах, знает их шаблоны и когда их нужно отправлять +- `App\Core\MailerFactory` - создаёт экземпляры класса для отправки писем, занимается настройками SMTP +- `App\Model\OrderMailer` - использует `MailerFactory` для отправки писем о заказах, знает их шаблоны и то, когда их следует отправлять Командные скрипты ================= -Приложения часто нуждаются в выполнении действий вне обычных HTTP-запросов - будь то обработка данных в фоновом режиме, обслуживание или периодические задачи. Для запуска служат простые скрипты в каталоге `bin/`, саму логику реализации мы размещаем в `app/Tasks/` (или `app/Commands/`). +Приложениям часто нужно выполнять действия вне обычных HTTP-запросов, будь то фоновая обработка данных, обслуживание или периодические задачи. Для запуска служат простые скрипты в каталоге `bin/`, а сама логика реализации размещается в `app/Tasks/` (или `app/Commands/`). Пример: @@ -414,27 +414,27 @@ class Order extends Nette\Database\Table\ActiveRow │ ├── ImportProducts.php ← импорт из системы поставщика │ └── SyncOrders.php ← синхронизация заказов └── Scheduled/ ← регулярные задачи - ├── NewsletterCommand.php ← рассылка новостей - └── ReminderCommand.php ← уведомления клиентам + ├── NewsletterCommand.php ← рассылка новостных писем + └── ReminderCommand.php ← уведомления клиентов \-- -Что относится к модели, а что к командным скриптам? Например, логика отправки одного электронного письма является частью модели, массовая рассылка тысяч писем уже относится к `Tasks/`. +Что относится к модели, а что к командным скриптам? Например, логика отправки одного письма - часть модели, а массовая отправка тысяч писем относится к `Tasks/`. -Задачи обычно [запускаем из командной строки |https://blog.nette.org/en/cli-scripts-in-nette-application] или через cron. Их можно запускать и через HTTP-запрос, но необходимо помнить о безопасности. Презентер, который запускает задачу, нужно защитить, например, только для вошедших пользователей или сильным токеном и доступом с разрешенных IP-адресов. Для длительных задач необходимо увеличить временной лимит скрипта и использовать `session_write_close()`, чтобы сессия не блокировалась. +Задачи обычно запускаются из командной строки или через cron: скрипт в `bin/` создаёт DI-контейнер методом [bootConsoleApplication() |bootstrapping#Разные окружения] и достаёт из него нужный сервис. Их можно запускать и HTTP-запросом, но при этом нужно подумать о безопасности. Презентер, запускающий задачу, нужно защитить, например только для вошедших пользователей или сильным токеном и доступом с разрешённых IP-адресов. Для долгих задач нужно увеличить ограничение времени работы скрипта и использовать `session_write_close()`, чтобы не блокировать сессию. Другие возможные каталоги ========================= -Кроме упомянутых базовых каталогов, вы можете в соответствии с потребностями проекта добавить другие специализированные папки. Посмотрим на наиболее частые из них и их использование: +Помимо упомянутых базовых каталогов вы можете добавить и другие специализированные папки под нужды проекта. Посмотрим на самые частые и на их применение: /--pre app/ -├── Api/ ← логика для API, независимая от презентационного слоя -├── Database/ ← миграционные скрипты и сидеры для тестовых данных +├── Api/ ← логика API, независимая от презентационного слоя +├── Database/ ← миграционные скрипты и сидеры тестовых данных ├── Components/ ← общие визуальные компоненты для всего приложения -├── Event/ ← полезно, если вы используете событийно-ориентированную архитектуру -├── Mail/ ← шаблоны электронной почты и связанная логика +├── Event/ ← полезно при событийно-ориентированной архитектуре +├── Mail/ ← шаблоны писем и связанная логика └── Utils/ ← вспомогательные классы \-- @@ -447,53 +447,53 @@ class Order extends Nette\Database\Table\ActiveRow │ └── UserForm.php ├── Grid/ ← компоненты для вывода данных │ └── DataGrid.php -└── Navigation/ ← навигационные элементы +└── Navigation/ ← элементы навигации ├── Breadcrumbs.php └── Menu.php \-- -Сюда относятся компоненты, имеющие более сложную логику. Если вы хотите делиться компонентами между несколькими проектами, рекомендуется выделить их в отдельный composer-пакет. +Сюда относятся компоненты с более сложной логикой. Если вы хотите использовать компоненты в нескольких проектах, стоит вынести их в отдельный пакет Composer. -В каталог `app/Mail` можно поместить управление электронной почтой: +В каталоге `app/Mail` вы можете разместить управление почтовым общением: /--pre app/Mail/ -├── templates/ ← шаблоны электронной почты +├── templates/ ← шаблоны писем │ ├── order-confirmation.latte │ └── welcome.latte └── OrderMailer.php \-- -Маппинг презентеров -=================== +Отображение презентеров +======================= -Маппинг определяет правила для вывода имени класса из имени презентера. Мы указываем их в [конфигурации |configuration] под ключом `application › mapping`. +Отображение задаёт правила выведения имени класса из имени презентера. Мы указываем их в [конфигурации|configuration] под ключом `application › mapping`. -На этой странице мы показали, что презентеры размещаем в папке `app/Presentation` (или `app/UI`). Эту конвенцию мы должны сообщить Nette в конфигурационном файле. Достаточно одной строки: +На этой странице мы показали, что размещаем презентеры в папке `app/Presentation` (или `app/UI`). Начиная с Nette Application 3.3 это соглашение по умолчанию, которое настраивать не нужно. Если вы используете другую структуру или хотите указать отображение явно, настройке по умолчанию соответствует такая строка: ```neon application: mapping: App\Presentation\*\**Presenter ``` -Как работает маппинг? Для лучшего понимания сначала представим приложение без модулей. Мы хотим, чтобы классы презентеров попадали в пространство имен `App\Presentation`, чтобы презентер `Home` отображался на класс `App\Presentation\HomePresenter`. Этого мы достигнем с помощью следующей конфигурации: +Как работает отображение? Для лучшего понимания сначала представим приложение без модулей. Мы хотим, чтобы классы презентеров попадали в пространство имён `App\Presentation`, так чтобы презентер `Home` отображался в класс `App\Presentation\HomePresenter`. Этого мы добиваемся такой конфигурацией: ```neon application: mapping: App\Presentation\*Presenter ``` -Маппинг работает так, что имя презентера `Home` заменяет звездочку в маске `App\Presentation\*Presenter`, тем самым мы получаем итоговое имя класса `App\Presentation\HomePresenter`. Просто! +Отображение работает так, что звёздочка в маске `App\Presentation\*Presenter` заменяется именем презентера `Home`, и получается итоговое имя класса `App\Presentation\HomePresenter`. Просто! -Однако, как вы видите в примерах в этой и других главах, классы презентеров мы размещаем в одноименных подкаталогах, например, презентер `Home` отображается на класс `App\Presentation\Home\HomePresenter`. Этого мы достигнем удвоением звездочки: +Однако, как вы видите в примерах в этой и других главах, мы размещаем классы презентеров в одноимённых подкаталогах, например презентер `Home` отображается в класс `App\Presentation\Home\HomePresenter`. Этого мы добиваемся двойной звёздочкой `**` (требуется Nette Application 3.2.3): ```neon application: mapping: App\Presentation\**Presenter ``` -Теперь перейдем к маппингу презентеров в модули. Для каждого модуля можно определить специфический маппинг: +Теперь перейдём к отображению презентеров в модулях. Мы можем задать для каждого модуля своё отображение: ```neon application: @@ -503,9 +503,9 @@ application: Api: App\Api\*Presenter ``` -Согласно этой конфигурации, презентер `Front:Home` отображается на класс `App\Presentation\Front\Home\HomePresenter`, в то время как презентер `Api:OAuth` на класс `App\Api\OAuthPresenter`. +По этой конфигурации презентер `Front:Home` отображается в класс `App\Presentation\Front\Home\HomePresenter`, а презентер `Api:OAuth` - в класс `App\Api\OAuthPresenter`. -Поскольку модули `Front` и `Admin` имеют схожий способ маппинга, и таких модулей, скорее всего, будет больше, можно создать общее правило, которое их заменит. В маску класса так добавится новая звездочка для модуля: +Поскольку у модулей `Front` и `Admin` похожий шаблон отображения, а таких модулей, скорее всего, будет больше, можно создать общее правило, которое их заменит. В маску класса добавляется новая звёздочка для модуля: ```neon application: @@ -514,9 +514,9 @@ application: Api: App\Api\*Presenter ``` -Это работает и для более глубоко вложенных структур каталогов, таких как, например, презентер `Admin:User:Edit`, сегмент со звездочкой повторяется для каждого уровня, и результатом является класс `App\Presentation\Admin\User\Edit\EditPresenter`. +Это работает и для более глубоко вложенных структур каталогов, например для презентера `Admin:User:Edit`, где сегмент со звёздочкой повторяется для каждого уровня модуля, и получается класс `App\Presentation\Admin\User\Edit\EditPresenter`. -Альтернативной записью является использование массива, состоящего из трех сегментов, вместо строки. Эта запись эквивалентна предыдущей: +Альтернативная запись - использовать вместо строки массив из трёх сегментов. Для показанных выше примеров такая запись равнозначна предыдущей: ```neon application: diff --git a/application/ru/how-it-works.texy b/application/ru/how-it-works.texy index 684db1a876..41a097e559 100644 --- a/application/ru/how-it-works.texy +++ b/application/ru/how-it-works.texy @@ -3,10 +3,10 @@
    -Вы читаете основной документ документации Nette. Вы узнаете весь принцип работы веб-приложений. От А до Я, с момента рождения до последнего вздоха PHP-скрипта. После прочтения вы будете знать: +Сейчас вы читаете основополагающую главу документации Nette. Вы узнаете полные принципы работы веб-приложений от А до Я, с момента рождения запроса до завершения выполнения PHP-скрипта. После прочтения вы будете понимать: -- как все это работает -- что такое Bootstrap, Presenter и DI-контейнер +- как всё это работает +- что такое Bootstrap, презентер и DI-контейнер - как выглядит структура каталогов
    @@ -15,89 +15,89 @@ Структура каталогов =================== -Откройте пример скелета веб-приложения под названием [WebProject |https://github.com/nette/web-project] и во время чтения вы можете смотреть на файлы, о которых идет речь. +Откройте пример скелета веб-приложения под названием [WebProject|https://github.com/nette/web-project]. По ходу чтения вы можете заглядывать в обсуждаемые файлы. Структура каталогов выглядит примерно так: /--pre web-project/ -├── app/ ← каталог с приложением +├── app/ ← каталог приложения │ ├── Core/ ← базовые классы, необходимые для работы -│ │ └── RouterFactory.php ← конфигурация URL-адресов -│ ├── Presentation/ ← презентеры, шаблоны и т.п. +│ │ └── RouterFactory.php ← настройка URL-адресов +│ ├── Presentation/ ← презентеры, шаблоны и прочее │ │ ├── @layout.latte ← шаблон макета │ │ └── Home/ ← каталог презентера Home │ │ ├── HomePresenter.php ← класс презентера Home -│ │ └── default.latte ← шаблон действия default -│ └── Bootstrap.php ← загрузочный класс Bootstrap +│ │ └── default.latte ← шаблон для действия default +│ └── Bootstrap.php ← стартовый класс Bootstrap ├── assets/ ← ресурсы (SCSS, TypeScript, исходные изображения) ├── bin/ ← скрипты, запускаемые из командной строки ├── config/ ← конфигурационные файлы │ ├── common.neon │ └── services.neon -├── log/ ← логируемые ошибки +├── log/ ← записанные ошибки ├── temp/ ← временные файлы, кеш, … ├── vendor/ ← библиотеки, установленные Composer │ ├── ... │ └── autoload.php ← автозагрузка всех установленных пакетов -├── www/ ← публичный каталог или document-root проекта +├── www/ ← публичный каталог, document-root проекта │ ├── assets/ ← скомпилированные статические файлы (CSS, JS, изображения, ...) -│ ├── .htaccess ← правила mod_rewrite -│ └── index.php ← первоначальный файл, которым запускается приложение +│ ├── .htaccess ← правила для mod_rewrite +│ └── index.php ← начальный файл, запускающий приложение └── .htaccess ← запрещает доступ ко всем каталогам, кроме www \-- -Структуру каталогов можно изменять как угодно, папки переименовывать или перемещать, она абсолютно гибкая. Nette, кроме того, обладает умным автоопределением и автоматически распознает расположение приложения, включая его базовый URL. +Структуру каталогов можно изменять как угодно, переименовывать или переносить папки, она полностью гибкая. У Nette есть и умное автоопределение, которое само распознаёт расположение приложения, включая базовый URL. -Для немного больших приложений мы можем [разделить папки с презентерами и шаблонами на подкаталоги |directory-structure#Презентеры и шаблоны] и классы на пространства имен, которые мы называем модулями. +Для чуть более крупных приложений мы можем разложить каталоги презентеров и шаблонов по [подкаталогам |directory-structure#Презентеры и шаблоны] и сгруппировать классы в пространства имён, которые мы называем модулями. -Каталог `www/` представляет собой так называемый публичный каталог или document-root проекта. Вы можете его переименовать без необходимости что-либо еще настраивать на стороне приложения. Нужно только [настроить хостинг |nette:troubleshooting#Как изменить или удалить каталог www из URL] так, чтобы document-root указывал на этот каталог. +Каталог `www/` представляет собой публичный каталог, или document-root проекта. Вы можете его переименовать, ничего больше на стороне приложения настраивать не придётся. Нужно лишь [настроить хостинг |nette:troubleshooting#Как изменить или убрать из URL каталог www?] так, чтобы document-root указывал на этот каталог. -WebProject можно также сразу скачать вместе с Nette с помощью [Composer |best-practices:composer]: +WebProject можно скачать сразу вместе с Nette через [Composer |best-practices:composer]: ```shell composer create-project nette/web-project ``` -На Linux или macOS установите для каталогов `log/` и `temp/` [права на запись |nette:troubleshooting#Настройка прав доступа к каталогам]. +В Linux или macOS задайте каталогам `log/` и `temp/` [права на запись |nette:troubleshooting#Задание прав на каталоги]. -Приложение WebProject готово к запуску, не нужно ничего настраивать, и его можно сразу отобразить в браузере, обратившись к папке `www/`. +Приложение WebProject готово к запуску, настраивать вообще ничего не нужно, и вы можете открыть его прямо в браузере, обратившись к папке `www/`. HTTP-запрос =========== -Все начинается в тот момент, когда пользователь в браузере открывает страницу. То есть когда браузер стучится на сервер с HTTP-запросом. Запрос направляется на единственный PHP-файл, который находится в публичном каталоге `www/`, и это `index.php`. Допустим, это запрос на адрес `https://example.com/product/123`. Благодаря подходящей [настройке сервера |nette:troubleshooting#Как настроить сервер для красивых URL] и этот URL отображается на файл `index.php`, и он выполняется. +Всё начинается с того, что пользователь открывает страницу в браузере. Браузер отправляет на сервер HTTP-запрос. Этот запрос нацелен на единственный PHP-файл в публичном каталоге `www/`, а именно на `index.php`. Допустим, запрос идёт по адресу `https://example.com/product/123`. Благодаря подходящей [настройке сервера |nette:troubleshooting#Как настроить сервер для красивых URL?] даже такой URL сопоставляется файлу `index.php`, который и выполняется. Его задача: -1) инициализировать среду +1) инициализировать окружение 2) получить фабрику 3) запустить приложение Nette, которое обработает запрос -Какую фабрику? Мы же не тракторы производим, а веб-страницы! Потерпите, сейчас все объяснится. +Какую фабрику? Мы же не тракторы производим, а сайты делаем! Погодите, сейчас всё объяснится. -Под словами «инициализация среды» мы подразумеваем, например, активацию [Tracy |tracy:], что является замечательным инструментом для логирования или визуализации ошибок. На production-сервере он логирует ошибки, на сервере разработки сразу отображает. Следовательно, к инициализации относится и решение, работает ли веб в режиме production или разработки. Для этого Nette использует [умное автоопределение |bootstrapping#Режим разработки vs режим production]: если вы запускаете веб на localhost, он работает в режиме разработки. Вам не нужно ничего настраивать, и приложение сразу готово как для разработки, так и для реального развертывания. Эти шаги выполняются и подробно описаны в главе о [классе Bootstrap |bootstrapping]. +Под "инициализацией окружения" мы понимаем, например, включение [Tracy|tracy:] - потрясающего инструмента для записи в лог и наглядного показа ошибок. На производственном сервере она записывает ошибки в лог, а в среде разработки показывает их прямо на экране. Поэтому инициализация включает и определение того, работает сайт в производственном режиме или в режиме разработки. Nette использует для этого [умное автоопределение |bootstrapping#Режим разработки и производственный режим]: если вы запускаете сайт на localhost, он работает в режиме разработки. Настраивать ничего не нужно, и приложение сразу готово и к разработке, и к боевому развёртыванию. Эти шаги выполняются и подробно описываются в главе о [классе Bootstrap|bootstrapping]. -Третьим пунктом (да, второй мы пропустили, но вернемся к нему) является запуск приложения. Обработкой HTTP-запросов в Nette занимается класс `Nette\Application\Application` (далее `Application`), поэтому когда мы говорим запустить приложение, мы имеем в виду конкретно вызов метода с характерным названием `run()` на объекте этого класса. +Третий пункт (да, второй мы пропустили, но вернёмся к нему) - запуск приложения. Обработка HTTP-запросов в Nette лежит на классе `Nette\Application\Application` (далее `Application`). Так что, когда мы говорим "запустить приложение", мы имеем в виду вызов метода с говорящим именем `run()` у объекта этого класса. -Nette — это наставник, который ведет вас к написанию чистых приложений по проверенным методикам. И одна из самых проверенных называется **dependency injection**, сокращенно DI. В данный момент мы не хотим загружать вас объяснением DI, для этого есть [отдельная глава |dependency-injection:introduction], важен результат, что ключевые объекты нам обычно будет создавать фабрика объектов, которая называется **DI-контейнер** (сокращенно DIC). Да, это та самая фабрика, о которой шла речь минуту назад. И она создаст нам и объект `Application`, поэтому нам сначала нужен контейнер. Мы получаем его с помощью класса `Configurator` и позволяем ему создать объект `Application`, вызываем на нем метод `run()`, и тем самым запускается приложение Nette. Именно это происходит в файле [index.php |bootstrapping#index.php]. +Nette выступает наставником, который направляет вас писать чистые приложения по проверенным методикам. Одна из самых устоявшихся - **внедрение зависимостей**, сокращённо DI. Мы не хотим сейчас нагружать вас объяснением DI, для этого есть [отдельная глава|dependency-injection:introduction]. Важное следствие в том, что ключевые объекты обычно создаёт фабрика объектов, известная как **DI-контейнер** (или DIC). Да, это та самая фабрика, о которой шла речь. Она порождает нам и объект `Application`, поэтому сначала нам нужен контейнер. Мы получаем его через класс `Configurator`, даём ему создать объект `Application`, вызываем у него метод `run()`, и приложение Nette запускается. Именно это и происходит в файле [index.php |bootstrapping#index.php]. Nette Application ================= -У класса Application одна задача: ответить на HTTP-запрос. +У класса `Application` единственная задача: ответить на HTTP-запрос. -Приложения, написанные на Nette, делятся на множество так называемых презентеров (в других фреймворках вы можете встретить термин controller, это одно и то же), которые представляют собой классы, каждый из которых представляет какую-то конкретную страницу сайта: например, главную страницу; продукт в интернет-магазине; форму входа; фид карты сайта и т. д. Приложение может иметь от одного до тысяч презентеров. +Приложения, написанные на Nette, делятся на множество так называемых презентеров (в других фреймворках вы можете встретить термин "контроллер", это по сути одно и то же). Это классы, каждый из которых представляет определённую страницу сайта: например, главную страницу, товар в интернет-магазине, форму входа, ленту карты сайта и так далее. У приложения может быть от одного до тысяч презентеров. -Application начинает с того, что запрашивает у так называемого маршрутизатора (router), чтобы он решил, какому из презентеров передать текущий запрос для обработки. Маршрутизатор решает, чья это ответственность. Он смотрит на входной URL `https://example.com/product/123` и на основе того, как он настроен, решает, что это работа, например, для **презентера** `Product`, от которого потребуется в качестве **действия** отображение (`show`) продукта с `id: 123`. Пару презентер + действие принято записывать, разделяя двоеточием, как `Product:show`. +`Application` начинает с того, что спрашивает так называемый маршрутизатор, какой презентер должен обработать текущий запрос. Маршрутизатор определяет ответственного. Он изучает входной URL `https://example.com/product/123` и по своей настройке решает, что эта задача принадлежит, например, **презентеру** `Product`, который должен выполнить **действие** `show` для товара с `id: 123`. Пару "презентер + действие" принято записывать через двоеточие: `Product:show`. -Таким образом, маршрутизатор преобразовал URL в пару `Presenter:action` + параметры, в нашем случае `Product:show` + `id: 123`. Как выглядит такой маршрутизатор, вы можете посмотреть в файле `app/Core/RouterFactory.php`, и мы подробно описываем его в главе [Маршрутизация |Routing]. +Итак, маршрутизатор превратил URL в пару `Презентер:действие` с параметрами, в нашем случае `Product:show` и `id: 123`. Как выглядит такой маршрутизатор, вы можете увидеть в файле `app/Core/RouterFactory.php`, а подробно мы описываем его в главе [Маршрутизация |Routing]. -Идем дальше. Application уже знает имя презентера и может продолжать. Создавая объект класса `ProductPresenter`, который является кодом презентера `Product`. Точнее говоря, он просит DI-контейнер создать презентер, потому что создание — это его работа. +Пойдём дальше. `Application` теперь знает имя презентера и может действовать. Он создаёт экземпляр класса `ProductPresenter`, содержащего код презентера `Product`. Точнее, он просит DI-контейнер создать презентер, потому что создание объектов - его обязанность. -Презентер может выглядеть примерно так: +Презентер может выглядеть так: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -109,92 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // получаем данные из модели и передаем в шаблон + // получаем данные из модели и передаём их в шаблон $this->template->product = $this->repository->getProduct($id); } } ``` -Обработку запроса берет на себя презентер. И задача ясна: выполнить действие `show` с `id: 123`. Что на языке презентеров означает, что вызывается метод `renderShow()`, и в параметре `$id` он получает `123`. +Презентер берёт обработку запроса на себя. Задача ясна: выполнить действие `show` с `id: 123`. В терминологии презентеров это значит, что вызывается метод `renderShow()`, получающий `123` в параметре `$id`. -Презентер может обслуживать несколько действий, то есть иметь несколько методов `render()`. Но мы рекомендуем проектировать презентеры с одним или как можно меньшим количеством действий. +Презентер может обрабатывать несколько действий, то есть у него может быть несколько методов `render()`. Однако мы рекомендуем проектировать презентеры с одним действием или с как можно меньшим их числом. -Итак, вызвался метод `renderShow(123)`, код которого, хотя и является вымышленным примером, но на нем вы можете увидеть, как передаются данные в шаблон, то есть записью в `$this->template`. +Итак, был вызван метод `renderShow(123)`. Его код - выдуманный пример, но он показывает, как данные передаются в шаблон, а именно записью в `$this->template`. -Затем презентер возвращает ответ. Это может быть HTML-страница, изображение, XML-документ, отправка файла с диска, JSON или, например, перенаправление на другую страницу. Важно, что если мы явно не скажем, как он должен ответить (что и происходит в случае `ProductPresenter`), ответом будет отрисовка шаблона с HTML-страницей. Почему? Потому что в 99% случаев мы хотим отрисовать шаблон, поэтому презентер воспринимает это поведение как стандартное и хочет облегчить нам работу. В этом смысл Nette. +Затем презентер возвращает ответ. Это может быть HTML-страница, изображение, XML-документ, отправка файла с диска, JSON или, скажем, перенаправление на другую страницу. Важно, что, если мы явно не указываем, как отвечать (а именно так и обстоит дело с `ProductPresenter`), ответом будет отрисовка шаблона в HTML-страницу. Почему? Потому что в 99 % случаев мы хотим отрисовать шаблон. Поэтому презентер принимает такое поведение как поведение по умолчанию, чтобы упростить нам работу. В этом суть Nette. -Нам даже не нужно указывать, какой шаблон отрисовать, путь к нему он выведет сам. В случае действия `show` он просто попытается загрузить шаблон `show.latte` в каталоге с классом `ProductPresenter`. Также он попытается найти макет в файле `@layout.latte` (подробнее о [поиске шаблонов |templates#Поиск шаблонов]). +Нам даже не нужно указывать, какой шаблон отрисовать: фреймворк выведет путь автоматически. В случае действия `show` он просто попробует загрузить шаблон `show.latte`, лежащий в том же каталоге, что и класс `ProductPresenter`. Он также попробует найти макет в файле `@layout.latte` (подробнее о [поиске шаблонов |templates#Поиск шаблона]). -И затем шаблоны отрисовываются. На этом задача презентера и всего приложения завершена, и работа выполнена. Если бы шаблон не существовал, вернулась бы страница с ошибкой 404. Больше о презентерах вы можете прочитать на странице [Презентеры |presenters]. +Затем шаблоны отрисовываются. На этом задача презентера и всего приложения завершена. Если шаблона не существует, возвращается страница с ошибкой 404. Подробнее о презентерах можно узнать на странице [Презентеры|presenters]. [* request-flow.svg *] -На всякий случай, попробуем повторить весь процесс с немного другим URL: +Для верности повторим весь ход событий с чуть другим URL: -1) URL будет `https://example.com` -2) загружаем приложение, создается контейнер и запускается `Application::run()` -3) маршрутизатор декодирует URL как пару `Home:default` -4) создается объект класса `HomePresenter` -5) вызывается метод `renderDefault()` (если существует) -6) отрисовывается шаблон, например, `default.latte` с макетом, например, `@layout.latte` +1) URL - `https://example.com` +2) Приложение стартует, создаётся DI-контейнер и выполняется `Application::run()`. +3) Маршрутизатор расшифровывает URL в пару `Home:default`. +4) Создаётся экземпляр класса `HomePresenter`. +5) Вызывается метод `renderDefault()` (если он существует). +6) Отрисовывается шаблон, например `default.latte`, вместе с макетом, например `@layout.latte`. -Возможно, вы сейчас столкнулись с большим количеством новых понятий, но мы верим, что они имеют смысл. Создание приложений в Nette — это огромное удовольствие. +Возможно, вы только что столкнулись со множеством новых понятий, но мы верим, что они осмысленны. Разрабатывать приложения на Nette на удивление просто. Шаблоны ======= -Раз уж речь зашла о шаблонах, в Nette используется система шаблонов [Latte |latte:]. Поэтому и расширения `.latte` у шаблонов. Latte используется, во-первых, потому что это самая безопасная система шаблонов для PHP, а во-вторых, самая интуитивно понятная. Вам не нужно учить много нового, достаточно знания PHP и нескольких тегов. Все вы узнаете [в документации |templates]. +Раз уж речь зашла о шаблонах: Nette использует систему шаблонов [Latte |latte:]. Поэтому файлы шаблонов имеют расширение `.latte`. Latte используется прежде всего потому, что это самая безопасная система шаблонов для PHP, а ещё самая интуитивная. Учить много нового не нужно: достаточно знания PHP и нескольких тегов. Всё нужное вы найдёте [в документации |templates]. -В шаблоне [создаются ссылки |creating-links] на другие презентеры и действия так: +В шаблоне вы [создаёте ссылки |creating-links] на другие презентеры и действия вот так: ```latte -деталь продукта +product detail ``` -Просто вместо реального URL вы пишете известную пару `Presenter:action` и указываете возможные параметры. Хитрость в `n:href`, которое говорит, что этот атрибут обработает Nette. И сгенерирует: +Просто напишите привычную пару `Презентер:действие` вместо настоящего URL и добавьте нужные параметры. Хитрость в `n:href`, которая говорит Nette обработать этот атрибут. Он затем породит: ```latte -деталь продукта +product detail ``` -Генерацией URL занимается уже упомянутый маршрутизатор. Дело в том, что маршрутизаторы в Nette уникальны тем, что они могут выполнять не только преобразование из URL в пару presenter:action, но и наоборот, то есть из имени презентера + действия + параметров генерировать URL. Благодаря этому в Nette вы можете полностью изменить формы URL во всем готовом приложении, не изменяя ни одного символа в шаблоне или презентере. Просто изменив маршрутизатор. Также благодаря этому работает так называемая канонизация, что является еще одной уникальной особенностью Nette, которая способствует лучшему SEO (оптимизации для поисковых систем), автоматически предотвращая существование дублирующегося контента на разных URL. Многие программисты считают это потрясающим. +Порождением URL занимается упомянутый маршрутизатор. Маршрутизаторы в Nette исключительны тем, что умеют не только преобразовывать URL в пару `Презентер:действие`, но и наоборот: порождать URL из имени презентера, действия и параметров. Благодаря этому вы можете полностью изменить формат URL во всём готовом приложении на Nette, не меняя ни одного символа в шаблонах или презентерах, - достаточно поправить маршрутизатор. Это же обеспечивает так называемую канонизацию, ещё одну уникальную возможность Nette, которая улучшает SEO, автоматически не давая одному и тому же содержимому существовать под разными URL. Многих программистов эта возможность поражает. Интерактивные компоненты ======================== -О презентерах мы должны вам рассказать еще одну вещь: у них есть встроенная система компонентов. Что-то подобное могут помнить старожилы из Delphi или ASP.NET Web Forms, на чем-то отдаленно похожем построены React или Vue.js. В мире PHP-фреймворков это совершенно уникальное явление. +Нам нужно рассказать о презентерах ещё одну вещь: в них встроена система компонентов. Те, у кого больше опыта, могут вспомнить нечто похожее из Delphi или ASP.NET Web Forms; React или Vue.js построены на в чём-то родственных идеях. В мире PHP-фреймворков это совершенно уникальная возможность. -Компоненты — это отдельные повторно используемые единицы, которые мы вставляем на страницы (то есть в презентеры). Это могут быть [формы |forms:in-presenter], [датагриды |https://componette.org/contributte/datagrid/], меню, опросы, в общем, все, что имеет смысл использовать повторно. Мы можем создавать собственные компоненты или использовать некоторые из [огромного выбора |https://componette.org] open source компонентов. +Компоненты - самостоятельные переиспользуемые единицы, которые мы встраиваем в страницы (то есть в презентеры). Это могут быть [формы |forms:in-presenter], [таблицы данных |https://componette.org/contributte/datagrid/], меню, опросы - в общем, всё, что имеет смысл использовать повторно. Мы можем создавать собственные компоненты или воспользоваться какими-то из [огромного выбора |https://componette.org] компонентов с открытым кодом. -Компоненты кардинально влияют на подход к созданию приложений. Они откроют вам новые возможности сборки страниц из готовых блоков. И к тому же у них есть что-то общее с [Голливудом |components#Стиль Голливуда]. +Компоненты принципиально меняют подход к разработке приложений. Они открывают новые возможности собирать страницы из заранее подготовленных единиц. И у них есть кое-что общее с [Голливудом |components#Голливудский стиль]. DI-контейнер и конфигурация =========================== -DI-контейнер или фабрика объектов — это сердце всего приложения. +DI-контейнер, то есть фабрика объектов, - сердце всего приложения. -Не беспокойтесь, это не какой-то магический черный ящик, как могло показаться из предыдущих строк. На самом деле, это один довольно скучный PHP-класс, который генерирует Nette и сохраняет в каталоге кеша. У него много методов, названных как `createServiceAbcd()`, и каждый из них умеет создавать и возвращать какой-то объект. Да, там есть и метод `createServiceApplication()`, который создает `Nette\Application\Application`, который нам был нужен в файле `index.php` для запуска приложения. И там есть методы, создающие отдельные презентеры. И так далее. +Не переживайте, это не какой-то волшебный чёрный ящик, как могли бы навести на мысль предыдущие строки. На деле это довольно обыденный PHP-класс, порождённый Nette и сохранённый в каталоге кеша. В нём много методов с именами вроде `createServiceAbcd()`, каждый из которых умеет создать и вернуть определённый объект. Да, там есть и метод `createServiceApplication__application()`, порождающий экземпляр `Nette\Application\Application`, который понадобился нам в `index.php` для запуска приложения. Есть и методы для создания отдельных презентеров и так далее. -Объектам, которые создает DI-контейнер, по какой-то причине говорят сервисы. +Объекты, создаваемые DI-контейнером, по некоторым причинам называют сервисами. -Что действительно особенного в этом классе, так это то, что его программируете не вы, а фреймворк. Он действительно генерирует PHP-код и сохраняет его на диск. Вы только даете инструкции, какие объекты должен уметь создавать контейнер и как именно. И эти инструкции записаны в [конфигурационных файлах |bootstrapping#Конфигурация DI-контейнера], для которых используется формат [NEON |neon:format], и поэтому они имеют расширение `.neon`. +По-настоящему особенное в этом классе то, что вы его не программируете - это делает фреймворк. Он действительно порождает PHP-код и сохраняет его на диск. Вы лишь даёте указания, какие объекты контейнер должен уметь создавать и как именно. Эти указания записываются в [конфигурационных файлах |bootstrapping#Конфигурация DI-контейнера], которые используют формат [NEON|neon:format] и потому имеют расширение `.neon`. -Конфигурационные файлы служат исключительно для инструктирования DI-контейнера. Так что, например, если я укажу в секции [session |http:configuration#Сессия] опцию `expiration: 14 days`, то DI-контейнер при создании объекта `Nette\Http\Session`, представляющего сессию, вызовет его метод `setExpiration('14 days')`, и тем самым конфигурация станет реальностью. +Конфигурационные файлы служат исключительно для указаний DI-контейнеру. Так что, если вы, например, зададите параметр `expiration: 14 days` в секции [session |http:configuration#Сессия], DI-контейнер при создании объекта `Nette\Http\Session`, представляющего сессию, вызовет его метод `setExpiration('14 days')` и тем самым воплотит конфигурацию в жизнь. -Для вас подготовлена целая глава, описывающая, что все можно [настроить |nette:configuring] и как [определить собственные сервисы |dependency-injection:services]. +Для вас подготовлена целая глава о том, что можно [настраивать |nette:configuring] и как [определять собственные сервисы |dependency-injection:services]. -Как только вы немного разберетесь в создании сервисов, вы столкнетесь со словом [autowiring |dependency-injection:autowiring]. Это фишка, которая невероятным образом упростит вам жизнь. Она умеет автоматически передавать объекты туда, где они вам нужны (например, в конструкторах ваших классов), без необходимости что-либо делать. Вы обнаружите, что DI-контейнер в Nette — это маленькое чудо. +Как только вы немного погрузитесь в создание сервисов, вы столкнётесь с термином [autowiring |dependency-injection:autowiring]. Это возможность, которая невероятно упростит вам жизнь. Она умеет автоматически передавать объекты туда, где они вам нужны (например, в конструкторы ваших классов), без каких-либо действий с вашей стороны. Вы обнаружите, что DI-контейнер в Nette - маленькое чудо. -Куда дальше? -============ +Что дальше? +=========== -Мы рассмотрели основные принципы приложений в Nette. Пока очень поверхностно, но скоро вы проникнете в глубину и со временем создадите замечательные веб-приложения. Куда двигаться дальше? Вы уже пробовали учебник [Пишем первое приложение |quickstart:]? +Мы разобрали основополагающие принципы приложений Nette. Пока это был поверхностный обзор, но вскоре вы погрузитесь глубже и со временем начнёте создавать замечательные веб-приложения. Куда двигаться дальше? Вы уже пробовали руководство [Создайте своё первое приложение|quickstart:]? -Кроме вышеописанного, Nette располагает целым арсеналом [полезных классов |utils:], [слоем базы данных |database:], и т. д. Попробуйте просто пролистать документацию. Или [блог |https://blog.nette.org]. Вы откроете много интересного. +Помимо описанного выше Nette предлагает целый арсенал [полезных классов|utils:], [слой работы с базой данных|database:] и многое другое. Попробуйте походить по документации. Или загляните в [блог|https://blog.nette.org]. Вы обнаружите много интересного. -Пусть фреймворк приносит вам много радости 💙 +Пусть фреймворк принесёт вам много радости 💙 diff --git a/application/ru/multiplier.texy b/application/ru/multiplier.texy index 15e753817b..415a0d5f84 100644 --- a/application/ru/multiplier.texy +++ b/application/ru/multiplier.texy @@ -2,23 +2,23 @@ Multiplier: динамические компоненты *********************************** .[perex] -Инструмент для динамического создания интерактивных компонентов +Инструмент для динамического создания интерактивных компонентов. -Начнем с типичного примера: у нас есть список товаров в интернет-магазине, и для каждого мы хотим вывести форму для добавления товара в корзину. Один из возможных вариантов — обернуть весь список в одну форму. Однако гораздо более удобный способ предлагает нам [api:Nette\Application\UI\Multiplier]. +Начнём с типичного примера: представьте список товаров в интернет-магазине, где для каждой позиции нужна форма "Добавить в корзину". Один из возможных подходов - обернуть весь список в одну форму. Однако куда более удобный способ предлагает [api:Nette\Application\UI\Multiplier]. -Multiplier позволяет удобно определить фабрику для нескольких компонентов. Он работает по принципу вложенных компонентов — каждый компонент, наследующий от [api:Nette\ComponentModel\Container], может содержать другие компоненты. +Multiplier позволяет удобно определить фабрику сразу для нескольких компонентов. Он работает по принципу вложенных компонентов: любой компонент, наследующий от [api:Nette\ComponentModel\Container], может содержать другие компоненты. .[tip] -См. главу о [модели компонентов |components#Компоненты в глубину] в документации или [лекцию Яна Тврдика |https://www.youtube.com/watch?v=8y3LLexWu-I]. +См. главу о [компонентной модели |components#Компоненты в подробностях] в документации. -Суть Multiplier заключается в том, что он выступает в роли родителя, который может динамически создавать своих потомков с помощью callback-функции, переданной в конструкторе. См. пример: +Суть Multiplier в том, что он выступает родителем, который может динамически создавать своих потомков с помощью callback, переданного в конструктор. Смотрите пример: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Количество товара:') + $form->addInteger('amount', 'Количество:') ->setRequired(); $form->addSubmit('send', 'Добавить в корзину'); return $form; @@ -26,7 +26,7 @@ protected function createComponentShopForm(): Multiplier } ``` -Теперь мы можем в шаблоне просто для каждого товара отрисовать форму — и каждая будет действительно уникальным компонентом. +Теперь в шаблоне мы можем просто отрисовать форму для каждого товара, и каждая из них действительно будет отдельным компонентом. ```latte {foreach $items as $item} @@ -37,23 +37,23 @@ protected function createComponentShopForm(): Multiplier {/foreach} ``` -Аргумент, переданный в теге `{control}`, имеет формат, который говорит: +Аргумент, переданный в тег `{control}`, следует формату, который означает: -1. получи компонент `shopForm` -2. и из него получи потомка `$item->id` +1. Получить компонент `shopForm`. +2. Получить у него потомка с именем `$item->id`. -При первом вызове пункта **1.** `shopForm` еще не существует, поэтому вызывается его фабрика `createComponentShopForm`. На полученном компоненте (экземпляре Multiplier) затем вызывается фабрика конкретной формы — это анонимная функция, которую мы передали Multiplier в конструкторе. +При первом выполнении пункта **1** компонента `shopForm` ещё нет, поэтому вызывается его фабрика `createComponentShopForm`. Затем у полученного компонента (экземпляра Multiplier) вызывается фабрика конкретной формы - та самая анонимная функция, которую мы передали в конструктор Multiplier. -В следующей итерации foreach метод `createComponentShopForm` уже не будет вызван (компонент существует), но поскольку мы ищем другого его потомка (`$item->id` будет разным в каждой итерации), снова будет вызвана анонимная функция и вернет нам новую форму. +На следующей итерации цикла foreach метод `createComponentShopForm` уже не вызовется (компонент существует). Однако, поскольку мы ищем другого потомка (ведь `$item->id` на каждой итерации будет разным), анонимная функция вызовется снова и вернёт новую форму. -Единственное, что остается, — это убедиться, что форма добавит в корзину действительно тот товар, который нужно — в настоящее время форма для каждого товара абсолютно одинакова. Нам поможет свойство Multiplier (и вообще любой фабрики компонентов в Nette Framework), а именно то, что каждая фабрика в качестве своего первого аргумента получает имя создаваемого компонента. В нашем случае это будет `$item->id`, что является именно той информацией, которая нам нужна. Достаточно немного изменить создание формы: +Остаётся только позаботиться о том, чтобы форма добавляла в корзину правильный товар - сейчас форма одинакова для всех товаров. Здесь нам помогает особенность Multiplier (да и вообще любой фабрики компонентов в Nette Framework): каждая фабрика получает первым аргументом имя создаваемого компонента. Кроме того, фабрика Multiplier получает вторым аргументом сам экземпляр Multiplier. В нашем случае первым аргументом будет `$item->id`, а это ровно те сведения, которые нам нужны. Значит, достаточно немного изменить создание формы: ```php protected function createComponentShopForm(): Multiplier { - return new Multiplier(function ($itemId) { + return new Multiplier(function (string $itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Количество товара:') + $form->addInteger('amount', 'Количество:') ->setRequired(); $form->addHidden('itemId', $itemId); $form->addSubmit('send', 'Добавить в корзину'); diff --git a/application/ru/presenters.texy b/application/ru/presenters.texy index 0c75055dae..3adcc01fcc 100644 --- a/application/ru/presenters.texy +++ b/application/ru/presenters.texy @@ -3,37 +3,37 @@
    -Мы познакомимся с тем, как в Nette пишутся презентеры и шаблоны. После прочтения вы будете знать: +Мы разберём, как в Nette пишутся презентеры и шаблоны. После прочтения вы будете понимать: -- как работает презентер -- что такое персистентные параметры +- как работают презентеры +- что такое постоянные параметры - как отрисовываются шаблоны
    -[Мы уже знаем |how-it-works#Nette Application], что презентер — это класс, представляющий какую-либо конкретную страницу веб-приложения, например, главную страницу; товар в интернет-магазине; форму входа; фид карты сайта и т. д. Приложение может иметь от одного до тысяч презентеров. В других фреймворках их также называют контроллерами. +[Мы уже знаем |how-it-works#Nette Application], что презентер - класс, представляющий определённую страницу веб-приложения, например главную страницу, товар в интернет-магазине, форму входа, ленту карты сайта и так далее. У приложения может быть от одного до тысяч презентеров. В других фреймворках их называют контроллерами. -Обычно под понятием презентер подразумевается потомок класса [api:Nette\Application\UI\Presenter], который подходит для генерации веб-интерфейсов и которому мы будем уделять внимание в оставшейся части этой главы. В общем смысле презентер — это любой объект, реализующий интерфейс [api:Nette\Application\IPresenter]. +Обычно под словом "презентер" понимают потомка класса [api:Nette\Application\UI\Presenter], который подходит для создания веб-интерфейсов и которому посвящена остальная часть этой главы. В общем смысле презентер - любой объект, реализующий интерфейс [api:Nette\Application\IPresenter]. Жизненный цикл презентера ========================= -Задача презентера — обработать запрос и вернуть ответ (это может быть HTML-страница, изображение, перенаправление и т. д.). +Задача презентера - обработать запрос и вернуть ответ (которым может быть HTML-страница, изображение, перенаправление и так далее). -То есть вначале ему передается запрос. Это не непосредственно HTTP-запрос, а объект [api:Nette\Application\Request], в который был преобразован HTTP-запрос с помощью маршрутизатора. С этим объектом мы обычно не сталкиваемся, так как презентер умно делегирует обработку запроса другим методам, которые мы сейчас покажем. +Итак, сначала ему передаётся запрос. Это не прямой HTTP-запрос, а объект [api:Nette\Application\Request], в который HTTP-запрос был преобразован с помощью маршрутизатора. Обычно мы с этим объектом напрямую не работаем, потому что презентер ловко передаёт обработку запроса другим методам, которые мы сейчас и рассмотрим. [* lifecycle.svg *] *** Жизненный цикл презентера .<> -Изображение представляет список методов, которые последовательно вызываются сверху вниз, если они существуют. Ни один из них не обязан существовать, у нас может быть совершенно пустой презентер без единого метода, и на нем можно построить простой статический сайт. +Диаграмма показывает список методов, которые вызываются по очереди сверху вниз, если они существуют. Ни один из них не обязателен: у вас может быть совершенно пустой презентер без единого метода, на котором вы построите простой статический сайт. `__construct()` --------------- -Конструктор не совсем относится к жизненному циклу презентера, так как вызывается в момент создания объекта. Но мы упоминаем его из-за важности. Конструктор (вместе с [методом inject |best-practices:inject-method-attribute]) служит для передачи зависимостей. +Строго говоря, конструктор не относится к жизненному циклу презентера, потому что вызывается в момент создания объекта. Но мы упоминаем его из-за его важности. Конструктор (вместе с [методом inject|best-practices:inject-method-attribute]) служит для передачи зависимостей. -Презентер не должен заниматься бизнес-логикой приложения, записывать и читать из базы данных, выполнять вычисления и т. д. Для этого существуют классы из слоя, который мы называем моделью. Например, класс `ArticleRepository` может отвечать за загрузку и сохранение статей. Чтобы презентер мог с ним работать, он получает его [через внедрение зависимостей |dependency-injection:passing-dependencies]: +Презентер не должен заниматься бизнес-логикой приложения, писать в базу данных или читать из неё, выполнять вычисления и подобное. За это отвечают классы слоя, который мы называем моделью. Например, класс `ArticleRepository` может отвечать за загрузку и сохранение статей. Чтобы презентер мог с ним работать, класс нужно [передать через внедрение зависимостей |dependency-injection:passing-dependencies]: ```php @@ -50,44 +50,47 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -Сразу после получения запроса вызывается метод `startup()`. Вы можете использовать его для инициализации свойств, проверки прав пользователя и т. д. Требуется, чтобы метод всегда вызывал предка `parent::startup()`. +Сразу после получения запроса вызывается метод `startup()`. Вы можете использовать его для инициализации свойств, проверки прав пользователя и подобного. Требуется, чтобы этот метод всегда вызывал родительский: `parent::startup()`. `action(args...)` .{toc: action()} -------------------------------------------------- -Аналог метода `render()`. В то время как `render()` предназначен для подготовки данных для конкретного шаблона, который затем будет отрисован, в `action()` обрабатывается запрос без связи с отрисовкой шаблона. Например, обрабатываются данные, пользователь входит в систему или выходит из нее, и так далее, а затем [перенаправляется в другое место |#Перенаправление]. +Похож на метод `render()`. Если `render()` предназначен для подготовки данных конкретного шаблона, который затем будет отрисован, то `action()` обрабатывает запрос, не обязательно отрисовывая после этого шаблон. Например, он может обработать данные, выполнить вход или выход пользователя и затем [перенаправить куда-то |#Перенаправление]. -Важно, что `action()` вызывается раньше, чем `render()`, поэтому в нем мы можем при необходимости изменить дальнейший ход событий, т. е. изменить шаблон, который будет отрисовываться, а также метод `render()`, который будет вызываться. Это делается с помощью `setView('jineView')`. +Важно, что `action()` вызывается *перед* `render()`. Это позволяет нам при необходимости изменить ход запроса внутри метода действия, например поменять шаблон, который будет отрисован, или даже метод `render()`, который будет вызван, с помощью `setView('otherView')`. -Методу передаются параметры из запроса. Возможно и рекомендуется указывать типы параметров, например, `actionShow(int $id, ?string $slug = null)` — если параметр `id` будет отсутствовать или не будет целым числом, презентер вернет [ошибку 404 |#Ошибка 404 и т.п] и завершит работу. +.{data-version:3.2.3} +Вы можете даже переключиться на совершенно другое действие методом `switch('otherAction')`. Он прерывает текущий метод и вместо этого запускает методы `action()` и `render()` нового действия (и отключает автоматическую [канонизацию|#Канонизация]). Сам запрос продолжается; прерывается лишь выполняющийся в данный момент метод. + +В метод передаются параметры из запроса. У этих параметров можно и рекомендуется указывать типы, например `actionShow(int $id, ?string $slug = null)`. Если параметра `id` нет или он не является целым числом, презентер возвращает [ошибку 404 |#Ошибка 404 и подобные] и завершается. `handle(args...)` .{toc: handle()} -------------------------------------------------- -Метод обрабатывает так называемые сигналы, с которыми мы познакомимся в главе, посвященной [компонентам |components#Сигнал]. Он предназначен в основном для компонентов и обработки AJAX-запросов. +Этот метод обрабатывает так называемые сигналы, о которых мы узнаем в главе, посвящённой [компонентам |components#Сигнал]. Он предназначен прежде всего для компонентов и обработки AJAX-запросов. -Методу передаются параметры из запроса, как и в случае `action()`, включая проверку типов. +В метод передаются параметры из запроса, как и в `action()`, включая проверку типов. `beforeRender()` ---------------- -Метод `beforeRender`, как следует из названия, вызывается перед каждым методом `render()`. Используется для общей конфигурации шаблона, передачи переменных для макета и т. п. +Метод `beforeRender`, как следует из его имени, вызывается перед каждым методом `render()`. Он служит для общей настройки шаблона, передачи переменных в макет и подобных задач. `render(args...)` .{toc: render()} ---------------------------------------------- -Место, где мы подготавливаем шаблон к последующей отрисовке, передаем ему данные и т. д. +Здесь мы готовим шаблон к последующей отрисовке, передаём в него данные и так далее. -Методу передаются параметры из запроса, как и в случае `action()`, включая проверку типов. +В метод передаются параметры из запроса, как и в `action()`, включая проверку типов. ```php public function renderShow(int $id): void { - // получаем данные из модели и передаем в шаблон + // получаем данные из модели и передаём их в шаблон $this->template->article = $this->articles->getById($id); } ``` @@ -96,7 +99,7 @@ public function renderShow(int $id): void `afterRender()` --------------- -Метод `afterRender`, как снова следует из названия, вызывается после каждого метода `render()`. Используется довольно редко. +Метод `afterRender`, как опять же следует из имени, вызывается после каждого метода `render()`. Используется он довольно редко. `shutdown()` @@ -105,95 +108,117 @@ public function renderShow(int $id): void Вызывается в конце жизненного цикла презентера. -**Хороший совет, прежде чем идти дальше**. Презентер, как видно, может обслуживать несколько действий/представлений, то есть иметь несколько методов `render()`. Но мы рекомендуем проектировать презентеры с одним или как можно меньшим количеством действий. +События +------- + +Помимо методов `startup()`, `beforeRender()` и `shutdown()`, вызываемых в рамках жизненного цикла презентера, можно определить и другие функции, которые будут вызваны автоматически. Презентер определяет так называемые [события |nette:glossary#События], и вы добавляете их обработчики в массивы `$onStartup`, `$onRender` и `$onShutdown`. + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` + +Обработчики из массива `$onStartup` вызываются прямо перед методом `startup()`, обработчики `$onRender` - между `beforeRender()` и `render()`, а обработчики `$onShutdown` - прямо перед `shutdown()`. + + +**Небольшой совет, прежде чем продолжим:** как видите, презентер может обрабатывать несколько действий или представлений, то есть у него может быть несколько методов `render()`. Однако мы рекомендуем проектировать презентеры с одним действием или с как можно меньшим их числом. Отправка ответа =============== -Ответом презентера обычно является [отрисовка шаблона с HTML-страницей |templates], но это также может быть отправка файла, JSON или, например, перенаправление на другую страницу. +Ответом презентера обычно служит [отрисовка шаблона в HTML-страницу|templates], но это может быть и отправка файла, JSON или даже перенаправление на другую страницу. -В любой момент жизненного цикла мы можем одним из следующих методов отправить ответ и одновременно завершить работу презентера: +В любой момент жизненного цикла мы можем воспользоваться одним из следующих методов, чтобы отправить ответ и одновременно завершить презентер: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` и `forward()` [перенаправляют |#Перенаправление] -- `error()` завершает презентер [из-за ошибки |#Ошибка 404 и т.п] +- `redirect()`, `redirectPermanent()`, `redirectUrl()` и `forward()` выполняют [перенаправление |#Перенаправление] +- `error()` завершает презентер [из-за ошибки |#Ошибка 404 и подобные] - `sendJson($data)` завершает презентер и [отправляет данные |#Отправка JSON] в формате JSON -- `sendTemplate()` завершает презентер и немедленно [отрисовывает шаблон |templates] +- `sendTemplate()` завершает презентер и сразу [отрисовывает шаблон |templates] - `sendResponse($response)` завершает презентер и отправляет [собственный ответ |#Ответы] - `terminate()` завершает презентер без ответа -Если вы не вызовете ни один из этих методов, презентер автоматически приступит к отрисовке шаблона. Почему? Потому что в 99% случаев мы хотим отрисовать шаблон, поэтому презентер воспринимает это поведение как стандартное и хочет облегчить нам работу. +Каждый из этих методов немедленно завершает презентер, выбрасывая исключение молчаливого завершения `Nette\Application\AbortException`. + +Если вы не вызовете ни один из этих методов, презентер автоматически перейдёт к отрисовке шаблона. Почему? Потому что в 99 % случаев мы хотим отрисовать шаблон, поэтому презентер принимает такое поведение как поведение по умолчанию, чтобы упростить нам работу. Создание ссылок =============== -Презентер располагает методом `link()`, с помощью которого можно создавать URL-ссылки на другие презентеры. Первым параметром является целевой презентер и действие, за ним следуют передаваемые аргументы, которые могут быть указаны как массив: +У презентера есть метод `link()`, служащий для создания URL-ссылок на другие презентеры. Первый параметр - целевой презентер и действие, за ними идут аргументы, которые можно передать массивом: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$id, 'lang' => 'en']); ``` -В шаблоне ссылки на другие презентеры и действия создаются следующим образом: +В шаблоне ссылки на другие презентеры и действия создаются так: ```latte -деталь продукта +product detail ``` -Просто вместо реального URL вы пишете известную пару `Presenter:action` и указываете возможные параметры. Хитрость в `n:href`, которое говорит, что этот атрибут обработает Latte и сгенерирует реальный URL. В Nette вам вообще не нужно думать об URL, только о презентерах и действиях. +Просто напишите привычную пару `Презентер:действие` вместо настоящего URL и добавьте нужные параметры. Хитрость в `n:href`, которая говорит Latte обработать этот атрибут и породить настоящий URL. В Nette вам вообще не нужно думать об URL, только о презентерах и действиях. -Дополнительную информацию можно найти в главе [Создание URL-ссылок |creating-links]. +Подробнее в главе [Создание URL-ссылок|creating-links]. Перенаправление =============== -Для перехода на другой презентер служат методы `redirect()` и `forward()`, которые имеют очень похожий синтаксис, как и метод [link() |#Создание ссылок]. +Для перехода к другому презентеру служат методы `redirect()` и `forward()`. Их синтаксис очень похож на метод [link() |#Создание ссылок]. -Метод `forward()` переходит на новый презентер немедленно без HTTP-перенаправления: +Метод `forward()` переходит к новому презентеру сразу, без HTTP-перенаправления: ```php $this->forward('Product:show'); ``` -Пример так называемого временного перенаправления с HTTP-кодом 302 (или 303, если метод текущего запроса POST): +Пример временного перенаправления с HTTP-кодом 302 (или 303, если текущий метод запроса - POST): ```php $this->redirect('Product:show', $id); ``` -Постоянное перенаправление с HTTP-кодом 301 достигается так: +Чтобы добиться постоянного перенаправления с HTTP-кодом 301, используйте: ```php $this->redirectPermanent('Product:show', $id); ``` -На другой URL вне приложения можно перенаправить методом `redirectUrl()`. В качестве второго параметра можно указать HTTP-код, по умолчанию 302 (или 303, если метод текущего запроса POST): +Перенаправить на другой URL вне приложения можно методом `redirectUrl()`. HTTP-код можно указать вторым параметром; по умолчанию это 302 (или 303, если текущий метод запроса - POST): ```php $this->redirectUrl('https://nette.org'); ``` -Перенаправление немедленно завершает работу презентера, выбрасывая так называемое тихое завершающее исключение `Nette\Application\AbortException`. +Перенаправление немедленно завершает работу презентера, выбрасывая так называемое исключение молчаливого завершения `Nette\Application\AbortException`. -Перед перенаправлением можно отправить [flash-сообщение |#Flash-сообщения], то есть сообщения, которые будут отображены в шаблоне после перенаправления. +Перед перенаправлением можно отправить [flash-сообщения |#Flash-сообщения], то есть сообщения, которые отобразятся в шаблоне после перенаправления. Flash-сообщения =============== -Это сообщения, обычно информирующие о результате какой-либо операции. Важной особенностью flash-сообщений является то, что они доступны в шаблоне даже после перенаправления. Даже после отображения они остаются активными еще 30 секунд — например, на случай, если из-за ошибки передачи пользователь обновит страницу — сообщение не исчезнет сразу. +Это сообщения, обычно извещающие о результате какой-то операции. Важная особенность flash-сообщений в том, что они остаются доступны в шаблоне и после перенаправления. После показа они остаются активными ещё 30 секунд: например, если пользователь обновит страницу из-за ошибки передачи, сообщение не исчезнет сразу. -Достаточно вызвать метод [flashMessage() |api:Nette\Application\UI\Control::flashMessage()], и о передаче в шаблон позаботится презентер. Первым параметром является текст сообщения, а необязательным вторым параметром — его тип (error, warning, info и т. п.). Метод `flashMessage()` возвращает экземпляр flash-сообщения, которому можно добавлять дополнительную информацию. +Достаточно вызвать метод [flashMessage() |api:Nette\Application\UI\Control::flashMessage()], а презентер позаботится о передаче сообщения в шаблон. Первый параметр - текст сообщения, необязательный второй - его тип (например, error, warning, info). Метод `flashMessage()` возвращает экземпляр flash-сообщения, что позволяет добавить дополнительные сведения. ```php -$this->flashMessage('Элемент был удален.'); +$this->flashMessage('The item has been deleted.'); $this->redirect(/* ... */); // и перенаправляем ``` -В шаблоне эти сообщения доступны в переменной `$flashes` как объекты `stdClass`, которые содержат свойства `message` (текст сообщения), `type` (тип сообщения) и могут содержать уже упомянутую пользовательскую информацию. Отрисуем их, например, так: +В шаблоне эти сообщения доступны в переменной `$flashes` как объекты `stdClass`, содержащие свойства `message` (текст сообщения), `type` (тип сообщения) и, возможно, упомянутые добавленные пользователем сведения. Отрисовываем мы их так: ```latte {foreach $flashes as $flash} @@ -202,10 +227,10 @@ $this->redirect(/* ... */); // и перенаправляем ``` -Ошибка 404 и т.п. -================= +Ошибка 404 и подобные +===================== -Если невозможно выполнить запрос, например, из-за того, что статья, которую мы хотим отобразить, не существует в базе данных, мы выбрасываем ошибку 404 методом `error(?string $message = null, int $httpCode = 404)`. +Если запрос выполнить нельзя, например потому что статьи, которую мы хотим показать, нет в базе данных, мы выбрасываем ошибку 404 методом `error(string $message = '', int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -218,13 +243,13 @@ public function renderShow(int $id): void } ``` -HTTP-код ошибки можно передать вторым параметром, по умолчанию 404. Метод работает так, что выбрасывает исключение `Nette\Application\BadRequestException`, после чего `Application` передает управление error-презентеру. Это презентер, задачей которого является отображение страницы, информирующей о возникшей ошибке. Настройка error-презентера выполняется в [конфигурации application |configuration]. +HTTP-код ошибки можно передать вторым параметром; по умолчанию это 404. Метод работает так, что выбрасывает `Nette\Application\BadRequestException`, после чего `Application` передаёт управление error-презентеру. Это презентер, задача которого - показать страницу с сообщением о произошедшей ошибке. Error-презентер задаётся в [конфигурации приложения|configuration]. Отправка JSON ============= -Пример action-метода, который отправляет данные в формате JSON и завершает работу презентера: +Метод `sendJson($data)` кодирует заданные данные в JSON, отправляет их как HTTP-ответ и завершает презентер. Пример: ```php public function actionData(): void @@ -238,9 +263,9 @@ public function actionData(): void Параметры запроса .{data-version:3.1.14} ======================================== -Презентер, а также каждый компонент, получает свои параметры из HTTP-запроса. Их значение можно узнать методами `getParameter($name)` или `getParameters()`. Значениями являются строки или массивы строк, это, по сути, необработанные данные, полученные непосредственно из URL. +Презентер, а также каждый компонент получают свои параметры из HTTP-запроса. Получить их значения можно методами `getParameter($name)` или `getParameters()`. Значениями служат строки или массивы строк, по сути сырые данные, полученные прямо из URL. -Для большего удобства рекомендуем сделать параметры доступными через свойства. Достаточно пометить их атрибутом `#[Parameter]`: +Ради большего удобства мы рекомендуем обращаться к параметрам через свойства. Достаточно пометить их атрибутом `#[Parameter]`: ```php use Nette\Application\Attributes\Parameter; // эта строка важна @@ -252,23 +277,23 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -У свойства рекомендуется указывать тип данных (например, `string`), и Nette автоматически преобразует значение в соответствии с ним. Значения параметров также можно [валидировать |#Валидация параметров]. +У свойства мы рекомендуем указывать тип данных (например, `string`), и Nette автоматически приведёт значение соответствующим образом. Значения параметров можно и [проверять |#Проверка параметров]. -При создании ссылки можно напрямую установить значение параметра: +При создании ссылки значение параметра можно задать напрямую: ```latte -нажмите +click ``` -Персистентные параметры -======================= +Постоянные параметры +==================== -Персистентные параметры служат для поддержания состояния между различными запросами. Их значение остается неизменным даже после нажатия на ссылку. В отличие от данных в сессии, они передаются в URL. И это происходит полностью автоматически, то есть нет необходимости явно указывать их в `link()` или `n:href`. +Постоянные параметры служат для сохранения состояния между разными запросами. Их значение остаётся тем же и после щелчка по ссылке. В отличие от данных сессии, они передаются в URL. И происходит это полностью автоматически, поэтому явно указывать их в `link()` или `n:href` не нужно. -Пример использования? У вас многоязычное приложение. Текущий язык — это параметр, который должен постоянно присутствовать в URL. Но было бы невероятно утомительно указывать его в каждой ссылке. Так что вы делаете его персистентным параметром `lang`, и он будет передаваться сам. Отлично! +Пример применения? Представьте многоязычное приложение. Текущий язык - параметр, который всегда должен быть частью URL. Но включать его в каждую ссылку было бы невероятно утомительно. Поэтому вы делаете его постоянным параметром `lang`, и он будет переноситься автоматически. Красота! -Создание персистентного параметра в Nette невероятно просто. Достаточно создать публичное свойство и пометить его атрибутом: (ранее использовалось `/** @persistent */`) +Создать постоянный параметр в Nette исключительно просто. Достаточно создать публичное свойство и пометить его атрибутом (раньше использовалось `/** @persistent */`): ```php use Nette\Application\Attributes\Persistent; // эта строка важна @@ -280,14 +305,14 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Если `$this->lang` будет иметь значение, например, `'en'`, то и ссылки, созданные с помощью `link()` или `n:href`, будут содержать параметр `lang=en`. И после нажатия на ссылку снова будет `$this->lang = 'en'`. +Если у `$this->lang` значение вроде `'en'`, то ссылки, созданные через `link()` или `n:href`, будут содержать и параметр `lang=en`. А после щелчка по ссылке `$this->lang` снова будет `'en'`. -У свойства рекомендуется указывать тип данных (например, `string`), и вы можете указать значение по умолчанию. Значения параметров можно [валидировать |#Валидация параметров]. +У свойства мы рекомендуем указывать тип данных (например, `string`), а также можно задать значение по умолчанию. Значения параметров можно [проверять |#Проверка параметров]. -Персистентные параметры по умолчанию передаются между всеми действиями данного презентера. Чтобы они передавались и между несколькими презентерами, их нужно определить либо: +Постоянные параметры обычно переносятся между всеми действиями данного презентера. Чтобы переносить их и между несколькими презентерами, их нужно определить либо: -- в общем предке, от которого наследуют презентеры -- в трейте, который используют презентеры: +- в общем предке, от которого наследуются презентеры +- либо в трейте, который презентеры используют: ```php trait LanguageAware @@ -302,42 +327,62 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -При создании ссылки можно изменить значение персистентного параметра: +При создании ссылки значение постоянного параметра можно изменить: ```latte -деталь на чешском +detail in Czech ``` -Или его можно *сбросить*, то есть удалить из URL. Тогда он примет свое значение по умолчанию: +Как вариант, его можно *сбросить*, то есть убрать из URL. Тогда он примет значение по умолчанию: ```latte -нажмите +click +``` + + +Общее пространство параметров +============================= + +Параметры запроса, [постоянные параметры |#Постоянные параметры] и параметры методов `action`, `render` и `handle` (сигналов) делят единое пространство, где каждый определяется своим именем. Если одно и то же имя встречается больше чем в одном из них, они относятся к одному и тому же значению. + +Этим часто пользуются. Например, постоянный параметр `lang` и аргумент `$lang` метода действия или сигнала - одно и то же: вы можете прочитать текущее значение постоянного параметра, просто указав его в сигнатуре метода: + +```php +#[Persistent] +public string $lang; + +public function handleSearch(string $query, string $lang): void +{ + // $lang содержит текущее значение постоянного параметра lang +} ``` +Поскольку это пространство общее, держите имена параметров уникальными, если только вы намеренно не хотите, чтобы они делили значение. Это относится и к сигналам, которые дополнительно читают параметры из тела POST-запроса, см. [Сигналы в подробностях |components#Сигналы в подробностях]. + Интерактивные компоненты ======================== -Презентеры имеют встроенную систему компонентов. Компоненты — это отдельные повторно используемые единицы, которые мы вставляем в презентеры. Это могут быть [формы |forms:in-presenter], датагриды, меню, в общем, все, что имеет смысл использовать повторно. +В презентеры встроена система компонентов. Компоненты - самостоятельные переиспользуемые единицы, которые мы встраиваем в презентеры. Это могут быть [формы |forms:in-presenter], таблицы данных, меню - в общем, всё, что имеет смысл использовать повторно. -Как компоненты вставляются в презентер и затем используются? Это вы узнаете в главе [Компоненты |components]. Вы даже узнаете, что у них общего с Голливудом. +Как компоненты встраиваются в презентеры и затем используются? Вы узнаете это в главе [Компоненты |components]. Вы даже выясните, что у них общего с Голливудом. -А где я могу получить компоненты? На странице [Componette |https://componette.org/search/component] вы найдете open-source компоненты, а также множество других дополнений для Nette, которые разместили добровольцы из сообщества вокруг фреймворка. +А где взять компоненты? На [Componette |https://componette.org/search/component] вы найдёте компоненты с открытым кодом и множество других дополнений для Nette, созданных добровольцами из сообщества фреймворка. -Идем в глубину -============== +Погружаемся глубже +================== .[tip] -Того, что мы до сих пор показали в этой главе, вам, скорее всего, будет вполне достаточно. Следующие строки предназначены для тех, кто интересуется презентерами в глубину и хочет знать абсолютно все. +То, что мы разобрали в этой главе до сих пор, скорее всего, покроет большинство случаев. Следующие разделы предназначены тем, кто хочет погрузиться в презентеры глубже и знать совершенно всё. -Валидация параметров --------------------- +Проверка параметров +------------------- -Значения [параметров запроса |#Параметры запроса] и [персистентных параметров |#Персистентные параметры], полученные из URL, записываются в свойства методом `loadState()`. Он также проверяет, соответствует ли тип данных, указанный у свойства, иначе отвечает ошибкой 404, и страница не отображается. +Значения [параметров запроса |#Параметры запроса] и [постоянных параметров |#Постоянные параметры], полученные из URL, записываются в свойства методом `loadState()`. Он также проверяет, соответствуют ли они типу данных, указанному у свойства; иначе он ответит ошибкой 404, и страница не отобразится. -Никогда слепо не доверяйте параметрам, потому что они могут быть легко изменены пользователем в URL. Так, например, мы проверим, что язык `$this->lang` находится среди поддерживаемых. Подходящий способ — переопределить упомянутый метод `loadState()`: +Никогда не доверяйте параметрам из URL вслепую, потому что пользователь легко может их переписать. Вот, например, как мы проверили бы, входит ли язык `$this->lang` в число поддерживаемых. Подходящий способ - переопределить упомянутый метод `loadState()`: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -347,8 +392,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // здесь устанавливается $this->lang - // следует собственная проверка значения: + parent::loadState($params); // здесь задаётся $this->lang + // далее идёт собственная проверка значения: if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } @@ -360,27 +405,27 @@ class ProductPresenter extends Nette\Application\UI\Presenter Сохранение и восстановление запроса ----------------------------------- -Запрос, который обрабатывает презентер, является объектом [api:Nette\Application\Request] и возвращается методом презентера `getRequest()`. +Запрос, обрабатываемый презентером, - объект [api:Nette\Application\Request], возвращаемый методом презентера `getRequest()`. -Текущий запрос можно сохранить в сессии или, наоборот, восстановить из нее и позволить презентеру снова его выполнить. Это полезно, например, в ситуации, когда пользователь заполняет форму, и у него истекает сессия. Чтобы не потерять данные, перед перенаправлением на страницу входа мы сохраняем текущий запрос в сессию с помощью `$reqId = $this->storeRequest()`, который возвращает его идентификатор в виде короткой строки, и передаем его как параметр презентеру входа. +Текущий запрос можно сохранить в сессию или, наоборот, восстановить из неё и дать презентеру выполнить его заново. Это полезно, например, когда пользователь заполняет форму, а его сессия входа истекает. Чтобы не потерять данные, перед перенаправлением на страницу входа мы сохраняем текущий запрос в сессию через `$reqId = $this->storeRequest()`. Это возвращает его идентификатор в виде короткой строки, которую мы затем передаём параметром презентеру входа. -После входа мы вызываем метод `$this->restoreRequest($reqId)`, который извлекает запрос из сессии и перенаправляет на него. Метод при этом проверяет, что запрос создал тот же пользователь, который сейчас вошел в систему. Если вошел другой пользователь или ключ недействителен, он ничего не делает, и программа продолжает работу. +После входа мы вызываем метод `$this->restoreRequest($reqId)`, который достаёт запрос из сессии. POST-запросы перебрасываются в него, а остальные (GET) перенаправляются на URL запроса. Метод проверяет, что запрос был создан тем же пользователем, который сейчас вошёл. Если войдёт другой пользователь или ключ недействителен, метод ничего не делает, и программа продолжает работу как обычно. -Посмотрите руководство [Как вернуться к предыдущей странице |best-practices:restore-request]. +См. руководство [Как вернуться на предыдущую страницу |best-practices:restore-request]. Канонизация ----------- -Презентеры обладают одной действительно замечательной особенностью, которая способствует лучшему SEO (оптимизации для поисковых систем). Они автоматически предотвращают существование дублирующегося контента на разных URL. Если к определенной цели ведет несколько URL-адресов, например, `/index` и `/index?page=1`, фреймворк определяет один из них как основной (канонический) и остальные перенаправляет на него с помощью HTTP-кода 301. Благодаря этому поисковые системы не индексируют страницы дважды и не размывают их page rank. +У презентеров есть по-настоящему прекрасная возможность, способствующая лучшему SEO (поисковой оптимизации). Они автоматически предотвращают существование одинакового содержимого под разными URL. Если к определённой цели ведут несколько URL, например `/index` и `/index?page=1`, фреймворк объявляет один из них основным (каноническим) и перенаправляет остальные на него HTTP-кодом 301. Благодаря этому поисковые системы не индексируют ваши страницы дважды и не размывают их вес. -Этот процесс называется канонизацией. Каноническим URL является тот, который генерирует [маршрутизатор |routing], как правило, это первый соответствующий маршрут в коллекции. +Этот процесс называется канонизацией. Канонический URL - тот, который порождает [маршрутизатор|routing], обычно первый подходящий маршрут в наборе. Канонизация включена по умолчанию и может быть отключена через `$this->autoCanonicalize = false`. -Перенаправление не происходит при AJAX- или POST-запросе, так как это привело бы к потере данных или не имело бы дополнительной ценности с точки зрения SEO. +Перенаправление не происходит при AJAX- или POST-запросах, потому что это могло бы привести к потере данных или не дало бы никакой пользы для SEO. -Канонизацию можно вызвать и вручную с помощью метода `canonicalize()`, которому, подобно методу `link()`, передается презентер, действие и параметры. Он создает ссылку и сравнивает ее с текущим URL-адресом. Если они отличаются, он перенаправляет на сгенерированную ссылку. +Вы можете вызвать канонизацию и вручную, методом `canonicalize()`. Как и методу `link()`, вы передаёте ему презентер, действие и параметры. Он порождает ссылку и сравнивает её с текущим URL-адресом. Если они различаются, он перенаправляет на порождённую ссылку. ```php public function actionShow(int $id, ?string $slug = null): void @@ -391,31 +436,13 @@ public function actionShow(int $id, ?string $slug = null): void } ``` - -События -------- - -Кроме методов `startup()`, `beforeRender()` и `shutdown()`, которые вызываются как часть жизненного цикла презентера, можно определить еще другие функции, которые должны вызываться автоматически. Презентер определяет так называемые [события |nette:glossary#События Events], обработчики которых вы добавляете в массивы `$onStartup`, `$onRender` и `$onShutdown`. - -```php -class ArticlePresenter extends Nette\Application\UI\Presenter -{ - public function __construct() - { - $this->onStartup[] = function () { - // ... - }; - } -} -``` - -Обработчики в массиве `$onStartup` вызываются непосредственно перед методом `startup()`, далее `$onRender` между `beforeRender()` и `render()`, и наконец `$onShutdown` непосредственно перед `shutdown()`. +Полный пример, объединяющий фильтры маршрутов с `canonicalize()` ради дружественных к SEO URL, см. в [Красивые URL со слагами |best-practices:pretty-urls]. Ответы ------ -Ответ, который возвращает презентер, является объектом, реализующим интерфейс [api:Nette\Application\Response]. Доступно несколько готовых ответов: +Ответ, возвращаемый презентером, - объект, реализующий интерфейс [api:Nette\Application\Response]. Доступно несколько готовых ответов: - [api:Nette\Application\Responses\CallbackResponse] - отправляет callback - [api:Nette\Application\Responses\FileResponse] - отправляет файл @@ -430,13 +457,13 @@ class ArticlePresenter extends Nette\Application\UI\Presenter ```php use Nette\Application\Responses; -// Простой текст +// Обычный текст $this->sendResponse(new Responses\TextResponse('Hello Nette!')); // Отправляет файл $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Ответом будет callback +// Отправляет callback $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { echo '

    Hello

    '; @@ -445,28 +472,88 @@ $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $ht $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +Вы можете написать и собственный ответ. Достаточно реализовать интерфейс `Nette\Application\Response` с единственным методом `send()`, получающим HTTP-запрос и ответ. Это полезно, например, при потоковой передаче данных, которые вы не хотите держать в памяти: + +```php +class CsvResponse implements Nette\Application\Response +{ + public function __construct( + private string $fileName, + private iterable $rows, + ) { + } + + public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void + { + $response->setContentType('text/csv', 'utf-8'); + $response->sendAsFile($this->fileName); + + $handle = fopen('php://output', 'w'); + foreach ($this->rows as $row) { + fputcsv($handle, $row); + } + + fclose($handle); + } +} +``` + +Затем вы отправляете его в презентере как обычно: `$this->sendResponse(new CsvResponse('export.csv', $rows));` + + +HTTP-кеширование +---------------- + +Метод `lastModified()` позволяет легко воспользоваться HTTP-кешированием. Вы передаёте ему дату и время последнего изменения содержимого (как временную метку, строку или объект `DateTimeInterface`), а при желании ещё валидатор ETag (короткую строку, определяющую текущую версию содержимого, например её хеш) и время истечения. Если у браузера уже есть подходящая версия, презентер отправляет ответ `304 Not Modified` и завершается, поэтому страница не отрисовывается и не передаётся зря: + +```php +public function renderArticle(int $id): void +{ + $article = $this->articles->getById($id); + $this->lastModified($article->updatedAt); + // ... +} +``` + + +Завершение шаблона .{data-version:3.3.0} +---------------------------------------- -Ограничение доступа с помощью `#[Requires]` .{data-version:3.2.2} ------------------------------------------------------------------ +Когда презентер отрисовывает шаблон, метод `sendTemplate()` прямо перед отрисовкой вызывает `completeTemplate()`. Этот метод заполняет переменные, помеченные атрибутом `#[TemplateVariable]`, и находит файл шаблона (переменные по умолчанию уже задаёт `TemplateFactory` при создании шаблона). Вы можете переопределить этот защищённый метод, чтобы добавить переменные, общие для всех представлений, или задать другой файл: -Атрибут `#[Requires]` предоставляет расширенные возможности для ограничения доступа к презентерам и их методам. Его можно использовать для указания HTTP-методов, требования AJAX-запроса, ограничения на тот же источник (same origin) и доступа только через переадресацию (forwarding). Атрибут можно применять как к классам презентеров, так и к отдельным методам `action()`, `render()`, `handle()` и `createComponent()`. +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My App'; +} +``` -Вы можете указать следующие ограничения: -- на HTTP-методы: `#[Requires(methods: ['GET', 'POST'])]` + +Ограничение доступа через `#[Requires]` .{data-version:3.2.3} +------------------------------------------------------------- + +Атрибут `#[Requires]` даёт продвинутые возможности ограничить доступ к презентерам и их методам. Им можно задать HTTP-методы, потребовать AJAX-запрос, ограничить тем же источником и разрешить доступ только через переброску. Атрибут можно применять и к классам презентеров, и к отдельным методам вроде `action()`, `render()`, `handle()` и `createComponent()`. + +Вы можете задать такие ограничения: +- по HTTP-методам: `#[Requires(methods: ['GET', 'POST'])]` - требование AJAX-запроса: `#[Requires(ajax: true)]` - доступ только с того же источника: `#[Requires(sameOrigin: true)]` -- доступ только через forward: `#[Requires(forward: true)]` -- ограничение на конкретные действия: `#[Requires(actions: 'default')]` +- доступ только через переброску: `#[Requires(forward: true)]` +- ограничения для конкретных действий: `#[Requires(actions: 'default')]` -Подробности можно найти в руководстве [Как использовать атрибут Requires |best-practices:attribute-requires]. +.[note] +Начиная с версии 3.3 совпадение источника проверяется по заголовку браузера `Sec-Fetch-Site` (раньше через cookie SameSite), что надёжнее и проверяет точное совпадение схемы, домена и порта. + +Подробности в руководстве [Как использовать атрибут Requires |best-practices:attribute-requires]. Проверка HTTP-метода -------------------- -Презентеры в Nette автоматически проверяют HTTP-метод каждого входящего запроса. Причиной этой проверки является прежде всего безопасность. По умолчанию разрешены методы `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. +Презентеры в Nette автоматически проверяют HTTP-метод каждого входящего запроса, прежде всего из соображений безопасности. По умолчанию разрешены методы `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`. -Если вы хотите дополнительно разрешить, например, метод `OPTIONS`, используйте для этого атрибут `#[Requires]` (начиная с Nette Application v3.2): +Если вы хотите дополнительно разрешить, например, метод `OPTIONS`, используйте атрибут `#[Requires]` (начиная с Nette Application v3.2.3): ```php #[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] @@ -475,26 +562,34 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -В версии 3.1 проверка выполняется в `checkHttpMethod()`, которая проверяет, содержится ли метод, указанный в запросе, в массиве `$presenter->allowedMethods`. Добавление метода сделайте так: +Начиная с версии 3.1.13 проверка выполняется в `checkHttpMethod()`, который проверяет, входит ли указанный в запросе метод в массив `$presenter->allowedMethods`. Начиная с версии 3.2.3 этот подход объявлен устаревшим в пользу `#[Requires]`. Переопределить метод можно так: ```php class MyPresenter extends Nette\Application\UI\Presenter { - protected function checkHttpMethod(): void - { - $this->allowedMethods[] = 'OPTIONS'; - parent::checkHttpMethod(); - } + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } } ``` -Важно подчеркнуть, что если вы разрешите метод `OPTIONS`, вы должны затем также соответствующим образом обработать его в рамках своего презентера. Метод часто используется как так называемый preflight request, который браузер автоматически отправляет перед фактическим запросом, когда необходимо выяснить, разрешен ли запрос с точки зрения политики CORS (Cross-Origin Resource Sharing). Если вы разрешите метод, но не реализуете правильный ответ, это может привести к несоответствиям и потенциальным проблемам безопасности. +Важно подчеркнуть, что если вы разрешаете метод `OPTIONS`, вы должны затем соответствующим образом обработать его в своём презентере. Этот метод часто используется как так называемый предварительный (preflight) запрос, который браузер автоматически отправляет перед настоящим запросом, когда нужно определить, допустим ли запрос по политике CORS (Cross-Origin Resource Sharing). Если вы разрешите метод, но не реализуете правильный ответ, это может привести к несогласованности и возможным проблемам с безопасностью. -Дальнейшее чтение -================= +Пометка устаревших действий .{data-version:3.2.3} +------------------------------------------------- + +Атрибут `#[Deprecated]` помечает действия, сигналы или целые презентеры как устаревшие и предназначенные к будущему удалению. При порождении ссылок на устаревшие части приложения Nette выдаёт предупреждение, чтобы обратить на это внимание разработчиков. + +Атрибут можно применить как ко всему классу презентера, так и к отдельным методам `action()`, `render()` и `handle()`. + + +Дополнительные материалы +======================== - [Методы и атрибуты inject |best-practices:inject-method-attribute] -- [Сборка презентеров из трейтов |best-practices:presenter-traits] +- [Составление презентеров из трейтов |best-practices:presenter-traits] - [Передача настроек в презентеры |best-practices:passing-settings-to-presenters] -- [Как вернуться к предыдущей странице |best-practices:restore-request] +- [Как вернуться на предыдущую страницу |best-practices:restore-request] diff --git a/application/ru/routing.texy b/application/ru/routing.texy index 4e2896a131..7981746ea4 100644 --- a/application/ru/routing.texy +++ b/application/ru/routing.texy @@ -3,26 +3,26 @@
    -Маршрутизатор отвечает за все, что связано с URL-адресами, чтобы вам больше не приходилось о них думать. Мы покажем: +Маршрутизатор берёт на себя всё, что связано с URL-адресами, так что вам о них думать не приходится. Мы покажем: -- как настроить маршрутизатор, чтобы URL были такими, как вы хотите -- расскажем о SEO и перенаправлении +- как настроить маршрутизатор, чтобы URL выглядели так, как вы хотите +- обсудим SEO и перенаправления - и покажем, как написать собственный маршрутизатор
    -Более человечные URL (или также крутые или красивые URL) более удобны в использовании, запоминаемы и положительно влияют на SEO. Nette учитывает это и полностью идет навстречу разработчикам. Вы можете спроектировать для своего приложения именно такую структуру URL-адресов, какую захотите. Вы можете спроектировать ее даже тогда, когда приложение уже готово, потому что это не потребует изменений в коде или шаблонах. Она определяется элегантным способом в одном [единственном месте |#Включение в приложение], в маршрутизаторе, и таким образом не разбросана в виде аннотаций во всех презентерах. +Более дружелюбные к человеку URL (их называют также красивыми) удобнее, лучше запоминаются и положительно влияют на SEO. Nette помнит об этом и полностью идёт навстречу потребностям разработчиков. Вы можете спроектировать для своего приложения ровно ту структуру URL, которую хотите. Вы можете спроектировать её даже тогда, когда приложение уже готово, потому что это не требует изменений ни в коде, ни в шаблонах. Она изящно задаётся в [одном месте |#Встраивание], в маршрутизаторе, а не разбросана аннотациями по всем презентерам. -Маршрутизатор в Nette уникален тем, что он **двусторонний.** Он умеет как декодировать URL в HTTP-запросе, так и создавать ссылки. Таким образом, он играет ключевую роль в [Nette Application |how-it-works#Nette Application], поскольку не только решает, какой презентер и действие будут выполнять текущий запрос, но также используется для [генерации URL |creating-links] в шаблоне и т. д. +Маршрутизатор в Nette исключителен тем, что он **двунаправленный**. Он умеет и расшифровывать URL из HTTP-запросов, и создавать ссылки. Тем самым он играет ключевую роль в [Nette Application |how-it-works#Nette Application], потому что не только решает, какой презентер и действие выполнят текущий запрос, но и используется для [порождения URL |creating-links] в шаблонах и не только. -Однако маршрутизатор не ограничен только этим использованием, вы можете использовать его в приложениях, где презентеры вообще не используются, для REST API и т. д. Подробнее в разделе [#Самостоятельное использование]. +Однако маршрутизатор не ограничивается таким применением: вы можете использовать его в приложениях, где презентеры вообще не применяются, для REST API и прочего. Подробности в разделе [#Самостоятельное использование]. -Коллекция маршрутов -=================== +Набор маршрутов +=============== -Самый приятный способ определения вида URL-адресов в приложении предлагает класс [api:Nette\Application\Routers\RouteList]. Определение состоит из списка так называемых маршрутов, то есть масок URL-адресов и связанных с ними презентеров и действий, с помощью простого API. Маршруты не нужно никак именовать. +Самый приятный способ задать структуру URL-адресов приложения предлагает класс [api:Nette\Application\Routers\RouteList]. Определение состоит из списка так называемых маршрутов, то есть масок URL-адресов и связанных с ними презентеров и действий, через простой API. Никак называть маршруты не нужно. ```php $router = new Nette\Application\Routers\RouteList; @@ -31,104 +31,104 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -Пример говорит, что если в браузере открыть `https://domain.com/rss.xml`, отобразится презентер `Feed` с действием `rss`, если `https://domain.com/article/12`, отобразится презентер `Article` с действием `view` и т. д. В случае ненахождения подходящего маршрута Nette Application реагирует выбрасыванием исключения [BadRequestException |api:Nette\Application\BadRequestException], которое отображается пользователю как страница ошибки 404 Not Found. +Пример показывает, что если мы откроем в браузере `https://domain.com/rss.xml`, отобразится презентер `Feed` с действием `rss`. Если `https://domain.com/article/12`, отобразится презентер `Article` с действием `view` и так далее. Если подходящего маршрута не найдено, Nette Application отвечает выбрасыванием [BadRequestException |api:Nette\Application\BadRequestException], которое показывается пользователю как страница ошибки 404 Not Found. Порядок маршрутов ----------------- -**Ключевым является порядок**, в котором перечислены отдельные маршруты, поскольку они оцениваются последовательно сверху вниз. Действует правило, что маршруты объявляются **от специфических к общим**: +**Порядок**, в котором перечислены отдельные маршруты, совершенно **решающий**, потому что они проверяются по очереди сверху вниз. Правило в том, что маршруты объявляются **от частных к общим**: ```php -// НЕПРАВИЛЬНО: 'rss.xml' перехватит первый маршрут и поймет эту строку как +// НЕВЕРНО: 'rss.xml' перехватывается первым маршрутом и воспринимается как $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); -// ПРАВИЛЬНО +// ВЕРНО $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Маршруты оцениваются сверху вниз также при генерации ссылок: +Маршруты проверяются сверху вниз и при порождении ссылок: ```php -// НЕПРАВИЛЬНО: ссылка на 'Feed:rss' сгенерируется как 'admin/feed/rss' +// НЕВЕРНО: ссылка на 'Feed:rss' порождается как 'admin/feed/rss' $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); -// ПРАВИЛЬНО +// ВЕРНО $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Мы не будем скрывать от вас, что правильное составление маршрутов требует определенного навыка. Пока вы не освоите его, полезным помощником будет [панель маршрутизации |#Отладка маршрутизатора]. +Не будем скрывать: правильно собрать маршруты требует некоторого навыка. Пока вы им не овладеете, полезным инструментом будет [панель маршрутизации |#Отладка маршрутизатора]. Маска и параметры ----------------- -Маска описывает относительный путь от корневого каталога сайта. Самой простой маской является статический URL: +Маска описывает относительный путь от корневого каталога сайта. Простейшая маска - статический URL: ```php $router->addRoute('products', 'Products:default'); ``` -Часто маски содержат так называемые **параметры**. Они указываются в угловых скобках (например, ``) и передаются в целевой презентер, например, методу `renderShow(int $year)` или в персистентный параметр `$year`: +Часто маски содержат так называемые **параметры**. Они заключаются в угловые скобки (например, ``) и передаются целевому презентеру, например в метод `renderShow(int $year)` или в постоянный параметр `$year`: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Пример говорит, что если в браузере открыть `https://example.com/chronicle/2020`, отобразится презентер `History` с действием `show` и параметром `year: 2020`. +Пример показывает, что если мы откроем в браузере `https://example.com/chronicle/2020`, отобразится презентер `History` с действием `show` и параметром `year: 2020`. -Параметрам можно определить значение по умолчанию прямо в маске, и тем самым они станут необязательными: +Значение по умолчанию для параметров можно задать прямо в маске, и тогда они станут необязательными: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Маршрут теперь будет принимать и URL `https://example.com/chronicle/`, который снова отобразит `History:show` с параметром `year: 2020`. +Теперь маршрут примет и URL `https://example.com/chronicle/`, который снова отобразит `History:show` с параметром `year: 2020`. -Параметром может быть, конечно, и имя презентера и действия. Например, так: +Разумеется, параметрами могут быть и имена презентера и действия. Например: ```php $router->addRoute('/', 'Home:default'); ``` -Указанный маршрут принимает, например, URL вида `/article/edit` или `/catalog/list` и понимает их как презентеры и действия `Article:edit` и `Catalog:list`. +Указанный маршрут принимает, например, URL вида `/article/edit` или `/catalog/list` и понимает их как презентеры и действия `Article:edit` и `Catalog:list` соответственно. -Одновременно он дает параметрам `presenter` и `action` значения по умолчанию `Home` и `default`, и они, следовательно, также необязательны. Так что маршрут принимает и URL вида `/article` и понимает его как `Article:default`. Или наоборот, ссылка на `Product:default` сгенерирует путь `/product`, ссылка на стандартный `Home:default` путь `/`. +При этом он задаёт параметрам `presenter` и `action` значения по умолчанию `Home` и `default`, благодаря чему они тоже становятся необязательными. Таким образом, маршрут принимает и URL вида `/article` и понимает его как `Article:default`. И наоборот, ссылка на `Product:default` порождает путь `/product`, а ссылка на `Home:default` по умолчанию порождает путь `/`. -Маска может описывать не только относительный путь от корневого каталога сайта, но и абсолютный путь, если начинается со слеша, или даже полный абсолютный URL, если начинается с двух слешей: +Маска может описывать не только относительный путь от корневого каталога сайта, но и абсолютный путь, если начинается со слеша, или даже весь абсолютный URL, если начинается с двух слешей: ```php -// относительно document root +// относительно корня документов $router->addRoute('/', /* ... */); // абсолютный путь (относительно домена) $router->addRoute('//', /* ... */); -// абсолютный URL включая домен (относительно схемы) +// абсолютный URL с доменом (относительно схемы) $router->addRoute('//.example.com//', /* ... */); -// абсолютный URL включая схему +// абсолютный URL со схемой $router->addRoute('https://.example.com//', /* ... */); ``` -Выражения валидации -------------------- +Проверочные выражения +--------------------- -Для каждого параметра можно установить условие валидации с помощью [регулярного выражения |https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Например, для параметра `id` мы укажем, что он может принимать только цифры с помощью регулярного выражения `\d+`: +Для каждого параметра можно задать условие проверки с помощью [регулярного выражения|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php]. Например, для параметра `id` мы указываем, что он может содержать только цифры, регулярным выражением `\d+`: ```php $router->addRoute('/[/]', /* ... */); ``` -Стандартным регулярным выражением для всех параметров является `[^/]+`, т. е. все, кроме слеша. Если параметр должен принимать и слеши, укажем выражение `.+`: +Регулярное выражение по умолчанию для всех параметров - `[^/]+`, то есть всё, кроме слеша. Если параметр должен принимать и слеши, мы задаём выражение `.+`: ```php -// принимает https://example.com/a/b/c, path будет 'a/b/c' +// принимает https://example.com/a/b/c, путь будет 'a/b/c' $router->addRoute('', /* ... */); ``` @@ -136,17 +136,17 @@ $router->addRoute('', /* ... */); Необязательные последовательности --------------------------------- -В маске можно обозначать необязательные части с помощью квадратных скобок. Необязательной может быть любая часть маски, в ней могут находиться и параметры: +В маске необязательные части можно пометить квадратными скобками. Необязательной может быть любая часть маски, и она может содержать параметры: ```php $router->addRoute('[/]', /* ... */); // Принимает пути: -// /cs/download => lang => cs, name => download +// /en/download => lang => en, name => download // /download => lang => null, name => download ``` -Когда параметр является частью необязательной последовательности, он, разумеется, также становится необязательным. Если у него нет указанного значения по умолчанию, то он будет null. +Когда параметр входит в необязательную последовательность, он, естественно, тоже становится необязательным. Если у него не задано значение по умолчанию, он будет null. Необязательные части могут быть и в домене: @@ -154,7 +154,7 @@ $router->addRoute('[/]', /* ... */); $router->addRoute('//[.]example.com//', /* ... */); ``` -Последовательности можно произвольно вкладывать и комбинировать: +Последовательности можно вкладывать и сочетать как угодно: ```php $router->addRoute( @@ -163,23 +163,23 @@ $router->addRoute( ); // Принимает пути: -// /cs/hello +// /en/hello // /en-us/hello // /hello // /hello/page-12 ``` -При генерации URL стремимся к кратчайшему варианту, поэтому все, что можно опустить, опускается. Поэтому, например, маршрут `index[.html]` генерирует путь `/index`. Изменить поведение можно, указав восклицательный знак после левой квадратной скобки: +При порождении URL предпочитается самый короткий вариант, поэтому всё, что можно опустить, опускается. Так, например, маршрут `index[.html]` порождает путь `/index`. Это поведение можно перевернуть, поставив после левой квадратной скобки восклицательный знак: ```php -// принимает /hello и /hello.html, генерирует /hello +// принимает /hello и /hello.html, порождает /hello $router->addRoute('[.html]', /* ... */); -// принимает /hello и /hello.html, генерирует /hello.html +// принимает /hello и /hello.html, порождает /hello.html $router->addRoute('[!.html]', /* ... */); ``` -Необязательные параметры (т. е. параметры, имеющие значение по умолчанию) без квадратных скобок ведут себя по сути так, как если бы они были заключены в скобки следующим образом: +Необязательные параметры (то есть параметры со значением по умолчанию) без квадратных скобок по сути ведут себя так, будто заключены следующим образом: ```php $router->addRoute('//', /* ... */); @@ -188,7 +188,7 @@ $router->addRoute('//', /* ... */); $router->addRoute('[/[/[]]]', /* ... */); ``` -Если мы хотим повлиять на поведение конечного слеша, чтобы, например, вместо `/home/` генерировалось только `/home`, этого можно достичь так: +Если мы хотим повлиять на поведение завершающего слеша, чтобы, например, порождался `/home` вместо `/home/`, этого можно добиться так: ```php $router->addRoute('[[/[/]]]', /* ... */); @@ -198,7 +198,7 @@ $router->addRoute('[[/[/]]]', /* ... */); Подстановочные знаки -------------------- -В маске абсолютного пути мы можем использовать следующие подстановочные знаки и избежать, например, необходимости записывать в маску домен, который может отличаться в среде разработки и production: +В маске абсолютного URL мы можем использовать следующие подстановочные знаки, чтобы, например, не приходилось писать в маске домен, который может различаться в среде разработки и в производственной среде: - `%tld%` = домен верхнего уровня, например `com` или `org` - `%sld%` = домен второго уровня, например `example` @@ -208,14 +208,14 @@ $router->addRoute('[[/[/]]]', /* ... */); ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); -$router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('//www.%sld%.%tld%/%basePath%//', /* ... */); ``` Расширенная запись ------------------ -Цель маршрута, обычно записываемая в виде `Presenter:action`, может быть также записана с помощью массива, который определяет отдельные параметры и их значения по умолчанию: +Цель маршрута, обычно записываемую в формате `Презентер:действие`, можно записать и массивом, задающим отдельные параметры и их значения по умолчанию: ```php $router->addRoute('/[/]', [ @@ -224,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Для более детальной спецификации можно использовать еще более расширенную форму, где кроме значений по умолчанию можно настроить и другие свойства параметров, например, валидационное регулярное выражение (см. параметр `id`): +Для более подробного описания можно использовать ещё более развёрнутую форму, где, помимо значений по умолчанию, можно задать другие свойства параметров, например проверочное регулярное выражение (см. параметр `id`): ```php use Nette\Routing\Route; @@ -242,19 +242,29 @@ $router->addRoute('/[/]', [ ]); ``` -Важно отметить, что если параметры, определенные в массиве, не указаны в маске пути, их значения нельзя изменить, даже с помощью query-параметров, указанных после вопросительного знака в URL. +Важно отметить, что если параметры, заданные в массиве, не перечислены в маске пути, их значения изменить нельзя, даже параметрами запроса, указанными после вопросительного знака в URL. + +Это полезно для **фиксированных параметров**: чтобы дать конкретной странице короткий запоминающийся URL. Например, чтобы `/tos` всегда открывал `Article:view` с `id: 123`: + +```php +$router->addRoute('tos', [ + 'presenter' => 'Article', + 'action' => 'view', + 'id' => 123, +]); +``` Фильтры и переводы ------------------ -Исходные коды приложения мы пишем на английском языке, но если сайт должен иметь русские URL, то простая маршрутизация типа: +Исходный код приложения мы пишем по-английски, но если у сайта должны быть чешские URL, то простая маршрутизация вида: ```php $router->addRoute('/', 'Home:default'); ``` -будет генерировать английские URL, например `/product/123` или `/cart`. Если мы хотим, чтобы презентеры и действия в URL были представлены русскими словами (например, `/продукт/123` или `/корзина`), мы можем использовать словарь перевода. Для его записи уже нужна "более многословная" версия второго параметра: +будет порождать английские URL вроде `/product/123` или `/cart`. Если мы хотим, чтобы презентеры и действия в URL были представлены чешскими словами (например, `/produkt/123` или `/kosik`), мы можем использовать словарь переводов. Для его записи нам уже нужен "более многословный" вариант второго параметра: ```php use Nette\Routing\Route; @@ -265,24 +275,24 @@ $router->addRoute('/', [ Route::FilterTable => [ // строка в URL => презентер 'produkt' => 'Product', - 'korzina' => 'Cart', + 'kosik' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'spisok' => 'list', + 'seznam' => 'list', ], ], ]); ``` -Несколько ключей словаря перевода могут вести на один и тот же презентер. Таким образом, к нему создаются различные псевдонимы. Каноническим вариантом (то есть тем, который будет в сгенерированном URL) считается последний ключ. +Несколько ключей словаря переводов могут вести к одному презентеру. Так у него возникают разные псевдонимы. Последний ключ считается каноническим вариантом (то есть тем, который окажется в порождённом URL). -Таблицу перевода можно таким образом использовать для любого параметра. При этом, если перевод не существует, берется исходное значение. Это поведение можно изменить, добавив `Route::FilterStrict => true`, и маршрут тогда отклонит URL, если значение отсутствует в словаре. +Таблицу переводов можно так использовать для любого параметра. Если перевода нет, берётся исходное значение. Это поведение можно изменить, добавив `Route::FilterStrict => true`, и тогда маршрут отклонит URL, если значения нет в словаре. -Кроме словаря перевода в виде массива, можно применить и собственные функции перевода. +Помимо словаря переводов в виде массива можно применять и собственные функции перевода. ```php use Nette\Routing\Route; @@ -298,15 +308,15 @@ $router->addRoute('//', [ ]); ``` -Функция `Route::FilterIn` преобразует параметр в URL в строку, которая затем передается в презентер, функция `FilterOut` обеспечивает преобразование в обратном направлении. +Функция `Route::FilterIn` преобразует параметр из URL в строку, которая затем передаётся презентеру; функция `FilterOut` обеспечивает преобразование в обратную сторону. -Параметры `presenter`, `action` и `module` уже имеют предопределенные фильтры, которые преобразуют между стилем PascalCase или camelCase и kebab-case, используемым в URL. Значение по умолчанию параметров записывается уже в преобразованном виде, поэтому, например, в случае презентера пишем ``, а не ``. +У параметров `presenter`, `action` и `module` уже есть предопределённые фильтры, преобразующие стиль PascalCase или camelCase в kebab-case, используемый в URL. Значение параметров по умолчанию записывается в том виде, в каком оно передаётся приложению (PascalCase для презентера и модуля, camelCase для действия), поэтому, например, в случае презентера мы пишем ``, а не ``. Общие фильтры ------------- -Помимо фильтров, предназначенных для конкретных параметров, мы можем определить также общие фильтры, которые получают ассоциативный массив всех параметров, которые могут как угодно модифицировать и затем вернуть. Общие фильтры определяем под ключом `null`. +Помимо фильтров, предназначенных для конкретных параметров, мы можем задать и общие фильтры, которые получают ассоциативный массив всех параметров, могут как угодно его изменить и вернуть. Общие фильтры задаются под пустым ключом. ```php use Nette\Routing\Route; @@ -321,65 +331,75 @@ $router->addRoute('/', [ ]); ``` -Общие фильтры дают возможность настроить поведение маршрута абсолютно любым способом. Мы можем использовать их, например, для модификации параметров на основе других параметров. Например, перевод `` и `` на основе текущего значения параметра ``. +Общие фильтры дают возможность совершенно как угодно изменить поведение маршрута. Мы можем использовать их, например, чтобы менять параметры на основе других параметров. Скажем, переводить `` и `` по текущему значению параметра ``. -Если у параметра определен собственный фильтр и одновременно существует общий фильтр, выполняется собственный `FilterIn` перед общим и, наоборот, общий `FilterOut` перед собственным. То есть внутри общего фильтра значения параметров `presenter` или `action` записаны в стиле PascalCase или camelCase. +Если у параметра задан собственный фильтр и при этом существует общий, свой `FilterIn` выполняется раньше общего, и наоборот, общий `FilterOut` выполняется раньше своего. Таким образом, внутри общего фильтра значения параметров `presenter` и `action` записаны в стиле PascalCase или camelCase соответственно. +О практическом применении этих фильтров, порождении дружественных к SEO URL вроде `/article/123-how-to-bake-bread` без изменения шаблонов, см. [Красивые URL со слагами |best-practices:pretty-urls]. -Односторонние маршруты OneWay ------------------------------ -Односторонние маршруты используются для сохранения функциональности старых URL, которые приложение уже не генерирует, но все еще принимает. Мы помечаем их флагом `OneWay`: +Флаг OneWay +----------- + +Односторонние маршруты служат для сохранения работоспособности старых URL, которые приложение больше не порождает, но всё ещё принимает. Мы помечаем их флагом `OneWay`: ```php // старый URL /product-info?id=123 -$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); +$router->addRoute('product-info', 'Product:detail', oneWay: true); // новый URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -При доступе к старому URL презентер автоматически перенаправляет на новый URL, так что поисковые системы не проиндексируют эти страницы дважды (см. [#SEO и канонизация]). +При обращении к старому URL презентер автоматически перенаправляет на новый, поэтому поисковые системы не проиндексируют эти страницы дважды (см. [#SEO и канонизация]). Динамическая маршрутизация с callback-функциями ----------------------------------------------- -Динамическая маршрутизация с callback-функциями позволяет вам напрямую назначать маршрутам функции (callback), которые будут выполнены при посещении данного пути. Эта гибкая функциональность позволяет быстро и эффективно создавать различные конечные точки (endpoints) для вашего приложения: +Динамическая маршрутизация с callback-функциями позволяет напрямую сопоставить маршрутам функции (callback), которые выполняются при посещении данного пути. Эта гибкая возможность позволяет быстро и эффективно создавать разные точки входа в ваше приложение: ```php $router->addRoute('test', function () { - echo 'вы находитесь по адресу /test'; + echo 'You are at the /test address'; }); ``` -Вы также можете определить в маске параметры, которые автоматически передадутся в ваш callback: +Вы можете задать в маске и параметры, которые автоматически передаются в ваш callback: ```php $router->addRoute('', function (string $lang) { echo match ($lang) { - 'cs' => 'Добро пожаловать на чешскую версию нашего сайта!', + 'cs' => 'Welcome to the Czech version of our website!', 'en' => 'Welcome to the English version of our website!', }; }); ``` +Помимо параметров из маски callback может получать и сервисы из DI-контейнера. Они передаются по типу параметра. Кроме того, параметр `$presenter` получает экземпляр [MicroPresenter |api:NetteModule\MicroPresenter], обрабатывающего маршрут: + +```php +$router->addRoute('', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) { + // ... +}); +``` + Модули ------ -Если у нас есть несколько маршрутов, относящихся к общему [модулю |directory-structure#Презентеры и шаблоны], мы используем `withModule()`: +Если у нас несколько маршрутов, относящихся к общему [модулю |directory-structure#Презентеры и шаблоны], мы используем `withModule()`. Указанный модуль автоматически подставляется перед презентером каждого маршрута группы и полностью исчезает из URL: ```php $router = new RouteList; -$router->withModule('Forum') // следующие маршруты являются частью модуля Forum - ->addRoute('rss', 'Feed:rss') // презентер будет Forum:Feed +$router->withModule('Forum') // следующие маршруты входят в модуль Forum + ->addRoute('rss', 'Feed:rss') // презентером будет Forum:Feed ->addRoute('/') - ->withModule('Admin') // следующие маршруты являются частью модуля Forum:Admin + ->withModule('Admin') // следующие маршруты входят в модуль Forum:Admin ->addRoute('sign:in', 'Sign:in'); ``` -Альтернативой является использование параметра `module`: +Альтернатива - параметр `module`, который так же задаёт фиксированный модуль и держит его вне URL: ```php // URL manage/dashboard/default отображается на презентер Admin:Dashboard @@ -388,11 +408,17 @@ $router->addRoute('manage//', [ ]); ``` +Имя каждого презентера полно только вместе с его модулем, например `Front:Admin:ProductList`. Всякий раз, когда такое полное имя попадает в параметр URL, маршрутизатор кодирует его по двум простым правилам: каждое двоеточие `:` (разделитель модулей) становится **точкой**, а каждая граница слов в имени PascalCase - **дефисом**. Так `Front:Admin:ProductList` появляется в URL как `front.admin.product-list` и расшифровывается обратно тем же способом. Именно поэтому модульное приложение без перечисленных выше средств порождает URL, полные точек. + +И `withModule()`, и параметр `module` этого избегают именно потому, что убирают известный префикс модуля из имени презентера прежде, чем оно попадёт в URL: раз модуль постоянен, кодировать его вообще не нужно. + +Иногда мы хотим, чтобы сам модуль менялся и появлялся в URL, и тогда мы берём `` прямо в маску. Осторожно с одной принципиальной деталью: **`` захватывает весь путь модуля**, то есть всё до последнего двоеточия в имени презентера. Для презентера `Shop:Admin:Product` это означает модуль `Shop:Admin` и презентер `Product`, а поскольку двоеточия становятся точками, мы получаем: + Поддомены --------- -Коллекции маршрутов можно разделять по поддоменам: +Наборы маршрутов можно разделять по поддоменам: ```php $router = new RouteList; @@ -401,7 +427,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -В имени домена можно использовать и [#Подстановочные знаки]: +В имени домена тоже можно использовать [#Подстановочные знаки]: ```php $router = new RouteList; @@ -413,20 +439,20 @@ $router->withDomain('example.%tld%') Префикс пути ------------ -Коллекции маршрутов можно разделять по пути в URL: +Наборы маршрутов можно разделять по пути в URL: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // ловит URL /eshop/rss - ->addRoute('/'); // ловит URL /eshop// + ->addRoute('rss', 'Feed:rss') // соответствует URL /eshop/rss + ->addRoute('/'); // соответствует URL /eshop// ``` -Комбинации ----------- +Сочетания +--------- -Вышеупомянутые разделения можно взаимно комбинировать: +Перечисленные группировки можно сочетать друг с другом: ```php $router = (new RouteList) @@ -446,37 +472,37 @@ $router = (new RouteList) ``` -Query-параметры ---------------- +Параметры запроса +----------------- -Маски могут также содержать query-параметры (параметры после вопросительного знака в URL). Для них нельзя определить валидационное выражение, но можно изменить имя, под которым они передадутся в презентер: +Маски могут содержать и параметры запроса (параметры после вопросительного знака в URL). Задать для них проверочное выражение нельзя, но можно изменить имя, под которым они передаются презентеру: ```php -// query-параметр 'cat' мы хотим использовать в приложении под именем 'categoryId' +// мы хотим использовать параметр запроса 'cat' в приложении под именем 'categoryId' $router->addRoute('product ? id= & cat=', /* ... */); ``` -Foo-параметры +Параметры Foo ------------- -Теперь мы углубляемся. Foo-параметры — это, по сути, безымянные параметры, которые позволяют сопоставлять регулярное выражение. Примером является маршрут, принимающий `/index`, `/index.html`, `/index.htm` и `/index.php`: +Теперь копнём глубже. Параметры Foo - по сути безымянные параметры, позволяющие сопоставить регулярное выражение. Пример - маршрут, принимающий `/index`, `/index.html`, `/index.htm` и `/index.php`: ```php $router->addRoute('index', /* ... */); ``` -Можно также явно определить строку, которая будет использоваться при генерации URL. Строка должна быть размещена непосредственно за вопросительным знаком. Следующий маршрут похож на предыдущий, но генерирует `/index.html` вместо `/index`, потому что строка `.html` установлена как генерируемое значение: +Можно и явно задать строку, которая будет использоваться при порождении URL. Строку нужно поместить сразу после вопросительного знака. Следующий маршрут похож на предыдущий, но порождает `/index.html` вместо `/index`, потому что как значение для порождения задана строка `.html`: ```php $router->addRoute('index', /* ... */); ``` -Включение в приложение -====================== +Встраивание +=========== -Чтобы подключить созданный маршрутизатор к приложению, мы должны сообщить о нем DI-контейнеру. Самый простой способ — подготовить фабрику, которая создаст объект маршрутизатора, и сообщить в конфигурации контейнера, что ее нужно использовать. Допустим, для этой цели мы напишем метод `App\Core\RouterFactory::createRouter()`: +Чтобы встроить созданный маршрутизатор в приложение, нам нужно сообщить о нём DI-контейнеру. Проще всего подготовить фабрику, которая создаст объект маршрутизатора, и сказать контейнеру в конфигурации использовать её. Допустим, для этого мы пишем метод `App\Core\RouterFactory::createRouter()`: ```php namespace App\Core; @@ -494,14 +520,14 @@ class RouterFactory } ``` -В [конфигурацию |dependency-injection:services] затем запишем: +Затем мы пишем в [конфигурации |dependency-injection:services]: ```neon services: - App\Core\RouterFactory::createRouter ``` -Любые зависимости, например, от базы данных и т. д., передаются фабричному методу как его параметры с помощью [autowiring |dependency-injection:autowiring]: +Любые зависимости, например от базы данных, передаются в фабричный метод его параметрами через [autowiring|dependency-injection:autowiring]: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -514,22 +540,22 @@ public static function createRouter(Nette\Database\Connection $db): RouteList SimpleRouter ============ -Гораздо более простым маршрутизатором, чем коллекция маршрутов, является [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Мы используем его тогда, когда у нас нет особых требований к форме URL, если недоступен `mod_rewrite` (или его альтернативы) или если мы пока не хотим заниматься красивыми URL. +Гораздо более простой маршрутизатор, чем набор маршрутов, - [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]. Мы используем его, когда у нас нет особых требований к формату URL, если `mod_rewrite` (или его аналоги) недоступен либо если мы пока не хотим возиться с красивыми URL. -Генерирует адреса примерно в таком виде: +Он порождает адреса примерно такого вида: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -Параметром конструктора SimpleRouter является стандартный презентер и действие, на который нужно направлять, если мы открываем страницу без параметров, например, `http://example.com/`. +Параметром конструктора `SimpleRouter` служит презентер и действие по умолчанию, то есть действие, которое выполнится, если мы откроем, например, `http://example.com/` без дополнительных параметров. ```php -// стандартным презентером будет 'Home' и действие 'default' +// презентером по умолчанию будет 'Home', а действием 'default' $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` -Рекомендуем SimpleRouter напрямую определить в [конфигурации |dependency-injection:services]: +Мы рекомендуем задавать SimpleRouter прямо в [конфигурации |dependency-injection:services]: ```neon services: @@ -540,9 +566,9 @@ services: SEO и канонизация ================= -Фреймворк способствует SEO (оптимизации для поисковых систем), предотвращая дублирование контента на разных URL. Если к определенной цели ведет несколько адресов, например, `/index` и `/index.html`, фреймворк определяет первый из них как основной (канонический) и остальные перенаправляет на него с помощью HTTP-кода 301. Благодаря этому поисковые системы не индексируют страницы дважды и не размывают их page rank. +Фреймворк способствует SEO (поисковой оптимизации), предотвращая существование одинакового содержимого под разными URL. Если к определённой цели ведут несколько адресов, например `/index` и `/index.html`, фреймворк объявляет первый основным (каноническим) и перенаправляет остальные на него HTTP-кодом 301. Благодаря этому поисковые системы не индексируют страницы дважды и не размывают их вес. -Этот процесс называется канонизацией. Каноническим URL является тот, который генерирует маршрутизатор, то есть первый подходящий маршрут в коллекции без флага OneWay. Поэтому в коллекции мы указываем **основные маршруты первыми**. +Этот процесс называется канонизацией. Канонический URL - тот, который порождает маршрутизатор, то есть первый подходящий маршрут в наборе без флага OneWay. Поэтому в наборе мы перечисляем **основные маршруты первыми**. Канонизацию выполняет презентер, подробнее в главе [канонизация |presenters#Канонизация]. @@ -550,9 +576,9 @@ SEO и канонизация HTTPS ===== -Чтобы использовать протокол HTTPS, необходимо включить его на хостинге и правильно настроить сервер. +Чтобы использовать протокол HTTPS, нужно включить его на хостинге и правильно настроить сервер. -Перенаправление всего сайта на HTTPS необходимо настроить на уровне сервера, например, с помощью файла .htaccess в корневом каталоге нашего приложения, и это с HTTP-кодом 301. Настройки могут отличаться в зависимости от хостинга и выглядят примерно так: +Перенаправление всего сайта на HTTPS должно быть задано на уровне сервера, например через файл `.htaccess` в корневом каталоге нашего приложения, с HTTP-кодом 301. Настройки могут отличаться в зависимости от хостинга и выглядеть примерно так: ``` @@ -564,15 +590,15 @@ HTTPS ``` -Маршрутизатор генерирует URL с тем же протоколом, с которым была загружена страница, так что больше ничего настраивать не нужно. +Маршрутизатор порождает URL с тем же протоколом, по которому была загружена страница, поэтому больше ничего задавать не нужно. -Однако, если нам в исключительных случаях нужно, чтобы разные маршруты работали под разными протоколами, мы укажем его в маске маршрута: +Однако если нам в виде исключения нужно, чтобы разные маршруты работали по разным протоколам, мы указываем это в маске маршрута: ```php -// Будет генерировать адрес с HTTP +// Будет порождать HTTP-адрес $router->addRoute('http://%host%//', /* ... */); -// Будет генерировать адрес с HTTPS +// Будет порождать HTTPS-адрес $router->addRoute('https://%host%//', /* ... */); ``` @@ -580,24 +606,24 @@ $router->addRoute('https://%host%//', /* ... */); Отладка маршрутизатора ====================== -Панель маршрутизации, отображаемая в [Tracy Bar |tracy:], является полезным помощником, который показывает список маршрутов, а также параметры, которые маршрутизатор получил из URL. +Панель маршрутизации, отображаемая в [Tracy Bar |tracy:], - полезный помощник, показывающий список маршрутов, а также параметры, которые маршрутизатор получил из URL. -Зеленая полоса с символом ✓ представляет маршрут, который обработал текущий URL, синим цветом и символом ≈ обозначены маршруты, которые также обработали бы URL, если бы зеленый их не опередил. Далее мы видим текущий презентер и действие. +Зелёная полоса с символом ✓ обозначает маршрут, который обработал текущий URL; синий цвет и символ ≈ указывают маршруты, которые тоже обработали бы URL, если бы зелёный их не опередил. Далее мы видим текущие презентер и действие. [* routing-debugger.webp *] -Одновременно, если происходит неожиданное перенаправление из-за [канонизации |#SEO и канонизация], полезно посмотреть в панель в строке *redirect*, где вы узнаете, как маршрутизатор изначально понял URL и почему перенаправил. +Кроме того, если из-за [канонизации |#SEO и канонизация] происходит неожиданное перенаправление, полезно посмотреть на панель в полосе *redirect*, где можно выяснить, как маршрутизатор изначально понял URL и почему он перенаправил. .[note] -При отладке маршрутизатора рекомендуем открыть в браузере Developer Tools (Ctrl+Shift+I или Cmd+Option+I) и в панели Network отключить кеш, чтобы в него не сохранялись перенаправления. +При отладке маршрутизатора мы рекомендуем открыть инструменты разработчика в браузере (Ctrl+Shift+I или Cmd+Option+I) и отключить кеш в панели Network, чтобы перенаправления в нём не сохранялись. Производительность ================== -Количество маршрутов влияет на скорость маршрутизатора. Их число определенно не должно превышать нескольких десятков. Если у вашего сайта слишком сложная структура URL, вы можете написать собственный [#Собственный маршрутизатор] под свои нужды. +Число маршрутов влияет на скорость работы маршрутизатора. Их число точно не должно превышать нескольких десятков. Если у вашего сайта слишком сложная структура URL, вы можете написать собственный [#Собственный маршрутизатор]. -Если маршрутизатор не имеет зависимостей, например, от базы данных, и его фабрика не принимает никаких аргументов, мы можем его скомпилированную форму сериализовать прямо в DI-контейнер и тем самым немного ускорить приложение. +Если у маршрутизатора нет зависимостей, например от базы данных, а его фабрика не принимает аргументов, мы можем сериализовать его скомпилированный вид прямо в DI-контейнер и тем самым немного ускорить приложение. ```neon routing: @@ -608,7 +634,7 @@ routing: Собственный маршрутизатор ========================= -Следующие строки предназначены для очень продвинутых пользователей. Вы можете создать собственный маршрутизатор и совершенно естественно включить его в коллекцию маршрутов. Маршрутизатор — это реализация интерфейса [api:Nette\Routing\Router] с двумя методами: +Следующие строки предназначены очень продвинутым пользователям. Вы можете создать собственный маршрутизатор и естественным образом встроить его в набор маршрутов. Маршрутизатор - реализация интерфейса [api:Nette\Routing\Router] с двумя методами: ```php use Nette\Http\IRequest as HttpRequest; @@ -628,7 +654,7 @@ class MyRouter implements Nette\Routing\Router } ``` -Метод `match` обрабатывает текущий запрос [$httpRequest |http:request], из которого можно получить не только URL, но и заголовки и т. д., в массив, содержащий имя презентера и его параметры. Если он не может обработать запрос, возвращает null. При обработке запроса мы должны вернуть как минимум презентер и действие. Имя презентера является полным и содержит также возможные модули: +Метод `match` обрабатывает текущий запрос [$httpRequest |http:request], из которого можно получить не только URL, но и заголовки и прочее, в массив с именем презентера и его параметрами. Если он не может обработать запрос, он возвращает null. При обработке запроса мы должны вернуть как минимум презентер; действие необязательно и по умолчанию равно `default`, если не указано. Имя презентера полное и включает все модули: ```php [ @@ -637,9 +663,9 @@ class MyRouter implements Nette\Routing\Router ] ``` -Метод `constructUrl`, наоборот, составляет из массива параметров итоговый абсолютный URL. Для этого он может использовать информацию из параметра [`$refUrl` |api:Nette\Http\UrlScript], который является текущим URL. +Метод `constructUrl`, наоборот, собирает итоговый абсолютный URL из массива параметров. Он может использовать сведения из параметра [`$refUrl`|api:Nette\Http\UrlScript], которым служит текущий URL. -В коллекцию маршрутов его добавите с помощью `add()`: +Добавьте его в набор маршрутов методом `add()`: ```php $router = new Nette\Application\Routers\RouteList; @@ -652,13 +678,13 @@ $router->addRoute(/* ... */); Самостоятельное использование ============================= -Под самостоятельным использованием мы подразумеваем использование возможностей маршрутизатора в приложении, которое не использует Nette Application и презентеры. Для него действует почти все, что мы показали в этой главе, со следующими отличиями: +Под самостоятельным использованием мы понимаем применение возможностей маршрутизатора в приложении, которое не использует Nette Application и презентеры. К нему относится почти всё, что мы показали в этой главе, со следующими отличиями: -- для коллекций маршрутов мы используем класс [api:Nette\Routing\RouteList] -- в качестве простого маршрутизатора класс [api:Nette\Routing\SimpleRouter] -- поскольку не существует пары `Presenter:action`, мы используем [#Расширенная запись] +- для наборов маршрутов мы используем класс [api:Nette\Routing\RouteList] +- как простой маршрутизатор - класс [api:Nette\Routing\SimpleRouter] +- поскольку пары `Презентер:действие` не существует, мы используем [#Расширенная запись] -Итак, мы снова создаем метод, который нам составит маршрутизатор, например: +Итак, снова создаём метод, который соберёт нам маршрутизатор, например: ```php namespace App\Core; @@ -682,26 +708,26 @@ class RouterFactory } ``` -Если вы используете DI-контейнер, что мы рекомендуем, снова добавляем метод в конфигурацию, а затем получаем маршрутизатор вместе с HTTP-запросом из контейнера: +Если вы используете DI-контейнер, что мы и рекомендуем, снова добавьте метод в конфигурацию, а затем получите маршрутизатор вместе с HTTP-запросом из контейнера: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Или объекты создаем напрямую: +Или создайте объекты напрямую: ```php $router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Теперь остается только запустить маршрутизатор в работу: +Теперь остаётся дать маршрутизатору сделать свою работу: ```php $params = $router->match($httpRequest); if ($params === null) { - // не найден подходящий маршрут, отправляем ошибку 404 + // подходящий маршрут не найден, отправляем ошибку 404 exit; } @@ -710,7 +736,7 @@ $controller = $params['controller']; // ... ``` -И наоборот, используем маршрутизатор для составления ссылки: +И наоборот, используйте маршрутизатор для сборки ссылки: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; @@ -718,4 +744,6 @@ $url = $router->constructUrl($params, $httpRequest->getUrl()); ``` -{{composer: nette/router}} +{{composer: nette/routing}} +{{repo: nette/routing}} +{{api: https://api.nette.org/routing/}} diff --git a/application/ru/templates.texy b/application/ru/templates.texy index c399dcdc7a..89fcead967 100644 --- a/application/ru/templates.texy +++ b/application/ru/templates.texy @@ -2,9 +2,9 @@ ******* .[perex] -Nette использует систему шаблонов [Latte |latte:]. Во-первых, потому что это самая безопасная система шаблонов для PHP, а во-вторых, самая интуитивно понятная. Вам не нужно учить много нового, достаточно знания PHP и нескольких тегов. +Nette использует систему шаблонов [Latte |latte:]. Latte выбран потому, что это самая безопасная система шаблонов для PHP и одновременно самая интуитивная. Учить много нового не нужно: достаточно знания PHP и нескольких тегов. -Обычно страница состоит из шаблона макета + шаблона данного действия. Так, например, может выглядеть шаблон макета, обратите внимание на блоки `{block}` и тег `{include}`: +Обычно страница состоит из шаблона макета и шаблона конкретного действия. Вот как может выглядеть шаблон макета; обратите внимание на блоки `{block}` и тег `{include}`: ```latte @@ -20,7 +20,7 @@ Nette использует систему шаблонов [Latte |latte:]. Во ``` -А это будет шаблон действия: +А вот шаблон действия: ```latte {block title}Homepage{/block} @@ -31,15 +31,15 @@ Nette использует систему шаблонов [Latte |latte:]. Во {/block} ``` -Он определяет блок `content`, который вставляется на место `{include content}` в макете, а также переопределяет блок `title`, которым перезаписывает `{block title}` в макете. Попробуйте представить результат. +Он задаёт блок `content`, который вставляется на место `{include content}` в макете, а также переопределяет блок `title`, перекрывающий `{block title}` в макете. Попробуйте представить результат. -Поиск шаблонов --------------- +Поиск шаблона +------------- -Вам не нужно указывать в презентерах, какой шаблон должен быть отрисован, фреймворк сам выведет путь и сэкономит вам написание. +В презентерах вам не нужно указывать, какой шаблон отрисовать: фреймворк выводит путь автоматически и избавляет вас от его написания. -Если вы используете структуру каталогов, где каждый презентер имеет собственный каталог, просто поместите шаблон в этот каталог под именем действия (соответственно, view), т. е. для действия `default` используйте шаблон `default.latte`: +Если вы используете структуру каталогов, где у каждого презентера свой каталог, просто положите шаблон в этот каталог под именем действия (то есть представления). Например, для действия `default` используйте шаблон `default.latte`: /--pre app/ @@ -49,34 +49,34 @@ app/ └── default.latte \-- -Если вы используете структуру, где презентеры находятся вместе в одном каталоге, а шаблоны — в папке `templates`, сохраните его либо в файле `..latte`, либо `/.latte`: +Если вы используете структуру, где презентеры лежат вместе в одном каталоге, а шаблоны в папке `templates`, сохраните его либо в файле `..latte`, либо `/.latte`: /--pre app/ └── Presenters/ ├── HomePresenter.php └── templates/ - ├── Home.default.latte ← 1-й вариант - └── Home/ - └── default.latte ← 2-й вариант + ├── Home/ + │ └── default.latte ← 1-й вариант + └── Home.default.latte ← 2-й вариант \-- -Каталог `templates` может быть расположен также на уровень выше, т. е. на том же уровне, что и каталог с классами презентеров. +Каталог `templates` можно поместить и на уровень выше, то есть рядом с каталогом классов презентеров. -Если шаблон не найден, презентер отвечает [ошибкой 404 - страница не найдена |presenters#Ошибка 404 и т.п]. +Если шаблон не найден, презентер отвечает [ошибкой 404 - страница не найдена |presenters#Ошибка 404 и подобные]. -View можно изменить с помощью `$this->setView('jineView')`. Также можно напрямую указать файл с шаблоном с помощью `$this->template->setFile('/path/to/template.latte')`. +Изменить представление можно через `$this->setView('otherView')`. Можно и прямо указать файл шаблона через `$this->template->setFile('/path/to/template.latte')`. .[note] -Файлы, где ищутся шаблоны, можно изменить, переопределив метод [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], который возвращает массив возможных имен файлов. +Файлы, в которых ищутся шаблоны, можно изменить, переопределив метод [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], возвращающий массив возможных имён файлов. Поиск шаблона макета -------------------- -Nette также автоматически ищет файл с макетом. +Nette также автоматически ищет файл макета. -Если вы используете структуру каталогов, где каждый презентер имеет собственный каталог, поместите макет либо в папку с презентером, если он специфичен только для него, либо на уровень выше, если он общий для нескольких презентеров: +Если вы используете структуру каталогов, где у каждого презентера свой каталог, поместите макет либо в папку с презентером, если он относится только к нему, либо на уровень выше, если он общий для нескольких презентеров: /--pre app/ @@ -88,7 +88,7 @@ app/ └── default.latte \-- -Если вы используете структуру, где презентеры находятся вместе в одном каталоге, а шаблоны — в папке `templates`, макет будет ожидаться в следующих местах: +Если вы используете структуру, где презентеры собраны в одном каталоге, а шаблоны в папке `templates`, макет будет ожидаться в этих местах: /--pre app/ @@ -96,33 +96,66 @@ app/ ├── HomePresenter.php └── templates/ ├── @layout.latte ← общий макет - ├── Home.@layout.latte ← только для Home, 1-й вариант - └── Home/ - └── @layout.latte ← только для Home, 2-й вариант + ├── Home/ + │ └── @layout.latte ← только для Home, 1-й вариант + └── Home.@layout.latte ← только для Home, 2-й вариант \-- -Если презентер находится в модуле, поиск будет производиться и на более высоких уровнях каталогов, в соответствии с вложенностью модуля. +Если презентер находится в модуле, поиск идёт и выше по уровням каталогов, соответственно вложенности модулей. -Название макета можно изменить с помощью `$this->setLayout('layoutAdmin')`, и тогда он будет ожидаться в файле `@layoutAdmin.latte`. Также можно напрямую указать файл с шаблоном макета с помощью `$this->setLayout('/path/to/template.latte')`. +Имя макета можно изменить через `$this->setLayout('layoutAdmin')`, и тогда он будет ожидаться в файле `@layoutAdmin.latte`. Можно и прямо указать файл шаблона макета через `$this->setLayout('/path/to/template.latte')`. -С помощью `$this->setLayout(false)` или тега `{layout none}` внутри шаблона поиск макета отключается. +Вызов `$this->setLayout(false)` или тег `{layout none}` внутри шаблона отключают поиск макета. .[note] -Файлы, где ищутся шаблоны макета, можно изменить, переопределив метод [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], который возвращает массив возможных имен файлов. +Файлы, в которых ищутся шаблоны макетов, можно изменить, переопределив метод [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], возвращающий массив возможных имён файлов. -Переменные в шаблоне --------------------- +Переменные шаблона +------------------ -Переменные в шаблон передаем, записывая их в `$this->template`, и затем они доступны в шаблоне как локальные переменные: +Переменные передаются в шаблоны записью в `$this->template`. Затем они становятся доступны в шаблоне как локальные переменные: ```php $this->template->article = $this->articles->getById($id); ``` -Таким простым способом мы можем передать в шаблоны любые переменные. Однако при разработке надежных приложений полезнее ограничиться. Например, явно определив перечень переменных, которые ожидает шаблон, и их типы. Благодаря этому PHP сможет проверять типы, IDE правильно подсказывать, а статический анализ выявлять ошибки. +Чтобы значение свойства автоматически передавалось в шаблон как переменная, пометьте его атрибутом `#[TemplateVariable]` и сделайте публичным: .{data-version:3.2.9} + +```php +use Nette\Application\Attributes\TemplateVariable; + +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + #[TemplateVariable] + public string $siteName = 'My blog'; +} +``` + +Если вы передадите в шаблон переменную с тем же именем, `#[TemplateVariable]` её не перебьёт. + + +Переменные по умолчанию +----------------------- + +Презентеры и компоненты автоматически передают в шаблоны несколько полезных переменных: + +- `$basePath` - абсолютный путь URL к корневому каталогу (например, `/eshop`) +- `$baseUrl` - абсолютный URL корневого каталога (например, `http://localhost/eshop`) +- `$user` - объект, [представляющий пользователя |security:authentication] +- `$presenter` - текущий презентер +- `$control` - текущий компонент или презентер +- `$flashes` - массив [сообщений |presenters#Flash-сообщения], отправленных функцией `flashMessage()` + +Если вы используете собственный класс шаблона, эти переменные передаются, если вы создадите для них свойства. + + +Типобезопасные шаблоны +---------------------- -А как такой перечень определить? Просто в виде класса и его свойств. Назовем его похоже на презентер, только с `Template` на конце: +При разработке надёжных приложений полезно явно определить, какие переменные ожидает шаблон и какого они типа. Это даёт проверку типов в PHP, умные подсказки в IDE и позволяет статическому анализу отлавливать ошибки. + +Как определить такой список? Просто как класс со свойствами, представляющими переменные шаблона. Назовите его похоже на презентер, только с `Template` на конце: ```php /** @@ -141,22 +174,24 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Объект `$this->template` в презентере теперь будет экземпляром класса `ArticleTemplate`. Так что PHP при записи будет проверять объявленные типы. А начиная с версии PHP 8.2 предупредит и о записи в несуществующую переменную, в предыдущих версиях того же можно достичь с помощью трейта [Nette\SmartObject |utils:smartobject]. +Объект `$this->template` в презентере теперь будет экземпляром класса `ArticleTemplate`. PHP таким образом проверит объявленные типы при записи. + +Класс шаблона Nette выбирает автоматически. Сначала он ищет класс с именем `Template`, например `ArticleEditTemplate` для действия `edit`, и только если такого нет, переходит к `Template`. -Аннотация `@property-read` предназначена для IDE и статического анализа, благодаря ей будет работать автодополнение, см. "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +Аннотация `@property-read` предназначена для IDE и статического анализа, она включает дополнение кода, см. "PhpStorm и дополнение кода для $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Роскошью автодополнения можно наслаждаться и в шаблонах, достаточно установить в PhpStorm плагин для Latte и указать в начале шаблона имя класса, подробнее в статье "Latte: как работать с системой типов":https://blog.nette.org/ru/latte-how-to-use-type-system: +Дополнение кода можно использовать и прямо в шаблонах. Достаточно установить плагин Latte для PhpStorm и указать в начале шаблона имя класса параметров шаблона, подробнее в главе [Latte: система типов |latte:type-system]: ```latte {templateType App\Presentation\Article\ArticleTemplate} ... ``` -Так же работают и шаблоны в компонентах, достаточно лишь соблюдать соглашение об именах и для компонента, например, `FifteenControl` создать класс шаблона `FifteenTemplate`. +То же относится и к компонентам. Достаточно придерживаться соглашения об именовании и создать класс параметров `FifteenTemplate` для компонента вида `FifteenControl`. -Если вам нужно создать `$template` как экземпляр другого класса, используйте метод `createTemplate()`: +Если вам нужно использовать другой класс параметров, воспользуйтесь методом `createTemplate()`: ```php public function renderDefault(): void @@ -168,58 +203,100 @@ public function renderDefault(): void } ``` +.{data-version:3.3.0} +Если вам нужно повлиять на то, как шаблон завершается перед отрисовкой, например добавить переменные, общие для всех действий, вы можете переопределить в презентере метод `completeTemplate()`. Он вызывается прямо перед отрисовкой шаблона: -Переменные по умолчанию ------------------------ - -Презентеры и компоненты автоматически передают в шаблоны несколько полезных переменных: - -- `$basePath` — абсолютный URL-путь к корневому каталогу (например, `/eshop`) -- `$baseUrl` — абсолютный URL к корневому каталогу (например, `http://localhost/eshop`) -- `$user` — объект, [представляющий пользователя |security:authentication] -- `$presenter` — текущий презентер -- `$control` — текущий компонент или презентер -- `$flashes` — массив [сообщений |presenters#Flash-сообщения], отправленных функцией `flashMessage()` - -Если вы используете собственный класс шаблона, эти переменные передадутся, если вы создадите для них свойства. +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'My blog'; +} +``` Создание ссылок --------------- -В шаблоне ссылки на другие презентеры и действия создаются следующим образом: +В шаблоне ссылки на другие презентеры и действия создаются так: ```latte -деталь продукта +product detail ``` -Атрибут `n:href` очень удобен для HTML-тегов ``. Если мы хотим вывести ссылку в другом месте, например, в тексте, используем `{link}`: +Атрибут `n:href` очень удобен для HTML-тегов ``. Если мы хотим вывести ссылку в другом месте, например в тексте, мы используем `{link}`: ```latte -Адрес: {link Home:default} +URL is: {link Home:default} ``` -Дополнительную информацию можно найти в главе [Создание URL-ссылок |creating-links]. +Подробнее в главе [Создание URL-ссылок|creating-links]. + + +Собственные фильтры, теги и прочее +---------------------------------- +Систему шаблонов Latte можно расширять собственными фильтрами, функциями, тегами и другими элементами. Есть три подхода, от быстрых разовых решений до архитектурных приёмов для целых приложений. -Собственные фильтры, теги и т.п. --------------------------------- +**Разово в методах презентера** -Систему шаблонов Latte можно расширить собственными фильтрами, функциями, тегами и т. п. Это можно сделать прямо в методе `render` или `beforeRender()`: +Самый быстрый подход - добавить фильтры или функции прямо в код презентера или компонента. В презентерах для этого хорошо подходят методы `beforeRender()` или `render()`: ```php -public function beforeRender(): void +protected function beforeRender(): void { - // добавление фильтра - $this->template->addFilter('foo', /* ... */); + // добавляем фильтр + $this->template->addFilter('money', fn($val) => '$' . number_format($val, 2)); + + // добавляем функцию + $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); +} +``` - // или конфигурируем непосредственно объект Latte\Engine +В шаблоне: + +```latte +

    Price: {$price|money}

    + +{if isWeekend($now)} ... {/if} +``` + +Для более сложной логики можно настроить объект `Latte\Engine` напрямую: + +```php +protected function beforeRender(): void +{ $latte = $this->template->getLatte(); - $latte->addFilterLoader(/* ... */); + $latte->setFeature(Latte\Feature::MigrationWarnings); +} +``` + +**С помощью атрибутов** + +Более изящный подход - определить фильтры и функции методами прямо в [классе параметров шаблона|#Типобезопасные шаблоны] презентера или компонента, пометив их атрибутами: + +```php +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + #[Latte\Attributes\TemplateFilter] + public function money(float $val): string + { + return '$' . number_format($val, 2); + } + + #[Latte\Attributes\TemplateFunction] + public function isWeekend(DateTimeInterface $date): bool + { + return $date->format('N') >= 6; + } } ``` -Latte версии 3 предлагает более продвинутый способ — создание [расширения |latte:extending-latte#Latte Extension] для каждого веб-проекта. Пример такого класса: +Latte автоматически обнаруживает и регистрирует методы, помеченные этими атрибутами. Имя фильтра или функции в шаблонах совпадает с именем метода. Эти методы должны быть публичными. + +**Глобально через расширения** + +Предыдущие подходы годятся для фильтров и функций, нужных только в отдельных презентерах или компонентах, а не во всём приложении. Для всего приложения лучше всего создать [расширение |latte:extending-latte#Расширение Latte]. Этот класс собирает все расширения Latte вашего проекта в одном месте. Краткий пример: ```php namespace App\Presentation\Accessory; @@ -251,11 +328,16 @@ final class LatteExtension extends Latte\Extension ]; } + private function filterTimeAgoInWords(DateTimeInterface $time): string + { + // ... + } + // ... } ``` -Зарегистрируем его с помощью [конфигурации |configuration#Шаблоны Latte]: +Зарегистрируйте расширение через [конфигурацию |configuration#Шаблоны Latte]: ```neon latte: @@ -263,13 +345,27 @@ latte: - App\Presentation\Accessory\LatteExtension ``` +Расширения дают несколько преимуществ: поддержку внедрения зависимостей, доступ к слою модели вашего приложения и управление всеми расширениями из одного места. Они поддерживают и собственные теги, провайдеры, проходы компилятора и прочее. + + +Настройка всех шаблонов +----------------------- + +Сервис `TemplateFactory`, создающий все шаблоны, предлагает публичный массив callback-функций `$onCreate`. Они вызываются каждый раз при создании любого шаблона, поэтому вы можете из одного места настроить фильтры, функции или переменные для всех шаблонов приложения. Каждая callback-функция получает только что созданный шаблон. Получите сервис `TemplateFactory` [через внедрение |dependency-injection:passing-dependencies] и зарегистрируйте callback-функции, например при старте приложения: + +```php +$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void { + $template->addFilter('money', fn($val) => '$' . number_format($val, 2)); +}; +``` + Перевод ------- -Если вы программируете многоязычное приложение, вам, скорее всего, потребуется выводить некоторые тексты в шаблоне на разных языках. Nette Framework для этой цели определяет интерфейс для перевода [api:Nette\Localization\Translator], который имеет единственный метод `translate()`. Он принимает сообщение `$message`, которое обычно является строкой, и любые другие параметры. Задача — вернуть переведенную строку. В Nette нет реализации по умолчанию, вы можете выбрать из нескольких готовых решений, которые найдете на [Componette |https://componette.org/search/localization], в соответствии со своими потребностями. В их документации вы узнаете, как настроить транслятор. +Если вы разрабатываете многоязычное приложение, вам, скорее всего, понадобится выводить в шаблоне часть текстов на разных языках. Для этого Nette Framework определяет интерфейс перевода [api:Nette\Localization\Translator] с единственным методом `translate()`. Он принимает сообщение `$message`, которым обычно служит строка, и любые другие параметры. Задача - вернуть переведённую строку. Реализации по умолчанию в Nette нет; вы можете выбрать из нескольких готовых решений, доступных на [Componette |https://componette.org/search/localization], по своим потребностям. Их документация объясняет, как настроить переводчик. -Шаблонам можно установить переводчик, который мы [получим |dependency-injection:passing-dependencies], методом `setTranslator()`: +Шаблонам можно задать переводчик, который мы [получаем через внедрение |dependency-injection:passing-dependencies], методом `setTranslator()`: ```php protected function beforeRender(): void @@ -279,7 +375,7 @@ protected function beforeRender(): void } ``` -Транслятор альтернативно можно настроить с помощью [конфигурации |configuration#Шаблоны Latte]: +Как вариант, переводчик можно задать через [конфигурацию |configuration#Шаблоны Latte]: ```neon latte: @@ -287,7 +383,7 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Затем переводчик можно использовать, например, как фильтр `|translate`, включая дополнительные параметры, которые передаются методу `translate()` (см. `foo, bar`): +Затем переводчик можно использовать, например, как фильтр `|translate`, в том числе с дополнительными параметрами, которые передаются в метод `translate()` (см. `foo, bar`): ```latte
    {='Корзина'|translate} @@ -295,7 +391,7 @@ latte: {$item|translate, foo, bar} ``` -Или как тег с подчеркиванием: +Или как тег с подчёркиванием: ```latte {_'Корзина'} @@ -303,14 +399,14 @@ latte: {_$item, foo, bar} ``` -Для перевода участка шаблона существует парный тег `{translate}` (начиная с Latte 2.11, ранее использовался тег `{_}`): +Для перевода участка шаблона есть парный тег `{translate}` (начиная с Latte 2.11, раньше использовался тег `{_}`): ```latte {translate}Заказ{/translate} {translate foo, bar}Заказ{/translate} ``` -Транслятор по умолчанию вызывается во время выполнения при отрисовке шаблона. Однако Latte версии 3 умеет переводить все статические тексты уже во время компиляции шаблона. Это экономит производительность, так как каждая строка переводится только один раз, и итоговый перевод записывается в скомпилированную форму. В каталоге кеша таким образом создается несколько скомпилированных версий шаблона, по одной для каждого языка. Для этого достаточно лишь указать язык вторым параметром: +Переводчик обычно вызывается во время выполнения, при отрисовке шаблона. Однако Latte версии 3 умеет переводить все статические тексты уже во время компиляции шаблона. Это экономит производительность, потому что каждая строка переводится только один раз, а готовый перевод записывается в скомпилированный вид. При этом в каталоге кеша появляется несколько скомпилированных версий шаблона, по одной на каждый язык. Для этого достаточно указать язык вторым параметром: ```php protected function beforeRender(): void @@ -320,4 +416,4 @@ protected function beforeRender(): void } ``` -Статическим текстом считается, например, `{_'hello'}` или `{translate}hello{/translate}`. Нестатические тексты, такие как `{_$foo}`, по-прежнему будут переводиться во время выполнения. +Под статическим текстом понимается, например, `{_'hello'}` или `{translate}hello{/translate}`. Нестатические тексты вроде `{_$foo}` по-прежнему будут переводиться во время выполнения. diff --git a/application/ru/upgrading.texy b/application/ru/upgrading.texy new file mode 100644 index 0000000000..a98a84b81c --- /dev/null +++ b/application/ru/upgrading.texy @@ -0,0 +1,47 @@ +Обновление +********** + + +Обновление до версии 3.0 +======================== + +Nette 3.0 добавляет объявления типов параметров и возвращаемых значений методов. Если вы переопределяете такой метод в классе, наследующем от Nette (например, в презентере или компоненте), вам нужно добавить те же объявления типов, иначе PHP выдаст ошибку "Declaration must be compatible". + +Интерфейс `Nette\Application\IRouter` изменился. Метод `match()` теперь возвращает, а `constructUrl()` принимает массив параметров вместо объекта `Nette\Application\Request`. + +Nette теперь проверяет, что каждый сигнал отправлен с того же источника (то есть с того же домена и поддомена). Эта политика одного источника - критически важный механизм безопасности, помогающий сократить возможные векторы атак. Если вы хотите разрешить другие источники, добавьте к методу-обработчику аннотацию `@crossOrigin`: + +```php +/** + * @crossOrigin + */ +public function handleXy(): void +{ +} +``` + +Это относится и к отправке форм. Если вы хотите разрешить отправку с других источников, сделайте так: + +```php +$form = new Nette\Application\UI\Form; +$form->allowCrossOrigin(); +``` + +Конструктор `Nette\ComponentModel\Component` не использовался годами и был удалён в версии 3.0. Это несовместимое изменение: если вы вызываете родительский конструктор в компоненте или презентере, наследующем от `Nette\Application\UI\Presenter`, вам нужно убрать этот вызов. + + +Обновление до версии 2.4 +======================== + +- `Route` и `SimpleRouter` теперь порождают ту же схему HTTP/HTTPS, по которой был открыт сайт. Маршрут, требующий определённого протокола, можно задать со схемой, например `Route('http://domain.cz/')`. +- Для параметров типа bool у методов render/action (то есть со значением по умолчанию true или false) и для постоянных параметров теперь различаются `false` и `null`. Если параметра нет в URL, его значением теперь будет `null` (раньше `false`). +- Класс, возвращаемый `Presenter::getReflection()`, больше не является потомком `Nette\Reflection\ClassType`, а `getReflection()->getMethod()` больше не является потомком `Nette\Reflection\Method`. +- Флаг `SECURED` и `Route::$defaultFlags` объявлены устаревшими. + + +Обновление до версии 2.3 +======================== + +- маршруты и имена презентеров **чувствительны к регистру**. Nette предупредит вас, если вы используете неверный регистр в имени презентера; из соображений производительности маска Route не проверяется, поэтому проверьте её вручную. +- `Route::addStyle()` и `Route::setStyleProperty()` объявлены устаревшими и теперь выдают `E_USER_DEPRECATED`. +- расширение шаблонов `.phtml` и старый синтаксис ссылок больше не поддерживаются. diff --git a/application/tr/@home.texy b/application/tr/@home.texy index be4ca80d0f..5b5f2f9b5a 100644 --- a/application/tr/@home.texy +++ b/application/tr/@home.texy @@ -2,84 +2,84 @@ Nette Application ***************** .[perex] -Nette Application, modern web uygulamaları oluşturmak için güçlü araçlar sunan Nette framework'ünün çekirdeğidir. Geliştirmeyi önemli ölçüde kolaylaştıran ve kodun güvenliğini ve sürdürülebilirliğini artıran bir dizi olağanüstü özellik sunar. +Nette Application, Nette framework'ünün çekirdeğidir ve modern web uygulamaları oluşturmak için güçlü araçlar sunar. Geliştirmeyi belirgin biçimde kolaylaştıran, kodun güvenliğini ve bakımını iyileştiren bir dizi olağanüstü özellik sağlar. Kurulum ------- -Kütüphaneyi [Composer|best-practices:composer] aracını kullanarak indirip kurabilirsiniz: +Kütüphaneyi [Composer|best-practices:composer] ile indirin ve kurun: ```shell composer require nette/application ``` -Neden Nette Application'ı Seçmelisiniz? ---------------------------------------- +Neden Nette Application? +------------------------ -Nette, web teknolojileri alanında her zaman öncü olmuştur. +Nette web teknolojilerinde her zaman öncü olmuştur. -**Çift Yönlü Yönlendirici:** Nette, benzersiz çift yönlülüğü ile gelişmiş bir yönlendirme sistemine sahiptir - yalnızca URL'leri uygulama eylemlerine çevirmekle kalmaz, aynı zamanda geriye dönük olarak URL adresleri de oluşturabilir. Bu şu anlama gelir: -- Şablonları düzenlemeye gerek kalmadan tüm uygulamanın URL yapısını istediğiniz zaman değiştirebilirsiniz -- URL'ler otomatik olarak standartlaştırılır, bu da SEO'yu iyileştirir -- Yönlendirme, ek açıklamalara dağılmış olarak değil, tek bir yerde tanımlanır +**Çift yönlü router:** Nette, çift yönlü olmasıyla eşsiz olan gelişmiş bir yönlendirme sistemine sahiptir; URL'leri yalnızca uygulama eylemlerine çevirmekle kalmaz, tersine URL de üretebilir. Bu şu anlama gelir: +- Şablonları değiştirmeye gerek kalmadan tüm uygulamanın URL yapısını istediğiniz zaman değiştirebilirsiniz +- URL'ler otomatik olarak kanonikleştirilir, bu da SEO'yu iyileştirir +- Yönlendirme tek bir yerde tanımlanır, anotasyonlara dağılmaz -**Bileşenler ve Sinyaller:** Delphi ve React.js'den ilham alan yerleşik bileşen sistemi, PHP framework'leri arasında tamamen benzersizdir: -- Yeniden kullanılabilir UI öğeleri oluşturmanıza olanak tanır +**Bileşenler ve sinyaller:** Delphi ve React.js'ten esinlenen yerleşik bileşen sistemi PHP framework'leri arasında eşsizdir: +- Yeniden kullanılabilir UI elemanları oluşturmayı sağlar - Hiyerarşik bileşen kompozisyonunu destekler -- Sinyalleri kullanarak AJAX isteklerinin zarif bir şekilde işlenmesini sunar -- [Componette](https://componette.org) üzerinde zengin hazır bileşen kütüphanesi +- Sinyaller sayesinde AJAX isteklerinin zarif bir şekilde işlenmesini sunar +- [Componette](https://componette.org) üzerinde zengin bir hazır bileşen kütüphanesi -**AJAX ve Snippet'ler:** Nette, Ruby on Rails için Hotwire veya Symfony UX Turbo gibi benzer çözümlerden çok önce, 2009'da AJAX ile çalışmanın devrim niteliğinde bir yolunu tanıttı: -- Snippet'ler, JavaScript yazmaya gerek kalmadan sayfanın yalnızca bölümlerini güncellemenizi sağlar +**AJAX ve snippet'ler:** Nette, AJAX ile çalışmanın devrimsel bir yolunu 2009'da, Ruby on Rails için Hotwire veya Symfony UX Turbo gibi benzer çözümlerden çok önce tanıttı: +- Snippet'ler, JavaScript yazmaya gerek kalmadan sayfanın yalnızca bir bölümünü güncellemeyi sağlar - Bileşen sistemiyle otomatik entegrasyon -- Sayfa bölümlerinin akıllıca geçersizleştirilmesi -- Minimum miktarda aktarılan veri +- Sayfa bölümlerinin akıllı biçimde geçersiz kılınması +- En az veri aktarımı -**Sezgisel Şablonlar [Latte|latte:]:** Gelişmiş özelliklere sahip PHP için en güvenli şablonlama sistemi: -- Bağlama duyarlı kaçış (escaping) ile XSS'ye karşı otomatik koruma -- Özel filtreler, fonksiyonlar ve etiketler aracılığıyla genişletilebilirlik -- AJAX için şablon kalıtımı ve snippet'ler -- Tip sistemi ile PHP 8.x için mükemmel destek +**Sezgisel [Latte|latte:] şablonları:** PHP için en güvenli şablon sistemi, gelişmiş özelliklerle: +- Bağlama duyarlı kaçış ile otomatik XSS koruması +- Özel filtreler, fonksiyonlar ve etiketlerle genişletilebilir +- Şablon kalıtımı ve AJAX için snippet'ler +- Tip sistemiyle mükemmel PHP 8.x desteği -**Dependency Injection:** Nette, Dependency Injection'ı tam olarak kullanır: -- Bağımlılıkların otomatik olarak geçirilmesi (autowiring) -- Anlaşılır NEON formatı kullanılarak yapılandırma -- Bileşen fabrikaları için destek +**Bağımlılık enjeksiyonu:** Nette, bağımlılık enjeksiyonundan tam olarak yararlanır: +- Bağımlılıkların otomatik aktarımı (autowiring) +- Anlaşılır NEON formatıyla yapılandırma +- Bileşen factory'leri için destek -Başlıca Avantajlar +Başlıca avantajlar ------------------ -- **Güvenlik**: XSS, CSRF vb. gibi [güvenlik açıklarına|nette:vulnerability-protection] karşı otomatik koruma. -- **Verimlilik**: Akıllı tasarım sayesinde daha az yazma, daha fazla işlev. -- **Hata Ayıklama**: Yönlendirme panelli [Tracy hata ayıklayıcı|tracy:]. -- **Performans**: Akıllı önbellek, bileşenlerin geç yüklenmesi (lazy loading). -- **Esneklik**: Uygulama tamamlandıktan sonra bile URL'lerin kolayca değiştirilmesi. -- **Bileşenler**: Yeniden kullanılabilir UI öğelerinin benzersiz sistemi. -- **Modern**: PHP 8.4+ ve tip sistemi için tam destek. +- **Güvenlik**: XSS, CSRF vb. [güvenlik açıklarına|nette:vulnerability-protection] karşı otomatik koruma +- **Verimlilik**: Akıllı tasarım sayesinde daha az yazı, daha çok özellik +- **Hata ayıklama**: Yönlendirme paneline sahip [Tracy hata ayıklayıcı|tracy:] +- **Performans**: Akıllı önbellek, bileşenlerin tembel yüklenmesi +- **Esneklik**: Uygulama tamamlandıktan sonra bile URL'leri kolayca değiştirme +- **Bileşenler**: Yeniden kullanılabilir UI elemanlarının eşsiz sistemi +- **Modern**: PHP 8.3+ ve tip sistemi için tam destek Başlarken --------- -1. [Uygulamalar nasıl çalışır? |how-it-works] - Temel mimariyi anlama -2. [Presenter'lar |presenters] - Presenter'lar ve eylemlerle çalışma -3. [Şablonlar |templates] - Latte'de şablon oluşturma -4. [Yönlendirme |routing] - URL adreslerini yapılandırma -5. [Etkileşimli bileşenler |components] - Bileşen sistemini kullanma +1. [Uygulamalar nasıl çalışır? |how-it-works] - Temel mimariyi anlamak +2. [Presenter'lar |presenters] - Presenter'lar ve eylemlerle çalışmak +3. [Şablonlar |templates] - Latte'de şablon oluşturmak +4. [Yönlendirme |routing] - URL adreslerini yapılandırmak +5. [Etkileşimli bileşenler |components] - Bileşen sistemini kullanmak -PHP ile Uyumluluk ------------------ +PHP uyumluluğu +-------------- -| sürüm | PHP ile uyumlu -|-----------|------------------- -| Nette Application 4.0 | PHP 8.1 – 8.4 -| Nette Application 3.2 | PHP 8.1 – 8.4 -| Nette Application 3.1 | PHP 7.2 – 8.3 -| Nette Application 3.0 | PHP 7.1 – 8.0 -| Nette Application 2.4 | PHP 5.6 – 8.0 +| sürüm | uyumlu olduğu PHP +|-----------------------|------------------- +| Nette Application 3.3 | PHP 8.3 - 8.5 +| Nette Application 3.2 | PHP 8.1 - 8.5 +| Nette Application 3.1 | PHP 7.2 - 8.3 +| Nette Application 3.0 | PHP 7.1 - 8.0 +| Nette Application 2.4 | PHP 5.6 - 8.0 -Son yama sürümü için geçerlidir. +En son yama sürümleri için geçerlidir. diff --git a/application/tr/@left-menu.texy b/application/tr/@left-menu.texy index 05dd6798ff..3a084f4197 100644 --- a/application/tr/@left-menu.texy +++ b/application/tr/@left-menu.texy @@ -1,22 +1,24 @@ Nette Application ***************** +- [Genel bakış |@home] - [Uygulamalar nasıl çalışır? |how-it-works] -- [Bootstrapping] -- [Presenter'lar |presenters] -- [Şablonlar |templates] +- [Bootstrapping|bootstrapping] +- [Presenter'lar|presenters] +- [Şablonlar|templates] - [Dizin yapısı |directory-structure] -- [Yönlendirme |routing] -- [URL Bağlantıları Oluşturma |creating-links] +- [Yönlendirme|routing] +- [URL bağlantıları oluşturma |creating-links] - [Etkileşimli bileşenler |components] -- [AJAX & snippet'ler |ajax] -- [Multiplier |Multiplier] -- [Yapılandırma |configuration] +- [AJAX ve snippet'ler |ajax] +- [Multiplier |multiplier] +- [Yapılandırma|configuration] +- [Yükseltme|upgrading] -Daha Fazla Okuma -**************** -- [Neden Nette kullanmalı? |www:10-reasons-why-nette] +İleri okuma +*********** +- [Neden Nette kullanmalı?|www:10-reasons-why-nette] - [Kurulum |nette:installation] -- [İlk uygulamamızı yazıyoruz! |quickstart:] -- [Kılavuzlar ve yöntemler |best-practices:] -- [Sorun Giderme |nette:troubleshooting] +- [İlk uygulamanızı oluşturun! |quickstart:] +- [En iyi uygulamalar |best-practices:] +- [Sorun giderme |nette:troubleshooting] diff --git a/application/tr/ajax.texy b/application/tr/ajax.texy index cad8af482b..77de245dba 100644 --- a/application/tr/ajax.texy +++ b/application/tr/ajax.texy @@ -1,22 +1,22 @@ -AJAX & Snippet'ler -****************** +AJAX ve snippet'ler +*******************
    -Sunucu ve tarayıcı arasında işlevselliğin sıklıkla bölündüğü modern web uygulamaları çağında, AJAX vazgeçilmez bir bağlantı elemanıdır. Nette Framework bize bu alanda hangi seçenekleri sunuyor? -- şablonun parçalarını, yani snippet'leri gönderme -- PHP ve JavaScript arasında değişkenleri iletme -- AJAX isteklerinin hatalarını ayıklama araçları +İşlevselliğin çoğu zaman sunucu ile tarayıcı arasında dağıtıldığı modern web uygulamaları çağında AJAX, vazgeçilmez bir bağlayıcı unsurdur. Nette Framework bu alanda hangi olanakları sunuyor? +- snippet denilen şablon parçalarının gönderilmesi +- PHP ile JavaScript arasında değişken aktarımı +- AJAX isteklerini hata ayıklama araçları
    -AJAX İsteği +AJAX isteği =========== -Bir AJAX isteği, temelde klasik bir HTTP isteğinden farklı değildir. Belirli parametrelerle bir presenter çağrılır. Ve isteğe nasıl yanıt vereceği presenter'a bağlıdır - JSON formatında veri döndürebilir, HTML kodunun bir kısmını, bir XML belgesini vb. gönderebilir. +Bir AJAX isteği, klasik bir HTTP isteğinden temelde farklı değildir. Belirli parametrelerle bir presenter çağrılır. İsteğe nasıl yanıt vereceğine presenter karar verir; veriyi JSON formatında döndürebilir, bir HTML kodu parçası, bir XML belgesi vb. gönderebilir. -Tarayıcı tarafında, `fetch()` fonksiyonunu kullanarak bir AJAX isteği başlatırız: +Tarayıcı tarafında AJAX isteğini `fetch()` fonksiyonuyla başlatırız: ```js fetch(url, { @@ -24,22 +24,22 @@ fetch(url, { }) .then(response => response.json()) .then(payload => { - // yanıtın işlenmesi + // yanıtı işle }); ``` -Sunucu tarafında, [HTTP isteğini kapsayan |http:request] servisin `$httpRequest->isAjax()` metoduyla bir AJAX isteğini tanırız. Algılama için `X-Requested-With` HTTP başlığını kullanır, bu yüzden onu göndermek önemlidir. Presenter içinde `$this->isAjax()` metodunu kullanabilirsiniz. +Sunucu tarafında AJAX isteği, [HTTP isteğini kapsülleyen |http:request] servisin `$httpRequest->isAjax()` metoduyla tanınır. Algılama için `X-Requested-With` HTTP header'ını kullanır, bu yüzden onu göndermek çok önemlidir. Presenter içinde `$this->isAjax()` metodunu kullanabilirsiniz. -Verileri JSON formatında göndermek istiyorsanız, [`sendJson()` |presenters#Yanıt Gönderme] metodunu kullanın. Metot ayrıca presenter'ın etkinliğini de sonlandırır. +Veriyi JSON formatında göndermek isterseniz [`sendJson()` |presenters#Yanıt gönderme] metodunu kullanın. Bu metot presenter'ın çalışmasını da sonlandırır. ```php public function actionExport(): void { - $this->sendJson($this->model->getData); + $this->sendJson($this->model->getData()); } ``` -AJAX için tasarlanmış özel bir şablonla yanıt vermeyi planlıyorsanız, bunu aşağıdaki gibi yapabilirsiniz: +AJAX için tasarlanmış özel bir şablonla yanıt vermeyi planlıyorsanız şöyle yapabilirsiniz: ```php public function handleClick($param): void @@ -55,26 +55,26 @@ public function handleClick($param): void Snippet'ler =========== -Nette'nin sunucuyu istemciyle bağlamak için sunduğu en güçlü araç snippet'lerdir. Onlar sayesinde, sıradan bir uygulamayı minimum çaba ve birkaç satır kodla AJAX uygulamasına dönüştürebilirsiniz. Tüm bunların nasıl çalıştığını, kodunu [GitHub'da |https://github.com/nette-examples/fifteen] bulabileceğiniz Fifteen örneği göstermektedir. +Nette'in sunucuyu istemciyle birleştirmek için sunduğu en güçlü araç snippet'lerdir. Onlarla sıradan bir uygulamayı en az çabayla ve yalnızca birkaç satır kodla AJAX uygulamasına dönüştürebilirsiniz. Bunun nasıl çalıştığını Fifteen örneği gösterir; kodunu [GitHub |https://github.com/nette-examples/fifteen] üzerinde bulabilirsiniz. -Snippet'ler veya kesitler, tüm sayfanın yeniden yüklenmesi yerine sayfanın yalnızca bölümlerini güncellemenize olanak tanır. Bu sadece daha hızlı ve daha verimli olmakla kalmaz, aynı zamanda daha rahat bir kullanıcı deneyimi de sağlar. Snippet'ler size Ruby on Rails için Hotwire'ı veya Symfony UX Turbo'yu hatırlatabilir. İlginç bir şekilde, Nette snippet'leri 14 yıl önce tanıttı. +Snippet'ler, tüm sayfayı yeniden yüklemek yerine yalnızca sayfanın bölümlerini güncellemenizi sağlar. Bu yalnızca daha hızlı ve verimli olmakla kalmaz, aynı zamanda daha rahat bir kullanıcı deneyimi sunar. Snippet'ler size Ruby on Rails için Hotwire'ı veya Symfony UX Turbo'yu hatırlatabilir. İlginçtir ki Nette snippet'leri 14 yıl önce tanıttı. -Snippet'ler nasıl çalışır? Sayfa ilk yüklendiğinde (AJAX olmayan istek), tüm snippet'ler dahil olmak üzere tüm sayfa yüklenir. Kullanıcı sayfayla etkileşime girdiğinde (örneğin, bir düğmeye tıkladığında, bir form gönderdiğinde vb.), tüm sayfayı yüklemek yerine bir AJAX isteği tetiklenir. Presenter'daki kod eylemi gerçekleştirir ve hangi snippet'lerin güncellenmesi gerektiğine karar verir. Nette bu snippet'leri oluşturur ve JSON formatında bir dizi olarak gönderir. Tarayıcıdaki işleyici kod, alınan snippet'leri sayfaya geri ekler. Bu nedenle, yalnızca değiştirilen snippet'lerin kodu aktarılır, bu da bant genişliğinden tasarruf sağlar ve tüm sayfanın içeriğini aktarmaya kıyasla yüklemeyi hızlandırır. +Snippet'ler nasıl çalışır? Sayfa ilk yüklendiğinde (AJAX olmayan istek) tüm sayfa, tüm snippet'ler dahil, yüklenir. Kullanıcı sayfayla etkileşime girdiğinde (örneğin bir düğmeye tıkladığında, bir formu gönderdiğinde vb.), tüm sayfayı yeniden yüklemek yerine bir AJAX isteği başlatılır. Presenter'daki kod eylemi gerçekleştirir ve hangi snippet'lerin güncellenmesi gerektiğine karar verir. Nette bu snippet'leri render eder ve snippet'leri içeren bir dizi taşıyan JSON payload'ı olarak gönderir. Tarayıcıdaki işleyici kod, aldığı snippet'leri sayfaya geri yerleştirir. Böylece yalnızca değişen snippet'lerin kodu aktarılır, bu da tüm sayfa içeriğini aktarmaya kıyasla bant genişliğinden tasarruf sağlar ve yüklemeyi hızlandırır. `redrawControl()` ile hiçbir snippet geçersiz kılınmazsa, Nette AJAX isteğinde de tüm sayfayı döndürür; snippet'ler yalnızca bir şey geçersiz kılındığında gönderilir. Naja ---- -Snippet'leri tarayıcı tarafında işlemek için [Naja kütüphanesi |https://naja.js.org] kullanılır. Bunu bir node.js paketi olarak [kurun |https://naja.js.org/#/guide/01-install-setup-naja] (Webpack, Rollup, Vite, Parcel ve diğer uygulamalarla kullanım için): +Snippet'leri tarayıcı tarafında işlemek için [Naja kütüphanesi |https://naja.js.org] kullanılır. Onu bir Node.js paketi olarak [kurun |https://naja.js.org/#/guide/01-install-setup-naja] (Webpack, Rollup, Vite, Parcel gibi paketleyicilerle kullanmak için): ```shell npm install naja ``` -…veya doğrudan sayfa şablonuna ekleyin: +…ya da doğrudan sayfa şablonuna ekleyin: ```latte - + ``` Önce kütüphaneyi [başlatmanız |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] gerekir: @@ -83,7 +83,7 @@ npm install naja naja.initialize(); ``` -Sıradan bir bağlantıdan (sinyal) veya form gönderiminden bir AJAX isteği oluşturmak için, ilgili bağlantıyı, formu veya düğmeyi `ajax` sınıfıyla işaretlemek yeterlidir: +Sıradan bir bağlantıyı (sinyali) veya form gönderimini AJAX isteğine dönüştürmek için ilgili bağlantıyı, formu veya düğmeyi `ajax` sınıfıyla işaretlemeniz yeterlidir: ```latte Git @@ -100,32 +100,34 @@ veya ``` -Snippet'lerin Yeniden Çizilmesi +Snippet'lerin yeniden çizilmesi ------------------------------- -[Control |components] sınıfının her nesnesi (Presenter'ın kendisi dahil), yeniden çizilmesini gerektiren değişiklikler olup olmadığını takip eder. Bunun için `redrawControl()` metodu kullanılır: +[Control |components] sınıfının her nesnesi (Presenter'ın kendisi dahil), yeniden çizilmesini gerektiren değişikliklerin olup olmadığını izler. Bunun için `redrawControl()` metodu kullanılır: ```php public function handleLogin(string $user): void { - // giriş yaptıktan sonra ilgili bölümü yeniden çizmek gerekir + // girişten sonra ilgili bölümün yeniden çizilmesi gerekir $this->redrawControl(); // ... } ``` -Nette, neyin yeniden çizileceği konusunda daha da hassas kontrol sağlar. Bahsedilen metot, argüman olarak snippet adını alabilir. Böylece, şablonun bölümleri düzeyinde geçersiz kılma (yani yeniden çizmeyi zorlama) mümkündür. Tüm bileşen geçersiz kılınırsa, her bir snippet'i de yeniden çizilir: +Nette, neyin yeniden çizileceği üzerinde daha da ince bir denetim sağlar. Metot, argüman olarak snippet'in adını kabul edebilir. Böylece şablon parçaları düzeyinde geçersiz kılmak (yani yeniden çizilmeye zorlamak) mümkündür. Tüm bileşen geçersiz kılınırsa, içindeki her snippet de yeniden çizilir: ```php // 'header' snippet'ini geçersiz kılar $this->redrawControl('header'); ``` +Bekleyen bir geçersiz kılmayı ikinci parametre `$redraw` ile iptal de edebilirsiniz: `$this->redrawControl('header', redraw: false)` çağrısı, snippet'i yeniden çizilmesi gerekmeyen olarak işaretler. Tam imza `redrawControl(?string $snippet = null, bool $redraw = true)` şeklindedir. -Latte'de Snippet'ler + +Latte'de snippet'ler -------------------- -Latte'de snippet kullanmak son derece kolaydır. Şablonun bir bölümünü snippet olarak tanımlamak için, onu `{snippet}` ve `{/snippet}` etiketleriyle sarmanız yeterlidir: +Latte'de snippet kullanmak son derece kolaydır. Şablonun bir bölümünü snippet olarak tanımlamak için onu `{snippet}` ve `{/snippet}` etiketleriyle sarmanız yeterlidir: ```latte {snippet header} @@ -133,9 +135,9 @@ Latte'de snippet kullanmak son derece kolaydır. Şablonun bir bölümünü snip {/snippet} ``` -Snippet, HTML sayfasında özel olarak oluşturulmuş bir `id` ile bir `
    ` öğesi oluşturur. Snippet yeniden çizildiğinde, bu öğenin içeriği güncellenir. Bu nedenle, sayfanın ilk oluşturulmasında, başlangıçta boş olsalar bile tüm snippet'lerin de oluşturulması gerekir. +Snippet, HTML sayfasında özel olarak üretilmiş bir `id`'ye sahip bir `
    ` elemanı oluşturur. Snippet yeniden çizildiğinde bu elemanın içeriği güncellenir. Bu yüzden sayfa ilk render edildiğinde tüm snippet'lerin de render edilmesi gerekir, başlangıçta boş olabilseler bile. -n:attribute kullanarak `
    ` dışında bir öğeyle de bir snippet oluşturabilirsiniz: +Snippet'i `
    ` dışında bir elemanla, n:niteliği kullanarak da oluşturabilirsiniz: ```latte
    @@ -144,10 +146,10 @@ n:attribute kullanarak `
    ` dışında bir öğeyle de bir snippet oluşturab ``` -Snippet Alanları +Snippet alanları ---------------- -Snippet adları ifadeler de olabilir: +Snippet adları ifade de olabilir: ```latte {foreach $items as $id => $item} @@ -155,7 +157,9 @@ Snippet adları ifadeler de olabilir: {/foreach} ``` -Bu şekilde birkaç snippet oluşturulur: `item-0`, `item-1` vb. Dinamik bir snippet'i doğrudan geçersiz kılsaydık (örneğin `item-1`), hiçbir şey yeniden çizilmezdi. Bunun nedeni, snippet'lerin gerçekten kesitler gibi çalışması ve yalnızca kendilerinin doğrudan oluşturulmasıdır. Ancak şablonda aslında `item-1` adında bir snippet yoktur. Bu, yalnızca snippet'in etrafındaki kodun, yani foreach döngüsünün yürütülmesiyle ortaya çıkar. Bu nedenle, yürütülmesi gereken şablon bölümünü `{snippetArea}` etiketiyle işaretleriz: +Tek başına bu, işlevsiz bir ara adımdır: statik bir `{snippet}` veya `{snippetArea}` dışında render edilen dinamik bir snippet, *Dynamic snippets are allowed only inside static snippet/snippetArea.* mesajıyla `E_USER_WARNING` tetikler. Bunu aşağıda düzeltiyoruz. + +Bu, `item-0`, `item-1` gibi birkaç snippet oluşturur. Dinamik bir snippet'i doğrudan geçersiz kılsaydık (örneğin `item-1`), hiçbir şey yeniden çizilmezdi. Bunun nedeni, snippet'lerin gerçekten alıntı gibi çalışması ve yalnızca kendilerinin doğrudan render edilmesidir. Oysa şablonda teknik olarak `item-1` adlı bir snippet yoktur. O ancak snippet'i çevreleyen kod, yani foreach döngüsü, çalıştırıldığında var olur. Bu yüzden şablonun çalıştırılması gereken bölümünü `{snippetArea}` etiketiyle işaretleriz: ```latte
      @@ -165,16 +169,16 @@ Bu şekilde birkaç snippet oluşturulur: `item-0`, `item-1` vb. Dinamik bir sni
    ``` -Ve hem snippet'in kendisini hem de tüm üst alanı yeniden çizeriz: +Ve hem tek tek snippet'in hem de tüm üst alanın yeniden çizilmesini isteriz: ```php $this->redrawControl('itemsContainer'); $this->redrawControl('item-1'); ``` -Aynı zamanda, `$items` dizisinin yalnızca yeniden çizilmesi gereken öğeleri içermesini sağlamak uygundur. +Aynı zamanda `$items` dizisinin yalnızca yeniden çizilmesi gereken öğeleri içermesini sağlamak yerinde olur. -Şablona `{include}` etiketi kullanarak snippet'ler içeren başka bir şablon eklersek, şablon eklemesini tekrar `snippetArea` içine almalı ve onu snippet ile birlikte geçersiz kılmalıyız: +Ana şablona snippet içeren başka bir şablonu `{include}` etiketiyle dahil edersek, şablon dahil etmeyi yine bir `snippetArea` içine sarmak ve onu snippet'le birlikte geçersiz kılmak gerekir: ```latte {snippetArea include} @@ -195,25 +199,25 @@ $this->redrawControl('item'); ``` -Bileşenlerde Snippet'ler +Bileşenlerde snippet'ler ------------------------ -[Bileşenlerde|components] de snippet'ler oluşturabilirsiniz ve Nette bunları otomatik olarak yeniden çizer. Ancak burada belirli bir sınırlama vardır: snippet'leri yeniden çizmek için `render()` metodunu parametresiz çağırır. Yani, şablonda parametreleri iletmek işe yaramaz: +[Bileşenlerin|components] içinde snippet oluşturabilirsiniz ve Nette bunları otomatik olarak yeniden çizer. Ancak bir sınırlama vardır: snippet'leri yeniden çizmek için Nette `render()` metodunu parametresiz çağırır. Bu yüzden şablonda parametre aktarmak işe yaramaz: ```latte -OK +Tamam {control productGrid} -işe yaramayacak: +çalışmaz: {control productGrid $arg, $arg} {control productGrid:paginator} ``` -Kullanıcı Verilerini Gönderme ------------------------------ +Kendi verilerinizi gönderme +--------------------------- -Snippet'lerle birlikte istemciye herhangi bir ek veri gönderebilirsiniz. Bunları `payload` nesnesine yazmanız yeterlidir: +Snippet'lerle birlikte istemciye istediğiniz ek veriyi gönderebilirsiniz. Onları `payload` nesnesine yazmanız yeterlidir: ```php public function actionDelete(int $id): void @@ -226,10 +230,16 @@ public function actionDelete(int $id): void ``` -Parametreleri İletme -==================== +Yeniden yönlendirme +------------------- + +AJAX isteği sırasında `redirect()` ve `redirectUrl()` metotları HTTP yeniden yönlendirmesi göndermez. Bunun yerine hedef URL'yi payload'a (AJAX yanıtında gönderilen veri nesnesine), yani onun `payload.redirect` özelliğine yazar ve gönderir; asıl yönlendirmeyi ise istemci tarafındaki kütüphane (Naja) gerçekleştirir. + + +Parametre aktarımı +================== -Bir bileşene AJAX isteği ile parametreler gönderirsek, bunlar ister sinyal parametreleri ister kalıcı parametreler olsun, istekte bileşenin adını da içeren global adlarını belirtmemiz gerekir. Parametrenin tam adını `getParameterId()` metodu döndürür. +Bir bileşene AJAX isteğiyle parametre gönderirken, ister sinyal parametreleri ister kalıcı parametreler olsun, istekte bileşenin adını da içeren genel adlarını belirtmemiz gerekir. `getParameterId()` metodu parametrenin tam adını döndürür. ```js let url = new URL({link //foo!}); @@ -240,10 +250,16 @@ fetch(url, { }) ``` -Ve bileşendeki karşılık gelen parametrelerle handle metodu: +Ve bileşende ilgili parametrelere sahip handle metodu: ```php public function handleFoo(int $bar): void { } ``` + + +İleri okuma +=========== + +- [Dinamik snippet'ler |best-practices:dynamic-snippets] diff --git a/application/tr/bootstrapping.texy b/application/tr/bootstrapping.texy index 6900984407..90487846bb 100644 --- a/application/tr/bootstrapping.texy +++ b/application/tr/bootstrapping.texy @@ -3,19 +3,22 @@ Bootstrapping
    -Bootstrapping, uygulama ortamının başlatılması, bir dependency injection (DI) konteynerinin oluşturulması ve uygulamanın başlatılması sürecidir. Şunları tartışacağız: +Bootstrapping, uygulama ortamının hazırlanması, bağımlılık enjeksiyonu (DI) konteynerinin oluşturulması ve uygulamanın başlatılması sürecidir. Şunları ele alacağız: -- Bootstrap sınıfının ortamı nasıl başlattığı -- uygulamaların NEON dosyaları kullanılarak nasıl yapılandırıldığı -- üretim ve geliştirme modları arasında nasıl ayrım yapılacağı -- DI konteynerinin nasıl oluşturulacağı ve yapılandırılacağı +- Bootstrap sınıfının ortamı nasıl hazırladığını +- uygulamaların NEON dosyalarıyla nasıl yapılandırıldığını +- üretim ve geliştirme modunun nasıl ayırt edildiğini +- DI konteynerinin nasıl oluşturulup yapılandırıldığını
    -Uygulamalar, ister web uygulamaları ister komut satırından çalıştırılan betikler olsun, çalışmalarına bir tür ortam başlatma ile başlarlar. Eski zamanlarda, bu genellikle ilk dosyanın dahil ettiği `include.inc.php` gibi bir dosyanın sorumluluğundaydı. Modern Nette uygulamalarında, bunun yerini uygulamanın bir parçası olarak `app/Bootstrap.php` dosyasında bulunan `Bootstrap` sınıfı almıştır. Örneğin şöyle görünebilir: +Uygulamalar, ister web tabanlı ister komut satırından çalıştırılan betikler olsun, çalışmalarına bir tür ortam hazırlığıyla başlar. Eskiden bundan, ilk dosyanın dahil ettiği belki `include.inc.php` adlı bir dosya sorumluydu. Modern Nette uygulamalarında onun yerini, uygulamanın bir parçası olarak `app/Bootstrap.php` dosyasında bulunan `Bootstrap` sınıfı aldı. Örneğin şöyle görünebilir: ```php +namespace App; + +use Nette; use Nette\Bootstrap\Configurator; class Bootstrap @@ -26,9 +29,9 @@ class Bootstrap public function __construct() { $this->rootDir = dirname(__DIR__); - // Yapılandırıcı, uygulama ortamını ve servisleri ayarlamaktan sorumludur. + // Configurator, uygulama ortamının ve servislerin kurulumundan sorumludur. $this->configurator = new Configurator; - // Nette tarafından oluşturulan geçici dosyalar için dizini ayarlar (örn. derlenmiş şablonlar) + // Nette'in ürettiği geçici dosyalar (örneğin derlenmiş şablonlar) için dizini ayarla $this->configurator->setTempDirectory($this->rootDir . '/temp'); } @@ -41,14 +44,14 @@ class Bootstrap private function initializeEnvironment(): void { - // Nette akıllıdır ve geliştirme modu otomatik olarak açılır, - // veya aşağıdaki satırın yorumunu kaldırarak belirli bir IP adresi için etkinleştirebilirsiniz: + // Nette akıllıdır ve geliştirme modu kendiliğinden açılır, + // ya da aşağıdaki satırın yorumunu kaldırarak belirli bir IP adresi için etkinleştirebilirsiniz: // $this->configurator->setDebugMode('secret@23.75.345.200'); - // Tracy'yi etkinleştirir: hata ayıklama için nihai "İsviçre çakısı". + // Tracy'yi etkinleştirir: en iyi "İsviçre çakısı" hata ayıklama aracı. $this->configurator->enableTracy($this->rootDir . '/log'); - // RobotLoader: seçilen dizindeki tüm sınıfları otomatik olarak yükler + // RobotLoader: seçilen dizindeki tüm sınıfları otomatik yükler $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); @@ -56,7 +59,7 @@ class Bootstrap private function setupContainer(): void { - // Yapılandırma dosyalarını yükler + // Yapılandırma dosyalarını yükle $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } @@ -66,91 +69,100 @@ class Bootstrap index.php ========= -Web uygulamaları durumunda ilk dosya, [genel dizinde |directory-structure#Genel Dizin www] `www/` bulunan `index.php` dosyasıdır. Bu dosya, Bootstrap sınıfından ortamı başlatmasını ve DI konteynerini oluşturmasını ister. Ardından, web uygulamasını başlatan `Application` servisini ondan alır: +Web uygulamalarında ilk dosya, [genel dizin |directory-structure#Genel dizin www/] `www/` içinde yer alan `index.php`'dir. Bootstrap sınıfına ortamı hazırlamasını ve DI konteynerini oluşturmasını söyler. Sonra konteynerden `Application` servisini alır ve o da web uygulamasını çalıştırır: ```php $bootstrap = new App\Bootstrap; -// Ortamı başlat + DI konteynerini oluştur +// Ortamı hazırla + DI konteynerini oluştur $container = $bootstrap->bootWebApplication(); -// DI konteyneri Nette\Application\Application nesnesini oluşturur +// DI konteyneri bir Nette\Application\Application nesnesi oluşturur $application = $container->getByType(Nette\Application\Application::class); // Nette uygulamasını başlat ve gelen isteği işle $application->run(); ``` -Gördüğünüz gibi, ortamı ayarlamaya ve bağımlılık enjeksiyonu (DI) konteynerini oluşturmaya [api:Nette\Bootstrap\Configurator] sınıfı yardımcı olur, şimdi onu daha yakından tanıyacağız. +.[note] +`$application` nesnesi, isteği işlerken [olaylar |nette:glossary#Olaylar] yayar: `onStartup`, `onRequest`, `onPresenter`, `onResponse`, `onShutdown` ve `onError` (işlenmemiş bir istisnada). Bunlara işleyici bağlayabilirsiniz; günlükleme veya uygulama geneli izleme için kullanışlıdır. +Gördüğünüz gibi, ortamın kurulmasına ve bağımlılık enjeksiyonu (DI) konteynerinin oluşturulmasına [api:Nette\Bootstrap\Configurator] sınıfı yardım eder. Şimdi onu daha ayrıntılı tanıtacağız. -Geliştirme vs Üretim Modu -========================= -Nette, geliştirme veya üretim sunucusunda çalışıp çalışmadığına bağlı olarak farklı davranır: +Geliştirme modu ve üretim modu +============================== + +Nette, bir geliştirme sunucusunda mı yoksa üretim sunucusunda mı çalıştığına göre farklı davranır: -🛠️ Geliştirme Modu (Development): - - Yararlı bilgilerle (SQL sorguları, yürütme süresi, kullanılan bellek) Tracy hata ayıklama çubuğunu gösterir - - Bir hata durumunda, fonksiyon çağrıları ve değişken içerikleriyle ayrıntılı bir hata sayfası gösterir - - Latte şablonları değiştiğinde, yapılandırma dosyaları düzenlendiğinde vb. önbelleği otomatik olarak yeniler +🛠️ Geliştirme modu: + - Yararlı bilgiler içeren Tracy hata ayıklama çubuğunu gösterir (SQL sorguları, çalışma süresi, kullanılan bellek) + - Hata durumunda, fonksiyon çağrılarını ve değişken içeriklerini gösteren ayrıntılı bir hata sayfası gösterir + - Latte şablonları, yapılandırma dosyaları vb. değiştiğinde önbelleği otomatik yeniler -🚀 Üretim Modu (Production): - - Hata ayıklama bilgisi göstermez, tüm hataları günlüğe yazar - - Bir hata durumunda, ErrorPresenter'ı veya genel "Server Error" sayfasını gösterir - - Önbellek asla otomatik olarak yenilenmez! - - Hız ve güvenlik için optimize edilmiştir +🚀 Üretim modu: + - Hiçbir hata ayıklama bilgisi göstermez, tüm hatalar günlüğe yazılır + - Hata durumunda ErrorPresenter'ı veya genel bir "Server Error" sayfasını gösterir + - Önbellek asla otomatik yenilenmez! + - Hız ve güvenlik için iyileştirilmiştir -Mod seçimi otomatik algılama ile yapılır, bu nedenle genellikle herhangi bir şeyi yapılandırmaya veya manuel olarak değiştirmeye gerek yoktur: +Mod seçimi otomatik algılamayla yapılır, bu yüzden genellikle bir şey yapılandırmaya veya modu elle değiştirmeye gerek yoktur: -- geliştirme modu: localhost'ta (IP adresi `127.0.0.1` veya `::1`) proxy mevcut değilse (yani HTTP başlığı yoksa) -- üretim modu: her yerde +- geliştirme modu: localhost'ta (IP adresi `127.0.0.1` veya `::1`), proxy yoksa (yani HTTP header'ı algılanmıyorsa) +- üretim modu: diğer her yerde -Geliştirme modunu diğer durumlarda da etkinleştirmek istersek, örneğin belirli bir IP adresinden erişen programcılar için, `setDebugMode()` kullanırız: +Geliştirme modunu başka durumlarda da etkinleştirmek istersek, örneğin belirli bir IP adresinden erişen programcılar için, `setDebugMode()` kullanırız: ```php -$this->configurator->setDebugMode('23.75.345.200'); // IP adresleri dizisi de belirtilebilir +$this->configurator->setDebugMode('23.75.345.200'); // bir IP adresi dizisi de verilebilir ``` -Kesinlikle IP adresini bir çerezle birleştirmenizi öneririz. `nette-debug` çerezine gizli bir belirteç, örneğin `secret1234` kaydederiz ve bu şekilde belirli bir IP adresinden erişen ve aynı zamanda çerezde belirtilen belirtece sahip olan programcılar için geliştirme modunu etkinleştiririz: +IP adresini bir çerezle birleştirmenizi kesinlikle öneririz. `nette-debug` çerezinde gizli bir belirteç, örneğin `secret1234`, saklayın ve böylece geliştirme modunu, belirli bir IP adresinden erişen ve çerezinde bu belirteci de taşıyan programcılar için etkinleştirin: ```php $this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` -Geliştirme modunu tamamen kapatabiliriz, localhost için bile: +Geliştirme modunu, localhost için bile, tamamen kapatabiliriz de: ```php $this->configurator->setDebugMode(false); ``` -Dikkat, `true` değeri geliştirme modunu zorla açar, bu üretim sunucusunda asla olmamalıdır. +`true` değerinin geliştirme modunu zorla açtığını, bunun da bir üretim sunucusunda **asla** olmaması gerektiğini unutmayın. + +Otomatik algılamayı içeride `Configurator::detectDebugMode()` statik metodu yürütür; onu kendiniz de çağırabilirsiniz, örneğin geliştirme modunu configurator dışında algılamak için. İsteğe bağlı bir IP adresi veya bilgisayar adı beyaz listesi kabul eder ve geçerli isteğin geliştirme modunda çalışıp çalışmayacağını döndürür: + +```php +$debug = Nette\Bootstrap\Configurator::detectDebugMode('23.75.345.200'); +``` -Hata Ayıklama Aracı Tracy +Hata ayıklama aracı Tracy ========================= -Kolay hata ayıklama için harika [Tracy |tracy:] aracını da etkinleştireceğiz. Geliştirme modunda hataları görselleştirir ve üretim modunda hataları belirtilen dizine günlüğe kaydeder: +Kolay hata ayıklama için mükemmel [Tracy |tracy:] aracını etkinleştireceğiz. Geliştirme modunda hataları görselleştirir, üretim modunda ise belirtilen dizine günlükler: ```php $this->configurator->enableTracy($this->rootDir . '/log'); ``` -Geçici Dosyalar +Geçici dosyalar =============== -Nette, DI konteyneri, RobotLoader, şablonlar vb. için önbellek kullanır. Bu nedenle, önbelleğin depolanacağı dizinin yolunu ayarlamak gerekir: +Nette; DI konteyneri, RobotLoader, şablonlar vb. için önbellek kullanır. Bu yüzden önbelleğin saklanacağı dizinin yolunu ayarlamak gerekir: ```php $this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` -Linux veya macOS'ta, `log/` ve `temp/` dizinlerine [yazma izinlerini |nette:troubleshooting#Dizin İzinlerini Ayarlama] ayarlayın. +Linux veya macOS'ta `log/` ve `temp/` dizinleri için [yazma izinlerini |nette:troubleshooting#Dizin İzinlerini Ayarlama] ayarlayın. RobotLoader =========== -Genellikle [RobotLoader |robot-loader:] kullanarak sınıfları otomatik olarak yüklemek isteyeceğiz, bu yüzden onu başlatmalı ve `Bootstrap.php` dosyasının bulunduğu dizinden (yani `__DIR__`) ve tüm alt dizinlerden sınıfları yüklemesine izin vermeliyiz: +Genellikle sınıfları [RobotLoader |robot-loader:] ile otomatik yüklemek isteriz, bu yüzden onu başlatmamız ve `Bootstrap.php`'nin bulunduğu dizinden (yani `__DIR__`) ve tüm alt dizinlerinden sınıfları yüklemesini sağlamamız gerekir: ```php $this->configurator->createRobotLoader() @@ -158,36 +170,38 @@ $this->configurator->createRobotLoader() ->register(); ``` -Alternatif bir yaklaşım, PSR-4'e uyarak sınıfları yalnızca [Composer |best-practices:composer] aracılığıyla yüklemektir. +Alternatif bir yaklaşım, sınıfları yalnızca PSR-4'e uygun olarak [Composer |best-practices:composer] üzerinden yüklemektir. -Zaman Dilimi -============ +Saat dilimi +=========== -Yapılandırıcı aracılığıyla varsayılan zaman dilimini ayarlayabilirsiniz. +Varsayılan saat dilimini configurator ile ayarlayabilirsiniz. ```php $this->configurator->setTimeZone('Europe/Prague'); ``` -DI Konteyner Yapılandırması -=========================== +DI konteynerinin yapılandırması +=============================== -Başlatma sürecinin bir parçası, tüm uygulamanın kalbi olan nesneler için bir fabrika olan DI konteynerinin oluşturulmasıdır. Aslında bu, Nette tarafından oluşturulan ve önbellek dizinine kaydedilen bir PHP sınıfıdır. Fabrika, uygulamanın temel nesnelerini üretir ve yapılandırma dosyaları aracılığıyla ona nasıl oluşturulacağını ve ayarlanacağını bildiririz, böylece tüm uygulamanın davranışını etkileriz. +Başlatma sürecinin bir parçası, tüm uygulamanın kalbi olan DI konteynerinin, yani nesne factory'sinin oluşturulmasıdır. Aslında Nette tarafından üretilen ve önbellek dizininde saklanan bir PHP sınıfıdır. Factory, uygulamanın anahtar nesnelerini üretir; yapılandırma dosyalarıyla ona bunları nasıl oluşturup ayarlayacağını söyleriz ve böylece tüm uygulamanın davranışını etkileriz. -Yapılandırma dosyaları genellikle [NEON |neon:format] formatında yazılır. Ayrı bir bölümde, [nelerin yapılandırılabileceğini |nette:configuring] öğreneceksiniz. +Yapılandırma dosyaları genellikle [NEON formatında |neon:format] yazılır. Ayrı bir bölümde [neyin yapılandırılabileceğini |nette:configuring] okuyabilirsiniz. .[tip] -Geliştirme modunda, kod veya yapılandırma dosyaları her değiştiğinde konteyner otomatik olarak güncellenir. Üretim modunda, yalnızca bir kez oluşturulur ve performansı en üst düzeye çıkarmak için değişiklikler kontrol edilmez. +Geliştirme modunda konteyner, kod veya yapılandırma dosyaları her değiştiğinde otomatik güncellenir. Üretim modunda ise yalnızca bir kez üretilir ve performansı en üst düzeye çıkarmak için değişiklikler denetlenmez. -Yapılandırma dosyalarını `addConfig()` kullanarak yükleriz: +`createContainer()` konteyneri kurup örneğini döndürürken, `loadContainer()` metodu yalnızca üretilen konteyner sınıfının adını döndürür; onu kendiniz örnekleyebilirsiniz. Bu, ileri düzey senaryolarda işe yarar. + +Yapılandırma dosyaları `addConfig()` ile yüklenir: ```php $this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` -Daha fazla yapılandırma dosyası eklemek istiyorsak, `addConfig()` fonksiyonunu birden çok kez çağırabiliriz. +Daha fazla yapılandırma dosyası eklemek istersek, `addConfig()` fonksiyonunu birden çok kez çağırabiliriz. ```php $configDir = $this->rootDir . '/config'; @@ -198,17 +212,17 @@ if (PHP_SAPI === 'cli') { } ``` -`cli.php` adı bir yazım hatası değildir, yapılandırma bir dizi olarak döndüren bir PHP dosyasında da yazılabilir. +`cli.php` adı yazım hatası değildir; yapılandırma, onu dizi olarak döndüren bir PHP dosyasında da yazılabilir. -Ayrıca [`includes` bölümünde |dependency-injection:configuration#Dosya Dahil Etme] başka yapılandırma dosyaları da ekleyebiliriz. +Başka yapılandırma dosyalarını [`includes` bölümünde |dependency-injection:configuration#Dosyaları Dahil Etme] de ekleyebiliriz. -Yapılandırma dosyalarında aynı anahtarlara sahip öğeler görünürse, bunlar üzerine yazılır veya [diziler durumunda birleştirilir |dependency-injection:configuration#Birleştirme]. Daha sonra eklenen dosya, öncekinden daha yüksek önceliğe sahiptir. `includes` bölümünün belirtildiği dosya, içine dahil edilen dosyalardan daha yüksek önceliğe sahiptir. +Yapılandırma dosyalarında aynı anahtarlara sahip öğeler geçerse üzerine yazılır, [dizilerde ise birleştirilir |dependency-injection:configuration#Birleştirme]. Sonra dahil edilen dosyanın önceliği öncekinden yüksektir. `includes` bölümünün listelendiği dosyanın önceliği, içinde dahil edilen dosyalardan yüksektir. -Statik Parametreler +Statik parametreler ------------------- -Yapılandırma dosyalarında kullanılan parametreleri [`parameters` bölümünde |dependency-injection:configuration#Parametreler] tanımlayabilir ve ayrıca `addStaticParameters()` metoduyla (diğer adı `addParameters()`) iletebilir (veya üzerine yazabiliriz). Önemli olan, farklı parametre değerlerinin ek DI konteynerlerinin, yani ek sınıfların oluşturulmasına neden olmasıdır. +Yapılandırma dosyalarında kullanılan parametreler [`parameters` bölümünde |dependency-injection:configuration#Parametreler] tanımlanabilir ve ayrıca `addStaticParameters()` metoduyla (eski, artık kullanımdan kaldırılmış takma adı `addParameters()`) aktarılabilir veya üzerine yazılabilir. Önemli olan, farklı parametre değerlerinin ek DI konteynerlerinin, yani ek sınıfların üretilmesine yol açmasıdır. ```php $this->configurator->addStaticParameters([ @@ -216,13 +230,13 @@ $this->configurator->addStaticParameters([ ]); ``` -`projectId` parametresine yapılandırmada normal `%projectId%` gösterimiyle başvurulabilir. +`projectId` parametresine yapılandırmada standart `%projectId%` gösterimiyle başvurulabilir. -Dinamik Parametreler +Dinamik parametreler -------------------- -Konteynere dinamik parametreler de ekleyebiliriz; bunların farklı değerleri, statik parametrelerin aksine, yeni DI konteynerlerinin oluşturulmasına neden olmaz. +Konteynere dinamik parametreler de ekleyebiliriz; bunların farklı değerleri, statik parametrelerin aksine, yeni DI konteynerlerinin üretilmesine yol açmaz. ```php $this->configurator->addDynamicParameters([ @@ -230,7 +244,7 @@ $this->configurator->addDynamicParameters([ ]); ``` -Bu şekilde, örneğin ortam değişkenlerini kolayca ekleyebiliriz, bunlara daha sonra yapılandırmada `%env.variable%` gösterimiyle başvurulabilir. +Böylece örneğin ortam değişkenlerini kolayca ekleyebiliriz; onlara yapılandırmada `%env.degisken%` gösterimiyle başvurulabilir. ```php $this->configurator->addDynamicParameters([ @@ -239,24 +253,25 @@ $this->configurator->addDynamicParameters([ ``` -Varsayılan Parametreler +Varsayılan parametreler ----------------------- -Yapılandırma dosyalarında şu statik parametreleri kullanabilirsiniz: +Yapılandırma dosyalarında şu parametreleri kullanabilirsiniz: -- `%appDir%`, `Bootstrap.php` dosyasını içeren dizine mutlak yoldur -- `%wwwDir%`, giriş dosyası `index.php` dosyasını içeren dizine mutlak yoldur -- `%tempDir%`, geçici dosyalar için dizine mutlak yoldur -- `%vendorDir%`, Composer'ın kütüphaneleri kurduğu dizine mutlak yoldur -- `%rootDir%`, projenin kök dizinine mutlak yoldur +- `%appDir%`, `Bootstrap.php` dosyasını içeren dizinin mutlak yoludur +- `%wwwDir%`, giriş dosyası `index.php`'yi içeren dizinin mutlak yoludur +- `%tempDir%`, geçici dosyalar dizininin mutlak yoludur +- `%vendorDir%`, Composer'ın kütüphaneleri kurduğu dizinin mutlak yoludur +- `%rootDir%`, projenin kök dizininin mutlak yoludur +- `%baseUrl%`, kök dizine giden mutlak URL'dir (çalışma zamanında çözülen dinamik bir parametre) - `%debugMode%`, uygulamanın hata ayıklama modunda olup olmadığını belirtir - `%consoleMode%`, isteğin komut satırından gelip gelmediğini belirtir -İçe Aktarılan Servisler +İçe aktarılan servisler ----------------------- -Şimdi daha derine iniyoruz. DI konteynerinin amacı nesneleri üretmek olsa da, istisnai olarak mevcut bir nesneyi konteynere ekleme ihtiyacı doğabilir. Bunu, servisi `imported: true` bayrağıyla tanımlayarak yaparız. +Şimdi biraz derine iniyoruz. DI konteynerinin amacı nesne oluşturmak olsa da, ara sıra var olan bir nesneyi konteynere yerleştirme ihtiyacı doğabilir. Bunu, servisi `imported: true` bayrağıyla tanımlayarak yaparız. ```neon services: @@ -265,7 +280,7 @@ services: imported: true ``` -Ve bootstrap'ta nesneyi konteynere ekleriz: +Ve bootstrap'ta nesneyi konteynere yerleştiririz: ```php $this->configurator->addServices([ @@ -274,15 +289,15 @@ $this->configurator->addServices([ ``` -Farklı Ortam -============ +Farklı ortamlar +=============== -Bootstrap sınıfını ihtiyaçlarınıza göre değiştirmekten çekinmeyin. Web projelerini ayırt etmek için `bootWebApplication()` metoduna parametreler ekleyebilirsiniz. Veya birim testleri için ortamı başlatan `bootTestEnvironment()`, komut satırından çağrılan betikler için `bootConsoleApplication()` gibi başka metotlar ekleyebiliriz. +`Bootstrap` sınıfını ihtiyaçlarınıza göre çekinmeden değiştirin. Web projelerini ayırt etmek için `bootWebApplication()` metoduna parametre ekleyebilirsiniz. Ya da başka metotlar ekleyebiliriz; örneğin birim testleri için ortamı hazırlayan `bootTestEnvironment()`, komut satırından çağrılan betikler için `bootConsoleApplication()` vb. ```php public function bootTestEnvironment(): Nette\DI\Container { - Tester\Environment::setup(); // Nette Tester'ı başlat + Tester\Environment::setup(); // Nette Tester'ın başlatılması $this->setupContainer(); return $this->configurator->createContainer(); } diff --git a/application/tr/components.texy b/application/tr/components.texy index 68661abfc4..2d08ea1a4d 100644 --- a/application/tr/components.texy +++ b/application/tr/components.texy @@ -1,9 +1,9 @@ -Etkileşimli Bileşenler +Etkileşimli bileşenler **********************
    -Bileşenler, sayfalara eklediğimiz bağımsız, yeniden kullanılabilir nesnelerdir. Bunlar formlar, veri ızgaraları, anketler, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir. Şunları göstereceğiz: +Bileşenler, sayfalara gömdüğümüz ayrı ve yeniden kullanılabilir nesnelerdir. Formlar, datagrid'ler, anketler, kısacası tekrar tekrar kullanmanın anlamlı olduğu her şey olabilirler. Şunları göstereceğiz: - bileşenler nasıl kullanılır? - nasıl yazılır? @@ -11,19 +11,19 @@ Bileşenler, sayfalara eklediğimiz bağımsız, yeniden kullanılabilir nesnele
    -Nette'nin yerleşik bir bileşen sistemi vardır. Delphi veya ASP.NET Web Forms'tan aşina olanlar benzer bir şey hatırlayabilir, React veya Vue.js de uzaktan benzer bir şeye dayanmaktadır. Ancak, PHP framework dünyasında bu benzersiz bir özelliktir. +Nette'in yerleşik bir bileşen sistemi vardır. Buna benzer bir şey Delphi veya ASP.NET Web Forms kıdemlilerine tanıdık gelebilir; React ya da Vue.js de uzaktan benzer bir şey üzerine kuruludur. Ancak PHP framework'leri dünyasında bu eşsiz bir özelliktir. -Bununla birlikte, bileşenler uygulama geliştirme yaklaşımını temelden etkiler. Sayfaları önceden hazırlanmış birimlerden oluşturabilirsiniz. Yönetimde bir veri ızgarasına mı ihtiyacınız var? Onu Nette için açık kaynaklı eklentilerin (yani sadece bileşenlerin değil) deposu olan [Componette |https://componette.org/search/component] adresinde bulabilir ve presenter'a kolayca ekleyebilirsiniz. +Aynı zamanda bileşenler, uygulama geliştirmeye yaklaşımı temelden etkiler. Sayfaları önceden hazırlanmış birimlerden oluşturabilirsiniz. Yönetim panelinizde bir datagrid mi lazım? Onu, Nette için açık kaynak eklentilerin (yalnızca bileşenlerin değil) deposu olan [Componette |https://componette.org/search/component] üzerinde bulun ve presenter'a eklemeniz yeterli. -Presenter'a istediğiniz sayıda bileşen ekleyebilirsiniz. Ve bazı bileşenlere başka bileşenler ekleyebilirsiniz. Bu, kökü presenter olan bir bileşen ağacı oluşturur. +Presenter'a istediğiniz sayıda bileşen yerleştirebilirsiniz. Bazı bileşenlerin içine de başka bileşenler gömebilirsiniz. Böylece kökü presenter olan bir bileşen ağacı oluşur. -Fabrika Metotları +Factory metotları ================= -Bileşenler presenter'a nasıl eklenir ve ardından kullanılır? Genellikle fabrika metotları aracılığıyla. +Bileşenler presenter'a nasıl yerleştirilir ve sonra nasıl kullanılır? Genellikle factory metotlarıyla. -Bileşen fabrikası, bileşenleri yalnızca gerçekten ihtiyaç duyulduğunda (lazy / on demand) oluşturmanın zarif bir yoludur. Tüm sihir, `` öğesinin oluşturulan bileşenin adı olduğu ve bileşeni oluşturup döndüren `createComponent()` adlı bir metodun uygulanmasında yatar. +Bileşen factory'si, bileşenleri yalnızca gerçekten gerektiğinde (lazy / talep üzerine) oluşturmanın zarif bir yoludur. Tüm sihir, `createComponent()` adlı bir metodu uygulamakta yatar; burada `` oluşturulan bileşenin adıdır ve metot bileşeni oluşturup döndürür. ```php .{file:DefaultPresenter.php} class DefaultPresenter extends Nette\Application\UI\Presenter @@ -31,49 +31,54 @@ class DefaultPresenter extends Nette\Application\UI\Presenter protected function createComponentPoll(): PollControl { $poll = new PollControl; - $poll->items = $this->item; + $poll->items = $this->items; return $poll; } } ``` -Tüm bileşenlerin ayrı metotlarda oluşturulması sayesinde kod daha okunaklı hale gelir. +Tüm bileşenler ayrı metotlarda oluşturulduğu için kod daha anlaşılır olur. .[note] -Bileşen adları, metot adında büyük harfle yazılsa bile her zaman küçük harfle başlar. +Bileşen adları, metot adında büyük harfle yazılsalar da, her zaman küçük harfle başlar. -Fabrikaları asla doğrudan çağırmayız, bileşeni ilk kullandığımızda kendiliğinden çağrılırlar. Bu sayede bileşen doğru zamanda ve yalnızca gerçekten ihtiyaç duyulduğunda oluşturulur. Bileşeni kullanmazsak (örneğin, sayfanın yalnızca bir kısmının aktarıldığı bir AJAX isteğinde veya şablon önbelleğe alınırken), hiç oluşturulmaz ve sunucu performansından tasarruf ederiz. +Factory'leri asla doğrudan çağırmayız; bileşeni ilk kullandığımızda otomatik çağrılırlar. Bu sayede bileşen doğru anda ve yalnızca gerçekten gerekiyorsa oluşturulur. Bileşeni kullanmazsak (örneğin sayfanın yalnızca bir bölümünün aktarıldığı bir AJAX isteğinde veya şablon önbelleğe alınırken), hiç oluşturulmaz ve sunucu performansından tasarruf edilir. ```php .{file:DefaultPresenter.php} -// bileşene erişiriz ve eğer ilk kez ise, +// bileşene erişiriz ve bu ilk seferse // onu oluşturan createComponentPoll() çağrılır $poll = $this->getComponent('poll'); // alternatif sözdizimi: $poll = $this['poll']; ``` -Şablonda, bir bileşeni [{control} |#Oluşturma] etiketi kullanarak oluşturmak mümkündür. Bu nedenle bileşenleri şablona manuel olarak iletmeye gerek yoktur. +Şablonda bir bileşen [{control} |#Render] etiketiyle render edilebilir. Bu yüzden bileşenleri şablona elle aktarmaya gerek yoktur. ```latte -

    Oy Verin

    +

    Lütfen oy verin

    {control poll} ``` +.[tip] +Sayısı değişken bileşenleri dinamik olarak oluşturmak için [Multiplier |multiplier] kullanın. -Hollywood Tarzı +`createComponent()` factory metotları yalnızca presenter'larda çalışmaz. Bir bileşeni başka bir bileşenin içine aynı şekilde yerleştirip ağaç halinde birleştirebilirsiniz; bu, örneğin bir bileşenin içinde ayrı render edilen bir form için kullanışlıdır. + + +Hollywood tarzı =============== -Bileşenler genellikle Hollywood tarzı dediğimiz yeni bir tekniği kullanır. Film seçmelerine katılanların sıkça duyduğu şu meşhur cümleyi mutlaka bilirsiniz: "Bizi aramayın, biz sizi ararız." İşte tam olarak bundan bahsediyoruz. +Bileşenler genellikle Hollywood tarzı demeyi sevdiğimiz taze bir teknik kullanır. Film seçmelerine katılanların sıkça duyduğu klişeyi mutlaka bilirsiniz: "Bizi aramayın, biz sizi ararız." İşte tam olarak mesele budur. -Nette'de, sürekli bir şeyler sormak yerine ("form gönderildi mi?", "geçerli miydi?" veya "kullanıcı bu düğmeye bastı mı?"), framework'e "bu olduğunda, şu metodu çağır" dersiniz ve geri kalan işi ona bırakırsınız. JavaScript ile programlama yapıyorsanız, bu programlama tarzına aşinasınızdır. Belirli bir olay gerçekleştiğinde çağrılan fonksiyonlar yazarsınız. Ve dil onlara ilgili parametreleri iletir. +Nette'te sürekli soru sormak zorunda kalmak yerine ("form gönderildi mi?", "geçerli miydi?", "kullanıcı bu düğmeye bastı mı?"), framework'e "bu olduğunda şu metodu çağır" dersiniz ve gerisini ona bırakırsınız. JavaScript programlıyorsanız bu programlama tarzına yakından aşinasınızdır. Belirli bir olay gerçekleştiğinde çağrılan fonksiyonlar yazarsınız. Ve dil onlara uygun parametreleri aktarır. -Bu, uygulama yazma şeklini tamamen değiştirir. Framework'e ne kadar çok görev bırakabilirseniz, o kadar az işiniz olur. Ve dolayısıyla gözden kaçırabileceğiniz şeyler de o kadar azalır. +Bu, uygulama yazmaya bakışı tamamen değiştirir. Framework'e ne kadar çok işi bırakabilirseniz, o kadar az işiniz olur. Ve o kadar az şeyi gözden kaçırırsınız. -Bileşen Yazma +Bileşen yazma ============= -Bileşen terimiyle genellikle [api:Nette\Application\UI\Control] sınıfının bir alt sınıfını kastederiz. (Dolayısıyla "kontroller" terimini kullanmak daha doğru olurdu, ancak "kontroller" Türkçede tamamen farklı bir anlama sahiptir ve "bileşenler" daha çok yerleşmiştir.) Bu arada, presenter [api:Nette\Application\UI\Presenter] kendisi de `Control` sınıfının bir alt sınıfıdır. +Bileşen terimiyle genellikle [api:Nette\Application\UI\Control] sınıfının bir torununu kastediyoruz. (Aslında "control" terimini kullanmak daha doğru olurdu, ama bunun bazı dillerde başka bir anlamı var ve "bileşen" daha çok yerleşti.) Presenter'ın kendisi, [api:Nette\Application\UI\Presenter], de `Control` sınıfının bir torunudur. ```php .{file:PollControl.php} use Nette\Application\UI\Control; @@ -84,22 +89,22 @@ class PollControl extends Control ``` -Oluşturma -========= +Render +====== -Biliyoruz ki bir bileşeni oluşturmak için `{control componentName}` etiketi kullanılır. Bu aslında bileşenin `render()` metodunu çağırır ve burada oluşturmayı biz hallederiz. Tıpkı presenter'da olduğu gibi, `$this->template` değişkeninde [Latte şablonuna|templates] sahibiz ve ona parametreleri iletiriz. Presenter'dan farklı olarak, şablon dosyasını belirtmeli ve oluşturulmasını sağlamalıyız: +Bir bileşeni render etmek için `{control bilesenAdi}` etiketinin kullanıldığını zaten biliyoruz. Bu aslında bileşenin `render()` metodunu çağırır; render'ı orada biz üstleniriz. Tıpkı presenter'daki gibi, `$this->template` değişkeninde parametre aktardığımız bir [Latte şablonu|templates] elimizin altındadır. Presenter'dan farklı olarak, şablon dosyasını belirtmemiz ve render ettirmemiz gerekir: ```php .{file:PollControl.php} public function render(): void { - // şablona bazı parametreler ekleriz + // şablona bazı parametreler ekle $this->template->param = $value; - // ve onu oluştururuz + // ve render et $this->template->render(__DIR__ . '/poll.latte'); } ``` -`{control}` etiketi, `render()` metoduna parametreler iletmeyi sağlar: +`{control}` etiketi, `render()` metoduna parametre aktarmayı sağlar: ```latte {control poll $id, $message} @@ -112,7 +117,7 @@ public function render(int $id, string $message): void } ``` -Bazen bir bileşen, ayrı ayrı oluşturmak istediğimiz birkaç bölümden oluşabilir. Her biri için kendi oluşturma metodumuzu oluştururuz, buradaki örnekte örneğin `renderPaginator()`: +Bazen bir bileşen, ayrı ayrı render etmek istediğimiz birkaç bölümden oluşabilir. Her biri için kendi render metodumuzu oluştururuz, burada örnekte `renderPaginator()`: ```php .{file:PollControl.php} public function renderPaginator(): void @@ -121,69 +126,69 @@ public function renderPaginator(): void } ``` -Ve şablonda onu şu şekilde çağırırız: +Şablonda ise onu şöyle çağırırız: ```latte {control poll:paginator} ``` -Daha iyi anlamak için, bu etiketin PHP'ye nasıl çevrildiğini bilmek iyidir. +Daha iyi anlamak için bu etiketin PHP koduna nasıl çevrildiğini bilmek iyidir. ```latte {control poll} {control poll:paginator 123, 'hello'} ``` -şu şekilde çevrilir: +şuna çevrilir: ```php $control->getComponent('poll')->render(); $control->getComponent('poll')->renderPaginator(123, 'hello'); ``` -`getComponent()` metodu `poll` bileşenini döndürür ve bu bileşen üzerinde `render()` metodunu veya etikette iki noktadan sonra farklı bir oluşturma yöntemi belirtilmişse `renderPaginator()` metodunu çağırır. +`getComponent()` metodu `poll` bileşenini döndürür ve bu bileşen üzerinde `render()` metodu, ya da etikette iki nokta üst üstenin ardından farklı bir render metodu belirtilmişse `renderPaginator()`, çağrılır. .[caution] -Dikkat, parametrelerin herhangi bir yerinde **`=>`** görünürse, tüm parametreler bir diziye paketlenir ve ilk argüman olarak iletilir: +Dikkat: parametrelerde köşeli parantez dışında **`=>`** geçerse, tüm parametreler bir diziye sarılır ve ilk argüman olarak aktarılır: ```latte {control poll, id: 123, message: 'hello'} ``` -şu şekilde çevrilir: +şuna çevrilir: ```php $control->getComponent('poll')->render(['id' => 123, 'message' => 'hello']); ``` -Alt bileşenin oluşturulması: +Bir alt bileşenin render edilmesi: ```latte {control cartControl-someForm} ``` -şu şekilde çevrilir: +şuna çevrilir: ```php $control->getComponent("cartControl-someForm")->render(); ``` -Bileşenler, presenter'lar gibi, şablonlara otomatik olarak birkaç yararlı değişken iletir: +Bileşenler, presenter'lar gibi, şablonlara birkaç yararlı değişkeni otomatik aktarır: -- `$basePath`, kök dizine mutlak URL yoludur (örn. `/eshop`) -- `$baseUrl`, kök dizine mutlak URL'dir (örn. `http://localhost/eshop`) -- `$user`, [kullanıcıyı temsil eden |security:authentication] nesnedir -- `$presenter`, mevcut presenter'dır -- `$control`, mevcut bileşendir -- `$flashes`, `flashMessage()` fonksiyonu tarafından gönderilen [mesajlar |#Flash Mesajları] dizisidir +- `$basePath`, kök dizine giden mutlak URL yoludur (örneğin `/eshop`) +- `$baseUrl`, kök dizine giden mutlak URL'dir (örneğin `http://localhost/eshop`) +- `$user`, [kullanıcıyı temsil eden |security:authentication] bir nesnedir +- `$presenter`, geçerli presenter'dır +- `$control`, geçerli bileşendir +- `$flashes`, `flashMessage()` fonksiyonuyla gönderilen [mesajların |#Flash mesajları] dizisidir Sinyal ====== -Nette uygulamasında gezinmenin `Presenter:action` çiftlerine bağlantı verme veya yönlendirme yapmaktan ibaret olduğunu zaten biliyoruz. Peki ya sadece **mevcut sayfada** bir eylem gerçekleştirmek istersek? Örneğin, bir tablodaki sütunların sıralamasını değiştirmek; bir öğeyi silmek; açık/koyu modu değiştirmek; bir form göndermek; bir ankette oy kullanmak; vb. +Bir Nette uygulamasında gezinmenin, `Presenter:eylem` çiftlerine bağlantı vermekten veya yönlendirmekten oluştuğunu zaten biliyoruz. Peki ya yalnızca **geçerli sayfada** bir eylem gerçekleştirmek istersek? Örneğin bir tablodaki sütunların sıralamasını değiştirmek; bir öğeyi silmek; açık/koyu modu değiştirmek; bir formu göndermek; bir ankette oy vermek vb. -Bu tür isteklere sinyal denir. Ve eylemlerin `action()` veya `render()` metotlarını tetiklemesi gibi, sinyaller de `handle()` metotlarını çağırır. Eylem (veya view) kavramı yalnızca presenter'larla ilgiliyken, sinyaller tüm bileşenlerle ilgilidir. Ve dolayısıyla presenter'larla da, çünkü `UI\Presenter`, `UI\Control`'un bir alt sınıfıdır. +Bu tür isteğe sinyal denir. Ve eylemler `action()` veya `render()` metotlarını çağırdığı gibi, sinyaller de `handle()` metotlarını çağırır. Eylem (veya görünüm) kavramı yalnızca presenter'ları ilgilendirirken, sinyaller tüm bileşenleri ilgilendirir. Dolayısıyla presenter'ları da, çünkü `UI\Presenter`, `UI\Control`'ün torunudur. ```php public function handleClick(int $x, int $y): void @@ -192,36 +197,36 @@ public function handleClick(int $x, int $y): void } ``` -Sinyali çağıran bir bağlantıyı normal şekilde oluştururuz, yani şablonda `n:href` niteliğiyle veya `{link}` etiketiyle, kodda `link()` metoduyla. Daha fazla bilgi için [URL Bağlantıları Oluşturma |creating-links#Sinyale Bağlantılar] bölümüne bakın. +Sinyal çağıran bir bağlantı her zamanki gibi oluşturulur; yani şablonda `n:href` niteliğiyle veya `{link}` etiketiyle, kodda `link()` metoduyla. Daha fazlası [URL bağlantıları oluşturma |creating-links#Sinyallere bağlantılar] bölümünde. ```latte buraya tıkla ``` -Sinyal her zaman mevcut presenter ve eylem üzerinde çağrılır, başka bir presenter veya başka bir eylem üzerinde çağrılamaz. +Sinyal her zaman geçerli presenter ve eylem üzerinde çağrılır; başka bir presenter veya eylem üzerinde çağırmak mümkün değildir. -Dolayısıyla sinyal, sayfanın orijinal istekteki gibi tamamen yeniden yüklenmesine neden olur, ancak ek olarak ilgili parametrelerle sinyal işleyici metodunu çağırır. Metot mevcut değilse, kullanıcıya 403 Forbidden hata sayfası olarak gösterilen [api:Nette\Application\UI\BadSignalException] istisnası atılır. +Böylece sinyal, sayfanın tıpkı özgün istekteki gibi yeniden yüklenmesine yol açar, ama ek olarak sinyal işleme metodunu uygun parametrelerle çağırır. Metot yoksa, kullanıcıya 403 Forbidden hata sayfası olarak gösterilen bir [api:Nette\Application\UI\BadSignalException] istisnası fırlatılır. Snippet'ler ve AJAX =================== -Sinyaller size biraz AJAX'ı hatırlatabilir: mevcut sayfada çağrılan işleyiciler. Ve haklısınız, sinyaller gerçekten de sık sık AJAX kullanılarak çağrılır ve ardından sayfanın yalnızca değiştirilmiş bölümleri tarayıcıya aktarılır. Yani sözde snippet'ler. Daha fazla bilgi için [AJAX'a ayrılmış sayfada |ajax] bulabilirsiniz. +Sinyaller size biraz AJAX'ı hatırlatabilir: geçerli sayfada çağrılan işleyiciler. Ve haklısınız, sinyaller gerçekten sıklıkla AJAX ile çağrılır ve ardından tarayıcıya yalnızca sayfanın değişen bölümleri aktarılır. Bunlara snippet denir. Daha fazla bilgiyi [AJAX'a ayrılmış sayfada |ajax] bulabilirsiniz. -Flash Mesajları +Flash mesajları =============== -Bileşenin, presenter'dan bağımsız kendi flash mesaj deposu vardır. Bunlar, örneğin bir işlemin sonucunu bildiren mesajlardır. Flash mesajlarının önemli bir özelliği, yönlendirmeden sonra bile şablonda kullanılabilir olmalarıdır. Görüntülendikten sonra bile 30 saniye daha canlı kalırlar - örneğin, hatalı bir aktarım nedeniyle kullanıcının sayfayı yenilemesi durumunda mesaj hemen kaybolmaz. +Bir bileşenin, presenter'dan bağımsız kendi flash mesaj deposu vardır. Bunlar örneğin bir işlemin sonucunu bildiren mesajlardır. Flash mesajlarının önemli bir özelliği, yönlendirmeden sonra da şablonda erişilebilir olmalarıdır. Gösterildikten sonra bile 30 saniye daha etkin kalırlar; örneğin kullanıcı bir aktarım hatası yüzünden sayfayı yenilerse mesaj hemen kaybolmaz. -Gönderme işlemi [flashMessage |api:Nette\Application\UI\Control::flashMessage()] metodu tarafından gerçekleştirilir. İlk parametre mesaj metni veya mesajı temsil eden bir `stdClass` nesnesidir. İsteğe bağlı ikinci parametre türüdür (error, warning, info vb.). `flashMessage()` metodu, flash mesajının bir örneğini `stdClass` nesnesi olarak döndürür ve buna ek bilgiler eklenebilir. +Gönderimi [flashMessage |api:Nette\Application\UI\Control::flashMessage()] metodu üstlenir. İlk parametre mesajın metni (`string`, `Stringable`) veya mesajı temsil eden bir `stdClass` nesnesidir. İsteğe bağlı ikinci parametre onun tipidir (error, warning, info vb.). `flashMessage()` metodu, flash mesajın örneğini bir `stdClass` nesnesi olarak döndürür; ona başka bilgiler eklenebilir. ```php $this->flashMessage('Öğe silindi.'); -$this->redirect(/* ... */); // ve yönlendiririz +$this->redirect(/* ... */); // ve yönlendir ``` -Şablonda bu mesajlar `$flashes` değişkeninde `stdClass` nesneleri olarak bulunur ve `message` (mesaj metni), `type` (mesaj türü) özelliklerini içerir ve daha önce bahsedilen kullanıcı bilgilerini içerebilir. Onları örneğin şu şekilde oluştururuz: +Bu mesajlar şablona `$flashes` değişkeninde `stdClass` nesneleri olarak sunulur; `message` (mesaj metni) ve `type` (mesaj tipi) özelliklerini içerirler ve sözü geçen kullanıcı bilgilerini de taşıyabilirler. Onları örneğin şöyle render ederiz: ```latte {foreach $flashes as $flash} @@ -230,39 +235,39 @@ $this->redirect(/* ... */); // ve yönlendiririz ``` -Sinyal Sonrası Yönlendirme -========================== +Sinyalin işlenmesinden sonra yönlendirme +======================================== -Bileşen sinyallerinin işlenmesinden sonra genellikle bir yönlendirme takip eder. Bu, formlardaki duruma benzer - gönderildikten sonra da yönlendiririz, böylece tarayıcıda sayfa yenilendiğinde veriler tekrar gönderilmez. +Bir bileşenin sinyalinin işlenmesinin ardından çoğu zaman bir yönlendirme gelir. Bu formlara benzer; onları gönderdikten sonra da, sayfa tarayıcıda yenilenirse verinin yeniden gönderilmesini engellemek için yönlendiririz. ```php -$this->redirect('this') // mevcut presenter ve eyleme yönlendirir +$this->redirect('this'); // geçerli presenter ve eyleme yönlendirir ``` -Bileşen yeniden kullanılabilir bir öğe olduğundan ve genellikle belirli presenter'larla doğrudan bir bağlantısı olmaması gerektiğinden, `redirect()` ve `link()` metotları parametreyi otomatik olarak bileşen sinyali olarak yorumlar: +Bir bileşen yeniden kullanılabilir bir öğe olduğundan ve genellikle belirli presenter'larla doğrudan bağı olmaması gerektiğinden, `redirect()` ve `link()` metotları parametreyi otomatik olarak bileşen sinyali diye yorumlar: ```php -$this->redirect('click') // aynı bileşenin 'click' sinyaline yönlendirir +$this->redirect('click'); // aynı bileşenin 'click' sinyaline yönlendirir ``` -Başka bir presenter'a veya eyleme yönlendirmeniz gerekiyorsa, bunu presenter aracılığıyla yapabilirsiniz: +Başka bir presenter'a veya eyleme yönlendirmeniz gerekirse bunu presenter üzerinden yapabilirsiniz: ```php -$this->getPresenter()->redirect('Product:show'); // başka bir presenter/eyleme yönlendirir +$this->getPresenter()->redirect('Product:show'); // başka bir presenter'a/eyleme yönlendirir ``` -Kalıcı Parametreler +Kalıcı parametreler =================== -Kalıcı parametreler, farklı istekler arasında bileşenlerde durumu korumak için kullanılır. Değerleri, bir bağlantıya tıklandıktan sonra bile aynı kalır. Oturumdaki verilerin aksine, URL'de aktarılırlar. Ve bu tamamen otomatiktir, aynı sayfadaki diğer bileşenlerde oluşturulan bağlantılar dahil. +Kalıcı parametreler, bileşenlerdeki durumu farklı istekler boyunca korumak için kullanılır. Değerleri, bir bağlantıya tıklandıktan sonra da aynı kalır. Oturum verilerinin aksine URL'de aktarılırlar. Ve bu, aynı sayfadaki başka bileşenlerde oluşturulan bağlantılar dahil, tamamen otomatik gerçekleşir. -Örneğin, içeriği sayfalandırmak için bir bileşeniniz var. Sayfada bu tür birkaç bileşen olabilir. Ve bir bağlantıya tıklandığında tüm bileşenlerin mevcut sayfalarında kalmasını istiyoruz. Bu nedenle, sayfa numarasını (`page`) kalıcı bir parametre yaparız. +Örneğin içeriği sayfalamak için bir bileşeniniz var. Bir sayfada böyle birkaç bileşen olabilir. Ve bir bağlantıya tıklandıktan sonra tüm bileşenlerin kendi geçerli sayfalarında kalmasını istiyoruz. Bu yüzden sayfa numarasını (`page`) kalıcı parametre yaparız. -Nette'de kalıcı bir parametre oluşturmak son derece basittir. Sadece genel bir özellik oluşturmanız ve onu bir nitelikle işaretlemeniz yeterlidir: (eskiden `/** @persistent */` kullanılırdı) +Nette'te kalıcı parametre oluşturmak son derece basittir. Public bir özellik oluşturup onu nitelikle işaretlemeniz yeterlidir: (önceden `/** @persistent */` kullanılıyordu) ```php -use Nette\Application\Attributes\Persistent; // bu satır önemlidir +use Nette\Application\Attributes\Persistent; // bu satır önemli class PaginatingControl extends Control { @@ -271,61 +276,61 @@ class PaginatingControl extends Control } ``` -Özellik için veri türünü (örn. `int`) belirtmenizi öneririz ve varsayılan bir değer de belirtebilirsiniz. Parametre değerleri [doğrulanabilir |#Kalıcı Parametrelerin Doğrulanması]. +Özellik için veri tipini (örneğin `int`) belirtmenizi öneririz, ayrıca bir varsayılan değer de verebilirsiniz. Parametre değerleri [doğrulanabilir |#Kalıcı parametrelerin doğrulanması]. -Bir bağlantı oluştururken, kalıcı parametrenin değeri değiştirilebilir: +Bağlantı oluştururken kalıcı parametrenin değeri değiştirilebilir: ```latte sonraki ``` -Veya *sıfırlanabilir*, yani URL'den kaldırılabilir. O zaman varsayılan değerini alacaktır: +Ya da *sıfırlanabilir*, yani URL'den kaldırılabilir. O zaman varsayılan değerini alır: ```latte sıfırla ``` -Kalıcı Bileşenler +Kalıcı bileşenler ================= -Sadece parametreler değil, bileşenler de kalıcı olabilir. Böyle bir bileşenin kalıcı parametreleri, presenter'ın farklı eylemleri arasında veya birden fazla presenter arasında da aktarılır. Kalıcı bileşenleri presenter sınıfındaki bir ek açıklama ile işaretleriz. Örneğin, `calendar` ve `poll` bileşenlerini şu şekilde işaretleriz: +Yalnızca parametreler değil, bileşenler de kalıcı olabilir. Kalıcı parametreleri o zaman presenter'ın farklı eylemleri arasında, hatta birden fazla presenter arasında bile aktarılır. Kalıcı bileşenleri presenter sınıfı üzerinde bir nitelikle işaretleriz. Örneğin `calendar` ve `poll` bileşenlerini şöyle işaretleriz: ```php -/** - * @persistent(calendar, poll) - */ +use Nette\Application\Attributes\Persistent; + +#[Persistent('calendar', 'poll')] class DefaultPresenter extends Nette\Application\UI\Presenter { } ``` -Bu bileşenlerin içindeki alt bileşenleri işaretlemeye gerek yoktur, onlar da kalıcı hale gelirler. +Bu bileşenlerin içindeki alt bileşenleri işaretlemeye gerek yoktur; onlar da kalıcı olur. -PHP 8'de, kalıcı bileşenleri işaretlemek için nitelikleri de kullanabilirsiniz: +Eski `@persistent` anotasyonu hâlâ çalışır, ama kullanımdan kaldırılmıştır ve bir uyarı tetikler: ```php -use Nette\Application\Attributes\Persistent; - -#[Persistent('calendar', 'poll')] +/** + * @persistent(calendar, poll) + */ class DefaultPresenter extends Nette\Application\UI\Presenter { } ``` -Bağımlılıklara Sahip Bileşenler -=============================== +Bağımlılıkları olan bileşenler +============================== -Bağımlılıklara sahip bileşenleri, onları kullanacak presenter'ları "kirletmeden" nasıl oluşturabiliriz? Nette'deki DI konteynerinin akıllı özellikleri sayesinde, klasik servisleri kullanırken olduğu gibi, işin çoğunu framework'e bırakabiliriz. +Bağımlılıkları olan bileşenler, onları kullanacak presenter'ları "karıştırmadan" nasıl oluşturulur? Nette'teki DI konteynerinin akıllı özellikleri sayesinde, klasik servislerin kullanımında olduğu gibi, işin çoğu framework'e bırakılabilir. -Örnek olarak, `PollFacade` servisine bağımlılığı olan bir bileşeni ele alalım: +`PollFacade` servisine bağımlılığı olan bir bileşen örneğini ele alalım: ```php class PollControl extends Control { public function __construct( - private int $id, // Bileşeni oluşturduğumuz anketin kimliği + private int $id, // bileşeni oluşturduğumuz anketin ID'si private PollFacade $facade, ) { } @@ -338,11 +343,11 @@ class PollControl extends Control } ``` -Klasik bir servis yazıyor olsaydık, çözülecek bir şey olmazdı. Tüm bağımlılıkların iletilmesi DI konteyneri tarafından görünmez bir şekilde halledilirdi. Ancak bileşenlerle genellikle, yeni örneklerini doğrudan presenter'da [fabrika metotlarında |#Fabrika Metotları] `createComponent…()` oluşturacak şekilde çalışırız. Ancak tüm bileşenlerin tüm bağımlılıklarını presenter'a iletmek, sonra onları bileşenlere iletmek hantaldır. Ve yazılan kod miktarı… +Klasik bir servis yazsaydık tartışacak bir şey olmazdı. DI konteyneri tüm bağımlılıkların aktarımını görünmez biçimde üstlenirdi. Ancak bileşenlerde bunları genellikle, presenter'da doğrudan [#Factory metotları] `createComponent…()` içinde yeni bir örnek oluşturarak hallederiz. Ama tüm bileşenlerin tüm bağımlılıklarını, yalnızca bileşenlere aktarmak için presenter'a aktarmak zahmetlidir. Bir de yazılacak kod miktarı... -Mantıksal soru şudur: neden bileşeni basitçe klasik bir servis olarak kaydetmiyor, presenter'a iletmiyor ve sonra `createComponent…()` metodunda döndürmüyoruz? Ancak bu yaklaşım uygunsuzdur, çünkü bileşeni birden çok kez oluşturabilmek istiyoruz. +Mantıklı soru şu: neden bileşeni klasik bir servis olarak kaydedip presenter'a aktarmıyor ve sonra `createComponent…()` metodunda döndürmüyoruz? Ancak bu yaklaşım uygun değildir, çünkü gerektiğinde bileşeni birden fazla kez oluşturabilme olanağını isteriz. -Doğru çözüm, bileşen için bir fabrika yazmaktır, yani bize bileşeni oluşturacak bir sınıf: +Doğru çözüm, bileşen için bir factory, yani bileşeni bizim için oluşturan bir sınıf yazmaktır: ```php class PollControlFactory @@ -359,14 +364,14 @@ class PollControlFactory } ``` -Bu fabrikayı yapılandırmamızda konteynerimize kaydederiz: +Bu factory'yi yapılandırmada konteynerimize kaydederiz: ```neon services: - PollControlFactory ``` -ve son olarak onu presenter'ımızda kullanırız: +ve son olarak presenter'ımızda kullanırız: ```php class PollPresenter extends Nette\Application\UI\Presenter @@ -378,13 +383,13 @@ class PollPresenter extends Nette\Application\UI\Presenter protected function createComponentPollControl(): PollControl { - $pollId = 1; // parametremizi iletebiliriz + $pollId = 1; // kendi parametremizi aktarabiliriz return $this->pollControlFactory->create($pollId); } } ``` -Harika olan şey, Nette DI'nin bu tür basit fabrikaları [oluşturabilmesidir |dependency-injection:factory], bu yüzden tüm kodunu yazmak yerine sadece arayüzünü yazmak yeterlidir: +İşin güzel yanı, Nette DI'nin böyle basit factory'leri [üretebilmesidir |dependency-injection:factory]; yani tüm kodunu yazmak yerine yalnızca arayüzünü yazmanız yeterli: ```php interface PollControlFactory @@ -393,21 +398,21 @@ interface PollControlFactory } ``` -Ve hepsi bu. Nette bu arayüzü dahili olarak uygular ve presenter'a iletir, burada onu zaten kullanabiliriz. Sihirli bir şekilde `$id` parametresini ve `PollFacade` sınıfının bir örneğini bileşenimize ekler. +Ve hepsi bu. Nette bu arayüzü içeride uygular ve onu presenter'a enjekte eder, biz de orada kullanırız. `$id` parametresini ve `PollFacade` sınıfının bir örneğini bileşenimize sihirli biçimde ekler. -Bileşenler Derinlemesine +Bileşenler derinlemesine ======================== -Nette Application'daki bileşenler, web uygulamasının yeniden kullanılabilir parçalarıdır, bunları sayfalara ekleriz ve bu bölümün tamamı onlara ayrılmıştır. Böyle bir bileşenin tam olarak hangi yetenekleri vardır? +Nette Application'daki bileşenler, sayfalara gömdüğümüz ve bu bölümün tamamının ayrıldığı, web uygulamasının yeniden kullanılabilir parçalarıdır. Peki böyle bir bileşenin yetenekleri tam olarak nelerdir? -1) şablonda oluşturulabilir -2) AJAX isteğinde [hangi bölümünün |ajax#Snippet ler] oluşturulacağını bilir (snippet'ler) -3) durumunu URL'de saklama yeteneğine sahiptir (kalıcı parametreler) -4) kullanıcı eylemlerine yanıt verme yeteneğine sahiptir (sinyaller) -5) hiyerarşik bir yapı oluşturur (kökü presenter'dır) +1) Bir şablonda render edilebilir +2) Bir AJAX isteğinde [kendisinin hangi bölümünü |ajax#Snippet'ler] render edeceğini bilir (snippet'ler) +3) Durumunu URL'de saklama yeteneğine sahiptir (kalıcı parametreler) +4) Kullanıcı eylemlerine tepki verme yeteneğine sahiptir (sinyaller) +5) Hiyerarşik bir yapı oluşturur (kökü presenter'dır) -Bu fonksiyonların her biri kalıtım çizgisindeki sınıflardan biri tarafından sağlanır. Oluşturma (1 + 2) [api:Nette\Application\UI\Control] tarafından, [yaşam döngüsüne |presenters#Presenter Yaşam Döngüsü] dahil etme (3, 4) [api:Nette\Application\UI\Component] sınıfı tarafından ve hiyerarşik yapı oluşturma (5) [Container ve Component |component-model:] sınıfları tarafından halledilir. +Bu işlevlerin her birini kalıtım zincirindeki sınıflardan biri üstlenir. Render'ı (1 + 2) [api:Nette\Application\UI\Control], [yaşam döngüsüne |presenters#Presenter'ın yaşam döngüsü] entegrasyonu (3, 4) [api:Nette\Application\UI\Component] sınıfı, hiyerarşik yapının oluşturulmasını (5) ise [Container ve Component |component-model:] sınıfları üstlenir. ``` Nette\ComponentModel\Component { IComponent } @@ -422,18 +427,18 @@ Nette\ComponentModel\Component { IComponent } ``` -Bileşenin Yaşam Döngüsü +Bileşenin yaşam döngüsü ----------------------- [* lifecycle-component.svg *] *** *Bileşenin yaşam döngüsü* .<> -Kalıcı Parametrelerin Doğrulanması +Kalıcı parametrelerin doğrulanması ---------------------------------- -URL'den alınan [kalıcı parametrelerin |#Kalıcı Parametreler] değerleri `loadState()` metodu tarafından özelliklere yazılır. Bu metot ayrıca özellikte belirtilen veri türünün eşleşip eşleşmediğini de kontrol eder, aksi takdirde 404 hatasıyla yanıt verir ve sayfa görüntülenmez. +URL'lerden alınan [#Kalıcı parametreler] değerleri özelliklere `loadState()` metoduyla yazılır. Bu metot ayrıca özellik için belirtilen veri tipinin uyup uymadığını denetler; uymuyorsa 404 hatasıyla yanıt verir ve sayfa gösterilmez. -Kalıcı parametrelere asla körü körüne güvenmeyin, çünkü kullanıcı tarafından URL'de kolayca üzerine yazılabilirler. Örneğin, `$this->page` sayfa numarasının 0'dan büyük olup olmadığını bu şekilde doğrularız. Uygun yol, bahsedilen `loadState()` metodunu geçersiz kılmaktır: +Kalıcı parametrelere asla körü körüne güvenmeyin, çünkü kullanıcı onları URL'de kolayca değiştirebilir. Örneğin sayfa numarası `$this->page`'in 0'dan büyük olup olmadığını böyle denetleriz. Uygun bir yol, sözü geçen `loadState()` metodunu ezmektir: ```php class PaginatingControl extends Control @@ -443,8 +448,8 @@ class PaginatingControl extends Control public function loadState(array $params): void { - parent::loadState($params); // burada $this->page ayarlanır - // ardından kendi değer kontrolümüz gelir: + parent::loadState($params); // $this->page burada ayarlanır + // ardından kendi değer denetimimiz gelir: if ($this->page < 1) { $this->error(); } @@ -452,27 +457,41 @@ class PaginatingControl extends Control } ``` -Ters işlem, yani kalıcı özelliklerden değerlerin toplanması, `saveState()` metodunun sorumluluğundadır. +Tersi süreci, yani değerlerin kalıcı özelliklerden toplanmasını, `saveState()` metodu üstlenir. + + +Presenter'a bağlanma +-------------------- +Bir bileşen presenter hiyerarşisinin parçası olduğu anda, `$onAnchor` dizisinde saklanan callback'leri çağrılır. O andan itibaren bileşenin presenter'ı elinin altındadır, güvenle bağlantı oluşturabilir, kalıcı parametreleri okuyabilir vb. -Sinyaller Derinlemesine +```php +$control->onAnchor[] = function ($control): void { + // bileşenin artık presenter'ı elinin altında +}; +``` + + +Sinyaller derinlemesine ----------------------- -Bir sinyal, sayfanın orijinal istekteki gibi tamamen yeniden yüklenmesine neden olur (AJAX ile çağrıldığı durumlar hariç) ve `signalReceived($signal)` metodunu çağırır; bu metodun `Nette\Application\UI\Component` sınıfındaki varsayılan uygulaması, `handle{signal}` kelimelerinden oluşan bir metodu çağırmaya çalışır. Daha sonraki işlemler ilgili nesneye bağlıdır. `Component`'ten (yani `Control` ve `Presenter`) miras alan nesneler, ilgili parametrelerle `handle{signal}` metodunu çağırmaya çalışarak yanıt verirler. +Bir sinyal, sayfanın tıpkı özgün istekteki gibi yeniden yüklenmesine yol açar (AJAX ile çağrılması dışında) ve varsayılan uygulaması `Nette\Application\UI\Component` sınıfında bulunan `signalReceived($signal)` metodunu çağırır; bu uygulama `handle` sözcüklerinden oluşan bir metodu çağırmayı dener. Sonraki işleme ilgili nesneye kalmıştır. `Component`'ten kalıtım alan nesneler (yani `Control` ve `Presenter`), `handle` metodunu uygun parametrelerle çağırmayı deneyerek tepki verir. + +Başka bir deyişle: `handle` fonksiyonunun tanımı, istekle gelen tüm parametrelerle birlikte alınır, URL'deki parametreler argümanlara ada göre atanır ve metot çağrılmaya çalışılır. Örneğin URL'deki `id` parametresinin değeri `$id` argümanı olarak, URL'deki `something` ise `$something` olarak aktarılır vb. Metot yoksa `signalReceived` metodu bir [istisna |api:Nette\Application\UI\BadSignalException] fırlatır. -Başka bir deyişle: `handle{signal}` fonksiyonunun tanımı alınır ve istekle birlikte gelen tüm parametreler alınır ve argümanlara URL'den parametreler ada göre atanır ve ilgili metot çağrılmaya çalışılır. Örneğin, `$id` parametresi olarak URL'deki `id` parametresinin değeri, `$something` olarak URL'deki `something` vb. iletilir. Ve metot mevcut değilse, `signalReceived` metodu [bir istisna |api:Nette\Application\UI\BadSignalException] atar. +Bir sinyal, URL'deki parametrelerin yanı sıra **isteğin POST gövdesinde** gönderilen parametreleri de okur. Bu işe yarar, çünkü sinyaller sıklıkla JavaScript ile çağrılır ve orada veriyi POST yöntemiyle göndermek doğaldır. Ancak aynı adlı bir parametre hem URL'den hem POST gövdesinden gelirse, **URL'deki değer önceliklidir**. Bu yüzden bir POST alanına URL veya rota parametresiyle aynı adı vermekten kaçının, yoksa URL değeri onu sessizce ezer. Sinyal parametreleri, eylem ve kalıcı parametrelerle ortak bir alanı paylaşır; bkz. [Ortak parametre alanı |presenters#Ortak parametre alanı]. -Sinyal, `SignalReceiver` arayüzünü uygulayan ve bileşen ağacına bağlı olan herhangi bir bileşen, presenter veya nesne tarafından alınabilir. +Bir sinyali, `SignalReceiver` arayüzünü uygulayan ve bileşen ağacına bağlı olan her bileşen, presenter veya nesne alabilir. -Sinyallerin ana alıcıları `Presenter`'lar ve `Control`'dan miras alan görsel bileşenler olacaktır. Sinyal, nesneye bir şey yapması gerektiğine dair bir işaret görevi görmelidir - anket kullanıcının oyunu saymalı, haber bloğu genişlemeli ve iki kat daha fazla haber göstermeli, form gönderildi ve verileri işlemeli vb. +Sinyallerin başlıca alıcıları `Presenter`'lar ve `Control`'den kalıtım alan görsel bileşenler olacaktır. Sinyal, bir nesneye bir şey yapması gerektiğinin işareti olmayı amaçlar: bir anket kullanıcının oyunu saymalı, bir haber bloğu genişleyip iki kat fazla haber göstermeli, bir form gönderildi ve veriyi işlemeli vb. -Sinyal için URL, [Component::link() |api:Nette\Application\UI\Component::link()] metodu kullanılarak oluşturulur. `$destination` parametresi olarak `{signal}!` dizesini ve `$args` olarak sinyale iletmek istediğimiz argümanlar dizisini iletiriz. Sinyal her zaman mevcut presenter ve eylem üzerinde mevcut parametrelerle çağrılır, sinyal parametreleri yalnızca eklenir. Ek olarak, en başta **sinyali belirten `?do` parametresi** eklenir. +Sinyalin URL'si [Component::link() |api:Nette\Application\UI\Component::link()] metoduyla oluşturulur. `$destination` parametresi olarak `{sinyal}!` dizesini, `$args` olarak ise sinyale aktarmak istediğimiz argümanların dizisini veririz. Sinyal her zaman geçerli presenter ve eylem üzerinde, geçerli parametrelerle çağrılır; sinyal parametreleri yalnızca eklenir. Ayrıca **sinyali belirten `?do` parametresi** eklenir. -Formatı ya `{signal}` ya da `{signalReceiver}-{signal}` şeklindedir. `{signalReceiver}`, presenter'daki bileşenin adıdır. Bu nedenle, bileşen adında tire olamaz - bileşen adını ve sinyali ayırmak için kullanılır, ancak bu şekilde birkaç bileşeni iç içe geçirmek mümkündür. +Biçimi ya `{sinyal}` ya da `{sinyalAlicisi}-{sinyal}`'dir. `{sinyalAlicisi}`, presenter'daki bileşenin adıdır. Bu yüzden bileşen adında kısa çizgi kullanılamaz; o, bileşen adını ve sinyali ayırmaya yarar, ama bu yolla birden fazla bileşeni iç içe geçirmek mümkündür. -[isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] metodu, bileşenin (ilk argüman) sinyalin (ikinci argüman) alıcısı olup olmadığını doğrular. İkinci argümanı atlayabiliriz - o zaman bileşenin herhangi bir sinyalin alıcısı olup olmadığını kontrol eder. İkinci parametre olarak `true` belirtilebilir ve böylece yalnızca belirtilen bileşenin değil, aynı zamanda herhangi bir alt öğesinin de alıcı olup olmadığını doğrular. +[isSignalReceiver()|api:Nette\Application\UI\Presenter::isSignalReceiver()] metodu, bileşenin (ilk argüman) sinyalin (ikinci argüman) alıcısı olup olmadığını denetler. İkinci argüman atlanabilir; o zaman bileşenin herhangi bir sinyalin alıcısı olup olmadığı denetlenir. İkinci parametre `true` yapılırsa, belirtilen bileşenin veya torunlarından herhangi birinin alıcı olup olmadığı doğrulanır. -`handle{signal}`'den önceki herhangi bir aşamada, sinyali manuel olarak [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] metodunu çağırarak yürütebiliriz; bu metot sinyalin işlenmesini üstlenir - sinyalin alıcısı olarak belirlenen bileşeni alır (sinyal alıcısı belirtilmemişse, presenter'ın kendisidir) ve ona sinyali gönderir. +`handle`'den önceki herhangi bir aşamada, sinyali [processSignal()|api:Nette\Application\UI\Presenter::processSignal()] metodunu çağırarak elle çalıştırabiliriz; bu metot sinyalin işlenmesini üstlenir: sinyalin alıcısı olarak belirlenen bileşeni alır (alıcı belirtilmemişse presenter'ın kendisidir) ve sinyali ona gönderir. Örnek: @@ -482,4 +501,4 @@ if ($this->isSignalReceiver($this, 'paging') || $this->isSignalReceiver($this, ' } ``` -Böylece sinyal erken yürütülür ve tekrar çağrılmaz. +Bu, sinyali erkenden çalıştırır ve sinyal bir daha çağrılmaz. diff --git a/application/tr/configuration.texy b/application/tr/configuration.texy index c933edfdd1..31ccd0b872 100644 --- a/application/tr/configuration.texy +++ b/application/tr/configuration.texy @@ -1,8 +1,8 @@ -Uygulama Yapılandırması +Uygulama yapılandırması *********************** .[perex] -Nette Uygulamaları için yapılandırma seçeneklerine genel bakış. +Nette Application'ın yapılandırma seçeneklerine genel bakış. Application @@ -10,62 +10,64 @@ Application ```neon application: - # Tracy BlueScreen'de "Nette Application" panelini göster? - debugger: ... # (bool) varsayılan true + # Tracy BlueScreen'de "Nette Application" paneli gösterilsin mi? + debugger: ... # (bool) Tracy varsa etkin - # hata durumunda error-presenter çağrılacak mı? - # yalnızca geliştirme modunda etkilidir - catchExceptions: ... # (bool) varsayılan true + # üretimde istisnaları her zaman error-presenter işler; + # bu seçenek yalnızca aynı davranışı geliştirme modunda da açar + catchExceptions: ... # (bool) varsayılan false - yani geliştirmede kapalı, üretimde her zaman açık - # error-presenter adı + # error-presenter'ın adı errorPresenter: Error # (string|array) varsayılan 'Nette:Error' - # presenter'lar ve eylemler için takma adları tanımlar + # presenter'lar ve eylemler için takma adlar tanımlar aliases: ... - # presenter adını sınıfa çevirme kurallarını tanımlar + # presenter adının sınıfa çevrilme kurallarını tanımlar mapping: ... - # hatalı bağlantılar uyarı oluşturmaz mı? + # geçersiz bağlantı uyarıları bastırılsın mı? # yalnızca geliştirme modunda etkilidir silentLinks: ... # (bool) varsayılan false ``` -`nette/application` sürüm 3.2'den itibaren bir çift error-presenter tanımlanabilir: +`nette/application` 3.2 sürümünden itibaren bir çift error presenter tanımlanabilir: ```neon application: errorPresenter: - 4xx: Error4xx # Nette\Application\BadRequestException istisnası için + 4xx: Error4xx # Nette\Application\BadRequestException için 5xx: Error5xx # diğer istisnalar için ``` -`silentLinks` seçeneği, Nette'nin geliştirme modunda bir bağlantı oluşturma başarısız olduğunda (örneğin, presenter mevcut olmadığı için vb.) nasıl davranacağını belirler. Varsayılan `false` değeri, Nette'nin bir `E_USER_WARNING` hatası atacağı anlamına gelir. `true` olarak ayarlamak bu hata mesajını bastırır. Üretim ortamında `E_USER_WARNING` her zaman tetiklenir. Bu davranışı, presenter değişkeni [$invalidLinkMode |creating-links#Geçersiz Bağlantılar] ayarlayarak da etkileyebiliriz. +Bunları ayırmak yararlıdır, çünkü iki durum temelden farklıdır. `BadRequestException` (4xx kodları), uygulamanın sorunsuz olduğu ve yalnızca ziyaretçinin var olmayan bir şey istediği anlamına gelir. Bu yüzden sitenizin layout'unda dostça bir mesaj gösteren tam donanımlı bir presenter kullanabilirsiniz. Buna karşılık 5xx hatası, uygulamada bir şeyin bozulduğu ve nedenini bilmediğiniz anlamına gelir. 5xx presenter'ını olabildiğince yalın tutun ki render edilirken başka bir şey bozulmasın; ideal olarak veritabanına, layout'a veya oturum açmış kullanıcıya dokunmamalıdır. + +`silentLinks` seçeneği, bağlantı üretimi başarısız olduğunda (örneğin presenter var olmadığı için) Nette'in geliştirme modunda nasıl davranacağını belirler. Varsayılan `false` değeri, Nette'in `E_USER_WARNING` hatası tetiklemesi demektir. `true` yapmak bu hata mesajını bastırır. Üretim ortamında `E_USER_WARNING` her zaman tetiklenir. Bu davranış, presenter'ın [$invalidLinkMode |creating-links#Geçersiz bağlantılar] değişkeni ayarlanarak da etkilenebilir. -[Takma adlar, sık kullanılan presenter'lara bağlantı vermeyi basitleştirir |creating-links#Takma Adlar Alias]. +[Takma adlar, sık kullanılan |creating-links#Takma adlar] presenter'lara başvurmayı kolaylaştırır. -[Eşleme, presenter adından sınıf adının nasıl türetileceğine ilişkin kuralları tanımlar |directory-structure#Presenter Eşlemesi]. +[Mapping, sınıf adının |directory-structure#Presenter mapping] presenter adından türetilme kurallarını tanımlar. -Presenter'ların Otomatik Kaydı +Presenter'ların otomatik kaydı ------------------------------ -Nette, presenter'ları otomatik olarak DI konteynerine servis olarak ekler, bu da oluşturulmalarını önemli ölçüde hızlandırır. Nette'nin presenter'ları nasıl bulduğu yapılandırılabilir: +Nette, presenter'ları DI konteynerine servis olarak otomatik ekler, bu da oluşturulmalarını belirgin biçimde hızlandırır. Nette'in presenter'ları nasıl bulduğu yapılandırılabilir: ```neon application: - # Composer sınıf haritasında presenter'ları ara? + # presenter'lar Composer sınıf haritasında aransın mı? scanComposer: ... # (bool) varsayılan true # sınıf ve dosya adının uyması gereken maske scanFilter: ... # (string) varsayılan '*Presenter' - # presenter'lar hangi dizinlerde aranacak? + # presenter'lar hangi dizinlerde aransın? scanDirs: # (string[]|false) varsayılan '%appDir%' - %vendorDir%/mymodule ``` -`scanDirs` içinde belirtilen dizinler, varsayılan `%appDir%` değerinin üzerine yazmaz, ancak onu tamamlar, bu nedenle `scanDirs` hem `%appDir%` hem de `%vendorDir%/mymodule` yollarını içerecektir. Varsayılan dizini atlamak istiyorsak, değeri üzerine yazan [ünlem işaretini |dependency-injection:configuration#Birleştirme] kullanırız: +`scanDirs` içinde listelenen dizinler varsayılan `%appDir%` değerini değiştirmez, ona eklenir; yani `scanDirs` hem `%appDir%` hem `%vendorDir%/mymodule` yollarını içerir. Varsayılan dizini atlamak istersek [ünlem işareti |dependency-injection:configuration#Birleştirme] kullanırız: ```neon application: @@ -73,36 +75,42 @@ application: - %vendorDir%/mymodule ``` -Dizin taraması, false değeri belirtilerek kapatılabilir. Presenter'ların otomatik olarak eklenmesini tamamen bastırmanızı önermiyoruz, çünkü aksi takdirde uygulama performansı düşer. +Dizin taraması, değeri `false` yaparak kapatılabilir. Presenter'lar o zaman servis olarak kaydedilmez, dolayısıyla [decorator |dependency-injection:configuration#Decorator] bölümünden ayarlanamazlar ve oluşturulmaları yavaşlar. Bu yüzden otomatik kaydı tamamen bastırmayı önermiyoruz, çünkü uygulamanın performansını düşürür. -Latte Şablonları +Latte şablonları ================ -Bu ayar, Latte'nin bileşenlerdeki ve presenter'lardaki davranışını genel olarak etkilemenizi sağlar. +Bu ayar, Latte'nin bileşenlerdeki ve presenter'lardaki davranışını genel olarak etkiler. ```neon latte: - # Ana şablon için (true) veya tüm bileşenler için (all) Tracy Bar'da Latte panelini göster? - debugger: ... # (true|false|'all') varsayılan true + # Tracy Bar'da Latte paneli ana şablon için (true) mi, tüm bileşenler için (all) mi gösterilsin? + debugger: ... # (true|false|'all') Tracy varsa etkin (yalnızca hata ayıklama modunda) - # declare(strict_types=1) başlığıyla şablonlar oluşturur + # şablonları declare(strict_types=1) başlığıyla üret strictTypes: ... # (bool) varsayılan false - # [katı ayrıştırıcı |latte:develop#striktní režim] modunu açar + # [katı ayrıştırıcı modunu |latte:develop#strict mode] etkinleştir strictParsing: ... # (bool) varsayılan false - # [oluşturulan kodun kontrolünü |latte:develop#Kontrola vygenerovaného kódu] etkinleştirir + # değişkenlerin kapsamını döngü gövdesiyle sınırlar + scopedLoopVariables: ... # (bool) varsayılan false + + # çift etiketlerdeki iç içelikten kaynaklanan girintiyi kaldırır + dedent: ... # (bool) varsayılan false + + # [üretilen kodun denetimini |latte:develop#Checking Generated Code] etkinleştir phpLinter: ... # (string) varsayılan null - # yerel ayarı ayarlar + # yerel ayarı belirle locale: cs_CZ # (string) varsayılan null # $this->template nesnesinin sınıfı templateClass: App\MyTemplateClass # varsayılan Nette\Bridges\ApplicationLatte\DefaultTemplate ``` -Latte sürüm 3 kullanıyorsanız, yeni [uzantıları |latte:extending-latte#Latte Extension] şu şekilde ekleyebilirsiniz: +Yeni [uzantıları |latte:extending-latte#Latte Extension] şöyle ekleyebilirsiniz: ```neon latte: @@ -110,20 +118,6 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Latte sürüm 2 kullanıyorsanız, yeni etiketleri sınıf adını belirterek veya bir servise referans vererek kaydedebilirsiniz. Varsayılan olarak `install()` metodu çağrılır, ancak bu, başka bir metodun adını belirterek değiştirilebilir: - -```neon -latte: - # özel Latte etiketlerini kaydet - macros: - - App\MyLatteMacros::register # statik metot, sınıf adı veya çağrılabilir - - @App\MyLatteMacrosFactory # install() metoduna sahip servis - - @App\MyLatteMacrosFactory::register # register() metoduna sahip servis - -services: - - App\MyLatteMacrosFactory -``` - Yönlendirme =========== @@ -132,14 +126,14 @@ Temel ayarlar: ```neon routing: - # Tracy Bar'da yönlendirme panelini göster? - debugger: ... # (bool) varsayılan true + # Tracy Bar'da yönlendirme paneli gösterilsin mi? + debugger: ... # (bool) Tracy varsa etkin (yalnızca hata ayıklama modunda) - # yönlendiriciyi DI konteynerine serileştirir + # router'ı DI konteynerine serileştir cache: ... # (bool) varsayılan false ``` -Yönlendirme genellikle [RouterFactory |routing#Rota Koleksiyonu] sınıfında tanımlanır. Alternatif olarak, rotalar yapılandırmada `maske: eylem` çiftleri kullanılarak da tanımlanabilir, ancak bu yöntem ayar konusunda o kadar geniş bir çeşitlilik sunmaz: +Yönlendirme genellikle [RouterFactory |routing#Rota koleksiyonu] sınıfında tanımlanır. Alternatif olarak rotalar yapılandırmada `maske: eylem` çiftleriyle de tanımlanabilir, ama bu yöntem pek esneklik sunmaz: ```neon routing: @@ -152,23 +146,23 @@ routing: Sabitler ======== -PHP sabitleri oluşturma. +PHP sabitlerinin oluşturulması. ```neon constants: Foobar: 'baz' ``` -Uygulama başlatıldıktan sonra `Foobar` sabiti oluşturulacaktır. +`Foobar` sabiti uygulama başladıktan sonra oluşturulur. .[note] -Sabitler, genel olarak erişilebilir değişkenler gibi kullanılmamalıdır. Nesnelere değer iletmek için [bağımlılık enjeksiyonunu |dependency-injection:passing-dependencies] kullanın. +Sabitler genel olarak erişilebilir değişkenler olarak kullanılmamalıdır. Nesnelere değer aktarmak için [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] kullanın. PHP === -PHP yönergelerinin ayarlanması. Tüm yönergelerin bir listesini [php.net |https://www.php.net/manual/en/ini.list.php] adresinde bulabilirsiniz. +PHP direktiflerinin ayarlanması. Tüm direktiflere genel bakışı [php.net |https://www.php.net/manual/en/ini.list.php] adresinde bulabilirsiniz. ```neon php: @@ -176,16 +170,17 @@ php: ``` -DI Servisleri +DI servisleri ============= Bu servisler DI konteynerine eklenir: -| Ad | Tip | Açıklama -|---------------------------------------------------------- -| `application.application` | [api:Nette\Application\Application] | [tüm uygulamanın başlatıcısı |how-it-works#Nette Application] -| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] -| `application.presenterFactory` | [api:Nette\Application\PresenterFactory] | presenter'lar için fabrika -| `application.###` | [api:Nette\Application\UI\Presenter] | bireysel presenter'lar -| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | `Latte\Engine` nesnesi için fabrika -| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | [`$this->template` |templates] için fabrika +| Ad | Tip | Açıklama +|----------------------------|---------------------------------------------------|----------------------------------------- +| `application.application` | [api:Nette\Application\Application] | [uygulama çalıştırıcısı |how-it-works#Nette Application] +| `application.linkGenerator` | [api:Nette\Application\LinkGenerator] | [LinkGenerator |creating-links#LinkGenerator] +| `application.presenterFactory` | [api:Nette\Application\IPresenterFactory] | presenter factory +| `application.###` | [api:Nette\Application\UI\Presenter] | tek tek presenter'lar +| `routing.router` | [api:Nette\Routing\Router] | router +| `latte.latteFactory` | [api:Nette\Bridges\ApplicationLatte\LatteFactory] | `Latte\Engine` nesnesi için factory +| `latte.templateFactory` | [api:Nette\Application\UI\TemplateFactory] | [`$this->template` |templates] için factory diff --git a/application/tr/creating-links.texy b/application/tr/creating-links.texy index ff2a3d3020..ca11c0a42b 100644 --- a/application/tr/creating-links.texy +++ b/application/tr/creating-links.texy @@ -1,146 +1,163 @@ -URL Bağlantıları Oluşturma +URL bağlantıları oluşturma **************************
    -Nette'de bağlantı oluşturmak parmakla göstermek kadar kolaydır. Sadece işaret etmeniz yeterlidir ve framework tüm işi sizin için yapar. Şunları göstereceğiz: +Nette'te bağlantı oluşturmak parmakla göstermek kadar basittir. Yalnızca nişan alın, gerisini framework sizin için yapar. Şunları göstereceğiz: -- şablonlarda ve başka yerlerde bağlantılar nasıl oluşturulur -- mevcut sayfaya bir bağlantı nasıl ayırt edilir -- geçersiz bağlantılarla ne yapılmalı +- şablonlarda ve başka yerlerde nasıl bağlantı oluşturulur +- geçerli sayfaya giden bağlantı nasıl ayırt edilir +- geçersiz bağlantılarla ne yapılır
    -[Çift yönlü yönlendirme |routing] sayesinde, şablonlarınıza veya kodunuza daha sonra değişebilecek veya karmaşık bir şekilde birleştirilmesi gereken uygulamanızın URL adreslerini asla sabit kodlamanız gerekmeyecektir. Bağlantıda presenter'ı ve eylemi belirtmeniz, olası parametreleri iletmeniz yeterlidir ve framework URL'yi kendisi oluşturacaktır. Aslında, bir fonksiyonu çağırmaya çok benzer. Bunu seveceksiniz. +[Çift yönlü yönlendirme |routing] sayesinde, uygulamanızın daha sonra değişebilecek veya birleştirmesi karmaşık olabilecek URL'lerini şablonlara ya da koda hiçbir zaman sabit yazmanız gerekmez. Bağlantıda yalnızca presenter'ı ve eylemi belirtin, gerekli parametreleri verin; URL'yi framework kendisi üretecektir. Aslında bir fonksiyon çağırmaya çok benzer. Bunu seveceksiniz. Presenter şablonunda ==================== -En sık olarak şablonlarda bağlantılar oluştururuz ve `n:href` niteliği harika bir yardımcıdır: +Bağlantıları çoğu zaman şablonlarda oluştururuz ve `n:href` niteliği harika bir yardımcıdır: ```latte detay ``` -HTML niteliği `href` yerine [n:niteliği |latte:syntax#n:nitelikler] `n:href` kullandığımıza dikkat edin. Değeri, `href` niteliğinde olduğu gibi bir URL değil, presenter'ın ve eylemin adıdır. +`href` HTML niteliği yerine [n:niteliği |latte:syntax#n:nitelikleri] `n:href` kullandığımıza dikkat edin. Değeri, `href` niteliğinde olacağı gibi bir URL değil, presenter'ın ve eylemin adıdır. -Bir bağlantıya tıklamak, basitleştirilmiş bir ifadeyle, `ProductPresenter::renderShow()` metodunu çağırmak gibidir. Ve eğer imzasında parametreler varsa, onu argümanlarla çağırabiliriz: +Bir bağlantıya tıklamak, basitçe söylersek, `ProductPresenter::renderShow()` metodunu çağırmak gibi bir şeydir. İmzasında parametreler varsa onu argümanlarla çağırabiliriz: ```latte ürün detayı ``` -Adlandırılmış parametreleri iletmek de mümkündür. Aşağıdaki bağlantı, `lang` parametresini `cs` değeriyle iletir: +Adlandırılmış parametreler de aktarılabilir. Aşağıdaki bağlantı, `lang` parametresini `en` değeriyle aktarır: ```latte -ürün detayı +ürün detayı ``` -Eğer `ProductPresenter::renderShow()` metodu imzasında `$lang` içermiyorsa, parametrenin değerini `$lang = $this->getParameter('lang')` kullanarak veya [özellikten |presenters#İstek Parametreleri] öğrenebilir. +`ProductPresenter::renderShow()` metodunun imzasında `$lang` yoksa, parametrenin değerini `$lang = $this->getParameter('lang')` ile veya bir [özellikten |presenters#İsteğin parametreleri] alabilir. -Parametreler bir dizide saklanıyorsa, `...` operatörü (Latte 2.x'te `(expand)` operatörü) ile genişletilebilirler: +Parametreler bir dizide saklanıyorsa, `...` operatörüyle açılabilirler: ```latte -{var $args = [$product->id, lang => cs]} -ürün detayı +{var $args = [$product->id, lang => en]} +ürün detayı ``` -Bağlantılarda sözde [kalıcı parametreler |presenters#Kalıcı Parametreler] de otomatik olarak iletilir. +[Kalıcı parametreler |presenters#Kalıcı parametreler] denilen parametreler de bağlantılarda otomatik olarak aktarılır. -`n:href` niteliği HTML `` etiketleri için çok kullanışlıdır. Bağlantıyı başka bir yerde, örneğin metinde yazdırmak istiyorsak, `{link}` kullanırız: +`n:href` niteliği HTML `` etiketleri için çok kullanışlıdır. Bağlantıyı başka bir yerde, örneğin metin içinde yazdırmak istersek `{link}` kullanırız: ```latte -Adres: {link Home:default} +URL şudur: {link Home:default} ``` Kodda ===== -Presenter'da bir bağlantı oluşturmak için `link()` metodu kullanılır: +Presenter'da bağlantı oluşturmak için `link()` metodu kullanılır: ```php $url = $this->link('Product:show', $product->id); ``` -Parametreler, adlandırılmış parametrelerin de belirtilebildiği bir dizi kullanılarak da iletilebilir: +Parametreler dizi olarak da aktarılabilir; burada adlandırılmış parametreler de belirtilebilir: ```php -$url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); +$url = $this->link('Product:show', [$product->id, 'lang' => 'en']); ``` -Bağlantılar presenter olmadan da oluşturulabilir, bunun için [#LinkGenerator] ve onun `link()` metodu vardır. +Bağlantılar presenter olmadan da, [#LinkGenerator] ve onun `link()` metoduyla oluşturulabilir. +Bazen bir bağlantıyı şimdi oluşturmanız, ama asıl URL'yi ancak daha sonra üretmeniz gerekir. Bunun için bir `Nette\Application\UI\Link` nesnesi döndüren `lazyLink()` metodu vardır. Avantajı, bu nesneyi örneğin bir şablona aktarabilmeniz ve render edilmeden önce parametrelerini `setParameter()` metoduyla hâlâ ayarlayabilmenizdir. URL'nin kendisi ancak nesne bir dizeye dönüştürüldüğünde birleştirilir: + +```php +$link = $this->lazyLink('Product:show', $id); +// ... +echo $link; // URL ancak burada üretilir +``` -Presenter'a Bağlantılar -======================= -Bağlantının hedefi bir presenter ve eylem ise, şu sözdizimine sahiptir: +Presenter'lara bağlantılar +========================== + +Bağlantının hedefi bir presenter ve eylem ise, sözdizimi şöyledir: ``` -[//] [[[[:]module:]presenter:]action | this] [#fragment] +[//] [[[[:]modül:]presenter:]eylem | this] [#fragman] ``` -Format, tüm Latte etiketleri ve bağlantılarla çalışan tüm presenter metotları tarafından desteklenir, yani `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` ve ayrıca [#LinkGenerator]. Dolayısıyla, örneklerde `n:href` kullanılmış olsa bile, fonksiyonlardan herhangi biri orada olabilirdi. +Bu formatı tüm Latte etiketleri ve bağlantılarla çalışan tüm presenter metotları destekler, yani `n:href`, `{link}`, `{plink}`, `link()`, `lazyLink()`, `isLinkCurrent()`, `redirect()`, `redirectPermanent()`, `forward()`, `canonicalize()` ve ayrıca [#LinkGenerator]. Yani örneklerde `n:href` kullanılsa da, orada bu fonksiyonlardan herhangi biri olabilirdi. -Temel form bu nedenle `Presenter:action` şeklindedir: +Temel biçim dolayısıyla `Presenter:eylem`'dir: ```latte ana sayfa ``` -Mevcut presenter'ın bir eylemine bağlantı veriyorsak, adını atlayabiliriz: +Geçerli presenter'ın bir eylemine bağlantı veriyorsak adını atlayabiliriz: ```latte ana sayfa ``` -Hedef eylem `default` ise, onu atlayabiliriz, ancak iki nokta üst üste kalmalıdır: +Hedef eylem `default` ise onu atlayabiliriz, ama iki nokta üst üste kalmalıdır: ```latte ana sayfa ``` -Bağlantılar ayrıca diğer [modüllere |directory-structure#Presenter lar ve Şablonlar] de yönlendirebilir. Burada bağlantılar, iç içe geçmiş alt modüle göreceli veya mutlak olarak ayırt edilir. Prensip, diskteki yollara benzer, yalnızca eğik çizgiler yerine iki nokta üst üste kullanılır. Mevcut presenter'ın `Front` modülünün bir parçası olduğunu varsayalım, o zaman şunu yazarız: +Bağlantılar başka [modüllere |directory-structure#Presenter'lar ve şablonlar] de gidebilir. Burada bağlantılar, iç içe bir alt modüle göreli veya mutlak olmak üzere ayrılır. İlke, disk yollarına benzer, yalnızca eğik çizgi yerine iki nokta üst üste kullanılır. Geçerli presenter'ın `Front` modülünün parçası olduğunu varsayarsak şöyle yazarız: ```latte Front:Shop:Product:show bağlantısı Admin:Product:show bağlantısı ``` -Özel bir durum, hedef olarak `this` belirttiğimiz [kendine bağlantıdır |#Mevcut Sayfaya Bağlantı]. +Özel bir durum, hedef olarak `this` belirttiğimiz [kendine giden |#Geçerli sayfaya bağlantı] bağlantıdır. ```latte yenile ``` -Izgara işareti `#` sonrasındaki bir parça (fragment) aracılığıyla sayfanın belirli bir bölümüne bağlantı verebiliriz: +Diyez işareti `#` ardından gelen fragman ile sayfanın belirli bir bölümüne bağlantı verebiliriz: ```latte -Home:default ve #main parçasına bağlantı +Home:default'a ve #main fragmanına bağlantı ``` +.{data-version:3.3.0} +Fragman, `#` anahtarıyla argüman olarak dinamik de belirlenebilir. Değeri otomatik kodlanır ve hedefte belirtilen fragmandan önceliklidir: -Mutlak Yollar +```php +$this->link('Home:default', ['#' => $fragment]); +``` + + +Mutlak yollar ============= -`link()` veya `n:href` kullanılarak oluşturulan bağlantılar her zaman mutlak yollardır (yani `/` karakteriyle başlarlar), ancak `https://domain` gibi protokol ve alan adı içeren mutlak URL'ler değildir. +`link()` veya `n:href` ile üretilen bağlantılar her zaman mutlak yollardır (yani `/` ile başlarlar), ama `https://domain` gibi protokol ve alan adı içeren mutlak URL'ler değildir. + +Mutlak bir URL üretmek için başına iki eğik çizgi ekleyin (örneğin `n:href="//Home:"`). Alternatif olarak, `$this->absoluteUrls = true` ayarlayarak presenter'ı yalnızca mutlak bağlantı üretmeye geçirebilirsiniz. -Mutlak bir URL oluşturmak için başına iki eğik çizgi ekleyin (örn. `n:href="//Home:"`). Veya presenter'ı yalnızca mutlak bağlantılar oluşturacak şekilde `$this->absoluteUrls = true` ayarlayarak değiştirebilirsiniz. +Şablonda göreli bir yolu mutlak yola dönüştürmek için `|absoluteUrl` filtresi de kullanılabilir. -Mevcut Sayfaya Bağlantı -======================= +Geçerli sayfaya bağlantı +======================== -`this` hedefi mevcut sayfaya bir bağlantı oluşturur: +`this` hedefi, geçerli sayfaya bir bağlantı oluşturur: ```latte yenile ``` -Aynı zamanda, `action()` veya `render()` metotlarının imzasında belirtilen tüm parametreler de, `action()` tanımlanmamışsa iletilir. Dolayısıyla, `Product:show` sayfasındaysak ve `id: 123` ise, `this` bağlantısı bu parametreyi de iletecektir. +Bu sırada `action()` veya (`action()` tanımlı değilse) `render()` metodunun imzasında belirtilen tüm parametreler aktarılır. Yani `id: 123` ile `Product:show` sayfasındaysak, `this` bağlantısı bu parametreyi de aktarır. Elbette parametreleri doğrudan belirtmek de mümkündür: @@ -148,13 +165,13 @@ Elbette parametreleri doğrudan belirtmek de mümkündür: yenile ``` -`isLinkCurrent()` fonksiyonu, bağlantı hedefinin mevcut sayfayla aynı olup olmadığını kontrol eder. Bu, örneğin şablonda bağlantıları ayırt etmek vb. için kullanılabilir. +`isLinkCurrent()` fonksiyonu, bağlantının hedefinin geçerli sayfayla aynı olup olmadığını denetler. Bu, örneğin şablonda bağlantıları ayırt etmek için kullanılabilir. -Parametreler `link()` metoduyla aynıdır, ancak ek olarak belirli bir eylem yerine, söz konusu presenter'ın herhangi bir eylemi anlamına gelen `*` joker karakterini belirtmek mümkündür. +Parametreleri `link()` metodununkiyle aynıdır, ama somut bir eylem yerine, o presenter'ın herhangi bir eylemi anlamına gelen `*` joker karakteri de kullanılabilir. ```latte {if !isLinkCurrent('Admin:login')} - Giriş Yap + Giriş {/if}
  • @@ -162,15 +179,15 @@ Parametreler `link()` metoduyla aynıdır, ancak ek olarak belirli bir eylem yer
  • ``` -Tek bir öğede `n:href` ile birlikte kullanıldığında, kısaltılmış bir form kullanılabilir: +Tek bir elemanda `n:href` ile birlikte kısaltılmış bir biçim kullanılabilir: ```latte ... ``` -`*` joker karakteri yalnızca eylem yerine kullanılabilir, presenter yerine kullanılamaz. +`*` joker karakteri yalnızca eylemin yerine kullanılabilir, presenter'ın yerine değil. -Belirli bir modülde veya alt modülünde olup olmadığımızı kontrol etmek için `isModuleCurrent(moduleName)` metodunu kullanırız. +Belirli bir modülde veya onun alt modülünde olup olmadığımızı belirlemek için `isModuleCurrent(modulAdi)` metodunu kullanın. ```latte
  • @@ -179,56 +196,72 @@ Belirli bir modülde veya alt modülünde olup olmadığımızı kontrol etmek i ``` -Sinyale Bağlantılar -=================== +Bağlantı tabanını değiştirme .{data-version:3.2.7} +================================================== + +Varsayılan olarak göreli bağlantılar geçerli presenter'dan türetilir. Bu, `{linkBase}` ile değiştirilebilir: + +```latte +{linkBase Admin:Dashboard} +ürün detayı +``` + +Bağlantı `Admin:Dashboard:Product:show`'a gidecektir. Yalnızca göreli bağlantılar etkilenir; iki nokta üst üste ile başlayan mutlak bağlantılar ve geçerli presenter'a giden bağlantılar (`this`, `show`) değişmeden kalır. + +`{linkBase}` tüm şablon için geçerlidir ve özellikle layout şablonlarında kullanışlıdır; orada çağıran presenter'dan bağımsız olarak tutarlı bağlantılar sağlar. +Etiket şablonun başına konmalıdır, aksi halde `CompileException` fırlatır. + + +Sinyallere bağlantılar +====================== -Bağlantının hedefi yalnızca bir presenter ve eylem olmak zorunda değildir, aynı zamanda bir [sinyal |components#Sinyal] de olabilir (`handle()` metodunu çağırırlar). O zaman sözdizimi aşağıdaki gibidir: +Bağlantının hedefi yalnızca bir presenter ve eylem olmak zorunda değildir; bir [sinyal |components#Sinyal] de olabilir (`handle()` metodunu çağırır). O zaman sözdizimi şöyledir: ``` -[//] [sub-component:]signal! [#fragment] +[//] [alt-bileşen:]sinyal! [#fragman] ``` -Sinyal bu nedenle bir ünlem işaretiyle ayırt edilir: +Sinyal, ünlem işaretiyle ayırt edilir: ```latte sinyal ``` -Bir alt bileşenin (veya alt-alt bileşenin) sinyaline bir bağlantı oluşturmak da mümkündür: +Bir alt bileşenin (veya alt alt bileşenin) sinyaline de bağlantı oluşturabilirsiniz: ```latte sinyal ``` -Bileşendeki Bağlantılar -======================= +Bileşende bağlantılar +===================== -[Bileşenler|components] bağımsız, yeniden kullanılabilir birimler olduğundan ve çevreleyen presenter'larla herhangi bir bağlantısı olmaması gerektiğinden, bağlantılar burada biraz farklı çalışır. Latte niteliği `n:href` ve `{link}` etiketi ile `link()` gibi bileşen metotları ve diğerleri, bağlantı hedefini **her zaman sinyal adı olarak** kabul eder. Bu nedenle ünlem işareti belirtmek bile gerekli değildir: +[Bileşenler|components] çevredeki presenter'larla hiçbir bağı olmaması gereken ayrı, yeniden kullanılabilir birimler olduğundan, burada bağlantılar biraz farklı çalışır. Latte niteliği `n:href` ve `{link}` etiketi, ayrıca `link()` gibi bileşen metotları **bağlantı hedefini her zaman sinyal adı olarak kabul eder**. Bu yüzden ünlem işareti eklemek bile gerekmez: ```latte sinyal, eylem değil ``` -Bileşen şablonunda presenter'lara bağlantı vermek isteseydik, bunun için `{plink}` etiketini kullanırdık: +Bileşen şablonunda presenter'lara bağlantı vermek istersek `{plink}` etiketini kullanırız: ```latte -giriş +ana sayfa ``` -veya kodda +ya da kodda ```php $this->getPresenter()->link('Home:default') ``` -Takma Adlar (Alias) .{data-version:v3.2.2} -========================================== +Takma adlar .{data-version:3.2.3} +================================= -Bazen Presenter:eylem çiftine kolayca hatırlanabilir bir takma ad atamak yararlı olabilir. Örneğin, `Front:Home:default` giriş sayfasını basitçe `home` olarak veya `Admin:Dashboard:default` sayfasını `admin` olarak adlandırmak. +Bazen bir Presenter:eylem çiftine kolay akılda kalan bir takma ad vermek yararlı olabilir. Örneğin `Front:Home:default` ana sayfasını basitçe `home`, `Admin:Dashboard:default`'u ise `admin` diye adlandırmak. -Takma adlar, [yapılandırmada|configuration] `application › aliases` anahtarı altında tanımlanır: +Takma adlar [yapılandırmada|configuration] `application › aliases` anahtarı altında tanımlanır: ```neon application: @@ -238,7 +271,7 @@ application: sign: Front:Sign:in ``` -Bağlantılarda, örneğin bir at işareti kullanılarak yazılırlar: +Bağlantılarda ise et işaretiyle yazılırlar, örneğin: ```latte yönetim @@ -247,17 +280,17 @@ Bağlantılarda, örneğin bir at işareti kullanılarak yazılırlar: `redirect()` ve benzeri gibi bağlantılarla çalışan tüm metotlarda da desteklenirler. -Geçersiz Bağlantılar +Geçersiz bağlantılar ==================== -Geçersiz bir bağlantı oluşturmamız olabilir - ya var olmayan bir presenter'a yönlendirdiği için, ya hedef metodun imzasında kabul ettiğinden daha fazla parametre ilettiği için ya da hedef eylem için bir URL oluşturulamadığı için. Geçersiz bağlantılarla nasıl başa çıkılacağını statik değişken `Presenter::$invalidLinkMode` belirler. Bu, şu değerlerin bir kombinasyonunu alabilir (sabitler): +Geçersiz bir bağlantı oluşturmamız olabilir; ya var olmayan bir presenter'a gittiği için, ya hedef metodun imzasında kabul ettiğinden fazla parametre aktardığı için, ya da hedef eylem için URL üretilemediği için. Geçersiz bağlantılarla nasıl başa çıkılacağı presenter'da `$this->invalidLinkMode` ile ayarlanır. Şu değerlerin (sabitlerin) bir bileşimini alabilir: -- `Presenter::InvalidLinkSilent` - sessiz mod, URL olarak # karakteri döndürülür -- `Presenter::InvalidLinkWarning` - E_USER_WARNING uyarısı atılır, bu üretim modunda günlüğe kaydedilir, ancak betiğin çalışmasını kesintiye uğratmaz -- `Presenter::InvalidLinkTextual` - görsel uyarı, hatayı doğrudan bağlantıya yazar -- `Presenter::InvalidLinkException` - InvalidLinkException istisnası atılır +- `Presenter::InvalidLinkSilent` - sessiz mod, URL olarak # karakterini döndürür +- `Presenter::InvalidLinkWarning` - bir E_USER_WARNING uyarısı fırlatılır; üretim modunda günlüğe yazılır, ama betiğin çalışmasını kesmez +- `Presenter::InvalidLinkTextual` - görsel uyarı, hatayı doğrudan bağlantının içine yazdırır +- `Presenter::InvalidLinkException` - InvalidLinkException fırlatır -Varsayılan ayar, üretim modunda `InvalidLinkWarning` ve geliştirme modunda `InvalidLinkWarning | InvalidLinkTextual` şeklindedir. Üretim ortamındaki `InvalidLinkWarning`, betiğin kesintiye uğramasına neden olmaz, ancak uyarı günlüğe kaydedilir. Geliştirme ortamında, [Tracy |tracy:] tarafından yakalanır ve bir mavi ekran görüntüler. `InvalidLinkTextual`, `#error:` karakterleriyle başlayan bir hata mesajını URL olarak döndürerek çalışır. Bu tür bağlantıların ilk bakışta belirgin olmasını sağlamak için CSS'imize ekleriz: +Varsayılan ayar, üretim modunda `InvalidLinkWarning`, geliştirme modunda ise `InvalidLinkWarning | InvalidLinkTextual`'dır. Üretim ortamında `InvalidLinkWarning` betiğin kesilmesine yol açmaz, ama uyarı günlüğe yazılır. Geliştirme ortamında [Tracy |tracy:] onu yakalar ve bir bluescreen gösterir. `InvalidLinkTextual`, URL olarak `#error:` karakterleriyle başlayan bir hata mesajı döndürerek çalışır. Böyle bağlantıların ilk bakışta göze çarpması için CSS'inize şunu ekleyin: ```css a[href^="#error:"] { @@ -266,7 +299,7 @@ a[href^="#error:"] { } ``` -Geliştirme ortamında uyarıların üretilmesini istemiyorsak, sessiz modu doğrudan [yapılandırmada|configuration] ayarlayabiliriz. +Geliştirme ortamında uyarı üretilmesini istemiyorsak, onları doğrudan [yapılandırmada|configuration] bastırabiliriz. ```neon application: @@ -277,10 +310,10 @@ application: LinkGenerator ============= -`link()` metodunun sunduğu benzer konforla, ancak presenter olmadan bağlantılar nasıl oluşturulur? Bunun için [api:Nette\Application\LinkGenerator] vardır. +`link()` metodundaki rahatlıkla, ama presenter olmadan nasıl bağlantı oluşturulur? İşte [api:Nette\Application\LinkGenerator] bunun içindir. -LinkGenerator, kurucu aracılığıyla size iletilmesini isteyebileceğiniz ve ardından `link()` metoduyla bağlantılar oluşturabileceğiniz bir servistir. +LinkGenerator, constructor üzerinden size aktarılabilen ve sonra `link()` metoduyla bağlantı oluşturabileceğiniz bir servistir. -Presenter'lara kıyasla burada bir fark vardır. LinkGenerator tüm bağlantıları doğrudan mutlak URL'ler olarak oluşturur. Ayrıca, "mevcut presenter" diye bir şey yoktur, bu nedenle hedef olarak yalnızca `link('default')` eylem adını belirtmek veya modüllere göreceli yollar belirtmek mümkün değildir. +Presenter'lara kıyasla bir fark vardır. LinkGenerator tüm bağlantıları doğrudan mutlak URL olarak oluşturur. Ayrıca "geçerli presenter" diye bir şey yoktur, bu yüzden hedef olarak yalnızca eylem adını `link('default')` belirtmek veya modüllere göreli yollar kullanmak mümkün değildir. -Geçersiz bağlantılar her zaman `Nette\Application\UI\InvalidLinkException` atar. +Geçersiz bağlantılar her zaman `Nette\Application\UI\InvalidLinkException` fırlatır. diff --git a/application/tr/directory-structure.texy b/application/tr/directory-structure.texy index 16b6298815..5b65e0b64f 100644 --- a/application/tr/directory-structure.texy +++ b/application/tr/directory-structure.texy @@ -1,45 +1,45 @@ -Uygulama Dizin Yapısı -********************* +Uygulamanın dizin yapısı +************************
    -Nette Framework projeleri için anlaşılır ve ölçeklenebilir bir dizin yapısı nasıl tasarlanır? Kodunuzu düzenlemenize yardımcı olacak kanıtlanmış en iyi uygulamaları göstereceğiz. Şunları öğreneceksiniz: +Nette Framework projelerinde anlaşılır ve ölçeklenebilir bir dizin yapısı nasıl tasarlanır? Size kodunuzu düzenlemenize yardım edecek kanıtlanmış uygulamaları göstereceğiz. Şunları öğreneceksiniz: -- uygulamayı dizinlere **mantıksal olarak nasıl bölersiniz** -- yapıyı projenin büyümesiyle **iyi ölçeklenecek** şekilde nasıl tasarlarsınız -- **olası alternatifler** ve avantajları veya dezavantajları nelerdir +- uygulamayı dizinlere **mantıklı biçimde nasıl yapılandıracağınızı** +- yapıyı, proje büyüdükçe **iyi ölçeklenecek** şekilde nasıl tasarlayacağınızı +- **olası alternatiflerin** neler olduğunu ve avantajlarıyla dezavantajlarını
    -Nette Framework'ün kendisinin herhangi bir belirli yapıya bağlı olmadığını belirtmek önemlidir. Herhangi bir ihtiyaca ve tercihe kolayca uyarlanabilecek şekilde tasarlanmıştır. +Nette Framework'ün kendisinin belirli bir yapıyı dayatmadığını belirtmek önemli. Her ihtiyaca ve tercihe kolayca uyarlanabilecek şekilde tasarlanmıştır. -Temel Proje Yapısı +Temel proje yapısı ================== -Nette Framework herhangi bir sabit dizin yapısı dikte etmese de, [Web Project|https://github.com/nette/web-project] şeklinde kanıtlanmış bir varsayılan düzenleme vardır: +Nette Framework sabit bir dizin yapısı dayatmasa da, [Web Project|https://github.com/nette/web-project] biçiminde kanıtlanmış bir varsayılan düzen vardır: /--pre web-project/ ├── app/ ← uygulama dizini -├── assets/ ← SCSS, JS dosyaları, resimler..., alternatif olarak resources/ +├── assets/ ← SCSS, JS dosyaları, görseller..., alternatif olarak resources/ ├── bin/ ← komut satırı betikleri ├── config/ ← yapılandırma -├── log/ ← günlüğe kaydedilen hatalar +├── log/ ← günlüklenen hatalar ├── temp/ ← geçici dosyalar, önbellek ├── tests/ ← testler -├── vendor/ ← Composer tarafından kurulan kütüphaneler +├── vendor/ ← Composer ile kurulan kütüphaneler └── www/ ← genel dizin (document-root) \-- -Bu yapıyı ihtiyaçlarınıza göre serbestçe değiştirebilirsiniz - klasörleri yeniden adlandırabilir veya taşıyabilirsiniz. Ardından, yalnızca `Bootstrap.php` dosyasındaki ve muhtemelen `composer.json` dosyasındaki dizinlere giden göreceli yolları ayarlamanız yeterlidir. Başka hiçbir şeye gerek yoktur, karmaşık yeniden yapılandırma yok, sabitlerde değişiklik yok. Nette akıllı otomatik algılamaya sahiptir ve URL tabanı da dahil olmak üzere uygulamanın konumunu otomatik olarak tanır. +Bu yapıyı ihtiyaçlarınıza göre serbestçe değiştirebilirsiniz: klasörleri yeniden adlandırın veya taşıyın. Sonra yalnızca `Bootstrap.php` ve gerekirse `composer.json` içindeki dizin göreli yollarını düzeltmeniz yeterli. Başka bir şey gerekmez, karmaşık bir yeniden yapılandırma yok, sabitlerde değişiklik yok. Nette akıllı otomatik algılamaya sahiptir ve uygulamanın konumunu, temel URL'si dahil, kendiliğinden tanır. -Kod Organizasyon Prensipleri -============================ +Kodu düzenleme ilkeleri +======================= -Yeni bir projeyi ilk kez incelerken, içinde hızla yönünüzü bulabilmelisiniz. `app/Model/` dizinini açtığınızı ve şu yapıyı gördüğünüzü hayal edin: +Yeni bir projeyi ilk kez incelediğinizde hızlıca yolunuzu bulabilmelisiniz. `app/Model/` dizinine tıkladığınızı ve şu yapıyı gördüğünüzü düşünün: /--pre app/Model/ @@ -48,9 +48,9 @@ Yeni bir projeyi ilk kez incelerken, içinde hızla yönünüzü bulabilmelisini └── Entities/ \-- -Bundan yalnızca projenin bazı servisler, depolar ve varlıklar kullandığını anlarsınız. Uygulamanın gerçek amacı hakkında hiçbir şey öğrenemezsiniz. +Bundan yalnızca projenin bazı servisler, repository'ler ve varlıklar kullandığını öğrenirsiniz. Uygulamanın asıl amacı hakkında hiçbir şey öğrenemezsiniz. -Başka bir yaklaşıma bakalım - **alanlara göre organizasyon**: +Farklı bir yaklaşıma bakalım: **alan adına göre düzenleme**: /--pre app/Model/ @@ -60,76 +60,76 @@ Başka bir yaklaşıma bakalım - **alanlara göre organizasyon**: └── Product/ \-- -Burada durum farklı - ilk bakışta bunun bir e-ticaret sitesi olduğu açık. Dizin adlarının kendisi uygulamanın neler yapabildiğini ortaya koyuyor - ödemeler, siparişler ve ürünlerle çalışıyor. +Burada durum farklı: ilk bakışta bunun bir e-ticaret sitesi olduğu bellidir. Dizin adlarının kendisi uygulamanın neler yapabildiğini ele verir: ödemelerle, siparişlerle ve ürünlerle çalışıyor. -İlk yaklaşım (sınıf türüne göre organizasyon) pratikte bir dizi sorun getirir: mantıksal olarak birbiriyle ilişkili kod farklı klasörlere dağılmıştır ve aralarında atlamanız gerekir. Bu nedenle alanlara göre organize edeceğiz. +İlk yaklaşım (sınıf tipine göre düzenleme) pratikte birkaç sorun getirir: mantıksal olarak ilişkili kod farklı klasörlere dağılır ve aralarında gidip gelmeniz gerekir. Bu yüzden alan adına göre düzenleyeceğiz. -İsim Alanları (Namespaces) --------------------------- +İsim alanları +------------- -Dizin yapısının uygulamadaki isim alanlarıyla örtüşmesi adettendir. Bu, dosyaların fiziksel konumunun isim alanlarına karşılık geldiği anlamına gelir. Örneğin, `app/Model/Product/ProductRepository.php` içinde bulunan bir sınıfın `App\Model\Product` isim alanına sahip olması gerekir. Bu prensip, kodda gezinmeye yardımcı olur ve otomatik yüklemeyi basitleştirir. +Dizin yapısının uygulamadaki isim alanlarına karşılık gelmesi âdettendir. Yani dosyaların fiziksel konumu isim alanlarıyla uyuşur. Örneğin `app/Model/Product/ProductRepository.php` konumundaki bir sınıfın isim alanı `App\Model\Product` olmalıdır. Bu ilke kodda gezinmeye yardım eder ve otomatik yüklemeyi basitleştirir. -Adlarda Tekil vs Çoğul Sayı ---------------------------- +Adlarda tekil mi çoğul mu +------------------------- -Uygulamanın ana dizinlerinde tekil sayı kullandığımıza dikkat edin: `app`, `config`, `log`, `temp`, `www`. Aynı şekilde uygulama içinde de: `Model`, `Core`, `Presentation`. Bunun nedeni, her birinin bütün bir kavramı temsil etmesidir. +Uygulamanın ana dizinlerinde tekil kullandığımıza dikkat edin: `app`, `config`, `log`, `temp`, `www`. Aynısı uygulamanın içi için de geçerlidir: `Model`, `Core`, `Presentation`. Bunun nedeni her birinin tek bir bütünlüklü kavramı temsil etmesidir. -Benzer şekilde, örneğin `app/Model/Product`, ürünlerle ilgili her şeyi temsil eder. Buna `Products` demeyiz, çünkü ürünlerle dolu bir klasör değildir (orada `nokia.php`, `samsung.php` dosyaları olurdu). Ürünlerle çalışmak için sınıflar içeren bir isim alanıdır - `ProductRepository.php`, `ProductService.php`. +Benzer şekilde `app/Model/Product`, ürünlerle ilgili her şeyi temsil eder. Ona `Products` demiyoruz, çünkü ürünlerle dolu bir klasör değil (öyle olsaydı `nokia.php`, `samsung.php` gibi dosyalar içerirdi). Ürünlerle çalışmaya yarayan sınıfları içeren bir isim alanıdır: `ProductRepository.php`, `ProductService.php`. -`app/Tasks` klasörü çoğul sayıdadır çünkü bir dizi bağımsız yürütülebilir betik içerir - `CleanupTask.php`, `ImportTask.php`. Her biri bağımsız bir birimdir. +`app/Tasks` klasörü çoğuldur, çünkü ayrı çalıştırılabilir betiklerden oluşan bir küme içerir: `CleanupTask.php`, `ImportTask.php`. Her biri bağımsız bir birimdir. -Tutarlılık için şunları kullanmanızı öneririz: -- İşlevsel bir bütünü temsil eden isim alanı için tekil sayı (birden fazla varlıkla çalışsa bile) -- Bağımsız birimlerin koleksiyonları için çoğul sayı -- Emin olmadığınızda veya bunun hakkında düşünmek istemiyorsanız, tekil sayıyı seçin +Tutarlılık için şunu öneririz: +- İşlevsel bir birimi temsil eden isim alanlarında tekil (birden fazla varlıkla çalışsa bile) +- Bağımsız birim koleksiyonlarında çoğul +- Kararsız kaldığınızda ya da düşünmek istemediğinizde tekili seçin -Genel Dizin `www/` +Genel dizin `www/` ================== -Bu dizin, web'den erişilebilen tek dizindir (document-root olarak da bilinir). `www/` yerine `public/` adıyla da sıkça karşılaşabilirsiniz - bu sadece bir gelenek meselesidir ve uygulamanın işlevselliği üzerinde hiçbir etkisi yoktur. Dizin şunları içerir: -- Uygulamanın [Giriş noktası |bootstrapping#index.php] `index.php` -- mod_rewrite (Apache için) kuralları içeren `.htaccess` dosyası -- Statik dosyalar (CSS, JavaScript, resimler) +Bu dizin, web'den erişilebilen tek dizindir (document-root). `www/` yerine sıkça `public/` adıyla karşılaşabilirsiniz; bu yalnızca bir uzlaşım meselesidir ve uygulamanın işleyişini etkilemez. Dizin şunları içerir: +- Uygulamanın [giriş noktası |bootstrapping#index.php] `index.php` +- mod_rewrite kurallarını içeren `.htaccess` dosyası (Apache için) +- Statik dosyalar (CSS, JavaScript, görseller) - Yüklenen dosyalar -Uygulamanın doğru güvenliği için [document-root'un doğru şekilde yapılandırılması |nette:troubleshooting#URL den www Dizini Nasıl Değiştirilir veya Kaldırılır] esastır. +Uygulamanın güvenliği için [document-root'un doğru yapılandırılmış olması |nette:troubleshooting#URL'den www Dizini Nasıl Değiştirilir ya da Kaldırılır?] çok önemlidir. .[note] -`node_modules/` klasörünü asla bu dizine yerleştirmeyin - yürütülebilir olabilecek ve genel olarak erişilebilir olmaması gereken binlerce dosya içerir. +`node_modules/` klasörünü asla bu dizine koymayın; çalıştırılabilir olabilecek binlerce dosya içerir ve herkese açık olmamalıdır. -Uygulama Dizini `app/` +Uygulama dizini `app/` ====================== Bu, uygulama kodunu içeren ana dizindir. Temel yapı: /--pre app/ -├── Core/ ← altyapısal konular +├── Core/ ← altyapı konuları ├── Model/ ← iş mantığı ├── Presentation/ ← presenter'lar ve şablonlar ├── Tasks/ ← komut betikleri -└── Bootstrap.php ← uygulamanın başlatma sınıfı +└── Bootstrap.php ← uygulamanın başlatıcı sınıfı \-- -`Bootstrap.php`, ortamı başlatan, yapılandırmayı yükleyen ve DI konteynerini oluşturan [uygulamanın başlangıç sınıfıdır|bootstrapping]. +`Bootstrap.php`, ortamı hazırlayan, yapılandırmayı yükleyen ve DI konteynerini oluşturan [uygulama başlatma sınıfıdır|bootstrapping]. -Şimdi bireysel alt dizinlere daha ayrıntılı bakalım. +Şimdi tek tek alt dizinlere daha ayrıntılı bakalım. -Presenter'lar ve Şablonlar +Presenter'lar ve şablonlar ========================== -Uygulamanın sunum kısmı `app/Presentation` dizinindedir. Alternatif olarak kısa `app/UI` da kullanılabilir. Bu, tüm presenter'lar, şablonları ve olası yardımcı sınıflar için yerdir. +Uygulamanın sunum bölümü `app/Presentation` dizinindedir. Alternatifi daha kısa olan `app/UI`'dır. Burası tüm presenter'ların, şablonlarının ve ilişkili yardımcı sınıfların yeridir. -Bu katmanı alanlara göre organize ederiz. E-ticaret, blog ve API'yi birleştiren karmaşık bir projede yapı şöyle görünürdü: +Bu katmanı alan adına göre düzenleriz. E-ticaret sitesini, blogu ve API'yi birleştiren karmaşık bir projede yapı şöyle görünürdü: /--pre app/Presentation/ -├── Shop/ ← e-ticaret ön yüzü +├── Shop/ ← e-ticaret frontend'i │ ├── Product/ │ ├── Cart/ │ └── Order/ @@ -143,11 +143,11 @@ Bu katmanı alanlara göre organize ederiz. E-ticaret, blog ve API'yi birleştir └── V1/ \-- -Buna karşılık, basit bir blog için şu bölümlemeyi kullanırdık: +Buna karşılık basit bir blog için şu yapıyı kullanırdık: /--pre app/Presentation/ -├── Front/ ← web sitesi ön yüzü +├── Front/ ← sitenin frontend'i │ ├── Home/ │ └── Post/ ├── Admin/ ← yönetim @@ -157,9 +157,9 @@ Buna karşılık, basit bir blog için şu bölümlemeyi kullanırdık: └── Export/ ← RSS, site haritaları vb. \-- -`Home/` veya `Dashboard/` gibi klasörler presenter'ları ve şablonları içerir. `Front/`, `Admin/` veya `Api/` gibi klasörlere **modüller** diyoruz. Teknik olarak bunlar, uygulamayı mantıksal olarak bölmek için kullanılan normal dizinlerdir. +`Home/` veya `Dashboard/` gibi klasörler presenter'ları ve şablonları içerir. `Front/`, `Admin/` veya `Api/` gibi klasörlere **modül** denir. Teknik olarak bunlar, uygulamayı mantıksal olarak bölmeye yarayan sıradan dizinlerdir. -Presenter içeren her klasör, aynı adı taşıyan bir presenter ve şablonlarını içerir. Örneğin, `Dashboard/` klasörü şunları içerir: +Presenter içeren her klasör, presenter dosyasının kendisini ve şablonlarını barındırır. Örneğin `Dashboard/` klasörü şunları içerir: /--pre Dashboard/ @@ -167,7 +167,7 @@ Presenter içeren her klasör, aynı adı taşıyan bir presenter ve şablonlar └── default.latte ← şablon \-- -Bu dizin yapısı, sınıfların isim alanlarına yansır. Örneğin, `DashboardPresenter`, `App\Presentation\Admin\Dashboard` isim alanında bulunur ([#Presenter Eşlemesi] bölümüne bakın): +Bu dizin yapısı sınıfların isim alanlarına yansır. Örneğin `DashboardPresenter`, `App\Presentation\Admin\Dashboard` isim alanındadır (bkz. [#Presenter mapping]): ```php namespace App\Presentation\Admin\Dashboard; @@ -178,22 +178,22 @@ class DashboardPresenter extends Nette\Application\UI\Presenter } ``` -Uygulamada `Admin` modülü içindeki `Dashboard` presenter'ına iki nokta üst üste gösterimiyle `Admin:Dashboard` olarak başvururuz. `default` eylemine ise `Admin:Dashboard:default` olarak başvururuz. İç içe geçmiş modüller durumunda, daha fazla iki nokta üst üste kullanırız, örneğin `Shop:Order:Detail:default`. +`Admin` modülündeki `Dashboard` presenter'ına uygulamada iki nokta üst üste gösterimiyle `Admin:Dashboard` diye başvururuz. Onun `default` eylemine ise `Admin:Dashboard:default` denir. İç içe modüllerde birden fazla iki nokta üst üste kullanırız, örneğin `Shop:Order:Detail:default`. -Esnek Yapı Geliştirme ---------------------- +Yapının esnek biçimde gelişmesi +------------------------------- -Bu yapının büyük avantajlarından biri, projenin artan ihtiyaçlarına ne kadar zarif bir şekilde uyum sağladığıdır. Örnek olarak, XML beslemeleri oluşturan bölümü ele alalım. Başlangıçta basit bir formumuz var: +Bu yapının büyük avantajlarından biri, projenin büyüyen ihtiyaçlarına ne kadar zarif uyum sağlamasıdır. Örnek olarak XML beslemeleri üreten bölümü ele alalım. Başlangıçta basit bir biçimimiz var: /--pre Export/ -├── ExportPresenter.php ← tüm dışa aktarımlar için tek bir presenter -├── sitemap.latte ← site haritası için şablon -└── feed.latte ← RSS beslemesi için şablon +├── ExportPresenter.php ← tüm dışa aktarımlar için tek presenter +├── sitemap.latte ← site haritası şablonu +└── feed.latte ← RSS beslemesi şablonu \-- -Zamanla başka besleme türleri eklenir ve onlar için daha fazla mantığa ihtiyacımız olur... Sorun değil! `Export/` klasörü basitçe bir modül haline gelir: +Zamanla daha fazla besleme tipi eklenir ve onlar için daha fazla mantık gerekir... Sorun değil! `Export/` klasörü kolayca bir modüle dönüşür: /--pre Export/ @@ -202,52 +202,52 @@ Zamanla başka besleme türleri eklenir ve onlar için daha fazla mantığa ihti │ └── sitemap.latte └── Feed/ ├── FeedPresenter.php - ├── zbozi.latte ← Zboží.cz için besleme - └── heureka.latte ← Heureka.cz için besleme + ├── amazon.latte ← Amazon için besleme + └── ebay.latte ← eBay için besleme \-- -Bu dönüşüm tamamen sorunsuzdur - sadece yeni alt klasörler oluşturmanız, kodu bunlara bölmeniz ve bağlantıları güncellemeniz yeterlidir (örneğin, `Export:feed` yerine `Export:Feed:zbozi`). Bu sayede yapıyı ihtiyaçlara göre kademeli olarak genişletebiliriz, iç içe geçme seviyesi sınırlı değildir. +Bu dönüşüm tamamen pürüzsüzdür: yalnızca yeni alt klasörler oluşturun, kodu onlara bölün ve bağlantıları güncelleyin (örneğin `Export:feed`'den `Export:Feed:amazon`'a). Bu sayede yapıyı gerektikçe kademeli olarak genişletebiliriz; iç içelik düzeyi hiçbir şekilde sınırlı değildir. -Örneğin, yönetimde sipariş yönetimiyle ilgili `OrderDetail`, `OrderEdit`, `OrderDispatch` vb. gibi birçok presenter'ınız varsa, daha iyi organizasyon için bu noktada (klasörler için) `Detail`, `Edit`, `Dispatch` ve diğer presenter'ları içerecek bir `Order` modülü (klasörü) oluşturabilirsiniz. +Örneğin yönetim panelinde sipariş yönetimiyle ilgili `OrderDetail`, `OrderEdit`, `OrderDispatch` gibi birçok presenter'ınız varsa, daha iyi düzen için `Order` adlı bir modül (klasör) oluşturabilirsiniz; bu modül `Detail`, `Edit`, `Dispatch` ve diğer presenter'ların (klasörlerini) içerecektir. -Şablonların Konumu +Şablonların konumu ------------------ -Önceki örneklerde, şablonların doğrudan presenter içeren klasörde bulunduğunu gördük: +Önceki örneklerde şablonların doğrudan presenter'ın bulunduğu klasörde olduğunu gördük: /--pre Dashboard/ ├── DashboardPresenter.php ← presenter -├── DashboardTemplate.php ← şablon için isteğe bağlı sınıf +├── DashboardTemplate.php ← isteğe bağlı şablon sınıfı └── default.latte ← şablon \-- -Bu konum pratikte en uygun olanıdır - tüm ilgili dosyalarınız hemen elinizin altındadır. +Bu konum pratikte en kullanışlısı olduğunu kanıtlar: ilişkili tüm dosyalar elinizin altındadır. -Alternatif olarak, şablonları `templates/` alt klasörüne yerleştirebilirsiniz. Nette her iki seçeneği de destekler. Hatta şablonları tamamen `Presentation/` klasörünün dışına bile yerleştirebilirsiniz. Şablonların yerleştirilme seçenekleri hakkında her şeyi [Şablonları Bulma |templates#Şablonları Bulma] bölümünde bulabilirsiniz. +Alternatif olarak şablonları bir `templates/` alt klasörüne koyabilirsiniz. Nette her iki seçeneği de destekler. Şablonları tamamen `Presentation/` klasörünün dışına bile koyabilirsiniz. Şablon konumu seçenekleriyle ilgili her şeyi [Şablon arama |templates#Şablon arama] bölümünde bulabilirsiniz. -Yardımcı Sınıflar ve Bileşenler +Yardımcı sınıflar ve bileşenler ------------------------------- -Presenter'lara ve şablonlara genellikle başka yardımcı dosyalar da eşlik eder. Bunları etki alanlarına göre mantıksal olarak yerleştiririz: +Presenter'lar ve şablonlar sıklıkla başka yardımcı dosyalarla birlikte gelir. Onları kapsamlarına göre mantıklı biçimde yerleştiririz: -1. **Doğrudan presenter yanında**, belirli bir presenter için özel bileşenler durumunda: +1. **Doğrudan presenter'ın yanına**, o presenter'a özgü bileşenler söz konusuysa: /--pre Product/ ├── ProductPresenter.php -├── ProductGrid.php ← ürün listeleme için bileşen -└── FilterForm.php ← filtreleme için form +├── ProductGrid.php ← ürün listelemesi bileşeni +└── FilterForm.php ← filtreleme formu \-- -2. **Modül için** - alfabenin hemen başında düzgün bir şekilde yerleştirilecek olan `Accessory` klasörünü kullanmanızı öneririz: +2. **Modül için** - alfabetik olarak başta yer aldığı için elverişli olan `Accessory` klasörünü kullanmanızı öneririz: /--pre Front/ ├── Accessory/ -│ ├── NavbarControl.php ← ön yüz için bileşenler +│ ├── NavbarControl.php ← frontend bileşenleri │ └── TemplateFilters.php ├── Product/ └── Cart/ @@ -263,13 +263,13 @@ Presenter'lara ve şablonlara genellikle başka yardımcı dosyalar da eşlik ed └── Admin/ \-- -Veya `LatteExtension.php` veya `TemplateFilters.php` gibi yardımcı sınıfları altyapısal `app/Core/Latte/` klasörüne yerleştirebilirsiniz. Ve bileşenleri `app/Components` içine. Seçim, ekibin alışkanlıklarına bağlıdır. +Alternatif olarak `LatteExtension.php` veya `TemplateFilters.php` gibi yardımcı sınıfları `app/Core/Latte/` altyapı klasörüne koyabilirsiniz. Bileşenleri de `app/Components` içine. Seçim ekip uzlaşımlarına bağlıdır. -Model - Uygulamanın Kalbi +Model - uygulamanın kalbi ========================= -Model, uygulamanın tüm iş mantığını içerir. Organizasyonu için yine kural geçerlidir - alanlara göre yapılandırırız: +Model, uygulamanın tüm iş mantığını içerir. Onu düzenleme kuralı yine aynıdır: alan adına göre yapılandırmak: /--pre app/Model/ @@ -284,9 +284,9 @@ Model, uygulamanın tüm iş mantığını içerir. Organizasyonu için yine kur └── Shipping/ ← kargoyla ilgili her şey \-- -Modelde tipik olarak şu tür sınıflarla karşılaşırsınız: +Model'de genellikle şu sınıf tipleriyle karşılaşırsınız: -**Fasadlar (Facades)**: Uygulamadaki belirli bir alana ana giriş noktasını temsil ederler. Tam kullanım senaryolarını (use-cases) uygulamak için farklı servisler arasındaki işbirliğini koordine eden bir orkestratör görevi görürler (örneğin "sipariş oluştur" veya "ödemeyi işle"). Orkestrasyon katmanının altında, fasad uygulama ayrıntılarını uygulamanın geri kalanından gizler, böylece söz konusu alanla çalışmak için temiz bir arayüz sağlar. +**Facade'lar**: uygulamadaki belirli bir alana giden ana giriş noktasını temsil ederler. Bir orkestra şefi gibi davranır, tam kullanım senaryolarını ("sipariş oluştur" veya "ödemeyi işle") gerçekleştirmek için çeşitli servisler arasındaki iş birliğini koordine ederler. Facade, bu orkestrasyon katmanının altında uygulama ayrıntılarını uygulamanın geri kalanından gizler ve böylece ilgili alanla çalışmak için temiz bir arayüz sunar. ```php class OrderFacade @@ -294,26 +294,26 @@ class OrderFacade public function createOrder(Cart $cart): Order { // doğrulama - // sipariş oluşturma - // e-posta gönderme + // siparişin oluşturulması + // e-posta gönderimi // istatistiklere yazma } } ``` -**Servisler**: Alan içindeki belirli bir iş operasyonuna odaklanırlar. Tüm kullanım senaryolarını düzenleyen bir fasadın aksine, bir servis belirli bir iş mantığını uygular (fiyat hesaplamaları veya ödeme işlemleri gibi). Servisler tipik olarak durumsuzdur ve daha karmaşık operasyonlar için yapı taşları olarak fasadlar tarafından veya daha basit görevler için doğrudan uygulamanın diğer bölümleri tarafından kullanılabilirler. +**Servisler**: bir alan içindeki belirli iş operasyonlarına odaklanır. Tüm kullanım senaryolarını yöneten facade'ların aksine, bir servis somut bir iş mantığını (fiyat hesaplaması veya ödeme işleme gibi) uygular. Servisler genellikle durumsuzdur ve daha karmaşık işlemler için yapı taşı olarak facade'lar tarafından ya da daha basit görevler için doğrudan uygulamanın başka bölümleri tarafından kullanılabilir. ```php class PricingService { public function calculateTotal(Order $order): Money { - // fiyat hesaplama + // fiyat hesaplaması } } ``` -**Depolar (Repositories)**: Veri deposuyla, tipik olarak veritabanıyla tüm iletişimi sağlarlar. Görevi, varlıkları yüklemek ve kaydetmek ve bunları aramak için metotlar uygulamaktır. Depo, uygulamanın geri kalanını veritabanının uygulama ayrıntılarından soyutlar ve verilerle çalışmak için nesne yönelimli bir arayüz sağlar. +**Repository'ler**: veri deposuyla, genellikle bir veritabanıyla, tüm iletişimi üstlenirler. Görevleri varlıkları yüklemek, kaydetmek ve onları aramaya yarayan metotları uygulamaktır. Bir repository, uygulamanın geri kalanını veritabanının uygulama ayrıntılarından korur ve veriyle çalışmak için nesne yönelimli bir arayüz sunar. ```php class OrderRepository @@ -328,10 +328,10 @@ class OrderRepository } ``` -**Varlıklar (Entities)**: Uygulamadaki ana iş kavramlarını temsil eden, kendi kimlikleri olan ve zamanla değişen nesnelerdir. Tipik olarak bunlar, ORM (Nette Database Explorer veya Doctrine gibi) kullanılarak veritabanı tablolarına eşlenen sınıflardır. Varlıklar, verileriyle ilgili iş kurallarını ve doğrulama mantığını içerebilir. +**Varlıklar**: uygulamadaki başlıca iş kavramlarını temsil eden, kendi kimliği olan ve zamanla değişen nesneler. Genellikle bir ORM (Nette Database Explorer veya Doctrine gibi) ile veritabanı tablolarına eşlenen sınıflardır. Varlıklar, verileriyle ilgili iş kurallarını ve doğrulama mantığını içerebilir. ```php -// orders veritabanı tablosuna eşlenen varlık +// 'orders' veritabanı tablosuna eşlenen varlık class Order extends Nette\Database\Table\ActiveRow { public function addItem(Product $product, int $quantity): void @@ -345,13 +345,13 @@ class Order extends Nette\Database\Table\ActiveRow } ``` -**Değer Nesneleri (Value Objects)**: Kendi kimlikleri olmayan değerleri temsil eden değişmez nesnelerdir - örneğin bir para tutarı veya bir e-posta adresi. Aynı değerlere sahip iki değer nesnesi örneği özdeş kabul edilir. +**Value Object'ler**: kendi kimliği olmayan değerleri temsil eden değişmez nesneler; örneğin bir para tutarı veya bir e-posta adresi. Aynı değerlere sahip iki value object örneği özdeş sayılır. -Altyapısal Kod -============== +Altyapı kodu +============ -`Core/` (veya `Infrastructure/`) klasörü, uygulamanın teknik temelinin evidir. Altyapısal kod tipik olarak şunları içerir: +`Core/` klasörü (ya da alternatif olarak `Infrastructure/`) uygulamanın teknik temelinin yuvasıdır. Altyapı kodu genellikle şunları içerir: /--pre app/Core/ @@ -363,14 +363,14 @@ Altyapısal Kod ├── Logging/ ← günlükleme ve izleme │ ├── SentryLogger.php │ └── FileLogger.php -├── Cache/ ← önbellekleme katmanı +├── Cache/ ← önbellek katmanı │ └── FullPageCache.php -└── Integration/ ← harici servislerle entegrasyon +└── Integration/ ← dış servislerle entegrasyon ├── Slack/ └── Stripe/ \-- -Daha küçük projelerde, elbette düz bir bölümleme yeterlidir: +Daha küçük projelerde doğal olarak düz bir yapı yeterlidir: /--pre Core/ @@ -379,82 +379,82 @@ Daha küçük projelerde, elbette düz bir bölümleme yeterlidir: └── QueueMailer.php \-- -Bu, şu kodu ifade eder: +Bu, şöyle bir koddur: -- Teknik altyapıyı çözer (yönlendirme, günlükleme, önbellekleme) -- Harici servisleri entegre eder (Sentry, Elasticsearch, Redis) +- Teknik altyapıyı üstlenir (yönlendirme, günlükleme, önbellekleme) +- Dış servisleri entegre eder (Sentry, Elasticsearch, Redis) - Tüm uygulama için temel servisleri sağlar (posta, veritabanı) -- Çoğunlukla belirli bir alandan bağımsızdır - önbellek veya günlükleyici e-ticaret veya blog için aynı şekilde çalışır. +- Çoğunlukla belirli bir alandan bağımsızdır; önbellek veya günlükleyici bir e-ticaret sitesinde de blogda da aynı çalışır. -Belirli bir sınıfın buraya mı yoksa modele mi ait olduğundan emin değil misiniz? Anahtar fark, `Core/` içindeki kodun: +Belirli bir sınıfın buraya mı yoksa model'e mi ait olduğunu merak mı ediyorsunuz? Temel fark şudur: `Core/` içindeki kod: -- Alan hakkında hiçbir şey bilmemesi (ürünler, siparişler, makaleler) -- Çoğunlukla başka bir projeye taşınabilmesi -- "Nasıl çalıştığını" (bir e-posta nasıl gönderilir) çözmesi, "ne yaptığını" (hangi e-postanın gönderileceği) değil +- Alan hakkında hiçbir şey bilmez (ürünler, siparişler, makaleler) +- Genellikle başka bir projeye taşınabilir +- "Nasıl çalıştığını" çözer (bir e-posta nasıl gönderilir), "ne yaptığını" değil (hangi e-posta gönderilir) Daha iyi anlamak için bir örnek: -- `App\Core\MailerFactory` - e-posta göndermek için sınıf örnekleri oluşturur, SMTP ayarlarını çözer -- `App\Model\OrderMailer` - siparişlerle ilgili e-postaları göndermek için `MailerFactory` kullanır, şablonlarını bilir ve ne zaman gönderilmeleri gerektiğini bilir +- `App\Core\MailerFactory` - e-posta göndermeye yarayan sınıfın örneklerini oluşturur, SMTP ayarlarını üstlenir +- `App\Model\OrderMailer` - siparişlerle ilgili e-postaları göndermek için `MailerFactory`'yi kullanır, şablonlarını ve ne zaman gönderilmeleri gerektiğini bilir -Komut Betikleri +Komut betikleri =============== -Uygulamaların genellikle normal HTTP istekleri dışında etkinlikler gerçekleştirmesi gerekir - ister arka planda veri işleme, ister bakım, ister periyodik görevler olsun. Çalıştırma için `bin/` dizinindeki basit betikler kullanılır, uygulama mantığının kendisi ise `app/Tasks/` (veya `app/Commands/`) içine yerleştirilir. +Uygulamaların sıklıkla olağan HTTP isteklerinin dışında işler yapması gerekir; ister arka planda veri işleme, ister bakım, ister düzenli görevler olsun. Çalıştırmak için `bin/` dizinindeki basit betikler kullanılır, asıl uygulama mantığı ise `app/Tasks/` (veya `app/Commands/`) içine konur. Örnek: /--pre app/Tasks/ ├── Maintenance/ ← bakım betikleri -│ ├── CleanupCommand.php ← eski verileri silme -│ └── DbOptimizeCommand.php ← veritabanı optimizasyonu -├── Integration/ ← harici sistemlerle entegrasyon -│ ├── ImportProducts.php ← tedarikçi sisteminden içe aktarma -│ └── SyncOrders.php ← sipariş senkronizasyonu +│ ├── CleanupCommand.php ← eski verilerin silinmesi +│ └── DbOptimizeCommand.php ← veritabanı iyileştirmesi +├── Integration/ ← dış sistemlerle entegrasyon +│ ├── ImportProducts.php ← tedarikçi sisteminden içe aktarım +│ └── SyncOrders.php ← siparişlerin eşitlenmesi └── Scheduled/ ← düzenli görevler - ├── NewsletterCommand.php ← bülten gönderme + ├── NewsletterCommand.php ← bültenlerin gönderimi └── ReminderCommand.php ← müşteri bildirimleri \-- -Modele ne aittir ve komut betiklerine ne aittir? Örneğin, tek bir e-posta gönderme mantığı modelin bir parçasıdır, binlerce e-postanın toplu gönderimi zaten `Tasks/` içine aittir. +Model'e ne, komut betiklerine ne ait? Örneğin tek bir e-posta gönderme mantığı model'in parçasıdır, binlerce e-postanın toplu gönderimi ise `Tasks/`'a aittir. -Görevler genellikle [komut satırından |https://blog.nette.org/en/cli-scripts-in-nette-application] veya cron aracılığıyla çalıştırılır. HTTP isteği aracılığıyla da çalıştırılabilirler, ancak güvenliği göz önünde bulundurmak gerekir. Görevi başlatan presenter'ın güvenliğini sağlamak gerekir, örneğin yalnızca oturum açmış kullanıcılar için veya güçlü bir belirteç ve izin verilen IP adreslerinden erişimle. Uzun görevler için betik zaman aşımını artırmak ve oturumun kilitlenmemesi için `session_write_close()` kullanmak gerekir. +Görevler genellikle komut satırından veya cron ile çalıştırılır: `bin/` içindeki betik, [bootConsoleApplication() |bootstrapping#Farklı ortamlar] metoduyla DI konteynerini oluşturur ve gereken servisi ondan çeker. Bir HTTP isteğiyle de çalıştırılabilirler, ama güvenlik göz önünde bulundurulmalıdır. Görevi çalıştıran presenter'ın güvenliği sağlanmalıdır; örneğin yalnızca oturum açmış kullanıcılar için ya da güçlü bir belirteç ve izin verilen IP adreslerinden erişimle. Uzun süren görevlerde betiğin zaman sınırını artırmak ve oturumu kilitlememek için `session_write_close()` kullanmak gerekir. -Diğer Olası Dizinler +Olası diğer dizinler ==================== -Bahsedilen temel dizinlere ek olarak, proje ihtiyaçlarına göre başka özel klasörler de ekleyebilirsiniz. En yaygın olanlarına ve kullanımlarına bakalım: +Sözü geçen temel dizinlerin yanı sıra, projenin ihtiyaçlarına göre başka özel klasörler ekleyebilirsiniz. En yaygınlarına ve kullanımlarına bakalım: /--pre app/ ├── Api/ ← sunum katmanından bağımsız API mantığı -├── Database/ ← test verileri için geçiş betikleri ve tohumlayıcılar -├── Components/ ← tüm uygulama genelinde paylaşılan görsel bileşenler -├── Event/ ← olay odaklı mimari kullanıyorsanız yararlıdır +├── Database/ ← migration betikleri ve test verisi için seeder'lar +├── Components/ ← tüm uygulamada paylaşılan görsel bileşenler +├── Event/ ← olay güdümlü bir mimari kullanılıyorsa yararlı ├── Mail/ ← e-posta şablonları ve ilgili mantık └── Utils/ ← yardımcı sınıflar \-- -Uygulama genelinde presenter'larda kullanılan paylaşılan görsel bileşenler için `app/Components` veya `app/Controls` klasörünü kullanabilirsiniz: +Uygulama genelindeki presenter'larda kullanılan paylaşılan görsel bileşenler için `app/Components` veya `app/Controls` klasörünü kullanabilirsiniz: /--pre app/Components/ ├── Form/ ← paylaşılan form bileşenleri │ ├── SignInForm.php │ └── UserForm.php -├── Grid/ ← veri listeleri için bileşenler +├── Grid/ ← veri listelemeleri için bileşenler │ └── DataGrid.php └── Navigation/ ← gezinme öğeleri ├── Breadcrumbs.php └── Menu.php \-- -Buraya daha karmaşık mantığa sahip bileşenler aittir. Bileşenleri birden fazla proje arasında paylaşmak istiyorsanız, bunları ayrı bir composer paketine ayırmak uygundur. +Daha karmaşık mantığa sahip bileşenler buraya aittir. Bileşenleri birden fazla proje arasında paylaşmak isterseniz, onları ayrı bir Composer paketine çıkarmanız yerinde olur. -E-posta iletişiminin yönetimini `app/Mail` dizinine yerleştirebilirsiniz: +`app/Mail` dizinine e-posta iletişiminin yönetimini koyabilirsiniz: /--pre app/Mail/ @@ -465,35 +465,35 @@ E-posta iletişiminin yönetimini `app/Mail` dizinine yerleştirebilirsiniz: \-- -Presenter Eşlemesi -================== +Presenter mapping +================= -Eşleme, presenter adından sınıf adını türetme kurallarını tanımlar. Bunları [yapılandırmada|configuration] `application › mapping` anahtarı altında belirtiriz. +Mapping, sınıf adının presenter adından türetilme kurallarını tanımlar. Onları [yapılandırmada|configuration] `application › mapping` anahtarı altında belirtiriz. -Bu sayfada, presenter'ları `app/Presentation` (veya `app/UI`) klasörüne yerleştirdiğimizi gösterdik. Bu geleneği Nette'ye yapılandırma dosyasında bildirmeliyiz. Tek bir satır yeterlidir: +Bu sayfada presenter'ları `app/Presentation` (veya `app/UI`) klasörüne koyduğumuzu gösterdik. Nette Application 3.3'ten beri bu, yapılandırılması gerekmeyen varsayılan uzlaşımdır. Farklı bir yapı kullanıyorsanız veya mapping'i açıkça belirtmek istiyorsanız, varsayılan ayar şu satıra karşılık gelir: ```neon application: mapping: App\Presentation\*\**Presenter ``` -Eşleme nasıl çalışır? Daha iyi anlamak için önce modülsüz bir uygulama hayal edelim. Presenter sınıflarının `App\Presentation` isim alanına düşmesini istiyoruz, böylece `Home` presenter'ı `App\Presentation\HomePresenter` sınıfına eşlenir. Bunu şu yapılandırmayla başarırız: +Mapping nasıl çalışır? Daha iyi anlamak için önce modülsüz bir uygulama düşünelim. Presenter sınıflarının `App\Presentation` isim alanına düşmesini istiyoruz, böylece `Home` presenter'ı `App\Presentation\HomePresenter` sınıfına eşlensin. Bu, şu yapılandırmayla sağlanır: ```neon application: mapping: App\Presentation\*Presenter ``` -Eşleme, `Home` presenter adının `App\Presentation\*Presenter` maskesindeki yıldız işaretini değiştirmesiyle çalışır, böylece sonuçta `App\Presentation\HomePresenter` sınıf adını elde ederiz. Basit! +Mapping, `App\Presentation\*Presenter` maskesindeki yıldızın presenter adı `Home` ile değiştirilmesiyle çalışır ve sonuçta `App\Presentation\HomePresenter` sınıf adı elde edilir. Basit! -Ancak bu ve diğer bölümlerdeki örneklerde gördüğünüz gibi, presenter sınıflarını aynı adlı alt dizinlere yerleştiririz, örneğin `Home` presenter'ı `App\Presentation\Home\HomePresenter` sınıfına eşlenir. Bunu iki nokta üst üste işaretini iki katına çıkararak başarırız (Nette Application 3.2 gerektirir): +Ancak bu ve diğer bölümlerdeki örneklerde gördüğünüz gibi, presenter sınıflarını aynı adlı alt dizinlere koyuyoruz; örneğin `Home` presenter'ı `App\Presentation\Home\HomePresenter` sınıfına eşlenir. Bunu çift yıldız `**` kullanarak sağlarız (Nette Application 3.2.3 gerektirir): ```neon application: mapping: App\Presentation\**Presenter ``` -Şimdi presenter'ları modüllere eşlemeye geçelim. Her modül için belirli bir eşleme tanımlayabiliriz: +Şimdi presenter'ları modüllere eşlemeye geçiyoruz. Her modül için özel bir mapping tanımlayabiliriz: ```neon application: @@ -503,9 +503,9 @@ application: Api: App\Api\*Presenter ``` -Bu yapılandırmaya göre, `Front:Home` presenter'ı `App\Presentation\Front\Home\HomePresenter` sınıfına eşlenirken, `Api:OAuth` presenter'ı `App\Api\OAuthPresenter` sınıfına eşlenir. +Bu yapılandırmaya göre `Front:Home` presenter'ı `App\Presentation\Front\Home\HomePresenter` sınıfına, `Api:OAuth` presenter'ı ise `App\Api\OAuthPresenter` sınıfına eşlenir. -`Front` ve `Admin` modülleri benzer bir eşleme yöntemine sahip olduğundan ve muhtemelen bu türden daha fazla modül olacağından, bunları değiştirecek genel bir kural oluşturmak mümkündür. Sınıf maskesine modül için yeni bir yıldız işareti eklenir: +`Front` ve `Admin` modüllerinin mapping deseni benzer olduğundan ve büyük olasılıkla böyle daha çok modül olacağından, onların yerine geçen genel bir kural oluşturmak mümkündür. Sınıf maskesine modül için yeni bir yıldız eklenir: ```neon application: @@ -514,9 +514,9 @@ application: Api: App\Api\*Presenter ``` -Bu, örneğin `Admin:User:Edit` presenter'ı gibi daha derinlemesine iç içe geçmiş dizin yapıları için de çalışır, yıldız işaretli segment her seviye için tekrarlanır ve sonuç `App\Presentation\Admin\User\Edit\EditPresenter` sınıfıdır. +Bu, daha derin iç içe dizin yapılarında da çalışır; örneğin `Admin:User:Edit` presenter'ında yıldızlı bölüm her modül düzeyi için yinelenir ve sonuçta `App\Presentation\Admin\User\Edit\EditPresenter` sınıfı elde edilir. -Alternatif bir gösterim, bir dize yerine üç segmentten oluşan bir dizi kullanmaktır. Bu gösterim öncekiyle eşdeğerdir: +Alternatif bir yazım, dize yerine üç bölümden oluşan bir dizi kullanmaktır. Yukarıda gösterilen örnekler için bu yazım öncekiyle eşdeğerdir: ```neon application: diff --git a/application/tr/how-it-works.texy b/application/tr/how-it-works.texy index 6b64f1a29a..14887db71f 100644 --- a/application/tr/how-it-works.texy +++ b/application/tr/how-it-works.texy @@ -1,101 +1,101 @@ -Uygulamalar Nasıl Çalışır? +Uygulamalar nasıl çalışır? **************************
    -Şu anda Nette dokümantasyonunun temel sayfasını okuyorsunuz. Web uygulamalarının çalışma prensibini öğreneceksiniz. A'dan Z'ye, başlangıç anından PHP betiğinin son nefesine kadar. Okuduktan sonra şunları bileceksiniz: +Şu anda Nette dokümantasyonunun temel bölümünü okuyorsunuz. Web uygulamalarının nasıl çalıştığının tüm ilkelerini, bir isteğin doğduğu andan PHP betiğinin çalışmasını bitirdiği ana kadar A'dan Z'ye öğreneceksiniz. Okuduktan sonra şunları anlayacaksınız: -- her şey nasıl çalışır -- Bootstrap, Presenter ve DI konteynerinin ne olduğu -- dizin yapısının nasıl göründüğü +- her şeyin nasıl çalıştığını +- Bootstrap'ın, Presenter'ın ve DI konteynerinin ne olduğunu +- dizin yapısının nasıl göründüğünü
    -Dizin Yapısı +Dizin yapısı ============ -[WebProject|https://github.com/nette/web-project] adlı web uygulaması iskelet örneğini açın ve okurken bahsedilen dosyalara bakabilirsiniz. +[WebProject|https://github.com/nette/web-project] adlı örnek web uygulaması iskeletini açın. Okurken sözü geçen dosyalara bakabilirsiniz. Dizin yapısı aşağı yukarı şöyle görünür: /--pre web-project/ ├── app/ ← uygulama dizini -│ ├── Core/ ← çalışması için gerekli temel sınıflar -│ │ └── RouterFactory.php ← URL adreslerinin yapılandırılması -│ ├── Presentation/ ← presenter'lar, şablonlar ve ilgili dosyalar -│ │ ├── @layout.latte ← düzen şablonu -│ │ └── Home/ ← Home presenter dizini +│ ├── Core/ ← çalışma için gerekli çekirdek sınıflar +│ │ └── RouterFactory.php ← URL adreslerinin yapılandırması +│ ├── Presentation/ ← presenter'lar, şablonlar vb. +│ │ ├── @layout.latte ← layout şablonu +│ │ └── Home/ ← Home presenter'ının dizini │ │ ├── HomePresenter.php ← Home presenter sınıfı -│ │ └── default.latte ← default eyleminin şablonu -│ └── Bootstrap.php ← Bootstrap başlatma sınıfı -├── assets/ ← kaynaklar (SCSS, TypeScript, kaynak görüntüler) +│ │ └── default.latte ← default eylemi için şablon +│ └── Bootstrap.php ← başlatıcı sınıf Bootstrap +├── assets/ ← kaynaklar (SCSS, TypeScript, kaynak görseller) ├── bin/ ← komut satırından çalıştırılan betikler ├── config/ ← yapılandırma dosyaları │ ├── common.neon │ └── services.neon -├── log/ ← günlüğe kaydedilen hatalar +├── log/ ← günlüklenen hatalar ├── temp/ ← geçici dosyalar, önbellek, … -├── vendor/ ← Composer tarafından kurulan kütüphaneler +├── vendor/ ← Composer ile kurulan kütüphaneler │ ├── ... -│ └── autoload.php ← kurulan tüm paketlerin otomatik yüklenmesi -├── www/ ← genel dizin veya projenin document-root'u -│ ├── assets/ ← derlenmiş statik dosyalar (CSS, JS, resimler, ...) +│ └── autoload.php ← kurulu tüm paketlerin otomatik yüklenmesi +├── www/ ← genel dizin, projenin document root'u +│ ├── assets/ ← derlenmiş statik dosyalar (CSS, JS, görseller, ...) │ ├── .htaccess ← mod_rewrite kuralları -│ └── index.php ← uygulamanın başlatıldığı ilk dosya +│ └── index.php ← uygulamayı başlatan ilk dosya └── .htaccess ← www dışındaki tüm dizinlere erişimi yasaklar \-- -Dizin yapısını istediğiniz gibi değiştirebilir, klasörleri yeniden adlandırabilir veya taşıyabilirsiniz, tamamen esnektir. Nette ayrıca akıllı otomatik algılamaya sahiptir ve URL tabanı da dahil olmak üzere uygulamanın konumunu otomatik olarak tanır. +Dizin yapısını istediğiniz gibi değiştirebilir, klasörleri yeniden adlandırabilir veya taşıyabilirsiniz; tamamen esnektir. Nette ayrıca akıllı otomatik algılamaya sahiptir ve uygulamanın konumunu, URL tabanı dahil, kendiliğinden tanır. -Biraz daha büyük uygulamalarda, presenter ve şablon klasörlerini [alt dizinlere ayırabilir |directory-structure#Presenter lar ve Şablonlar] ve sınıfları modül dediğimiz isim alanlarına bölebiliriz. +Biraz daha büyük uygulamalarda presenter ve şablon klasörlerini [alt dizinlere |directory-structure#Presenter'lar ve şablonlar] ayırabilir ve sınıfları modül dediğimiz isim alanlarına gruplayabiliriz. -`www/` dizini, projenin sözde genel dizinini veya document-root'unu temsil eder. Uygulama tarafında başka bir şey ayarlamaya gerek kalmadan yeniden adlandırabilirsiniz. Yalnızca [hosting'i yapılandırmak |nette:troubleshooting#URL den www Dizini Nasıl Değiştirilir veya Kaldırılır] gerekir, böylece document-root bu dizine işaret eder. +`www/` dizini projenin genel dizinini, yani document-root'unu temsil eder. Uygulama tarafında başka bir şey yapılandırmaya gerek kalmadan yeniden adlandırabilirsiniz. Yalnızca document-root'un bu dizini göstermesi için [hosting'i yapılandırmanız |nette:troubleshooting#URL'den www Dizini Nasıl Değiştirilir ya da Kaldırılır?] gerekir. -WebProject'i Nette dahil olmak üzere doğrudan [Composer|best-practices:composer] kullanarak da indirebilirsiniz: +WebProject'i, Nette dahil, doğrudan [Composer |best-practices:composer] ile de indirebilirsiniz: ```shell composer create-project nette/web-project ``` -Linux veya macOS'ta, `log/` ve `temp/` dizinlerine [yazma izinlerini |nette:troubleshooting#Dizin İzinlerini Ayarlama] ayarlayın. +Linux veya macOS'ta `log/` ve `temp/` dizinleri için [yazma izinlerini |nette:troubleshooting#Dizin İzinlerini Ayarlama] ayarlayın. -WebProject uygulaması çalışmaya hazırdır, hiçbir şey yapılandırmaya gerek yoktur ve `www/` klasörüne erişerek doğrudan tarayıcıda görüntüleyebilirsiniz. +WebProject uygulaması çalışmaya hazırdır; hiçbir şey yapılandırmaya gerek yoktur ve `www/` klasörüne giderek doğrudan tarayıcıda görüntüleyebilirsiniz. -HTTP İsteği +HTTP isteği =========== -Her şey, kullanıcının tarayıcıda bir sayfa açmasıyla başlar. Yani tarayıcı bir HTTP isteği ile sunucuya dokunduğunda. İstek, genel `www/` dizininde bulunan tek bir PHP dosyasına, yani `index.php`'ye yönlendirilir. Diyelim ki istek `https://example.com/product/123` adresine yapıldı. Uygun [sunucu yapılandırması |nette:troubleshooting#Sunucu Kullanıcı Dostu URL ler İçin Nasıl Ayarlanır] sayesinde, bu URL bile `index.php` dosyasına eşlenir ve yürütülür. +Her şey, kullanıcının tarayıcısında bir sayfa açmasıyla başlar. Tarayıcı sunucuya bir HTTP isteği gönderir. Bu istek, genel dizin `www/` içinde yer alan tek bir PHP dosyasını, yani `index.php`'yi hedefler. İsteğin `https://example.com/product/123` adresine geldiğini varsayalım. Uygun [sunucu yapılandırması |nette:troubleshooting#Güzel URL'ler İçin Sunucu Nasıl Yapılandırılır?] sayesinde bu URL de `index.php` dosyasına eşlenir ve o dosya çalıştırılır. Görevi şudur: -1) ortamı başlatmak -2) fabrikayı almak -3) isteği işleyecek Nette uygulamasını başlatmak +1) ortamı hazırlamak +2) factory'yi elde etmek +3) isteği işleyen Nette uygulamasını çalıştırmak -Hangi fabrika? Traktör üretmiyoruz, web sayfaları üretiyoruz! Sabırlı olun, hemen açıklanacak. +Ne factory'si? Traktör üretmiyoruz, web siteleri yapıyoruz! Sabırlı olun, birazdan açıklanacak. -"Ortamı başlatmak" ifadesiyle, örneğin hataları günlüğe kaydetmek veya görselleştirmek için harika bir araç olan [Tracy|tracy:]'nin etkinleştirilmesini kastediyoruz. Üretim sunucusunda hataları günlüğe kaydeder, geliştirme sunucusunda doğrudan görüntüler. Dolayısıyla başlatma, web sitesinin üretim veya geliştirme modunda çalışıp çalışmadığına karar vermeyi de içerir. Bunun için Nette [akıllı otomatik algılama |bootstrapping#Geliştirme vs Üretim Modu] kullanır: web sitesini localhost'ta çalıştırırsanız, geliştirme modunda çalışır. Bu nedenle hiçbir şey yapılandırmanıza gerek yoktur ve uygulama hem geliştirme hem de canlı dağıtım için hemen hazırdır. Bu adımlar [Bootstrap sınıfı|bootstrapping] hakkındaki bölümde gerçekleştirilir ve ayrıntılı olarak açıklanır. +"Ortamın hazırlanması" derken, örneğin hataları günlüklemek veya görselleştirmek için harika bir araç olan [Tracy|tracy:]'nin etkinleştirilmesini kastediyoruz. Üretim sunucusunda hataları günlükler, geliştirme ortamında ise doğrudan gösterir. Dolayısıyla hazırlık, sitenin üretim modunda mı yoksa geliştirme modunda mı çalıştığının belirlenmesini de kapsar. Nette bunun için [akıllı otomatik algılama |bootstrapping#Geliştirme modu ve üretim modu] kullanır: siteyi localhost'ta çalıştırırsanız geliştirme modunda çalışır. Hiçbir şey yapılandırmanız gerekmez ve uygulama hem geliştirmeye hem de canlı yayına hemen hazırdır. Bu adımlar [Bootstrap sınıfı|bootstrapping] bölümünde ayrıntılı olarak yapılır ve anlatılır. -Üçüncü nokta (evet, ikincisini atladık, ama ona geri döneceğiz) uygulamayı başlatmaktır. Nette'de HTTP isteklerini işlemekten `Nette\Application\Application` sınıfı (bundan sonra `Application` olarak anılacaktır) sorumludur, bu yüzden uygulamayı başlatmak dediğimizde, özellikle bu sınıfın nesnesinde anlamlı bir ada sahip `run()` metodunu çağırmayı kastediyoruz. +Üçüncü nokta (evet, ikinciyi atladık, ama ona döneceğiz) uygulamanın başlatılmasıdır. Nette'te HTTP isteklerinin işlenmesinden `Nette\Application\Application` sınıfı (bundan sonra `Application`) sorumludur. Yani uygulamayı çalıştır dediğimizde, tam olarak bu sınıfın bir nesnesi üzerinde adı yerinde olan `run()` metodunu çağırmayı kastediyoruz. -Nette, sizi kanıtlanmış metodolojilere göre temiz uygulamalar yazmaya yönlendiren bir akıl hocasıdır. Ve bu kesinlikle en kanıtlanmış olanlardan biri **dependency injection** (bağımlılık enjeksiyonu), kısaca DI olarak adlandırılır. Şu anda sizi DI'yi açıklamakla yormak istemiyoruz, bunun için [ayrı bir bölüm|dependency-injection:introduction] var, önemli olan sonuç, temel nesnelerin genellikle **DI konteyner** (kısaca DIC) olarak adlandırılan bir nesne fabrikası tarafından oluşturulacağıdır. Evet, bu az önce bahsedilen fabrika. Ve bize `Application` nesnesini de üretecek, bu yüzden önce konteynere ihtiyacımız var. Onu `Configurator` sınıfını kullanarak alırız ve `Application` nesnesini üretmesine izin veririz, üzerinde `run()` metodunu çağırırız ve böylece Nette uygulaması başlar. Tam olarak bu, [index.php |bootstrapping#index.php] dosyasında olur. +Nette bir akıl hocası gibi davranır ve sizi kanıtlanmış yöntemlere göre temiz uygulamalar yazmaya yönlendirir. Bunların en yerleşiklerinden biri kısaca DI denilen **bağımlılık enjeksiyonudur**. Şu anda sizi DI'yi açıklayarak yormak istemiyoruz; bunun için [ayrı bir bölüm|dependency-injection:introduction] var. Önemli sonucu şudur: anahtar nesneler genellikle **DI konteyneri** (veya DIC) olarak bilinen bir nesne factory'si tarafından oluşturulur. Evet, daha önce sözü geçen factory budur. `Application` nesnesini de bizim için o üretir, bu yüzden önce konteynere ihtiyacımız var. Onu `Configurator` sınıfıyla elde ederiz, `Application` nesnesini oluşturmasını sağlarız, üzerinde `run()` metodunu çağırırız ve böylece Nette uygulaması başlar. Tam olarak bu, [index.php |bootstrapping#index.php] dosyasında olan şeydir. Nette Application ================= -Application sınıfının tek bir görevi vardır: HTTP isteğine yanıt vermek. +`Application` sınıfının tek bir görevi vardır: HTTP isteğine yanıt vermek. -Nette'de yazılan uygulamalar, her biri web sitesinin belirli bir sayfasını temsil eden birçok sözde presenter'a (diğer framework'lerde controller terimiyle karşılaşabilirsiniz, aynı şeydir) bölünür: örn. ana sayfa; e-ticaretteki bir ürün; giriş formu; site haritası beslemesi vb. Bir uygulamanın bir ila binlerce presenter'ı olabilir. +Nette'te yazılan uygulamalar, presenter denilen birçok parçaya bölünür (başka framework'lerde "controller" terimiyle karşılaşabilirsiniz, aslında aynı şeydir). Bunlar, her biri sitenin belirli bir sayfasını temsil eden sınıflardır: örneğin ana sayfa, bir e-ticaret sitesindeki ürün, giriş formu, site haritası beslemesi vb. Bir uygulamada birden binlerce presenter olabilir. -Application, mevcut isteği işlemek için hangi presenter'a ileteceğine karar vermesi için sözde yönlendiriciye (router) sorarak başlar. Yönlendirici, bunun kimin sorumluluğu olduğuna karar verir. Giriş URL'si `https://example.com/product/123`'e bakar ve nasıl ayarlandığına bağlı olarak, bunun örneğin `id: 123` olan ürünü görüntüleme (**eylem**) isteyeceği **presenter** `Product`'ın işi olduğuna karar verir. Presenter + eylem çiftini iki nokta üst üste ile `Product:show` olarak yazmak iyi bir alışkanlıktır. +`Application`, geçerli isteği hangi presenter'ın işleyeceğine karar vermesi için önce router denilen bileşene sorar. Sorumluluğu router belirler. Girdi olarak gelen `https://example.com/product/123` URL'sini inceler ve yapılandırmasına göre bu işin örneğin `Product` **presenter**'ına ait olduğuna, onun da `id: 123` olan ürün için `show` **eylemini** gerçekleştirmesi gerektiğine karar verir. Presenter + eylem çiftini iki nokta üst üste ile ayırarak yazmak iyi bir alışkanlıktır, örneğin `Product:show`. -Yani yönlendirici, URL'yi `Presenter:action` + parametreler çiftine dönüştürdü, bizim durumumuzda `Product:show` + `id: 123`. Böyle bir yönlendiricinin nasıl göründüğünü `app/Core/RouterFactory.php` dosyasında görebilirsiniz ve onu [Yönlendirme|Routing] bölümünde ayrıntılı olarak açıklıyoruz. +Böylece router, URL'yi `Presenter:eylem` çiftine + parametrelere dönüştürdü; bizim durumumuzda `Product:show` + `id: 123`. Böyle bir router'ın neye benzediğini `app/Core/RouterFactory.php` dosyasında görebilirsiniz, ayrıntılı olarak da [Yönlendirme |Routing] bölümünde anlatıyoruz. -Devam edelim. Application artık presenter'ın adını biliyor ve devam edebilir. `ProductPresenter` sınıfının nesnesini üreterek, ki bu presenter `Product`'ın kodudur. Daha doğrusu, presenter'ı üretmesi için DI konteynerine sorar, çünkü üretmek onun işidir. +Devam edelim. `Application` artık presenter'ın adını biliyor ve ilerleyebilir. Bunu, `Product` presenter'ının kodunu içeren `ProductPresenter` sınıfının bir örneğini oluşturarak yapar. Daha doğrusu, presenter'ı oluşturmasını DI konteynerinden ister, çünkü nesne oluşturmak onun işidir. Presenter şöyle görünebilir: @@ -109,92 +109,92 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function renderShow(int $id): void { - // modelden verileri alır ve şablona iletiriz + // model'den veriyi al ve şablona aktar $this->template->product = $this->repository->getProduct($id); } } ``` -İsteğin işlenmesini presenter devralır. Ve görev açıktır: `id: 123` ile `show` eylemini gerçekleştirin. Bu, presenter'ların dilinde, `renderShow()` metodunun çağrılacağı ve `$id` parametresinde `123` alacağı anlamına gelir. +İsteğin işlenmesini presenter devralır. Görev açıktır: `id: 123` ile `show` eylemini yürütmek. Presenter terminolojisinde bu, `$id` parametresinde `123` alan `renderShow()` metodunun çağrılması demektir. -Presenter birden fazla eylemi işleyebilir, yani birden fazla `render()` metoduna sahip olabilir. Ancak, bir veya mümkün olduğunca az eyleme sahip presenter'lar tasarlamanızı öneririz. +Bir presenter birden fazla eylemi işleyebilir, yani birden fazla `render()` metoduna sahip olabilir. Ancak presenter'ları tek veya olabildiğince az eylemle tasarlamanızı öneririz. -Yani, `renderShow(123)` metodu çağrıldı, kodu hayali bir örnek olsa da, verilerin şablona nasıl iletildiğini, yani `$this->template`'e yazılarak görebilirsiniz. +Böylece `renderShow(123)` metodu çağrıldı. Kodu hayali bir örnektir, ama verinin şablona nasıl aktarıldığını, yani `$this->template`'e yazılarak aktarıldığını gösterir. -Ardından presenter yanıtı döndürür. Bu bir HTML sayfası, bir resim, bir XML belgesi, diskten bir dosya gönderimi, JSON veya başka bir sayfaya yönlendirme olabilir. Önemli olan, açıkça nasıl yanıt vereceğini söylemezsek (ki bu `ProductPresenter` durumudur), yanıtın bir HTML sayfasıyla şablonun oluşturulması olacağıdır. Neden? Çünkü vakaların %99'unda bir şablon oluşturmak istiyoruz, bu yüzden presenter bu davranışı varsayılan olarak kabul eder ve işimizi kolaylaştırmak ister. Nette'nin amacı budur. +Ardından presenter bir yanıt döndürür. Bu bir HTML sayfası, bir görsel, bir XML belgesi, diskten bir dosyanın gönderilmesi, JSON veya belki başka bir sayfaya yönlendirme olabilir. Önemlisi, nasıl yanıt verileceğini açıkça belirtmezsek (ki `ProductPresenter`'da durum budur), yanıt bir şablonun HTML sayfasına render edilmesi olacaktır. Neden? Çünkü vakaların %99'unda bir şablon render etmek isteriz. Bu yüzden presenter, işimizi kolaylaştırmak için bu davranışı varsayılan olarak benimser. Nette'in özü budur. -Hangi şablonun oluşturulacağını bile belirtmemize gerek yok, yolunu kendisi türetir. `show` eylemi durumunda, `ProductPresenter` sınıfıyla aynı dizindeki `show.latte` şablonunu basitçe yüklemeye çalışır. Ayrıca `@layout.latte` dosyasındaki düzeni bulmaya çalışır ([şablonları bulma |templates#Şablonları Bulma] hakkında daha fazla bilgi). +Hangi şablonun render edileceğini belirtmemiz bile gerekmez; framework yolu kendiliğinden çıkarır. `show` eylemi durumunda, `ProductPresenter` sınıfıyla aynı dizinde yer alan `show.latte` şablonunu yüklemeyi dener. Layout'u da `@layout.latte` dosyasında bulmaya çalışır ([şablon arama |templates#Şablon arama] hakkında daha fazla ayrıntı). -Ve ardından şablonları oluşturur. Böylece presenter'ın ve tüm uygulamanın görevi tamamlanır ve iş biter. Şablon mevcut değilse, 404 hata sayfası döndürülür. Presenter'lar hakkında daha fazla bilgiyi [Presenter'lar|presenters] sayfasında okuyabilirsiniz. +Sonra şablonlar render edilir. Bununla presenter'ın ve tüm uygulamanın görevi tamamlanır. Şablon yoksa 404 hata sayfası döndürülür. Presenter'lar hakkında daha fazlasını [Presenter'lar|presenters] sayfasında öğrenebilirsiniz. [* request-flow.svg *] Emin olmak için, tüm süreci biraz farklı bir URL ile özetleyelim: -1) URL `https://example.com` olacak -2) uygulamayı başlatırız, konteyner oluşturulur ve `Application::run()` çalıştırılır -3) yönlendirici URL'yi `Home:default` çifti olarak kodlar -4) `HomePresenter` sınıfının nesnesi oluşturulur -5) `renderDefault()` metodu çağrılır (varsa) -6) örneğin `@layout.latte` düzeniyle `default.latte` şablonu oluşturulur +1) URL `https://example.com` +2) Uygulama başlar, DI konteyneri oluşturulur ve `Application::run()` çalıştırılır. +3) Router, URL'yi `Home:default` çiftine çözer. +4) `HomePresenter` sınıfının bir örneği oluşturulur. +5) `renderDefault()` metodu çağrılır (varsa). +6) Şablon, örneğin `default.latte`, layout ile birlikte, örneğin `@layout.latte`, render edilir. -Şimdi birçok yeni terimle karşılaşmış olabilirsiniz, ancak anlamlı olduklarına inanıyoruz. Nette'de uygulama oluşturmak son derece keyiflidir. +Az önce birçok yeni kavramla karşılaşmış olabilirsiniz, ama bunların anlamlı olduğuna inanıyoruz. Nette'te uygulama geliştirmek son derece basittir. Şablonlar ========= -Şablonlardan bahsetmişken, Nette'de [Latte |latte:] şablonlama sistemi kullanılır. Bu nedenle şablonlarda `.latte` uzantıları bulunur. Latte kullanılır çünkü hem PHP için en güvenli şablonlama sistemidir hem de en sezgisel sistemdir. Çok fazla yeni şey öğrenmenize gerek yok, PHP bilginiz ve birkaç etiket yeterlidir. Her şeyi [belgelerde |templates] öğreneceksiniz. +Şablonlardan söz açılmışken, Nette [Latte |latte:] şablon sistemini kullanır. Bu yüzden şablon dosyalarının uzantısı `.latte`'dir. Latte, hem PHP için en güvenli şablon sistemi olduğu hem de en sezgisel olduğu için tercih edilir. Yeni çok şey öğrenmeniz gerekmez; PHP bilgisi ve birkaç etiket yeterlidir. İhtiyacınız olan her şeyi [dokümantasyonda |templates] bulacaksınız. -Şablonda, diğer presenter'lara ve eylemlere [bağlantılar oluşturulur |creating-links] şu şekilde: +Şablonda başka presenter'lara ve eylemlere şöyle [bağlantı oluşturursunuz |creating-links]: ```latte ürün detayı ``` -Sadece gerçek URL yerine bilinen `Presenter:action` çiftini yazın ve olası parametreleri belirtin. İşin püf noktası, bu niteliğin Nette tarafından işleneceğini söyleyen `n:href`'tir. Ve şunu oluşturur: +Gerçek URL yerine tanıdık `Presenter:eylem` çiftini yazın ve gerekli parametreleri ekleyin. İşin püf noktası, Nette'e bu niteliği işlemesini söyleyen `n:href`'tir. Şunu üretecektir: ```latte ürün detayı ``` -URL oluşturmaktan daha önce bahsedilen yönlendirici sorumludur. Aslında, Nette'deki yönlendiriciler olağanüstüdür çünkü yalnızca URL'den presenter:action çiftine dönüşümler yapmakla kalmaz, aynı zamanda tersini de yapabilirler, yani presenter adı + eylem + parametrelerden bir URL oluşturabilirler. Bu sayede Nette'de, şablonda veya presenter'da tek bir karakteri bile değiştirmeden tüm hazır uygulamanın URL şekillerini tamamen değiştirebilirsiniz. Sadece yönlendiriciyi düzenleyerek. Ayrıca bu sayede, Nette'nin başka bir benzersiz özelliği olan ve farklı URL'lerde yinelenen içeriğin varlığını otomatik olarak önleyerek daha iyi SEO'ya (arama motoru optimizasyonu) katkıda bulunan sözde kanonikleştirme çalışır. Birçok programcı bunu şaşırtıcı buluyor. +URL üretimini yukarıda sözü geçen router üstlenir. Nette'teki router'lar olağanüstüdür, çünkü yalnızca URL'den `Presenter:eylem` çiftine dönüşümü değil, tersini de yapabilirler: presenter adından, eylemden ve parametrelerden URL üretebilirler. Bu sayede, Nette'te tamamlanmış bir uygulamanın tümündeki URL formatını, şablonlarda veya presenter'larda tek bir karakteri bile değiştirmeden, yalnızca router'ı değiştirerek tamamen dönüştürebilirsiniz. Bu ayrıca, aynı içeriğin farklı URL'lerde var olmasını otomatik olarak engelleyerek SEO'yu (arama motoru optimizasyonunu) iyileştiren, Nette'e özgü bir başka özellik olan kanonikleştirmeyi mümkün kılar. Birçok programcı bu yeteneği hayranlıkla karşılıyor. -Interaktif Bileşenler -===================== +Etkileşimli bileşenler +====================== -Presenter'lar hakkında size bir şey daha söylemeliyiz: yerleşik bir bileşen sistemleri vardır. Delphi veya ASP.NET Web Forms'tan aşina olanlar benzer bir şey hatırlayabilir, React veya Vue.js de uzaktan benzer bir şeye dayanmaktadır. PHP framework dünyasında bu tamamen benzersiz bir özelliktir. +Presenter'lar hakkında size bir şey daha söylememiz gerekiyor: yerleşik bir bileşen sistemine sahiptirler. Daha deneyimli olanlar buna benzer bir şeyi Delphi'den veya ASP.NET Web Forms'tan hatırlayabilir; React veya Vue.js de bir ölçüde ilgili kavramlar üzerine kurulmuştur. PHP framework'leri dünyasında bu tamamen eşsiz bir özelliktir. -Bileşenler, sayfalara (yani presenter'lara) eklediğimiz bağımsız, yeniden kullanılabilir birimlerdir. Bunlar [formlar |forms:in-presenter], [veri ızgaraları |https://componette.org/contributte/datagrid/], menüler, oylama anketleri, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir. Kendi bileşenlerimizi oluşturabilir veya [geniş yelpazedeki |https://componette.org] açık kaynaklı bileşenlerden bazılarını kullanabiliriz. +Bileşenler, sayfalara (yani presenter'lara) gömdüğümüz bağımsız, yeniden kullanılabilir birimlerdir. Bunlar [formlar |forms:in-presenter], [datagrid'ler |https://componette.org/contributte/datagrid/], menüler, anketler, kısacası yeniden kullanmanın anlamlı olduğu her şey olabilir. Kendi bileşenlerimizi oluşturabilir veya açık kaynak bileşenlerin [geniş seçkisinden |https://componette.org] yararlanabiliriz. -Bileşenler, uygulama oluşturma yaklaşımını temelden etkiler. Size sayfaları önceden hazırlanmış birimlerden oluşturma konusunda yeni olanaklar sunarlar. Ve ayrıca [Hollywood |components#Hollywood Tarzı] ile ortak bir yanları vardır. +Bileşenler, uygulama geliştirmeye yaklaşımı temelden etkiler. Sayfaları önceden hazırlanmış birimlerden oluşturmak için yeni olanaklar açarlar. Ve [Hollywood |components#Hollywood tarzı] ile de ortak bir yanları vardır. -DI Konteyneri ve Yapılandırma +DI konteyneri ve yapılandırma ============================= -DI konteyneri veya nesne fabrikası, tüm uygulamanın kalbidir. +DI konteyneri, yani nesne factory'si, tüm uygulamanın kalbidir. -Endişelenmeyin, önceki satırlardan görünebileceği gibi sihirli bir kara kutu değildir. Aslında, Nette tarafından oluşturulan ve önbellek dizinine kaydedilen oldukça sıkıcı bir PHP sınıfıdır. `createServiceAbcd()` gibi adlandırılmış birçok metodu vardır ve her biri belirli bir nesneyi üretebilir ve döndürebilir. Evet, uygulamayı başlatmak için `index.php` dosyasında ihtiyaç duyduğumuz `Nette\Application\Application`'ı üreten `createServiceApplication()` metodu da vardır. Ve bireysel presenter'ları üreten metotlar da vardır. Ve bu böyle devam eder. +Merak etmeyin, önceki satırların düşündürebileceği gibi sihirli bir kara kutu değildir. Gerçekte, Nette tarafından üretilen ve önbellek dizininde saklanan oldukça sıradan bir PHP sınıfıdır. `createServiceAbcd()` gibi adlandırılmış, her biri belirli bir nesneyi oluşturup döndürebilen birçok metot içerir. Evet, uygulamayı çalıştırmak için `index.php`'de ihtiyaç duyduğumuz `Nette\Application\Application` örneğini üreten bir `createServiceApplication__application()` metodu da vardır. Ayrıca tek tek presenter'ları oluşturan metotlar da bulunur, vb. -DI konteynerinin oluşturduğu nesnelere bir nedenden dolayı servis denir. +DI konteynerinin oluşturduğu nesnelere, bir nedenle, servis denir. -Bu sınıfın gerçekten özel olan yanı, onu sizin değil, framework'ün programlamasıdır. Gerçekten PHP kodunu oluşturur ve diske kaydeder. Siz sadece konteynerin hangi nesneleri üretebileceği ve tam olarak nasıl üreteceği konusunda talimatlar verirsiniz. Ve bu talimatlar, [NEON|neon:format] formatının kullanıldığı ve dolayısıyla `.neon` uzantısına sahip olan [yapılandırma dosyalarında|bootstrapping] yazılır. +Bu sınıfın gerçekten özel yanı, onu sizin programlamamanızdır; framework programlar. Gerçekten PHP kodunu üretir ve diske kaydeder. Siz yalnızca konteynerin hangi nesneleri ve tam olarak nasıl oluşturabilmesi gerektiğine dair talimat verirsiniz. Bu talimatlar [yapılandırma dosyalarına |bootstrapping#DI konteynerinin yapılandırması] yazılır; bu dosyalar [NEON|neon:format] formatını kullanır ve bu yüzden `.neon` uzantısı taşır. -Yapılandırma dosyaları yalnızca DI konteynerini bilgilendirmek için kullanılır. Yani örneğin, [session |http:configuration#Oturum Session] bölümünde `expiration: 14 days` seçeneğini belirtirsem, DI konteyneri oturumu temsil eden `Nette\Http\Session` nesnesini oluştururken onun `setExpiration('14 days')` metodunu çağırır ve böylece yapılandırma gerçeğe dönüşür. +Yapılandırma dosyaları yalnızca DI konteynerine talimat vermeye yarar. Yani örneğin [session |http:configuration#Oturum] bölümünde `expiration: 14 days` seçeneğini belirtirseniz, DI konteyneri oturumu temsil eden `Nette\Http\Session` nesnesini oluştururken onun `setExpiration('14 days')` metodunu çağırır ve böylece yapılandırmayı gerçeğe dönüştürür. -Nelerin [yapılandırılabileceğini |nette:configuring] ve kendi [servislerinizi nasıl tanımlayacağınızı |dependency-injection:services] açıklayan tam bir bölüm sizin için hazırlanmıştır. +Neyin [yapılandırılabileceğini |nette:configuring] ve [kendi servislerinizi nasıl tanımlayacağınızı |dependency-injection:services] anlatan koca bir bölüm sizi bekliyor. -Servis oluşturmaya biraz daldığınızda, [autowiring |dependency-injection:autowiring] kelimesiyle karşılaşırsınız. Bu, hayatınızı inanılmaz derecede basitleştiren bir özelliktir. Nesneleri ihtiyaç duyduğunuz yerlere (örneğin sınıflarınızın kurucularına) otomatik olarak iletebilir, hiçbir şey yapmanıza gerek kalmadan. Nette'deki DI konteynerinin küçük bir mucize olduğunu keşfedeceksiniz. +Servis oluşturmaya biraz daldığınızda [autowiring |dependency-injection:autowiring] terimiyle karşılaşacaksınız. Bu, hayatınızı inanılmaz kolaylaştıracak bir özelliktir. Nesneleri, siz hiçbir şey yapmadan, ihtiyaç duyduğunuz yere (örneğin sınıflarınızın constructor'larına) otomatik olarak aktarabilir. Nette'teki DI konteynerinin küçük bir mucize olduğunu keşfedeceksiniz. -Nereye Devam Edelim? -==================== +Sırada ne var? +============== -Nette'deki uygulamaların temel prensiplerini gözden geçirdik. Henüz çok yüzeysel, ancak yakında derinlemesine dalacak ve zamanla harika web uygulamaları oluşturacaksınız. Nereye devam etmeli? [İlk Uygulamamızı Yazıyoruz|quickstart:] eğitimini denediniz mi? +Nette uygulamalarının temel ilkelerini ele aldık. Şimdilik yüzeysel bir bakış oldu, ama yakında daha derine inecek ve zamanla harika web uygulamaları oluşturacaksınız. Peki bundan sonra nereye? [İlk uygulamanızı oluşturun|quickstart:] eğitimini denediniz mi? -Yukarıda açıklananlara ek olarak, Nette [kullanışlı sınıflar|utils:], [veritabanı katmanı|database:] vb. içeren tam bir cephaneliğe sahiptir. Sadece belgeleri veya [blogu|https://blog.nette.org] gözden geçirmeyi deneyin. Birçok ilginç şey keşfedeceksiniz. +Yukarıda anlatılanların yanı sıra Nette, koca bir [kullanışlı sınıf|utils:] cephaneliği, bir [veritabanı katmanı|database:] ve daha fazlasını sunar. Dokümantasyonu tıklayarak gezmeyi deneyin. Ya da [blog|https://blog.nette.org]'a uğrayın. Birçok ilginç şey keşfedeceksiniz. -Framework'ün size bolca neşe getirmesini dileriz 💙 +Framework size bol keyif getirsin 💙 diff --git a/application/tr/multiplier.texy b/application/tr/multiplier.texy index 746642ce3e..98a05df472 100644 --- a/application/tr/multiplier.texy +++ b/application/tr/multiplier.texy @@ -1,24 +1,24 @@ -Multiplier: Dinamik Bileşenler +Multiplier: dinamik bileşenler ****************************** .[perex] -Etkileşimli bileşenlerin dinamik olarak oluşturulması için bir araç +Etkileşimli bileşenleri dinamik olarak oluşturmak için bir araç. -Tipik bir örnekten başlayalım: bir e-ticaret sitesinde ürün listemiz var ve her biri için sepete ürün eklemek için bir form yazdırmak istiyoruz. Olası seçeneklerden biri, tüm listeyi tek bir formla sarmaktır. Ancak, [api:Nette\Application\UI\Multiplier] bize çok daha uygun bir yol sunar. +Tipik bir örnekle başlayalım: bir e-ticaret sitesinde ürün listesi düşünün; her ürün için bir "Sepete ekle" formu istiyorsunuz. Olası bir yaklaşım, tüm listeyi tek bir forma sarmaktır. Ancak çok daha kullanışlı bir yöntemi [api:Nette\Application\UI\Multiplier] sunar. -Multiplier, birden fazla bileşen için bir fabrika tanımlamayı kolaylaştırır. İç içe geçmiş bileşenler prensibine göre çalışır - [api:Nette\ComponentModel\Container]'dan miras alan her bileşen başka bileşenler içerebilir. +Multiplier, birden fazla bileşen için rahatça bir factory tanımlamanızı sağlar. İç içe bileşenler ilkesiyle çalışır: [api:Nette\ComponentModel\Container]'dan kalıtım alan her bileşen başka bileşenler içerebilir. .[tip] -Belgelerdeki [bileşen modeli |components#Bileşenler Derinlemesine] bölümüne veya [Honza Tvrdík'in sunumuna|https://www.youtube.com/watch?v=8y3LLexWu-I] bakın. +Dokümantasyondaki [bileşen modeli |components#Bileşenler derinlemesine] bölümüne bakın. -Multiplier'ın özü, kurucuda iletilen bir geri çağırma (callback) kullanarak yavrularını dinamik olarak oluşturabilen bir ebeveyn konumunda hareket etmesidir. Örneğe bakın: +Multiplier'ın özü, constructor'a verilen bir callback yardımıyla çocuklarını dinamik olarak oluşturabilen bir ebeveyn gibi davranmasıdır. Örneğe bakın: ```php protected function createComponentShopForm(): Multiplier { return new Multiplier(function () { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Ürün sayısı:') + $form->addInteger('amount', 'Miktar:') ->setRequired(); $form->addSubmit('send', 'Sepete ekle'); return $form; @@ -26,7 +26,7 @@ protected function createComponentShopForm(): Multiplier } ``` -Şimdi şablonda her ürün için formu kolayca oluşturabiliriz - ve her biri gerçekten benzersiz bir bileşen olacaktır. +Artık şablonda formu her ürün için kolayca render edebiliriz ve her biri gerçekten benzersiz bir bileşen olacaktır. ```latte {foreach $items as $item} @@ -37,23 +37,23 @@ protected function createComponentShopForm(): Multiplier {/foreach} ``` -`{control}` etiketinde iletilen argüman şu formatta: +`{control}` etiketinde verilen argüman şu anlama gelen bir formatı izler: -1. `shopForm` bileşenini al -2. ve ondan `$item->id` yavrusunu al +1. `shopForm` bileşenini al. +2. Ondan `$item->id` adlı çocuğu al. -**1.** noktasının ilk çağrısında `shopForm` henüz mevcut değildir, bu yüzden fabrikası `createComponentShopForm` çağrılır. Alınan bileşen (Multiplier örneği) üzerinde daha sonra belirli formun fabrikası çağrılır - ki bu, Multiplier'a kurucuda ilettiğimiz anonim fonksiyondur. +**1.** noktasının ilk çağrısında `shopForm` bileşeni henüz yoktur, bu yüzden onun factory'si `createComponentShopForm` çağrılır. Sonra elde edilen bileşen (bir Multiplier örneği) üzerinde, somut form için factory çağrılır; bu da Multiplier'ın constructor'ına verdiğimiz anonim fonksiyondur. -Foreach'in bir sonraki yinelemesinde, `createComponentShopForm` metodu artık çağrılmaz (bileşen mevcuttur), ancak farklı bir yavrusunu aradığımız için (`$item->id` her yinelemede farklı olacaktır), anonim fonksiyon tekrar çağrılır ve bize yeni bir form döndürür. +Foreach döngüsünün bir sonraki turunda `createComponentShopForm` metodu tekrar çağrılmaz (bileşen zaten vardır). Ancak farklı bir çocuk aradığımız için (her turda `$item->id` farklı olacağından), anonim fonksiyon tekrar çağrılır ve yeni bir form döndürür. -Geriye kalan tek şey, formun sepete gerçekten eklemesi gereken ürünü eklemesini sağlamaktır - şu anda her ürün için form tamamen aynıdır. Multiplier'ın (ve genel olarak Nette Framework'teki her bileşen fabrikasının) özelliği bize yardımcı olur, yani her fabrikanın ilk argümanı olarak oluşturulan bileşenin adını almasıdır. Bizim durumumuzda bu `$item->id` olacaktır, ki bu tam olarak ihtiyacımız olan bilgidir. Bu nedenle, form oluşturmayı hafifçe ayarlamak yeterlidir: +Geriye tek bir şey kalıyor: formun sepete doğru ürünü eklediğinden emin olmak; şu anda form her ürün için aynı. Burada bize Multiplier'ın (ve genel olarak Nette Framework'teki her bileşen factory'sinin) bir özelliği yardımcı olur: her factory, ilk argüman olarak oluşturulan bileşenin adını alır. Multiplier factory'si ayrıca ikinci argüman olarak Multiplier örneğinin kendisini alır. Bizim durumumuzda ilk argüman `$item->id` olacak, ki tam da ihtiyacımız olan bilgi budur. Yani formun oluşturulmasını biraz değiştirmemiz yeterli: ```php protected function createComponentShopForm(): Multiplier { - return new Multiplier(function ($itemId) { + return new Multiplier(function (string $itemId) { $form = new Nette\Application\UI\Form; - $form->addInteger('count', 'Ürün sayısı:') + $form->addInteger('amount', 'Miktar:') ->setRequired(); $form->addHidden('itemId', $itemId); $form->addSubmit('send', 'Sepete ekle'); diff --git a/application/tr/presenters.texy b/application/tr/presenters.texy index 2e9d6aa932..842739248d 100644 --- a/application/tr/presenters.texy +++ b/application/tr/presenters.texy @@ -3,37 +3,37 @@ Presenter'lar
    -Nette'de presenter'ların ve şablonların nasıl yazıldığını öğreneceğiz. Okuduktan sonra şunları bileceksiniz: +Nette'te presenter'ların ve şablonların nasıl yazıldığını inceleyeceğiz. Okuduktan sonra şunları anlayacaksınız: -- presenter nasıl çalışır -- kalıcı parametreler nedir -- şablonlar nasıl oluşturulur +- presenter'ların nasıl çalıştığını +- kalıcı parametrelerin ne olduğunu +- şablonların nasıl render edildiğini
    -[Zaten biliyoruz |how-it-works#Nette Application], presenter'ın bir web uygulamasının belirli bir sayfasını temsil eden bir sınıf olduğunu, örn. ana sayfa; e-ticaretteki bir ürün; giriş formu; site haritası beslemesi vb. Bir uygulamanın bir ila binlerce presenter'ı olabilir. Diğer framework'lerde bunlara controller da denir. +[Presenter'ın |how-it-works#Nette Application] bir web uygulamasının belirli bir sayfasını (ana sayfa, e-ticaret sitesindeki bir ürün, giriş formu, site haritası beslemesi vb.) temsil eden bir sınıf olduğunu artık biliyoruz. Bir uygulamada birden binlerce presenter olabilir. Başka framework'lerde bunlara controller da denir. -Genellikle presenter terimiyle, web arayüzleri oluşturmak için uygun olan ve bu bölümün geri kalanında ele alacağımız [api:Nette\Application\UI\Presenter] sınıfının bir alt sınıfı kastedilir. Genel anlamda, presenter [api:Nette\Application\IPresenter] arayüzünü uygulayan herhangi bir nesnedir. +Genellikle presenter terimiyle, web arayüzleri üretmeye uygun olan ve bu bölümün geri kalanının odağı olacak [api:Nette\Application\UI\Presenter] sınıfının bir torununu kastederiz. Genel anlamda presenter, [api:Nette\Application\IPresenter] arayüzünü uygulayan herhangi bir nesnedir. -Presenter Yaşam Döngüsü -======================= +Presenter'ın yaşam döngüsü +========================== -Presenter'ın görevi, isteği işlemek ve bir yanıt döndürmektir (bu bir HTML sayfası, bir resim, bir yönlendirme vb. olabilir). +Presenter'ın görevi bir isteği işlemek ve bir yanıt döndürmektir (bu bir HTML sayfası, bir görsel, bir yönlendirme vb. olabilir). -Yani başlangıçta ona bir istek iletilir. Bu doğrudan bir HTTP isteği değil, HTTP isteğinin yönlendirici yardımıyla dönüştürüldüğü [api:Nette\Application\Request] nesnesidir. Bu nesneyle genellikle temas etmeyiz, çünkü presenter isteğin işlenmesini akıllıca şimdi göstereceğimiz diğer metotlara devreder. +Yani önce ona bir istek verilir. Bu doğrudan HTTP isteği değil, HTTP isteğinin router yardımıyla dönüştürüldüğü bir [api:Nette\Application\Request] nesnesidir. Bu nesneyle genellikle doğrudan uğraşmayız, çünkü presenter isteğin işlenmesini şimdi inceleyeceğimiz başka metotlara akıllıca devreder. -[* lifecycle.svg *] *** *Presenter yaşam döngüsü* .<> +[* lifecycle.svg *] *** Presenter'ın yaşam döngüsü .<> -Resim, mevcutsa yukarıdan aşağıya sırayla çağrılan metotların bir listesini temsil eder. Hiçbirinin mevcut olması gerekmez, tek bir metodu olmayan tamamen boş bir presenter'a sahip olabilir ve üzerine basit bir statik web sitesi kurabiliriz. +Şema, varsa yukarıdan aşağıya sırayla çağrılan metotların listesini gösterir. Hiçbiri zorunlu değildir; tek bir metodu olmayan tamamen boş bir presenter'ınız olabilir ve üzerine basit bir statik site kurabilirsiniz. `__construct()` --------------- -Kurucu, nesnenin oluşturulma anında çağrıldığı için tam olarak presenter yaşam döngüsüne ait değildir. Ancak önemi nedeniyle bahsediyoruz. Kurucu ([inject metodu|best-practices:inject-method-attribute] ile birlikte) bağımlılıkları iletmek için kullanılır. +Constructor, nesnenin oluşturulduğu anda çağrıldığı için tam olarak presenter'ın yaşam döngüsüne ait değildir. Ancak önemi nedeniyle onu da anıyoruz. Constructor ([inject metoduyla|best-practices:inject-method-attribute] birlikte) bağımlılıkları aktarmaya yarar. -Presenter, uygulamanın iş mantığını işlememeli, veritabanından yazıp okumamalı, hesaplamalar yapmamalı vb. Bunun için model olarak adlandırdığımız katmandan sınıflar vardır. Örneğin, `ArticleRepository` sınıfı makaleleri yüklemek ve kaydetmekten sorumlu olabilir. Presenter'ın onunla çalışabilmesi için, onu [bağımlılık enjeksiyonu |dependency-injection:passing-dependencies] aracılığıyla iletmesini ister: +Presenter, uygulamanın iş mantığını üstlenmemeli, veritabanına yazmamalı veya ondan okumamalı, hesaplama yapmamalıdır. Bu, model dediğimiz katmandaki sınıfların işidir. Örneğin bir `ArticleRepository` sınıfı makaleleri yüklemekten ve kaydetmekten sorumlu olabilir. Presenter'ın onunla çalışabilmesi için ona [bağımlılık enjeksiyonuyla aktarılması |dependency-injection:passing-dependencies] gerekir: ```php @@ -50,44 +50,47 @@ class ArticlePresenter extends Nette\Application\UI\Presenter `startup()` ----------- -İstek alındıktan hemen sonra `startup()` metodu çağrılır. Özellikleri başlatmak, kullanıcı izinlerini doğrulamak vb. için kullanabilirsiniz. Metodun her zaman atası `parent::startup()`'ı çağırması gerekir. +İstek alınır alınmaz `startup()` metodu çağrılır. Onu özellikleri ilklendirmek, kullanıcı izinlerini denetlemek vb. için kullanabilirsiniz. Bu metodun her zaman üst sınıfını çağırması gerekir: `parent::startup()`. -`action(args...)` .{toc: action()} --------------------------------------------------- +`action(args...)` .{toc: action()} +------------------------------------------------- + +`render()` metoduna benzer. `render()` ardından render edilecek belirli bir şablon için veri hazırlamayı amaçlarken, `action()` bir isteği, ardından mutlaka bir şablon render etmeden işler. Örneğin veriyi işleyebilir, kullanıcının oturumunu açıp kapatabilir vb. ve sonra [başka bir yere yönlendirebilir |#Yönlendirme]. -`render()` metodunun bir benzeri. `render()` belirli bir şablon için veri hazırlamak ve ardından onu oluşturmak için tasarlanmışken, `action()`'da istek şablon oluşturmaya bağlı kalmadan işlenir. Örneğin, veriler işlenir, kullanıcı giriş yapar veya çıkış yapar vb. ve ardından [başka bir yere yönlendirilir |#Yönlendirme]. +Önemli olan, `action()`'in `render()`'den *önce* çağrılmasıdır. Bu, eylem metodunun içinde isteğin gidişatını değiştirebilmemizi sağlar; örneğin `setView('digerGorunum')` ile render edilecek şablonu, hatta çağrılacak `render()` metodunu değiştirerek. -Önemli olan, `action()`'ın `render()`'dan önce çağrılmasıdır, bu yüzden içinde olayların sonraki seyrini değiştirebiliriz, yani oluşturulacak şablonu ve ayrıca çağrılacak `render()` metodunu değiştirebiliriz. Ve bunu `setView('jineView')` kullanarak yaparız. +.{data-version:3.2.3} +`switch('digerEylem')` metoduyla tamamen başka bir eyleme bile geçebilirsiniz. Bu, geçerli metodu keser ve onun yerine yeni eylemin `action()` ve `render()` metotlarını çalıştırır (ve otomatik [kanonikleştirmeyi|#Kanonikleştirme] kapatır). İsteğin kendisi devam eder; yalnızca o an çalışan metot kesilir. -Metoda istekten parametreler iletilir. Parametrelere tür belirtmek mümkündür ve önerilir, örn. `actionShow(int $id, ?string $slug = null)` - eğer `id` parametresi eksikse veya tamsayı değilse, presenter [404 hatası |#Hata 404 ve Benzerleri] döndürür ve çalışmayı sonlandırır. +Metoda istekten gelen parametreler aktarılır. Bu parametreler için tip belirtmek mümkündür ve önerilir, örneğin `actionShow(int $id, ?string $slug = null)`. `id` parametresi eksikse veya tam sayı değilse, presenter [404 hatası |#Hata 404 vb.] döndürür ve sonlanır. -`handle(args...)` .{toc: handle()} +`handle(args...)` .{toc: handle()} -------------------------------------------------- -Metot, [bileşenler |components#Sinyal] bölümünde tanışacağımız sözde sinyalleri işler. Aslında özellikle bileşenler ve AJAX isteklerinin işlenmesi için tasarlanmıştır. +Bu metot, [bileşenlere |components#Sinyal] ayrılmış bölümde öğreneceğimiz sinyalleri işler. Öncelikle bileşenler ve AJAX isteklerinin işlenmesi içindir. -Metoda, `action()` durumunda olduğu gibi, tür kontrolü dahil olmak üzere istekten parametreler iletilir. +Metoda, `action()`'de olduğu gibi, tip denetimi dahil, istekten gelen parametreler aktarılır. `beforeRender()` ---------------- -`beforeRender` metodu, adından da anlaşılacağı gibi, her `render()` metodundan önce çağrılır. Şablonun ortak yapılandırması, layout için değişkenlerin iletilmesi vb. için kullanılır. +`beforeRender` metodu, adının da söylediği gibi, her `render()` metodundan önce çağrılır. Ortak şablon ayarları, layout'a değişken aktarımı ve benzeri işler için kullanılır. -`render(args...)` .{toc: render()} ----------------------------------------------- +`render(args...)` .{toc: render()} +------------------------------------------------- -Şablonu sonraki oluşturma için hazırladığımız, ona veri ilettiğimiz vb. yer. +Burada şablonu ardından render edilmek üzere hazırlar, ona veri aktarırız vb. -Metoda, `action()` durumunda olduğu gibi, tür kontrolü dahil olmak üzere istekten parametreler iletilir. +Metoda, `action()`'de olduğu gibi, tip denetimi dahil, istekten gelen parametreler aktarılır. ```php public function renderShow(int $id): void { - // modelden verileri alır ve şablona iletiriz + // model'den veriyi al ve şablona aktar $this->template->article = $this->articles->getById($id); } ``` @@ -96,61 +99,83 @@ public function renderShow(int $id): void `afterRender()` --------------- -`afterRender` metodu, adından da anlaşılacağı gibi, her `render()` metodundan sonra çağrılır. Daha çok istisnai durumlarda kullanılır. +`afterRender` metodu, adının yine söylediği gibi, her `render()` metodundan sonra çağrılır. Oldukça ender kullanılır. `shutdown()` ------------ -Presenter yaşam döngüsünün sonunda çağrılır. +Presenter'ın yaşam döngüsünün sonunda çağrılır. + + +Olaylar +------- + +Presenter'ın yaşam döngüsünün parçası olarak çağrılan `startup()`, `beforeRender()` ve `shutdown()` metotlarının yanı sıra, otomatik çağrılacak başka fonksiyonlar da tanımlanabilir. Presenter, [olaylar |nette:glossary#Olaylar] denilen şeyleri tanımlar; işleyicilerini `$onStartup`, `$onRender` ve `$onShutdown` dizilerine eklersiniz. + +```php +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + public function __construct() + { + $this->onStartup[] = function () { + // ... + }; + } +} +``` + +`$onStartup` dizisindeki işleyiciler `startup()` metodundan hemen önce, `$onRender` işleyicileri `beforeRender()` ile `render()` arasında ve son olarak `$onShutdown` işleyicileri `shutdown()`'dan hemen önce çağrılır. -**Devam etmeden önce iyi bir tavsiye**. Gördüğünüz gibi presenter birden fazla eylemi/view'i işleyebilir, yani birden fazla `render()` metoduna sahip olabilir. Ancak, bir veya mümkün olduğunca az eyleme sahip presenter'lar tasarlamanızı öneririz. +**Devam etmeden önce bir öneri:** Gördüğünüz gibi bir presenter birden fazla eylemi/görünümü işleyebilir, yani birden fazla `render()` metoduna sahip olabilir. Ancak presenter'ları tek veya olabildiğince az eylemle tasarlamanızı öneririz. -Yanıt Gönderme +Yanıt gönderme ============== -Presenter'ın yanıtı genellikle [bir HTML sayfasıyla şablonun oluşturulmasıdır|templates], ancak bir dosya gönderimi, JSON veya başka bir sayfaya yönlendirme de olabilir. +Presenter'ın yanıtı genellikle [bir şablonun HTML sayfasına render edilmesidir|templates], ama bir dosya, JSON gönderimi, hatta başka bir sayfaya yönlendirme de olabilir. -Yaşam döngüsünün herhangi bir anında, aşağıdaki metotlardan biriyle bir yanıt gönderebilir ve aynı zamanda presenter'ı sonlandırabiliriz: +Yaşam döngüsünün herhangi bir noktasında, aşağıdaki metotlardan birini kullanarak bir yanıt gönderip presenter'ı aynı anda sonlandırabiliriz: -- `redirect()`, `redirectPermanent()`, `redirectUrl()` ve `forward()` [yönlendirir |#Yönlendirme] -- `error()` presenter'ı [bir hata nedeniyle |#Hata 404 ve Benzerleri] sonlandırır -- `sendJson($data)` presenter'ı sonlandırır ve verileri JSON formatında [gönderir |#JSON Gönderme] -- `sendTemplate()` presenter'ı sonlandırır ve hemen [şablonu oluşturur |templates] -- `sendResponse($response)` presenter'ı sonlandırır ve [özel bir yanıt |#Yanıtlar] gönderir +- `redirect()`, `redirectPermanent()`, `redirectUrl()` ve `forward()` bir [yönlendirme |#Yönlendirme] gerçekleştirir +- `error()` presenter'ı [bir hata nedeniyle |#Hata 404 vb.] sonlandırır +- `sendJson($data)` presenter'ı sonlandırır ve [veriyi |#JSON gönderme] JSON formatında gönderir +- `sendTemplate()` presenter'ı sonlandırır ve [şablonu |templates] hemen render eder +- `sendResponse($response)` presenter'ı sonlandırır ve [kendi yanıtınızı |#Yanıtlar] gönderir - `terminate()` presenter'ı yanıtsız sonlandırır -Bu metotlardan hiçbirini çağırmazsanız, presenter otomatik olarak şablonu oluşturmaya devam eder. Neden? Çünkü vakaların %99'unda bir şablon oluşturmak istiyoruz, bu yüzden presenter bu davranışı varsayılan olarak kabul eder ve işimizi kolaylaştırmak ister. +Bu metotların her biri, sessiz sonlandırma istisnası `Nette\Application\AbortException`'ı fırlatarak presenter'ı hemen sonlandırır. + +Bu metotlardan hiçbirini çağırmazsanız, presenter otomatik olarak şablonu render etmeye geçer. Neden? Çünkü vakaların %99'unda bir şablon render etmek isteriz, bu yüzden presenter işimizi kolaylaştırmak için bu davranışı varsayılan olarak benimser. -Bağlantı Oluşturma +Bağlantı oluşturma ================== -Presenter, diğer presenter'lara URL bağlantıları oluşturmak için kullanılabilecek `link()` metoduna sahiptir. İlk parametre hedef presenter ve eylemdir, ardından bir dizi olarak belirtilebilecek iletilen argümanlar gelir: +Presenter'ın, başka presenter'lara URL bağlantıları oluşturmaya yarayan bir `link()` metodu vardır. İlk parametre hedef presenter ve eylemdir, ardından dizi olarak da aktarılabilen argümanlar gelir: ```php $url = $this->link('Product:show', $id); -$url = $this->link('Product:show', [$id, 'lang' => 'tr']); +$url = $this->link('Product:show', [$id, 'lang' => 'en']); ``` -Şablonda, diğer presenter'lara ve eylemlere bağlantılar şu şekilde oluşturulur: +Şablonda başka presenter'lara ve eylemlere bağlantılar şöyle oluşturulur: ```latte ürün detayı ``` -Sadece gerçek URL yerine bilinen `Presenter:action` çiftini yazın ve olası parametreleri belirtin. İşin püf noktası, bu niteliğin Latte tarafından işleneceğini ve gerçek bir URL oluşturacağını söyleyen `n:href`'tir. Nette'de URL'leri hiç düşünmenize gerek yoktur, sadece presenter'ları ve eylemleri düşünmeniz yeterlidir. +Gerçek URL yerine tanıdık `Presenter:eylem` çiftini yazın ve gerekli parametreleri ekleyin. İşin püf noktası, Latte'ye bu niteliği işlemesini ve gerçek URL'yi üretmesini söyleyen `n:href`'tir. Nette'te URL'leri hiç düşünmeniz gerekmez, yalnızca presenter'ları ve eylemleri. -Daha fazla bilgi için [URL Bağlantıları Oluşturma|creating-links] bölümünde bulabilirsiniz. +Daha fazla bilgiyi [URL bağlantıları oluşturma|creating-links] bölümünde bulabilirsiniz. Yönlendirme =========== -Başka bir presenter'a geçmek için, [link() |#Bağlantı Oluşturma] metoduyla çok benzer bir sözdizimine sahip olan `redirect()` ve `forward()` metotları kullanılır. +Başka bir presenter'a geçmek için `redirect()` ve `forward()` metotları kullanılır. Sözdizimleri [link() |#Bağlantı oluşturma] metodununkine çok benzer. `forward()` metodu, HTTP yönlendirmesi olmadan hemen yeni presenter'a geçer: @@ -158,42 +183,42 @@ Başka bir presenter'a geçmek için, [link() |#Bağlantı Oluşturma] metoduyla $this->forward('Product:show'); ``` -HTTP kodu 302 (veya mevcut isteğin metodu POST ise 303) ile sözde geçici yönlendirme örneği: +HTTP kodu 302 ile geçici yönlendirme örneği (geçerli istek yöntemi POST ise 303): ```php $this->redirect('Product:show', $id); ``` -HTTP kodu 301 ile kalıcı yönlendirmeyi şu şekilde başarırsınız: +HTTP kodu 301 ile kalıcı yönlendirme için şunu kullanın: ```php $this->redirectPermanent('Product:show', $id); ``` -Uygulama dışındaki başka bir URL'ye `redirectUrl()` metoduyla yönlendirilebilir. İkinci parametre olarak HTTP kodu belirtilebilir, varsayılan 302'dir (veya mevcut isteğin metodu POST ise 303): +`redirectUrl()` metoduyla uygulamanın dışındaki başka bir URL'ye yönlendirebilirsiniz. HTTP kodu ikinci parametre olarak belirtilebilir; varsayılan 302'dir (geçerli istek yöntemi POST ise 303). ```php $this->redirectUrl('https://nette.org'); ``` -Yönlendirme, sözde sessiz sonlandırma istisnası `Nette\Application\AbortException` atarak presenter'ın etkinliğini hemen sonlandırır. +Yönlendirme, sessiz sonlandırma istisnası denilen `Nette\Application\AbortException`'ı fırlatarak presenter'ın çalışmasını hemen sonlandırır. -Yönlendirmeden önce, yönlendirmeden sonra şablonda görüntülenecek olan [#flash mesajları] (geçici mesajlar) gönderilebilir. +Yönlendirmeden önce [#Flash mesajları], yani yönlendirmeden sonra şablonda gösterilecek mesajlar gönderilebilir. -Flash Mesajları +Flash mesajları =============== -Bunlar genellikle bir işlemin sonucunu bildiren mesajlardır. Flash mesajlarının önemli bir özelliği, yönlendirmeden sonra bile şablonda kullanılabilir olmalarıdır. Görüntülendikten sonra bile 30 saniye daha canlı kalırlar - örneğin, hatalı bir aktarım nedeniyle kullanıcının sayfayı yenilemesi durumunda mesaj hemen kaybolmaz. +Bunlar genellikle bir işlemin sonucunu bildiren mesajlardır. Flash mesajlarının önemli bir özelliği, yönlendirmeden sonra da şablonda erişilebilir kalmalarıdır. Gösterildikten sonra 30 saniye daha etkin kalırlar; örneğin kullanıcı bir aktarım hatası yüzünden sayfayı yenilerse mesaj hemen kaybolmaz. -Sadece [flashMessage() |api:Nette\Application\UI\Control::flashMessage()] metodunu çağırmanız yeterlidir ve presenter şablona iletilmesini halleder. İlk parametre mesaj metni ve isteğe bağlı ikinci parametre türüdür (error, warning, info vb.). `flashMessage()` metodu, flash mesajının bir örneğini döndürür ve buna ek bilgiler eklenebilir. +[flashMessage() |api:Nette\Application\UI\Control::flashMessage()] metodunu çağırmanız yeterli, onu şablona aktarmayı presenter üstlenir. İlk parametre mesajın metni, isteğe bağlı ikinci parametre ise tipidir (örneğin error, warning, info). `flashMessage()` metodu flash mesajın örneğini döndürür, böylece ek bilgiler eklenebilir. ```php $this->flashMessage('Öğe silindi.'); -$this->redirect(/* ... */); // ve yönlendiririz +$this->redirect(/* ... */); // ve yönlendir ``` -Şablonda bu mesajlar `$flashes` değişkeninde `stdClass` nesneleri olarak bulunur ve `message` (mesaj metni), `type` (mesaj türü) özelliklerini içerir ve daha önce bahsedilen kullanıcı bilgilerini içerebilir. Onları örneğin şu şekilde oluştururuz: +Şablonda bu mesajlar `$flashes` değişkeninde, `message` (mesaj metni), `type` (mesaj tipi) ve muhtemelen daha önce sözü geçen kullanıcı bilgilerini içeren `stdClass` nesneleri olarak sunulur. Onları şöyle render ederiz: ```latte {foreach $flashes as $flash} @@ -202,10 +227,10 @@ $this->redirect(/* ... */); // ve yönlendiririz ``` -Hata 404 ve Benzerleri -====================== +Hata 404 vb. +============ -İstek yerine getirilemiyorsa, örneğin görüntülemek istediğimiz makale veritabanında mevcut olmadığı için, `error(?string $message = null, int $httpCode = 404)` metoduyla 404 hatası atarız. +İstek yerine getirilemiyorsa, örneğin göstermek istediğimiz makale veritabanında yoksa, `error(string $message = '', int $httpCode = 404)` metoduyla 404 hatası fırlatırız. ```php public function renderShow(int $id): void @@ -218,13 +243,13 @@ public function renderShow(int $id): void } ``` -Hatanın HTTP kodu ikinci parametre olarak iletilebilir, varsayılan 404'tür. Metot, `Nette\Application\BadRequestException` istisnası atarak çalışır, bunun üzerine `Application` kontrolü error-presenter'a devreder. Bu, meydana gelen hatayı bildiren bir sayfa görüntülemekle görevli bir presenter'dır. Error-presenter ayarı [application yapılandırmasında|configuration] yapılır. +HTTP hata kodu ikinci parametre olarak aktarılabilir; varsayılan 404'tür. Metot, bir `Nette\Application\BadRequestException` fırlatarak çalışır; ardından `Application` denetimi error presenter'a devreder. Bu, oluşan hatayı bildiren bir sayfa göstermekle görevli bir presenter'dır. Error presenter, [uygulama yapılandırmasında|configuration] ayarlanır. -JSON Gönderme +JSON gönderme ============= -Verileri JSON formatında gönderen ve presenter'ı sonlandıran bir action-metodu örneği: +`sendJson($data)` metodu, verilen veriyi JSON'a kodlar, HTTP yanıtı olarak gönderir ve presenter'ı sonlandırır. Örnek: ```php public function actionData(): void @@ -235,15 +260,15 @@ public function actionData(): void ``` -İstek Parametreleri .{data-version:3.1.14} -========================================== +İsteğin parametreleri .{data-version:3.1.14} +============================================ -Presenter ve ayrıca her bileşen, HTTP isteğinden parametrelerini alır. Değerlerini `getParameter($name)` veya `getParameters()` metoduyla öğrenebilirsiniz. Değerler dizeler veya dize dizileridir, aslında doğrudan URL'den alınan ham verilerdir. +Presenter, ve her bileşen de, parametrelerini HTTP isteğinden alır. Değerlerini `getParameter($name)` veya `getParameters()` metotlarıyla elde edebilirsiniz. Değerler dizelerdir veya dize dizileridir, yani doğrudan URL'den alınan ham veridir. -Daha fazla kolaylık için parametreleri özellikler aracılığıyla erişilebilir hale getirmenizi öneririz. Bunları `#[Parameter]` niteliğiyle işaretlemeniz yeterlidir: +Daha fazla rahatlık için parametrelere özellikler üzerinden erişmenizi öneririz. Onları `#[Parameter]` niteliğiyle işaretlemeniz yeterlidir: ```php -use Nette\Application\Attributes\Parameter; // bu satır önemlidir +use Nette\Application\Attributes\Parameter; // bu satır önemli class HomePresenter extends Nette\Application\UI\Presenter { @@ -252,26 +277,26 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Özellik için veri türünü (örn. `string`) belirtmenizi öneririz ve Nette değeri buna göre otomatik olarak dönüştürür. Parametre değerleri ayrıca [doğrulanabilir |#Parametre Doğrulaması]. +Özellik için veri tipini (örneğin `string`) belirtmenizi öneririz; Nette değeri buna göre otomatik dönüştürür. Parametre değerleri ayrıca [doğrulanabilir |#Parametrelerin doğrulanması]. -Bir bağlantı oluştururken, parametrelere doğrudan değer atanabilir: +Bağlantı oluştururken parametrenin değerini doğrudan ayarlayabilirsiniz: ```latte tıkla ``` -Kalıcı Parametreler +Kalıcı parametreler =================== -Kalıcı parametreler, farklı istekler arasında durumu korumak için kullanılır. Değerleri, bir bağlantıya tıklandıktan sonra bile aynı kalır. Oturumdaki verilerin aksine, URL'de aktarılırlar. Ve bu tamamen otomatiktir, bu yüzden onları `link()` veya `n:href` içinde açıkça belirtmeye gerek yoktur. +Kalıcı parametreler, durumu farklı istekler boyunca korumak için kullanılır. Değerleri, bir bağlantıya tıklandıktan sonra da aynı kalır. Oturum verilerinin aksine URL'de aktarılırlar. Ve bu tamamen otomatik gerçekleşir, bu yüzden onları `link()` veya `n:href` içinde açıkça belirtmeye gerek yoktur. -Kullanım örneği? Çok dilli bir uygulamanız var. Mevcut dil, sürekli olarak URL'nin bir parçası olması gereken bir parametredir. Ancak her bağlantıda onu belirtmek inanılmaz derecede yorucu olurdu. Bu yüzden onu kalıcı bir `lang` parametresi yaparsınız ve kendi kendine aktarılır. Harika! +Örnek bir kullanım? Çok dilli bir uygulamanız olduğunu düşünün. Geçerli dil, URL'nin her zaman parçası olması gereken bir parametredir. Ama onu her bağlantıya yazmak inanılmaz sıkıcı olurdu. Bu yüzden onu `lang` kalıcı parametresi yaparsınız ve otomatik olarak taşınır. Şahane! -Nette'de kalıcı bir parametre oluşturmak son derece basittir. Sadece genel bir özellik oluşturmanız ve onu bir nitelikle işaretlemeniz yeterlidir: (eskiden `/** @persistent */` kullanılırdı) +Nette'te kalıcı parametre oluşturmak son derece basittir. Public bir özellik oluşturup onu nitelikle işaretlemeniz yeterlidir: (önceden `/** @persistent */` kullanılıyordu) ```php -use Nette\Application\Attributes\Persistent; // bu satır önemlidir +use Nette\Application\Attributes\Persistent; // bu satır önemli class ProductPresenter extends Nette\Application\UI\Presenter { @@ -280,14 +305,14 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Eğer `$this->lang` değeri örneğin `'tr'` ise, `link()` veya `n:href` kullanılarak oluşturulan bağlantılar da `lang=tr` parametresini içerecektir. Ve bağlantıya tıklandıktan sonra yine `$this->lang = 'tr'` olacaktır. +`$this->lang` örneğin `'en'` değerini taşıyorsa, `link()` veya `n:href` ile oluşturulan bağlantılar da `lang=en` parametresini içerecektir. Ve bağlantıya tıklandıktan sonra `$this->lang` yine `'en'` olacaktır. -Özellik için veri türünü (örn. `string`) belirtmenizi öneririz ve varsayılan bir değer de belirtebilirsiniz. Parametre değerleri [doğrulanabilir |#Parametre Doğrulaması]. +Özellik için veri tipini (örneğin `string`) belirtmenizi öneririz, ayrıca bir varsayılan değer de verebilirsiniz. Parametre değerleri [doğrulanabilir |#Parametrelerin doğrulanması]. -Kalıcı parametreler standart olarak söz konusu presenter'ın tüm eylemleri arasında aktarılır. Birden fazla presenter arasında da aktarılmaları için, ya: +Kalıcı parametreler genellikle ilgili presenter'ın tüm eylemleri arasında aktarılır. Onları birden fazla presenter arasında da aktarmak için, ya: -- presenter'ların miras aldığı ortak bir atada tanımlanmaları gerekir -- presenter'ların kullandığı bir trait'te tanımlanmaları gerekir: +- presenter'ların kalıtım aldığı ortak bir atada +- ya da presenter'ların kullandığı bir trait'te tanımlanmaları gerekir: ```php trait LanguageAware @@ -302,42 +327,62 @@ class ProductPresenter extends Nette\Application\UI\Presenter } ``` -Bir bağlantı oluştururken, kalıcı parametrenin değeri değiştirilebilir: +Bağlantı oluştururken kalıcı parametrenin değeri değiştirilebilir: ```latte -İngilizce detay +Çekçe detay ``` -Veya *sıfırlanabilir*, yani URL'den kaldırılabilir. O zaman varsayılan değerini alacaktır: +Ya da *sıfırlanabilir*, yani URL'den kaldırılabilir. O zaman varsayılan değerini alır: ```latte tıkla ``` -İnteraktif Bileşenler +Ortak parametre alanı ===================== -Presenter'ların yerleşik bir bileşen sistemi vardır. Bileşenler, presenter'lara eklediğimiz bağımsız, yeniden kullanılabilir birimlerdir. Bunlar [formlar |forms:in-presenter], veri ızgaraları, menüler, aslında tekrar tekrar kullanılması mantıklı olan her şey olabilir. +İsteğin parametreleri, [kalıcı parametreler |#Kalıcı parametreler] ve `action`, `render` ile `handle` (sinyal) metotlarının parametreleri tek bir alanı paylaşır; her biri adıyla tanımlanır. Aynı ad birden fazlasında geçerse, hepsi bir ve aynı değere karşılık gelir. -Bileşenler presenter'a nasıl eklenir ve ardından kullanılır? Bunu [Bileşenler |components] bölümünde öğreneceksiniz. Hatta Hollywood ile ortak yanlarının ne olduğunu bile keşfedeceksiniz. +Bu sıkça avantaja çevrilir. Örneğin `lang` kalıcı parametresi ile bir eylem ya da sinyal metodunun `$lang` argümanı bir ve aynıdır; kalıcı bir parametrenin geçerli değerini, onu yalnızca metot imzasına yazarak okuyabilirsiniz: -Ve bileşenleri nereden alabilirim? [Componette |https://componette.org/search/component] sayfasında, framework etrafındaki topluluktan gönüllülerin buraya yerleştirdiği açık kaynaklı bileşenleri ve Nette için bir dizi başka eklentiyi bulabilirsiniz. +```php +#[Persistent] +public string $lang; +public function handleSearch(string $query, string $lang): void +{ + // $lang, lang kalıcı parametresinin geçerli değerini taşır +} +``` + +Bu alan paylaşıldığı için, bilinçli olarak aynı değeri paylaşmalarını istemiyorsanız parametre adlarını benzersiz tutun. Bu, ayrıca isteğin POST gövdesinden de parametre okuyan sinyaller için de geçerlidir; bkz. [Sinyaller derinlemesine |components#Sinyaller derinlemesine]. + + +Etkileşimli bileşenler +====================== -Derinlemesine İnceliyoruz -========================= +Presenter'ların yerleşik bir bileşen sistemi vardır. Bileşenler, presenter'lara gömdüğümüz ayrı ve yeniden kullanılabilir birimlerdir. [Formlar |forms:in-presenter], datagrid'ler, menüler, kısacası tekrar tekrar kullanmanın anlamlı olduğu her şey olabilirler. + +Bileşenler presenter'lara nasıl gömülür ve sonra nasıl kullanılır? Bunu [Bileşenler |components] bölümünde öğreneceksiniz. Hatta Hollywood ile neyi ortak yaptıklarını da öğreneceksiniz. + +Peki bileşenleri nereden bulurum? [Componette |https://componette.org/search/component] üzerinde, framework topluluğundan gönüllülerin katkıda bulunduğu açık kaynak bileşenleri ve Nette için birçok başka eklentiyi bulacaksınız. + + +Daha derine +=========== .[tip] -Bu bölümde şimdiye kadar gösterdiklerimizle muhtemelen tamamen idare edeceksiniz. Aşağıdaki satırlar, presenter'ları derinlemesine merak eden ve kesinlikle her şeyi bilmek isteyenler içindir. +Bu bölümde şu ana kadar ele aldıklarımız çoğu kullanım için yeterli olacaktır. Aşağıdaki kısımlar, presenter'lara daha derinlemesine dalmak ve kesinlikle her şeyi bilmek isteyenler içindir. -Parametre Doğrulaması ---------------------- +Parametrelerin doğrulanması +--------------------------- -URL'den alınan [istek parametrelerinin |#İstek Parametreleri] ve [kalıcı parametrelerin |#Kalıcı Parametreler] değerleri `loadState()` metodu tarafından özelliklere yazılır. Bu metot ayrıca özellikte belirtilen veri türünün eşleşip eşleşmediğini de kontrol eder, aksi takdirde 404 hatasıyla yanıt verir ve sayfa görüntülenmez. +URL'lerden alınan [#İsteğin parametreleri] ve [#Kalıcı parametreler] değerleri özelliklere `loadState()` metoduyla yazılır. Bu metot ayrıca özellikte belirtilen veri tipinin uyup uymadığını denetler; uymuyorsa 404 hatasıyla yanıt verir ve sayfa gösterilmez. -Parametrelere asla körü körüne güvenmeyin, çünkü kullanıcı tarafından URL'de kolayca üzerine yazılabilirler. Örneğin, `$this->lang` dilinin desteklenenler arasında olup olmadığını bu şekilde doğrularız. Uygun yol, bahsedilen `loadState()` metodunu geçersiz kılmaktır: +URL'den gelen parametrelere asla körü körüne güvenmeyin, çünkü kullanıcı onları kolayca değiştirebilir. Örneğin `$this->lang` dilinin desteklenenler arasında olup olmadığını böyle doğrularız. Bunun uygun bir yolu, sözü geçen `loadState()` metodunu ezmektir: ```php class ProductPresenter extends Nette\Application\UI\Presenter @@ -347,9 +392,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter public function loadState(array $params): void { - parent::loadState($params); // burada $this->lang ayarlanır - // ardından kendi değer kontrolümüz gelir: - if (!in_array($this->lang, ['en', 'tr'])) { + parent::loadState($params); // $this->lang burada ayarlanır + // ardından kendi değer denetimimiz gelir: + if (!in_array($this->lang, ['en', 'cs'])) { $this->error(); } } @@ -357,30 +402,30 @@ class ProductPresenter extends Nette\Application\UI\Presenter ``` -İsteği Kaydetme ve Geri Yükleme -------------------------------- +İsteğin kaydedilmesi ve geri yüklenmesi +--------------------------------------- -Presenter'ın işlediği istek [api:Nette\Application\Request] nesnesidir ve presenter'ın `getRequest()` metodu tarafından döndürülür. +Presenter'ın işlediği istek, presenter'ın `getRequest()` metodunun döndürdüğü bir [api:Nette\Application\Request] nesnesidir. -Mevcut istek oturuma kaydedilebilir veya tam tersi, ondan geri yüklenebilir ve presenter'ın onu tekrar yürütmesi sağlanabilir. Bu, örneğin kullanıcı bir form doldururken ve oturumu sona erdiğinde kullanışlıdır. Verileri kaybetmemek için, giriş sayfasına yönlendirmeden önce mevcut isteği `$reqId = $this->storeRequest()` kullanarak oturuma kaydederiz, bu da kısa bir dize şeklinde kimliğini döndürür ve bunu giriş presenter'ına parametre olarak iletiriz. +Geçerli istek oturuma kaydedilebilir, ya da tersine oturumdan geri yüklenip presenter'a yeniden yürüttürülebilir. Bu, örneğin kullanıcı bir formu doldururken oturumunun süresi dolduğunda işe yarar. Veri kaybını önlemek için, giriş sayfasına yönlendirmeden önce geçerli isteği `$reqId = $this->storeRequest()` ile oturuma kaydederiz. Bu, isteğin tanımlayıcısını kısa bir dize olarak döndürür; onu giriş presenter'ına parametre olarak aktarırız. -Giriş yaptıktan sonra, isteği oturumdan alan ve ona yönlendiren `$this->restoreRequest($reqId)` metodunu çağırırız. Metot bu arada isteği oluşturan kullanıcının şimdi giriş yapanla aynı olduğunu doğrular. Farklı bir kullanıcı giriş yaparsa veya anahtar geçersizse, hiçbir şey yapmaz ve program devam eder. +Girişten sonra `$this->restoreRequest($reqId)` metodunu çağırırız; bu metot isteği oturumdan alır. POST istekleri ona iletilir, diğerleri (GET) ise isteğin URL'sine yönlendirilir. Metot, isteğin şu anda oturum açmış olan kullanıcının kendisi tarafından oluşturulduğunu doğrular. Başka bir kullanıcı giriş yaparsa veya anahtar geçersizse hiçbir şey yapmaz ve program her zamanki gibi devam eder. -[Önceki sayfaya nasıl geri dönülür |best-practices:restore-request] kılavuzuna bakın. +[Önceki sayfaya nasıl dönülür |best-practices:restore-request] kılavuzuna bakın. Kanonikleştirme --------------- -Presenter'ların SEO'ya (arama motoru optimizasyonu) katkıda bulunan gerçekten harika bir özelliği vardır. Farklı URL'lerde yinelenen içeriğin varlığını otomatik olarak önlerler. Belirli bir hedefe birden fazla URL adresi yönlendiriyorsa, örn. `/index` ve `/index?page=1`, framework bunlardan birini birincil (kanonik) olarak belirler ve diğerlerini HTTP kodu 301 ile ona yönlendirir. Bu sayede arama motorları sayfalarınızı iki kez indekslemez ve sayfa sıralamalarını seyreltmez. +Presenter'ların, daha iyi SEO'ya (arama motoru optimizasyonuna) katkıda bulunan gerçekten mükemmel bir özelliği vardır. Aynı içeriğin farklı URL'lerde bulunmasını otomatik olarak engellerler. Belirli bir hedefe birden fazla URL gidiyorsa, örneğin `/index` ve `/index?page=1`, framework bunlardan birini birincil (kanonik) sayar ve diğerlerini HTTP kodu 301 ile ona yönlendirir. Bu sayede arama motorları sayfalarınızı iki kez indekslemez ve sayfa sıralamanızı seyreltmez. -Bu sürece kanonikleştirme denir. Kanonik URL, [yönlendirici|routing] tarafından oluşturulan URL'dir, yani genellikle koleksiyondaki ilk eşleşen rotadır. +Bu sürece kanonikleştirme denir. Kanonik URL, [router|routing] tarafından üretilen, genellikle koleksiyondaki ilk uyan rotanın URL'sidir. -Kanonikleştirme varsayılan olarak açıktır ve `$this->autoCanonicalize = false` aracılığıyla kapatılabilir. +Kanonikleştirme varsayılan olarak etkindir ve `$this->autoCanonicalize = false` ile kapatılabilir. -AJAX veya POST isteği sırasında yönlendirme gerçekleşmez, çünkü veri kaybına neden olur veya SEO açısından ek bir değeri olmaz. +AJAX veya POST istekleri sırasında yönlendirme yapılmaz, çünkü bu veri kaybına yol açabilir ya da SEO açısından ek bir değer sunmaz. -Kanonikleştirmeyi manuel olarak `canonicalize()` metoduyla da tetikleyebilirsiniz; bu metoda `link()` metoduna benzer şekilde presenter, eylem ve parametreler iletilir. Bir bağlantı oluşturur ve onu mevcut URL adresiyle karşılaştırır. Farklıysa, oluşturulan bağlantıya yönlendirir. +Kanonikleştirmeyi `canonicalize()` metoduyla elle de tetikleyebilirsiniz. `link()` metoduna benzer şekilde ona presenter'ı, eylemi ve parametreleri verirsiniz. Bir bağlantı üretir ve onu geçerli URL adresiyle karşılaştırır. Farklılarsa üretilen bağlantıya yönlendirir. ```php public function actionShow(int $id, ?string $slug = null): void @@ -391,34 +436,16 @@ public function actionShow(int $id, ?string $slug = null): void } ``` - -Olaylar -------- - -Presenter yaşam döngüsünün bir parçası olarak çağrılan `startup()`, `beforeRender()` ve `shutdown()` metotlarına ek olarak, otomatik olarak çağrılması gereken başka fonksiyonlar da tanımlanabilir. Presenter, işleyicilerini `$onStartup`, `$onRender` ve `$onShutdown` dizilerine ekleyeceğiniz sözde [olayları |nette:glossary#Olaylar Events] tanımlar. - -```php -class ArticlePresenter extends Nette\Application\UI\Presenter -{ - public function __construct() - { - $this->onStartup[] = function () { - // ... - }; - } -} -``` - -`$onStartup` dizisindeki işleyiciler `startup()` metodundan hemen önce, `$onRender` `beforeRender()` ile `render()` arasında ve son olarak `$onShutdown` `shutdown()` metodundan hemen önce çağrılır. +SEO dostu URL'ler üretmek için rota filtrelerini `canonicalize()` ile birleştiren eksiksiz bir desen için [Slug'lu güzel URL'ler |best-practices:pretty-urls] bölümüne bakın. Yanıtlar -------- -Presenter'ın döndürdüğü yanıt, [api:Nette\Application\Response] arayüzünü uygulayan bir nesnedir. Bir dizi hazır yanıt mevcuttur: +Presenter'ın döndürdüğü yanıt, [api:Nette\Application\Response] arayüzünü uygulayan bir nesnedir. Hazır birkaç yanıt vardır: -- [api:Nette\Application\Responses\CallbackResponse] - bir geri çağırma gönderir -- [api:Nette\Application\Responses\FileResponse] - bir dosya gönderir +- [api:Nette\Application\Responses\CallbackResponse] - bir callback gönderir +- [api:Nette\Application\Responses\FileResponse] - dosyayı gönderir - [api:Nette\Application\Responses\ForwardResponse] - forward() - [api:Nette\Application\Responses\JsonResponse] - JSON gönderir - [api:Nette\Application\Responses\RedirectResponse] - yönlendirme @@ -431,42 +458,102 @@ Yanıtlar `sendResponse()` metoduyla gönderilir: use Nette\Application\Responses; // Düz metin -$this->sendResponse(new Responses\TextResponse('Merhaba Nette!')); +$this->sendResponse(new Responses\TextResponse('Hello Nette!')); -// Bir dosya gönderir +// Dosya gönderir $this->sendResponse(new Responses\FileResponse(__DIR__ . '/invoice.pdf', 'Invoice13.pdf')); -// Yanıt bir geri çağırma olacak +// Callback gönderir $callback = function (Nette\Http\IRequest $httpRequest, Nette\Http\IResponse $httpResponse) { if ($httpResponse->getHeader('Content-Type') === 'text/html') { - echo '

    Merhaba

    '; + echo '

    Hello

    '; } }; $this->sendResponse(new Responses\CallbackResponse($callback)); ``` +Kendi yanıtınızı da yazabilirsiniz. HTTP isteğini ve yanıtını alan tek bir `send()` metoduna sahip `Nette\Application\Response` arayüzünü uygulamanız yeterlidir. Bu, örneğin bellekte tutmak istemediğiniz veriyi akışla gönderirken işe yarar: + +```php +class CsvResponse implements Nette\Application\Response +{ + public function __construct( + private string $fileName, + private iterable $rows, + ) { + } + + public function send(Nette\Http\IRequest $request, Nette\Http\IResponse $response): void + { + $response->setContentType('text/csv', 'utf-8'); + $response->sendAsFile($this->fileName); + + $handle = fopen('php://output', 'w'); + foreach ($this->rows as $row) { + fputcsv($handle, $row); + } + + fclose($handle); + } +} +``` + +Sonra onu presenter'da her zamanki gibi gönderirsiniz: `$this->sendResponse(new CsvResponse('export.csv', $rows));` + -`#[Requires]` ile Erişim Kısıtlaması .{data-version:3.2.2} ----------------------------------------------------------- +HTTP önbelleklemesi +------------------- -`#[Requires]` niteliği, presenter'lara ve metotlarına erişimi kısıtlamak için gelişmiş seçenekler sunar. HTTP metotlarını belirtmek, AJAX isteği gerektirmek, aynı kaynağa (same origin) kısıtlamak ve yalnızca yönlendirme (forwarding) yoluyla erişime izin vermek için kullanılabilir. Nitelik, hem presenter sınıflarına hem de `action()`, `render()`, `handle()` ve `createComponent()` bireysel metotlarına uygulanabilir. +`lastModified()` metodu, HTTP önbelleklemesinden kolayca yararlanmayı sağlar. Ona içeriğin son değiştirilme tarih ve saatini (marka zamanı, dize veya `DateTimeInterface` nesnesi olarak), isteğe bağlı olarak bir ETag doğrulayıcısını (içeriğin geçerli sürümünü tanımlayan kısa bir dize, örneğin hash'i) ve bir sona erme süresini verirsiniz. Tarayıcı zaten uyan bir sürüme sahipse, presenter `304 Not Modified` yanıtı gönderir ve sonlanır; böylece sayfa gereksiz yere render edilmez veya aktarılmaz: -Şu kısıtlamaları belirleyebilirsiniz: +```php +public function renderArticle(int $id): void +{ + $article = $this->articles->getById($id); + $this->lastModified($article->updatedAt); + // ... +} +``` + + +Şablonun tamamlanması .{data-version:3.3.0} +------------------------------------------- + +Presenter bir şablonu render ederken, `sendTemplate()` metodu render'dan hemen önce `completeTemplate()`'i çağırır. Bu metot `#[TemplateVariable]` niteliğiyle işaretlenmiş değişkenleri doldurur ve şablon dosyasını bulur (varsayılan değişkenler, şablon oluşturulurken zaten `TemplateFactory` tarafından ayarlanır). Tüm görünümlerde ortak değişkenler eklemek veya farklı bir dosya belirlemek için bu protected metodu ezebilirsiniz: + +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'Uygulamam'; +} +``` + + +`#[Requires]` ile erişimi kısıtlama .{data-version:3.2.3} +--------------------------------------------------------- + +`#[Requires]` niteliği, presenter'lara ve metotlarına erişimi kısıtlamak için gelişmiş seçenekler sunar. HTTP metotlarını belirtmek, AJAX isteği zorunlu kılmak, aynı origin'e kısıtlamak ve yalnızca forward yoluyla erişime izin vermek için kullanılabilir. Nitelik hem presenter sınıflarına hem de `action()`, `render()`, `handle()` ve `createComponent()` gibi tek tek metotlara uygulanabilir. + +Şu kısıtlamaları belirtebilirsiniz: - HTTP metotlarına: `#[Requires(methods: ['GET', 'POST'])]` -- AJAX isteği gerektirme: `#[Requires(ajax: true)]` -- yalnızca aynı kaynaktan erişim: `#[Requires(sameOrigin: true)]` -- yalnızca yönlendirme (forward) yoluyla erişim: `#[Requires(forward: true)]` +- AJAX isteği zorunluluğu: `#[Requires(ajax: true)]` +- yalnızca aynı origin'den erişim: `#[Requires(sameOrigin: true)]` +- yalnızca forward yoluyla erişim: `#[Requires(forward: true)]` - belirli eylemlere kısıtlama: `#[Requires(actions: 'default')]` -Ayrıntılar için [#Requires Niteliği Nasıl Kullanılır |best-practices:attribute-requires] kılavuzuna bakın. +.[note] +3.3 sürümünden beri aynı origin eşleşmesi, tarayıcının `Sec-Fetch-Site` header'ı kullanılarak doğrulanır (önceden bir SameSite çerezi üzerinden); bu daha güvenilirdir ve şemanın, alan adının ve portun tam eşleşmesini denetler. +Ayrıntıları [Requires niteliği nasıl kullanılır |best-practices:attribute-requires] kılavuzunda bulabilirsiniz. -HTTP Metodu Kontrolü --------------------- -Nette'deki presenter'lar, her gelen isteğin HTTP metodunu otomatik olarak doğrular. Bu kontrolün nedeni öncelikle güvenliktir. Standart olarak `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` metotlarına izin verilir. +HTTP metodunun denetimi +----------------------- -Örneğin `OPTIONS` metoduna ek olarak izin vermek istiyorsanız, `#[Requires]` niteliğini kullanın (Nette Application v3.2'den itibaren): +Nette'teki presenter'lar, öncelikle güvenlik nedeniyle, gelen her isteğin HTTP metodunu otomatik doğrular. Varsayılan olarak `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` metotlarına izin verilir. + +Ek olarak örneğin `OPTIONS` metoduna da izin vermek isterseniz `#[Requires]` niteliğini kullanın (Nette Application v3.2.3'ten beri): ```php #[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])] @@ -475,26 +562,34 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Sürüm 3.1'de doğrulama, istekte belirtilen metodun `$presenter->allowedMethods` dizisinde bulunup bulunmadığını kontrol eden `checkHttpMethod()` içinde yapılır. Metodu şu şekilde ekleyin: +3.1.13 sürümünden beri doğrulama, istekte belirtilen metodun `$presenter->allowedMethods` dizisinde bulunup bulunmadığını denetleyen `checkHttpMethod()` içinde yapılır. 3.2.3 sürümünden itibaren bu yaklaşım `#[Requires]` lehine kullanımdan kaldırılmıştır. Metodu şöyle ezebilirsiniz: ```php class MyPresenter extends Nette\Application\UI\Presenter { - protected function checkHttpMethod(): void - { - $this->allowedMethods[] = 'OPTIONS'; - parent::checkHttpMethod(); - } + protected function checkHttpMethod(): void + { + $this->allowedMethods[] = 'OPTIONS'; + parent::checkHttpMethod(); + } } ``` -`OPTIONS` metoduna izin verirseniz, onu daha sonra presenter'ınız içinde uygun şekilde işlemeniz gerektiğini vurgulamak önemlidir. Metot genellikle, isteğin CORS (Cross-Origin Resource Sharing) politikası açısından izinli olup olmadığını belirlemek gerektiğinde tarayıcının gerçek istekten önce otomatik olarak gönderdiği sözde bir preflight isteği olarak kullanılır. Metoda izin verir ancak doğru yanıtı uygulamazsanız, bu tutarsızlıklara ve potansiyel güvenlik sorunlarına yol açabilir. +Şunu vurgulamak önemli: `OPTIONS` metodunu etkinleştirirseniz, ardından onu presenter'ınızda uygun şekilde işlemeniz gerekir. Bu metot sıklıkla preflight isteği olarak kullanılır; tarayıcı, isteğin CORS (Cross-Origin Resource Sharing) politikasına göre izin verilebilir olup olmadığını belirlemek gerektiğinde asıl istekten önce onu otomatik gönderir. Metodu etkinleştirip doğru yanıtı uygulamazsanız, bu tutarsızlıklara ve olası güvenlik sorunlarına yol açabilir. -Daha Fazla Okuma -================ +Kullanımdan kaldırılan eylemleri işaretleme .{data-version:3.2.3} +----------------------------------------------------------------- + +`#[Deprecated]` niteliği; eylemleri, sinyalleri veya tüm presenter'ları kullanımdan kaldırılmış ve ileride silinmek üzere planlanmış diye işaretler. Uygulamanın kullanımdan kaldırılmış bölümlerine bağlantı üretilirken Nette, geliştiricileri uyarmak için bir uyarı fırlatır. + +Niteliği ya tüm presenter sınıfına ya da tek tek `action()`, `render()` ve `handle()` metotlarına uygulayabilirsiniz. + + +İleri okuma +=========== -- [Inject Metotları ve Nitelikleri |best-practices:inject-method-attribute] -- [Trait'lerden Presenter Oluşturma |best-practices:presenter-traits] -- [Ayarları Presenter'lara İletme |best-practices:passing-settings-to-presenters] -- [Önceki Sayfaya Nasıl Geri Dönülür |best-practices:restore-request] +- [Inject metotları ve nitelikleri |best-practices:inject-method-attribute] +- [Presenter'ları trait'lerden oluşturma |best-practices:presenter-traits] +- [Presenter'lara ayar aktarma |best-practices:passing-settings-to-presenters] +- [Önceki sayfaya nasıl dönülür |best-practices:restore-request] diff --git a/application/tr/routing.texy b/application/tr/routing.texy index 8fccd6c0b1..bf1556e382 100644 --- a/application/tr/routing.texy +++ b/application/tr/routing.texy @@ -1,28 +1,28 @@ -Yönlendirme (Routing) -********************* +Yönlendirme +***********
    -Yönlendirici (Router), URL adresleriyle ilgili her şeyden sorumludur, böylece artık onlar hakkında düşünmek zorunda kalmazsınız. Şunları göstereceğiz: +Router, URL adresleriyle ilgili her şeyi üstlenir, böylece onları düşünmek zorunda kalmazsınız. Size şunları göstereceğiz: -- URL'lerin istediğiniz gibi olması için yönlendirici nasıl ayarlanır -- SEO ve yönlendirme hakkında konuşacağız -- ve kendi yönlendiricinizi nasıl yazacağınızı göstereceğiz +- URL'lerin istediğiniz gibi görünmesi için router nasıl yapılandırılır +- SEO ve yönlendirme üzerine konuşacağız +- ve kendi router'ınızı nasıl yazacağınızı göstereceğiz
    -Daha insancıl URL'ler (veya havalı ya da güzel URL'ler) daha kullanışlı, daha akılda kalıcıdır ve SEO'ya olumlu katkıda bulunurlar. Nette bunu düşünür ve geliştiricilere tam destek verir. Uygulamanız için tam olarak istediğiniz URL adresi yapısını tasarlayabilirsiniz. Hatta uygulama bittiğinde bile tasarlayabilirsiniz, çünkü kodda veya şablonlarda herhangi bir müdahale gerektirmez. Zarif bir şekilde tek bir [yerde |#Uygulamaya Dahil Etme], yönlendiricide tanımlanır ve böylece tüm presenter'lara anotasyonlar şeklinde dağılmaz. +İnsana daha dostça URL'ler (havalı veya güzel URL de denir) daha kullanışlıdır, akılda daha iyi kalır ve SEO'ya olumlu katkı yapar. Nette bunu göz önünde bulundurur ve geliştiricilerin ihtiyaçlarını tam olarak karşılar. Uygulamanız için istediğiniz URL yapısını tam olarak tasarlayabilirsiniz. Üstelik uygulama zaten tamamlandığında bile tasarlayabilirsiniz, çünkü kodda veya şablonlarda hiçbir değişiklik gerektirmez. Tüm presenter'lara anotasyon olarak dağılmak yerine [tek bir yerde |#Entegrasyon], router'da zarif biçimde tanımlanır. -Nette'deki yönlendirici, **çift yönlü** olmasıyla olağanüstüdür. Hem HTTP isteğindeki URL'yi çözebilir hem de bağlantılar oluşturabilir. Bu nedenle [Nette Application |how-it-works#Nette Application]'da hayati bir rol oynar, çünkü hem mevcut isteği hangi presenter ve eylemin yürüteceğine karar verir hem de şablonda vb. [URL oluşturmak |creating-links] için kullanılır. +Nette'teki router olağanüstüdür, çünkü **çift yönlüdür**. Hem HTTP isteklerinden URL'leri çözebilir hem de bağlantı oluşturabilir. Böylece [Nette Application |how-it-works#Nette Application] içinde çok önemli bir rol oynar: yalnızca geçerli isteği hangi presenter'ın ve eylemin yürüteceğine karar vermekle kalmaz, şablonlarda vb. [URL üretmek |creating-links] için de kullanılır. -Ancak yönlendirici yalnızca bu kullanımla sınırlı değildir, presenter'ların hiç kullanılmadığı uygulamalarda, REST API'ler için vb. kullanabilirsiniz. Daha fazla bilgi [#Bağımsız Kullanım] bölümünde. +Ancak router'ın kullanımı bununla sınırlı değildir; onu presenter'ların hiç kullanılmadığı uygulamalarda, REST API'lerde vb. de kullanabilirsiniz. Daha fazla ayrıntı [#Bağımsız kullanım] bölümünde. -Rota Koleksiyonu +Rota koleksiyonu ================ -Uygulamadaki URL adreslerinin şeklini tanımlamanın en hoş yolu [api:Nette\Application\Routers\RouteList] sınıfını sunar. Tanım, sözde rotaların, yani URL adresi maskelerinin ve bunlarla ilişkili presenter'ların ve eylemlerin basit bir API kullanılarak bir listesinden oluşur. Rotaları herhangi bir şekilde adlandırmamız gerekmez. +Bir uygulamadaki URL adreslerinin yapısını tanımlamanın en hoş yolunu [api:Nette\Application\Routers\RouteList] sınıfı sunar. Tanım, rota denilen şeylerin listesinden oluşur; yani basit bir API ile URL adreslerinin maskeleri ve onlara bağlı presenter'lar ile eylemler. Rotaları hiçbir şekilde adlandırmamız gerekmez. ```php $router = new Nette\Application\Routers\RouteList; @@ -31,16 +31,16 @@ $router->addRoute('article/', 'Article:view'); // ... ``` -Örnek, tarayıcıda `https://domain.com/rss.xml` açarsak, `Feed` presenter'ının `rss` eylemiyle görüntüleneceğini, `https://domain.com/article/12` açarsak, `Article` presenter'ının `view` eylemiyle görüntüleneceğini vb. söyler. Uygun bir rota bulunamazsa, Nette Application [BadRequestException |api:Nette\Application\BadRequestException] istisnası atarak yanıt verir, bu da kullanıcıya 404 Not Found hata sayfası olarak gösterilir. +Örnek şunu gösterir: tarayıcıda `https://domain.com/rss.xml` açarsak, `rss` eylemiyle `Feed` presenter'ı gösterilir. `https://domain.com/article/12` ise `view` eylemiyle `Article` presenter'ını gösterir vb. Uygun bir rota bulunamazsa Nette Application, kullanıcıya 404 Not Found hata sayfası olarak gösterilen bir [BadRequestException |api:Nette\Application\BadRequestException] fırlatarak yanıt verir. -Rotaların Sırası +Rotaların sırası ---------------- -Bireysel rotaların listelendiği sıra **kesinlikle anahtar** öneme sahiptir, çünkü yukarıdan aşağıya doğru sırayla değerlendirilirler. Rotaları **özgülden genele** doğru bildirme kuralı geçerlidir: +Rotaların listelenme **sırası** kesinlikle **çok önemlidir**, çünkü yukarıdan aşağıya sırayla değerlendirilirler. Kural şudur: rotaları **özelden genele** doğru bildiririz: ```php -// YANLIŞ: 'rss.xml' ilk rota tarafından yakalanır ve bu dizeyi olarak anlar +// YANLIŞ: 'rss.xml' ilk rota tarafından yakalanır ve bu dize sanılır $router->addRoute('', 'Article:view'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -49,10 +49,10 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('', 'Article:view'); ``` -Rotalar, bağlantılar oluşturulurken de yukarıdan aşağıya doğru değerlendirilir: +Bağlantı üretilirken de rotalar yukarıdan aşağıya değerlendirilir: ```php -// YANLIŞ: 'Feed:rss' bağlantısı 'admin/feed/rss' olarak oluşturulur +// YANLIŞ: 'Feed:rss' bağlantısı 'admin/feed/rss' olarak üretilir $router->addRoute('admin//', 'Admin:default'); $router->addRoute('rss.xml', 'Feed:rss'); @@ -61,54 +61,54 @@ $router->addRoute('rss.xml', 'Feed:rss'); $router->addRoute('admin//', 'Admin:default'); ``` -Doğru rota derlemesinin belirli bir beceri gerektirdiğini sizden saklamayacağız. Buna hakim olana kadar, [yönlendirme paneli |#Yönlendirici Hata Ayıklaması] sizin için yararlı bir yardımcı olacaktır. +Rotaları doğru kurmanın biraz beceri gerektirdiğini sizden saklamayacağız. Bunda ustalaşana kadar [yönlendirme paneli |#Router'ın hata ayıklaması] işinize yarayacak bir araç olacak. -Maske ve Parametreler +Maske ve parametreler --------------------- -Maske, web sitesinin kök dizininden göreceli yolu tanımlar. En basit maske statik bir URL'dir: +Maske, sitenin kök dizininden itibaren göreli yolu tanımlar. En basit maske statik bir URL'dir: ```php $router->addRoute('products', 'Products:default'); ``` -Genellikle maskeler sözde **parametreler** içerir. Bunlar sivri parantez içinde belirtilir (örn. ``) ve hedef presenter'a, örneğin `renderShow(int $year)` metoduna veya kalıcı parametre `$year`'a iletilir: +Maskeler sıklıkla **parametre** denilen şeyleri içerir. Bunlar açılı parantez içine alınır (örneğin ``) ve hedef presenter'a, örneğin `renderShow(int $year)` metoduna veya `$year` kalıcı parametresine aktarılır: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Örnek, tarayıcıda `https://example.com/chronicle/2020` açarsak, `History` presenter'ının `show` eylemiyle ve `year: 2020` parametresiyle görüntüleneceğini söyler. +Örnek şunu gösterir: tarayıcıda `https://example.com/chronicle/2020` açarsak, `show` eylemiyle ve `year: 2020` parametresiyle `History` presenter'ı gösterilir. -Parametrelere doğrudan maskede varsayılan bir değer atayabiliriz ve böylece isteğe bağlı hale gelirler: +Parametreler için doğrudan maskede varsayılan bir değer belirtebiliriz; bu onları isteğe bağlı yapar: ```php $router->addRoute('chronicle/', 'History:show'); ``` -Rota şimdi `https://example.com/chronicle/` URL'sini de kabul edecektir, bu da yine `History:show`'u `year: 2020` parametresiyle görüntüler. +Rota artık `https://example.com/chronicle/` URL'sini de kabul edecek ve yine `year: 2020` parametresiyle `History:show`'u gösterecektir. -Parametre elbette presenter ve eylem adı da olabilir. Örneğin şöyle: +Elbette presenter ve eylem adları da parametre olabilir. Örneğin: ```php $router->addRoute('/', 'Home:default'); ``` -Belirtilen rota, örn. `/article/edit` veya `/catalog/list` şeklindeki URL'leri kabul eder ve bunları `Article:edit` ve `Catalog:list` presenter'ları ve eylemleri olarak anlar. +Belirtilen rota örneğin `/article/edit` veya `/catalog/list` biçimindeki URL'leri kabul eder ve onları sırasıyla `Article:edit` ile `Catalog:list` presenter'ları ve eylemleri olarak anlar. -Aynı zamanda `presenter` ve `action` parametrelerine `Home` ve `default` varsayılan değerlerini verir ve dolayısıyla bunlar da isteğe bağlıdır. Yani rota, `/article` şeklindeki URL'yi de kabul eder ve onu `Article:default` olarak anlar. Veya tersi, `Product:default` bağlantısı `/product` yolunu, varsayılan `Home:default` bağlantısı `/` yolunu oluşturur. +Aynı zamanda `presenter` ve `action` parametrelerine `Home` ve `default` varsayılan değerlerini verir, bu da onları da isteğe bağlı yapar. Böylece rota `/article` gibi bir URL'yi de kabul eder ve onu `Article:default` olarak anlar. Ya da tersine, `Product:default`'a giden bir bağlantı `/product` yolunu, varsayılan `Home:default`'a giden bir bağlantı ise `/` yolunu üretir. -Maske yalnızca web sitesinin kök dizininden göreceli yolu değil, aynı zamanda eğik çizgiyle başlıyorsa mutlak yolu veya hatta iki eğik çizgiyle başlıyorsa tüm mutlak URL'yi tanımlayabilir: +Maske yalnızca sitenin kök dizininden itibaren göreli yolu değil, eğik çizgiyle başlıyorsa mutlak yolu, hatta iki eğik çizgiyle başlıyorsa tüm mutlak URL'yi de tanımlayabilir: ```php -// document root'a göreceli +// document root'a göreli $router->addRoute('/', /* ... */); -// mutlak yol (alan adına göreceli) +// mutlak yol (alan adına göreli) $router->addRoute('//', /* ... */); -// alan adı dahil mutlak URL (şemaya göreceli) +// alan adı dahil mutlak URL (şemaya göreli) $router->addRoute('//.example.com//', /* ... */); // şema dahil mutlak URL @@ -116,16 +116,16 @@ $router->addRoute('https://.example.com//', /* ... */); ``` -Doğrulama İfadeleri +Doğrulama ifadeleri ------------------- -Her parametre için [düzenli ifade|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php] kullanarak bir doğrulama koşulu belirlenebilir. Örneğin, `id` parametresinin yalnızca rakamlardan oluşabileceğini `\d+` düzenli ifadesiyle belirleriz: +Her parametre için bir [düzenli ifadeyle|https://www.php.net/manual/en/reference.pcre.pattern.syntax.php] doğrulama koşulu belirtilebilir. Örneğin `id` parametresi için, `\d+` regex'iyle yalnızca rakam içerebileceğini belirtiriz: ```php $router->addRoute('/[/]', /* ... */); ``` -Tüm parametreler için varsayılan düzenli ifade `[^/]+`'dır, yani eğik çizgi dışındaki her şey. Parametrenin eğik çizgileri de kabul etmesi gerekiyorsa, `.+` ifadesini belirtiriz: +Tüm parametreler için varsayılan düzenli ifade `[^/]+`'dir, yani eğik çizgi dışındaki her şey. Bir parametrenin eğik çizgi de kabul etmesi gerekiyorsa ifadeyi `.+` yaparız: ```php // https://example.com/a/b/c kabul eder, path 'a/b/c' olur @@ -133,20 +133,20 @@ $router->addRoute('', /* ... */); ``` -İsteğe Bağlı Diziler +İsteğe bağlı diziler -------------------- -Maskede isteğe bağlı bölümleri köşeli parantez kullanarak işaretleyebilirsiniz. Maskenin herhangi bir bölümü isteğe bağlı olabilir, içinde parametreler de bulunabilir: +Maskede isteğe bağlı bölümler köşeli parantezle işaretlenebilir. Maskenin herhangi bir bölümü isteğe bağlı olabilir ve parametre içerebilir: ```php $router->addRoute('[/]', /* ... */); -// Yolları kabul eder: -// /tr/download => lang => tr, name => download +// Şu yolları kabul eder: +// /en/download => lang => en, name => download // /download => lang => null, name => download ``` -Parametre isteğe bağlı bir dizinin parçası olduğunda, doğal olarak isteğe bağlı hale gelir. Varsayılan bir değeri belirtilmemişse, null olur. +Bir parametre isteğe bağlı bir dizinin parçası olduğunda, doğal olarak kendisi de isteğe bağlı olur. Belirtilmiş bir varsayılan değeri yoksa null olacaktır. İsteğe bağlı bölümler alan adında da olabilir: @@ -154,7 +154,7 @@ Parametre isteğe bağlı bir dizinin parçası olduğunda, doğal olarak isteğ $router->addRoute('//[.]example.com//', /* ... */); ``` -Diziler istenildiği gibi iç içe geçirilebilir ve birleştirilebilir: +Diziler iç içe geçirilebilir ve istenildiği gibi birleştirilebilir: ```php $router->addRoute( @@ -162,60 +162,60 @@ $router->addRoute( 'Home:default', ); -// Yolları kabul eder: -// /tr/hello +// Şu yolları kabul eder: +// /en/hello // /en-us/hello // /hello // /hello/page-12 ``` -URL oluşturulurken en kısa varyant hedeflenir, bu nedenle atlanabilecek her şey atlanır. Bu yüzden örneğin `index[.html]` rotası `/index` yolunu oluşturur. Sol köşeli parantezden sonra bir ünlem işareti belirterek davranışı tersine çevirmek mümkündür: +URL üretilirken en kısa seçenek yeğlenir, bu yüzden atlanabilecek her şey atlanır. Bu nedenle örneğin `index[.html]` rotası `/index` yolunu üretir. Bu davranış, sol köşeli parantezin ardına ünlem işareti konarak tersine çevrilebilir: ```php -// /hello ve /hello.html kabul eder, /hello oluşturur +// /hello ve /hello.html kabul eder, /hello üretir $router->addRoute('[.html]', /* ... */); -// /hello ve /hello.html kabul eder, /hello.html oluşturur +// /hello ve /hello.html kabul eder, /hello.html üretir $router->addRoute('[!.html]', /* ... */); ``` -Köşeli parantez olmadan isteğe bağlı parametreler (yani varsayılan değere sahip parametreler) aslında aşağıdaki gibi parantez içine alınmış gibi davranırlar: +Köşeli parantezsiz isteğe bağlı parametreler (yani varsayılan değeri olanlar) aslında şöyle sarılmış gibi davranır: ```php $router->addRoute('//', /* ... */); -// şuna karşılık gelir: +// buna karşılık gelir: $router->addRoute('[/[/[]]]', /* ... */); ``` -Eğer bitiş eğik çizgisinin davranışını etkilemek isteseydik, örneğin `/home/` yerine sadece `/home` oluşturulsun, bunu şu şekilde başarabiliriz: +Sondaki eğik çizginin davranışını etkilemek, örneğin `/home/` yerine `/home` üretilmesini istemek istersek, bu şöyle sağlanabilir: ```php $router->addRoute('[[/[/]]]', /* ... */); ``` -Joker Karakterler +Joker karakterler ----------------- -Mutlak yol maskesinde aşağıdaki joker karakterleri kullanabilir ve böylece örneğin geliştirme ve üretim ortamlarında farklı olabilen alan adını maskeye yazma zorunluluğundan kaçınabiliriz: +Mutlak URL maskesinde, örneğin geliştirme ve üretim ortamları arasında farklı olabilecek alan adını maskeye yazmak zorunda kalmamak için şu joker karakterleri kullanabiliriz: -- `%tld%` = üst düzey alan adı, örn. `com` veya `org` -- `%sld%` = ikinci düzey alan adı, örn. `example` -- `%domain%` = alt alan adları olmadan alan adı, örn. `example.com` -- `%host%` = tüm ana bilgisayar adı, örn. `www.example.com` +- `%tld%` = üst düzey alan adı, örneğin `com` veya `org` +- `%sld%` = ikinci düzey alan adı, örneğin `example` +- `%domain%` = alt alan adları olmadan alan adı, örneğin `example.com` +- `%host%` = tüm host, örneğin `www.example.com` - `%basePath%` = kök dizine giden yol ```php $router->addRoute('//www.%domain%/%basePath%//', /* ... */); -$router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('//www.%sld%.%tld%/%basePath%//', /* ... */); ``` -Genişletilmiş Gösterim ----------------------- +Gelişmiş yazım +-------------- -Genellikle `Presenter:eylem` şeklinde yazılan rota hedefi, bireysel parametreleri ve varsayılan değerlerini tanımlayan bir dizi kullanılarak da yazılabilir: +Genellikle `Presenter:eylem` biçiminde yazılan rota hedefi, tek tek parametreleri ve varsayılan değerlerini tanımlayan bir dizi kullanılarak da yazılabilir: ```php $router->addRoute('/[/]', [ @@ -224,7 +224,7 @@ $router->addRoute('/[/]', [ ]); ``` -Daha ayrıntılı belirtim için, varsayılan değerlere ek olarak parametrelerin diğer özelliklerini, örneğin doğrulama düzenli ifadesini (bkz. `id` parametresi) ayarlayabileceğimiz daha da genişletilmiş bir form kullanılabilir: +Daha ayrıntılı belirtim için, varsayılan değerlerin yanı sıra başka parametre özelliklerini de (örneğin bir doğrulama düzenli ifadesini, bkz. `id` parametresi) ayarlayabildiğimiz daha da genişletilmiş bir biçim kullanılabilir: ```php use Nette\Routing\Route; @@ -242,19 +242,29 @@ $router->addRoute('/[/]', [ ]); ``` -Dizide tanımlanan parametreler yol maskesinde belirtilmemişse, değerlerinin URL'deki soru işaretinden sonra belirtilen sorgu parametreleri kullanılarak bile değiştirilemeyeceğini belirtmek önemlidir. +Şunu belirtmek önemlidir: dizide tanımlanan parametreler yol maskesinde listelenmiyorsa, değerleri değiştirilemez; URL'de soru işaretinden sonra belirtilen sorgu parametreleriyle bile. + +Bu, **sabit parametreler** için yararlıdır; belirli bir sayfaya kısa, akılda kalan bir URL vermek. Örneğin `/tos`'un her zaman `id: 123` ile `Article:view`'u açması için: + +```php +$router->addRoute('tos', [ + 'presenter' => 'Article', + 'action' => 'view', + 'id' => 123, +]); +``` -Filtreler ve Çeviriler +Filtreler ve çeviriler ---------------------- -Uygulamanın kaynak kodlarını İngilizce yazıyoruz, ancak web sitesinin Türkçe URL'lere sahip olması gerekiyorsa, o zaman basit yönlendirme türü: +Uygulamanın kaynak kodunu İngilizce yazarız, ama sitenin Çekçe URL'leri olması gerekiyorsa, şöyle basit bir yönlendirme: ```php $router->addRoute('/', 'Home:default'); ``` -`/product/123` veya `/cart` gibi İngilizce URL'ler üretecektir. URL'deki presenter'ların ve eylemlerin Türkçe kelimelerle temsil edilmesini istiyorsak (örn. `/urun/123` veya `/sepet`), bir çeviri sözlüğü kullanabiliriz. Yazımı için zaten ikinci parametrenin "daha konuşkan" varyantına ihtiyacımız var: +İngilizce URL'ler üretecektir, örneğin `/product/123` veya `/cart`. URL'deki presenter'ların ve eylemlerin Çekçe sözcüklerle temsil edilmesini istiyorsak (örneğin `/produkt/123` veya `/kosik`), bir çeviri sözlüğü kullanabiliriz. Onu yazmak için ikinci parametrenin "daha ayrıntılı" seçeneğine ihtiyacımız var: ```php use Nette\Routing\Route; @@ -264,25 +274,25 @@ $router->addRoute('/', [ Route::Value => 'Home', Route::FilterTable => [ // URL'deki dize => presenter - 'urun' => 'Product', - 'sepet' => 'Cart', + 'produkt' => 'Product', + 'kosik' => 'Cart', 'katalog' => 'Catalog', ], ], 'action' => [ Route::Value => 'default', Route::FilterTable => [ - 'liste' => 'list', + 'seznam' => 'list', ], ], ]); ``` -Çeviri sözlüğünün birden fazla anahtarı aynı presenter'a yol açabilir. Böylece ona farklı takma adlar oluşturulur. Kanonik varyant (yani oluşturulan URL'de olacak olan) olarak son anahtar kabul edilir. +Çeviri sözlüğündeki birden fazla anahtar aynı presenter'a gidebilir. Bu, onun için çeşitli takma adlar oluşturur. Son anahtar kanonik seçenek sayılır (yani üretilen URL'de yer alacak olan). -Çeviri tablosu bu şekilde herhangi bir parametreye uygulanabilir. Çeviri mevcut değilse, orijinal değer alınır. Bu davranışı `Route::FilterStrict => true` ekleyerek değiştirebiliriz ve rota daha sonra değer sözlükte yoksa URL'yi reddeder. +Çeviri tablosu bu şekilde herhangi bir parametre için kullanılabilir. Bir çeviri yoksa özgün değer alınır. Bu davranışı `Route::FilterStrict => true` ekleyerek değiştirebiliriz; o zaman rota, değer sözlükte yoksa URL'yi reddeder. -Dizi şeklindeki çeviri sözlüğüne ek olarak, kendi çeviri fonksiyonlarımızı da dağıtabiliriz. +Dizi biçimindeki çeviri sözlüğünün yanı sıra kendi çeviri fonksiyonlarınız da devreye sokulabilir. ```php use Nette\Routing\Route; @@ -298,15 +308,15 @@ $router->addRoute('//', [ ]); ``` -`Route::FilterIn` fonksiyonu, URL'deki parametre ile daha sonra presenter'a iletilen dize arasında dönüştürme yapar, `FilterOut` fonksiyonu ters yönde dönüştürmeyi sağlar. +`Route::FilterIn` fonksiyonu, URL'deki parametre ile sonra presenter'a aktarılan dize arasında dönüşüm yapar; `FilterOut` fonksiyonu ise ters yöndeki dönüşümü sağlar. -`presenter`, `action` ve `module` parametrelerinin zaten URL'de kullanılan PascalCase veya camelCase ve kebab-case stilleri arasında dönüştürme yapan önceden tanımlanmış filtreleri vardır. Parametrelerin varsayılan değeri zaten dönüştürülmüş biçimde yazılır, bu yüzden örneğin presenter durumunda `` yazarız, `` değil. +`presenter`, `action` ve `module` parametrelerinin, PascalCase veya camelCase stili ile URL'lerde kullanılan kebab-case arasında dönüşüm yapan önceden tanımlı filtreleri vardır. Parametrelerin varsayılan değeri, uygulamaya aktarıldığı biçimde yazılır (presenter ve module için PascalCase, action için camelCase); yani örneğin bir presenter söz konusuysa `` yazarız, `` değil. -Genel Filtreler +Genel filtreler --------------- -Belirli parametrelere yönelik filtrelere ek olarak, tüm parametrelerin ilişkisel bir dizisini alan, bunları herhangi bir şekilde değiştirebilen ve sonra döndüren genel filtreler de tanımlayabiliriz. Genel filtreleri `null` anahtarı altında tanımlarız. +Belirli parametreler için tasarlanmış filtrelerin yanı sıra, tüm parametrelerin ilişkisel dizisini alan, onu istediği gibi değiştirip döndürebilen genel filtreler de tanımlayabiliriz. Genel filtreler boş anahtar altında tanımlanır. ```php use Nette\Routing\Route; @@ -321,30 +331,32 @@ $router->addRoute('/', [ ]); ``` -Genel filtreler, rotanın davranışını kesinlikle herhangi bir şekilde değiştirme yeteneği verir. Bunları örneğin parametreleri diğer parametrelere göre değiştirmek için kullanabiliriz. Örneğin, `` ve ``'ın mevcut `` parametresinin değerine göre çevrilmesi. +Genel filtreler, rotanın davranışını kesinlikle her şekilde değiştirme olanağı sunar. Onları örneğin parametreleri başka parametrelere göre değiştirmek için kullanabiliriz. Örneğin `` ve ``'ı `` parametresinin geçerli değerine göre çevirmek için. -Bir parametrenin kendi filtresi tanımlanmışsa ve aynı zamanda genel bir filtre varsa, kendi `FilterIn` filtresi genel filtreden önce ve tersine genel `FilterOut` filtresi kendi filtresinden önce yürütülür. Yani genel filtre içinde, `presenter` veya `action` parametrelerinin değerleri PascalCase veya camelCase stilinde yazılır. +Bir parametrenin kendi filtresi tanımlıysa ve bir de genel filtre varsa, kendi `FilterIn`'i genelden önce, buna karşılık genel `FilterOut` kendi filtreden önce çalıştırılır. Böylece genel filtrenin içinde `presenter` ve `action` parametrelerinin değerleri sırasıyla PascalCase ve camelCase stilinde yazılıdır. +Bu filtrelerin pratik bir kullanımı için, hiçbir şablonu değiştirmeden `/article/123-ekmek-nasil-pisirilir` gibi SEO dostu URL'ler üretmeyi anlatan [Slug'lu güzel URL'ler |best-practices:pretty-urls] bölümüne bakın. -Tek Yönlüler (OneWay) ---------------------- -Tek yönlü rotalar, uygulamanın artık oluşturmadığı ancak hala kabul ettiği eski URL'lerin işlevselliğini korumak için kullanılır. Bunları `OneWay` bayrağıyla işaretleriz: +OneWay bayrağı +-------------- + +Tek yönlü rotalar, uygulamanın artık üretmediği ama hâlâ kabul ettiği eski URL'lerin işlevselliğini korumaya yarar. Onları `OneWay` bayrağıyla işaretleriz: ```php // eski URL /product-info?id=123 -$router->addRoute('product-info', 'Product:detail', $router::ONE_WAY); +$router->addRoute('product-info', 'Product:detail', oneWay: true); // yeni URL /product/123 $router->addRoute('product/', 'Product:detail'); ``` -Eski URL'ye erişildiğinde, presenter otomatik olarak yeni URL'ye yönlendirir, böylece arama motorları bu sayfaları iki kez indekslemez ([#SEO ve kanonikleştirme] bölümüne bakın). +Eski URL'ye erişildiğinde presenter otomatik olarak yeni URL'ye yönlendirir, böylece arama motorları bu sayfaları iki kez indekslemez (bkz. [#SEO ve kanonikleştirme]). -Geri Çağrılarla Dinamik Yönlendirme ------------------------------------ +Callback'lerle dinamik yönlendirme +---------------------------------- -Geri çağırmalarla dinamik yönlendirme, rotalara doğrudan, ilgili yol ziyaret edildiğinde yürütülecek fonksiyonlar (geri çağırmalar) atamanıza olanak tanır. Bu esnek işlevsellik, uygulamanız için çeşitli uç noktaları (endpoints) hızlı ve verimli bir şekilde oluşturmanıza olanak tanır: +Callback'lerle dinamik yönlendirme, rotalara doğrudan, ilgili yol ziyaret edildiğinde çalıştırılan fonksiyonlar (callback'ler) atamanızı sağlar. Bu esnek işlevsellik, uygulamanız için çeşitli uç noktaları hızlı ve verimli biçimde oluşturmanıza olanak tanır: ```php $router->addRoute('test', function () { @@ -352,47 +364,61 @@ $router->addRoute('test', function () { }); ``` -Ayrıca maskede, geri çağırmanıza otomatik olarak iletilecek parametreler tanımlayabilirsiniz: +Maskede, callback'inize otomatik aktarılan parametreler de tanımlayabilirsiniz: ```php -$router->addRoute('', function (string $lang) { +$router->addRoute('', function (string $lang) { echo match ($lang) { - 'tr' => 'Web sitemizin Türkçe versiyonuna hoş geldiniz!', - 'en' => 'Welcome to the English version of our website!', + 'cs' => 'Sitemizin Çekçe sürümüne hoş geldiniz!', + 'en' => 'Sitemizin İngilizce sürümüne hoş geldiniz!', }; }); ``` +Maskeden gelen parametrelerin yanı sıra callback, DI konteynerinden servisler de alabilir. Bunlar parametre tipine göre aktarılır. Ayrıca `$presenter` parametresi, rotayı işleyen [MicroPresenter |api:NetteModule\MicroPresenter] örneğini alır: + +```php +$router->addRoute('', function (string $lang, Nette\Http\Request $httpRequest, NetteModule\MicroPresenter $presenter) { + // ... +}); +``` + Modüller -------- -Ortak bir [modüle |directory-structure#Presenter lar ve Şablonlar] ait birden fazla rotamız varsa, `withModule()` kullanırız: +Ortak bir [modüle |directory-structure#Presenter'lar ve şablonlar] ait birden fazla rotamız varsa `withModule()` kullanırız. Verilen modül, gruptaki her rotanın presenter'ının önüne otomatik eklenir ve URL'den tamamen kaybolur: ```php $router = new RouteList; -$router->withModule('Forum') // aşağıdaki rotalar Forum modülünün bir parçasıdır +$router->withModule('Forum') // sonraki rotalar Forum modülünün parçasıdır ->addRoute('rss', 'Feed:rss') // presenter Forum:Feed olacak ->addRoute('/') - ->withModule('Admin') // aşağıdaki rotalar Forum:Admin modülünün bir parçasıdır + ->withModule('Admin') // sonraki rotalar Forum:Admin modülünün parçasıdır ->addRoute('sign:in', 'Sign:in'); ``` -Alternatif olarak `module` parametresini kullanmaktır: +Bir alternatif de `module` parametresidir; o da sabit bir modül belirler ve onu URL'nin dışında tutar: ```php -// URL manage/dashboard/default, Admin:Dashboard presenter'ına eşlenir +// manage/dashboard/default URL'si Admin:Dashboard presenter'ına eşlenir $router->addRoute('manage//', [ 'module' => 'Admin', ]); ``` +Her presenter adı ancak modülüyle birlikte tamdır, örneğin `Front:Admin:ProductList`. Böyle bir tam ad bir URL parametresine düştüğünde, router onu iki basit kuralla kodlar: her iki nokta üst üste `:` (modül ayırıcısı) bir **noktaya**, PascalCase bir addaki her sözcük sınırı ise bir **kısa çizgiye** dönüşür. Yani `Front:Admin:ProductList`, URL'de `front.admin.product-list` olarak görünür ve aynı şekilde geri çözülür. Modüler bir uygulamanın, yukarıdaki araçlar olmadan, noktalarla dolu URL'ler üretmesinin nedeni budur. + +Hem `withModule()` hem de `module` parametresi bundan tam olarak şu yüzden kaçınır: presenter adı URL'ye ulaşmadan önce bilinen modül ön ekini ondan soyarlar; modül sabit olduğundan hiç kodlanması gerekmez. + +Bazen modülün kendisinin değişmesini ve URL'de görünmesini isteriz, bu yüzden doğrudan maskede ``'e başvururuz. Çok önemli bir ayrıntıya dikkat edin: **`` tüm modül yolunu yakalar**, yani presenter adındaki son iki nokta üst üsteye kadar olan her şeyi. `Shop:Admin:Product` presenter'ı için bu, `Shop:Admin` modülü ve `Product` presenter'ı demektir; iki nokta üst üste noktaya dönüştüğünden şunu elde ederiz: -Alt Alan Adları + +Alt alan adları --------------- -Rota koleksiyonlarını alt alan adlarına göre bölebiliriz: +Rota koleksiyonları alt alan adlarına göre bölünebilir: ```php $router = new RouteList; @@ -401,7 +427,7 @@ $router->withDomain('example.com') ->addRoute('/'); ``` -Alan adında [#joker karakterler] de kullanılabilir: +Alan adında [#Joker karakterler] de kullanılabilir: ```php $router = new RouteList; @@ -410,23 +436,23 @@ $router->withDomain('example.%tld%') ``` -Yol Öneki ---------- +Yol ön eki +---------- -Rota koleksiyonlarını URL'deki yola göre bölebiliriz: +Rota koleksiyonları URL'deki yola göre bölünebilir: ```php $router = new RouteList; $router->withPath('eshop') - ->addRoute('rss', 'Feed:rss') // /eshop/rss URL'sini yakalar - ->addRoute('/'); // /eshop// URL'sini yakalar + ->addRoute('rss', 'Feed:rss') // /eshop/rss URL'siyle eşleşir + ->addRoute('/'); // /eshop// URL'siyle eşleşir ``` -Kombinasyonlar --------------- +Bileşimler +---------- -Yukarıdaki bölümlemeyi birbirleriyle birleştirebiliriz: +Yukarıdaki gruplamalar birbirleriyle birleştirilebilir: ```php $router = (new RouteList) @@ -446,10 +472,10 @@ $router = (new RouteList) ``` -Sorgu Parametreleri +Sorgu parametreleri ------------------- -Maskeler ayrıca sorgu parametrelerini (URL'deki soru işaretinden sonraki parametreler) de içerebilir. Bunlar için bir doğrulama ifadesi tanımlanamaz, ancak presenter'a iletilecekleri adı değiştirebilirsiniz: +Maskeler sorgu parametreleri de (URL'de soru işaretinden sonraki parametreler) içerebilir. Bunlar için doğrulama ifadesi tanımlanamaz, ama presenter'a aktarıldıkları ad değiştirilebilir: ```php // 'cat' sorgu parametresini uygulamada 'categoryId' adıyla kullanmak istiyoruz @@ -457,26 +483,26 @@ $router->addRoute('product ? id= & cat=', /* ... */); ``` -Foo Parametreleri +Foo parametreleri ----------------- -Şimdi daha derine iniyoruz. Foo parametreleri aslında düzenli bir ifadeyle eşleşmeyi sağlayan isimsiz parametrelerdir. Örnek olarak `/index`, `/index.html`, `/index.htm` ve `/index.php` kabul eden bir rota verilebilir: +Şimdi daha derine iniyoruz. Foo parametreleri aslında bir düzenli ifadeyle eşleşmeyi sağlayan adsız parametrelerdir. Bir örnek, `/index`, `/index.html`, `/index.htm` ve `/index.php` kabul eden bir rotadır: ```php $router->addRoute('index', /* ... */); ``` -URL oluşturulurken kullanılacak dizeyi açıkça tanımlamak da mümkündür. Dize doğrudan soru işaretinden sonra yerleştirilmelidir. Aşağıdaki rota öncekine benzer, ancak `/index` yerine `/index.html` oluşturur, çünkü `.html` dizesi oluşturma değeri olarak ayarlanmıştır: +URL üretilirken kullanılacak dizeyi açıkça tanımlamak da mümkündür. Dize doğrudan soru işaretinin ardına konmalıdır. Aşağıdaki rota öncekine benzer, ama `/index` yerine `/index.html` üretir, çünkü üretim değeri olarak `.html` dizesi ayarlanmıştır: ```php $router->addRoute('index', /* ... */); ``` -Uygulamaya Dahil Etme -===================== +Entegrasyon +=========== -Oluşturulan yönlendiriciyi uygulamaya dahil etmek için DI konteynerine ondan bahsetmeliyiz. En kolay yol, yönlendirici nesnesini üretecek bir fabrika hazırlamak ve konteyner yapılandırmasında onu kullanmasını söylemektir. Bu amaçla `App\Core\RouterFactory::createRouter()` metodunu yazdığımızı varsayalım: +Oluşturduğumuz router'ı uygulamaya entegre etmek için DI konteynerine ondan söz etmemiz gerekir. En kolayı, router nesnesini oluşturacak bir factory hazırlamak ve yapılandırmada konteynere onu kullanmasını söylemektir. Bunun için `App\Core\RouterFactory::createRouter()` metodunu yazdığımızı varsayalım: ```php namespace App\Core; @@ -494,14 +520,14 @@ class RouterFactory } ``` -[Yapılandırmaya |dependency-injection:services] şunu yazarız: +Sonra [yapılandırmaya |dependency-injection:services] şunu yazarız: ```neon services: - App\Core\RouterFactory::createRouter ``` -Veritabanı vb. gibi herhangi bir bağımlılık, fabrika metoduna parametreleri olarak [autowiring|dependency-injection:autowiring] kullanılarak iletilir: +Örneğin bir veritabanına olan bağımlılıklar, factory metoduna [autowiring|dependency-injection:autowiring] ile parametreleri olarak aktarılır: ```php public static function createRouter(Nette\Database\Connection $db): RouteList @@ -514,18 +540,18 @@ public static function createRouter(Nette\Database\Connection $db): RouteList SimpleRouter ============ -Rota koleksiyonundan çok daha basit bir yönlendirici [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]'dır. URL şekli konusunda özel gereksinimlerimiz olmadığında, `mod_rewrite` (veya alternatifleri) mevcut olmadığında veya henüz güzel URL'lerle uğraşmak istemediğimizde kullanırız. +Rota koleksiyonundan çok daha basit bir router [SimpleRouter |api:Nette\Application\Routers\SimpleRouter]'dır. Onu, URL biçimiyle ilgili özel gereksinimlerimiz olmadığında, `mod_rewrite` (veya alternatifleri) kullanılamıyorsa ya da henüz güzel URL'lerle uğraşmak istemiyorsak kullanırız. -Kabaca şu şekilde adresler üretir: +Aşağı yukarı şu biçimde adresler üretir: ``` http://example.com/?presenter=Product&action=detail&id=123 ``` -SimpleRouter'ın kurucu parametresi, parametresiz bir sayfa açtığımızda, örn. `http://example.com/`, yönlendirilmesi gereken varsayılan presenter ve eylemdir. +`SimpleRouter` constructor'ının parametresi varsayılan bir presenter ve eylemdir; yani örneğin `http://example.com/` adresini ek parametre olmadan açtığımızda çalıştırılacak eylem. ```php -// varsayılan presenter 'Home' ve eylem 'default' olacak +// varsayılan presenter 'Home', eylem 'default' olacak $router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` @@ -537,22 +563,22 @@ services: ``` -SEO ve Kanonikleştirme +SEO ve kanonikleştirme ====================== -Framework, farklı URL'lerde yinelenen içeriği önleyerek SEO'ya (arama motoru optimizasyonu) katkıda bulunur. Belirli bir hedefe birden fazla adres yönlendiriyorsa, örn. `/index` ve `/index.html`, framework bunlardan ilkini birincil (kanonik) olarak belirler ve diğerlerini HTTP kodu 301 ile ona yönlendirir. Bu sayede arama motorları sayfalarınızı iki kez indekslemez ve sayfa sıralamalarını seyreltmez. +Framework, aynı içeriğin farklı URL'lerde bulunmasını engelleyerek SEO'ya (arama motoru optimizasyonuna) katkı yapar. Belirli bir hedefe birden fazla adres gidiyorsa, örneğin `/index` ve `/index.html`, framework ilkini birincil (kanonik) sayar ve diğerlerini HTTP kodu 301 ile ona yönlendirir. Bu sayede arama motorları sayfaları iki kez indekslemez ve sayfa sıralamalarını seyreltmez. -Bu sürece kanonikleştirme denir. Kanonik URL, yönlendirici tarafından oluşturulan URL'dir, yani koleksiyondaki OneWay bayrağı olmayan ilk uygun rotadır. Bu nedenle koleksiyonda **birincil rotaları ilk olarak** belirtiriz. +Bu sürece kanonikleştirme denir. Kanonik URL, router tarafından üretilen, yani koleksiyondaki OneWay bayrağı olmayan ilk uyan rotanın URL'sidir. Bu yüzden koleksiyonda **önce birincil rotaları** listeleriz. -Kanonikleştirme presenter tarafından yapılır, daha fazla bilgi [kanonikleştirme |presenters#Kanonikleştirme] bölümünde. +Kanonikleştirmeyi presenter gerçekleştirir; daha fazlası [kanonikleştirme |presenters#Kanonikleştirme] bölümünde. HTTPS ===== -HTTPS protokolünü kullanabilmek için barındırmada etkinleştirmek ve sunucuyu doğru şekilde yapılandırmak gerekir. +HTTPS protokolünü kullanmak için onu hosting'de etkinleştirmek ve sunucuyu doğru yapılandırmak gerekir. -Tüm web sitesinin HTTPS'ye yönlendirilmesi sunucu düzeyinde ayarlanmalıdır, örneğin uygulamamızın kök dizinindeki .htaccess dosyası kullanılarak ve HTTP kodu 301 ile. Ayar barındırmaya göre değişebilir ve kabaca şöyle görünür: +Tüm sitenin HTTPS'e yönlendirilmesi sunucu düzeyinde, örneğin uygulamamızın kök dizinindeki `.htaccess` dosyasıyla ve HTTP kodu 301 ile ayarlanmalıdır. Ayarlar hosting'e göre değişebilir ve aşağı yukarı şöyle görünür: ``` @@ -564,40 +590,40 @@ Tüm web sitesinin HTTPS'ye yönlendirilmesi sunucu düzeyinde ayarlanmalıdır, ``` -Yönlendirici, sayfanın yüklendiği protokolle aynı protokole sahip URL'ler üretir, bu yüzden başka bir şey ayarlamaya gerek yoktur. +Router, URL'leri sayfanın yüklendiği protokolün aynısıyla üretir, bu yüzden başka bir şey ayarlamaya gerek yoktur. -Ancak istisnai olarak farklı rotaların farklı protokoller altında çalışması gerekiyorsa, bunu rota maskesinde belirtiriz: +Ancak istisnai olarak farklı rotaların farklı protokoller altında çalışmasına ihtiyaç duyarsak, bunu rota maskesinde belirtiriz: ```php -// HTTP ile bir adres üretecek +// HTTP adresi üretecek $router->addRoute('http://%host%//', /* ... */); -// HTTPS ile bir adres üretecek +// HTTPS adresi üretecek $router->addRoute('https://%host%//', /* ... */); ``` -Yönlendirici Hata Ayıklaması -============================ +Router'ın hata ayıklaması +========================= -[Tracy Bar |tracy:]'da görüntülenen yönlendirme paneli, rotaların listesini ve yönlendiricinin URL'den aldığı parametreleri gösteren yararlı bir yardımcıdır. +[Tracy Bar |tracy:]'da görünen yönlendirme paneli, rotaların listesini ve ayrıca router'ın URL'den elde ettiği parametreleri gösteren yararlı bir yardımcıdır. -Yeşil çubuk ve ✓ sembolü, mevcut URL'yi işleyen rotayı temsil eder; mavi renk ve ≈ sembolü, yeşil olan onları geçmeseydi URL'yi de işleyecek olan rotaları gösterir. Ayrıca mevcut presenter ve eylemi de görürüz. +✓ simgeli yeşil çubuk, geçerli URL'yi işleyen rotayı temsil eder; mavi renk ve ≈ simgesi ise, yeşil olan onların önüne geçmeseydi URL'yi işleyecek olan rotaları gösterir. Ayrıca geçerli presenter'ı ve eylemi görürüz. [* routing-debugger.webp *] -Aynı zamanda, [kanonikleştirme |#SEO ve Kanonikleştirme] nedeniyle beklenmedik bir yönlendirme olursa, yönlendiricinin URL'yi başlangıçta nasıl anladığını ve neden yönlendirdiğini öğrenmek için *redirect* çubuğundaki panele bakmak yararlıdır. +Aynı zamanda [kanonikleştirme |#SEO ve kanonikleştirme] nedeniyle beklenmedik bir yönlendirme olursa, panelin *redirect* çubuğuna bakmak yararlıdır; orada router'ın URL'yi başlangıçta nasıl anladığını ve neden yönlendirdiğini öğrenebilirsiniz. .[note] -Yönlendiriciyi hata ayıklarken, tarayıcıda Geliştirici Araçları'nı (Ctrl+Shift+I veya Cmd+Option+I) açmanızı ve Ağ panelinde önbelleği devre dışı bırakmanızı öneririz, böylece yönlendirmeler orada saklanmaz. +Router'ın hata ayıklamasını yaparken, yönlendirmelerin orada saklanmaması için tarayıcıda Developer Tools'u açmanızı (Ctrl+Shift+I veya Cmd+Option+I) ve Network panelinde önbelleği kapatmanızı öneririz. Performans ========== -Rotaların sayısı yönlendiricinin hızını etkiler. Sayıları kesinlikle birkaç düzineyi geçmemelidir. Web sitenizin çok karmaşık bir URL yapısı varsa, özel bir [#Özel Yönlendirici] yazabilirsiniz. +Rotaların sayısı router'ın hızını etkiler. Sayıları kesinlikle birkaç düzineyi aşmamalıdır. Sitenizin URL yapısı fazla karmaşıksa, kendi [#Kendi router'ınız]'ınızı yazabilirsiniz. -Yönlendiricinin örneğin veritabanı gibi herhangi bir bağımlılığı yoksa ve fabrikası herhangi bir argüman kabul etmiyorsa, derlenmiş formunu doğrudan DI konteynerine serileştirebilir ve böylece uygulamayı biraz hızlandırabiliriz. +Router'ın örneğin bir veritabanına bağımlılığı yoksa ve factory'si argüman almıyorsa, derlenmiş biçimini doğrudan DI konteynerine serileştirebilir ve böylece uygulamayı biraz hızlandırabiliriz. ```neon routing: @@ -605,10 +631,10 @@ routing: ``` -Özel Yönlendirici +Kendi router'ınız ================= -Aşağıdaki satırlar çok ileri düzey kullanıcılar içindir. Kendi yönlendiricinizi oluşturabilir ve onu tamamen doğal bir şekilde rota koleksiyonuna dahil edebilirsiniz. Yönlendirici, iki metoda sahip [api:Nette\Routing\Router] arayüzünün bir uygulamasıdır: +Aşağıdaki satırlar çok ileri düzey kullanıcılar içindir. Kendi router'ınızı oluşturabilir ve onu doğal olarak rota koleksiyonuna entegre edebilirsiniz. Router, iki metotlu [api:Nette\Routing\Router] arayüzünün bir uygulamasıdır: ```php use Nette\Http\IRequest as HttpRequest; @@ -628,7 +654,7 @@ class MyRouter implements Nette\Routing\Router } ``` -`match` metodu, yalnızca URL'yi değil, aynı zamanda başlıkları vb. de alabileceğiniz mevcut isteği [$httpRequest |http:request] işler ve presenter adını ve parametrelerini içeren bir diziye dönüştürür. İsteği işleyemezse, null döndürür. İsteği işlerken en azından presenter ve eylemi döndürmeliyiz. Presenter adı tamdır ve olası modülleri de içerir: +`match` metodu, yalnızca URL'nin değil header'ların vb. de elde edilebildiği geçerli istek [$httpRequest |http:request]'i, presenter adını ve parametrelerini içeren bir diziye işler. İsteği işleyemezse null döndürür. İsteği işlerken en azından presenter'ı döndürmemiz gerekir; eylem isteğe bağlıdır ve belirtilmezse `default` olur. Presenter adı tamdır ve varsa modülleri de içerir: ```php [ @@ -637,9 +663,9 @@ class MyRouter implements Nette\Routing\Router ] ``` -`constructUrl` metodu ise tam tersine, parametreler dizisinden sonuçta ortaya çıkan mutlak URL'yi oluşturur. Bunun için mevcut URL olan [`$refUrl`|api:Nette\Http\UrlScript] parametresindeki bilgileri kullanabilir. +`constructUrl` metodu ise tam tersine, parametre dizisinden sonuçtaki mutlak URL'yi kurar. Geçerli URL olan [`$refUrl`|api:Nette\Http\UrlScript] parametresindeki bilgilerden yararlanabilir. -`add()` kullanarak rota koleksiyonuna eklersiniz: +Onu rota koleksiyonuna `add()` ile ekleyin: ```php $router = new Nette\Application\Routers\RouteList; @@ -649,16 +675,16 @@ $router->addRoute(/* ... */); ``` -Bağımsız Kullanım +Bağımsız kullanım ================= -Bağımsız kullanım derken, Nette Application ve presenter'ları kullanmayan bir uygulamada yönlendiricinin yeteneklerini kullanmayı kastediyoruz. Bu bölümde gösterdiğimiz hemen hemen her şey onun için geçerlidir, şu farklılıklarla: +Bağımsız kullanımdan kastımız, router'ın yeteneklerinden Nette Application ve presenter'ları kullanmayan bir uygulamada yararlanmaktır. Bu bölümde gösterdiğimiz hemen her şey onun için de geçerlidir, şu farklarla: - rota koleksiyonları için [api:Nette\Routing\RouteList] sınıfını kullanırız -- basit yönlendirici olarak [api:Nette\Routing\SimpleRouter] sınıfını kullanırız -- `Presenter:eylem` çifti olmadığı için, [#Genişletilmiş Gösterim] kullanırız +- basit router olarak [api:Nette\Routing\SimpleRouter] sınıfını +- `Presenter:eylem` çifti var olmadığından [#Gelişmiş yazım] kullanırız -Yani yine bize yönlendiriciyi oluşturacak bir metot oluştururuz, örn.: +Yine router'ı bizim için kuracak bir metot oluştururuz, örneğin: ```php namespace App\Core; @@ -682,35 +708,35 @@ class RouterFactory } ``` -DI konteyneri kullanıyorsanız, ki bunu öneririz, metodu tekrar yapılandırmaya ekleriz ve ardından yönlendiriciyi HTTP isteğiyle birlikte konteynerden alırız: +Önerdiğimiz gibi bir DI konteyneri kullanıyorsanız, metodu yine yapılandırmaya ekleyin ve sonra router'ı HTTP isteğiyle birlikte konteynerden alın: ```php $router = $container->getByType(Nette\Routing\Router::class); $httpRequest = $container->getByType(Nette\Http\IRequest::class); ``` -Veya nesneleri doğrudan üretiriz: +Ya da nesneleri doğrudan oluşturun: ```php $router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` -Şimdi geriye sadece yönlendiriciyi işe koymak kalıyor: +Şimdi geriye yalnızca router'ın işini yapmasına izin vermek kalıyor: ```php $params = $router->match($httpRequest); if ($params === null) { - // uygun bir rota bulunamadı, 404 hatası göndeririz + // uyan rota bulunamadı, 404 hatası gönder exit; } -// alınan parametreleri işleriz +// elde edilen parametreleri işle $controller = $params['controller']; // ... ``` -Ve tersine, bir bağlantı oluşturmak için yönlendiriciyi kullanırız: +Ve tersine, bir bağlantı kurmak için router'ı kullanın: ```php $params = ['controller' => 'ArticleController', 'id' => 123]; @@ -718,4 +744,6 @@ $url = $router->constructUrl($params, $httpRequest->getUrl()); ``` -{{composer: nette/router}} +{{composer: nette/routing}} +{{repo: nette/routing}} +{{api: https://api.nette.org/routing/}} diff --git a/application/tr/templates.texy b/application/tr/templates.texy index b335c961a8..635ca43481 100644 --- a/application/tr/templates.texy +++ b/application/tr/templates.texy @@ -2,9 +2,9 @@ ********* .[perex] -Nette, [Latte |latte:] şablonlama sistemini kullanır. Bunun nedeni, hem PHP için en güvenli şablonlama sistemi olması hem de en sezgisel sistem olmasıdır. Çok fazla yeni şey öğrenmenize gerek yoktur, PHP bilginiz ve birkaç etiket yeterlidir. +Nette, [Latte |latte:] şablon sistemini kullanır. Latte kullanılır, çünkü PHP için en güvenli ve aynı zamanda en sezgisel şablon sistemidir. Yeni çok şey öğrenmeniz gerekmez; PHP bilgisi ve birkaç etiket yeterlidir. -Bir sayfanın bir layout şablonu + ilgili eylemin şablonundan oluşması yaygındır. Örneğin bir layout şablonu şöyle görünebilir, `{block}` bloklarına ve `{include}` etiketine dikkat edin: +Bir sayfanın layout şablonu + somut eylemin şablonundan oluşması yaygındır. Bir layout şablonu şöyle görünebilir; `{block}` bloklarına ve `{include}` etiketine dikkat edin: ```latte @@ -20,26 +20,26 @@ Bir sayfanın bir layout şablonu + ilgili eylemin şablonundan oluşması yayg ``` -Ve bu da eylemin şablonu olacaktır: +Eylem şablonu ise şöyle olurdu: ```latte -{block title}Ana Sayfa{/block} +{block title}Ana sayfa{/block} {block content} -

    Ana Sayfa

    +

    Ana sayfa

    ... {/block} ``` -Bu, layout'taki `{include content}` yerine eklenecek olan `content` bloğunu tanımlar ve ayrıca layout'taki `{block title}` öğesinin üzerine yazacak olan `title` bloğunu yeniden tanımlar. Sonucu hayal etmeye çalışın. +Layout'ta `{include content}` yerine eklenen `content` bloğunu tanımlar ve ayrıca layout'taki `{block title}`'ın üzerine yazan `title` bloğunu yeniden tanımlar. Sonucu gözünüzde canlandırmaya çalışın. -Şablonları Bulma ----------------- +Şablon arama +------------ -Presenter'larda hangi şablonun oluşturulacağını belirtmeniz gerekmez, framework yolu kendisi türetir ve size yazmaktan tasarruf sağlar. +Presenter'larda hangi şablonun render edileceğini belirtmeniz gerekmez; framework yolu kendiliğinden çıkarır ve sizi yazmaktan kurtarır. -Her presenter'ın kendi dizinine sahip olduğu bir dizin yapısı kullanıyorsanız, şablonu basitçe bu dizine eylemin (veya view'in) adıyla yerleştirin, yani `default` eylemi için `default.latte` şablonunu kullanın: +Her presenter'ın kendi dizinine sahip olduğu bir dizin yapısı kullanıyorsanız, şablonu bu dizine eylemin (yani görünümün) adıyla koymanız yeterlidir. Örneğin `default` eylemi için `default.latte` şablonunu kullanın: /--pre app/ @@ -49,46 +49,46 @@ app/ └── default.latte \-- -Presenter'ların birlikte tek bir dizinde ve şablonların `templates` klasöründe olduğu bir yapı kullanıyorsanız, onu ya `..latte` dosyasına ya da `/.latte` dosyasına kaydedin: +Presenter'ların tek bir dizinde bir arada olduğu ve şablonların bir `templates` klasöründe bulunduğu bir yapı kullanıyorsanız, onu ya `..latte` ya da `/.latte` dosyasına kaydedin: /--pre app/ └── Presenters/ ├── HomePresenter.php └── templates/ - ├── Home.default.latte ← 1. seçenek - └── Home/ - └── default.latte ← 2. seçenek + ├── Home/ + │ └── default.latte ← 1. seçenek + └── Home.default.latte ← 2. seçenek \-- -`templates` dizini bir seviye yukarıda da yer alabilir, yani presenter sınıflarını içeren dizinle aynı seviyede. +`templates` dizini bir düzey yukarıya, yani presenter sınıflarının bulunduğu dizinle aynı düzeye de konabilir. -Şablon bulunamazsa, presenter [404 - sayfa bulunamadı hatası |presenters#Hata 404 ve Benzerleri] ile yanıt verir. +Şablon bulunamazsa presenter [404 - sayfa bulunamadı hatasıyla |presenters#Hata 404 vb.] yanıt verir. -View'i `$this->setView('jineView')` kullanarak değiştirirsiniz. Ayrıca şablon dosyasını doğrudan `$this->template->setFile('/path/to/template.latte')` kullanarak belirtebilirsiniz. +Görünümü `$this->setView('digerGorunum')` ile değiştirebilirsiniz. Şablon dosyasını `$this->template->setFile('/path/to/template.latte')` ile doğrudan belirtmek de mümkündür. .[note] -Şablonların arandığı dosyalar, olası dosya adları dizisini döndüren [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] metodunu geçersiz kılarak değiştirilebilir. +Şablonların arandığı dosyalar, olası dosya adlarının dizisini döndüren [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] metodu ezilerek değiştirilebilir. -Layout Şablonunu Bulma ----------------------- +Layout şablonu arama +-------------------- -Nette ayrıca layout dosyasını otomatik olarak bulur. +Nette layout dosyasını da otomatik arar. -Her presenter'ın kendi dizinine sahip olduğu bir dizin yapısı kullanıyorsanız, layout'u ya yalnızca ona özelse presenter içeren klasöre ya da birden fazla presenter için ortaksa bir seviye yukarıya yerleştirin: +Her presenter'ın kendi dizinine sahip olduğu bir dizin yapısı kullanıyorsanız, layout'u yalnızca ona özgüyse presenter'ın klasörüne, birden fazla presenter için ortaksa bir düzey yukarıya koyun: /--pre app/ └── Presentation/ ├── @layout.latte ← ortak layout └── Home/ - ├── @layout.latte ← yalnızca Home presenter için + ├── @layout.latte ← yalnızca Home presenter'ı için ├── HomePresenter.php └── default.latte \-- -Presenter'ların birlikte tek bir dizinde ve şablonların `templates` klasöründe olduğu bir yapı kullanıyorsanız, layout şu yerlerde beklenecektir: +Presenter'ların tek bir dizinde toplandığı ve şablonların bir `templates` klasöründe bulunduğu bir yapı kullanıyorsanız, layout şu konumlarda beklenir: /--pre app/ @@ -96,33 +96,66 @@ app/ ├── HomePresenter.php └── templates/ ├── @layout.latte ← ortak layout - ├── Home.@layout.latte ← yalnızca Home için, 1. seçenek - └── Home/ - └── @layout.latte ← yalnızca Home için, 2. seçenek + ├── Home/ + │ └── @layout.latte ← yalnızca Home için, 1. seçenek + └── Home.@layout.latte ← yalnızca Home için, 2. seçenek \-- -Presenter bir modülde bulunuyorsa, modülün iç içe geçme durumuna göre daha üst dizin seviyelerinde de aranacaktır. +Presenter bir modülde yer alıyorsa, modül iç içeliğine göre dizin düzeylerinde daha yukarıya doğru da arama yapılır. -Layout adı `$this->setLayout('layoutAdmin')` kullanılarak değiştirilebilir ve ardından `@layoutAdmin.latte` dosyasında beklenecektir. Ayrıca layout şablonu dosyasını doğrudan `$this->setLayout('/path/to/template.latte')` kullanarak belirtebilirsiniz. +Layout'un adı `$this->setLayout('layoutAdmin')` ile değiştirilebilir, o zaman `@layoutAdmin.latte` dosyasında beklenir. Layout şablonu dosyasını `$this->setLayout('/path/to/template.latte')` ile doğrudan da belirtebilirsiniz. -`$this->setLayout(false)` veya şablon içindeki `{layout none}` etiketi kullanılarak layout araması kapatılır. +`$this->setLayout(false)` veya şablonun içindeki `{layout none}` etiketi, layout aramasını kapatır. .[note] -Layout şablonlarının arandığı dosyalar, olası dosya adları dizisini döndüren [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] metodunu geçersiz kılarak değiştirilebilir. +Layout şablonlarının arandığı dosyalar, olası dosya adlarının dizisini döndüren [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] metodu ezilerek değiştirilebilir. -Şablondaki Değişkenler ----------------------- +Şablon değişkenleri +------------------- -Değişkenleri şablona `$this->template`'e yazarak iletiriz ve ardından şablonda yerel değişkenler olarak kullanılabilirler: +Değişkenler şablonlara `$this->template`'e yazılarak aktarılır. Şablonda yerel değişken olarak erişilebilir olurlar: ```php $this->template->article = $this->articles->getById($id); ``` -Bu şekilde herhangi bir değişkeni şablonlara kolayca iletebiliriz. Ancak sağlam uygulamalar geliştirirken kendimizi sınırlamak daha yararlı olur. Örneğin, şablonun beklediği değişkenlerin listesini ve türlerini açıkça tanımlayarak. Bu sayede PHP türleri kontrol edebilir, IDE doğru şekilde öneride bulunabilir ve statik analiz hataları ortaya çıkarabilir. +Bir özelliğin değerini şablona otomatik olarak değişken şeklinde aktarmak için onu `#[TemplateVariable]` niteliğiyle ve public görünürlükle işaretleyin: .{data-version:3.2.9} + +```php +use Nette\Application\Attributes\TemplateVariable; + +class ArticlePresenter extends Nette\Application\UI\Presenter +{ + #[TemplateVariable] + public string $siteName = 'Blogum'; +} +``` + +Şablona aynı adlı bir değişken aktarırsanız, `#[TemplateVariable]` onun üzerine yazmaz. + + +Varsayılan değişkenler +---------------------- + +Presenter'lar ve bileşenler şablonlara birkaç yararlı değişkeni otomatik aktarır: + +- `$basePath`, kök dizine giden mutlak URL yoludur (örneğin `/eshop`) +- `$baseUrl`, kök dizine giden mutlak URL'dir (örneğin `http://localhost/eshop`) +- `$user`, [kullanıcıyı temsil eden |security:authentication] bir nesnedir +- `$presenter`, geçerli presenter'dır +- `$control`, geçerli bileşen veya presenter'dır +- `$flashes`, `flashMessage()` fonksiyonuyla gönderilen [mesajların |presenters#Flash mesajları] dizisidir + +Kendi şablon sınıfınızı kullanıyorsanız, bu değişkenler onlar için bir özellik oluşturursanız aktarılır. + + +Tip güvenli şablonlar +--------------------- -Ve böyle bir listeyi nasıl tanımlarız? Basitçe bir sınıf ve onun özellikleri şeklinde. Onu presenter'a benzer şekilde adlandırırız, ancak sonunda `Template` ile: +Sağlam uygulamalar geliştirirken, şablonun hangi değişkenleri beklediğini ve bunların tiplerini açıkça tanımlamak yararlıdır. Bu, PHP'de tip denetimi, IDE'nizde akıllı ipuçları sağlar ve statik analizin hataları yakalamasını mümkün kılar. + +Böyle bir listeyi nasıl tanımlarsınız? Basitçe, şablon değişkenlerini temsil eden özelliklere sahip bir sınıf olarak. Onu presenter'a benzer şekilde, sonuna `Template` ekleyerek adlandırın: ```php /** @@ -141,22 +174,24 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template } ``` -Presenter'daki `$this->template` nesnesi artık `ArticleTemplate` sınıfının bir örneği olacaktır. Böylece PHP yazarken bildirilen türleri kontrol edecektir. Ve PHP 8.2 sürümünden itibaren, mevcut olmayan bir değişkene yazma konusunda da uyaracaktır, önceki sürümlerde aynı şeye [Nette\SmartObject |utils:smartobject] trait'ini kullanarak ulaşılabilir. +Presenter'daki `$this->template` nesnesi artık `ArticleTemplate` sınıfının bir örneği olacaktır. Böylece PHP, yazma sırasında bildirilen tipleri denetler. + +Nette şablon sınıfını otomatik seçer. Önce `Template` adlı bir sınıf arar, örneğin `edit` eylemi için `ArticleEditTemplate`, ve ancak o yoksa `Template`'e geri döner. -`@property-read` ek açıklaması IDE ve statik analiz içindir, sayesinde öneri işlevi çalışacaktır, bkz. "PhpStorm and code completion for $this⁠-⁠>⁠template":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. +`@property-read` anotasyonu IDE ve statik analiz içindir, kod tamamlamayı sağlar; bkz. "PhpStorm ve $this⁠-⁠>⁠template için kod tamamlama":https://blog.nette.org/en/phpstorm-and-code-completion-for-this-template. [* phpstorm-completion.webp *] -Öneri lüksünün tadını şablonlarda da çıkarabilirsiniz, sadece PhpStorm'a Latte eklentisini kurmanız ve şablonun başına sınıf adını belirtmeniz yeterlidir, daha fazla bilgi için "Latte: tür sistemi nasıl kullanılır":https://blog.nette.org/tr/latte-how-to-use-type-system makalesine bakın: +Kod tamamlamayı doğrudan şablonlarda da kullanabilirsiniz. PhpStorm için Latte eklentisini kurmanız ve şablonun başında şablon parametre sınıfının adını belirtmeniz yeterlidir; ayrıntılar [Latte: tip sistemi |latte:type-system] bölümünde: ```latte {templateType App\Presentation\Article\ArticleTemplate} ... ``` -Bu şekilde bileşenlerdeki şablonlar da çalışır, sadece adlandırma kuralına uymanız ve örneğin `FifteenControl` bileşeni için `FifteenTemplate` şablon sınıfını oluşturmanız yeterlidir. +Aynısı bileşenler için de geçerlidir. Adlandırma kuralına uyun ve `FifteenControl` gibi bir bileşen için `FifteenTemplate` parametre sınıfını oluşturun. -`$template`'i farklı bir sınıfın örneği olarak oluşturmanız gerekiyorsa, `createTemplate()` metodunu kullanın: +Farklı bir parametre sınıfı kullanmanız gerekirse `createTemplate()` metodunu kullanın: ```php public function renderDefault(): void @@ -168,58 +203,100 @@ public function renderDefault(): void } ``` +.{data-version:3.3.0} +Şablonun render edilmeden önce nasıl tamamlanacağını etkilemeniz gerekirse, örneğin tüm eylemlerde ortak değişkenler eklemek için, presenter'da `completeTemplate()` metodunu ezebilirsiniz. Şablon render edilmeden hemen önce çağrılır: -Varsayılan Değişkenler ----------------------- - -Presenter'lar ve bileşenler, şablonlara otomatik olarak birkaç yararlı değişken iletir: - -- `$basePath`, kök dizine mutlak URL yoludur (örn. `/eshop`) -- `$baseUrl`, kök dizine mutlak URL'dir (örn. `http://localhost/eshop`) -- `$user`, [kullanıcıyı temsil eden |security:authentication] nesnedir -- `$presenter`, mevcut presenter'dır -- `$control`, mevcut bileşen veya presenter'dır -- `$flashes`, `flashMessage()` fonksiyonu tarafından gönderilen [mesajlar |presenters#Flash Mesajları] dizisidir - -Kendi şablon sınıfınızı kullanıyorsanız, bu değişkenler için bir özellik oluşturursanız iletilirler. +```php +protected function completeTemplate(Nette\Application\UI\Template $template): void +{ + parent::completeTemplate($template); + $template->siteName = 'Blogum'; +} +``` -Bağlantı Oluşturma +Bağlantı oluşturma ------------------ -Şablonda, diğer presenter'lara ve eylemlere bağlantılar şu şekilde oluşturulur: +Şablonda başka presenter'lara ve eylemlere bağlantılar şöyle oluşturulur: ```latte ürün detayı ``` -`n:href` niteliği HTML `` etiketleri için çok kullanışlıdır. Bağlantıyı başka bir yerde, örneğin metinde yazdırmak istiyorsak, `{link}` kullanırız: +`n:href` niteliği HTML `` etiketleri için çok kullanışlıdır. Bağlantıyı başka bir yerde, örneğin metin içinde yazdırmak istersek `{link}` kullanırız: ```latte -Adres: {link Home:default} +URL şudur: {link Home:default} ``` -Daha fazla bilgi için [URL Bağlantıları Oluşturma|creating-links] bölümünde bulabilirsiniz. +Daha fazla bilgiyi [URL bağlantıları oluşturma|creating-links] bölümünde bulabilirsiniz. -Özel Filtreler, Etiketler vb. +Özel filtreler, etiketler vb. ----------------------------- -Latte şablonlama sistemi özel filtreler, fonksiyonlar, etiketler vb. ile genişletilebilir. Bu, doğrudan `render` veya `beforeRender()` metodunda yapılabilir: +Latte şablon sistemi özel filtreler, fonksiyonlar, etiketler ve başka öğelerle genişletilebilir. Hızlı geçici çözümlerden tüm uygulamalar için mimari kalıplara uzanan üç yaklaşım vardır. + +**Presenter metotlarında geçici olarak** + +En hızlı yaklaşım, filtreleri veya fonksiyonları doğrudan presenter ya da bileşen kodunda eklemektir. Presenter'larda bunun için `beforeRender()` veya `render()` metotları uygundur: ```php -public function beforeRender(): void +protected function beforeRender(): void { // filtre ekleme - $this->template->addFilter('foo', /* ... */); + $this->template->addFilter('money', fn($val) => '$' . number_format($val, 2)); + + // fonksiyon ekleme + $this->template->addFunction('isWeekend', fn($date) => $date->format('N') >= 6); +} +``` + +Şablonda: - // veya doğrudan Latte\Engine nesnesini yapılandırırız +```latte +

    Fiyat: {$price|money}

    + +{if isWeekend($now)} ... {/if} +``` + +Daha karmaşık mantık için `Latte\Engine` nesnesini doğrudan yapılandırabilirsiniz: + +```php +protected function beforeRender(): void +{ $latte = $this->template->getLatte(); - $latte->addFilterLoader(/* ... */); + $latte->setFeature(Latte\Feature::MigrationWarnings); +} +``` + +**Nitelikleri kullanarak** + +Daha zarif bir yaklaşım, filtreleri ve fonksiyonları doğrudan presenter'ın veya bileşenin [şablon parametre sınıfında|#Tip güvenli şablonlar] metot olarak tanımlamak ve niteliklerle işaretlemektir: + +```php +class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template +{ + #[Latte\Attributes\TemplateFilter] + public function money(float $val): string + { + return '$' . number_format($val, 2); + } + + #[Latte\Attributes\TemplateFunction] + public function isWeekend(DateTimeInterface $date): bool + { + return $date->format('N') >= 6; + } } ``` -Latte sürüm 3, her web projesi için bir [extension |latte:extending-latte#Latte Extension] oluşturarak daha gelişmiş bir yol sunar. Böyle bir sınıfın kaba bir örneği: +Latte, bu niteliklerle işaretlenmiş metotları otomatik bulur ve kaydeder. Şablonlardaki filtre veya fonksiyon adı metot adıyla aynıdır. Bu metotlar public olmalıdır. + +**Uzantılarla genel olarak** + +Önceki yaklaşımlar, yalnızca belirli presenter'larda veya bileşenlerde gereken filtre ve fonksiyonlar için uygundur, uygulama geneli için değil. Tüm uygulama için bir [uzantı |latte:extending-latte#Latte Extension] oluşturmak en iyi sonucu verir. Bu sınıf, projenizdeki tüm Latte uzantılarını tek yerde toplar. Kısa bir örnek: ```php namespace App\Presentation\Accessory; @@ -251,11 +328,16 @@ final class LatteExtension extends Latte\Extension ]; } + private function filterTimeAgoInWords(DateTimeInterface $time): string + { + // ... + } + // ... } ``` -Onu [yapılandırma |configuration#Latte Şablonları] kullanarak kaydederiz: +Uzantıyı [yapılandırma |configuration#Latte şablonları] üzerinden kaydedin: ```neon latte: @@ -263,13 +345,27 @@ latte: - App\Presentation\Accessory\LatteExtension ``` +Uzantılar birkaç avantaj sunar: bağımlılık enjeksiyonu desteği, uygulamanızın model katmanına erişim ve tüm uzantıların tek yerden yönetimi. Ayrıca özel etiketleri, sağlayıcıları, compiler pass'leri ve daha fazlasını desteklerler. + + +Tüm şablonların ayarlanması +--------------------------- + +Tüm şablonları oluşturan `TemplateFactory` servisi, public bir `$onCreate` callback dizisi sunar. Bunlar herhangi bir şablon her oluşturulduğunda çağrılır, böylece uygulamadaki tüm şablonlar için filtreleri, fonksiyonları veya değişkenleri tek bir yerden ayarlayabilirsiniz. Her callback, yeni oluşturulan şablonu alır. `TemplateFactory` servisini [enjekte ettirin |dependency-injection:passing-dependencies] ve callback'leri, örneğin uygulama başlarken kaydedin: + +```php +$templateFactory->onCreate[] = function (Nette\Bridges\ApplicationLatte\Template $template): void { + $template->addFilter('money', fn($val) => '$' . number_format($val, 2)); +}; +``` + Çeviri ------ -Çok dilli bir uygulama programlıyorsanız, muhtemelen şablondaki bazı metinleri farklı dillerde yazdırmanız gerekecektir. Nette Framework bu amaçla tek bir metodu `translate()` olan [api:Nette\Localization\Translator] çeviri arayüzünü tanımlar. Bu metot, genellikle bir dize olan `$message` mesajını ve isteğe bağlı diğer parametreleri alır. Görevi, çevrilmiş dizeyi döndürmektir. Nette'de varsayılan bir uygulama yoktur, ihtiyaçlarınıza göre [Componette |https://componette.org/search/localization] adresinde bulabileceğiniz birkaç hazır çözüm arasından seçim yapabilirsiniz. Belgelerinde çevirmeni nasıl yapılandıracağınızı öğreneceksiniz. +Çok dilli bir uygulama programlıyorsanız, büyük olasılıkla şablondaki bazı metinleri farklı dillerde çıkarmanız gerekecek. Nette Framework bunun için tek bir `translate()` metoduna sahip [api:Nette\Localization\Translator] çeviri arayüzünü tanımlar. Genellikle bir dize olan `$message` mesajını ve başka parametreleri kabul eder. Görevi çevrilmiş dizeyi döndürmektir. Nette'in varsayılan bir uygulaması yoktur; ihtiyacınıza göre [Componette |https://componette.org/search/localization] üzerinde bulunan hazır çözümlerden seçebilirsiniz. Çevirmenin nasıl yapılandırılacağını dokümantasyonları anlatır. -Şablonlara, `setTranslator()` metoduyla [bize iletilmesini istediğimiz |dependency-injection:passing-dependencies] bir çevirmen ayarlanabilir: +Şablonlara, [aktarılmasını sağladığımız |dependency-injection:passing-dependencies] çevirmen `setTranslator()` metoduyla ayarlanabilir: ```php protected function beforeRender(): void @@ -279,7 +375,7 @@ protected function beforeRender(): void } ``` -Çevirmen alternatif olarak [yapılandırma |configuration#Latte Şablonları] kullanılarak ayarlanabilir: +Alternatif olarak çevirmen [yapılandırma |configuration#Latte şablonları] ile ayarlanabilir: ```neon latte: @@ -287,7 +383,7 @@ latte: - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` -Ardından çevirmen örneğin `|translate` filtresi olarak kullanılabilir ve `translate()` metoduna iletilen ek parametreler dahil (bkz. `foo, bar`): +Sonra çevirmen örneğin bir `|translate` filtresi olarak, `translate()` metoduna aktarılan ek parametrelerle birlikte kullanılabilir (bkz. `foo, bar`): ```latte
    {='Sepet'|translate} @@ -295,7 +391,7 @@ Ardından çevirmen örneğin `|translate` filtresi olarak kullanılabilir ve `t {$item|translate, foo, bar} ``` -Veya alt çizgi etiketi olarak: +Ya da alt çizgi etiketi olarak: ```latte {_'Sepet'} @@ -303,14 +399,14 @@ Veya alt çizgi etiketi olarak: {_$item, foo, bar} ``` -Şablonun bir bölümünü çevirmek için eşli `{translate}` etiketi vardır (Latte 2.11'den itibaren, daha önce `{_}` etiketi kullanılırdı): +Şablonun bir bölümünü çevirmek için `{translate}` çift etiketi vardır (Latte 2.11'den beri, önceden `{_}` etiketi kullanılıyordu): ```latte {translate}Sipariş{/translate} {translate foo, bar}Sipariş{/translate} ``` -Çevirmen standart olarak şablon oluşturulurken çalışma zamanında çağrılır. Ancak Latte sürüm 3, tüm statik metinleri şablon derlemesi sırasında çevirebilir. Bu, her dize yalnızca bir kez çevrildiği ve sonuçtaki çeviri derlenmiş forma yazıldığı için performanstan tasarruf sağlar. Önbellek dizininde, her dil için bir tane olmak üzere şablonun birden fazla derlenmiş sürümü oluşturulur. Bunun için dili ikinci parametre olarak belirtmek yeterlidir: +Çevirmen normalde şablon render edilirken, çalışma zamanında çağrılır. Ancak Latte 3 sürümü, tüm statik metinleri daha şablon derlenirken çevirebilir. Bu performanstan tasarruf sağlar, çünkü her dize yalnızca bir kez çevrilir ve elde edilen çeviri derlenmiş biçime yazılır. Böylece önbellek dizininde şablonun her dil için bir tane olmak üzere birden fazla derlenmiş sürümü oluşur. Bunun için dili ikinci parametre olarak belirtmeniz yeterlidir: ```php protected function beforeRender(): void @@ -320,4 +416,4 @@ protected function beforeRender(): void } ``` -Statik metin derken örneğin `{_'merhaba'}` veya `{translate}merhaba{/translate}` kastedilir. `{_$foo}` gibi statik olmayan metinler çalışma zamanında çevrilmeye devam edecektir. +Statik metin, örneğin `{_'merhaba'}` veya `{translate}merhaba{/translate}` demektir. `{_$foo}` gibi statik olmayan metinler çalışma zamanında çevrilmeye devam eder. diff --git a/application/tr/upgrading.texy b/application/tr/upgrading.texy new file mode 100644 index 0000000000..64046aa60b --- /dev/null +++ b/application/tr/upgrading.texy @@ -0,0 +1,47 @@ +Yükseltme +********* + + +Sürüm 3.0'a yükseltme +===================== + +Nette 3.0, metot parametrelerine ve dönüş değerlerine tür bildirimleri ekler. Nette'ten kalıtım alan bir sınıfta (örneğin bir presenter'da veya bileşende) böyle bir metodu ezerseniz, aynı tür bildirimlerini eklemeniz gerekir; aksi halde PHP "Declaration must be compatible" hatası verir. + +`Nette\Application\IRouter` arayüzü değişti. `match()` metodu artık bir `Nette\Application\Request` nesnesi yerine bir parametre dizisi döndürür, `constructUrl()` de böyle bir dizi kabul eder. + +Nette artık her sinyalin aynı origin'den (yani aynı alan adı ve alt alan adından) gönderildiğini denetler. Bu same-origin politikası, olası saldırı vektörlerini azaltmaya yardımcı olan kritik bir güvenlik mekanizmasıdır. Başka origin'lere izin vermek isterseniz, işleyici metoduna `@crossOrigin` anotasyonunu ekleyin: + +```php +/** + * @crossOrigin + */ +public function handleXy(): void +{ +} +``` + +Aynısı form gönderimleri için de geçerlidir. Başka origin'lerden gönderime izin vermek isterseniz şöyle yapın: + +```php +$form = new Nette\Application\UI\Form; +$form->allowCrossOrigin(); +``` + +`Nette\ComponentModel\Component` sınıfının constructor'ı yıllardır kullanılmıyordu ve 3.0 sürümünde kaldırıldı. Bu bir BC break'tir: `Nette\Application\UI\Presenter`'dan kalıtım alan bir bileşende veya presenter'da üst sınıfın constructor'ını çağırıyorsanız, çağrıyı kaldırmanız gerekir. + + +Sürüm 2.4'e yükseltme +===================== + +- `Route` ve `SimpleRouter` artık siteye erişilirken kullanılan HTTP/HTTPS şemasının aynısını üretir. Belirli bir protokol gerektiren bir rota, şemayla birlikte tanımlanabilir, örneğin `Route('http://domain.cz/')`. +- render/action metotlarının bool tipindeki parametrelerinde (yani varsayılan değeri true veya false olanlarda) ve kalıcı parametrelerde artık `false` ile `null` birbirinden ayrılıyor. Parametre URL'de yoksa değeri artık `null` (önceden `false`). +- `Presenter::getReflection()` tarafından döndürülen sınıf artık `Nette\Reflection\ClassType`'ın torunu değil, `getReflection()->getMethod()` da artık `Nette\Reflection\Method`'un torunu değil. +- `SECURED` bayrağı ve `Route::$defaultFlags` kullanımdan kaldırıldı. + + +Sürüm 2.3'e yükseltme +===================== + +- rotalar ve presenter adları **büyük-küçük harfe duyarlıdır**. Presenter adında yanlış harf kullanırsanız Nette sizi uyarır; performans nedeniyle Route maskesi denetlenmez, bu yüzden onu elle doğrulayın. +- `Route::addStyle()` ve `Route::setStyleProperty()` kullanımdan kaldırıldı ve artık `E_USER_DEPRECATED` tetikliyor. +- `.phtml` şablon uzantısı ve eski bağlantı sözdizimi artık desteklenmiyor. diff --git a/assets/cs/@home.texy b/assets/cs/@home.texy index b76f584b80..fcd04cf3cd 100644 --- a/assets/cs/@home.texy +++ b/assets/cs/@home.texy @@ -3,7 +3,7 @@ Nette Assets
    -Už vás nebaví ručně spravovat statické soubory ve vašich webových aplikacích? Zapomeňte na pevné kódování cest, řešení zneplatnění cache nebo starosti s verzováním souborů. Nette Assets transformuje způsob, jakým pracujete s obrázky, styly, skripty a dalšími statickými zdroji. +Už vás nebaví ručně spravovat statické soubory ve vašich webových aplikacích? Zapomeňte na natvrdo zapsané cesty, řešení zneplatnění cache nebo starosti s verzováním souborů. Nette Assets transformuje způsob, jakým pracujete s obrázky, styly, skripty a dalšími statickými zdroji. - **Chytré verzování** zajistí, že prohlížeče vždy načtou nejnovější soubory - **Automatická detekce** typů a rozměrů souborů @@ -17,7 +17,7 @@ Už vás nebaví ručně spravovat statické soubory ve vašich webových aplika Proč Nette Assets? ================== -Práce se statickými soubory často znamená opakující se, chybový kód. Ručně konstruujete URL, přidáváte parametry verzí pro zrušení cache a řešíte různé typy souborů odlišně. To vede ke kódu jako: +Práce se statickými soubory často znamená opakující se kód náchylný k chybám. Ručně konstruujete URL, přidáváte parametry verzí pro invalidaci cache a řešíte různé typy souborů odlišně. To vede ke kódu jako: ```latte Logo @@ -57,7 +57,7 @@ Vyžaduje PHP 8.1 nebo vyšší a perfektně funguje s Nette Frameworkem, ale lz První kroky =========== -Nette Assets funguje ihned po instalaci bez jakékékoli konfigurace. Umístěte své statické soubory do adresáře `www/assets/` a začněte je používat: +Nette Assets funguje ihned po instalaci bez jakékoli konfigurace. Umístěte své statické soubory do adresáře `www/assets/` a začněte je používat: ```latte {* Zobrazí obrázek s automatickými rozměry *} @@ -66,7 +66,7 @@ Nette Assets funguje ihned po instalaci bez jakékékoli konfigurace. Umístěte {* Zahrne šablonu stylů s verzováním *} {asset 'style.css'} -{* Načte JavaScript modul *} +{* Načte skript *} {asset 'app.js'} ``` @@ -79,14 +79,15 @@ Jak to funguje Nette Assets je postaveno na třech základních konceptech, které jej činí výkonným, ale zároveň jednoduchým na použití: -Assets - vaše soubory chytře ----------------------------- +Assety - chytré soubory +----------------------- **Asset** představuje jakýkoli statický soubor ve vaší aplikaci. Každý soubor se stává objektem s užitečnými vlastnostmi jen pro čtení: ```php $image = $assets->getAsset('photo.jpg'); echo $image->url; // '/assets/photo.jpg?v=1699123456' +echo $image->file; // '/var/www/assets/photo.jpg' (lokální cesta, nebo null) echo $image->width; // 1920 echo $image->height; // 1080 echo $image->mimeType; // 'image/jpeg' @@ -96,7 +97,7 @@ Různé typy souborů poskytují různé vlastnosti: - **Obrázky**: šířka, výška, alternativní text, líné načítání - **Skripty**: typ modulu, integrity hashe, crossorigin - **Styly**: media queries, integrity -- **Audio/Video**: délka, rozměry +- **Audio/Video**: délka, rozměry (jen video) - **Fonty**: správné přednačítání s CORS Knihovna automaticky detekuje typy souborů a vytváří odpovídající třídu assetu. @@ -107,7 +108,7 @@ Mappery - odkud soubory pocházejí **Mapper** ví, jak najít soubory a vytvořit pro ně URL. Můžete mít více mapperů pro různé účely - lokální soubory, CDN, cloudové úložiště nebo build nástroje (každý z nich má jméno). Vestavěný `FilesystemMapper` zpracovává lokální soubory, zatímco `ViteMapper` se integruje s moderními build nástroji. -Mappery jsou definovány v [konfiguraci]. +Mappery jsou definovány v [konfiguraci |configuration]. Registry - vaše hlavní rozhraní @@ -116,21 +117,23 @@ Registry - vaše hlavní rozhraní **Registry** spravuje všechny mappery a poskytuje hlavní API: ```php -// Vstříkněte registry do vaší služby +// Nechte si registry předat do své služby public function __construct( private Nette\Assets\Registry $assets ) {} +``` +```php // Získejte assety z různých mapperů -$logo = $this->assets->getAsset('images:logo.png'); // 'image' mapper -$app = $this->assets->getAsset('app:main.js'); // 'app' mapper +$logo = $this->assets->getAsset('images:logo.png'); // mapper 'images' +$app = $this->assets->getAsset('app:main.js'); // mapper 'app' $style = $this->assets->getAsset('style.css'); // používá výchozí mapper ``` -Registry automaticky vybírá správný mapper a kešuje výsledky pro výkon. +Registry automaticky vybírá správný mapper a kvůli výkonu kešuje výsledky. -Práce s Assets v PHP +Práce s assety v PHP ==================== Registry poskytuje dvě metody pro získávání assetů: @@ -178,7 +181,7 @@ echo $image->mimeType; // 'image/jpeg' // Vlastnosti skriptu $script = $assets->getAsset('app.js'); -echo $script->type; // 'module' or null +echo $script->type; // null ('module' u Vite entry pointů) // Vlastnosti audia $audio = $assets->getAsset('song.mp3'); @@ -191,8 +194,11 @@ $url = (string) $assets->getAsset('document.pdf'); .[note] Vlastnosti jako rozměry nebo délka se načítají líně pouze při přístupu, což udržuje knihovnu rychlou. +.[tip] +Pro přesnou statickou analýzu nainstalujte rozšíření [nette/phpstan-rules |tools:phpstan-rules#Assety]. PHPStan pak zná konkrétní typ každého assetu, takže `getAsset('photo.jpg')` chápe jako `ImageAsset` a přístup k `->width` nehlásí chybu. + -Použití Assets v Latte šablonách +Použití assetů v šablonách Latte ================================ Nette Assets poskytuje intuitivní integraci s [Latte|latte:] pomocí tagů a funkcí. @@ -207,7 +213,7 @@ Tag `{asset}` vykresluje kompletní HTML elementy: {* Vykreslí: *} {asset 'hero.jpg'} -{* Vykreslí: *} +{* Vykreslí: *} {asset 'app.js'} {* Vykreslí: *} @@ -216,11 +222,11 @@ Tag `{asset}` vykresluje kompletní HTML elementy: Tag automaticky: - Detekuje typ assetu a generuje odpovídající HTML -- Zahrnuje verzování pro zrušení cache +- Zahrnuje verzování pro invalidaci cache - Přidává rozměry pro obrázky - Nastavuje správné atributy (typ, media atd.) -Při použití uvnitř HTML atributů vypíše pouze URL: +Při použití uvnitř HTML atributů nebo uvnitř elementů ` ``` -Weitere Validierungsregeln fügen wir mit der Methode `addRule()` hinzu. Der erste Parameter ist die Regel, der zweite ist wieder der Text der Fehlermeldung, und es kann noch ein Argument der Validierungsregel folgen. Was ist damit gemeint? +Weitere Validierungsregeln fügen wir über die Methode `addRule()` hinzu. Der erste Parameter ist die Regel, der zweite wiederum der Text der Fehlermeldung, und danach kann ein Argument für die Validierungsregel folgen. Was bedeutet das? -Wir erweitern das Formular um ein neues optionales Feld „Alter“, das eine ganze Zahl sein muss (`addInteger()`) und außerdem in einem erlaubten Bereich (`$form::Range`) liegen muss. Und hier verwenden wir genau den dritten Parameter der Methode `addRule()`, mit dem wir dem Validator den erforderlichen Bereich als Paar `[von, bis]` übergeben: +Erweitern wir das Formular um ein neues optionales Feld "Alter", das eine ganze Zahl sein muss (`addInteger()`) und außerdem in einem erlaubten Bereich liegen muss (`$form::Range`). Hier verwenden wir den dritten Parameter der Methode `addRule()`, um dem Validator den verlangten Bereich als Paar `[min, max]` zu übergeben: ```php $form->addInteger('age', 'Alter:') - ->addRule($form::Range, 'Das Alter muss zwischen 18 und 120 liegen', [18, 120]); + ->addRule($form::Range, 'Das Alter muss zwischen 18 und 120 liegen.', [18, 120]); ``` .[tip] -Wenn der Benutzer das Feld nicht ausfüllt, werden die Validierungsregeln nicht überprüft, da das Element optional ist. +Füllt der Benutzer das Feld nicht aus, werden die Validierungsregeln nicht geprüft, denn das Element ist optional. -Hier entsteht Raum für ein kleines Refactoring. In der Fehlermeldung und im dritten Parameter sind die Zahlen doppelt aufgeführt, was nicht ideal ist. Wenn wir [mehrsprachige Formulare |rendering#Übersetzung] erstellen würden und die Meldung mit Zahlen in mehrere Sprachen übersetzt würde, würde eine spätere Änderung der Werte erschwert. Aus diesem Grund können Platzhalter `%d` verwendet werden, und Nette füllt die Werte ein: +Damit entsteht Raum für ein kleines Refactoring. In der Fehlermeldung und im dritten Parameter stehen die Zahlen doppelt, was nicht ideal ist. Würden wir [mehrsprachige Formulare |rendering#Übersetzen] bauen und die Meldung mit den Zahlen in mehrere Sprachen übersetzen, wäre das Ändern der Werte mühsam. Aus diesem Grund lassen sich die Platzhalter `%d` verwenden, und Nette setzt die Werte ein: ```php - ->addRule($form::Range, 'Das Alter muss zwischen %d und %d Jahren liegen', [18, 120]); + ->addRule($form::Range, 'Das Alter muss zwischen %d und %d Jahren liegen.', [18, 120]); ``` -Kehren wir zum Element `password` zurück, das wir ebenfalls obligatorisch machen und noch die minimale Passwortlänge überprüfen (`$form::MinLength`), wieder unter Verwendung des Platzhalters: +Kehren wir zum Element `password` zurück, machen es ebenfalls zum Pflichtfeld und prüfen außerdem die Mindestlänge des Passworts (`$form::MinLength`), wieder mit einem Platzhalter in der Meldung: ```php $form->addPassword('password', 'Passwort:') ->setRequired('Wählen Sie ein Passwort') - ->addRule($form::MinLength, 'Das Passwort muss mindestens %d Zeichen lang sein', 8); + ->addRule($form::MinLength, 'Ihr Passwort muss mindestens %d Zeichen lang sein.', 8); ``` -Wir fügen dem Formular noch ein Feld `passwordVerify` hinzu, in das der Benutzer das Passwort zur Kontrolle noch einmal eingibt. Mit Validierungsregeln überprüfen wir, ob beide Passwörter übereinstimmen (`$form::Equal`). Und als Parameter geben wir einen Verweis auf das erste Passwort mithilfe von [eckigen Klammern |#Zugriff auf Elemente] an: +Fügen wir dem Formular noch ein Feld `passwordVerify` hinzu, in dem der Benutzer das Passwort zur Bestätigung erneut eingibt. Über Validierungsregeln prüfen wir, ob beide Passwörter gleich sind (`$form::Equal`). Als Argument geben wir über [eckige Klammern |#Zugriff auf die Elemente] einen Verweis auf das erste Passwort an: ```php -$form->addPassword('passwordVerify', 'Passwort zur Kontrolle:') - ->setRequired('Bitte geben Sie das Passwort zur Kontrolle noch einmal ein') - ->addRule($form::Equal, 'Die Passwörter stimmen nicht überein', $form['password']) +$form->addPassword('passwordVerify', 'Passwort erneut:') + ->setRequired('Geben Sie das Passwort zur Kontrolle erneut ein') + ->addRule($form::Equal, 'Die Passwörter stimmen nicht überein.', $form['password']) ->setOmitted(); ``` -Mit `setOmitted()` haben wir das Element markiert, dessen Wert uns eigentlich egal ist und das nur aus Validierungsgründen existiert. Der Wert wird nicht an `$data` übergeben. +Mit `setOmitted()` haben wir ein Element gekennzeichnet, dessen Wert uns eigentlich nicht interessiert und das nur zur Validierung da ist. Sein Wert wird nicht an `$data` übergeben. -Damit haben wir ein voll funktionsfähiges Formular mit Validierung in PHP und JavaScript fertiggestellt. Die Validierungsfähigkeiten von Nette sind weitaus umfangreicher, es können Bedingungen erstellt, Teile der Seite entsprechend ein- und ausgeblendet werden usw. Alles erfahren Sie im Kapitel über [Formularvalidierung|validation]. +Damit haben wir ein voll funktionsfähiges Formular mit Validierung in PHP und in JavaScript. Die Fähigkeiten von Nette zur Validierung reichen viel weiter; es lassen sich Bedingungen erstellen, anhand derer sich Teile der Seite ein- und ausblenden lassen, und so weiter. Alles erfahren Sie im Kapitel über die [Validierung von Formularen|validation]. Standardwerte ============= -Formularelementen weisen wir üblicherweise Standardwerte zu: +Für Formularelemente setzen wir üblicherweise Standardwerte: ```php $form->addEmail('email', 'E-Mail') ->setDefaultValue($lastUsedEmail); ``` -Oft ist es nützlich, Standardwerte für alle Elemente gleichzeitig festzulegen. Zum Beispiel, wenn das Formular zur Bearbeitung von Datensätzen dient. Wir lesen den Datensatz aus der Datenbank und setzen die Standardwerte: +Oft ist es nützlich, die Standardwerte für alle Elemente auf einmal zu setzen. Zum Beispiel, wenn das Formular zum Bearbeiten von Datensätzen dient. Wir lesen den Datensatz aus der Datenbank und setzen die Standardwerte: ```php -//$row = ['name' => 'John', 'age' => '33', /* ... */]; +// $row = ['name' => 'John', 'age' => '33', /* ... */]; $form->setDefaults($row); ``` -Rufen Sie `setDefaults()` erst nach der Definition der Elemente auf. +Rufen Sie `setDefaults()` nach dem Definieren der Elemente auf. + +Bei einem bereits abgesendeten Formular hat `setDefaults()` keine Wirkung - es überschreibt nicht, was der Benutzer ausgefüllt hat, es ist also gefahrlos, es in der Factory des Formulars ohne Bedingung aufzurufen. Wenn Sie die Werte auch nach dem Absenden erzwingen müssen, verwenden Sie stattdessen `setValues()`. Rendering des Formulars ======================= -Standardmäßig wird das Formular als Tabelle gerendert. Die einzelnen Elemente erfüllen die grundlegende Zugänglichkeitsregel – alle Beschriftungen sind als `
    - +
    @@ -56,9 +56,9 @@ protected function createComponentSignInForm(): Form ``` -Die Form des resultierenden HTML-Codes liegt vollständig in Ihren Händen. Wenn Sie das Attribut `n:name` bei den Elementen ` ``` -Výchozím a základním kontextem HTML stránky je HTML text. Jaká zde platí pravidla? Speciální význam mají znaky `<` a `&`, které představují začátek značky nebo entity, takže je musíme escapovat, a to nahrazením za HTML entitu (`<` za `<` `&` za `&`). +Výchozím a základním kontextem HTML stránky je HTML text. Jaká zde platí pravidla? Speciální význam mají znaky `<` a `&`, které představují začátek značky nebo entity, takže je musíme escapovat, a to nahrazením za HTML entitu (`<` za `<`, `&` za `&`). -Druhým nejběžnějším kontextem je hodnota HTML atributu. Od textu se liší v tom, že speciální význam tu má uvozovka `"` nebo `'`, která atribut ohraničuje. Tu je třeba zapsat entitou, aby nebyla chápána jako konec atributu. Naopak v atributu lze bezpečně používat znak `<`, protože tady žádný speciální význam nemá, tady nemůže být chápán jako začátek značky či komentáře. Ale pozor, v HTML lze psát hodnoty atributů i bez uvozovek, v takovém případě má speciální význam celá řada znaků, jde tedy o další samostatný kontext. +Druhým nejběžnějším kontextem je hodnota HTML atributu. Od textu se liší v tom, že speciální význam tu má uvozovka `"` nebo `'`, která atribut ohraničuje. Tu je třeba zapsat entitou, aby nebyla chápána jako konec atributu. Naopak v atributu lze bezpečně používat znak `<`, protože tady žádný speciální význam nemá a nemůže být chápán jako začátek značky či komentáře. Ale pozor, v HTML lze psát hodnoty atributů i bez uvozovek, v takovém případě má speciální význam celá řada znaků, jde tedy o další samostatný kontext. Možná vás překvapí, ale speciální pravidla platí uvnitř elementů ` - + ``` -Der Standard- und Grundkontext einer HTML-Seite ist HTML-Text. Welche Regeln gelten hier? Eine besondere Bedeutung haben die Zeichen `<` und `&`, die den Anfang eines Tags oder einer Entität darstellen, daher müssen wir sie escapen, und zwar durch Ersetzen durch eine HTML-Entität (`<` durch `<`, `&` durch `&`). +Der standardmäßige und grundlegende Kontext einer HTML-Seite ist HTML-Text. Welche Regeln gelten hier? Die Zeichen `<` und `&` haben eine besondere Bedeutung, sie stehen für den Beginn eines Tags oder einer Entity, wir müssen sie also escapen, indem wir sie durch HTML-Entities ersetzen (`<` wird zu `<`, `&` wird zu `&`). -Der zweithäufigste Kontext ist der Wert eines HTML-Attributs. Er unterscheidet sich vom Text dadurch, dass hier das Anführungszeichen `"` oder `'`, das das Attribut begrenzt, eine besondere Bedeutung hat. Es muss als Entität geschrieben werden, damit es nicht als Ende des Attributs verstanden wird. Im Attribut kann das Zeichen `<` hingegen sicher verwendet werden, da es hier keine besondere Bedeutung hat, hier kann es nicht als Anfang eines Tags oder Kommentars verstanden werden. Aber Vorsicht, in HTML können Attributwerte auch ohne Anführungszeichen geschrieben werden, in diesem Fall hat eine ganze Reihe von Zeichen eine besondere Bedeutung, es handelt sich also um einen weiteren separaten Kontext. +Der zweithäufigste Kontext ist der Wert eines HTML-Attributs. Er unterscheidet sich vom Text darin, dass hier das Anführungszeichen `"` oder `'`, das das Attribut begrenzt, eine besondere Bedeutung hat. Es muss als Entity geschrieben werden, damit es nicht als Ende des Attributs interpretiert wird. Umgekehrt lässt sich das Zeichen `<` innerhalb eines Attributs gefahrlos verwenden, weil es dort keine besondere Bedeutung hat; es kann nicht als Beginn eines Tags oder Kommentars interpretiert werden. Aber Achtung: In HTML lassen sich Attributwerte auch ohne Anführungszeichen schreiben, und dann hat eine ganze Reihe von Zeichen eine besondere Bedeutung, womit ein weiterer eigener Kontext entsteht. -Vielleicht überrascht es Sie, aber spezielle Regeln gelten innerhalb der Elemente ` ``` -The default and basic context of an HTML page is HTML text. What are the rules here? The characters `<` and `&` have special meanings, representing the start of a tag or entity, so we must escape them by replacing them with HTML entities (`<` becomes `<`, `&` becomes `&`). +The default and basic context of an HTML page is HTML text. What are the rules here? The characters `<` and `&` have special meanings, representing the start of a tag or entity, so we must escape them by replacing them with HTML entities (`<` becomes `<`, `&` becomes `&`). The second most common context is the value of an HTML attribute. It differs from text in that the quotation mark `"` or `'`, which delimits the attribute, has a special meaning here. It needs to be written as an entity so it's not interpreted as the end of the attribute. Conversely, the `<` character can be used safely within an attribute because it has no special meaning there; it cannot be interpreted as the start of a tag or comment. But beware, in HTML, attribute values can also be written without quotes, in which case a whole range of characters have special meanings, making it another separate context. @@ -108,7 +108,7 @@ It gets interesting inside HTML comments. Here, HTML entities are not used for e Contexts can also be layered, which occurs when we embed JavaScript or CSS into HTML. This can be done in two different ways, using an element or an attribute: -```html +```latte @@ -132,7 +132,7 @@ Let's take the string `Rock'n'Roll`. If you print it in HTML text, in this particular case, no replacement is needed because the string does not contain any characters with special meaning. The situation changes if you print it inside an HTML attribute enclosed in single quotes. In that case, you need to escape the quotes into HTML entities: -```html +```latte
    ``` @@ -152,13 +152,13 @@ alert('Rock\'n\'Roll'); If we insert this code into an HTML document using ` ``` However, if we wanted to insert it into an HTML attribute, we still need to escape the quotes into HTML entities: -```html +```latte
    ``` @@ -168,9 +168,9 @@ But the nested context doesn't have to be just JS or CSS. It is also commonly a https://example.org/?a=Jazz&b=Rock%27n%27Roll ``` -And when we print this string in an attribute, we still apply escaping according to this context and replace `&` with `&`: +And when we print this string in an attribute, we still apply escaping according to this context and replace `&` with `&`: -```html +```latte ``` @@ -314,7 +314,7 @@ Notice that there are no quotes around the attribute values. The coder might hav An attacker inserts a cleverly crafted string `foo onload=alert('Hacked!')` as the image caption. We already know that Twig cannot determine whether a variable is being printed in the HTML text flow, inside an attribute, an HTML comment, etc.; in short, it does not distinguish contexts. And it just mechanically converts the characters `< > & ' "` into HTML entities. So the resulting code will look like this: -```html +```latte foo ``` @@ -330,7 +330,7 @@ Now let's see how Latte handles the same template: Latte sees the template the same way you do. Unlike Twig, it understands HTML and knows that the variable is being printed as the value of an attribute that is not enclosed in quotes. Therefore, it adds them. When an attacker inserts the same caption, the resulting code will look like this: -```html +```latte foo onload=alert('Hacked!') ``` @@ -351,16 +351,16 @@ Thanks to context-aware escaping, it is possible to use PHP variables natively w If the variable `$movie` contains the string `'Amarcord & 8 1/2'`, the following output will be generated. Notice the different escaping used within HTML compared to within JavaScript, and yet another different escaping in the `onclick` attribute: ```latte -

    Amarcord & 8 1/2

    +

    Amarcord & 8 1/2

    - + ``` Link Checking ------------- -Latte automatically checks whether a variable used in `src` or `href` attributes contains a web URL (i.e., HTTP protocol) and prevents the output of links that could pose a security risk. +Latte automatically checks whether a variable used in URL attributes such as `src`, `href`, `action`, `formaction`, or `data` on the `` element contains a safe URL. It permits common protocols (`http`, `https`, `ftp`, `mailto`, `tel`, `sms`) as well as relative URLs, while blocking potentially dangerous ones such as `javascript:`. ```latte {var $link = 'javascript:attack()'} @@ -377,7 +377,32 @@ Outputs: The check can be disabled using the [nocheck |filters#nocheck] filter. +Client-Side Frameworks +---------------------- + +Frameworks such as Vue or Angular treat a part of the page as their own template and interpret the double curly braces `{{ ... }}` inside it as an expression. If an attacker manages to get such a sequence into a printed variable, the framework evaluates it in the browser after the page has loaded, even though no XSS occurred on the server side. + +Latte prevents this. When printing into HTML text, it converts the pair `{{` into `{{`; the empty comment is invisible to the reader, yet it prevents the framework from recognizing its own tag. Any other occurrence of the `{` character is replaced by the entity `{` (inside attribute values, every `{` is escaped this way). + +```latte +{var $query = 'Hi {{ constructor.constructor("alert(1)")() }}'} + +

    Search: {$query}

    +``` + +Outputs: + +``` +

    Search: Hi {{ constructor.constructor("alert(1)")() }}

    +``` + +The protection works automatically and requires no configuration. + + Limits of Latte =============== -Latte is not a complete XSS protection for the entire application. We would be unhappy if you stopped thinking about security when using Latte. Latte's goal is to ensure that an attacker cannot alter the page structure, forge HTML elements or attributes. But it does not check the content correctness of the printed data. Nor the correctness of JavaScript behavior. That goes beyond the competence of the templating system. Verifying the correctness of data, especially data entered by the user and therefore untrusted, is an important task for the programmer. +Latte is not a complete XSS protection for the entire application. We would be unhappy if you stopped thinking about security when using Latte. Latte's goal is to ensure that an attacker cannot alter the page structure or forge HTML elements or attributes. But it does not check the content correctness of the printed data. Nor the correctness of JavaScript behavior. That goes beyond the competence of the templating system. Verifying the correctness of data, especially data entered by the user and therefore untrusted, is an important task for the programmer. + +.[tip] +Do you want to test your knowledge of escaping in individual contexts? Try the [XSS vulnerability quiz |https://blog.nette.org/en/quiz-can-you-defend-against-xss-vulnerability]. diff --git a/latte/en/sandbox.texy b/latte/en/sandbox.texy index 230afc9881..ef1e9f8a13 100644 --- a/latte/en/sandbox.texy +++ b/latte/en/sandbox.texy @@ -22,17 +22,27 @@ $policy->allowMethods(Nette\Security\User::class, ['isLoggedIn', 'isAllowed']); $policy->allowProperties(Nette\Database\Row::class, $policy::All); ``` +Permissions granted with `allowMethods()` and `allowProperties()` also apply to instances of subclasses of the given class (the check uses `is_a()`). + Isn't that amazing? You can control everything at a very low level. If the template attempts to call a disallowed function or access a disallowed method or property, it throws a `Latte\SecurityViolationException`. + +Secure Default Policy +===================== + Creating a policy from scratch, where everything is forbidden, might not be convenient, so you can start from a secure baseline: ```php $policy = Latte\Sandbox\SecurityPolicy::createSafePolicy(); ``` -This secure baseline means that all standard tags are allowed except for `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `widget`. All standard filters are allowed except for `datastream`, `noescape`, and `nocheck`. Finally, access to the methods and properties of the `$iterator` object is allowed. +This secure baseline means that all standard tags are allowed except for `contentType`, `debugbreak`, `dump`, `extends`, `import`, `include`, `layout`, `php`, `sandbox`, `snippet`, `snippetArea`, `templatePrint`, `varPrint`, `embed`. All standard filters are allowed except for `datastream`, `noescape`, and `nocheck`. Finally, access to the methods and properties of the `$iterator` object is allowed. + + +Activating the Sandbox +====================== -The rules apply to the template that we insert with the [`{sandbox}` |tags#Including Templates] tag. This is somewhat analogous to `{include}`, but it enables sandbox mode and also does not pass any external variables: +The rules apply to the template that we insert with the [`{sandbox}` |tags#Including Templates] tag. This is somewhat analogous to `{include}`: it enables sandbox mode and, like `{include}`, does not automatically pass the surrounding variables. You can, however, pass them explicitly, for example `{sandbox 'untrusted.latte', a: 1, b: 2}`: ```latte {sandbox 'untrusted.latte'} @@ -48,9 +58,36 @@ If we wanted to enable sandbox mode directly for all templates, it's easy: $latte->setSandboxMode(); ``` -To ensure that a user doesn't insert PHP code into the page that is syntactically correct but forbidden and causes a PHP Compile Error, we recommend having [templates checked by the PHP linter |develop#Checking Generated Code]. You can activate this functionality using the `Engine::enablePhpLint()` method. Since it needs to call the PHP binary for the check, pass its path as a parameter: + +Checking the Generated Code +=========================== + +To ensure that a user doesn't insert PHP code into the page that is syntactically correct but forbidden and causes a PHP Compile Error, we recommend having [templates checked by the PHP linter |develop#Checking Generated Code]. You can activate this functionality using the `Engine::enablePhpLinter()` method. Since it needs to call the PHP binary for the check, pass its path as a parameter: ```php $latte = new Latte\Engine; $latte->enablePhpLinter('/path/to/php'); ``` + + +What the Sandbox Doesn't Guard +============================== + +Beyond the tags, functions, methods, and properties you allow, the sandbox unconditionally forbids several constructs regardless of the policy: the `new` operator, the `$this` variable, variable variables (`$$var`), and the `|noescape` filter. + +The sandbox reliably guards *explicit* operations: calls to functions, methods, and filters, as well as access to object properties. There is one thing its checks cannot reach, however, and it is worth knowing about. + +When you print an object or use it in any string context, PHP automatically calls its magic method `__toString()`. The policy does not check this *implicit* conversion, so it runs even if the `__toString()` method is not among the allowed ones. The template author can thus trigger `__toString()` on any object they can reach in the template. This differs from the explicit form `{$obj->__toString()}`, which the sandbox blocks: + +```latte +{$obj} {* calls __toString() *} +{$obj . '!'} {* same (concatenation) *} +{="price: $obj"} {* same (string interpolation) *} +{$obj|upper} {* same (through a filter) *} +``` + +The purpose of `__toString()` is to produce a text representation of the object, so its reachability usually does no harm. A problem arises only when `__toString()` has side effects (such as writing to or querying a database) or when it returns sensitive data. + +Latte could catch these most direct cases, but not reliably in all of them. Converting an object to a string is not a method call but a built-in language operation that occurs in many places within an expression. In some of them (for example, comparing an object with a string, or inside a called function or filter) it could not be intercepted reliably without also blocking legitimate use. That is why you need to count on `__toString()` being reachable. + +**Do not expose objects whose `__toString()` has side effects or reveals sensitive data to the sandbox.** This applies not only to objects you pass to the template directly, but also to those the author obtains as the return value of an allowed function, method, or property. diff --git a/latte/en/syntax.texy b/latte/en/syntax.texy index ef926e75be..9334edd13c 100644 --- a/latte/en/syntax.texy +++ b/latte/en/syntax.texy @@ -21,9 +21,9 @@ Let's take a closer look at these important elements and how they can help you c Tags ==== -A template contains tags that control the template logic (for example, *foreach* loops) or output expressions. A single delimiter `{ ... }` is used for both, so you don't have to think about which delimiter to use in which situation, unlike other systems. If the `{` character is followed by a quote or space, Latte does not consider it the beginning of a tag, allowing you to use JavaScript constructs, JSON, or CSS rules in your templates without problems. +A template contains tags that control the template logic (for example, *foreach* loops) or output expressions. A single delimiter `{ ... }` is used for both, so you don't have to think about which delimiter to use in which situation, unlike other systems. If the `{` character is immediately followed by whitespace, a quote, or another `{` or `}`, Latte does not consider it the beginning of a tag, allowing you to use JavaScript constructs, JSON, or CSS rules in your templates without problems. -See the [overview of all tags|tags]. Additionally, you can create your own [custom tags|custom-tags]. +See the [overview of all tags|tags]. Additionally, you can create your own [custom tags|custom-tags]. You can also change the `{ }` delimiters or turn them off entirely (using `{syntax double}`, `{syntax off}`, or the `n:syntax` attribute); see [changing the syntax |tags#syntax]. Latte Understands PHP @@ -109,7 +109,7 @@ Which outputs, depending on the variable `$url`:

    Title

    ``` -However, n:attributes are not only a shortcut for pair tags, there are some pure n:attributes as well, for example the coder's best friend [n:class|tags#n:class] or the very handy [n:href |application:creating-links#In the Presenter Template]. +However, n:attributes are not only a shortcut for pair tags; there are some pure n:attributes as well, for example the coder's best friend [n:class|tags#n:class] or the very handy [n:href |application:creating-links#In the Presenter Template]. In addition to the syntax using quotes `
    `, you can use alternative syntax with curly braces `
    `. The main advantage is that you can freely use both single and double quotes inside `{...}`: @@ -118,8 +118,8 @@ In addition to the syntax using quotes `
    `, you can use alternat ``` -Smart HTML Attributes .{data-version:3.1} -========================================= +Smart HTML Attributes .{data-version:3.1.0} +=========================================== Latte makes working with standard HTML attributes incredibly easy. It handles boolean attributes like `checked` for you, removes attributes containing `null`, and allows you to compose `class` and `style` values using arrays. It even automatically serializes data for `data-` attributes into JSON. @@ -157,10 +157,11 @@ Filters can be chained, and they are applied in order from left to right:

    {$heading|lower|capitalize}

    ``` -Parameters are entered after the filter name, separated by colons or commas: +Arguments follow the filter name after a colon and any further ones are separated by commas; a parenthesized call works as well: ```latte

    {$heading|truncate:20,''}

    +

    {$heading|truncate(20, '')}

    ``` Filters can also be applied to an expression: @@ -178,7 +179,7 @@ On a block: Or directly on a value (in combination with the [`{=expr}` |tags#Printing] tag): ```latte -

    {=' Hello world '|trim}

    +

    {=' Hello world '|trim}

    ``` If the value can be `null` and you want to avoid applying the filter in that case, use the [nullsafe filter |filters#Nullsafe Filters] `?|`: @@ -218,6 +219,41 @@ 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 control tag appears alone on a line, the entire line (indentation and newline) is removed from the output (this does not apply to tags that print output, such as `{$var}`, `{=...}`, or `{_...}`, which keep their indentation and trailing newline): + +```latte +
      + {foreach $items as $item} +
    • {$item}
    • + {/foreach} +
    +``` + +Outputs: + +```latte +
      +
    • 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. + +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 =============== @@ -245,7 +281,7 @@ Use the global namespace separator to distinguish global constants from simple s {if \PROJECT_ID === 1} ... {/if} ``` -This notation is entirely valid in PHP itself, where the slash indicates that the constant is in the global namespace. +This notation is entirely valid in PHP itself, where the backslash indicates that the constant is in the global namespace. Short Ternary Operator diff --git a/latte/en/tags.texy b/latte/en/tags.texy index cfbc94ae6f..160e79dd19 100644 --- a/latte/en/tags.texy +++ b/latte/en/tags.texy @@ -106,6 +106,7 @@ An overview and description of all the tags available by default in the Latte te .[table-latte-tags language-latte] |## Available only with Nette Forms | `{form}` … `{/form}` | [renders form tags |forms:rendering#form] +| `{form scope}`, `{form detached}` | [form variants without the tag or detached |forms:rendering#form] | `{label}` … `{/label}` | [renders a form control label |forms:rendering#label input] | `{input}` | [renders a form control |forms:rendering#label input] | `{inputError}` | [prints the error message of a form control |forms:rendering#inputError] @@ -137,7 +138,7 @@ You can write anything you know from PHP as an expression. You simply don't have ```latte -{='0' . ($num ?? $num * 3) . ', ' . PHP_VERSION} +{='0' . ($num ?? $num * 3) . ', ' . \PHP_VERSION} ``` Please don't look for any meaning in the previous example, but if you find one, let us know :-) @@ -252,10 +253,10 @@ Did you know you can add the `tag-` prefix to n:attributes? Then the condition w Awesome. -`n:else` `n:elseif` .{toc: n:else}{data-version:3.0.11} +`n:else` `n:elseif` .{toc: n:else}{data-version:3.0.12} ------------------------------------------------------- -If you write the `{if} ... {/if}` condition in the form of an [n:attribute |syntax#n:attributes], you have the option to specify alternative branches using `n:else` and `n:elseif` (since Latte 3.1): +If you write the `{if} ... {/if}` condition in the form of an [n:attribute |syntax#n:attributes], you have the option to specify alternative branches using `n:else` (since 3.0.12) and `n:elseif` (since 3.1): ```latte In stock {$count} items @@ -265,7 +266,7 @@ If you write the `{if} ... {/if}` condition in the form of an [n:attribute |synt not available ``` -The `n:else` attribute can also be used in conjunction with [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [`n:ifcontent`|#nifcontent], and [`n:ifchanged` |#ifchanged]. +The `n:else` attribute can also be used in conjunction with [`n:ifset` |#ifset elseifset], [`n:foreach` |#foreach], [`n:try` |#try], [`n:ifcontent` |#n-ifcontent], and [`n:ifchanged` |#ifchanged]. `{/if $cond}` @@ -340,7 +341,7 @@ Compares a value with multiple options. This is similar to the `switch` statemen - uses strict comparison (`===`) - does not require `break` -It is thus the exact equivalent of the `match` structure introduced in PHP 8.0. +It thus closely mirrors the `match` structure introduced in PHP 8.0. ```latte {switch $transport} @@ -632,7 +633,7 @@ The template name can be any PHP expression: {include $ajax ? 'ajax.latte' : 'not-ajax.latte'} ``` -If the template exists can be checked by the function [`hasTemplate()` |functions#hasTemplate]. +Whether a template exists can be checked using the [`hasTemplate()` |functions#hasTemplate] function. The included content can be modified using [filters |syntax#Filters]. The following example removes all HTML and adjusts the case: @@ -665,10 +666,10 @@ When including a template created by an end user, you should consider sandboxing .[note] See also [`{block name}` |template-inheritance#Blocks] -Blocks without a name provide the ability to apply [filters |syntax#Filters] to a part of the template. For example, you can apply a [strip |filters#spaceless] filter to remove unnecessary spaces: +Blocks without a name provide the ability to apply [filters |syntax#Filters] to a part of the template. For example, you can apply a [spaceless |filters#spaceless] filter to remove unnecessary spaces: ```latte -{block|strip} +{block|spaceless}
    • Hello World
    @@ -890,7 +891,7 @@ Requires the [Tracy|tracy:] library. `{php}` ------- -Allows executing any PHP code. The tag must be activated using the [RawPhpExtension |develop#RawPhpExtension] extension. +By default, `{php}` works as an obsolete alias for [`{do}` |#do] and evaluates only a single expression. To execute any PHP code, the tag must be activated using the [RawPhpExtension |develop#RawPhpExtension] extension. `{spaceless}` @@ -950,7 +951,7 @@ HTML Coder Helpers --------- .[note] -Since Latte 3.1, the standard HTML class attribute has gained the [same functionality |html-attributes#classes]. So you don't need to use n:class anymore +Since Latte 3.1, the standard HTML class attribute has gained the [same functionality |html-attributes#classes]. So you don't need to use n:class anymore. Thanks to `n:class`, it's very easy to generate the HTML `class` attribute exactly as needed. diff --git a/latte/en/template-inheritance.texy b/latte/en/template-inheritance.texy index 81ce857bb5..7f7af3c27b 100644 --- a/latte/en/template-inheritance.texy +++ b/latte/en/template-inheritance.texy @@ -68,7 +68,7 @@ The output might look like this: ``` -In a child template, blocks can only be located at the top level or inside another block, i.e.: +In a child template, blocks are usually placed at the top level or inside another block, for example: ```latte {block content} @@ -96,7 +96,7 @@ If you want the output inside the block to be displayed conditionally, use the f {/block} ``` -Code outside blocks in the child template is executed before rendering the layout template, so you can use it to define variables like `{var $foo = bar}` and propagate data throughout the inheritance chain: +Code in the header of the child template (that is, before the first block or any output) is executed before the layout template is rendered, so you can use it to define variables like `{var $foo = bar}` and propagate data throughout the inheritance chain. Code placed between or after blocks in a template with `{layout}` is not executed at all: ```latte {layout 'layout.latte'} @@ -105,6 +105,14 @@ Code outside blocks in the child template is executed before rendering the layou ... ``` +If you want to pass variables only to the layout, without creating them in the current template, list them directly in the `{layout}` (or `{extends}`) tag after a comma: .{data-version:3.1.5} + +```latte +{layout 'layout.latte', robots: noindex} +``` + +The variable `$robots` will be available in the layout and its blocks, but not in the blocks of the current template. An explicitly passed variable also takes precedence over a template parameter of the same name. + Multilevel Inheritance ---------------------- @@ -130,9 +138,9 @@ Tips ---- Here are some tips for working with layout inheritance: -- If you use `{layout}` in a template, it must be the first template tag in that template. +- If you use `{layout}` in a template, it must be in the template header, that is, before any output. Only tags that produce no output (like `{var}`, `{templateType}`, `{import}`, or comments) may precede it. -- The layout can be [found automatically |develop#Automatic Layout Lookup] (like in [presenters |application:templates#Template Lookup]). In this case, if the template should not have a layout, it indicates this with the `{layout none}` tag. +- The layout can be [found automatically |develop#Automatic Layout Lookup] (like in [presenters |application:templates#Template Lookup]). In this case, if the template should not have a layout, it indicates this with the `{layout none}` tag. Conversely, `{layout auto}` (or `{extends auto}`) restores automatic layout lookup. - The `{layout}` tag has an alias `{extends}`. @@ -161,7 +169,7 @@ A block provides a way to change how a certain part of a template is rendered, b If you render this template, the result will be exactly the same with or without the `{block}` tags. Blocks have access to variables from outer scopes. They just provide a way to be overridden by a child template: ```latte .{file: child.latte} -{layout 'parent.Latte'} +{layout 'parent.latte'} {block post}
    @@ -171,7 +179,7 @@ If you render this template, the result will be exactly the same with or without {/block} ``` -Now, when rendering the child template, the loop will use the block defined in the child template `child.Latte` instead of the one defined in `parent.Latte`; the executed template is then equivalent to the following: +Now, when rendering the child template, the loop will use the block defined in the child template `child.latte` instead of the one defined in `parent.latte`; the executed template is then equivalent to the following: ```latte {foreach $posts as $post} @@ -341,7 +349,7 @@ The template with the definitions is loaded using [`{import}` |#Horizontal Reuse

    {include textarea, 'comment'}

    ``` -Definitions do not have access to the variables of the active context, but they do have access to global variables. +Like blocks, definitions do not have access to the variables of the active context, only to global variables. The exception is a definition that has no declared parameters, is referenced by a static name, and is included in the same file where it is defined: such a definition does have access to the context variables of the place from which it is included. Dynamic Block Names @@ -455,6 +463,8 @@ The `{import}` tag should be the first template tag after `{layout}`. The templa You can use as many `{import}` statements as you want in a template. If two imported templates define the same block, the first one wins. However, the main template has the highest priority and can override any imported block. +The `{import}` tag can also pass arguments to the imported template, for example `{import 'blocks.latte', foo: 1}`. These arguments are then available as variables in the imported blocks and definitions. + The content of overwritten blocks can be preserved by inserting the block in the same way as a [#parent-block]: ```latte @@ -526,7 +536,7 @@ The output might look like this: ``` -Blocks inside embed tags form a separate layer independent of other blocks. Therefore, they can have the same name as a block outside the embed and are not affected in any way. Using the [include |#Printing Blocks] tag inside `{embed}` tags, you can insert blocks created here, blocks from the embedded template (which *are not* [local |#Local Blocks]), and also blocks from the main template that *are* local. You can also [import blocks |#Horizontal Reuse] from other files: +Blocks inside embed tags form a separate layer, isolated from the blocks outside the embed. Therefore, they can have the same name as a block outside without colliding, and are not affected by it. Using the [include |#Printing Blocks] tag inside `{embed}` tags, you can insert blocks created here, blocks from the embedded template (which *are not* [local |#Local Blocks]), and also blocks from the main template that *are* local. You can also [import blocks |#Horizontal Reuse] from other files: ```latte {block outer}…{/block} @@ -570,6 +580,8 @@ Using `{embed}`, you can embed not only templates but also other blocks, so the {/embed} ``` +There is, however, one difference between the two: when you embed a block instead of a file, the blocks from the outer layer remain accessible inside the embed. So unlike with an embedded file, `{include outer}` would work there. + If we pass an expression to `{embed}` and it's not clear whether it's a block name or a file name, add the keyword `block` or `file`: ```latte diff --git a/latte/en/type-system.texy b/latte/en/type-system.texy index eac2758138..eee9ea0b51 100644 --- a/latte/en/type-system.texy +++ b/latte/en/type-system.texy @@ -35,7 +35,7 @@ $latte->render('template.latte', new CatalogTemplateParameters( )); ``` -Then insert the `{templateType}` tag with the full class name (including the namespace) at the beginning of the template. This defines that the variables `$lang` and `$products` exist in the template, including their respective types. You can also specify the types of local variables using the [`{var}` |tags#var-default], `{varType}`, and [`{define}` |template-inheritance#Definitions] tags. +Then insert the `{templateType}` tag with the full class name (including the namespace) at the beginning of the template. This defines that the variables `$lang`, `$products`, and `$address` exist in the template, including their respective types. You can also specify the types of local variables using the [`{var}` |tags#var-default], `{varType}`, and [`{define}` |template-inheritance#Definitions] tags. From this point on, your IDE can correctly provide autocompletion. @@ -63,7 +63,7 @@ How to declare variable types? Use the `{varType}` tag for existing variables, o `{templatePrint}` ----------------- -You can also have the class generated using the `{templatePrint}` tag. If you place it at the beginning of the template, instead of the normal rendering, a suggestion for the class code will be displayed. Then simply select the code with one click and copy it into your project. +You can also have the class generated using the `{templatePrint}` tag. If you place it at the beginning of the template, instead of the normal rendering, a suggestion for the class code will be displayed. Then simply select the code with one click and copy it into your project. You can optionally specify a parent class name, e.g. `{templatePrint ParentClass}`, and the generated class will extend it. `{varPrint}` diff --git a/latte/es/@home.texy b/latte/es/@home.texy index b4be54fe88..7c261c81cc 100644 --- a/latte/es/@home.texy +++ b/latte/es/@home.texy @@ -1,2 +1,3 @@ {{maintitle: Latte – las plantillas más seguras y realmente intuitivas para PHP}} {{description: Latte es el sistema de plantillas más seguro para PHP. Previene muchas vulnerabilidades de seguridad. Apreciará su sintaxis intuitiva y muchas características útiles.}} +{{leftbar: no}} diff --git a/latte/es/@left-menu.texy b/latte/es/@left-menu.texy index ad96b47fc7..a3bed73610 100644 --- a/latte/es/@left-menu.texy +++ b/latte/es/@left-menu.texy @@ -1,24 +1,30 @@ -- [Empezando con Latte |guide] +- [Primeros pasos |Guide] - [¿Por qué usar plantillas? |why-use] - Conceptos ⚗️ - - [La seguridad es lo primero |safety-first] - - [Herencia de plantillas |Template Inheritance] - - [Sistema de tipos |type-system] - - [Sandbox |Sandbox] + - [La seguridad primero|safety-first] + - [Herencia de plantillas|template-inheritance] + - [Sistema de tipos|type-system] + - [Sandbox|sandbox] + - [Atributos HTML|html-attributes] - Para diseñadores 🎨 - - [Sintaxis |syntax] - - [Etiquetas |tags] - - [Filtros |filters] - - [Funciones |functions] - - [Consejos y trucos |recipes] + - [Sintaxis|syntax] + - [Etiquetas|tags] + - [Filtros|filters] + - [Funciones|functions] + - [Trucos y consejos |recipes] - Para desarrolladores 🧮 - - [Prácticas de desarrollo |develop] - - [Extendiendo Latte |extending-latte] + - [Prácticas para desarrolladores |develop] + - [Extender Latte|extending-latte] + - [Filtros personalizados |custom-filters] + - [Funciones personalizadas |custom-functions] + - [Etiquetas personalizadas |custom-tags] + - [Pases del compilador |compiler-passes] + - [Loaders |loaders] -- [Tutoriales y procedimientos 💡|cookbook/@home] +- [Recetas 💡|cookbook/@home] - [Migración desde Twig |cookbook/migration-from-twig] - - [… otros |cookbook/@home] + - [… más |cookbook/@home] - "Playground .[link-external]":https://fiddle.nette.org/latte/ .{padding-top:1em} diff --git a/latte/es/@menu.texy b/latte/es/@menu.texy index 588b76348f..a284f440d1 100644 --- a/latte/es/@menu.texy +++ b/latte/es/@menu.texy @@ -1,12 +1,12 @@
      -- [Introducción |@home] -- [Documentación |guide] +- [Inicio |@home] +- [Documentación |Guide] - "GitHub .[link-external]":https://github.com/nette/latte
    diff --git a/latte/es/@meta.texy b/latte/es/@meta.texy index ff4ce063ad..c4bc1e0492 100644 --- a/latte/es/@meta.texy +++ b/latte/es/@meta.texy @@ -1 +1 @@ -{{sitename: Latte Documentación}} +{{sitename: Documentación de Latte}} diff --git a/latte/es/compiler-passes.texy b/latte/es/compiler-passes.texy index 3fbb00ba08..8e2725299f 100644 --- a/latte/es/compiler-passes.texy +++ b/latte/es/compiler-passes.texy @@ -1,27 +1,27 @@ -Pases de compilación -******************** +Creación de pases del compilador +******************************** .[perex] -Los pases de compilación proporcionan un mecanismo poderoso para analizar y modificar las plantillas Latte *después* de que se analizan en un árbol de sintaxis abstracto (AST) y *antes* de que se genere el código PHP final. Esto permite la manipulación avanzada de plantillas, optimizaciones, controles de seguridad (como Sandbox) y la recopilación de información sobre las plantillas. Esta guía le guiará a través de la creación de sus propios pases de compilación. +Los pases del compilador ofrecen un mecanismo potente para analizar y modificar las plantillas de Latte *después* de que se hayan analizado en un árbol de sintaxis abstracta (AST) y *antes* de que se genere el código PHP final. Esto permite manipulaciones avanzadas de las plantillas, optimizaciones, comprobaciones de seguridad (como el Sandbox) y la recopilación de información sobre las plantillas. Esta guía le acompañará en la creación de sus propios pases del compilador. -¿Qué es un pase de compilación? +¿Qué es un pase del compilador? =============================== -Para comprender el rol de los pases de compilación, consulte el [proceso de compilación de Latte |custom-tags#Entendiendo el proceso de compilación]. Como puede ver, los pases de compilación operan en una fase crucial, permitiendo una intervención profunda entre el análisis inicial y la salida final del código. +Para entender el papel de los pases del compilador, vea el [proceso de compilación de Latte |custom-tags#Entender el proceso de compilación]. Como se aprecia, los pases del compilador actúan en una fase crucial y permiten intervenir a fondo entre el análisis inicial y la salida final del código. -En esencia, un pase de compilación es simplemente un objeto PHP invocable (como una función, método estático o método de instancia) que acepta un solo argumento: el nodo raíz del AST de la plantilla, que siempre es una instancia de `Latte\Compiler\Nodes\TemplateNode`. +En esencia, un pase del compilador no es más que un callable de PHP (una función, un método estático o un método de instancia) que acepta un argumento: el nodo raíz del AST de la plantilla, que siempre es una instancia de `Latte\Compiler\Nodes\TemplateNode`. -El objetivo principal de un pase de compilación suele ser uno o ambos de los siguientes: +El objetivo principal de un pase del compilador suele ser uno de estos dos, o ambos: -- Análisis: Recorrer el AST y recopilar información sobre la plantilla (p. ej., encontrar todos los bloques definidos, verificar el uso de etiquetas específicas, asegurar que se cumplan ciertas restricciones de seguridad). -- Modificación: Cambiar la estructura del AST o los atributos de los nodos (p. ej., agregar automáticamente atributos HTML, optimizar ciertas combinaciones de etiquetas, reemplazar etiquetas obsoletas por nuevas, implementar reglas de sandbox). +- Análisis: recorrer el AST y reunir información sobre la plantilla (por ejemplo, encontrar todos los bloques definidos, comprobar el uso de determinadas etiquetas o asegurar que se cumplen ciertas restricciones de seguridad). +- Modificación: cambiar la estructura del AST o las propiedades de los nodos (por ejemplo, añadir atributos HTML automáticamente, optimizar ciertas combinaciones de etiquetas, sustituir etiquetas obsoletas por nuevas o aplicar reglas de sandbox). Registro ======== -Los pases de compilación se registran usando el método de [extensión |extending-latte#getPasses] `getPasses()`. Este método devuelve un array asociativo donde las claves son nombres únicos de los pases (usados internamente y para ordenar) y los valores son objetos PHP invocables que implementan la lógica del pase. +Los pases del compilador se registran mediante el método [`getPasses()` |extending-latte#getPasses()] de una extensión. Este método devuelve un array asociativo donde las claves son nombres únicos de los pases (usados internamente y para el orden) y los valores son los callables de PHP que implementan la lógica del pase. ```php use Latte\Compiler\Nodes\TemplateNode; @@ -33,7 +33,7 @@ class MyExtension extends Extension { return [ 'modificationPass' => $this->modifyTemplateAst(...), - // ... otros pases ... + // ... otros passes ... ]; } @@ -44,13 +44,13 @@ class MyExtension extends Extension } ``` -Los pases registrados por las extensiones base de Latte y sus propias extensiones se ejecutan secuencialmente. El orden puede ser importante, especialmente si un pase depende de los resultados o modificaciones de otro. Latte proporciona un mecanismo auxiliar para controlar este orden si es necesario; consulte la documentación de [`Extension::getPasses()` |extending-latte#getPasses] para obtener detalles. +Los pases registrados por las extensiones del núcleo de Latte y por sus extensiones propias se ejecutan de forma secuencial. El orden puede ser importante, sobre todo si un pase depende de los resultados o las modificaciones de otro. Latte ofrece un mecanismo auxiliar para controlar ese orden si hace falta; vea los detalles en la documentación de [`Extension::getPasses()` |extending-latte#getPasses()]. Ejemplo de AST ============== -Para tener una mejor idea del AST, agregamos un ejemplo. Esta es la plantilla de origen: +Para hacerse una idea mejor del AST, añadimos una muestra. Esta es la plantilla de origen: ```latte {foreach $category->getItems() as $item} @@ -74,7 +74,7 @@ Latte\Compiler\Nodes\TemplateNode( content: Latte\Compiler\Nodes\FragmentNode( - Latte\Compiler\Nodes\TextNode(' ') - Latte\Compiler\Nodes\Html\ElementNode('li')( - content: Latte\Essential\Nodes\PrintNode( + content: Latte\Compiler\Nodes\PrintNode( expression: Latte\Compiler\Nodes\Php\Expression\PropertyFetchNode( object: Latte\Compiler\Nodes\Php\Expression\VariableNode('$item') name: Latte\Compiler\Nodes\Php\IdentifierNode('name') @@ -95,12 +95,12 @@ Latte\Compiler\Nodes\TemplateNode( \-- -Recorriendo el AST con `NodeTraverser` -====================================== +Recorrer el AST con `NodeTraverser` +=================================== -Escribir manualmente funciones recursivas para recorrer la compleja estructura del AST es tedioso y propenso a errores. Latte proporciona una herramienta especial para este propósito: [api:Latte\Compiler\NodeTraverser]. Esta clase implementa el [patrón de diseño Visitor |https://en.wikipedia.org/wiki/Visitor_pattern], que hace que el recorrido del AST sea sistemático y fácil de manejar. +Escribir a mano funciones recursivas para recorrer la compleja estructura del AST es tedioso y propenso a errores. Latte ofrece una herramienta específica para ello: [api:Latte\Compiler\NodeTraverser]. Esta clase implementa el [patrón de diseño Visitor |https://en.wikipedia.org/wiki/Visitor_pattern], lo que hace el recorrido del AST sistemático y manejable. -El uso básico implica crear una instancia de `NodeTraverser` y llamar a su método `traverse()`, pasando el nodo raíz del AST y uno o dos objetos invocables "visitor": +El uso básico consiste en crear una instancia de `NodeTraverser` y llamar a su método `traverse()`, pasándole el nodo raíz del AST y uno o dos callables "visitantes": ```php use Latte\Compiler\Node; @@ -110,42 +110,46 @@ use Latte\Compiler\Nodes; (new NodeTraverser)->traverse( $templateNode, - // visitor 'enter': Se llama al entrar en el nodo (antes de sus hijos) + // visitante 'enter': se llama al entrar en un nodo (antes que sus hijos) enter: function (Node $node) { - echo "Entrando en el nodo de tipo: " . $node::class . "\n"; - // Aquí puede examinar el nodo + echo "Entering node of type: " . $node::class . "\n"; + // Aquí puede inspeccionar el nodo if ($node instanceof Nodes\TextNode) { - // echo "Texto encontrado: " . $node->content . "\n"; + // echo "Found text: " . $node->content . "\n"; } }, - // visitor 'leave': Se llama al salir del nodo (después de sus hijos) + // visitante 'leave': se llama al salir de un nodo (después de sus hijos) leave: function (Node $node) { - echo "Saliendo del nodo de tipo: " . $node::class . "\n"; + echo "Leaving node of type: " . $node::class . "\n"; // Aquí puede realizar acciones después de procesar los hijos }, ); ``` -Puede proporcionar solo el visitor `enter`, solo el visitor `leave`, o ambos, según sus necesidades. +Puede proporcionar solo el visitante `enter`, solo el `leave` o ambos, según sus necesidades. -**`enter(Node $node)`:** Esta función se ejecuta para cada nodo **antes** de que el traverser visite cualquiera de los hijos de ese nodo. Es útil para: +**`enter(Node $node)`:** esta función se ejecuta para cada nodo **antes** de que el recorredor visite ninguno de sus hijos. Resulta útil para: -- Recopilar información al recorrer el árbol hacia abajo. -- Tomar decisiones *antes* de procesar los hijos (como decidir omitirlos, consulte [#Optimización del recorrido]). -- Potencialmente modificar el nodo antes de visitar a los hijos (menos común). +- Reunir información mientras se desciende por el árbol. +- Tomar decisiones *antes* de procesar los hijos (por ejemplo, decidir saltárselos, vea [#Optimizar el recorrido]). +- Modificar eventualmente el nodo antes de visitar los hijos (menos habitual). -**`leave(Node $node)`:** Esta función se ejecuta para cada nodo **después** de que todos sus hijos (y sus subárboles completos) hayan sido visitados por completo (tanto entrada como salida). Es el lugar más común para: +**`leave(Node $node)`:** esta función se ejecuta para cada nodo **después** de que todos sus hijos (y sus subárboles completos) se hayan visitado por entero (tanto entrada como salida). Es el lugar más habitual para: -Ambos visitors `enter` y `leave` pueden opcionalmente devolver un valor para influir en el proceso de recorrido. Devolver `null` (o nada) continúa el recorrido normalmente, devolver una instancia de `Node` reemplaza el nodo actual, y devolver constantes especiales como `NodeTraverser::RemoveNode` o `NodeTraverser::StopTraversal` modifica el flujo, como se explica en las siguientes secciones. +- Sustituir un nodo después de procesar sus hijos. +- Eliminar nodos del AST. +- Agregar la información reunida en todo el subárbol. + +Tanto el visitante `enter` como el `leave` pueden devolver opcionalmente un valor para influir en el recorrido. Devolver `null` (o nada) continúa el recorrido con normalidad, devolver una instancia de `Node` sustituye el nodo actual, y devolver constantes especiales como `NodeTraverser::RemoveNode` o `NodeTraverser::StopTraversal` modifica el flujo, como se explica en las secciones siguientes. Cómo funciona el recorrido -------------------------- -`NodeTraverser` utiliza internamente el método `getIterator()`, que cada clase `Node` debe implementar (como se discutió en [Creación de etiquetas personalizadas |custom-tags#Implementación de getIterator para subnodos]). Itera sobre los hijos obtenidos mediante `getIterator()`, llama recursivamente a `traverse()` en ellos y asegura que los visitors `enter` y `leave` se llamen en el orden correcto de profundidad primero para cada nodo en el árbol accesible a través de los iteradores. Esto vuelve a enfatizar por qué un `getIterator()` correctamente implementado en sus propios nodos de etiqueta es absolutamente esencial para el correcto funcionamiento de los pases de compilación. +Internamente, `NodeTraverser` usa el método `getIterator()` que toda clase `Node` debe implementar (como se comenta en [Creación de etiquetas personalizadas |custom-tags#Implementar getIterator() para los subnodos]). Itera sobre los hijos que devuelve `getIterator()` y llama recursivamente a `traverse()` sobre ellos, garantizando que los visitantes `enter` y `leave` se llamen en el orden correcto en profundidad para cada nodo del árbol accesible mediante iteradores. Esto vuelve a poner de relieve por qué un `getIterator()` bien implementado en los nodos de sus etiquetas propias es absolutamente esencial para que los pases del compilador funcionen correctamente. -Escribamos un recorrido simple que cuente cuántas veces se usa la etiqueta `{do}` (representada por `Latte\Essential\Nodes\DoNode`) en la plantilla. +Escribamos un pase sencillo que cuente cuántas veces se usa la etiqueta `{do}` (representada por `Latte\Essential\Nodes\DoNode`) en la plantilla. ```php use Latte\Compiler\Node; @@ -163,10 +167,10 @@ function countDoTags(TemplateNode $templateNode): void $count++; } }, - // El visitor 'leave' no es necesario para esta tarea + // el visitante 'leave' no hace falta para esta tarea ); - echo "Etiqueta {do} encontrada $count veces.\n"; + echo "Found {do} tag $count times.\n"; } $latte = new Latte\Engine; @@ -174,25 +178,25 @@ $ast = $latte->parse($templateSource); countDoTags($ast); ``` -En este ejemplo, solo necesitamos el visitor `enter` para verificar el tipo de cada nodo visitado. +En este ejemplo solo necesitábamos el visitante `enter` para comprobar el tipo de cada nodo encontrado. -A continuación, exploraremos cómo estos visitors realmente modifican el AST. +A continuación veremos cómo usar estos visitantes para modificar realmente el AST. -Modificación del AST -==================== +Modificar el AST +================ -Uno de los propósitos principales de los pases de compilación es modificar el árbol de sintaxis abstracto. Esto permite transformaciones potentes, optimizaciones o la aplicación de reglas directamente en la estructura de la plantilla antes de generar el código PHP. `NodeTraverser` proporciona varias formas de lograr esto dentro de los visitors `enter` y `leave`. +Uno de los principales cometidos de los pases del compilador es modificar el árbol de sintaxis abstracta. Esto permite transformaciones potentes, optimizaciones o la aplicación de reglas directamente sobre la estructura de la plantilla antes de generar el código PHP. `NodeTraverser` ofrece varias formas de lograrlo dentro de los visitantes `enter` y `leave`. -**Nota importante:** La modificación del AST requiere precaución. Cambios incorrectos, como eliminar nodos esenciales o reemplazar un nodo con un tipo incompatible, pueden provocar errores durante la generación de código o causar un comportamiento inesperado durante la ejecución del programa. Siempre pruebe a fondo sus pases de modificación. +**Nota importante:** modificar el AST exige cuidado. Los cambios incorrectos, como eliminar nodos esenciales o sustituir un nodo por otro de tipo incompatible, pueden provocar errores durante la generación del código o un comportamiento inesperado en ejecución. Pruebe siempre a fondo sus pases de modificación. -Modificación de propiedades de nodos ------------------------------------- +Cambiar propiedades de los nodos +-------------------------------- -La forma más sencilla de modificar el árbol es cambiar directamente las **propiedades públicas** de los nodos visitados durante el recorrido. Todos los nodos almacenan sus argumentos analizados, contenido o atributos en propiedades públicas. +La forma más sencilla de modificar el árbol es cambiar directamente las **propiedades públicas** de los nodos encontrados durante el recorrido. Todos los nodos guardan sus argumentos analizados, su contenido o sus atributos en propiedades públicas. -**Ejemplo:** Creemos un pase que encuentre todos los nodos de texto estático (`TextNode`, que representan HTML normal o texto fuera de las etiquetas Latte) y convierta su contenido a mayúsculas *directamente en el AST*. +**Ejemplo:** creemos un pase que encuentre todos los nodos de texto estático (`TextNode`, que representan HTML o texto plano fuera de las etiquetas de Latte) y convierta su contenido a mayúsculas *directamente en el AST*. ```php use Latte\Compiler\Node; @@ -204,30 +208,30 @@ function uppercaseStaticText(TemplateNode $templateNode): void { (new NodeTraverser)->traverse( $templateNode, - // Podemos usar 'enter', ya que TextNode no tiene hijos que procesar + // Podemos usar 'enter' porque TextNode no tiene hijos que procesar primero enter: function (Node $node) { // ¿Es este nodo un bloque de texto estático? if ($node instanceof TextNode) { // ¡Sí! Modificamos directamente su propiedad pública 'content'. $node->content = mb_strtoupper(html_entity_decode($node->content)); } - // No es necesario devolver nada; el cambio se aplica directamente. + // No hace falta devolver nada; la modificación ocurre in situ. }, ); } ``` -En este ejemplo, el visitor `enter` comprueba si el `$node` actual es de tipo `TextNode`. Si es así, actualizamos directamente su propiedad pública `$content` usando `mb_strtoupper()`. Esto cambia directamente el contenido del texto estático almacenado en el AST *antes* de generar el código PHP. Como estamos modificando el objeto directamente, no necesitamos devolver nada desde el visitor. +En este ejemplo, el visitante `enter` comprueba si el `$node` actual es un `TextNode`. Si lo es, actualizamos directamente su propiedad pública `$content` con `mb_strtoupper()`. Esto cambia directamente el contenido del texto estático guardado en el AST *antes* de generar el código PHP. Como modificamos el objeto directamente, no necesitamos devolver nada desde el visitante. -Efecto: Si la plantilla contenía `

    Hola

    {= $var }Mundo`, después de este pase, el AST representará algo como: `

    HOLA

    {= $var }MUNDO`. Esto NO AFECTA el contenido de `$var`. +Efecto: si la plantilla contenía `

    Hello

    {= $var }World`, tras este pase el AST representará algo como: `

    HELLO

    {= $var }WORLD`. Esto NO afecta al contenido de `$var`. -Reemplazo de nodos ------------------- +Sustituir nodos +--------------- -Una técnica de modificación más potente es reemplazar completamente un nodo por otro. Esto se hace **devolviendo una nueva instancia de `Node`** desde el visitor `enter` o `leave`. `NodeTraverser` luego reemplaza el nodo original por el devuelto en la estructura del nodo padre. +Una técnica de modificación más potente es sustituir por completo un nodo por otro distinto. Esto se hace **devolviendo la nueva instancia de `Node`** desde el visitante `enter` o `leave`. El `NodeTraverser` sustituirá entonces el nodo original por el devuelto dentro de la estructura del nodo padre. -**Ejemplo:** Creemos un pase que encuentre todos los usos de la constante `PHP_VERSION` (representada por `ConstantFetchNode`) y los reemplace directamente con un literal de cadena (`StringNode`) que contenga la versión *real* de PHP detectada *durante la compilación*. Esta es una forma de optimización en tiempo de compilación. +**Ejemplo:** creemos un pase que encuentre todos los usos de la constante `PHP_VERSION` (representada por `ConstantFetchNode`) y los sustituya directamente por un literal de cadena (`StringNode`) que contenga la versión *real* de PHP detectada *durante la compilación*. Es una forma de optimización en tiempo de compilación. ```php use Latte\Compiler\Node; @@ -240,19 +244,19 @@ function inlinePhpVersion(TemplateNode $templateNode): void { (new NodeTraverser)->traverse( $templateNode, - // 'leave' se usa a menudo para reemplazar, asegurando que los hijos (si existen) - // se procesen primero, aunque 'enter' también funcionaría aquí. + // 'leave' se usa a menudo para los reemplazos, para asegurar que los hijos (si los hay) + // se procesen primero, aunque aquí también funcionaría 'enter'. leave: function (Node $node) { - // ¿Es este nodo un acceso a constante y el nombre de la constante es 'PHP_VERSION'? + // ¿Es este nodo un acceso a constante y la constante se llama 'PHP_VERSION'? if ($node instanceof ConstantFetchNode && (string) $node->name === 'PHP_VERSION') { - // Creamos un nuevo StringNode que contiene la versión actual de PHP + // Crea un nuevo StringNode con la versión actual de PHP $newNode = new StringNode(PHP_VERSION); - // Opcional, pero buena práctica: copiar la información de posición + // Opcional pero recomendable: copia la información de posición $newNode->position = $node->position; - // Devolvemos el nuevo StringNode. Traverser reemplazará - // el ConstantFetchNode original con este $newNode. + // Devuelve el nuevo StringNode. El traverser sustituirá + // el ConstantFetchNode original por este $newNode. return $newNode; } // Si no devolvemos un Node, se conserva el $node original. @@ -261,37 +265,37 @@ function inlinePhpVersion(TemplateNode $templateNode): void } ``` -Aquí, el visitor `leave` identifica el `ConstantFetchNode` específico para `PHP_VERSION`. Luego, crea un `StringNode` completamente nuevo que contiene el valor de la constante `PHP_VERSION` *en tiempo de compilación*. Al devolver este `$newNode`, le dice al traverser que reemplace el `ConstantFetchNode` original en el AST. +Aquí, el visitante `leave` identifica el `ConstantFetchNode` concreto de `PHP_VERSION`. Después crea un `StringNode` completamente nuevo que contiene el valor de la constante `PHP_VERSION` *en tiempo de compilación*. Al devolver este `$newNode`, le indica al recorredor que sustituya en el AST el `ConstantFetchNode` original. -Efecto: Si la plantilla contenía `{= PHP_VERSION }` y la compilación se ejecuta en PHP 8.2.1, el AST después de este pase representará efectivamente `{= '8.2.1' }`. +Efecto: si la plantilla contenía `{= PHP_VERSION }` y la compilación se ejecuta en PHP 8.2.1, el AST tras este pase representará en la práctica `{= '8.2.1' }`. -**Elección entre `enter` vs. `leave` para el reemplazo:** +**Elegir `enter` o `leave` para la sustitución:** -- Use `leave` si la creación del nuevo nodo depende de los resultados del procesamiento de los hijos del nodo antiguo, o si simplemente desea asegurarse de que los hijos se visiten antes del reemplazo (práctica común). -- Use `enter` si desea reemplazar el nodo *antes* de que se visiten sus hijos. +- Use `leave` si la creación del nuevo nodo depende del resultado de procesar los hijos del nodo antiguo, o si simplemente quiere asegurarse de que los hijos se visitan antes de la sustitución (práctica habitual). +- Use `enter` si quiere sustituir un nodo *antes* incluso de visitar sus hijos. -Eliminación de nodos --------------------- +Eliminar nodos +-------------- -Puede eliminar completamente un nodo del AST devolviendo la constante especial `NodeTraverser::RemoveNode` desde el visitor. +Puede eliminar por completo un nodo del AST devolviendo desde un visitante la constante especial `NodeTraverser::RemoveNode`. -**Ejemplo:** Eliminemos todos los comentarios de la plantilla (`{* ... *}`), que están representados por `CommentNode` en el AST generado por el núcleo de Latte (aunque típicamente se procesan antes, esto sirve como ejemplo). +**Ejemplo:** eliminemos de la salida todos los comentarios HTML (``). Los comentarios de Latte `{* ... *}` no se pueden atacar así, porque el analizador descarta su contenido y los sustituye por un `NopNode` vacío en lugar de un nodo de comentario específico, pero los comentarios HTML se conservan como nodos `Html\CommentNode`, así que aquí sí podemos quitarlos. ```php use Latte\Compiler\Node; use Latte\Compiler\NodeTraverser; use Latte\Compiler\Nodes\TemplateNode; -use Latte\Compiler\Nodes\CommentNode; +use Latte\Compiler\Nodes\Html\CommentNode; -function removeCommentNodes(TemplateNode $templateNode): void +function removeHtmlComments(TemplateNode $templateNode): void { (new NodeTraverser)->traverse( $templateNode, - // 'enter' está bien aquí, ya que no necesitamos información sobre los hijos para eliminar el comentario + // aquí vale 'enter', porque no necesitamos información de los hijos para eliminar un comentario enter: function (Node $node) { if ($node instanceof CommentNode) { - // Indicamos al traverser que elimine este nodo del AST + // Indica al traverser que elimine este nodo del AST return NodeTraverser::RemoveNode; } }, @@ -299,41 +303,41 @@ function removeCommentNodes(TemplateNode $templateNode): void } ``` -**Advertencia:** Use `RemoveNode` con precaución. Eliminar un nodo que contiene contenido esencial o afecta la estructura (como eliminar el nodo de contenido de un bucle) puede llevar a plantillas dañadas o código generado inválido. Es más seguro para nodos que son verdaderamente opcionales o independientes (como comentarios o etiquetas de depuración) o para nodos estructurales vacíos (p. ej., un `FragmentNode` vacío puede eliminarse de forma segura en algunos contextos mediante un pase de limpieza). +**Precaución:** use `RemoveNode` con cuidado. Eliminar un nodo que contenga contenido esencial o que afecte a la estructura (como eliminar el nodo de contenido de un bucle) puede dejar plantillas rotas o generar código inválido. Es más seguro con nodos verdaderamente opcionales o autocontenidos (como los comentarios o las etiquetas de depuración) o con nodos estructurales vacíos (por ejemplo, un `FragmentNode` vacío puede eliminarse sin riesgo en algunos contextos mediante un pase de limpieza). -Estos tres métodos (modificación de propiedades, reemplazo de nodos y eliminación de nodos) proporcionan las herramientas fundamentales para manipular el AST dentro de sus pases de compilación. +Estos tres métodos (modificar propiedades, sustituir nodos y eliminar nodos) constituyen las herramientas fundamentales para manipular el AST dentro de sus pases del compilador. -Optimización del recorrido -========================== +Optimizar el recorrido +====================== -El AST de las plantillas puede ser bastante grande, conteniendo potencialmente miles de nodos. Recorrer cada nodo individual puede ser innecesario y afectar el rendimiento de la compilación si su pase solo está interesado en partes específicas del árbol. `NodeTraverser` ofrece formas de optimizar el recorrido: +Los AST de las plantillas pueden llegar a ser bastante grandes y contener miles de nodos. Recorrer cada nodo puede ser innecesario y afectar al rendimiento de la compilación si su pase solo se interesa por partes concretas del árbol. `NodeTraverser` ofrece formas de optimizar el recorrido: -Omitir hijos ------------- +Saltarse los hijos +------------------ -Si sabe que una vez que encuentra un cierto tipo de nodo, ninguno de sus descendientes puede contener los nodos que está buscando, puede decirle al traverser que omita la visita de sus hijos. Esto se hace devolviendo la constante `NodeTraverser::DontTraverseChildren` desde el visitor **`enter`**. Esto omite ramas enteras durante el recorrido, lo que potencialmente ahorra un tiempo considerable, especialmente en plantillas con expresiones PHP complejas dentro de las etiquetas. +Si sabe que, en cuanto encuentre cierto tipo de nodo, ninguno de sus descendientes puede contener los nodos que busca, puede indicarle al recorredor que se salte la visita a sus hijos. Se hace devolviendo la constante `NodeTraverser::DontTraverseChildren` desde el visitante **`enter`**. Así poda ramas enteras del recorrido y puede ahorrar mucho tiempo, sobre todo en plantillas con expresiones PHP complejas dentro de las etiquetas. Detener el recorrido -------------------- -Si su pase solo necesita encontrar la *primera* ocurrencia de algo (un tipo específico de nodo, el cumplimiento de una condición), puede detener por completo todo el proceso de recorrido una vez que lo encuentre. Esto se logra devolviendo la constante `NodeTraverser::StopTraversal` desde el visitor `enter` o `leave`. El método `traverse()` dejará de visitar cualquier nodo adicional. Esto es muy eficiente si solo necesita la primera coincidencia en un árbol potencialmente muy grande. +Si su pase solo necesita encontrar la *primera* aparición de algo (un tipo de nodo concreto, el cumplimiento de una condición), puede detener por completo todo el proceso de recorrido en cuanto lo encuentre. Se consigue devolviendo la constante `NodeTraverser::StopTraversal` desde el visitante `enter` o `leave`. El método `traverse()` deja de visitar más nodos. Esto es muy eficaz cuando solo necesita la primera coincidencia en un árbol potencialmente enorme. -Ayudante útil `NodeHelpers` +La útil clase `NodeHelpers` =========================== -Aunque `NodeTraverser` ofrece un control detallado, Latte también proporciona una clase auxiliar práctica, [api:Latte\Compiler\NodeHelpers], que encapsula `NodeTraverser` para varias tareas comunes de búsqueda y análisis, a menudo requiriendo menos código preparatorio. +Aunque `NodeTraverser` ofrece un control muy fino, Latte proporciona también una cómoda clase de utilidades, [api:Latte\Compiler\NodeHelpers], que envuelve a `NodeTraverser` para varias tareas habituales de búsqueda y análisis, a menudo con menos código repetitivo. find(Node $startNode, callable $filter): array .[method] -------------------------------------------------------- -Este método estático encuentra **todos** los nodos en el subárbol que comienza en `$startNode` (inclusive) que satisfacen el callback `$filter`. Devuelve un array de los nodos coincidentes. +Este método estático encuentra **todos** los nodos del subárbol que empieza en `$startNode` (incluido) que cumplen el callback `$filter`. Devuelve un array con los nodos coincidentes. -**Ejemplo:** Encontrar todos los nodos de variables (`VariableNode`) en toda la plantilla. +**Ejemplo:** encontrar todos los nodos de variable (`VariableNode`) de toda la plantilla. ```php use Latte\Compiler\NodeHelpers; @@ -353,9 +357,9 @@ function findAllVariables(TemplateNode $templateNode): array findFirst(Node $startNode, callable $filter): ?Node .[method] -------------------------------------------------------------- -Similar a `find`, pero detiene el recorrido inmediatamente después de encontrar el **primer** nodo que satisface el callback `$filter`. Devuelve el objeto `Node` encontrado o `null` si no se encuentra ningún nodo coincidente. Esto es esencialmente un envoltorio práctico alrededor de `NodeTraverser::StopTraversal`. +Parecido a `find`, pero detiene el recorrido en cuanto encuentra el **primer** nodo que cumple el callback `$filter`. Devuelve el objeto `Node` encontrado o `null` si no hay ninguna coincidencia. En esencia, es un envoltorio cómodo de `NodeTraverser::StopTraversal`. -**Ejemplo:** Encontrar el nodo `{parameters}` (igual que el ejemplo manual anterior, pero más corto). +**Ejemplo:** encontrar el nodo `{parameters}`. ```php use Latte\Compiler\NodeHelpers; @@ -365,23 +369,35 @@ use Latte\Essential\Nodes\ParametersNode; function findParametersNodeHelper(TemplateNode $templateNode): ?ParametersNode { return NodeHelpers::findFirst( - $templateNode->head, // Buscar solo en la sección principal para mayor eficiencia + $templateNode->head, // Busca solo en la sección head por eficiencia fn($node) => $node instanceof ParametersNode, ); } ``` +clone(Latte\Compiler\Node $node): Node .[method] +------------------------------------------------ + +Este método estático crea una **copia profunda** de un nodo y de todo su subárbol. Resulta útil cuando necesita duplicar una rama del AST, por ejemplo para insertar una copia modificada de un nodo dejando el original intacto. + +```php +use Latte\Compiler\NodeHelpers; + +$copy = NodeHelpers::clone($node); +``` + + toValue(ExpressionNode $node, bool $constants = false): mixed .[method] ----------------------------------------------------------------------- -Este método estático intenta evaluar un `ExpressionNode` **en tiempo de compilación** y devolver su valor PHP correspondiente. Funciona de manera confiable solo para nodos literales simples (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) e instancias de `ArrayNode` que contienen solo dichos elementos evaluables. +Este método estático intenta evaluar un `ExpressionNode` **en tiempo de compilación** y devolver su valor PHP correspondiente. Funciona de forma fiable solo con nodos literales simples (`StringNode`, `IntegerNode`, `FloatNode`, `BooleanNode`, `NullNode`) e instancias de `ArrayNode` que contengan únicamente elementos evaluables de ese tipo. -Si `$constants` se establece en `true`, también intentará resolver `ConstantFetchNode` y `ClassConstantFetchNode` verificando `defined()` y usando `constant()`. +Si `$constants` se pone a `true`, intentará además resolver `ConstantFetchNode` y `ClassConstantFetchNode` comprobando `defined()` y usando `constant()`. Si el nodo contiene variables, llamadas a funciones u otros elementos dinámicos, no se puede evaluar en tiempo de compilación y el método lanzará una `InvalidArgumentException`. -**Caso de uso:** Obtener el valor estático de un argumento de etiqueta durante la compilación para tomar decisiones en tiempo de compilación. +**Caso de uso:** obtener el valor estático del argumento de una etiqueta durante la compilación para tomar decisiones en ese momento. ```php use Latte\Compiler\NodeHelpers; @@ -393,7 +409,7 @@ function getStaticStringArgument(ExpressionNode $argumentNode): ?string $value = NodeHelpers::toValue($argumentNode); return is_string($value) ? $value : null; } catch (\InvalidArgumentException $e) { - // El argumento no era una cadena literal estática + // El argumento no era una cadena literal estática return null; } } @@ -403,13 +419,13 @@ function getStaticStringArgument(ExpressionNode $argumentNode): ?string toText(?Node $node): ?string .[method] -------------------------------------- -Este método estático es útil para extraer el contenido de texto plano de nodos simples. Funciona principalmente con: -- `TextNode`: Devuelve su `$content`. -- `FragmentNode`: Concatena el resultado de `toText()` para todos sus hijos. Si algún hijo no es convertible a texto (p. ej., contiene un `PrintNode`), devuelve `null`. -- `NopNode`: Devuelve una cadena vacía. -- Otros tipos de nodos: Devuelve `null`. +Este método estático resulta útil para extraer el contenido de texto plano de nodos sencillos. Funciona sobre todo con: +- `TextNode`: devuelve su `$content`. +- `FragmentNode`: concatena el resultado de `toText()` de todos sus hijos. Si algún hijo no se puede convertir a texto (por ejemplo, contiene un `PrintNode`), devuelve `null`. +- `NopNode`: devuelve una cadena vacía. +- Otros tipos de nodo: devuelve `null`. -**Caso de uso:** Obtener el contenido de texto estático del valor de un atributo HTML o un elemento HTML simple para analizarlo durante un pase de compilación. +**Caso de uso:** obtener el contenido de texto estático del valor de un atributo HTML o de un elemento HTML sencillo para analizarlo durante un pase del compilador. ```php use Latte\Compiler\NodeHelpers; @@ -417,30 +433,30 @@ use Latte\Compiler\Nodes\Html\AttributeNode; function getStaticAttributeValue(AttributeNode $attr): ?string { - // $attr->value es típicamente un AreaNode (como FragmentNode o TextNode) + // $attr->value suele ser un AreaNode (como FragmentNode o TextNode) return NodeHelpers::toText($attr->value); } -// Ejemplo de uso en un pase: +// Ejemplo de uso en un pass: // if ($node instanceof Html\ElementNode && $node->name === 'meta') { -// $nameAttrValue = getStaticAttributeValue($node->getAttributeNode('name')); +// $nameAttrValue = $node->getAttribute('name'); // if ($nameAttrValue === 'description') { ... } // } ``` -`NodeHelpers` puede simplificar sus pases de compilación proporcionando soluciones listas para usar para tareas comunes de recorrido y análisis del AST. +`NodeHelpers` puede simplificar sus pases del compilador aportando soluciones listas para las tareas habituales de recorrido y análisis del AST. Ejemplos prácticos ================== -Apliquemos los conceptos de recorrido y modificación del AST para resolver algunos problemas prácticos. Estos ejemplos demuestran patrones comunes utilizados en los pases de compilación. +Apliquemos los conceptos de recorrido y modificación del AST para resolver algunos problemas prácticos. Estos ejemplos muestran patrones habituales en los pases del compilador. -Adición automática de `loading="lazy"` a `` ------------------------------------------------- +Añadir automáticamente `loading="lazy"` a `` +------------------------------------------------- -Los navegadores modernos admiten la carga diferida nativa para imágenes mediante el atributo `loading="lazy"`. Creemos un pase que agregue automáticamente este atributo a todas las etiquetas `` que aún no tengan un atributo `loading`. +Los navegadores modernos admiten la carga diferida nativa de imágenes mediante el atributo `loading="lazy"`. Creemos un pase que añada automáticamente este atributo a todas las etiquetas `` que aún no tengan un atributo `loading`. ```php use Latte\Compiler\Node; @@ -452,36 +468,33 @@ function addLazyLoading(Nodes\TemplateNode $templateNode): void { (new NodeTraverser)->traverse( $templateNode, - // Podemos usar 'enter', ya que modificamos el nodo directamente - // y no dependemos de los hijos para esta decisión. + // Podemos usar 'enter' porque modificamos el nodo directamente + // y esta decisión no depende de los hijos. enter: function (Node $node) { - // ¿Es un elemento HTML con el nombre 'img'? + // ¿Es un elemento HTML llamado 'img'? if ($node instanceof Html\ElementNode && $node->name === 'img') { - // Aseguramos que el nodo de atributos exista - $node->attributes ??= new Nodes\FragmentNode; - - // Verificamos si ya existe un atributo 'loading' (sin importar mayúsculas/minúsculas) + // Comprueba si el atributo 'loading' ya existe (sin distinguir mayúsculas) foreach ($node->attributes->children as $attrNode) { if ($attrNode instanceof Html\AttributeNode && $attrNode->name instanceof Nodes\TextNode // Nombre de atributo estático && strtolower($attrNode->name->content) === 'loading' ) { - return; // Ya existe, no hacer nada + return; // Ya existe, no hace nada } } - // Agregamos un espacio si los atributos no están vacíos + // Antepone un espacio si los atributos no están vacíos if ($node->attributes->children) { $node->attributes->children[] = new Nodes\TextNode(' '); } - // Creamos un nuevo nodo de atributo: loading="lazy" + // Crea el nuevo nodo de atributo: loading="lazy" $node->attributes->children[] = new Html\AttributeNode( name: new Nodes\TextNode('loading'), value: new Nodes\TextNode('lazy'), quote: '"', ); - // El cambio se aplica directamente en el objeto, no es necesario devolver nada. + // Modificación hecha in situ, no hace falta devolver nada. } }, ); @@ -489,17 +502,17 @@ function addLazyLoading(Nodes\TemplateNode $templateNode): void ``` Explicación: -- El visitor `enter` busca nodos `Html\ElementNode` con el nombre `img`. -- Itera sobre los atributos existentes (`$node->attributes->children`) y comprueba si el atributo `loading` ya está presente. -- Si no se encuentra, crea un nuevo `Html\AttributeNode` que representa `loading="lazy"`. +- El visitante `enter` busca nodos `Html\ElementNode` llamados `img`. +- Recorre los atributos existentes (`$node->attributes->children`) para comprobar si ya hay un atributo `loading`. +- Si no lo encuentra, crea un nuevo `Html\AttributeNode` que representa `loading="lazy"` y lo añade (con un espacio delante si hace falta). -Verificación de llamadas a funciones ------------------------------------- +Comprobar llamadas a funciones +------------------------------ -Los pases de compilación son la base de Latte Sandbox. Aunque el Sandbox real es sofisticado, podemos demostrar el principio básico de verificar llamadas a funciones prohibidas. +Los pases del compilador son la base del Sandbox de Latte. Aunque el Sandbox real es sofisticado, podemos mostrar el principio básico de comprobar llamadas a funciones prohibidas. -**Objetivo:** Prevenir el uso de la función potencialmente peligrosa `shell_exec` dentro de las expresiones de la plantilla. +**Objetivo:** impedir el uso de la función potencialmente peligrosa `shell_exec` dentro de las expresiones de las plantillas. ```php use Latte\Compiler\Node; @@ -512,17 +525,16 @@ function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void { $forbiddenFunctions = ['shell_exec' => true, 'exec' => true]; // Lista simple - $traverser = new NodeTraverser; (new NodeTraverser)->traverse( $templateNode, enter: function (Node $node) use ($forbiddenFunctions) { - // ¿Es un nodo de llamada directa a función? + // ¿Es un nodo de llamada directa a función? if ($node instanceof Php\Expression\FunctionCallNode && $node->name instanceof Php\NameNode && isset($forbiddenFunctions[strtolower((string) $node->name)]) ) { throw new SecurityViolationException( - "La función {$node->name}() no está permitida.", + "Function {$node->name}() is not allowed.", $node->position, ); } @@ -533,23 +545,23 @@ function checkForbiddenFunctions(Nodes\TemplateNode $templateNode): void Explicación: - Definimos una lista de nombres de funciones prohibidas. -- El visitor `enter` comprueba `FunctionCallNode`. -- Si el nombre de la función (`$node->name`) es un `NameNode` estático, verificamos su representación de cadena en minúsculas contra nuestra lista prohibida. -- Si se encuentra una función prohibida, lanzamos `Latte\SecurityViolationException`, que indica claramente una violación de la regla de seguridad y detiene la compilación. +- El visitante `enter` busca nodos `FunctionCallNode`. +- Si el nombre de la función (`$node->name`) es un `NameNode` estático, comparamos su representación en minúsculas con nuestra lista de prohibidas. +- Si encontramos una función prohibida, lanzamos una `Latte\SecurityViolationException`, que indica con claridad la violación de una regla de seguridad y detiene la compilación. -Estos ejemplos muestran cómo los pases de compilación que usan `NodeTraverser` pueden aprovecharse para análisis, modificaciones automáticas y aplicación de restricciones de seguridad interactuando directamente con la estructura AST de la plantilla. +Estos ejemplos muestran cómo los pases del compilador, mediante `NodeTraverser`, se pueden emplear para analizar, modificar automáticamente y hacer cumplir restricciones de seguridad interactuando directamente con la estructura del AST de la plantilla. -Mejores prácticas -================= +Buenas prácticas +================ -Al escribir pases de compilación, tenga en cuenta estas pautas para crear extensiones robustas, mantenibles y eficientes: +Al escribir pases del compilador, tenga presentes estas pautas para crear extensiones robustas, mantenibles y eficientes: -- **El orden es importante:** Sea consciente del orden en que se ejecutan los pases. Si su pase depende de la estructura AST creada por otro pase (p. ej., pases base de Latte u otro pase personalizado), o si otros pases pueden depender de sus modificaciones, use el mecanismo de ordenación proporcionado por `Extension::getPasses()` para definir dependencias (`before`/`after`). Consulte la documentación de [`Extension::getPasses()` |extending-latte#getPasses] para obtener detalles. -- **Responsabilidad única:** Intente que los pases realicen una tarea bien definida. Para transformaciones complejas, considere dividir la lógica en múltiples pases, tal vez uno para análisis y otro para modificación basado en los resultados del análisis. Esto mejora la claridad y la capacidad de prueba. -- **Rendimiento:** Recuerde que los pases de compilación agregan tiempo a la compilación de la plantilla (aunque esto generalmente ocurre solo una vez, hasta que la plantilla cambia). Evite operaciones computacionalmente costosas en sus pases si es posible. Aproveche las optimizaciones de recorrido como `NodeTraverser::DontTraverseChildren` y `NodeTraverser::StopTraversal` siempre que sepa que no necesita visitar ciertas partes del AST. -- **Use `NodeHelpers`:** Para tareas comunes como encontrar nodos específicos o evaluar estáticamente expresiones simples, verifique si `Latte\Compiler\NodeHelpers` ofrece un método adecuado antes de escribir su propia lógica `NodeTraverser`. Puede ahorrar tiempo y reducir la cantidad de código preparatorio. -- **Manejo de errores:** Si su pase detecta un error o un estado inválido en el AST de la plantilla, lance `Latte\CompileException` (o `Latte\SecurityViolationException` para problemas de seguridad) con un mensaje claro y el objeto `Position` relevante (generalmente `$node->position`). Esto proporciona retroalimentación útil al desarrollador de la plantilla. -- **Idempotencia (si es posible):** Idealmente, ejecutar su pase varias veces en el mismo AST debería producir el mismo resultado que ejecutarlo una sola vez. Esto no siempre es factible, pero simplifica la depuración y el razonamiento sobre las interacciones de los pases si se logra. Por ejemplo, asegúrese de que su pase de modificación verifique si la modificación ya se ha aplicado antes de aplicarla nuevamente. +- **El orden importa:** sea consciente del orden en que se ejecutan los pases. Si su pase depende de la estructura del AST creada por otro pase (por ejemplo, los pases del núcleo de Latte u otro pase propio), o si otros pases pueden depender de sus modificaciones, use el mecanismo de ordenación que ofrece `Extension::getPasses()` para definir dependencias (`before`/`after`). Vea los detalles en la documentación de [`Extension::getPasses()` |extending-latte#getPasses()]. +- **Responsabilidad única:** procure que cada pase realice una única tarea bien definida. Para transformaciones complejas, valore dividir la lógica en varios pases: quizá uno para el análisis y otro para modificar según los resultados del análisis. Así gana claridad y facilidad de prueba. +- **Rendimiento:** recuerde que los pases del compilador se suman al tiempo de compilación de la plantilla (aunque esto suele ocurrir una sola vez, hasta que la plantilla cambia). Evite en lo posible operaciones costosas dentro de sus pases. Aproveche las optimizaciones del recorrido, como `NodeTraverser::DontTraverseChildren` y `NodeTraverser::StopTraversal`, siempre que sepa que no necesita visitar ciertas partes del AST. +- **Use `NodeHelpers`:** para tareas habituales, como encontrar nodos concretos o evaluar estáticamente expresiones sencillas, compruebe si `Latte\Compiler\NodeHelpers` ofrece un método adecuado antes de escribir su propia lógica con `NodeTraverser`. Puede ahorrarle tiempo y reducir el código repetitivo. +- **Tratamiento de errores:** si su pase detecta un error o un estado inválido en el AST de la plantilla, lance una `Latte\CompileException` (o `Latte\SecurityViolationException` en cuestiones de seguridad) con un mensaje claro y el objeto `Position` correspondiente (normalmente `$node->position`). Así ofrece información útil a quien desarrolla la plantilla. +- **Idempotencia (si es posible):** lo ideal es que ejecutar su pase varias veces sobre el mismo AST produzca el mismo resultado que ejecutarlo una vez. No siempre es viable, pero cuando se logra simplifica la depuración y el razonamiento sobre la interacción entre pases. Por ejemplo, haga que su pase de modificación compruebe si la modificación ya se ha aplicado antes de aplicarla de nuevo. -Siguiendo estas prácticas, puede aprovechar eficazmente los pases de compilación para ampliar las capacidades de Latte de manera potente y confiable, contribuyendo a un procesamiento de plantillas más seguro, optimizado o funcionalmente más rico. +Siguiendo estas prácticas podrá aprovechar los pases del compilador para extender las capacidades de Latte de forma potente y fiable, contribuyendo a un procesamiento de plantillas más seguro, más optimizado o más rico en funciones. diff --git a/latte/es/cookbook/@home.texy b/latte/es/cookbook/@home.texy index c52c04ac36..c0d662cb05 100644 --- a/latte/es/cookbook/@home.texy +++ b/latte/es/cookbook/@home.texy @@ -1,13 +1,15 @@ -Tutoriales y procedimientos -*************************** +Recetas +******* .[perex] -Ejemplos de código y recetas para realizar tareas comunes con Latte. +Ejemplos de código y recetas para resolver tareas habituales con Latte. -- [Procedimientos para desarrolladores |/develop] +- [Prácticas para desarrolladores |/develop] - [Pasar variables entre plantillas |passing-variables] - [Todo lo que siempre quiso saber sobre la agrupación |grouping] - [¿Cómo escribir consultas SQL en Latte? |how-to-write-sql-queries-in-latte] +- [Migración desde Latte 3.0 |migration-from-latte-30] +- [Migración desde Latte 2 |migration-from-latte2] - [Migración desde PHP |migration-from-php] - [Migración desde Twig |migration-from-twig] - [Usar Latte con Slim 4 |slim-framework] diff --git a/latte/es/cookbook/grouping.texy b/latte/es/cookbook/grouping.texy index d17635e747..54ae660e1f 100644 --- a/latte/es/cookbook/grouping.texy +++ b/latte/es/cookbook/grouping.texy @@ -1,27 +1,29 @@ -Todo lo que siempre quisiste saber sobre la agrupación -****************************************************** +Todo lo que siempre quiso saber sobre la agrupación +*************************************************** .[perex] -Al trabajar con datos en plantillas, a menudo puedes encontrar la necesidad de agruparlos o mostrarlos de forma específica según ciertos criterios. Latte ofrece varias herramientas potentes para este propósito. +Al trabajar con datos en las plantillas, a menudo necesita agrupar elementos, dividirlos en lotes o recorrerlos según una condición. Latte ofrece tres herramientas para ello, cada una adecuada para una situación algo distinta. -El filtro y la función `|group` permiten una agrupación eficiente de datos según un criterio especificado, el filtro `|batch` facilita la división de datos en lotes de tamaño fijo y la etiqueta `{iterateWhile}` proporciona la capacidad de controlar de forma más compleja el flujo de los bucles mediante condiciones. Cada una de estas características ofrece posibilidades específicas para trabajar con datos, lo que las convierte en herramientas indispensables para la visualización dinámica y estructurada de información en las plantillas Latte. +El filtro `|group` agrupa los elementos por un criterio dado, el filtro `|batch` los divide en lotes de tamaño fijo y la etiqueta `{iterateWhile}` recorre los datos paso a paso y decide por sí misma cuándo interrumpir el bucle interior. Vamos a verlos uno por uno. Filtro y función `group` .{data-version:3.0.16} =============================================== -Imagina una tabla de base de datos `items` con elementos divididos en categorías: +La herramienta se puede usar de dos formas: como filtro `$items|group: …` o como función `group($items, …)`. Semánticamente son equivalentes: elija según la legibilidad. + +Imagine una tabla de base de datos `items` cuyos elementos pertenecen a distintas categorías: | id | categoryId | name -|------------------ -| 1 | 1 | Apple -| 2 | 1 | Banana -| 3 | 2 | PHP -| 4 | 3 | Green -| 5 | 3 | Red -| 6 | 3 | Blue +|-----|------------|-------- +| 1 | 1 | Apple +| 2 | 1 | Banana +| 3 | 2 | PHP +| 4 | 3 | Green +| 5 | 3 | Red +| 6 | 3 | Blue -Una lista simple de todos los elementos usando una plantilla Latte se vería así: +Una lista sencilla de todos los elementos con una plantilla de Latte tendría este aspecto: ```latte
      @@ -31,7 +33,7 @@ Una lista simple de todos los elementos usando una plantilla Latte se vería as
    ``` -Sin embargo, si quisiéramos que los elementos estuvieran organizados en grupos por categoría, necesitaríamos dividirlos de modo que cada categoría tuviera su propia lista. El resultado debería verse así: +Pero si quisiéramos los elementos organizados en grupos por categoría, tenemos que dividirlos de modo que cada categoría tenga su propia lista. El resultado deseado sería este: ```latte
      @@ -50,7 +52,7 @@ Sin embargo, si quisiéramos que los elementos estuvieran organizados en grupos
    ``` -La tarea se puede resolver fácil y elegantemente usando `|group`. Como parámetro, especificamos `categoryId`, lo que significa que los elementos se dividirán en arrays más pequeños según el valor de `$item->categoryId` (si `$item` fuera un array, se usaría `$item['categoryId']`): +Esta tarea se resuelve de forma fácil y elegante con `|group`. Indicamos `categoryId` como parámetro, lo que significa que los elementos se repartirán en arrays más pequeños según el valor de `$item->categoryId` (si `$item` fuera un array, se usaría `$item['categoryId']`): ```latte {foreach ($items|group: categoryId) as $categoryId => $categoryItems} @@ -62,25 +64,23 @@ La tarea se puede resolver fácil y elegantemente usando `|group`. Como parámet {/foreach} ``` -El filtro también se puede usar en Latte como una función, lo que nos da una sintaxis alternativa: `{foreach group($items, categoryId) ...}`. - -Si deseas agrupar elementos según criterios más complejos, puedes usar una función en el parámetro del filtro. Por ejemplo, agrupar elementos por la longitud del nombre se vería así: +Si quiere agrupar los elementos según criterios más complejos, puede usar una función como parámetro del filtro. La clave de cada grupo será entonces el valor de retorno de la función; por ejemplo, al agrupar por la longitud del nombre será el número de caracteres: ```latte -{foreach ($items|group: fn($item) => strlen($item->name)) as $items} +{foreach ($items|group: fn($item) => strlen($item->name)) as $length => $group} ... {/foreach} ``` -Es importante tener en cuenta que `$categoryItems` no es un array común, sino un objeto que se comporta como un iterador. Para acceder al primer elemento del grupo, puedes usar la función [`first()` |latte:functions#first]. +Es importante señalar que cada grupo (incluido `$categoryItems`) no es un array corriente, sino un objeto que se comporta como un iterador, así que no puede acceder a los elementos por índice, por ejemplo `$categoryItems[0]`. Sí puede contar los elementos con `count($categoryItems)` y, para acceder al primer elemento del grupo, usar la función [`first()` |latte:functions#first()]. -Esta flexibilidad en la agrupación de datos hace que `group` sea una herramienta excepcionalmente útil para presentar datos en plantillas Latte. +Esta flexibilidad convierte a `|group` en una herramienta excepcionalmente útil para presentar datos. Bucles anidados --------------- -Imaginemos que tenemos una tabla de base de datos con otra columna `subcategoryId`, que define las subcategorías de los elementos individuales. Queremos mostrar cada categoría principal en una lista `