` er
```
-Dynamische Schnipsel .[#toc-dynamic-snippets]
-=============================================
+Schnipsel Bereiche .[#toc-snippet-areas]
+----------------------------------------
-In Nette können Sie auch Snippets mit einem dynamischen Namen definieren, der auf einem Laufzeitparameter basiert. Dies eignet sich besonders für verschiedene Listen, bei denen nur eine Zeile geändert werden muss, aber nicht die ganze Liste mit übertragen werden soll. Ein Beispiel hierfür wäre:
+Snippet-Namen können auch Ausdrücke sein:
```latte
-
- {foreach $list as $id => $item}
- - {$item} update
- {/foreach}
-
+{foreach $items as $id => $item}
+
{$item}
+{/foreach}
```
-Es gibt ein statisches Snippet namens `itemsContainer`, das mehrere dynamische Snippets enthält: `item-0`, `item-1` und so weiter.
+Auf diese Weise erhalten wir mehrere Snippets wie `item-0`, `item-1`, usw. Wenn wir einen dynamischen Ausschnitt (z. B. `item-1`) direkt ungültig machen würden, würde nichts neu gezeichnet werden. Der Grund dafür ist, dass Snippets als echte Auszüge funktionieren und nur sie selbst direkt gerendert werden. In der Vorlage gibt es jedoch technisch gesehen kein Snippet namens `item-1`. Es taucht nur auf, wenn der umgebende Code des Snippets ausgeführt wird, in diesem Fall die foreach-Schleife. Daher markieren wir den Teil der Vorlage, der ausgeführt werden muss, mit dem Tag `{snippetArea}`:
-Ein dynamisches Snippet kann nicht direkt neu gezeichnet werden (das erneute Zeichnen von `item-1` hat keine Auswirkung), Sie müssen das übergeordnete Snippet (in diesem Beispiel `itemsContainer`) neu zeichnen. Dies bewirkt, dass der Code des übergeordneten Snippets ausgeführt wird, aber nur seine Unter-Snippets an den Browser gesendet werden. Wenn Sie nur einen der Teil-Snippets senden wollen, müssen Sie die Eingabe für das übergeordnete Snippet so ändern, dass die anderen Teil-Snippets nicht erzeugt werden.
+```latte
+
+ {foreach $items as $id => $item}
+ - {$item}
+ {/foreach}
+
+```
-Im obigen Beispiel müssen Sie sicherstellen, dass bei einer AJAX-Anfrage nur ein Element zum Array `$list` hinzugefügt wird, so dass die Schleife `foreach` nur ein dynamisches Snippet ausgibt.
+Und wir werden sowohl das einzelne Snippet als auch den gesamten übergreifenden Bereich neu zeichnen:
```php
-class HomePresenter extends Nette\Application\UI\Presenter
-{
- /**
- * This method returns data for the list.
- * Usually this would just request the data from a model.
- * For the purpose of this example, the data is hard-coded.
- */
- private function getTheWholeList(): array
- {
- return [
- 'First',
- 'Second',
- 'Third',
- ];
- }
-
- public function renderDefault(): void
- {
- if (!isset($this->template->list)) {
- $this->template->list = $this->getTheWholeList();
- }
- }
-
- public function handleUpdate(int $id): void
- {
- $this->template->list = $this->isAjax()
- ? []
- : $this->getTheWholeList();
- $this->template->list[$id] = 'Updated item';
- $this->redrawControl('itemsContainer');
- }
-}
+$this->redrawControl('itemsContainer');
+$this->redrawControl('item-1');
```
+Es ist auch wichtig, dass das Array `$items` nur die Elemente enthält, die neu gezeichnet werden sollen.
-Snippets in einer eingebundenen Vorlage .[#toc-snippets-in-an-included-template]
-================================================================================
-
-Es kann vorkommen, dass sich das Snippet in einer Vorlage befindet, die von einer anderen Vorlage eingebunden wird. In diesem Fall müssen wir den Einbindungscode in der zweiten Vorlage mit dem `snippetArea` -Makro einschließen, dann zeichnen wir sowohl den Snippet-Bereich als auch das eigentliche Snippet neu.
-
-Das Makro `snippetArea` sorgt dafür, dass der darin enthaltene Code ausgeführt wird, aber nur das eigentliche Snippet in der eingebundenen Vorlage an den Browser gesendet wird.
+Beim Einfügen einer anderen Vorlage in die Hauptvorlage unter Verwendung des `{include}` -Tags, das Snippets enthält, muss die eingefügte Vorlage erneut in ein `snippetArea` -Tag eingeschlossen und sowohl das Snippet als auch der Bereich gemeinsam ungültig gemacht werden:
```latte
-{* parent.latte *}
-{snippetArea wrapper}
- {include 'child.latte'}
+{snippetArea include}
+ {include 'included.latte'}
{/snippetArea}
```
+
```latte
-{* child.latte *}
+{* enthalten.latte *}
{snippet item}
-...
+ ...
{/snippet}
```
+
```php
-$this->redrawControl('wrapper');
+$this->redrawControl('include');
$this->redrawControl('item');
```
-Sie können es auch mit dynamischen Snippets kombinieren.
-
-
-Hinzufügen und Löschen .[#toc-adding-and-deleting]
-==================================================
-Wenn Sie ein neues Element in die Liste einfügen und `itemsContainer` ungültig machen, liefert die AJAX-Anfrage Snippets, die das neue Element enthalten, aber der Javascript-Handler kann es nicht darstellen. Das liegt daran, dass es kein HTML-Element mit der neu erstellten ID gibt.
+Schnipsel in Komponenten .[#toc-snippets-in-components]
+-------------------------------------------------------
-In diesem Fall besteht die einfachste Möglichkeit darin, die gesamte Liste in ein weiteres Snippet zu verpacken und alles ungültig zu machen:
+Sie können Snippets in [Komponenten |components] erstellen, und Nette wird sie automatisch neu zeichnen. Es gibt jedoch eine bestimmte Einschränkung: Um Snippets neu zu zeichnen, wird die Methode `render()` ohne Parameter aufgerufen. Die Übergabe von Parametern in der Vorlage funktioniert also nicht:
```latte
-{snippet wholeList}
-
- {foreach $list as $id => $item}
- - {$item} update
- {/foreach}
-
-{/snippet}
-
Add
+OK
+{control productGrid}
+
+will not work:
+{control productGrid $arg, $arg}
+{control productGrid:paginator}
```
+
+Senden von Benutzerdaten .[#toc-sending-user-data]
+--------------------------------------------------
+
+Neben den Snippets können Sie beliebige weitere Daten an den Client senden. Schreiben Sie sie einfach in das `payload` Objekt:
+
```php
-public function handleAdd(): void
+public function actionDelete(int $id): void
{
- $this->template->list = $this->getTheWholeList();
- $this->template->list[] = 'New one';
- $this->redrawControl('wholeList');
+ //...
+ if ($this->isAjax()) {
+ $this->payload->message = 'Success';
+ }
}
```
-Das Gleiche gilt für das Löschen eines Eintrags. Es wäre möglich, ein leeres Snippet zu senden, aber in der Regel können Listen paginiert werden, und es wäre kompliziert, das Löschen eines Elements und das Laden eines anderen (das sich auf einer anderen Seite der paginierten Liste befand) zu implementieren.
-
-Parameter an die Komponente senden .[#toc-sending-parameters-to-component]
-==========================================================================
+Senden von Parametern .[#toc-sending-parameters]
+================================================
Wenn wir Parameter über eine AJAX-Anfrage an die Komponente senden, egal ob es sich um Signalparameter oder dauerhafte Parameter handelt, müssen wir ihren globalen Namen angeben, der auch den Namen der Komponente enthält. Den vollständigen Namen des Parameters gibt die Methode `getParameterId()` zurück.
```js
-$.getJSON(
- {link changeCountBasket!},
- {
- {$control->getParameterId('id')}: id,
- {$control->getParameterId('count')}: count
- }
-});
+let url = new URL({link //foo!});
+url.searchParams.set({$control->getParameterId('bar')}, bar);
+
+fetch(url, {
+ headers: {'X-Requested-With': 'XMLHttpRequest'},
+})
```
-Und behandeln Sie die Methode mit den entsprechenden Parametern in der Komponente.
+Eine Handle-Methode mit den entsprechenden Parametern in der Komponente:
```php
-public function handleChangeCountBasket(int $id, int $count): void
+public function handleFoo(int $bar): void
{
-
}
```
diff --git a/application/de/bootstrap.texy b/application/de/bootstrap.texy
index 590e4e76ef..6c441bfcf9 100644
--- a/application/de/bootstrap.texy
+++ b/application/de/bootstrap.texy
@@ -20,18 +20,44 @@ use Nette\Bootstrap\Configurator;
class Bootstrap
{
- public static function boot(): Configurator
+ private Configurator $configurator;
+ private string $rootDir;
+
+ public function __construct()
+ {
+ $this->rootDir = dirname(__DIR__);
+ // Der Konfigurator ist für das Einrichten der Anwendungsumgebung und der Dienste zuständig.
+ $this->configurator = new Configurator;
+ // Legen Sie das Verzeichnis für temporäre Dateien fest, die von Nette erzeugt werden (z. B. kompilierte Vorlagen)
+ $this->configurator->setTempDirectory($this->rootDir . '/temp');
+ }
+
+ public function bootWebApplication(): Nette\DI\Container
{
- $appDir = dirname(__DIR__);
- $configurator = new Configurator;
- //$configurator->setDebugMode('secret@23.75.345.200');
- $configurator->enableTracy($appDir . '/log');
- $configurator->setTempDirectory($appDir . '/temp');
- $configurator->createRobotLoader()
+ $this->initializeEnvironment();
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+ }
+
+ private function initializeEnvironment(): void
+ {
+ // Nette ist intelligent, und der Entwicklungsmodus wird automatisch aktiviert,
+ // oder Sie können ihn für eine bestimmte IP-Adresse aktivieren, indem Sie die folgende Zeile auskommentieren:
+ // $this->configurator->setDebugMode('secret@23.75.345.200');
+
+ // Aktiviert Tracy: das ultimative "Schweizer Taschenmesser" zur Fehlersuche.
+ $this->configurator->enableTracy($this->rootDir . '/log');
+
+ // RobotLoader: Lädt automatisch alle Klassen im angegebenen Verzeichnis
+ $this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
- $configurator->addConfig($appDir . '/config/common.neon');
- return $configurator;
+ }
+
+ private function setupContainer(): void
+ {
+ // Konfigurationsdateien laden
+ $this->configurator->addConfig($this->rootDir . '/config/common.neon');
}
}
```
@@ -40,16 +66,15 @@ class Bootstrap
index.php .[#toc-index-php]
===========================
-Im Falle von Webanwendungen ist die Ausgangsdatei `index.php`, die sich im öffentlichen Verzeichnis `www/` befindet. Sie überlässt es der Klasse `Bootstrap`, die Umgebung zu initialisieren und den `$configurator` zurückzugeben, der den DI-Container erstellt. Dann wird der Dienst `Application` aufgerufen, der die Webanwendung ausführt:
+Die Ausgangsdatei für Webanwendungen ist `index.php`, die sich im öffentlichen Verzeichnis `www/` befindet. Sie verwendet die Klasse `Bootstrap`, um die Umgebung zu initialisieren und einen DI-Container zu erstellen. Anschließend wird der Dienst `Application` aus dem Container abgerufen, der die Webanwendung startet:
```php
-// Initialisieren der Umgebung + Abrufen des Configurator-Objekts
-$configurator = App\Bootstrap::boot();
-// Erstellen eines DI-Containers
-$container = $configurator->createContainer();
-// DI-Container erstellt ein Nette\Application\Application-Objekt
+$bootstrap = new App\Bootstrap;
+// Initialisierung der Umgebung + Erstellung eines DI-Containers
+$container = $bootstrap->bootWebApplication();
+// DI-Container erstellt ein Nette\Anwendung\Anwendungsobjekt
$application = $container->getByType(Nette\Application\Application::class);
-// Nette-Anwendung starten
+// Starten Sie die Nette-Anwendung und bearbeiten Sie die eingehende Anfrage
$application->run();
```
@@ -66,19 +91,19 @@ Die Auswahl des Modus erfolgt durch automatische Erkennung, so dass in der Regel
Wenn Sie den Entwicklungsmodus in anderen Fällen aktivieren möchten, z. B. für Programmierer, die von einer bestimmten IP-Adresse aus zugreifen, können Sie `setDebugMode()` verwenden:
```php
-$configurator->setDebugMode('23.75.345.200'); // eine oder mehrere IP-Adressen
+$this->configurator->setDebugMode('23.75.345.200'); // eine oder mehrere IP-Adressen
```
Wir empfehlen auf jeden Fall, eine IP-Adresse mit einem Cookie zu kombinieren. Wir speichern ein geheimes Token im `nette-debug` Cookie, z.B. `secret1234`, und der Entwicklungsmodus wird für Programmierer mit dieser Kombination von IP und Cookie aktiviert.
```php
-$configurator->setDebugMode('secret1234@23.75.345.200');
+$this->configurator->setDebugMode('secret1234@23.75.345.200');
```
Wir können den Entwicklermodus auch komplett abschalten, sogar für localhost:
```php
-$configurator->setDebugMode(false);
+$this->configurator->setDebugMode(false);
```
Beachten Sie, dass der Wert `true` den Entwicklermodus standardmäßig einschaltet, was auf einem Produktionsserver niemals passieren sollte.
@@ -90,7 +115,7 @@ Debugging-Werkzeug Tracy .[#toc-debugging-tool-tracy]
Zur einfachen Fehlersuche werden wir das großartige Tool [Tracy |tracy:] einschalten. Im Entwicklermodus zeigt es Fehler an und im Produktionsmodus protokolliert es Fehler in das angegebene Verzeichnis:
```php
-$configurator->enableTracy($appDir . '/log');
+$this->configurator->enableTracy($this->rootDir . '/log');
```
@@ -100,7 +125,7 @@ Temporäre Dateien .[#toc-temporary-files]
Nette verwendet den Cache für DI-Container, RobotLoader, Vorlagen usw. Daher ist es notwendig, den Pfad zu dem Verzeichnis festzulegen, in dem der Cache gespeichert werden soll:
```php
-$configurator->setTempDirectory($appDir . '/temp');
+$this->configurator->setTempDirectory($this->rootDir . '/temp');
```
Unter Linux oder macOS setzen Sie die [Schreibrechte |nette:troubleshooting#Setting directory permissions] für die Verzeichnisse `log/` und `temp/`.
@@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader]
Normalerweise wollen wir die Klassen automatisch mit [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__`) und aus allen seinen Unterverzeichnissen:
```php
-$configurator->createRobotLoader()
+$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
```
@@ -126,7 +151,7 @@ Zeitzone .[#toc-timezone]
Configurator ermöglicht es Ihnen, eine Zeitzone für Ihre Anwendung festzulegen.
```php
-$configurator->setTimeZone('Europe/Prague');
+$this->configurator->setTimeZone('Europe/Prague');
```
@@ -143,16 +168,17 @@ Im Entwicklungsmodus wird der Container jedes Mal automatisch aktualisiert, wenn
Konfigurationsdateien werden mit `addConfig()` geladen:
```php
-$configurator->addConfig($appDir . '/config/common.neon');
+$this->configurator->addConfig($this->rootDir . '/config/common.neon');
```
Die Methode `addConfig()` kann mehrfach aufgerufen werden, um mehrere Dateien hinzuzufügen.
```php
-$configurator->addConfig($appDir . '/config/common.neon');
-$configurator->addConfig($appDir . '/config/local.neon');
+$configDir = $this->rootDir . '/config';
+$this->configurator->addConfig($configDir . '/common.neon');
+$this->configurator->addConfig($configDir . '/services.neon');
if (PHP_SAPI === 'cli') {
- $configurator->addConfig($appDir . '/config/cli.php');
+ $this->configurator->addConfig($configDir . '/cli.php');
}
```
@@ -169,7 +195,7 @@ Statische Parameter .[#toc-static-parameters]
Parameter, die in Konfigurationsdateien verwendet werden, können [im Abschnitt `parameters` |dependency-injection:configuration#parameters] definiert und auch von der Methode `addStaticParameters()` übergeben (oder überschrieben) werden (sie hat den Alias `addParameters()`). Wichtig ist, dass unterschiedliche Parameterwerte die Erzeugung zusätzlicher DI-Container, d.h. zusätzlicher Klassen, bewirken.
```php
-$configurator->addStaticParameters([
+$this->configurator->addStaticParameters([
'projectId' => 23,
]);
```
@@ -183,7 +209,7 @@ Dynamische Parameter .[#toc-dynamic-parameters]
Wir können dem Container auch dynamische Parameter hinzufügen, deren unterschiedliche Werte, im Gegensatz zu statischen Parametern, nicht die Erzeugung neuer DI-Container verursachen.
```php
-$configurator->addDynamicParameters([
+$this->configurator->addDynamicParameters([
'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
```
@@ -191,7 +217,7 @@ $configurator->addDynamicParameters([
Umgebungsvariablen können mit dynamischen Parametern leicht verfügbar gemacht werden. Wir können über `%env.variable%` in Konfigurationsdateien auf sie zugreifen.
```php
-$configurator->addDynamicParameters([
+$this->configurator->addDynamicParameters([
'env' => getenv(),
]);
```
@@ -206,6 +232,7 @@ Sie können die folgenden statischen Parameter in den Konfigurationsdateien verw
- `%wwwDir%` ist der absolute Pfad zu dem Verzeichnis, das die `index.php` Eintragsdatei enthält
- `%tempDir%` ist der absolute Pfad zu dem Verzeichnis für temporäre Dateien
- `%vendorDir%` ist der absolute Pfad zu dem Verzeichnis, in dem Composer die Bibliotheken installiert
+- `%rootDir%` ist der absolute Pfad zum Stammverzeichnis des Projekts
- `%debugMode%` gibt an, ob sich die Anwendung im Debug-Modus befindet
- `%consoleMode%` zeigt an, ob die Anfrage über die Befehlszeile kam
@@ -225,7 +252,7 @@ services:
Erstellen Sie eine neue Instanz und fügen Sie sie in Bootstrap ein:
```php
-$configurator->addServices([
+$this->configurator->addServices([
'myservice' => new App\Model\MyCustomService('foobar'),
]);
```
@@ -234,13 +261,21 @@ $configurator->addServices([
Verschiedene Umgebungen .[#toc-different-environments]
======================================================
-Es steht Ihnen frei, die Klasse `Bootstrap` an Ihre Bedürfnisse anzupassen. Sie können der Methode `boot()` Parameter hinzufügen, um Webprojekte zu unterscheiden, oder andere Methoden hinzufügen, wie `bootForTests()`, die die Umgebung für Unit-Tests initialisiert, `bootForCli()` für Skripte, die von der Befehlszeile aus aufgerufen werden, und so weiter.
+Zögern Sie nicht, die Klasse `Bootstrap` nach Ihren Bedürfnissen anzupassen. Sie können der Methode `bootWebApplication()` Parameter hinzufügen, um zwischen Webprojekten zu unterscheiden. Alternativ können Sie auch andere Methoden hinzufügen, z. B. `bootTestEnvironment()`, um die Umgebung für Unit-Tests zu initialisieren, `bootConsoleApplication()` für Skripte, die von der Befehlszeile aus aufgerufen werden, und so weiter.
```php
-public static function bootForTests(): Configurator
+public function bootTestEnvironment(): Nette\DI\Container
+{
+ Tester\Environment::setup(); // Initialisierung des Nette-Testers
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+}
+
+public function bootConsoleApplication(): Nette\DI\Container
{
- $configurator = self::boot();
- Tester\Environment::setup(); // Nette Tester Initialisierung
- return $configurator;
+ $this->configurator->setDebugMode(false);
+ $this->initializeEnvironment();
+ $this->setupContainer();
+ return $this->configurator->createContainer();
}
```
diff --git a/application/de/components.texy b/application/de/components.texy
index b4540b4808..d47cc5bd78 100644
--- a/application/de/components.texy
+++ b/application/de/components.texy
@@ -230,6 +230,28 @@ In der Vorlage stehen diese Meldungen in der Variablen `$flashes` als Objekte `s
```
+Umleitung nach einem Signal .[#toc-redirection-after-a-signal]
+==============================================================
+
+Nach der Verarbeitung eines Komponentensignals folgt oft eine Umleitung. Diese Situation ist ähnlich wie bei Formularen - nach dem Absenden eines Formulars leiten wir ebenfalls um, um eine erneute Übermittlung von Daten zu verhindern, wenn die Seite im Browser aktualisiert wird.
+
+```php
+$this->redirect('this') // redirects to the current presenter and action
+```
+
+Da eine Komponente ein wiederverwendbares Element ist und in der Regel keine direkte Abhängigkeit von bestimmten Presentern haben sollte, interpretieren die Methoden `redirect()` und `link()` den Parameter automatisch als Komponentensignal:
+
+```php
+$this->redirect('click') // redirects to the 'click' signal of the same component
+```
+
+Wenn Sie zu einem anderen Präsentator oder einer Aktion umleiten müssen, können Sie dies über den Präsentator tun:
+
+```php
+$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action
+```
+
+
Dauerhafte Parameter .[#toc-persistent-parameters]
==================================================
@@ -347,7 +369,7 @@ services:
Schließlich werden wir diese Fabrik in unserem Präsentator verwenden:
```php
-class PollPresenter extends Nette\UI\Application\Presenter
+class PollPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private PollControlFactory $pollControlFactory,
@@ -380,7 +402,7 @@ Komponenten im Detail .[#toc-components-in-depth]
Komponenten in einer Nette-Anwendung sind die wiederverwendbaren Teile einer Web-Anwendung, die wir in Seiten einbetten, was das Thema dieses Kapitels ist. Was genau sind die Fähigkeiten einer solchen Komponente?
1) sie ist in einer Vorlage renderbar
-2) sie weiß, welcher Teil von ihr bei einer [AJAX-Anfrage |ajax#invalidation] gerendert werden soll (Snippets)
+2) es weiß, [welcher Teil von sich selbst |ajax#snippets] während einer AJAX-Anfrage zu rendern ist (Snippets)
3) er kann seinen Zustand in einer URL speichern (persistente Parameter)
4) es hat die Fähigkeit, auf Benutzeraktionen zu reagieren (Signale)
5) er erstellt eine hierarchische Struktur (wobei die Wurzel der Präsentator ist)
diff --git a/application/de/configuration.texy b/application/de/configuration.texy
index da3a0fcebf..848f7abb64 100644
--- a/application/de/configuration.texy
+++ b/application/de/configuration.texy
@@ -13,11 +13,15 @@ Anwendung:
# zeigt das Feld "Nette Anwendung" in Tracy BlueScreen?
debugger: ... # (bool) standardmäßig true
- # wird der error-presenter im Fehlerfall aufgerufen?
- catchExceptions: ... # (bool) steht im Produktionsmodus standardmäßig auf true
+ # wird error-presenter im Fehlerfall aufgerufen?
+ # hat nur im Entwicklermodus Auswirkungen
+ catchExceptions: ... # (bool) ist standardmäßig true
# Name des Fehlermelders
- errorPresenter: Error # (string) Standardwert ist 'Nette:Error'
+ errorPresenter: Error # (string|array) Standardwert ist 'Nette:Error'
+
+ # definiert Aliase für Moderatoren und Veranstaltungen
+ aliases: ...
# definiert die Regeln für die Auflösung des Presenter-Namens in eine Klasse
mapping: ...
@@ -27,10 +31,19 @@ Anwendung:
silentLinks: ... # (bool) ist standardmäßig auf false eingestellt
```
-Da Fehlerpräsenter im Entwicklungsmodus standardmäßig nicht aufgerufen werden und die Fehler von Tracy angezeigt werden, hilft das Ändern des Wertes `catchExceptions` in `true` dabei, zu überprüfen, ob die Fehlerpräsenter während der Entwicklung korrekt funktionieren.
+Ab `nette/application` Version 3.2 ist es möglich, ein Paar von Fehlerpräsentern zu definieren:
+
+```neon
+application:
+ errorPresenter:
+ 4xx: Error4xx # für Nette\Application\BadRequestException
+ 5xx: Error5xx # für andere Ausnahmen
+```
Die Option `silentLinks` legt fest, wie sich Nette im Entwicklermodus verhält, wenn die Link-Generierung fehlschlägt (z. B. weil kein Presenter vorhanden ist usw.). Der Standardwert `false` bedeutet, dass Nette `E_USER_WARNING` auslöst. Die Einstellung `true` unterdrückt diese Fehlermeldung. In einer Produktionsumgebung wird immer `E_USER_WARNING` aufgerufen. Wir können dieses Verhalten auch beeinflussen, indem wir die Presenter-Variable [$invalidLinkMode |creating-links#Invalid Links] setzen.
+[Aliasnamen vereinfachen das Aufsuchen |creating-links#aliases] häufig verwendeter Moderatoren.
+
Das [Mapping definiert die Regeln |modules#mapping], nach denen der Klassenname aus dem Presenter-Namen abgeleitet wird.
@@ -82,6 +95,9 @@ Latte:
# aktiviert die [Überprüfung von generiertem Code |latte:develop#Checking Generated Code]
phpLinter: ... # (string) Voreinstellung ist null
+ # legt das Gebietsschema fest
+ locale: cs_CZ # (string) Voreinstellung ist null
+
# Klasse von $this->template
templateClass: App\MyTemplateClass # Standardwert ist Nette\Bridges\ApplicationLatte\DefaultTemplate
```
@@ -91,7 +107,7 @@ Wenn Sie Latte Version 3 verwenden, können Sie neue [Erweiterungen |latte:creat
```neon
latte:
extensions:
- - Latte\Essential\TranslatorExtension
+ - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```
/--comment
diff --git a/application/de/creating-links.texy b/application/de/creating-links.texy
index 1ec3fbd1e9..2b5a91f73d 100644
--- a/application/de/creating-links.texy
+++ b/application/de/creating-links.texy
@@ -38,7 +38,7 @@ Es ist auch möglich, benannte Parameter zu übergeben. Der folgende Link überg
detail
```
-Wenn die Methode `ProductPresenter::renderShow()` nicht `$lang` in ihrer Signatur hat, kann sie den Wert des Parameters mit `$lang = $this->getParameter('lang')` lesen.
+Wenn die Methode `ProductPresenter::renderShow()` nicht `$lang` in ihrer Signatur hat, kann sie den Wert des Parameters über `$lang = $this->getParameter('lang')` oder über die [Eigenschaft |presenters#Request Parameters] abrufen.
Wenn die Parameter in einem Array gespeichert sind, können sie mit dem `...` -Operator (oder `(expand)` -Operator in Latte 2.x) expandiert werden:
@@ -140,7 +140,7 @@ Das Ziel `this` wird einen Link zur aktuellen Seite erstellen:
refresh
```
-Gleichzeitig werden alle Parameter, die in der Signatur des Befehls `render
()` oder `action()` Methode angegeben sind, übertragen. Wenn wir uns also auf den Seiten `Product:show` und `id:123` befinden, wird der Link zu `this` auch diesen Parameter übergeben.
+Gleichzeitig werden alle Parameter, die in der Signatur des Befehls `action()` oder `render()` Methode angegebenen Parameter, wenn die `action()` nicht definiert ist, übertragen. Wenn wir uns also auf den Seiten `Product:show` und `id:123` befinden, wird der Link zu `this` auch diesen Parameter übergeben.
Natürlich ist es auch möglich, die Parameter direkt anzugeben:
@@ -213,7 +213,7 @@ Da [Komponenten |components] separate, wiederverwendbare Einheiten sind, die kei
Wenn wir auf Präsentatoren in der Komponentenvorlage verlinken wollen, verwenden wir das Tag `{plink}`:
```latte
-home
+home
```
oder im Code
@@ -223,6 +223,30 @@ $this->getPresenter()->link('Home:default')
```
+Aliasnamen .[#toc-aliases]{data-version:v3.2.2}
+===============================================
+
+Manchmal ist es sinnvoll, einem Presenter:Action-Paar einen leicht zu merkenden Alias zuzuweisen. Zum Beispiel könnten Sie die Homepage `Front:Home:default` einfach als `home` oder `Admin:Dashboard:default` als `admin` benennen.
+
+Aliasnamen werden in der [Konfiguration |configuration] unter dem Schlüssel `application › aliases` definiert:
+
+```neon
+application:
+ aliases:
+ home: Front:Home:default
+ admin: Admin:Dashboard:default
+ sign: Front:Sign:in
+```
+
+In Links werden sie mit dem at-Symbol geschrieben, zum Beispiel:
+
+```latte
+administration
+```
+
+Sie werden in allen Methoden unterstützt, die mit Links arbeiten, wie `redirect()` und ähnliche.
+
+
Ungültige Links .[#toc-invalid-links]
=====================================
diff --git a/application/de/how-it-works.texy b/application/de/how-it-works.texy
index 3ac55697e4..fde1735364 100644
--- a/application/de/how-it-works.texy
+++ b/application/de/how-it-works.texy
@@ -22,18 +22,18 @@ Die Verzeichnisstruktur sieht in etwa so aus:
/--pre
web-project/
├── app/ ← Verzeichnis mit Anwendung
-│ ├── Presenters/ ← Presenter-Klassen
-│ │ ├── HomePresenter.php ← Home presenterklasse
-│ │ └── templates/ ← Vorlagenverzeichnis
-│ │ ├── @layout.latte ← Vorlage für gemeinsames Layout
-│ │ └── Home/ ← Vorlagen für Home-presenter
-│ │ └── default.latte ← Vorlage für Aktion `default`
-│ ├── Router/ ← Konfiguration von URL-Adressen
+│ ├── Core/ ← grundlegende notwendige Klassen
+│ │ └── RouterFactory.php ← Konfiguration der URL-Adressen
+│ ├── UI/ ← Moderatoren, Vorlagen & Co.
+│ │ ├── @layout.latte ← Vorlage für gemeinsames Layout
+│ │ └── Home/ ← Home Presenter Verzeichnis
+│ │ ├── HomePresenter.php ← Home Presenter Klasse
+│ │ └── default.latte ← Vorlage für Aktion default
│ └── Bootstrap.php ← bootende Klasse Bootstrap
├── bin/ ← Skripte für die Kommandozeile
├── config/ ← Konfigurationsdateien
│ ├── common.neon
-│ └── local.neon
+│ └── services.neon
├── log/ ← Fehlerprotokolle
├── temp/ ← temporäre Dateien, Cache, …
├── vendor/ ← vom Composer installierte Bibliotheken
@@ -91,7 +91,7 @@ In Nette geschriebene Anwendungen sind in viele so genannte Presenter unterteilt
Die Anwendung beginnt damit, dass sie den so genannten Router bittet, zu entscheiden, an welchen der Presenter die aktuelle Anfrage zur Bearbeitung weitergeleitet werden soll. Der Router entscheidet, wer dafür zuständig ist. Er sieht sich die Eingabe-URL `https://example.com/product/123` handelt, der ein Produkt mit `id: 123` als Aktion an `show` weiterleiten möchte. Es ist eine gute Angewohnheit, ein durch einen Doppelpunkt getrenntes Paar aus Präsentator + Aktion als `Product:show` zu schreiben.
-Der Router verwandelt also die URL in ein Paar `Presenter:action` + Parameter, in unserem Fall `Product:show` + `id: 123`. Sie können sehen, wie ein Router in der Datei `app/Router/RouterFactory.php` aussieht, und wir werden ihn im Kapitel [Routing] ausführlich beschreiben.
+Der Router verwandelt also die URL in ein Paar `Presenter:action` + Parameter, in unserem Fall `Product:show` + `id: 123`. Sie können sehen, wie ein Router in der Datei `app/Core/RouterFactory.php` aussieht, und wir werden ihn im Kapitel [Routing] ausführlich beschreiben.
Machen wir weiter. Die Anwendung kennt bereits den Namen des Präsentators und kann fortfahren. Sie erstellt ein Objekt `ProductPresenter`, das den Code des Presenters `Product` darstellt. Genauer gesagt, sie bittet den DI-Container um die Erstellung des Presenters, denn die Erstellung von Objekten ist seine Aufgabe.
@@ -121,12 +121,9 @@ So wurde die Methode `renderShow(123)` aufgerufen, deren Code ein fiktives Beisp
Anschließend gibt der Präsentator die Antwort zurück. Dies kann eine HTML-Seite, ein Bild, ein XML-Dokument, das Senden einer Datei von der Festplatte, JSON oder die Routing zu einer anderen Seite sein. Wichtig ist, dass, wenn wir nicht ausdrücklich sagen, wie zu antworten ist (was bei `ProductPresenter` der Fall ist), die Antwort darin besteht, die Vorlage mit einer HTML-Seite wiederzugeben. Und warum? Nun, weil wir in 99 % der Fälle eine Vorlage zeichnen wollen, so dass der Präsentator dieses Verhalten als Standard annimmt und uns die Arbeit erleichtern will. Das ist der Punkt von Nette.
-Wir müssen nicht einmal angeben, welche Vorlage gezeichnet werden soll, er leitet den Pfad dorthin nach einer einfachen Logik ab. Im Fall von presenter `Product` und action `show` versucht er zu sehen, ob eine dieser Vorlagendateien relativ zu dem Verzeichnis existiert, in dem sich die Klasse `ProductPresenter` befindet:
+Wir müssen nicht einmal angeben, welche Vorlage gerendert werden soll; das Framework wird den Pfad selbst ermitteln. Im Fall der Aktion `show` versucht es einfach, die Vorlage `show.latte` im Verzeichnis mit der Klasse `ProductPresenter` zu laden. Es versucht auch, das Layout in der Datei `@layout.latte` zu finden (mehr über die [Vorlagensuche |templates#Template Lookup]).
-- `templates/Product/show.latte`
-- `templates/Product.show.latte`
-
-Außerdem wird versucht, das Layout in der Datei `@layout.latte` zu finden, und dann wird die Vorlage gerendert. Nun ist die Aufgabe des Präsentators und der gesamten Anwendung abgeschlossen. Wenn die Vorlage nicht existiert, wird eine Seite mit dem Fehler 404 zurückgegeben. Weitere Informationen über Präsentatoren finden Sie auf der Seite [Präsentatoren |Presenters].
+Anschließend werden die Vorlagen gerendert. Damit ist die Aufgabe des Präsentators und der gesamten Anwendung abgeschlossen, und die Arbeit ist getan. Wenn die Vorlage nicht vorhanden wäre, würde eine 404-Fehlerseite zurückgegeben werden. Weitere Informationen über Präsentatoren finden Sie auf der Seite [Präsentatoren |presenters].
[* request-flow.svg *]
@@ -137,7 +134,7 @@ Um sicherzugehen, versuchen wir, den gesamten Prozess mit einer etwas anderen UR
3) der Router dekodiert die URL als ein Paar `Home:default`
4) ein `HomePresenter` Objekt wird erstellt
5) die Methode `renderDefault()` wird aufgerufen (falls vorhanden)
-6) eine Vorlage `templates/Home/default.latte` mit einem Layout `templates/@layout.latte` wird gerendert
+6) eine Vorlage `default.latte` mit einem Layout `@layout.latte` wird gerendert
Vielleicht sind Sie jetzt auf eine Menge neuer Konzepte gestoßen, aber wir glauben, dass sie sinnvoll sind. Das Erstellen von Anwendungen in Nette ist ein Kinderspiel.
diff --git a/application/de/modules.texy b/application/de/modules.texy
index fcdf4331c4..75981ac23a 100644
--- a/application/de/modules.texy
+++ b/application/de/modules.texy
@@ -2,29 +2,31 @@ Module
******
.[perex]
-In Nette stellen Module die logischen Einheiten dar, aus denen eine Anwendung besteht. Sie umfassen Presenter, Templates, eventuell auch Komponenten und Modellklassen.
+Module bringen Klarheit in Nette-Anwendungen, indem sie eine einfache Unterteilung in logische Einheiten ermöglichen.
-Ein Verzeichnis für Presenter und eines für Templates würde für echte Projekte nicht ausreichen. Dutzende von Dateien in einem Ordner zu haben, ist zumindest unorganisiert. Wie kommt man da wieder raus? Wir teilen sie einfach in Unterverzeichnisse auf der Festplatte und in Namensräume im Code auf. Und das ist genau das, was die Nette-Module tun.
-
-Vergessen wir also einen einzigen Ordner für Präsentatoren und Vorlagen und erstellen wir stattdessen Module, zum Beispiel `Admin` und `Front`.
+Ähnlich wie bei der Organisation von Dateien in Ordnern auf einer Festplatte, können wir in Nette Presenter, Vorlagen und andere Hilfsklassen in Module unterteilen. Wie funktioniert das in der Praxis? Ganz einfach, indem man neue Unterverzeichnisse in die Struktur einfügt. Hier ist ein Beispiel für eine Struktur mit zwei Modulen, Front und Admin:
/--pre
-app/
-├── Presenters/
-├── Modules/ ← Verzeichnis mit Modulen
-│ ├── Admin/ ← Modul Admin
-│ │ ├── Presenters/ ← seine Presenters
-│ │ │ ├── DashboardPresenter.php
-│ │ │ └── templates/
-│ └── Front/ ← Modul Front
-│ └── Presenters/ ← seine Presenters
-│ └── ...
+app/
+├── UI/
+│ ├── Admin/ ← Admin module
+│ │ ├── @layout.latte
+│ │ ├── Dashboard/
+│ │ │ ├── DashboardPresenter.php
+│ │ │ └── default.latte
+│ │ └── ...
+│ ├── Front/ ← Front module
+│ │ ├── @layout.latte
+│ │ ├── Home/
+│ │ │ ├── HomePresenter.php
+│ │ │ └── default.latte
+│ │ └── ...
\--
-Diese Verzeichnisstruktur spiegelt sich in den Klassennamensräumen wider, so dass z. B. `DashboardPresenter` im Namensraum `App\Modules\Admin\Presenters` liegt:
+Diese Verzeichnisstruktur spiegelt sich in den Namespaces der Klassen wider, so befindet sich z.B. `DashboardPresenter` im Namespace `App\UI\Admin\Dashboard`:
```php
-namespace App\Modules\Admin\Presenters;
+namespace App\UI\Admin\Dashboard;
class DashboardPresenter extends Nette\Application\UI\Presenter
{
@@ -32,35 +34,49 @@ class DashboardPresenter extends Nette\Application\UI\Presenter
}
```
-Der Präsentator `Dashboard` innerhalb des Moduls `Admin` wird innerhalb der Anwendung mit der Doppelpunktschreibweise als `Admin:Dashboard` referenziert, und seine Aktion `default` als `Admin:Dashboard:default`.
-Und woher weiß Nette selbst, dass `Admin:Dashboard` die Klasse `App\Modules\Admin\Presenters\DashboardPresenter` repräsentiert? Dies wird durch ein [Mapping |#mapping] in der Konfiguration festgelegt.
-Die vorgegebene Struktur ist also nicht fest vorgegeben und kann nach Belieben verändert werden.
+In der Anwendung wird der Presenter `Dashboard` innerhalb des Moduls `Admin` mit Doppelpunkt als `Admin:Dashboard` bezeichnet. Für die Aktion `default` wird er als `Admin:Dashboard:default` bezeichnet.
+
+Die vorgestellte Struktur ist nicht starr; Sie können [sie |#mapping] in der Konfiguration [vollständig an Ihre Bedürfnisse anpassen |#mapping]. .[tip]
-Module können neben Presentern und Templates natürlich auch alle anderen Elemente enthalten, wie z.B. Komponenten, Modellklassen, etc.
+Module können neben Presentern und Vorlagen auch alle anderen Dateien, wie Komponenten und Hilfsklassen, enthalten. Wenn Sie überlegen, wo Sie diese ablegen wollen, sollten Sie einen Ordner `Accessory` verwenden:
+
+/--pre
+app/
+├── UI/
+│ ├── Admin/
+│ │ ├── Accessory/
+│ │ │ ├── FormFactory.php
+│ │ │ └── AdminLayout.php
+│ │ ├── Dashboard/
+│ │ └── ...
+\--
Verschachtelte Module .[#toc-nested-modules]
--------------------------------------------
-Module müssen nicht nur eine flache Struktur bilden, Sie können auch Untermodule erstellen, zum Beispiel:
+Module können mehrere Verschachtelungsebenen haben, ähnlich wie eine Verzeichnisstruktur auf einer Festplatte:
/--pre
-app/
-├── Modules/ ← Verzeichnis mit Modulen
-│ ├── Blog/ ← Modul Blog
-│ │ ├── Admin/ ← Submodul Admin
-│ │ │ ├── Presenters/
+app/
+├── UI/
+│ ├── Blog/ ← Blog module
+│ │ ├── Admin/ ← Admin submodule
+│ │ │ ├── Dashboard/
+│ │ │ └── ...
+│ │ ├── Front/ ← Front submodule
+│ │ │ ├── @layout.latte
+│ │ │ ├── Home/
│ │ │ └── ...
-│ │ └── Front/ ← Submodul Front
-│ │ ├── Presenters/
-│ │ └── ...
-│ ├── Forum/ ← Modul Forum
+│ ├── Forum/ ← Forum module
│ │ └── ...
\--
-So wird das Modul `Blog` in die Untermodule `Admin` und `Front` aufgeteilt. Dies spiegelt sich auch in den Namensräumen wider, die dann `App\Modules\Blog\Admin\Presenters` usw. lauten. Der Präsentator `Dashboard` innerhalb des Submoduls wird als `Blog:Admin:Dashboard` bezeichnet.
+Das Modul `Blog` ist in die Untermodule `Admin` und `Front` unterteilt. Dies spiegelt sich auch in den Namespaces wider, die dann als `App\UI\Blog\Admin` und ähnlich erscheinen. Um auf den Präsentator `Dashboard` innerhalb des Submoduls `Admin` zu verweisen, wird er als `Blog:Admin:Dashboard` bezeichnet.
+
+Die Verschachtelung kann so tief wie nötig sein und erlaubt die Erstellung von Sub-Submodulen.
-Die Verschachtelung kann beliebig tief gehen, so dass Sub-Submodule erstellt werden können.
+Wenn Sie zum Beispiel in der Verwaltung viele Präsentatoren haben, die mit der Auftragsverwaltung zusammenhängen, wie `OrderDetail`, `OrderEdit`, `OrderDispatch`, usw., könnten Sie ein `Order` Modul erstellen, in dem Präsentatoren wie `Detail`, `Edit`, `Dispatch` und andere organisiert werden.
Erstellen von Links .[#toc-creating-links]
@@ -99,50 +115,69 @@ Routing .[#toc-routing]
Siehe [Kapitel über Routing |routing#Modules].
-Abbildung .[#toc-mapping]
--------------------------
+Kartierung .[#toc-mapping]
+--------------------------
-Legt die Regeln fest, nach denen der Klassenname aus dem Namen des Präsentators abgeleitet wird. Wir schreiben sie in die [Konfiguration |configuration] unter dem Schlüssel `application › mapping`.
+Mapping definiert die Regeln für die Ableitung des Klassennamens aus dem Presenter-Namen. Diese Regeln werden in der [Konfiguration |configuration] unter dem Schlüssel `application › mapping` angegeben.
-Beginnen wir mit einem Beispiel, das keine Module verwendet. Wir wollen nur, dass die Presenter-Klassen den Namespace `App\Presenters` haben. Das bedeutet, dass ein Presenter wie `Home` auf die Klasse `App\Presenters\HomePresenter` abgebildet werden soll. Dies kann durch die folgende Konfiguration erreicht werden:
+Die oben auf dieser Seite erwähnten Verzeichnisstrukturen basieren auf der folgenden Zuordnung:
```neon
application:
- mapping:
- *: App\Presenters\*Presenter
+ mapping: App\UI\*\**Presenter
```
-Der Name des Presenters wird durch das Sternchen in der Klassenmaske ersetzt und das Ergebnis ist der Klassenname. Einfach!
+Wie funktioniert das Mapping? Zum besseren Verständnis stellen wir uns zunächst eine Anwendung ohne Module vor. Wir wollen, dass die Presenter-Klassen in den Namensraum `App\UI` fallen, so dass der Presenter `Home` auf die Klasse `App\UI\HomePresenter` abgebildet wird. Dies kann mit dieser Konfiguration erreicht werden:
-Wenn wir die Vortragenden in Module unterteilen, können wir für jedes Modul eine eigene Zuordnung vornehmen:
+```neon
+application:
+ mapping: App\UI\*Presenter
+```
+
+Diese Zuordnung funktioniert, indem das Sternchen in der Maske `App\UI\*Presenter` durch den Presenter-Namen `Home` ersetzt wird, was zu dem endgültigen Klassennamen `App\UI\HomePresenter` führt. Einfach!
+
+Wie Sie jedoch in den Beispielen in diesem und anderen Kapiteln sehen können, platzieren wir Presenter-Klassen in gleichnamigen Unterverzeichnissen, z. B. wird der Presenter `Home` der Klasse `App\UI\Home\HomePresenter` zugeordnet. Dies wird durch die Verdoppelung des Sternchens erreicht (erfordert Nette Application 3.2):
+
+```neon
+application:
+ mapping: App\UI\**Presenter
+```
+
+Gehen wir nun dazu über, Presenter in Modulen abzubilden. Für jedes Modul können wir spezifische Zuordnungen definieren:
```neon
application:
mapping:
- Front: App\Modules\Front\Presenters\*Presenter
- Admin: App\Modules\Admin\Presenters\*Presenter
+ Front: App\UI\Front\**Presenter
+ Admin: App\UI\Admin\**Presenter
Api: App\Api\*Presenter
```
-Der Referent `Front:Home` wird der Klasse `App\Modules\Front\Presenters\HomePresenter` zugeordnet und der Referent `Admin:Dashboard` der Klasse `App\Modules\Admin\Presenters\DashboardPresenter`.
+Nach dieser Konfiguration wird der Präsentator `Front:Home` der Klasse `App\UI\Front\Home\HomePresenter` zugeordnet, während der Präsentator `Api:OAuth` der Klasse `App\Api\OAuthPresenter` zugeordnet wird.
-Es ist praktischer, eine allgemeine (Stern-)Regel zu erstellen, um die ersten beiden zu ersetzen. Das zusätzliche Sternchen wird der Klassenmaske nur für dieses Modul hinzugefügt:
+Da die Module `Front` und `Admin` einen ähnlichen Zuordnungsansatz haben und es wahrscheinlich noch mehr solcher Module gibt, ist es möglich, eine allgemeine Regel zu erstellen, die sie ersetzt. In der Klassenmaske wird ein neues Sternchen für das Modul hinzugefügt:
```neon
application:
mapping:
- *: App\Modules\*\Presenters\*Presenter
+ *: App\UI\*\**Presenter
Api: App\Api\*Presenter
```
-Was aber, wenn wir verschachtelte Module verwenden und einen Präsentator `Admin:User:Edit` haben? In diesem Fall wird das Segment mit dem Sternchen, das das Modul für jede Ebene darstellt, einfach wiederholt und das Ergebnis ist die Klasse `App\Modules\Admin\User\Presenters\EditPresenter`.
+Bei mehrstufig verschachtelten Modulen, wie z. B. dem Moderator `Admin:User:Edit`, wird das Sternchen-Segment für jede Stufe wiederholt, was zu der Klasse `App\UI\Admin\User\Edit\EditPresenter` führt.
-Eine alternative Schreibweise ist die Verwendung eines Arrays, das aus drei Segmenten anstelle einer Zeichenkette besteht. Diese Notation ist gleichwertig mit der vorherigen:
+Eine alternative Schreibweise ist die Verwendung eines Arrays, das aus drei Segmenten besteht, anstelle einer Zeichenkette. Diese Notation ist äquivalent zur vorherigen:
```neon
application:
mapping:
- *: [App\Modules, *, Presenters\*Presenter]
+ *: [App\UI, *, **Presenter]
+ Api: [App\Api, '', *Presenter]
```
-Der Standardwert ist `*: *Module\*Presenter`.
+Wenn wir nur eine Regel in der Konfiguration haben, die allgemeine, können wir kurz schreiben:
+
+```neon
+application:
+ mapping: App\UI\*\**Presenter
+```
diff --git a/application/de/presenters.texy b/application/de/presenters.texy
index 35bc4c3513..8622224006 100644
--- a/application/de/presenters.texy
+++ b/application/de/presenters.texy
@@ -60,7 +60,7 @@ Unmittelbar nach Erhalt der Anfrage wird die Methode `startup ()` aufgerufen. Si
Es ist wichtig, dass `action()` vor aufgerufen wird `render()`aufgerufen wird, damit wir darin möglicherweise den weiteren Verlauf des Lebenszyklus ändern können, d. h. die Vorlage, die gerendert wird, und auch die Methode `render()` die aufgerufen wird, mit `setView('otherView')`.
-Die Parameter der Anfrage werden an die Methode übergeben. Es ist möglich und empfehlenswert, Typen für die Parameter anzugeben, z. B. `actionShow(int $id, string $slug = null)` - wenn der Parameter `id` fehlt oder keine ganze Zahl ist, gibt der Präsentator den [Fehler 404 |#Error 404 etc.] zurück und bricht die Operation ab.
+Die Parameter der Anfrage werden an die Methode übergeben. Es ist möglich und empfehlenswert, Typen für die Parameter anzugeben, z. B. `actionShow(int $id, ?string $slug = null)` - wenn der Parameter `id` fehlt oder keine ganze Zahl ist, gibt der Präsentator den [Fehler 404 |#Error 404 etc.] zurück und bricht die Operation ab.
`handle(args...)` .{toc: handle()}
@@ -205,7 +205,7 @@ In der Vorlage sind diese Meldungen in der Variablen `$flashes` als Objekte `std
Fehler 404 usw. .[#toc-error-404-etc]
=====================================
-Wenn wir die Anfrage nicht erfüllen können, weil z.B. der Artikel, den wir anzeigen wollen, nicht in der Datenbank existiert, werden wir den Fehler 404 mit der Methode `error(string $message = null, int $httpCode = 404)` ausgeben, die den HTTP-Fehler 404 darstellt:
+Wenn wir die Anfrage nicht erfüllen können, weil z.B. der Artikel, den wir anzeigen wollen, nicht in der Datenbank existiert, werden wir den Fehler 404 mit der Methode `error(?string $message = null, int $httpCode = 404)` ausgeben, die den HTTP-Fehler 404 darstellt:
```php
public function renderShow(int $id): void
@@ -236,6 +236,32 @@ public function actionData(): void
```
+Parameter anfordern .[#toc-request-parameters]
+==============================================
+
+Der Präsentator, wie auch jede Komponente, bezieht seine Parameter aus der HTTP-Anfrage. Ihre Werte können mit der Methode `getParameter($name)` oder `getParameters()` abgerufen werden. Bei den Werten handelt es sich um Strings oder Arrays von Strings, also im Wesentlichen um Rohdaten, die direkt aus der URL bezogen werden.
+
+Für zusätzlichen Komfort empfiehlt es sich, die Parameter über Eigenschaften zugänglich zu machen. Beschriften Sie sie einfach mit dem `#[Parameter]` Attribut:
+
+```php
+use Nette\Application\Attributes\Parameter; // diese Zeile ist wichtig
+
+class HomePresenter extends Nette\Application\UI\Presenter
+{
+ #[Parameter]
+ public string $theme; // muss öffentlich sein
+}
+```
+
+Für Eigenschaften empfehlen wir die Angabe des Datentyps (z. B. `string`). Nette wird den Wert dann automatisch auf der Grundlage dieses Typs umwandeln. Parameterwerte können auch [validiert |#Validation of Parameters] werden.
+
+Beim Erstellen einer Verknüpfung können Sie den Wert für die Parameter direkt festlegen:
+
+```latte
+click
+```
+
+
Dauerhafte Parameter .[#toc-persistent-parameters]
==================================================
@@ -257,7 +283,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter
Wenn `$this->lang` einen Wert wie `'en'` hat, dann werden Links, die mit `link()` oder `n:href` erstellt werden, auch den Parameter `lang=en` enthalten. Und wenn der Link angeklickt wird, wird er wieder `$this->lang = 'en'` sein.
-Für Eigenschaften wird empfohlen, den Datentyp anzugeben (z. B. `string`), und Sie können auch einen Standardwert angeben. Parameterwerte können [validiert |#Validation of Persistent Parameters] werden.
+Für Eigenschaften wird empfohlen, den Datentyp anzugeben (z. B. `string`), und Sie können auch einen Standardwert angeben. Parameterwerte können [validiert |#Validation of Parameters] werden.
Persistente Parameter werden standardmäßig zwischen allen Aktionen eines bestimmten Präsentators weitergegeben. Um sie zwischen mehreren Präsentatoren zu übergeben, müssen Sie sie entweder definieren:
@@ -307,18 +333,12 @@ Tiefer gehen .[#toc-going-deeper]
Was wir bisher in diesem Kapitel gezeigt haben, wird wahrscheinlich ausreichen. Die folgenden Zeilen sind für diejenigen gedacht, die sich eingehend mit Moderatoren beschäftigen und alles wissen wollen.
-Anforderung und Parameter .[#toc-requirement-and-parameters]
-------------------------------------------------------------
-
-Die vom Präsentator bearbeitete Anfrage ist das Objekt [api:Nette\Application\Request] und wird von der Methode `getRequest()` des Präsentators zurückgegeben. Sie enthält ein Array von Parametern, und jeder von ihnen gehört entweder zu einer der Komponenten oder direkt zum Präsentator (der eigentlich auch eine Komponente ist, wenn auch eine spezielle). Nette verteilt also die Parameter um und übergibt sie zwischen den einzelnen Komponenten (und dem Präsentator) durch Aufruf der Methode `loadState(array $params)`. Die Parameter können mit der Methode `getParameters(): array`, einzeln mit `getParameter($name)` abgerufen werden. Bei den Parameterwerten handelt es sich um Strings oder Arrays von Strings, also im Grunde um Rohdaten, die direkt aus einer URL bezogen werden.
+Validierung von Parametern .[#toc-validation-of-parameters]
+-----------------------------------------------------------
+Die Werte von [Anfrageparametern |#request parameters] und [dauerhaften Parametern |#persistent parameters], die von URLs empfangen werden, werden von der Methode `loadState()` in Eigenschaften geschrieben. Sie prüft auch, ob der in der Eigenschaft angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt.
-Validierung von persistenten Parametern .[#toc-validation-of-persistent-parameters]
------------------------------------------------------------------------------------
-
-Die Werte von [persistenten Parametern |#persistent parameters], die von URLs empfangen werden, werden von der Methode `loadState()` in Eigenschaften geschrieben. Sie prüft auch, ob der in der Eigenschaft angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt.
-
-Verlassen Sie sich niemals blind auf persistente Parameter, da sie leicht vom Benutzer in der URL überschrieben werden können. So überprüfen wir zum Beispiel, ob `$this->lang` zu den unterstützten Sprachen gehört. Eine gute Möglichkeit, dies zu tun, besteht darin, die oben erwähnte Methode `loadState()` zu überschreiben:
+Verlassen Sie sich niemals blind auf Parameter, da sie leicht vom Benutzer in der URL überschrieben werden können. So überprüfen wir zum Beispiel, ob `$this->lang` zu den unterstützten Sprachen gehört. Eine gute Möglichkeit, dies zu tun, besteht darin, die oben erwähnte Methode `loadState()` zu überschreiben:
```php
class ProductPresenter extends Nette\Application\UI\Presenter
@@ -341,7 +361,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter
Speichern und Wiederherstellen der Anfrage .[#toc-save-and-restore-the-request]
-------------------------------------------------------------------------------
-Sie können die aktuelle Anfrage in einer Session speichern oder sie aus der Session wiederherstellen und den Präsentator sie erneut ausführen lassen. Dies ist z. B. nützlich, wenn ein Benutzer ein Formular ausfüllt und seine Anmeldung abläuft. Um keine Daten zu verlieren, speichern wir vor der Routing zur Anmeldeseite die aktuelle Anfrage in der Session mit `$reqId = $this->storeRequest()`, die einen Bezeichner in Form einer kurzen Zeichenkette zurückgibt und ihn als Parameter an den Anmeldepräsentator übergibt.
+Die Anfrage, die der Präsentator bearbeitet, ist ein Objekt [api:Nette\Application\Request] und wird von der Methode `getRequest()` des Präsentators zurückgegeben.
+
+Sie können die aktuelle Anfrage in einer Sitzung speichern oder sie aus der Sitzung wiederherstellen und den Präsentator sie erneut ausführen lassen. Dies ist z. B. nützlich, wenn ein Benutzer ein Formular ausfüllt und sein Login abläuft. Um keine Daten zu verlieren, speichern wir vor der Weiterleitung zur Anmeldeseite die aktuelle Anfrage in der Sitzung mit `$reqId = $this->storeRequest()`, die einen Bezeichner in Form eines kurzen Strings zurückgibt und diesen als Parameter an den Anmeldepräsentator übergibt.
Nach der Anmeldung rufen wir die Methode `$this->restoreRequest($reqId)` auf, die die Anfrage aus der Session abholt und an diese weiterleitet. Die Methode prüft, ob die Anfrage von demselben Benutzer erstellt wurde, der jetzt angemeldet ist. Wenn sich ein anderer Benutzer anmeldet oder der Schlüssel ungültig ist, tut sie nichts und das Programm läuft weiter.
@@ -362,7 +384,7 @@ Eine Umleitung findet bei einer AJAX- oder POST-Anfrage nicht statt, da dies zu
Sie können die Kanonisierung auch manuell mit der Methode `canonicalize()` aufrufen, die wie die Methode `link()` den Präsentator, Aktionen und Parameter als Argumente erhält. Sie erstellt einen Link und vergleicht ihn mit der aktuellen URL. Wenn sie sich unterscheidet, wird sie auf den erzeugten Link umgeleitet.
```php
-public function actionShow(int $id, string $slug = null): void
+public function actionShow(int $id, ?string $slug = null): void
{
$realSlug = $this->facade->getSlugForId($id);
// leitet um, wenn $slug nicht mit $realSlug übereinstimmt
@@ -425,6 +447,51 @@ $this->sendResponse(new Responses\CallbackResponse($callback));
```
+Zugangsbeschränkung mit `#[Requires]` .[#toc-access-restriction-using-requires]{data-version:3.2.2}
+---------------------------------------------------------------------------------------------------
+
+Das Attribut `#[Requires]` Attribut bietet erweiterte Optionen zur Einschränkung des Zugriffs auf Präsentatoren und ihre Methoden. Es kann verwendet werden, um HTTP-Methoden zu spezifizieren, AJAX-Anfragen zu verlangen, den Zugriff auf denselben Ursprung zu beschränken und den Zugriff nur auf Weiterleitungen zu beschränken. Das Attribut kann sowohl auf Presenter-Klassen als auch auf einzelne Methoden angewendet werden, z. B. `action()`, `render()`, `handle()`, und `createComponent()`.
+
+Sie können diese Einschränkungen angeben:
+- auf HTTP-Methoden: `#[Requires(methods: ['GET', 'POST'])]`
+- die eine AJAX-Anfrage erfordern: `#[Requires(ajax: true)]`
+- Zugriff nur vom selben Ursprung: `#[Requires(sameOrigin: true)]`
+- Zugriff nur über Weiterleitung: `#[Requires(forward: true)]`
+- Einschränkungen für bestimmte Aktionen: `#[Requires(actions: 'default')]`
+
+Für Einzelheiten siehe [Verwendung des Requires Attributs |best-practices:attribute-requires].
+
+
+HTTP-Methodenprüfung .[#toc-http-method-check]
+----------------------------------------------
+
+In Nette überprüfen die Moderatoren die HTTP-Methode jeder eingehenden Anfrage automatisch, hauptsächlich aus Sicherheitsgründen. Standardmäßig sind die Methoden `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` zugelassen.
+
+Wenn Sie zusätzliche Methoden wie `OPTIONS` aktivieren möchten, können Sie das `#[Requires]` Attribut verwenden (ab Nette Application v3.2):
+
+```php
+#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
+class MyPresenter extends Nette\Application\UI\Presenter
+{
+}
+```
+
+In Version 3.1 wird die Überprüfung in `checkHttpMethod()` durchgeführt, das prüft, ob die in der Anfrage angegebene Methode im Array `$presenter->allowedMethods` enthalten ist. Fügen Sie eine Methode wie diese hinzu:
+
+```php
+class MyPresenter extends Nette\Application\UI\Presenter
+{
+ protected function checkHttpMethod(): void
+ {
+ $this->allowedMethods[] = 'OPTIONS';
+ parent::checkHttpMethod();
+ }
+}
+```
+
+Es ist wichtig zu betonen, dass, wenn Sie die `OPTIONS` -Methode zulassen, Sie sie auch in Ihrem Präsentator richtig behandeln müssen. Diese Methode wird häufig als so genannte Preflight-Anfrage verwendet, die von Browsern automatisch vor der eigentlichen Anfrage gesendet wird, wenn es darum geht, festzustellen, ob die Anfrage aus Sicht der CORS-Richtlinie (Cross-Origin Resource Sharing) zulässig ist. Wenn Sie diese Methode zulassen, aber keine angemessene Antwort implementieren, kann dies zu Inkonsistenzen und potenziellen Sicherheitsproblemen führen.
+
+
Weitere Lektüre .[#toc-further-reading]
=======================================
diff --git a/application/de/routing.texy b/application/de/routing.texy
index dac42c271d..f3b4ae88de 100644
--- a/application/de/routing.texy
+++ b/application/de/routing.texy
@@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [
@@ -225,7 +225,7 @@ $router->addRoute('/[/]', [
]);
```
-Oder wir können diese Form verwenden, beachten Sie die Umschreibung des regulären Ausdrucks für die Validierung:
+Für eine detailliertere Spezifikation kann eine noch umfangreichere Form verwendet werden, bei der zusätzlich zu den Standardwerten weitere Parametereigenschaften festgelegt werden können, wie z. B. ein regulärer Ausdruck für die Validierung (siehe den Parameter `id` ):
```php
use Nette\Routing\Route;
@@ -243,7 +243,7 @@ $router->addRoute('/[/]', [
]);
```
-Diese gesprächigeren Formate sind nützlich, um andere Metadaten hinzuzufügen.
+Es ist wichtig zu beachten, dass, wenn die im Array definierten Parameter nicht in der Pfadmaske enthalten sind, ihre Werte nicht geändert werden können, auch nicht mit Abfrageparametern, die nach einem Fragezeichen in der URL angegeben werden.
Filter und Übersetzungen .[#toc-filters-and-translations]
@@ -477,10 +477,10 @@ $router->addRoute('index.html \.html?|\.php|>', /* ... */);
Einbindung .[#toc-integration]
==============================
-Um unseren Router in die Anwendung einzubinden, müssen wir ihn dem DI-Container mitteilen. Am einfachsten ist es, die Fabrik vorzubereiten, die das Router-Objekt erstellt, und der Container-Konfiguration mitzuteilen, dass sie es verwenden soll. Schreiben wir also eine Methode für diesen Zweck `App\Router\RouterFactory::createRouter()`:
+Um unseren Router in die Anwendung einzubinden, müssen wir ihn dem DI-Container mitteilen. Am einfachsten ist es, die Fabrik vorzubereiten, die das Router-Objekt erstellt, und der Container-Konfiguration mitzuteilen, dass sie es verwenden soll. Schreiben wir also eine Methode für diesen Zweck `App\Core\RouterFactory::createRouter()`:
```php
-namespace App\Router;
+namespace App\Core;
use Nette\Application\Routers\RouteList;
@@ -499,7 +499,7 @@ Dann schreiben wir in [configuration |dependency-injection:services]:
```neon
services:
- - App\Router\RouterFactory::createRouter
+ - App\Core\RouterFactory::createRouter
```
Alle Abhängigkeiten, wie z. B. eine Datenbankverbindung usw., werden der Factory-Methode als Parameter über [Autowiring |dependency-injection:autowiring] übergeben:
@@ -663,7 +663,7 @@ Unter getrennter Nutzung verstehen wir die Verwendung der Router-Funktionen in e
Wir werden also wieder eine Methode erstellen, die einen Router aufbaut, zum Beispiel:
```php
-namespace App\Router;
+namespace App\Core;
use Nette\Routing\RouteList;
@@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class);
Oder wir erstellen die Objekte direkt:
```php
-$router = App\Router\RouterFactory::createRouter();
+$router = App\Core\RouterFactory::createRouter();
$httpRequest = (new Nette\Http\RequestFactory)->fromGlobals();
```
diff --git a/application/de/templates.texy b/application/de/templates.texy
index a21bd7266f..466cb501c3 100644
--- a/application/de/templates.texy
+++ b/application/de/templates.texy
@@ -34,35 +34,81 @@ Und dies könnte die Aktionsvorlage sein:
Sie definiert den Block `content`, der anstelle von `{include content}` in das Layout eingefügt wird, und definiert auch den Block `title` neu, der `{block title}` im Layout überschreibt. Versuchen Sie, sich das Ergebnis vorzustellen.
-Suche nach Templates .[#toc-search-for-templates]
--------------------------------------------------
+Vorlage nachschlagen .[#toc-template-lookup]
+--------------------------------------------
+
+In Presentern müssen Sie nicht angeben, welche Vorlage gerendert werden soll; das Framework bestimmt den Pfad automatisch, was die Codierung für Sie einfacher macht.
+
+Wenn Sie eine Verzeichnisstruktur verwenden, in der jeder Präsentator sein eigenes Verzeichnis hat, legen Sie die Vorlage einfach in diesem Verzeichnis unter dem Namen der Aktion (d. h. der Ansicht) ab. Verwenden Sie zum Beispiel für die Aktion `default` die Vorlage `default.latte`:
-Der Pfad zu den Vorlagen wird nach einer einfachen Logik hergeleitet. Es wird versucht zu sehen, ob eine dieser Vorlagendateien relativ zu dem Verzeichnis existiert, in dem sich die Presenter-Klasse befindet, wobei `` der Name des aktuellen Präsentators ist und `` der Name der aktuellen Aktion ist:
+/--pre
+app/
+└── UI/
+ └── Home/
+ ├── HomePresenter.php
+ └── default.latte
+\--
-- `templates//.latte`
-- `templates/..latte`
+Wenn Sie eine Struktur verwenden, bei der sich die Präsentatoren in einem Verzeichnis und die Vorlagen in einem Ordner `templates` befinden, speichern Sie sie entweder in einer Datei `..latte` oder `/.latte`:
-Wird die Vorlage nicht gefunden, wird versucht, im Verzeichnis `templates` eine Ebene höher zu suchen, d. h. auf der gleichen Ebene wie das Verzeichnis mit der Presenter-Klasse.
+/--pre
+app/
+└── Presenters/
+ ├── HomePresenter.php
+ └── templates/
+ ├── Home.default.latte ← 1st variant
+ └── Home/
+ └── default.latte ← 2nd variant
+\--
-Wenn die Vorlage auch dort nicht gefunden wird, ist die Antwort ein [404-Fehler |presenters#Error 404 etc.].
+Das Verzeichnis `templates` kann auch eine Ebene höher platziert werden, auf derselben Ebene wie das Verzeichnis mit den Presenter-Klassen.
-Sie können die Ansicht auch mit `$this->setView('otherView')` ändern. Oder geben Sie statt der Suche direkt den Namen der Vorlagendatei mit `$this->template->setFile('/path/to/template.latte')` an.
+Wenn die Vorlage nicht gefunden wird, antwortet der Präsentator mit dem [Fehler 404 - Seite nicht gefunden |presenters#Error 404 etc].
+
+Sie können die Ansicht mit `$this->setView('anotherView')` ändern. Es ist auch möglich, die Vorlagendatei direkt mit `$this->template->setFile('/path/to/template.latte')` anzugeben.
.[note]
-Sie können die Pfade, in denen Vorlagen gesucht werden, ändern, indem Sie die Methode [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()] überschreiben, die ein Array mit möglichen Dateipfaden zurückgibt.
+Dateien, in denen Vorlagen gesucht werden, können durch Überschreiben der Methode [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] geändert werden, die ein Array mit möglichen Dateinamen zurückgibt.
+
+
+Layout-Vorlagen-Suche .[#toc-layout-template-lookup]
+----------------------------------------------------
+
+Nette sucht auch automatisch nach der Layout-Datei.
+
+Wenn Sie eine Verzeichnisstruktur verwenden, in der jeder Präsentator sein eigenes Verzeichnis hat, legen Sie das Layout entweder in dem Ordner mit dem Präsentator ab, wenn es nur für diesen spezifisch ist, oder eine Ebene höher, wenn es für mehrere Präsentatoren gemeinsam ist:
+
+/--pre
+app/
+└── UI/
+ ├── @layout.latte ← common layout
+ └── Home/
+ ├── @layout.latte ← only for Home presenter
+ ├── HomePresenter.php
+ └── default.latte
+\--
+
+Wenn Sie eine Struktur verwenden, bei der die Vortragenden in einem Verzeichnis zusammengefasst sind und sich die Vorlagen in einem Ordner `templates` befinden, wird das Layout an den folgenden Stellen erwartet:
-Das Layout wird in den folgenden Dateien erwartet:
+/--pre
+app/
+└── Presenters/
+ ├── HomePresenter.php
+ └── templates/
+ ├── @layout.latte ← common layout
+ ├── Home.@layout.latte ← only for Home, 1st variant
+ └── Home/
+ └── @layout.latte ← only for Home, 2nd variant
+\--
-- `templates//@.latte`
-- `templates/.@.latte`
-- `templates/@.latte` gemeinsames Layout für mehrere Präsentatoren
+Befindet sich der Präsentator in einem [Modul |modules], wird er auch weiter oben im Verzeichnisbaum entsprechend der Verschachtelung des Moduls gesucht.
-`` ist der Name des aktuellen Präsentators und `` ist der Name des Layouts, der standardmäßig `'layout'` lautet. Der Name kann mit `$this->setLayout('otherLayout')` geändert werden, so dass `@otherLayout.latte` Dateien ausprobiert werden.
+Der Name des Layouts kann mit `$this->setLayout('layoutAdmin')` geändert werden und wird dann in der Datei `@layoutAdmin.latte` erwartet. Sie können die Layout-Vorlagendatei auch direkt mit `$this->setLayout('/path/to/template.latte')` angeben.
-Sie können auch direkt den Dateinamen der Layoutvorlage mit `$this->setLayout('/path/to/template.latte')` angeben. Durch die Verwendung von `$this->setLayout(false)` wird die Layout-Suche deaktiviert.
+Die Verwendung von `$this->setLayout(false)` oder des Tags `{layout none}` innerhalb der Vorlage deaktiviert die Layout-Suche.
.[note]
-Sie können die Pfade, in denen Vorlagen gesucht werden, ändern, indem Sie die Methode [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] überschreiben, die ein Array mit möglichen Dateipfaden zurückgibt.
+Die Dateien, in denen Layoutvorlagen gesucht werden, können durch Überschreiben der Methode [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] geändert werden, die ein Array mit möglichen Dateinamen zurückgibt.
Variablen in der Vorlage .[#toc-variables-in-the-template]
@@ -104,7 +150,7 @@ Die `@property-read` Annotation ist für die IDE und die statische Analyse, sie
Sie können sich auch den Luxus gönnen, in Vorlagen zu flüstern. Installieren Sie einfach das Latte-Plugin in PhpStorm und geben Sie den Klassennamen am Anfang der Vorlage an, siehe den Artikel "Latte: how to type system":https://blog.nette.org/de/latte-wie-benutzt-man-das-typensystem:
```latte
-{templateType App\Presenters\ArticleTemplate}
+{templateType App\UI\Article\ArticleTemplate}
...
```
@@ -176,7 +222,7 @@ public function beforeRender(): void
Latte Version 3 bietet einen fortgeschritteneren Weg, indem es eine [Erweiterung |latte:creating-extension] für jedes Webprojekt erstellt. Hier ist ein grobes Beispiel für eine solche Klasse:
```php
-namespace App\Templating;
+namespace App\UI\Accessory;
final class LatteExtension extends Latte\Extension
{
@@ -214,7 +260,7 @@ Wir registrieren sie mit [configuration |configuration#Latte]:
```neon
latte:
extensions:
- - App\Templating\LatteExtension
+ - App\UI\Accessory\LatteExtension
```
@@ -239,7 +285,7 @@ Alternativ kann der Übersetzer auch über die [Konfiguration |configuration#Lat
```neon
latte:
extensions:
- - Latte\Essential\TranslatorExtension
+ - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```
Der Übersetzer kann dann z.B. als Filter `|translate` verwendet werden, wobei zusätzliche Parameter an die Methode `translate()` übergeben werden (siehe `foo, bar`):
diff --git a/application/el/@home.texy b/application/el/@home.texy
index 8020e59b5f..9a733fd102 100644
--- a/application/el/@home.texy
+++ b/application/el/@home.texy
@@ -28,8 +28,9 @@ composer require nette/application
| έκδοση | συμβατό με PHP
|-----------|-------------------
-| Εφαρμογή Nette 4.0 | PHP 8.0 - 8.2
-| Nette Application 3.1 | PHP 7.2 - 8.2
+| Nette Application 4.0 | PHP 8.1 – 8.3
+| Nette Application 3.2 | PHP 8.1 – 8.3
+| 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/el/ajax.texy b/application/el/ajax.texy
index 8ca0741a48..ab0e2f48f5 100644
--- a/application/el/ajax.texy
+++ b/application/el/ajax.texy
@@ -3,10 +3,10 @@ AJAX & Snippets
-Οι σύγχρονες διαδικτυακές εφαρμογές τρέχουν σήμερα κατά το ήμισυ σε έναν διακομιστή και κατά το ήμισυ σε ένα πρόγραμμα περιήγησης. Το AJAX είναι ένας ζωτικής σημασίας ενωτικός παράγοντας. Τι υποστήριξη προσφέρει το Nette Framework;
-- αποστολή τμημάτων προτύπου (τα λεγόμενα *snippets*)
-- μεταβίβαση μεταβλητών μεταξύ PHP και JavaScript
-- αποσφαλμάτωση εφαρμογών AJAX
+Στην εποχή των σύγχρονων διαδικτυακών εφαρμογών, όπου η λειτουργικότητα συχνά εκτείνεται μεταξύ του διακομιστή και του προγράμματος περιήγησης, το AJAX είναι ένα απαραίτητο συνδετικό στοιχείο. Ποιες επιλογές προσφέρει το Nette Framework σε αυτόν τον τομέα;
+- αποστολή τμημάτων του προτύπου, των λεγόμενων αποσπασμάτων
+- διαβίβαση μεταβλητών μεταξύ PHP και JavaScript
+- εργαλεία για την αποσφαλμάτωση αιτημάτων AJAX
@@ -14,29 +14,32 @@ AJAX & Snippets
Αίτηση AJAX .[#toc-ajax-request]
================================
-Ένα αίτημα AJAX δεν διαφέρει από ένα κλασικό αίτημα - ο παρουσιαστής καλείται με μια συγκεκριμένη προβολή και παραμέτρους. Εξαρτάται επίσης από τον παρουσιαστή πώς θα απαντήσει σε αυτό: μπορεί να χρησιμοποιήσει τη δική του ρουτίνα, η οποία επιστρέφει ένα τμήμα κώδικα HTML (απόσπασμα HTML), ένα έγγραφο XML, ένα αντικείμενο JSON ή κώδικα JavaScript.
+Ένα αίτημα AJAX δεν διαφέρει ουσιαστικά από ένα κλασικό αίτημα HTTP. Ένας παρουσιαστής καλείται με συγκεκριμένες παραμέτρους. Από τον παρουσιαστή εξαρτάται πώς θα απαντήσει στο αίτημα - μπορεί να επιστρέψει δεδομένα σε μορφή JSON, να στείλει ένα τμήμα κώδικα HTML, ένα έγγραφο XML κ.λπ.
-Από την πλευρά του διακομιστή, ένα αίτημα AJAX μπορεί να ανιχνευθεί χρησιμοποιώντας τη μέθοδο service που [ενθυλακώνει το αίτημα HTTP |http:request] `$httpRequest->isAjax()` (ανιχνεύει με βάση την επικεφαλίδα HTTP `X-Requested-With`). Στο εσωτερικό του παρουσιαστή, είναι διαθέσιμη μια συντόμευση με τη μορφή της μεθόδου `$this->isAjax()`.
+Από την πλευρά του προγράμματος περιήγησης, ξεκινάμε ένα αίτημα AJAX χρησιμοποιώντας τη συνάρτηση `fetch()`:
-Υπάρχει ένα προεπεξεργασμένο αντικείμενο που ονομάζεται `payload` και είναι αφιερωμένο στην αποστολή δεδομένων στο πρόγραμμα περιήγησης σε JSON.
-
-```php
-public function actionDelete(int $id): void
-{
- if ($this->isAjax()) {
- $this->payload->message = 'Success';
- }
- // ...
-}
+```js
+fetch(url, {
+ headers: {'X-Requested-With': 'XMLHttpRequest'},
+})
+.then(response => response.json())
+.then(payload => {
+ // επεξεργασία της απάντησης
+});
```
-Για πλήρη έλεγχο της εξόδου JSON χρησιμοποιήστε τη μέθοδο `sendJson` στον παρουσιαστή σας. Τερματίζει αμέσως τον presenter και θα κάνετε χωρίς πρότυπο:
+Στην πλευρά του διακομιστή, ένα αίτημα AJAX αναγνωρίζεται από τη μέθοδο `$httpRequest->isAjax()` της υπηρεσίας που [ενθυλακώνει το αίτημα HTTP |http:request]. Χρησιμοποιεί την επικεφαλίδα HTTP `X-Requested-With`, οπότε είναι απαραίτητη η αποστολή της. Μέσα στον παρουσιαστή, μπορείτε να χρησιμοποιήσετε τη μέθοδο `$this->isAjax()`.
+
+Εάν θέλετε να στείλετε δεδομένα σε μορφή JSON, χρησιμοποιήστε τη μέθοδο [`sendJson()` |presenters#Sending a response] μέθοδο. Η μέθοδος τερματίζει επίσης τη δραστηριότητα του παρουσιαστή.
```php
-$this->sendJson(['key' => 'value', /* ... */]);
+public function actionExport(): void
+{
+ $this->sendJson($this->model->getData);
+}
```
-Εάν θέλουμε να στείλουμε HTML, μπορούμε είτε να ορίσουμε ένα ειδικό πρότυπο για αιτήσεις AJAX:
+Αν σκοπεύετε να απαντήσετε με ένα ειδικό πρότυπο σχεδιασμένο για AJAX, μπορείτε να το κάνετε ως εξής:
```php
public function handleClick($param): void
@@ -44,27 +47,43 @@ public function handleClick($param): void
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
- // ...
+ //...
}
```
+Αποσπάσματα .[#toc-snippets]
+============================
+
+Το πιο ισχυρό εργαλείο που προσφέρει η Nette για τη σύνδεση του διακομιστή με τον πελάτη είναι τα snippets. Με αυτά μπορείτε να μετατρέψετε μια συνηθισμένη εφαρμογή σε AJAX με ελάχιστη προσπάθεια και λίγες γραμμές κώδικα. Το παράδειγμα Fifteen δείχνει πώς λειτουργούν όλα αυτά, και ο κώδικάς του μπορεί να βρεθεί στο [GitHub |https://github.com/nette-examples/fifteen].
+
+Τα αποσπάσματα, ή αποκόμματα, σας επιτρέπουν να ενημερώνετε μόνο τμήματα της σελίδας, αντί να επαναφορτώνετε ολόκληρη τη σελίδα. Αυτό είναι πιο γρήγορο και αποτελεσματικό, και παρέχει επίσης μια πιο άνετη εμπειρία χρήσης. Τα αποσπάσματα μπορεί να σας θυμίζουν το Hotwire για το Ruby on Rails ή το Symfony UX Turbo. Είναι ενδιαφέρον ότι η Nette εισήγαγε τα snippets 14 χρόνια νωρίτερα.
+
+Πώς λειτουργούν τα snippets; Όταν φορτώνεται για πρώτη φορά η σελίδα (μια αίτηση χωρίς-AJAX), φορτώνεται ολόκληρη η σελίδα, συμπεριλαμβανομένων όλων των snippets. Όταν ο χρήστης αλληλεπιδρά με τη σελίδα (π.χ. κάνει κλικ σε ένα κουμπί, υποβάλλει μια φόρμα κ.λπ.), αντί να φορτωθεί ολόκληρη η σελίδα, γίνεται ένα αίτημα AJAX. Ο κώδικας στον παρουσιαστή εκτελεί την ενέργεια και αποφασίζει ποια αποσπάσματα χρειάζονται ενημέρωση. Η Nette αποδίδει αυτά τα αποσπάσματα και τα αποστέλλει με τη μορφή ενός πίνακα JSON. Ο κώδικας χειρισμού στο πρόγραμμα περιήγησης εισάγει στη συνέχεια τα ληφθέντα αποσπάσματα πίσω στη σελίδα. Επομένως, μεταφέρεται μόνο ο κώδικας των αλλαγμένων αποσπασμάτων, εξοικονομώντας εύρος ζώνης και επιταχύνοντας τη φόρτωση σε σύγκριση με τη μεταφορά ολόκληρου του περιεχομένου της σελίδας.
+
+
Naja .[#toc-naja]
-=================
+-----------------
-Η [βιβλιοθήκη Naja |https://naja.js.org] χρησιμοποιείται για το χειρισμό αιτημάτων AJAX στην πλευρά του προγράμματος περιήγησης. [Εγκαταστήστε |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
```
-...ή να την εισαγάγετε απευθείας στο πρότυπο της σελίδας:
+... ή να την εισαγάγετε απευθείας στο πρότυπο της σελίδας:
```html
```
-Για να δημιουργήσετε μια αίτηση AJAX από έναν κανονικό σύνδεσμο (σήμα) ή μια υποβολή φόρμας, απλά επισημάνετε τον σχετικό σύνδεσμο, τη φόρμα ή το κουμπί με την κλάση `ajax`:
+Πρώτα πρέπει να [αρχικοποιήσετε |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] τη βιβλιοθήκη:
+
+```js
+naja.initialize();
+```
+
+Για να μετατρέψετε έναν συνηθισμένο σύνδεσμο (σήμα) ή την υποβολή φόρμας σε αίτηση AJAX, απλά σημειώστε τον αντίστοιχο σύνδεσμο, φόρμα ή κουμπί με την κλάση `ajax`:
```html
Go
@@ -74,64 +93,39 @@ npm install naja
or
+
```
-Snippets .[#toc-snippets]
-=========================
-
-Υπάρχει ένα πολύ πιο ισχυρό εργαλείο ενσωματωμένης υποστήριξης AJAX - τα αποσπάσματα. Η χρήση τους καθιστά δυνατή τη μετατροπή μιας κανονικής εφαρμογής σε εφαρμογή AJAX χρησιμοποιώντας μόνο μερικές γραμμές κώδικα. Το πώς λειτουργούν όλα αυτά παρουσιάζεται στο παράδειγμα Fifteen του οποίου ο κώδικας είναι επίσης προσβάσιμος στο build ή στο [GitHub |https://github.com/nette-examples/fifteen].
-
-Ο τρόπος που λειτουργούν τα snippets είναι ότι ολόκληρη η σελίδα μεταφέρεται κατά το αρχικό (δηλαδή μη-AJAX) αίτημα και στη συνέχεια με κάθε AJAX [υποερώτημα |components#signal] (αίτημα της ίδιας προβολής του ίδιου παρουσιαστή) μεταφέρεται μόνο ο κώδικας των αλλαγμένων τμημάτων στο αποθετήριο `payload` που αναφέρθηκε προηγουμένως.
-
-Τα Snippets μπορεί να σας θυμίζουν το Hotwire για το Ruby on Rails ή το Symfony UX Turbo, αλλά η Nette τα επινόησε δεκατέσσερα χρόνια νωρίτερα.
-
+Επανασχεδίαση αποσπασμάτων .[#toc-redrawing-snippets]
+-----------------------------------------------------
-Ακύρωση των Snippets .[#toc-invalidation-of-snippets]
-=====================================================
-
-Κάθε απόγονος της κλάσης [Control |components] (που είναι και ένας Παρουσιαστής) είναι σε θέση να θυμάται αν υπήρξαν αλλαγές κατά τη διάρκεια μιας αίτησης που απαιτούν την εκ νέου εμφάνιση. Υπάρχει ένα ζευγάρι μεθόδων για το χειρισμό αυτό: `redrawControl()` και `isControlInvalid()`. Ένα παράδειγμα:
+Κάθε αντικείμενο της κλάσης [Control |components] (συμπεριλαμβανομένου και του ίδιου του Presenter) διατηρεί αρχείο για το αν έχουν συμβεί αλλαγές που καθιστούν αναγκαία την επανασχεδίασή του. Για το σκοπό αυτό χρησιμοποιείται η μέθοδος `redrawControl()`.
```php
public function handleLogin(string $user): void
{
- // Το αντικείμενο πρέπει να αναδημιουργηθεί εκ νέου μετά τη σύνδεση του χρήστη.
+ // μετά τη σύνδεση, είναι απαραίτητο να σχεδιάσετε εκ νέου το σχετικό τμήμα
$this->redrawControl();
- // ...
+ //...
}
```
-Η Nette ωστόσο προσφέρει μια ακόμη πιο λεπτή ανάλυση από ολόκληρα στοιχεία. Οι αναφερόμενες μέθοδοι δέχονται το όνομα ενός λεγόμενου "αποσπάσματος" ως προαιρετική παράμετρο. Ένα "απόσπασμα" είναι ουσιαστικά ένα στοιχείο στο πρότυπό σας που επισημαίνεται για το σκοπό αυτό με μια ετικέτα Latte, περισσότερα γι' αυτό αργότερα. Έτσι είναι δυνατόν να ζητήσετε από ένα στοιχείο να ξανασχεδιάσει μόνο *μέρη* του προτύπου του. Εάν ακυρωθεί ολόκληρο το συστατικό, τότε όλα τα αποσπάσματά του αναδημιουργούνται εκ νέου. Ένα συστατικό είναι "άκυρο" επίσης εάν οποιοδήποτε από τα υποσυστήματά του είναι άκυρο.
-
-```php
-$this->isControlInvalid(); // -> false
-$this->redrawControl('header'); // ακυρώνει το απόσπασμα με το όνομα 'header'
-$this->isControlInvalid('header'); // -> true
-$this->isControlInvalid('footer'); // -> false
-$this->isControlInvalid(); // -> true, τουλάχιστον ένα απόσπασμα είναι άκυρο
+Η Nette επιτρέπει επίσης έναν πιο λεπτομερή έλεγχο του τι χρειάζεται επανασχεδίαση. Η προαναφερθείσα μέθοδος μπορεί να λάβει το όνομα του αποσπάσματος ως όρισμα. Έτσι, είναι δυνατή η ακύρωση (που σημαίνει: εξαναγκασμός σε επανασχεδίαση) σε επίπεδο τμήματος προτύπου. Εάν ακυρωθεί ολόκληρο το συστατικό, κάθε απόσπασμα αυτού επανασχεδιάζεται επίσης:
-$this->redrawControl(); // ακυρώνει ολόκληρο το συστατικό, κάθε απόσπασμα
-$this->isControlInvalid('footer'); // -> true
+```php
+// ακυρώνει το απόσπασμα 'header'
+$this->redrawControl('header');
```
-Ένα συστατικό που λαμβάνει σήμα επισημαίνεται αυτόματα για επανασχεδίαση.
-
-Χάρη στην επανασχεδίαση αποσπασμάτων γνωρίζουμε επακριβώς ποια τμήματα ποιων στοιχείων πρέπει να επανασχεδιαστούν.
-
-
-Ετικέτα `{snippet} … {/snippet}` .{toc: Tag snippet}
-====================================================
-
-Η απόδοση της σελίδας εξελίσσεται πολύ παρόμοια με μια κανονική αίτηση: φορτώνονται τα ίδια πρότυπα κ.λπ. Το ζωτικής σημασίας μέρος είναι, ωστόσο, να παραλείπονται τα μέρη που δεν πρέπει να φτάσουν στην έξοδο- τα υπόλοιπα μέρη πρέπει να συσχετίζονται με ένα αναγνωριστικό και να αποστέλλονται στο χρήστη σε κατανοητή μορφή για έναν χειριστή JavaScript.
-
-Σύνταξη .[#toc-syntax]
-----------------------
+Αποσπάσματα σε Latte .[#toc-snippets-in-latte]
+----------------------------------------------
-Εάν υπάρχει ένα στοιχείο ελέγχου ή ένα απόσπασμα στο πρότυπο, πρέπει να το τυλίξουμε χρησιμοποιώντας την ετικέτα `{snippet} ... {/snippet}` pair - θα διασφαλίσει ότι το αποδιδόμενο απόσπασμα θα "αποκοπεί" και θα σταλεί στο πρόγραμμα περιήγησης. Θα το περικλείσει επίσης σε ένα βοηθητικό `` tag (είναι δυνατόν να χρησιμοποιηθεί ένα διαφορετικό). Στο ακόλουθο παράδειγμα ορίζεται ένα απόσπασμα με το όνομα `header`. Μπορεί κάλλιστα να αντιπροσωπεύει το πρότυπο ενός στοιχείου:
+Η χρήση αποσπασμάτων στο Latte είναι εξαιρετικά εύκολη. Για να ορίσετε ένα μέρος του προτύπου ως snippet, απλά τυλίξτε το σε ετικέτες `{snippet}` και `{/snippet}`:
```latte
{snippet header}
@@ -139,7 +133,9 @@ $this->isControlInvalid('footer'); // -> true
{/snippet}
```
-Ένα απόσπασμα άλλου τύπου από το `
` ή ένα απόσπασμα με πρόσθετα χαρακτηριστικά HTML επιτυγχάνεται με τη χρήση της παραλλαγής χαρακτηριστικών:
+Το απόσπασμα δημιουργεί ένα στοιχείο `
` στη σελίδα HTML με ένα ειδικά δημιουργημένο `id`. Κατά την επανασχεδίαση ενός αποσπάσματος, το περιεχόμενο αυτού του στοιχείου ενημερώνεται. Επομένως, κατά την αρχική απόδοση της σελίδας, όλα τα snippets πρέπει επίσης να αποδοθούν, ακόμη και αν μπορεί αρχικά να είναι κενά.
+
+Μπορείτε επίσης να δημιουργήσετε ένα απόσπασμα με ένα στοιχείο διαφορετικό από το `
` χρησιμοποιώντας ένα χαρακτηριστικό n:attribute:
```latte
@@ -148,138 +144,106 @@ $this->isControlInvalid('footer'); // -> true
```
-Δυναμικά αποσπάσματα .[#toc-dynamic-snippets]
-=============================================
+Περιοχές αποσπασμάτων .[#toc-snippet-areas]
+-------------------------------------------
-Στο Nette μπορείτε επίσης να ορίσετε αποσπάσματα με δυναμικό όνομα βάσει μιας παραμέτρου εκτέλεσης. Αυτό είναι πιο κατάλληλο για διάφορες λίστες όπου πρέπει να αλλάξουμε μόνο μια γραμμή αλλά δεν θέλουμε να μεταφέρουμε ολόκληρη τη λίστα μαζί με αυτήν. Ένα τέτοιο παράδειγμα θα ήταν το εξής:
+Τα ονόματα αποσπασμάτων μπορούν επίσης να είναι εκφράσεις:
```latte
-
- {foreach $list as $id => $item}
- - {$item} update
- {/foreach}
-
+{foreach $items as $id => $item}
+ {$item}
+{/foreach}
```
-Υπάρχει ένα στατικό απόσπασμα που ονομάζεται `itemsContainer`, το οποίο περιέχει διάφορα δυναμικά αποσπάσματα: `item-0`, `item-1` κ.ο.κ.
+ `item-0`, `item-1`, κ.λπ. Αν ακυρώναμε άμεσα ένα δυναμικό απόσπασμα (π.χ. `item-1`), τίποτα δεν θα ξανασχεδιαζόταν. Ο λόγος είναι ότι τα αποσπάσματα λειτουργούν ως αληθινά αποσπάσματα και μόνο τα ίδια αποδίδονται άμεσα. Ωστόσο, στο πρότυπο, δεν υπάρχει τεχνικά ένα απόσπασμα με το όνομα `item-1`. Εμφανίζεται μόνο όταν εκτελείται ο περιβάλλων κώδικας του αποσπάσματος, στην προκειμένη περίπτωση, ο βρόχος foreach. Ως εκ τούτου, θα επισημάνουμε το τμήμα του προτύπου που πρέπει να εκτελεστεί με την ετικέτα `{snippetArea}`:
-Δεν μπορείτε να ξανασχεδιάσετε άμεσα ένα δυναμικό απόσπασμα (η επανασχεδίαση του `item-1` δεν έχει κανένα αποτέλεσμα), πρέπει να ξανασχεδιάσετε το γονικό του απόσπασμα (σε αυτό το παράδειγμα `itemsContainer`). Αυτό προκαλεί την εκτέλεση του κώδικα του γονικού αποσπάσματος, αλλά στη συνέχεια αποστέλλονται στο πρόγραμμα περιήγησης μόνο τα επιμέρους αποσπάσματά του. Αν θέλετε να στείλετε μόνο ένα από τα υπο-στοιχεία, πρέπει να τροποποιήσετε την είσοδο για το γονικό απόσπασμα ώστε να μην παράγει τα άλλα υπο-στοιχεία.
+```latte
+
+ {foreach $items as $id => $item}
+ - {$item}
+ {/foreach}
+
+```
-Στο παραπάνω παράδειγμα πρέπει να βεβαιωθείτε ότι για μια αίτηση AJAX θα προστεθεί μόνο ένα στοιχείο στον πίνακα `$list`, επομένως ο βρόχος `foreach` θα εκτυπώσει μόνο ένα δυναμικό απόσπασμα.
+Και θα ξανασχεδιάσουμε τόσο το μεμονωμένο απόσπασμα όσο και ολόκληρη την υπερκείμενη περιοχή:
```php
-class HomePresenter extends Nette\Application\UI\Presenter
-{
- /**
- * This method returns data for the list.
- * Usually this would just request the data from a model.
- * For the purpose of this example, the data is hard-coded.
- */
- private function getTheWholeList(): array
- {
- return [
- 'First',
- 'Second',
- 'Third',
- ];
- }
-
- public function renderDefault(): void
- {
- if (!isset($this->template->list)) {
- $this->template->list = $this->getTheWholeList();
- }
- }
-
- public function handleUpdate(int $id): void
- {
- $this->template->list = $this->isAjax()
- ? []
- : $this->getTheWholeList();
- $this->template->list[$id] = 'Updated item';
- $this->redrawControl('itemsContainer');
- }
-}
+$this->redrawControl('itemsContainer');
+$this->redrawControl('item-1');
```
+Είναι επίσης σημαντικό να διασφαλίσουμε ότι ο πίνακας `$items` περιέχει μόνο τα στοιχεία που πρέπει να επανασχεδιαστούν.
-Αποσπάσματα σε συμπεριλαμβανόμενο πρότυπο .[#toc-snippets-in-an-included-template]
-==================================================================================
-
-Μπορεί να συμβεί το απόσπασμα να βρίσκεται σε ένα πρότυπο το οποίο συμπεριλαμβάνεται από ένα διαφορετικό πρότυπο. Σε αυτή την περίπτωση πρέπει να τυλίξουμε τον κώδικα συμπερίληψης στο δεύτερο πρότυπο με την ετικέτα `snippetArea`, και στη συνέχεια να ξανασχεδιάσουμε τόσο το snippetArea όσο και το πραγματικό απόσπασμα.
-
-Η ετικέτα `snippetArea` διασφαλίζει ότι ο κώδικας στο εσωτερικό της εκτελείται, αλλά μόνο το πραγματικό απόσπασμα στο συμπεριλαμβανόμενο πρότυπο αποστέλλεται στο πρόγραμμα περιήγησης.
+Κατά την εισαγωγή ενός άλλου προτύπου στο κύριο με τη χρήση της ετικέτας `{include}`, το οποίο έχει αποσπάσματα, είναι απαραίτητο να τυλίξετε και πάλι το συμπεριλαμβανόμενο πρότυπο σε ένα `snippetArea` και να ακυρώσετε τόσο το απόσπασμα όσο και την περιοχή μαζί:
```latte
-{* parent.latte *}
-{snippetArea wrapper}
- {include 'child.latte'}
+{snippetArea include}
+ {include 'included.latte'}
{/snippetArea}
```
+
```latte
-{* child.latte *}
+{* included.latte *}
{snippet item}
-...
+ ...
{/snippet}
```
+
```php
-$this->redrawControl('wrapper');
+$this->redrawControl('include');
$this->redrawControl('item');
```
-Μπορείτε επίσης να το συνδυάσετε με δυναμικά αποσπάσματα.
+Αποσπάσματα σε στοιχεία .[#toc-snippets-in-components]
+------------------------------------------------------
-Προσθήκη και διαγραφή .[#toc-adding-and-deleting]
-=================================================
-
-Εάν προσθέσετε ένα νέο στοιχείο στη λίστα και ακυρώσετε το `itemsContainer`, η αίτηση AJAX επιστρέφει αποσπάσματα που περιλαμβάνουν το νέο στοιχείο, αλλά ο χειριστής javascript δεν θα είναι σε θέση να το αποδώσει. Αυτό συμβαίνει επειδή δεν υπάρχει κανένα στοιχείο HTML με το νεοδημιουργηθέν ID.
-
-Σε αυτή την περίπτωση, ο απλούστερος τρόπος είναι να τυλίξετε ολόκληρη τη λίστα σε ένα ακόμη απόσπασμα και να τα ακυρώσετε όλα:
+Μπορείτε να δημιουργήσετε αποσπάσματα μέσα σε [στοιχεία |components] και η Nette θα τα ανασχεδιάσει αυτόματα. Ωστόσο, υπάρχει ένας συγκεκριμένος περιορισμός: για να ξανασχεδιάσει αποσπάσματα, καλεί τη μέθοδο `render()` χωρίς καμία παράμετρο. Έτσι, η μετάδοση παραμέτρων στο πρότυπο δεν θα λειτουργήσει:
```latte
-{snippet wholeList}
-
- {foreach $list as $id => $item}
- - {$item} update
- {/foreach}
-
-{/snippet}
-Add
+OK
+{control productGrid}
+
+will not work:
+{control productGrid $arg, $arg}
+{control productGrid:paginator}
```
+
+Αποστολή δεδομένων χρήστη .[#toc-sending-user-data]
+---------------------------------------------------
+
+Μαζί με τα αποσπάσματα, μπορείτε να στείλετε οποιαδήποτε πρόσθετα δεδομένα στον πελάτη. Απλά γράψτε τα στο αντικείμενο `payload`:
+
```php
-public function handleAdd(): void
+public function actionDelete(int $id): void
{
- $this->template->list = $this->getTheWholeList();
- $this->template->list[] = 'New one';
- $this->redrawControl('wholeList');
+ //...
+ if ($this->isAjax()) {
+ $this->payload->message = 'Success';
+ }
}
```
-Το ίδιο ισχύει και για τη διαγραφή ενός στοιχείου. Θα ήταν δυνατό να στείλετε κενό snippet, αλλά συνήθως οι λίστες μπορούν να είναι σελιδοποιημένες και θα ήταν περίπλοκο να υλοποιήσετε τη διαγραφή ενός στοιχείου και τη φόρτωση ενός άλλου (το οποίο βρισκόταν σε διαφορετική σελίδα της σελιδοποιημένης λίστας).
-
-Αποστολή παραμέτρων στο συστατικό .[#toc-sending-parameters-to-component]
-=========================================================================
+Παράμετροι αποστολής .[#toc-sending-parameters]
+===============================================
Όταν στέλνουμε παραμέτρους στο στοιχείο μέσω αίτησης AJAX, είτε πρόκειται για παραμέτρους σήματος είτε για μόνιμες παραμέτρους, πρέπει να παρέχουμε το συνολικό τους όνομα, το οποίο περιέχει επίσης το όνομα του στοιχείου. Το πλήρες όνομα της παραμέτρου επιστρέφει η μέθοδος `getParameterId()`.
```js
-$.getJSON(
- {link changeCountBasket!},
- {
- {$control->getParameterId('id')}: id,
- {$control->getParameterId('count')}: count
- }
-});
+let url = new URL({link //foo!});
+url.searchParams.set({$control->getParameterId('bar')}, bar);
+
+fetch(url, {
+ headers: {'X-Requested-With': 'XMLHttpRequest'},
+})
```
-Και χειρίζεται τη μέθοδο με s αντίστοιχες παραμέτρους στο συστατικό.
+Μια μέθοδος χειρισμού με τις αντίστοιχες παραμέτρους στο συστατικό:
```php
-public function handleChangeCountBasket(int $id, int $count): void
+public function handleFoo(int $bar): void
{
-
}
```
diff --git a/application/el/bootstrap.texy b/application/el/bootstrap.texy
index 71f6028048..f71018bac5 100644
--- a/application/el/bootstrap.texy
+++ b/application/el/bootstrap.texy
@@ -20,18 +20,44 @@ use Nette\Bootstrap\Configurator;
class Bootstrap
{
- public static function boot(): Configurator
+ private Configurator $configurator;
+ private string $rootDir;
+
+ public function __construct()
+ {
+ $this->rootDir = dirname(__DIR__);
+ // Ο διαμορφωτής είναι υπεύθυνος για τη ρύθμιση του περιβάλλοντος και των υπηρεσιών της εφαρμογής.
+ $this->configurator = new Configurator;
+ // Ορίστε τον κατάλογο για τα προσωρινά αρχεία που παράγονται από τη Nette (π.χ. μεταγλωττισμένα πρότυπα)
+ $this->configurator->setTempDirectory($this->rootDir . '/temp');
+ }
+
+ public function bootWebApplication(): Nette\DI\Container
{
- $appDir = dirname(__DIR__);
- $configurator = new Configurator;
- //$configurator->setDebugMode('secret@23.75.345.200');
- $configurator->enableTracy($appDir . '/log');
- $configurator->setTempDirectory($appDir . '/temp');
- $configurator->createRobotLoader()
+ $this->initializeEnvironment();
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+ }
+
+ private function initializeEnvironment(): void
+ {
+ // Η Nette είναι έξυπνη και η λειτουργία ανάπτυξης ενεργοποιείται αυτόματα,
+ // ή μπορείτε να την ενεργοποιήσετε για μια συγκεκριμένη διεύθυνση IP ξεσχολιάζοντας την ακόλουθη γραμμή:
+ // $this->configurator->setDebugMode('secret@23.75.345.200'),
+
+ // Ενεργοποιεί το Tracy: το απόλυτο εργαλείο αποσφαλμάτωσης "ελβετικό μαχαίρι του στρατού".
+ $this->configurator->enableTracy($this->rootDir . '/log');
+
+ // RobotLoader: αυτόματη φόρτωση όλων των κλάσεων στον δεδομένο κατάλογο
+ $this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
- $configurator->addConfig($appDir . '/config/common.neon');
- return $configurator;
+ }
+
+ private function setupContainer(): void
+ {
+ // Φόρτωση αρχείων διαμόρφωσης
+ $this->configurator->addConfig($this->rootDir . '/config/common.neon');
}
}
```
@@ -40,16 +66,15 @@ class Bootstrap
index.php .[#toc-index-php]
===========================
-Στην περίπτωση των διαδικτυακών εφαρμογών, το αρχικό αρχείο είναι το `index.php`, το οποίο βρίσκεται στον δημόσιο κατάλογο `www/`. Επιτρέπει στην κλάση `Bootstrap` να αρχικοποιήσει το περιβάλλον και να επιστρέψει το `$configurator` που δημιουργεί το DI container. Στη συνέχεια αποκτά την υπηρεσία `Application`, η οποία εκτελεί την εφαρμογή ιστού:
+Το αρχικό αρχείο για τις διαδικτυακές εφαρμογές είναι το `index.php`, το οποίο βρίσκεται στον δημόσιο κατάλογο `www/`. Χρησιμοποιεί την κλάση `Bootstrap` για την αρχικοποίηση του περιβάλλοντος και τη δημιουργία ενός δοχείου DI. Στη συνέχεια, λαμβάνει την υπηρεσία `Application` από το δοχείο, η οποία εκκινεί την εφαρμογή ιστού:
```php
-// αρχικοποίηση του περιβάλλοντος + λήψη του αντικειμένου Configurator
-$configurator = App\Bootstrap::boot();
-// Δημιουργία ενός δοχείου DI
-$container = $configurator->createContainer();
+$bootstrap = new App\Bootstrap;
+// Αρχικοποίηση του περιβάλλοντος + δημιουργία ενός δοχείου DI
+$container = $bootstrap->bootWebApplication();
// Το δοχείο DI δημιουργεί ένα αντικείμενο Nette\Application\Application
$application = $container->getByType(Nette\Application\Application::class);
-// έναρξη της εφαρμογής Nette
+// Εκκίνηση της εφαρμογής Nette και χειρισμός της εισερχόμενης αίτησης
$application->run();
```
@@ -66,19 +91,19 @@ $application->run();
Αν θέλετε να ενεργοποιήσετε τη λειτουργία ανάπτυξης σε άλλες περιπτώσεις, για παράδειγμα, για προγραμματιστές που έχουν πρόσβαση από μια συγκεκριμένη διεύθυνση IP, μπορείτε να χρησιμοποιήσετε τη διεύθυνση `setDebugMode()`:
```php
-$configurator->setDebugMode('23.75.345.200'); // μία ή περισσότερες διευθύνσεις IP
+$this->configurator->setDebugMode('23.75.345.200'); // μία ή περισσότερες διευθύνσεις IP
```
Συνιστούμε οπωσδήποτε τον συνδυασμό μιας διεύθυνσης IP με ένα cookie. Θα αποθηκεύσουμε ένα μυστικό token στο cookie `nette-debug`, π.χ. `secret1234`, και η λειτουργία ανάπτυξης θα ενεργοποιηθεί για τους προγραμματιστές με αυτόν τον συνδυασμό IP και cookie.
```php
-$configurator->setDebugMode('secret1234@23.75.345.200');
+$this->configurator->setDebugMode('secret1234@23.75.345.200');
```
Μπορούμε επίσης να απενεργοποιήσουμε εντελώς τη λειτουργία προγραμματιστή, ακόμη και για το localhost:
```php
-$configurator->setDebugMode(false);
+$this->configurator->setDebugMode(false);
```
Σημειώστε ότι η τιμή `true` ενεργοποιεί τη λειτουργία προγραμματιστή με σκληρό τρόπο, κάτι που δεν πρέπει ποτέ να συμβαίνει σε έναν διακομιστή παραγωγής.
@@ -90,7 +115,7 @@ $configurator->setDebugMode(false);
Για εύκολη αποσφαλμάτωση, θα ενεργοποιήσουμε το σπουδαίο εργαλείο [Tracy |tracy:]. Στη λειτουργία προγραμματιστή απεικονίζει τα σφάλματα και στη λειτουργία παραγωγής καταγράφει τα σφάλματα στον καθορισμένο κατάλογο:
```php
-$configurator->enableTracy($appDir . '/log');
+$this->configurator->enableTracy($this->rootDir . '/log');
```
@@ -100,7 +125,7 @@ $configurator->enableTracy($appDir . '/log');
Η Nette χρησιμοποιεί την κρυφή μνήμη για το DI container, το RobotLoader, τα πρότυπα κ.λπ. Ως εκ τούτου, είναι απαραίτητο να ορίσετε τη διαδρομή προς τον κατάλογο όπου θα αποθηκεύεται η προσωρινή μνήμη:
```php
-$configurator->setTempDirectory($appDir . '/temp');
+$this->configurator->setTempDirectory($this->rootDir . '/temp');
```
Σε Linux ή macOS, ορίστε τα [δικαιώματα εγγραφής |nette:troubleshooting#Setting directory permissions] για τους καταλόγους `log/` και `temp/`.
@@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader]
Συνήθως, θα θέλουμε να φορτώνουμε αυτόματα τις κλάσεις χρησιμοποιώντας [τον RobotLoader |robot-loader:], οπότε πρέπει να τον εκκινήσουμε και να τον αφήσουμε να φορτώσει κλάσεις από τον κατάλογο όπου βρίσκεται το `Bootstrap.php` (δηλαδή το `__DIR__`) και όλους τους υποκαταλόγους του:
```php
-$configurator->createRobotLoader()
+$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
```
@@ -126,7 +151,7 @@ $configurator->createRobotLoader()
Το Configurator σας επιτρέπει να καθορίσετε μια ζώνη ώρας για την εφαρμογή σας.
```php
-$configurator->setTimeZone('Europe/Prague');
+$this->configurator->setTimeZone('Europe/Prague');
```
@@ -143,16 +168,17 @@ $configurator->setTimeZone('Europe/Prague');
Τα αρχεία διαμόρφωσης φορτώνονται με τη χρήση του `addConfig()`:
```php
-$configurator->addConfig($appDir . '/config/common.neon');
+$this->configurator->addConfig($this->rootDir . '/config/common.neon');
```
Η μέθοδος `addConfig()` μπορεί να κληθεί πολλές φορές για την προσθήκη πολλών αρχείων.
```php
-$configurator->addConfig($appDir . '/config/common.neon');
-$configurator->addConfig($appDir . '/config/local.neon');
+$configDir = $this->rootDir . '/config';
+$this->configurator->addConfig($configDir . '/common.neon');
+$this->configurator->addConfig($configDir . '/services.neon');
if (PHP_SAPI === 'cli') {
- $configurator->addConfig($appDir . '/config/cli.php');
+ $this->configurator->addConfig($configDir . '/cli.php');
}
```
@@ -169,7 +195,7 @@ if (PHP_SAPI === 'cli') {
Οι παράμετροι που χρησιμοποιούνται σε αρχεία ρυθμίσεων μπορούν να οριστούν [στην ενότητα `parameters` |dependency-injection:configuration#parameters] και επίσης να μεταβιβαστούν (ή να αντικατασταθούν) από τη μέθοδο `addStaticParameters()` (έχει το ψευδώνυμο `addParameters()`). Είναι σημαντικό ότι διαφορετικές τιμές παραμέτρων προκαλούν τη δημιουργία πρόσθετων δοχείων DI, δηλαδή πρόσθετων κλάσεων.
```php
-$configurator->addStaticParameters([
+$this->configurator->addStaticParameters([
'projectId' => 23,
]);
```
@@ -183,7 +209,7 @@ $configurator->addStaticParameters([
Μπορούμε επίσης να προσθέσουμε δυναμικές παραμέτρους στο δοχείο, οι διαφορετικές τιμές τους, σε αντίθεση με τις στατικές παραμέτρους, δεν θα προκαλέσουν τη δημιουργία νέων δοχείων DI.
```php
-$configurator->addDynamicParameters([
+$this->configurator->addDynamicParameters([
'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
```
@@ -191,7 +217,7 @@ $configurator->addDynamicParameters([
Οι μεταβλητές περιβάλλοντος θα μπορούσαν εύκολα να γίνουν διαθέσιμες με τη χρήση δυναμικών παραμέτρων. Μπορούμε να έχουμε πρόσβαση σε αυτές μέσω της διεύθυνσης `%env.variable%` στα αρχεία ρυθμίσεων.
```php
-$configurator->addDynamicParameters([
+$this->configurator->addDynamicParameters([
'env' => getenv(),
]);
```
@@ -206,6 +232,7 @@ $configurator->addDynamicParameters([
- `%wwwDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο που περιέχει το αρχείο καταχώρησης `index.php`
- `%tempDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο για τα προσωρινά αρχεία
- `%vendorDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο όπου ο Composer εγκαθιστά τις βιβλιοθήκες
+- `%rootDir%` είναι η απόλυτη διαδρομή προς τον ριζικό κατάλογο του έργου
- Το `%debugMode%` δηλώνει αν η εφαρμογή βρίσκεται σε κατάσταση αποσφαλμάτωσης.
- Το `%consoleMode%` δηλώνει αν η αίτηση υποβλήθηκε μέσω της γραμμής εντολών.
@@ -225,7 +252,7 @@ services:
Δημιουργούμε μια νέα περίπτωση και την εισάγουμε στο bootstrap:
```php
-$configurator->addServices([
+$this->configurator->addServices([
'myservice' => new App\Model\MyCustomService('foobar'),
]);
```
@@ -234,13 +261,21 @@ $configurator->addServices([
Διαφορετικά περιβάλλοντα .[#toc-different-environments]
=======================================================
-Μπορείτε να προσαρμόσετε την τάξη `Bootstrap` ανάλογα με τις ανάγκες σας. Μπορείτε να προσθέσετε παραμέτρους στη μέθοδο `boot()` για να διαφοροποιήσετε τα έργα ιστού ή να προσθέσετε άλλες μεθόδους, όπως η `bootForTests()`, η οποία αρχικοποιεί το περιβάλλον για δοκιμές μονάδας, η `bootForCli()` για σενάρια που καλούνται από τη γραμμή εντολών κ.ο.κ.
+Μη διστάσετε να προσαρμόσετε την τάξη `Bootstrap` σύμφωνα με τις ανάγκες σας. Μπορείτε να προσθέσετε παραμέτρους στη μέθοδο `bootWebApplication()` για να διαφοροποιήσετε τα διάφορα web projects. Εναλλακτικά, μπορείτε να προσθέσετε άλλες μεθόδους, όπως `bootTestEnvironment()` για την αρχικοποίηση του περιβάλλοντος για δοκιμές μονάδας, `bootConsoleApplication()` για σενάρια που καλούνται από τη γραμμή εντολών κ.ο.κ.
```php
-public static function bootForTests(): Configurator
+public function bootTestEnvironment(): Nette\DI\Container
{
- $configurator = self::boot();
Tester\Environment::setup(); // Αρχικοποίηση Nette Tester
- return $configurator;
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+}
+
+public function bootConsoleApplication(): Nette\DI\Container
+{
+ $this->configurator->setDebugMode(false);
+ $this->initializeEnvironment();
+ $this->setupContainer();
+ return $this->configurator->createContainer();
}
```
diff --git a/application/el/components.texy b/application/el/components.texy
index ae5fc64c2f..06fafcf4d4 100644
--- a/application/el/components.texy
+++ b/application/el/components.texy
@@ -230,6 +230,28 @@ $this->redirect(/* ... */); // και ανακατεύθυνση
```
+Επανακατεύθυνση μετά από ένα σήμα .[#toc-redirection-after-a-signal]
+====================================================================
+
+Μετά την επεξεργασία ενός σήματος συνιστωσών, ακολουθεί συχνά ανακατεύθυνση. Αυτή η κατάσταση είναι παρόμοια με τις φόρμες - μετά την υποβολή μιας φόρμας, κάνουμε επίσης ανακατεύθυνση για να αποτρέψουμε την εκ νέου υποβολή δεδομένων όταν η σελίδα ανανεώνεται στο πρόγραμμα περιήγησης.
+
+```php
+$this->redirect('this') // redirects to the current presenter and action
+```
+
+Δεδομένου ότι ένα συστατικό είναι ένα επαναχρησιμοποιήσιμο στοιχείο και συνήθως δεν πρέπει να έχει άμεση εξάρτηση από συγκεκριμένους παρουσιαστές, οι μέθοδοι `redirect()` και `link()` ερμηνεύουν αυτόματα την παράμετρο ως σήμα συστατικού:
+
+```php
+$this->redirect('click') // redirects to the 'click' signal of the same component
+```
+
+Εάν χρειάζεται να ανακατευθύνετε σε διαφορετικό παρουσιαστή ή ενέργεια, μπορείτε να το κάνετε μέσω του παρουσιαστή:
+
+```php
+$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action
+```
+
+
Μόνιμες παράμετροι .[#toc-persistent-parameters]
================================================
@@ -347,7 +369,7 @@ services:
Τέλος, θα χρησιμοποιήσουμε αυτό το εργοστάσιο στον παρουσιαστή μας:
```php
-class PollPresenter extends Nette\UI\Application\Presenter
+class PollPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private PollControlFactory $pollControlFactory,
@@ -380,7 +402,7 @@ interface PollControlFactory
Τα συστατικά σε μια εφαρμογή Nette είναι τα επαναχρησιμοποιήσιμα μέρη μιας διαδικτυακής εφαρμογής που ενσωματώνουμε σε σελίδες, τα οποία αποτελούν το αντικείμενο αυτού του κεφαλαίου. Ποιες ακριβώς είναι οι δυνατότητες ενός τέτοιου συστατικού;
1) είναι δυνατό να αποδοθεί σε ένα πρότυπο
-2) γνωρίζει ποιο μέρος του εαυτού του να αποδώσει κατά τη διάρκεια μιας [αίτησης AJAX |ajax#invalidation] (αποσπάσματα)
+2) γνωρίζει [ποιο μέρος του εαυτού του |ajax#snippets] να αποδώσει κατά τη διάρκεια μιας αίτησης AJAX (αποσπάσματα)
3) έχει τη δυνατότητα να αποθηκεύει την κατάστασή του σε μια διεύθυνση URL (μόνιμες παράμετροι)
4) έχει τη δυνατότητα να ανταποκρίνεται σε ενέργειες του χρήστη (σήματα)
5) δημιουργεί μια ιεραρχική δομή (όπου η ρίζα είναι ο παρουσιαστής)
@@ -430,7 +452,7 @@ class PaginatingControl extends Control
}
```
-Η αντίθετη διαδικασία, δηλαδή η συλλογή τιμών από persistent properites, αντιμετωπίζεται από τη μέθοδο `saveState()`.
+Η αντίθετη διαδικασία, δηλαδή η συλλογή τιμών από persistent properties, αντιμετωπίζεται από τη μέθοδο `saveState()`.
Σήματα σε βάθος .[#toc-signals-in-depth]
diff --git a/application/el/configuration.texy b/application/el/configuration.texy
index 01b036704b..dcf8543ece 100644
--- a/application/el/configuration.texy
+++ b/application/el/configuration.texy
@@ -13,11 +13,15 @@ application:
# δείχνει τον πίνακα "Nette Application" στο Tracy BlueScreen?
debugger: ... # (bool) προεπιλογή true
- # θα καλείται ο παρουσιαστής σφαλμάτων σε περίπτωση σφάλματος;
- catchExceptions: ... # (bool) προεπιλεγμένη τιμή true σε κατάσταση παραγωγής
+ # θα κληθεί ο παρουσιαστής σφαλμάτων στο σφάλμα;
+ # έχει αποτέλεσμα μόνο σε λειτουργία προγραμματιστή
+ catchExceptions: ... # (bool) προεπιλογή true
# όνομα του error-presenter
- errorPresenter: Error # (string) προεπιλογή 'Nette:Error'
+ errorPresenter: Error # (string|array) προεπιλογή 'Nette:Error'
+
+ # ορίζει ψευδώνυμα για παρουσιαστές και εκδηλώσεις
+ aliases: ...
# ορίζει τους κανόνες για την επίλυση του ονόματος του παρουσιαστή σε μια κλάση
mapping: ...
@@ -27,10 +31,19 @@ application:
silentLinks: ... # (bool) προεπιλογή σε false
```
-Επειδή οι παρουσιαστές σφαλμάτων δεν καλούνται εξ ορισμού σε κατάσταση ανάπτυξης και τα σφάλματα εμφανίζονται από το Tracy, η αλλαγή της τιμής `catchExceptions` σε `true` βοηθάει στην επαλήθευση της σωστής λειτουργίας των παρουσιαστών σφαλμάτων κατά την ανάπτυξη.
+Από την έκδοση 3.2 του `nette/application` είναι δυνατό να ορίσετε ένα ζεύγος παρουσιαστών σφαλμάτων:
+
+```neon
+application:
+ errorPresenter:
+ 4xx: Error4xx # για την εξαίρεση Nette\Application\BadRequestException
+ 5xx: Error5xx # για άλλες εξαιρέσεις
+```
Η επιλογή `silentLinks` καθορίζει τον τρόπο με τον οποίο η Nette συμπεριφέρεται στη λειτουργία ανάπτυξης όταν η δημιουργία συνδέσμων αποτυγχάνει (για παράδειγμα, επειδή δεν υπάρχει παρουσιαστής κ.λπ.). Η προεπιλεγμένη τιμή `false` σημαίνει ότι η Nette ενεργοποιεί το `E_USER_WARNING`. Η ρύθμιση σε `true` καταστέλλει αυτό το μήνυμα σφάλματος. Σε περιβάλλον παραγωγής, το `E_USER_WARNING` ενεργοποιείται πάντα. Μπορούμε επίσης να επηρεάσουμε αυτή τη συμπεριφορά θέτοντας τη μεταβλητή του παρουσιαστή [$invalidLinkMode |creating-links#Invalid Links].
+Τα [ψευδώνυμα απλοποιούν την αναφορά σε |creating-links#aliases] συχνά χρησιμοποιούμενους παρουσιαστές.
+
Η [αντιστοίχιση ορίζει τους κανόνες |modules#mapping] με τους οποίους το όνομα της κλάσης προκύπτει από το όνομα του παρουσιαστή.
@@ -82,6 +95,9 @@ latte:
# ενεργοποιεί τον [έλεγχο του παραγόμενου κώδικα |latte:develop#Checking Generated Code]
phpLinter: ... # (string) η προεπιλογή είναι null
+ # ορίζει την τοπική γλώσσα
+ locale: cs_CZ # (string) η προεπιλογή είναι null
+
# κλάση του $this->template
templateClass: App\MyTemplateClass # προεπιλογή σε Nette\Bridges\ApplicationLatte\DefaultTemplate
```
@@ -91,7 +107,7 @@ latte:
```neon
latte:
extensions:
- - Latte\Essential\TranslatorExtension
+ - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```
/--comment
diff --git a/application/el/creating-links.texy b/application/el/creating-links.texy
index e36d982225..1914401594 100644
--- a/application/el/creating-links.texy
+++ b/application/el/creating-links.texy
@@ -38,7 +38,7 @@
detail
```
-Εάν η μέθοδος `ProductPresenter::renderShow()` δεν έχει στην υπογραφή της την `$lang`, μπορεί να διαβάσει την τιμή της παραμέτρου χρησιμοποιώντας την `$lang = $this->getParameter('lang')`.
+Εάν η μέθοδος `ProductPresenter::renderShow()` δεν έχει στην υπογραφή της την `$lang`, μπορεί να ανακτήσει την τιμή της παραμέτρου χρησιμοποιώντας την `$lang = $this->getParameter('lang')` ή από την [ιδιότητα |presenters#Request Parameters].
Εάν οι παράμετροι είναι αποθηκευμένες σε πίνακα, μπορούν να επεκταθούν με τον τελεστή `...` (ή `(expand)` στο Latte 2.x):
@@ -140,7 +140,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']);
refresh
```
-Ταυτόχρονα, όλες οι παράμετροι που καθορίζονται στην υπογραφή της εντολής `render()` ή `action()` μεταφέρονται. Έτσι, αν βρισκόμαστε στις σελίδες `Product:show` και `id:123`, ο σύνδεσμος προς την `this` θα μεταφέρει και αυτή την παράμετρο.
+Ταυτόχρονα, όλες οι παράμετροι που καθορίζονται στην υπογραφή της `action()` ή `render()` μεθόδου, εάν η `action()` δεν έχει οριστεί, μεταφέρονται. Έτσι, αν βρισκόμαστε στις σελίδες `Product:show` και `id:123`, ο σύνδεσμος προς την `this` θα μεταφέρει και αυτή την παράμετρο.
Φυσικά, είναι δυνατόν να καθορίσετε τις παραμέτρους απευθείας:
@@ -213,7 +213,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']);
Αν θέλουμε να συνδέσουμε με παρουσιαστές στο πρότυπο συστατικού, χρησιμοποιούμε την ετικέτα `{plink}`:
```latte
-home
+home
```
ή στον κώδικα
@@ -223,6 +223,30 @@ $this->getPresenter()->link('Home:default')
```
+Ψευδώνυμα .[#toc-aliases]{data-version:v3.2.2}
+==============================================
+
+Μερικές φορές είναι χρήσιμο να αντιστοιχίσετε ένα εύκολα απομνημονεύσιμο ψευδώνυμο σε ένα ζεύγος Παρουσιαστής:ενέργεια. Για παράδειγμα, θα μπορούσατε να ονομάσετε την αρχική σελίδα `Front:Home:default` απλά ως `home` ή `Admin:Dashboard:default` ως `admin`.
+
+Τα ψευδώνυμα ορίζονται στη [ρύθμιση παρα |configuration] μέτρων κάτω από το κλειδί `application › aliases`:
+
+```neon
+application:
+ aliases:
+ home: Front:Home:default
+ admin: Admin:Dashboard:default
+ sign: Front:Sign:in
+```
+
+Στους συνδέσμους, γράφονται χρησιμοποιώντας το σύμβολο at, για παράδειγμα:
+
+```latte
+administration
+```
+
+Υποστηρίζονται σε όλες τις μεθόδους που λειτουργούν με συνδέσμους, όπως το `redirect()` και παρόμοια.
+
+
Άκυροι σύνδεσμοι .[#toc-invalid-links]
======================================
diff --git a/application/el/how-it-works.texy b/application/el/how-it-works.texy
index 2cfe6c52b9..8593798f00 100644
--- a/application/el/how-it-works.texy
+++ b/application/el/how-it-works.texy
@@ -22,18 +22,18 @@
/--pre
web-project/
├── app/ ← directory with application
-│ ├── Presenters/ ← presenter classes
-│ │ ├── HomePresenter.php ← Home presenter class
-│ │ └── templates/ ← templates directory
-│ │ ├── @layout.latte ← template of shared layout
-│ │ └── Home/ ← templates for Home presenter
-│ │ └── default.latte ← template for action `default`
-│ ├── Router/ ← configuration of URL addresses
+│ ├── Core/ ← βασικές αναγκαίες τάξεις
+│ │ └── RouterFactory.php ← διαμόρφωση των διευθύνσεων URL
+│ ├── UI/ ← παρουσιαστές, πρότυπα και λοιπά.
+│ │ ├── @layout.latte ← πρότυπο κοινής διάταξης
+│ │ └── Home/ ← Αρχικός κατάλογος παρουσιαστών
+│ │ ├── HomePresenter.php ← Κλάση οικιακού παρουσιαστή
+│ │ └── default.latte ← πρότυπο για τη δράση default
│ └── Bootstrap.php ← booting class Bootstrap
├── bin/ ← scripts for the command line
├── config/ ← configuration files
│ ├── common.neon
-│ └── local.neon
+│ └── services.neon
├── log/ ← error logs
├── temp/ ← temporary files, cache, …
├── vendor/ ← libraries installed by Composer
@@ -91,7 +91,7 @@ composer create-project nette/web-project
Η εφαρμογή ξεκινά ζητώντας από τον λεγόμενο δρομολογητή να αποφασίσει ποιος από τους παρουσιαστές θα περάσει το τρέχον αίτημα για επεξεργασία. Ο δρομολογητής αποφασίζει ποιανού ευθύνη είναι. Κοιτάζει τη διεύθυνση URL εισόδου `https://example.com/product/123`, ο οποίος θέλει να `show` ένα προϊόν με `id: 123` ως ενέργεια. Είναι καλή συνήθεια να γράφετε τα ζεύγη παρουσιαστής + δράση χωρισμένα με άνω και κάτω τελεία ως `Product:show`.
-Έτσι, ο δρομολογητής μετατρέπει τη διεύθυνση URL σε ένα ζεύγος `Presenter:action` + παράμετροι, στην περίπτωσή μας `Product:show` + `id: 123`. Μπορείτε να δείτε πώς μοιάζει ένας δρομολογητής στο αρχείο `app/Router/RouterFactory.php` και θα τον περιγράψουμε αναλυτικά στο κεφάλαιο [Δρομολόγηση |Routing].
+Έτσι, ο δρομολογητής μετατρέπει τη διεύθυνση URL σε ένα ζεύγος `Presenter:action` + παράμετροι, στην περίπτωσή μας `Product:show` + `id: 123`. Μπορείτε να δείτε πώς μοιάζει ένας δρομολογητής στο αρχείο `app/Core/RouterFactory.php` και θα τον περιγράψουμε αναλυτικά στο κεφάλαιο [Δρομολόγηση |Routing].
Ας συνεχίσουμε. Η εφαρμογή γνωρίζει ήδη το όνομα του παρουσιαστή και μπορεί να συνεχίσει. Δημιουργώντας ένα αντικείμενο `ProductPresenter`, το οποίο είναι ο κώδικας του παρουσιαστή `Product`. Πιο συγκεκριμένα, ζητάει από το DI container τη δημιουργία του presenter, επειδή η παραγωγή αντικειμένων είναι η δουλειά του.
@@ -121,12 +121,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter
Στη συνέχεια, ο παρουσιαστής επιστρέφει την απάντηση. Αυτό μπορεί να είναι μια σελίδα HTML, μια εικόνα, ένα έγγραφο XML, η αποστολή ενός αρχείου από το δίσκο, JSON ή η ανακατεύθυνση σε μια άλλη σελίδα. Σημαντικό είναι ότι, αν δεν πούμε ρητά πώς να απαντήσουμε (κάτι που συμβαίνει στην περίπτωση του `ProductPresenter`), η απάντηση θα είναι η απόδοση του προτύπου με μια σελίδα HTML. Γιατί; Λοιπόν, επειδή στο 99% των περιπτώσεων θέλουμε να σχεδιάσουμε ένα πρότυπο, οπότε ο παρουσιαστής θεωρεί αυτή τη συμπεριφορά ως προεπιλεγμένη και θέλει να διευκολύνει τη δουλειά μας. Αυτό είναι το νόημα της Nette.
-Δεν χρειάζεται καν να δηλώσουμε ποιο πρότυπο θέλουμε να σχεδιάσουμε, αυτός εξάγει τη διαδρομή προς αυτό σύμφωνα με απλή λογική. Στην περίπτωση του presenter `Product` και της δράσης `show`, προσπαθεί να δει αν ένα από αυτά τα αρχεία προτύπων υπάρχει σε σχέση με τον κατάλογο όπου βρίσκεται η κλάση `ProductPresenter`:
+Δεν χρειάζεται καν να καθορίσουμε ποιο πρότυπο θα αναπαραχθεί- το πλαίσιο θα βρει μόνο του τη διαδρομή. Στην περίπτωση της ενέργειας `show`, απλώς προσπαθεί να φορτώσει το πρότυπο `show.latte` στον κατάλογο με την κλάση `ProductPresenter`. Προσπαθεί επίσης να βρει τη διάταξη στο αρχείο `@layout.latte` (περισσότερα για την [αναζήτηση προτύπων |templates#Template Lookup]).
-- `templates/Product/show.latte`
-- `templates/Product.show.latte`
-
-Θα προσπαθήσει επίσης να βρει τη διάταξη στο αρχείο `@layout.latte` και στη συνέχεια θα αποδώσει το πρότυπο. Τώρα ολοκληρώνεται η εργασία του παρουσιαστή και ολόκληρης της εφαρμογής. Εάν το πρότυπο δεν υπάρχει, θα επιστραφεί μια σελίδα με σφάλμα 404. Μπορείτε να διαβάσετε περισσότερα για τους παρουσιαστές στη σελίδα [Παρουσιαστές |Presenters].
+Στη συνέχεια, τα πρότυπα αποδίδονται. Με αυτόν τον τρόπο ολοκληρώνεται η εργασία του παρουσιαστή και ολόκληρης της εφαρμογής, και η εργασία έχει τελειώσει. Εάν το πρότυπο δεν υπήρχε, θα επιστρεφόταν μια σελίδα σφάλματος 404. Μπορείτε να διαβάσετε περισσότερα για τους παρουσιαστές στη σελίδα [Παρουσιαστές |presenters].
[* request-flow.svg *]
@@ -137,7 +134,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter
3) ο δρομολογητής αποκωδικοποιεί τη διεύθυνση URL ως ζεύγος `Home:default`
4) δημιουργείται ένα αντικείμενο `HomePresenter`
5) καλείται η μέθοδος `renderDefault()` (αν υπάρχει)
-6) αποδίδεται ένα πρότυπο `templates/Home/default.latte` με διάταξη `templates/@layout.latte`
+6) αποδίδεται ένα πρότυπο `default.latte` με διάταξη `@layout.latte`
Μπορεί να έχετε συναντήσει πολλές νέες έννοιες τώρα, αλλά πιστεύουμε ότι βγάζουν νόημα. Η δημιουργία εφαρμογών στη Nette είναι πανεύκολη.
diff --git a/application/el/modules.texy b/application/el/modules.texy
index b2bb1dffd0..5e22451dba 100644
--- a/application/el/modules.texy
+++ b/application/el/modules.texy
@@ -2,29 +2,31 @@
********
.[perex]
-Στη Nette, οι ενότητες αντιπροσωπεύουν τις λογικές μονάδες που συνθέτουν μια εφαρμογή. Περιλαμβάνουν παρουσιαστές, πρότυπα, ενδεχομένως επίσης συστατικά και κλάσεις μοντέλων.
+Οι ενότητες φέρνουν σαφήνεια στις εφαρμογές Nette διευκολύνοντας τον εύκολο διαχωρισμό σε λογικές μονάδες.
-Ένας κατάλογος για τους παρουσιαστές και ένας για τα πρότυπα δεν θα ήταν αρκετός για πραγματικά έργα. Το να έχετε δεκάδες αρχεία σε έναν φάκελο είναι τουλάχιστον ανοργάνωτο. Πώς να απαλλαγείτε από αυτό; Απλώς τα χωρίζουμε σε υποκαταλόγους στο δίσκο και σε χώρους ονομάτων στον κώδικα. Και αυτό ακριβώς κάνουν τα modules της Nette.
-
-Ας ξεχάσουμε λοιπόν έναν ενιαίο φάκελο για τους παρουσιαστές και τα πρότυπα και ας δημιουργήσουμε αντ' αυτού ενότητες, για παράδειγμα `Admin` και `Front`.
+Παρόμοια με την οργάνωση των αρχείων σε φακέλους σε ένα σκληρό δίσκο, στη Nette μπορούμε να χωρίσουμε τους παρουσιαστές, τα πρότυπα και άλλες βοηθητικές κλάσεις σε ενότητες. Πώς λειτουργεί αυτό στην πράξη; Απλά με την ενσωμάτωση νέων υποκαταλόγων στη δομή. Ακολουθεί ένα παράδειγμα δομής με δύο ενότητες, Front και Admin:
/--pre
-app/
-├── Presenters/
-├── Modules/ ← directory with modules
-│ ├── Admin/ ← module Admin
-│ │ ├── Presenters/ ← its presenters
-│ │ │ ├── DashboardPresenter.php
-│ │ │ └── templates/
-│ └── Front/ ← module Front
-│ └── Presenters/ ← its presenters
-│ └── ...
+app/
+├── UI/
+│ ├── Admin/ ← Admin module
+│ │ ├── @layout.latte
+│ │ ├── Dashboard/
+│ │ │ ├── DashboardPresenter.php
+│ │ │ └── default.latte
+│ │ └── ...
+│ ├── Front/ ← Front module
+│ │ ├── @layout.latte
+│ │ ├── Home/
+│ │ │ ├── HomePresenter.php
+│ │ │ └── default.latte
+│ │ └── ...
\--
-Αυτή η δομή καταλόγου θα αντικατοπτρίζεται από τα namespaces των κλάσεων, έτσι για παράδειγμα το `DashboardPresenter` θα βρίσκεται στο namespace `App\Modules\Admin\Presenters`:
+Αυτή η δομή καταλόγου αντικατοπτρίζεται στα namespaces των κλάσεων, έτσι για παράδειγμα, η `DashboardPresenter` βρίσκεται στο namespace `App\UI\Admin\Dashboard`:
```php
-namespace App\Modules\Admin\Presenters;
+namespace App\UI\Admin\Dashboard;
class DashboardPresenter extends Nette\Application\UI\Presenter
{
@@ -32,35 +34,49 @@ class DashboardPresenter extends Nette\Application\UI\Presenter
}
```
-Ο παρουσιαστής `Dashboard` μέσα στην ενότητα `Admin` αναφέρεται μέσα στην εφαρμογή χρησιμοποιώντας τον συμβολισμό της άνω και κάτω τελείας ως `Admin:Dashboard`, και η ενέργεια `default` ως `Admin:Dashboard:default`.
-Και πώς γνωρίζει η Nette proper ότι το `Admin:Dashboard` αντιπροσωπεύει την κλάση `App\Modules\Admin\Presenters\DashboardPresenter`; Αυτό καθορίζεται από την [αντιστοίχιση |#mapping] στη διαμόρφωση.
-Έτσι, η δεδομένη δομή δεν είναι αυστηρά καθορισμένη και μπορείτε να την τροποποιήσετε ανάλογα με τις ανάγκες σας.
+Στην εφαρμογή, αναφερόμαστε στον παρουσιαστή `Dashboard` μέσα στην ενότητα `Admin` χρησιμοποιώντας τον συμβολισμό της άνω και κάτω τελείας ως `Admin:Dashboard`. Για τη δράση του `default`, αναφερόμαστε σε αυτόν ως `Admin:Dashboard:default`.
+
+Η δομή που παρουσιάζεται δεν είναι άκαμπτη- μπορείτε να [την προσαρμόσετε πλήρως στις ανάγκες σας |#mapping] στη διαμόρφωση. .[tip]
-Οι ενότητες μπορούν φυσικά να περιέχουν όλα τα άλλα στοιχεία εκτός από τους παρουσιαστές και τα πρότυπα, όπως συστατικά, κλάσεις μοντέλων κ.λπ.
+Οι ενότητες μπορούν να περιλαμβάνουν όλα τα άλλα αρχεία, όπως συστατικά και βοηθητικές κλάσεις, εκτός από τους παρουσιαστές και τα πρότυπα. Αν σκέφτεστε πού να τα τοποθετήσετε αυτά, σκεφτείτε να χρησιμοποιήσετε έναν φάκελο `Accessory`:
+
+/--pre
+app/
+├── UI/
+│ ├── Admin/
+│ │ ├── Accessory/
+│ │ │ ├── FormFactory.php
+│ │ │ └── AdminLayout.php
+│ │ ├── Dashboard/
+│ │ └── ...
+\--
Ενσωματωμένες ενότητες .[#toc-nested-modules]
---------------------------------------------
-Οι ενότητες δεν χρειάζεται να σχηματίζουν μόνο μια επίπεδη δομή, μπορείτε επίσης να δημιουργήσετε υποενότητες, για παράδειγμα:
+Οι ενότητες μπορούν να έχουν πολλαπλά επίπεδα ένθεσης, παρόμοια με μια δομή καταλόγου σε ένα δίσκο:
/--pre
-app/
-├── Modules/ ← directory with modules
-│ ├── Blog/ ← module Blog
-│ │ ├── Admin/ ← submodule Admin
-│ │ │ ├── Presenters/
+app/
+├── UI/
+│ ├── Blog/ ← Blog module
+│ │ ├── Admin/ ← Admin submodule
+│ │ │ ├── Dashboard/
+│ │ │ └── ...
+│ │ ├── Front/ ← Front submodule
+│ │ │ ├── @layout.latte
+│ │ │ ├── Home/
│ │ │ └── ...
-│ │ └── Front/ ← submodule Front
-│ │ ├── Presenters/
-│ │ └── ...
-│ ├── Forum/ ← module Forum
+│ ├── Forum/ ← Forum module
│ │ └── ...
\--
-Έτσι, η ενότητα `Blog` χωρίζεται σε υποενότητες `Admin` και `Front`. Και πάλι, αυτό θα αντικατοπτρίζεται στα namespaces, τα οποία θα είναι `App\Modules\Blog\Admin\Presenters` κ.λπ. Ο παρουσιαστής `Dashboard` μέσα στην υποενότητα αναφέρεται ως `Blog:Admin:Dashboard`.
+Η ενότητα `Blog` χωρίζεται σε υποενότητες `Admin` και `Front`. Αυτό αντικατοπτρίζεται επίσης στα namespaces, τα οποία εμφανίζονται ως `App\UI\Blog\Admin` και παρόμοια. Για να αναφερθούμε στον παρουσιαστή `Dashboard` μέσα στην υποενότητα `Admin`, αναφερόμαστε σε αυτόν ως `Blog:Admin:Dashboard`.
+
+Η φωλεοποίηση μπορεί να είναι όσο βαθιά χρειάζεται, επιτρέποντας τη δημιουργία υπο-υπομονάδων.
-Η ένθεση μπορεί να προχωρήσει όσο βαθιά θέλετε, οπότε μπορούν να δημιουργηθούν υπο-υποενότητες.
+Για παράδειγμα, αν στη διαχείριση έχετε πολλούς παρουσιαστές που σχετίζονται με τη διαχείριση παραγγελιών, όπως `OrderDetail`, `OrderEdit`, `OrderDispatch`, κ.λπ., μπορείτε να δημιουργήσετε μια ενότητα `Order` στην οποία θα οργανωθούν παρουσιαστές όπως `Detail`, `Edit`, `Dispatch` και άλλοι.
Δημιουργία συνδέσμων .[#toc-creating-links]
@@ -102,47 +118,66 @@ class DashboardPresenter extends Nette\Application\UI\Presenter
Χαρτογράφηση .[#toc-mapping]
----------------------------
-Καθορίζει τους κανόνες με τους οποίους το όνομα της κλάσης προκύπτει από το όνομα του παρουσιαστή. Τους γράφουμε στη [διαμόρφωση |configuration] κάτω από το κλειδί `application › mapping`.
+Η αντιστοίχιση ορίζει τους κανόνες για την εξαγωγή του ονόματος της κλάσης από το όνομα του παρουσιαστή. Οι κανόνες αυτοί καθορίζονται στη [διαμόρφωση |configuration] στο κλειδί `application › mapping`.
-Ας ξεκινήσουμε με ένα δείγμα που δεν χρησιμοποιεί ενότητες. Θα θέλουμε απλώς οι κλάσεις presenter να έχουν το namespace `App\Presenters`. Αυτό σημαίνει ότι ένας παρουσιαστής όπως το `Home` θα πρέπει να αντιστοιχίζεται στην κλάση `App\Presenters\HomePresenter`. Αυτό μπορεί να επιτευχθεί με την ακόλουθη διαμόρφωση:
+Οι δομές καταλόγων που αναφέρθηκαν νωρίτερα σε αυτή τη σελίδα βασίζονται στην ακόλουθη αντιστοίχιση:
```neon
application:
- mapping:
- *: App\Presenters\*Presenter
+ mapping: App\UI\*\**Presenter
```
-Το όνομα του παρουσιαστή αντικαθίσταται με τον αστερίσκο στη μάσκα κλάσης και το αποτέλεσμα είναι το όνομα της κλάσης. Εύκολο!
+Πώς λειτουργεί η χαρτογράφηση; Για καλύτερη κατανόηση, ας φανταστούμε πρώτα μια εφαρμογή χωρίς ενότητες. Θέλουμε οι κλάσεις του παρουσιαστή να υπάγονται στο χώρο ονομάτων `App\UI`, έτσι ώστε ο παρουσιαστής `Home` να αντιστοιχίζεται στην κλάση `App\UI\HomePresenter`. Αυτό μπορεί να επιτευχθεί με αυτή τη διαμόρφωση:
-Αν χωρίσουμε τους παρουσιαστές σε ενότητες, μπορούμε να έχουμε τη δική μας χαρτογράφηση για κάθε ενότητα:
+```neon
+application:
+ mapping: App\UI\*Presenter
+```
+
+Αυτή η αντιστοίχιση λειτουργεί αντικαθιστώντας τον αστερίσκο στη μάσκα `App\UI\*Presenter` με το όνομα του παρουσιαστή `Home`, με αποτέλεσμα το τελικό όνομα της κλάσης `App\UI\HomePresenter`. Απλό!
+
+Ωστόσο, όπως μπορείτε να δείτε στα παραδείγματα σε αυτό και σε άλλα κεφάλαια, τοποθετούμε τις κλάσεις παρουσιαστή σε επώνυμους υποκαταλόγους, π.χ. ο παρουσιαστής `Home` αντιστοιχίζεται στην κλάση `App\UI\Home\HomePresenter`. Αυτό επιτυγχάνεται με τον διπλασιασμό του αστερίσκου (απαιτεί Nette Application 3.2):
+
+```neon
+application:
+ mapping: App\UI\**Presenter
+```
+
+Τώρα, ας προχωρήσουμε στην αντιστοίχιση των παρουσιαστών σε ενότητες. Μπορούμε να ορίσουμε συγκεκριμένες αντιστοιχίσεις για κάθε ενότητα:
```neon
application:
mapping:
- Front: App\Modules\Front\Presenters\*Presenter
- Admin: App\Modules\Admin\Presenters\*Presenter
+ Front: App\UI\Front\**Presenter
+ Admin: App\UI\Admin\**Presenter
Api: App\Api\*Presenter
```
-Τώρα ο παρουσιαστής `Front:Home` αντιστοιχίζεται στην κλάση `App\Modules\Front\Presenters\HomePresenter` και ο παρουσιαστής `Admin:Dashboard` στην κλάση `App\Modules\Admin\Presenters\DashboardPresenter`.
+Σύμφωνα με αυτή τη διαμόρφωση, ο παρουσιαστής `Front:Home` αντιστοιχίζεται στην κλάση `App\UI\Front\Home\HomePresenter`, ενώ ο παρουσιαστής `Api:OAuth` αντιστοιχίζεται στην κλάση `App\Api\OAuthPresenter`.
-Είναι πιο πρακτικό να δημιουργήσετε έναν γενικό κανόνα (αστέρι) για να αντικαταστήσετε τους δύο πρώτους. Ο επιπλέον αστερίσκος θα προστεθεί στη μάσκα κλάσης μόνο για την ενότητα:
+Δεδομένου ότι οι ενότητες `Front` και `Admin` έχουν παρόμοια προσέγγιση αντιστοίχισης και είναι πιθανό να υπάρχουν περισσότερες τέτοιες ενότητες, είναι δυνατόν να δημιουργηθεί ένας γενικός κανόνας που να τις αντικαθιστά. Ένας νέος αστερίσκος για την ενότητα προστίθεται στη μάσκα κλάσης:
```neon
application:
mapping:
- *: App\Modules\*\Presenters\*Presenter
+ *: App\UI\*\**Presenter
Api: App\Api\*Presenter
```
-Τι γίνεται όμως αν χρησιμοποιούμε φωλιασμένες ενότητες και έχουμε έναν παρουσιαστή `Admin:User:Edit`; Σε αυτή την περίπτωση, το τμήμα με τον αστερίσκο που αντιπροσωπεύει την ενότητα για κάθε επίπεδο απλώς επαναλαμβάνεται και το αποτέλεσμα είναι η κλάση `App\Modules\Admin\User\Presenters\EditPresenter`.
+Για πολυεπίπεδες φωλιασμένες ενότητες, όπως ο παρουσιαστής `Admin:User:Edit`, το τμήμα αστερίσκου επαναλαμβάνεται για κάθε επίπεδο, με αποτέλεσμα την κλάση `App\UI\Admin\User\Edit\EditPresenter`.
-Ένας εναλλακτικός συμβολισμός είναι η χρήση ενός πίνακα που αποτελείται από τρία τμήματα αντί για συμβολοσειρά. Αυτή η σημειογραφία είναι ισοδύναμη με την προηγούμενη:
+Ένας εναλλακτικός συμβολισμός είναι η χρήση ενός πίνακα που αποτελείται από τρία τμήματα αντί για μια συμβολοσειρά. Αυτός ο συμβολισμός είναι ισοδύναμος με τον προηγούμενο:
```neon
application:
mapping:
- *: [App\Modules, *, Presenters\*Presenter]
+ *: [App\UI, *, **Presenter]
+ Api: [App\Api, '', *Presenter]
```
-Η προεπιλεγμένη τιμή είναι `*: *Module\*Presenter`.
+Αν έχουμε μόνο έναν κανόνα στη διαμόρφωση, τον γενικό, μπορούμε να γράψουμε συνοπτικά:
+
+```neon
+application:
+ mapping: App\UI\*\**Presenter
+```
diff --git a/application/el/presenters.texy b/application/el/presenters.texy
index 07cb8b4482..8b7a737672 100644
--- a/application/el/presenters.texy
+++ b/application/el/presenters.texy
@@ -60,7 +60,7 @@ class ArticlePresenter extends Nette\Application\UI\Presenter
Είναι σημαντικό ότι `action()` καλείται πριν από την `render()`, ώστε μέσα σε αυτό να μπορούμε ενδεχομένως να αλλάξουμε την επόμενη πορεία του κύκλου ζωής, δηλαδή να αλλάξουμε το πρότυπο που θα αποδοθεί και επίσης τη μέθοδο `render()` που θα κληθεί, χρησιμοποιώντας το `setView('otherView')`.
-Οι παράμετροι από το αίτημα περνούν στη μέθοδο. Είναι δυνατόν και συνιστάται να καθορίσετε τύπους για τις παραμέτρους, π.χ. `actionShow(int $id, string $slug = null)` - αν η παράμετρος `id` λείπει ή αν δεν είναι ακέραιος αριθμός, ο παρουσιαστής επιστρέφει [σφάλμα 404 |#Error 404 etc.] και τερματίζει τη λειτουργία.
+Οι παράμετροι από το αίτημα περνούν στη μέθοδο. Είναι δυνατόν και συνιστάται να καθορίσετε τύπους για τις παραμέτρους, π.χ. `actionShow(int $id, ?string $slug = null)` - αν η παράμετρος `id` λείπει ή αν δεν είναι ακέραιος αριθμός, ο παρουσιαστής επιστρέφει [σφάλμα 404 |#Error 404 etc.] και τερματίζει τη λειτουργία.
`handle(args...)` .{toc: handle()}
@@ -205,7 +205,7 @@ $this->redirect(/* ... */);
Σφάλμα 404 κ.λπ. .[#toc-error-404-etc]
======================================
-Όταν δεν μπορούμε να ικανοποιήσουμε το αίτημα επειδή για παράδειγμα το άρθρο που θέλουμε να εμφανίσουμε δεν υπάρχει στη βάση δεδομένων, θα πετάξουμε το σφάλμα 404 χρησιμοποιώντας τη μέθοδο `error(string $message = null, int $httpCode = 404)`, η οποία αντιπροσωπεύει το σφάλμα HTTP 404:
+Όταν δεν μπορούμε να ικανοποιήσουμε το αίτημα επειδή για παράδειγμα το άρθρο που θέλουμε να εμφανίσουμε δεν υπάρχει στη βάση δεδομένων, θα πετάξουμε το σφάλμα 404 χρησιμοποιώντας τη μέθοδο `error(?string $message = null, int $httpCode = 404)`, η οποία αντιπροσωπεύει το σφάλμα HTTP 404:
```php
public function renderShow(int $id): void
@@ -236,6 +236,32 @@ public function actionData(): void
```
+Παράμετροι αίτησης .[#toc-request-parameters]
+=============================================
+
+Ο παρουσιαστής, όπως και κάθε στοιχείο, λαμβάνει τις παραμέτρους του από την αίτηση HTTP. Οι τιμές τους μπορούν να ανακτηθούν χρησιμοποιώντας τη μέθοδο `getParameter($name)` ή `getParameters()`. Οι τιμές είναι συμβολοσειρές ή πίνακες συμβολοσειρών, ουσιαστικά ακατέργαστα δεδομένα που λαμβάνονται απευθείας από τη διεύθυνση URL.
+
+Για μεγαλύτερη ευκολία, συνιστούμε να κάνετε τις παραμέτρους προσβάσιμες μέσω ιδιοτήτων. Απλά σχολιάστε τις με την εντολή `#[Parameter]` χαρακτηριστικό:
+
+```php
+use Nette\Application\Attributes\Parameter; // αυτή η γραμμή είναι σημαντική
+
+class HomePresenter extends Nette\Application\UI\Presenter
+{
+ #[Parameter]
+ public string $theme; // πρέπει να είναι δημόσια
+}
+```
+
+Για τις ιδιότητες, προτείνουμε να προσδιορίσετε τον τύπο δεδομένων (π.χ. `string`). Στη συνέχεια, η Nette θα μετατρέψει αυτόματα την τιμή με βάση αυτόν. Οι τιμές των παραμέτρων μπορούν επίσης να [επικυρωθούν |#Validation of Parameters].
+
+Κατά τη δημιουργία ενός συνδέσμου, μπορείτε να ορίσετε απευθείας την τιμή για τις παραμέτρους:
+
+```latte
+click
+```
+
+
Εμμένουσες παράμετροι .[#toc-persistent-parameters]
===================================================
@@ -257,7 +283,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter
Εάν το `$this->lang` έχει μια τιμή όπως `'en'`, τότε οι σύνδεσμοι που δημιουργούνται με χρήση των `link()` ή `n:href` θα περιέχουν επίσης την παράμετρο `lang=en`. Και όταν ο σύνδεσμος πατηθεί, θα είναι και πάλι `$this->lang = 'en'`.
-Για τις ιδιότητες, συνιστούμε να περιλαμβάνετε τον τύπο δεδομένων (π.χ. `string`) και μπορείτε επίσης να συμπεριλάβετε μια προεπιλεγμένη τιμή. Οι τιμές των παραμέτρων μπορούν να [επικυρωθούν |#Validation of Persistent Parameters].
+Για τις ιδιότητες, συνιστούμε να συμπεριλάβετε τον τύπο δεδομένων (π.χ. `string`) και μπορείτε επίσης να συμπεριλάβετε μια προεπιλεγμένη τιμή. Οι τιμές των παραμέτρων μπορούν να [επικυρωθούν |#Validation of Parameters].
Οι μόνιμες παράμετροι μεταβιβάζονται μεταξύ όλων των ενεργειών ενός συγκεκριμένου παρουσιαστή από προεπιλογή. Για να τις περάσετε μεταξύ πολλαπλών παρουσιαστών, πρέπει να τις ορίσετε είτε:
@@ -307,18 +333,12 @@ class ProductPresenter extends Nette\Application\UI\Presenter
Όσα έχουμε δείξει μέχρι στιγμής σε αυτό το κεφάλαιο μάλλον αρκούν. Οι επόμενες γραμμές απευθύνονται σε όσους ενδιαφέρονται για τους παρουσιαστές σε βάθος και θέλουν να μάθουν τα πάντα.
-Απαίτηση και παράμετροι .[#toc-requirement-and-parameters]
-----------------------------------------------------------
+Επικύρωση των παραμέτρων .[#toc-validation-of-parameters]
+---------------------------------------------------------
-Το αίτημα που χειρίζεται ο παρουσιαστής είναι το αντικείμενο [api:Nette\Application\Request] και επιστρέφεται από τη μέθοδο του παρουσιαστή `getRequest()`. Περιλαμβάνει έναν πίνακα παραμέτρων και κάθε μία από αυτές ανήκει είτε σε κάποιο από τα συστατικά είτε απευθείας στον παρουσιαστή (ο οποίος στην πραγματικότητα είναι επίσης ένα συστατικό, αν και ειδικό). Έτσι, η Nette ανακατανέμει τις παραμέτρους και περνάει μεταξύ των επιμέρους συστατικών (και του παρουσιαστή) καλώντας τη μέθοδο `loadState(array $params)`. Οι παράμετροι μπορούν να ληφθούν με τη μέθοδο `getParameters(): array`, μεμονωμένα με τη χρήση του `getParameter($name)`. Οι τιμές των παραμέτρων είναι συμβολοσειρές ή πίνακες συμβολοσειρών, είναι ουσιαστικά ακατέργαστα δεδομένα που λαμβάνονται απευθείας από μια διεύθυνση URL.
+Οι τιμές των [παραμέτρων αίτησης |#request parameters] και των [μόνιμων παραμέτρων |#persistent parameters] που λαμβάνονται από τις διευθύνσεις URL εγγράφονται στις ιδιότητες από τη μέθοδο `loadState()`. Ελέγχει επίσης αν ο τύπος δεδομένων που καθορίζεται στην ιδιότητα ταιριάζει, διαφορετικά θα απαντήσει με σφάλμα 404 και η σελίδα δεν θα εμφανιστεί.
-
-Επικύρωση μόνιμων παραμέτρων .[#toc-validation-of-persistent-parameters]
-------------------------------------------------------------------------
-
-Οι τιμές των [μόνιμων παραμέτρων |#persistent parameters] που λαμβάνονται από τις διευθύνσεις URL εγγράφονται στις ιδιότητες με τη μέθοδο `loadState()`. Ελέγχει επίσης αν ο τύπος δεδομένων που καθορίζεται στην ιδιότητα ταιριάζει, διαφορετικά θα απαντήσει με σφάλμα 404 και η σελίδα δεν θα εμφανιστεί.
-
-Ποτέ μην εμπιστεύεστε τυφλά τις μόνιμες παραμέτρους, καθώς μπορούν εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση URL. Για παράδειγμα, με αυτόν τον τρόπο ελέγχουμε αν το `$this->lang` είναι μεταξύ των υποστηριζόμενων γλωσσών. Ένας καλός τρόπος για να το κάνετε αυτό είναι να παρακάμψετε τη μέθοδο `loadState()` που αναφέρθηκε παραπάνω:
+Ποτέ μην εμπιστεύεστε τυφλά τις παραμέτρους, καθώς μπορούν εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση URL. Για παράδειγμα, με αυτόν τον τρόπο ελέγχουμε αν το `$this->lang` είναι μεταξύ των υποστηριζόμενων γλωσσών. Ένας καλός τρόπος για να το κάνετε αυτό είναι να παρακάμψετε τη μέθοδο `loadState()` που αναφέρθηκε παραπάνω:
```php
class ProductPresenter extends Nette\Application\UI\Presenter
@@ -341,7 +361,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter
Αποθήκευση και επαναφορά της αίτησης .[#toc-save-and-restore-the-request]
-------------------------------------------------------------------------
-Μπορείτε να αποθηκεύσετε την τρέχουσα αίτηση σε μια συνεδρία ή να την επαναφέρετε από τη συνεδρία και να αφήσετε τον παρουσιαστή να την εκτελέσει ξανά. Αυτό είναι χρήσιμο, για παράδειγμα, όταν ένας χρήστης συμπληρώνει μια φόρμα και λήγει η σύνδεσή του. Για να μην χαθούν δεδομένα, πριν από την ανακατεύθυνση στη σελίδα σύνδεσης, αποθηκεύουμε το τρέχον αίτημα στη σύνοδο χρησιμοποιώντας το `$reqId = $this->storeRequest()`, το οποίο επιστρέφει ένα αναγνωριστικό με τη μορφή σύντομης συμβολοσειράς και το περνάει ως παράμετρο στον παρουσιαστή σύνδεσης.
+Το αίτημα που χειρίζεται ο παρουσιαστής είναι ένα αντικείμενο [api:Nette\Application\Request] και επιστρέφεται από τη μέθοδο του παρουσιαστή `getRequest()`.
+
+Μπορείτε να αποθηκεύσετε την τρέχουσα αίτηση σε μια συνεδρία ή να την επαναφέρετε από τη συνεδρία και να αφήσετε τον παρουσιαστή να την εκτελέσει ξανά. Αυτό είναι χρήσιμο, για παράδειγμα, όταν ένας χρήστης συμπληρώνει μια φόρμα και η σύνδεσή του λήγει. Για να μην χαθούν δεδομένα, πριν από την ανακατεύθυνση στη σελίδα σύνδεσης, αποθηκεύουμε την τρέχουσα αίτηση στη σύνοδο χρησιμοποιώντας τη μέθοδο `$reqId = $this->storeRequest()`, η οποία επιστρέφει ένα αναγνωριστικό με τη μορφή σύντομης συμβολοσειράς και το περνάει ως παράμετρο στον παρουσιαστή σύνδεσης.
Μετά την είσοδο, καλούμε τη μέθοδο `$this->restoreRequest($reqId)`, η οποία παραλαμβάνει το αίτημα από τη σύνοδο και το προωθεί σε αυτήν. Η μέθοδος επαληθεύει ότι το αίτημα δημιουργήθηκε από τον ίδιο χρήστη που τώρα έχει συνδεθεί είναι. Αν συνδεθεί άλλος χρήστης ή το κλειδί είναι άκυρο, δεν κάνει τίποτα και το πρόγραμμα συνεχίζει.
@@ -362,7 +384,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter
Μπορείτε επίσης να επικαλεστείτε την κανονικοποίηση χειροκίνητα χρησιμοποιώντας τη μέθοδο `canonicalize()`, η οποία, όπως και η μέθοδος `link()`, λαμβάνει τον παρουσιαστή, τις ενέργειες και τις παραμέτρους ως ορίσματα. Δημιουργεί έναν σύνδεσμο και τον συγκρίνει με την τρέχουσα διεύθυνση URL. Εάν είναι διαφορετική, ανακατευθύνει στον δημιουργημένο σύνδεσμο.
```php
-public function actionShow(int $id, string $slug = null): void
+public function actionShow(int $id, ?string $slug = null): void
{
$realSlug = $this->facade->getSlugForId($id);
// ανακατευθύνει εάν το $slug είναι διαφορετικό από το $realSlug
@@ -425,6 +447,51 @@ $this->sendResponse(new Responses\CallbackResponse($callback));
```
+Περιορισμός πρόσβασης με χρήση `#[Requires]` .[#toc-access-restriction-using-requires]{data-version:3.2.2}
+----------------------------------------------------------------------------------------------------------
+
+Το `#[Requires]` παρέχει προηγμένες επιλογές για τον περιορισμό της πρόσβασης στους παρουσιαστές και τις μεθόδους τους. Μπορεί να χρησιμοποιηθεί για τον προσδιορισμό μεθόδων HTTP, την απαίτηση αιτήσεων AJAX, τον περιορισμό της πρόσβασης στην ίδια προέλευση και τον περιορισμό της πρόσβασης μόνο στην προώθηση. Το χαρακτηριστικό μπορεί να εφαρμοστεί σε κλάσεις παρουσιαστών καθώς και σε μεμονωμένες μεθόδους όπως οι `action()`, `render()`, `handle()`, και `createComponent()`.
+
+Μπορείτε να καθορίσετε αυτούς τους περιορισμούς:
+- σε μεθόδους HTTP: `#[Requires(methods: ['GET', 'POST'])]`
+- AJAX: `#[Requires(ajax: true)]`
+- πρόσβαση μόνο από την ίδια προέλευση: `#[Requires(sameOrigin: true)]`
+- πρόσβαση μόνο μέσω προώθησης: `#[Requires(forward: true)]`
+- περιορισμοί σε συγκεκριμένες ενέργειες: `#[Requires(actions: 'default')]`
+
+Για λεπτομέρειες, ανατρέξτε στην ενότητα [Πώς να χρησιμοποιήσετε το Requires attribute |best-practices:attribute-requires].
+
+
+Έλεγχος μεθόδου HTTP .[#toc-http-method-check]
+----------------------------------------------
+
+Στη Nette, οι παρουσιαστές επαληθεύουν αυτόματα τη μέθοδο HTTP κάθε εισερχόμενης αίτησης κυρίως για λόγους ασφαλείας. Από προεπιλογή, επιτρέπονται οι μέθοδοι `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH`.
+
+Εάν θέλετε να ενεργοποιήσετε επιπλέον μεθόδους, όπως η `OPTIONS`, μπορείτε να χρησιμοποιήσετε την εντολή `#[Requires]` (από την εφαρμογή Nette Application v3.2):
+
+```php
+#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
+class MyPresenter extends Nette\Application\UI\Presenter
+{
+}
+```
+
+Στην έκδοση 3.1, η επαλήθευση πραγματοποιείται στο `checkHttpMethod()`, το οποίο ελέγχει αν η μέθοδος που καθορίζεται στην αίτηση περιλαμβάνεται στον πίνακα `$presenter->allowedMethods`. Προσθέστε μια μέθοδο ως εξής:
+
+```php
+class MyPresenter extends Nette\Application\UI\Presenter
+{
+ protected function checkHttpMethod(): void
+ {
+ $this->allowedMethods[] = 'OPTIONS';
+ parent::checkHttpMethod();
+ }
+}
+```
+
+Είναι ζωτικής σημασίας να τονιστεί ότι αν επιτρέψετε τη μέθοδο `OPTIONS`, πρέπει επίσης να τη χειριστείτε σωστά μέσα στον παρουσιαστή σας. Αυτή η μέθοδος χρησιμοποιείται συχνά ως το λεγόμενο preflight request, το οποίο οι φυλλομετρητές στέλνουν αυτόματα πριν από το πραγματικό αίτημα, όταν είναι απαραίτητο να καθοριστεί αν το αίτημα επιτρέπεται από την άποψη της πολιτικής CORS (Cross-Origin Resource Sharing). Εάν επιτρέψετε αυτή τη μέθοδο αλλά δεν υλοποιήσετε μια κατάλληλη απάντηση, μπορεί να οδηγήσει σε ασυνέπειες και πιθανά ζητήματα ασφάλειας.
+
+
Περαιτέρω ανάγνωση .[#toc-further-reading]
==========================================
diff --git a/application/el/routing.texy b/application/el/routing.texy
index 448fa6f67b..78ebb607a0 100644
--- a/application/el/routing.texy
+++ b/application/el/routing.texy
@@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [
@@ -225,7 +225,7 @@ $router->addRoute('/[/]', [
]);
```
-Ή μπορούμε να χρησιμοποιήσουμε αυτή τη μορφή, παρατηρήστε την αναδιατύπωση της κανονικής έκφρασης επικύρωσης:
+Για μια πιο λεπτομερή προδιαγραφή, μπορεί να χρησιμοποιηθεί μια ακόμη πιο εκτεταμένη μορφή, όπου εκτός από τις προεπιλεγμένες τιμές, μπορούν να οριστούν και άλλες ιδιότητες παραμέτρων, όπως μια κανονική έκφραση επικύρωσης (βλ. την παράμετρο `id` ):
```php
use Nette\Routing\Route;
@@ -243,7 +243,7 @@ $router->addRoute('/[/]', [
]);
```
-Αυτές οι πιο ομιλητικές μορφές είναι χρήσιμες για την προσθήκη άλλων μεταδεδομένων.
+Είναι σημαντικό να σημειωθεί ότι εάν οι παράμετροι που ορίζονται στον πίνακα δεν περιλαμβάνονται στη μάσκα διαδρομής, οι τιμές τους δεν μπορούν να αλλάξουν, ούτε καν με τη χρήση παραμέτρων ερωτήματος που καθορίζονται μετά από ένα ερωτηματικό στη διεύθυνση URL.
Φίλτρα και μεταφράσεις .[#toc-filters-and-translations]
@@ -477,10 +477,10 @@ $router->addRoute('index.html \.html?|\.php|>', /* ... */);
Ενσωμάτωση .[#toc-integration]
==============================
-Για να συνδέσουμε τον δρομολογητή μας στην εφαρμογή, πρέπει να ενημερώσουμε το DI container σχετικά με αυτόν. Ο ευκολότερος τρόπος είναι να προετοιμάσουμε το εργοστάσιο που θα κατασκευάσει το αντικείμενο του δρομολογητή και να πούμε στη διαμόρφωση του δοχείου να το χρησιμοποιήσει. Ας πούμε λοιπόν ότι γράφουμε μια μέθοδο για το σκοπό αυτό `App\Router\RouterFactory::createRouter()`:
+Για να συνδέσουμε τον δρομολογητή μας στην εφαρμογή, πρέπει να ενημερώσουμε το DI container σχετικά με αυτόν. Ο ευκολότερος τρόπος είναι να προετοιμάσουμε το εργοστάσιο που θα κατασκευάσει το αντικείμενο του δρομολογητή και να πούμε στη διαμόρφωση του δοχείου να το χρησιμοποιήσει. Ας πούμε λοιπόν ότι γράφουμε μια μέθοδο για το σκοπό αυτό `App\Core\RouterFactory::createRouter()`:
```php
-namespace App\Router;
+namespace App\Core;
use Nette\Application\Routers\RouteList;
@@ -499,7 +499,7 @@ class RouterFactory
```neon
services:
- - App\Router\RouterFactory::createRouter
+ - App\Core\RouterFactory::createRouter
```
Οποιεσδήποτε εξαρτήσεις, όπως μια σύνδεση βάσης δεδομένων κ.λπ., περνούν στη μέθοδο factory ως παράμετροι με τη χρήση [αυτόματης σύνδεσης |dependency-injection:autowiring]:
@@ -663,7 +663,7 @@ $router->addRoute(/* ... */);
Έτσι και πάλι θα δημιουργήσουμε μια μέθοδο που θα κατασκευάσει ένα δρομολογητή, για παράδειγμα:
```php
-namespace App\Router;
+namespace App\Core;
use Nette\Routing\RouteList;
@@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class);
Ή θα δημιουργήσουμε αντικείμενα απευθείας:
```php
-$router = App\Router\RouterFactory::createRouter();
+$router = App\Core\RouterFactory::createRouter();
$httpRequest = (new Nette\Http\RequestFactory)->fromGlobals();
```
diff --git a/application/el/templates.texy b/application/el/templates.texy
index 4b83599f45..dfe884dd1b 100644
--- a/application/el/templates.texy
+++ b/application/el/templates.texy
@@ -34,35 +34,81 @@
Ορίζει το μπλοκ `content`, το οποίο εισάγεται στη θέση του `{include content}` στη διάταξη, και επίσης επαναπροσδιορίζει το μπλοκ `title`, το οποίο αντικαθιστά το `{block title}` στη διάταξη. Προσπαθήστε να φανταστείτε το αποτέλεσμα.
-Αναζήτηση προτύπων .[#toc-search-for-templates]
------------------------------------------------
+Αναζήτηση προτύπου .[#toc-template-lookup]
+------------------------------------------
-Η διαδρομή προς τα πρότυπα προκύπτει σύμφωνα με μια απλή λογική. Προσπαθεί να δει αν ένα από αυτά τα αρχεία προτύπων υπάρχει σε σχέση με τον κατάλογο όπου βρίσκεται η κλάση presenter, όπου `` είναι το όνομα του τρέχοντος παρουσιαστή και `` είναι το όνομα της τρέχουσας δράσης:
+Στους παρουσιαστές, δεν χρειάζεται να καθορίσετε ποιο πρότυπο πρέπει να αποδοθεί- το πλαίσιο θα καθορίσει αυτόματα τη διαδρομή, διευκολύνοντας την κωδικοποίηση για εσάς.
-- `templates//.latte`
-- `templates/..latte`
+Αν χρησιμοποιείτε μια δομή καταλόγου όπου κάθε παρουσιαστής έχει το δικό του κατάλογο, απλά τοποθετήστε το πρότυπο σε αυτόν τον κατάλογο κάτω από το όνομα της ενέργειας (π.χ. προβολή). Για παράδειγμα, για τη δράση `default`, χρησιμοποιήστε το πρότυπο `default.latte`:
-Αν το πρότυπο δεν βρεθεί, θα προσπαθήσει να ψάξει στον κατάλογο `templates` ένα επίπεδο πιο πάνω, δηλαδή στο ίδιο επίπεδο με τον κατάλογο με την κλάση παρουσιαστή.
+/--pre
+app/
+└── UI/
+ └── Home/
+ ├── HomePresenter.php
+ └── default.latte
+\--
-Εάν το πρότυπο δεν βρεθεί ούτε εκεί, η απάντηση είναι ένα [σφάλμα 404 |presenters#Error 404 etc.].
+Εάν χρησιμοποιείτε μια δομή όπου οι παρουσιαστές βρίσκονται μαζί σε έναν κατάλογο και τα πρότυπα σε έναν φάκελο `templates`, αποθηκεύστε το είτε σε ένα αρχείο `..latte` είτε στο `/.latte`:
-Μπορείτε επίσης να αλλάξετε την προβολή χρησιμοποιώντας το `$this->setView('otherView')`. Ή, αντί για αναζήτηση, καθορίστε απευθείας το όνομα του αρχείου προτύπου χρησιμοποιώντας τη διεύθυνση `$this->template->setFile('/path/to/template.latte')`.
+/--pre
+app/
+└── Presenters/
+ ├── HomePresenter.php
+ └── templates/
+ ├── Home.default.latte ← 1st variant
+ └── Home/
+ └── default.latte ← 2nd variant
+\--
+
+Ο κατάλογος `templates` μπορεί επίσης να τοποθετηθεί ένα επίπεδο ψηλότερα, στο ίδιο επίπεδο με τον κατάλογο με τις κλάσεις παρουσιαστών.
+
+Εάν το πρότυπο δεν βρεθεί, ο παρουσιαστής απαντά με το [σφάλμα 404 - σελίδα δεν βρέθηκε |presenters#Error 404 etc].
+
+Μπορείτε να αλλάξετε την προβολή χρησιμοποιώντας το `$this->setView('anotherView')`. Είναι επίσης δυνατό να καθορίσετε απευθείας το αρχείο προτύπου με το `$this->template->setFile('/path/to/template.latte')`.
.[note]
-Μπορείτε να αλλάξετε τις διαδρομές στις οποίες αναζητούνται τα πρότυπα υπερκαλύπτοντας τη μέθοδο [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], η οποία επιστρέφει έναν πίνακα πιθανών διαδρομών αρχείων.
+Τα αρχεία στα οποία αναζητούνται τα πρότυπα μπορούν να αλλάξουν με την παράκαμψη της μεθόδου [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], η οποία επιστρέφει έναν πίνακα πιθανών ονομάτων αρχείων.
+
+
+Αναζήτηση προτύπων διάταξης .[#toc-layout-template-lookup]
+----------------------------------------------------------
+
+Η Nette αναζητά επίσης αυτόματα το αρχείο διάταξης.
+
+Εάν χρησιμοποιείτε μια δομή καταλόγου όπου κάθε παρουσιαστής έχει το δικό του κατάλογο, τοποθετήστε τη διάταξη είτε στο φάκελο με τον παρουσιαστή, εάν αφορά μόνο αυτόν, είτε ένα επίπεδο ψηλότερα εάν είναι κοινή για πολλούς παρουσιαστές:
+
+/--pre
+app/
+└── UI/
+ ├── @layout.latte ← common layout
+ └── Home/
+ ├── @layout.latte ← only for Home presenter
+ ├── HomePresenter.php
+ └── default.latte
+\--
+
+Εάν χρησιμοποιείτε μια δομή όπου οι παρουσιαστές είναι ομαδοποιημένοι σε έναν κατάλογο και τα πρότυπα βρίσκονται σε έναν φάκελο `templates`, η διάταξη αναμένεται στις ακόλουθες θέσεις:
-Η διάταξη αναμένεται στα ακόλουθα αρχεία:
+/--pre
+app/
+└── Presenters/
+ ├── HomePresenter.php
+ └── templates/
+ ├── @layout.latte ← common layout
+ ├── Home.@layout.latte ← only for Home, 1st variant
+ └── Home/
+ └── @layout.latte ← only for Home, 2nd variant
+\--
-- `templates//@.latte`
-- `templates/.@.latte`
-- `templates/@.latte` διάταξη κοινή για πολλούς παρουσιαστές
+Εάν ο παρουσιαστής βρίσκεται σε μια [ενότητα |modules], θα αναζητήσει επίσης πιο πάνω στο δέντρο καταλόγων σύμφωνα με την ένθεση της ενότητας.
-`` είναι το όνομα του τρέχοντος παρουσιαστή και `` είναι το όνομα της διάταξης, η οποία είναι εξ ορισμού `'layout'`. Το όνομα μπορεί να αλλάξει με το `$this->setLayout('otherLayout')`, έτσι ώστε να δοκιμάζονται τα αρχεία `@otherLayout.latte`.
+Το όνομα της διάταξης μπορεί να αλλάξει χρησιμοποιώντας το `$this->setLayout('layoutAdmin')` και τότε θα αναμένεται στο αρχείο `@layoutAdmin.latte`. Μπορείτε επίσης να καθορίσετε απευθείας το αρχείο προτύπου διάταξης χρησιμοποιώντας το `$this->setLayout('/path/to/template.latte')`.
-Μπορείτε επίσης να καθορίσετε απευθείας το όνομα του αρχείου του προτύπου διάταξης χρησιμοποιώντας το `$this->setLayout('/path/to/template.latte')`. Η χρήση του `$this->setLayout(false)` θα απενεργοποιήσει την αναζήτηση διάταξης.
+Η χρήση του `$this->setLayout(false)` ή της ετικέτας `{layout none}` μέσα στο πρότυπο απενεργοποιεί την αναζήτηση διάταξης.
.[note]
-Μπορείτε να αλλάξετε τις διαδρομές στις οποίες αναζητούνται τα πρότυπα με την παράκαμψη της μεθόδου [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], η οποία επιστρέφει έναν πίνακα πιθανών διαδρομών αρχείων.
+Τα αρχεία στα οποία αναζητούνται τα πρότυπα διάταξης μπορούν να αλλάξουν με την παράκαμψη της μεθόδου [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], η οποία επιστρέφει έναν πίνακα πιθανών ονομάτων αρχείων.
Μεταβλητές στο πρότυπο .[#toc-variables-in-the-template]
@@ -104,7 +150,7 @@ class ArticleTemplate extends Nette\Bridges\ApplicationLatte\Template
Μπορείτε επίσης να αφεθείτε στην πολυτέλεια του ψιθυρίσματος στα πρότυπα, απλά εγκαταστήστε το πρόσθετο Latte στο PhpStorm και καθορίστε το όνομα της κλάσης στην αρχή του προτύπου, δείτε το άρθρο "Latte: πώς να πληκτρολογήσετε το σύστημα":https://blog.nette.org/el/latte-pos-na-chresimopoiesete-to-systema-typon:
```latte
-{templateType App\Presenters\ArticleTemplate}
+{templateType App\UI\Article\ArticleTemplate}
...
```
@@ -176,7 +222,7 @@ public function beforeRender(): void
Latte έκδοση 3 προσφέρει έναν πιο προηγμένο τρόπο δημιουργώντας μια [επέκταση |latte:creating-extension] για κάθε έργο ιστού. Εδώ είναι ένα πρόχειρο παράδειγμα μιας τέτοιας κλάσης:
```php
-namespace App\Templating;
+namespace App\UI\Accessory;
final class LatteExtension extends Latte\Extension
{
@@ -214,7 +260,7 @@ final class LatteExtension extends Latte\Extension
```neon
latte:
extensions:
- - App\Templating\LatteExtension
+ - App\UI\Accessory\LatteExtension
```
@@ -239,7 +285,7 @@ protected function beforeRender(): void
```neon
latte:
extensions:
- - Latte\Essential\TranslatorExtension
+ - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```
Ο μεταφραστής μπορεί στη συνέχεια να χρησιμοποιηθεί, για παράδειγμα, ως φίλτρο `|translate`, με πρόσθετες παραμέτρους που περνούν στη μέθοδο `translate()` (βλ. `foo, bar`):
diff --git a/application/en/@home.texy b/application/en/@home.texy
index fac8c0d767..0892ae2d2d 100644
--- a/application/en/@home.texy
+++ b/application/en/@home.texy
@@ -28,8 +28,9 @@ composer require nette/application
| version | compatible with PHP
|-----------|-------------------
-| Nette Application 4.0 | PHP 8.0 – 8.2
-| Nette Application 3.1 | PHP 7.2 – 8.2
+| Nette Application 4.0 | PHP 8.1 – 8.3
+| Nette Application 3.2 | PHP 8.1 – 8.3
+| 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/ajax.texy b/application/en/ajax.texy
index f43ff28c2f..819a6e4627 100644
--- a/application/en/ajax.texy
+++ b/application/en/ajax.texy
@@ -3,10 +3,10 @@ AJAX & Snippets
-Modern web applications nowadays run half on a server and half in a browser. AJAX is a vital uniting factor. What support does the Nette Framework offer?
-- sending template fragments (so-called *snippets*)
+In the era of modern web applications, where functionality often spans between the server and the browser, AJAX is an essential connecting element. What options does the Nette Framework offer in this area?
+- sending parts of the template, so-called snippets
- passing variables between PHP and JavaScript
-- AJAX applications debugging
+- tools for debugging AJAX requests
@@ -14,29 +14,32 @@ Modern web applications nowadays run half on a server and half in a browser. AJA
AJAX Request
============
-An AJAX request does not differ from a classic request - the presenter is called with a specific view and parameters. It is also up to the presenter how to respond to it: it can use its own routine, which returns an HTML code fragment (HTML snippet), an XML document, a JSON object, or JavaScript code.
+An AJAX request fundamentally does not differ from a classic HTTP request. A presenter is called with specific parameters. It's up to the presenter how to respond to the request - it can return data in JSON format, send a part of HTML code, an XML document, etc.
-On the server side, an AJAX request can be detected using the service method [encapsulating the HTTP request |http:request] `$httpRequest->isAjax()` (detects based on the HTTP header `X-Requested-With`). Inside the presenter, a shortcut is available in the form of the method `$this->isAjax()`.
+On the browser side, we initiate an AJAX request using the `fetch()` function:
-There is a pre-processed object called `payload` dedicated to sending data to the browser in JSON.
-
-```php
-public function actionDelete(int $id): void
-{
- if ($this->isAjax()) {
- $this->payload->message = 'Success';
- }
- // ...
-}
+```js
+fetch(url, {
+ headers: {'X-Requested-With': 'XMLHttpRequest'},
+})
+.then(response => response.json())
+.then(payload => {
+ // processing the response
+});
```
-For a full control over your JSON output use the `sendJson` method in your presenter. It terminates presenter immediately and you'll do without a template:
+On the server side, an AJAX request is recognized by the `$httpRequest->isAjax()` method of the service [encapsulating the HTTP request |http:request]. It uses the HTTP header `X-Requested-With`, so it's essential to send it. Within the presenter, you can use the `$this->isAjax()` method.
+
+If you want to send data in JSON format, use the [`sendJson()` |presenters#Sending a response] method. The method also terminates the presenter's activity.
```php
-$this->sendJson(['key' => 'value', /* ... */]);
+public function actionExport(): void
+{
+ $this->sendJson($this->model->getData);
+}
```
-If we want to send HTML, we can either set a special template for AJAX requests:
+If you plan to respond with a special template designed for AJAX, you can do it as follows:
```php
public function handleClick($param): void
@@ -49,22 +52,38 @@ 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 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, or clippings, allow you to update only parts of the page, instead of reloading the entire page. This is faster and more efficient, and 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.), instead of loading the entire page, an AJAX request is made. The code in the presenter performs the action and decides which snippets need updating. Nette renders these snippets and sends them in the form of a JSON array. The handling code in the browser then inserts the received snippets back into the page. Therefore, only the code of the changed snippets is transferred, saving bandwidth and speeding up loading compared to transferring the entire page content.
+
+
Naja
-====
+----
-The [Naja library|https://naja.js.org] is used to handle AJAX requests on the browser side. [Install |https://naja.js.org/#/guide/01-install-setup-naja] it as a node.js package (to use with Webpack, Rollup, Vite, Parcel and more):
+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 such as Webpack, Rollup, Vite, Parcel, and others):
```shell
npm install naja
```
-…or insert it directly into the page template:
+... or insert it directly into the page template:
```html
```
-To create an AJAX request from a regular link (signal) or form submittion, simply mark the relevant link, form, or button with the class `ajax`:
+First you need to [initialize |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] the library:
+
+```js
+naja.initialize();
+```
+
+To make an ordinary link (signal) or form submission an AJAX request, simply mark the respective link, form, or button with the `ajax` class:
```html
Go
@@ -74,64 +93,39 @@ To create an AJAX request from a regular link (signal) or form submittion, simpl
or
+
```
-Snippets
-========
-
-There is a far more powerful tool of built-in AJAX support – snippets. Using them makes it possible to turn a regular application into an AJAX one using only a few lines of code. How it all works is demonstrated in the Fifteen example whose code is also accessible in the build or on [GitHub |https://github.com/nette-examples/fifteen].
-
-The way snippets work is that the whole page is transferred during the initial (i.e. non-AJAX) request and then with every AJAX [subrequest |components#signal] (request of the same view of the same presenter) only the code of the changed parts is transferred in the `payload` repository mentioned earlier.
+Redrawing Snippets
+------------------
-Snippets may remind you of Hotwire for Ruby on Rails or Symfony UX Turbo, but Nette came up with them fourteen years earlier.
-
-
-Invalidation of Snippets
-========================
-
-Each descendant of class [Control |components] (which a Presenter is too) is able to remember whether there were any changes during a request that require it to re-render. There are a pair of methods to handle this: `redrawControl()` and `isControlInvalid()`. An example:
+Every object of the [Control |components] class (including the Presenter itself) keeps a record of whether changes have occurred that necessitate its redrawing. The `redrawControl()` method is employed for this purpose.
```php
public function handleLogin(string $user): void
{
- // The object has to re-render after the user has logged in
+ // after logging in, it is necessary to redraw the relevant part
$this->redrawControl();
// ...
}
```
-Nette however offers an even finer resolution than whole components. The listed methods accept the name of a so-called "snippet" as an optional parameter. A "snippet" is basically an element in your template marked for that purpose by a Latte tag, more on that later. Thus it is possible to ask a component to redraw only *parts* of its template. If the entire component is invalidated then all of its snippets are re-rendered. A component is “invalid” also if any of its subcomponents is invalid.
-
-```php
-$this->isControlInvalid(); // -> false
-$this->redrawControl('header'); // invalidates the snippet named 'header'
-$this->isControlInvalid('header'); // -> true
-$this->isControlInvalid('footer'); // -> false
-$this->isControlInvalid(); // -> true, at least one snippet is invalid
+Nette also allows for finer control of what needs redrawing. The aforementioned method can take the snippet name as an argument. Thus, it's possible to invalidate (meaning: force a redraw) at the template part level. If the entire component is invalidated, every snippet of it is also redrawn:
-$this->redrawControl(); // invalidates the whole component, every snippet
-$this->isControlInvalid('footer'); // -> true
+```php
+// invalidates the 'header' snippet
+$this->redrawControl('header');
```
-A component which receives a signal is automatically marked for redrawing.
-
-Thanks to snippet redrawing we know exactly which parts of which elements should be re-rendered.
-
-
-Tag `{snippet} … {/snippet}` .{toc: Tag snippet}
-================================================
-Rendering of the page proceeds very similarly to a regular request: the same templates are loaded, etc. The vital part is, however, to leave out the parts that are not supposed to reach the output; the other parts shall be associated with an identifier and sent to the user in a comprehensible format for a JavaScript handler.
+Snippets in Latte
+-----------------
-
-Syntax
-------
-
-If there is a control or a snippet in the template, we have to wrap it using the `{snippet} ... {/snippet}` pair tag - it will make sure that the rendered snippet will be "cut out" and sent to the browser. It will also enclose it in a helper `` tag (it is possible to use a different one). In the following example a snippet named `header` is defined. It may as well represent the template of a component:
+Using snippets in Latte is extremely easy. To define a part of the template as a snippet, simply wrap it in `{snippet}` and `{/snippet}` tags:
```latte
{snippet header}
@@ -139,7 +133,9 @@ If there is a control or a snippet in the template, we have to wrap it using the
{/snippet}
```
-A snippet of a type other than `
` or a snippet with additional HTML attributes is achieved by using the attribute variant:
+The snippet creates an element `
` in the HTML page with a specially generated `id`. When redrawing a snippet, the content of this element is updated. Therefore, when the page is initially rendered, all snippets must also be rendered, even if they may initially be empty.
+
+You can also create a snippet with an element other than `
` using an n:attribute:
```latte
@@ -148,138 +144,106 @@ A snippet of a type other than `` or a snippet with additional HTML attribu
```
-Dynamic Snippets
-================
+Snippet Areas
+-------------
-In Nette you can also define snippets with a dynamic name based on a runtime parameter. This is most suitable for various lists where we need to change just one row but we don't want transfer the whole list along with it. An example of this would be:
+Snippet names can also be expressions:
```latte
-
- {foreach $list as $id => $item}
- - {$item} update
- {/foreach}
-
+{foreach $items as $id => $item}
+
{$item}
+{/foreach}
```
-There is one static snippet called `itemsContainer`, containing several dynamic snippets: `item-0`, `item-1` and so on.
+This way, we'll get 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 being, snippets function as true excerpts and only they themselves are rendered directly. However, in the template, there isn't technically a snippet named `item-1`. It only emerges when executing the surrounding code of the snippet, in this case, the foreach loop. Hence, we'll mark the part of the template that needs to be executed with the `{snippetArea}` tag:
-You can't redraw a dynamic snippet directly (redrawing of `item-1` has no effect), you have to redraw its parent snippet (in this example `itemsContainer`). This causes the code of the parent snippet to be executed, but then just its sub-snippets are sent to the browser. If you want to send over just one of the sub-snippets, you have to modify input for the parent snippet to not generate the other sub-snippets.
+```latte
+
+ {foreach $items as $id => $item}
+ - {$item}
+ {/foreach}
+
+```
-In the example above you have to make sure that for an AJAX request only one item will be added to the `$list` array, therefore the `foreach` loop will print just one dynamic snippet.
+And we'll redraw both the individual snippet and the entire overarching area:
```php
-class HomePresenter extends Nette\Application\UI\Presenter
-{
- /**
- * This method returns data for the list.
- * Usually this would just request the data from a model.
- * For the purpose of this example, the data is hard-coded.
- */
- private function getTheWholeList(): array
- {
- return [
- 'First',
- 'Second',
- 'Third',
- ];
- }
-
- public function renderDefault(): void
- {
- if (!isset($this->template->list)) {
- $this->template->list = $this->getTheWholeList();
- }
- }
-
- public function handleUpdate(int $id): void
- {
- $this->template->list = $this->isAjax()
- ? []
- : $this->getTheWholeList();
- $this->template->list[$id] = 'Updated item';
- $this->redrawControl('itemsContainer');
- }
-}
+$this->redrawControl('itemsContainer');
+$this->redrawControl('item-1');
```
+It's also essential to ensure that the `$items` array contains only the items that should be redrawn.
-Snippets in an Included Template
-================================
-
-It can happen that the snippet is in a template which is being included from a different template. In that case we need to wrap the inclusion code in the second template with the `snippetArea` tag, then we redraw both the snippetArea and the actual snippet.
-
-Tag `snippetArea` ensures that the code inside is executed but only the actual snippet in the included template is sent to the browser.
+When inserting another template into the main one using the `{include}` tag, which has snippets, it's necessary to again wrap the included template in a `snippetArea` and invalidate both the snippet and the area together:
```latte
-{* parent.latte *}
-{snippetArea wrapper}
- {include 'child.latte'}
+{snippetArea include}
+ {include 'included.latte'}
{/snippetArea}
```
+
```latte
-{* child.latte *}
+{* included.latte *}
{snippet item}
-...
+ ...
{/snippet}
```
+
```php
-$this->redrawControl('wrapper');
+$this->redrawControl('include');
$this->redrawControl('item');
```
-You can also combine it with dynamic snippets.
+Snippets in Components
+----------------------
-Adding and Deleting
-===================
-
-If you add a new item into the list and invalidate `itemsContainer`, the AJAX request returns snippets including the new one, but the javascript handler won’t be able to render it. This is because there is no HTML element with the newly created ID.
-
-In this case, the simplest way is to wrap the whole list in one more snippet and invalidate it all:
+You can create snippets within [components], and Nette will automatically redraw them. However, there's a specific limitation: to redraw snippets, it calls the `render()` method without any parameters. Thus, passing parameters in the template won't work:
```latte
-{snippet wholeList}
-
- {foreach $list as $id => $item}
- - {$item} update
- {/foreach}
-
-{/snippet}
-
Add
+OK
+{control productGrid}
+
+will not work:
+{control productGrid $arg, $arg}
+{control productGrid:paginator}
```
+
+Sending User Data
+-----------------
+
+Along with snippets, you can send any additional data to the client. Simply write them into the `payload` object:
+
```php
-public function handleAdd(): void
+public function actionDelete(int $id): void
{
- $this->template->list = $this->getTheWholeList();
- $this->template->list[] = 'New one';
- $this->redrawControl('wholeList');
+ // ...
+ if ($this->isAjax()) {
+ $this->payload->message = 'Success';
+ }
}
```
-The same goes for deleting an item. It would be possible to send empty snippet, but usually lists can be paginated and it would be complicated to implement deleting one item and loading another (which used to be on a different page of the paginated list).
-
-Sending Parameters to Component
-===============================
+Sending Parameters
+==================
When we send parameters to the component via AJAX request, whether signal parameters or persistent parameters, we must provide their global name, which also contains the name of the component. The full name of parameter returns the `getParameterId()` method.
```js
-$.getJSON(
- {link changeCountBasket!},
- {
- {$control->getParameterId('id')}: id,
- {$control->getParameterId('count')}: count
- }
-});
+let url = new URL({link //foo!});
+url.searchParams.set({$control->getParameterId('bar')}, bar);
+
+fetch(url, {
+ headers: {'X-Requested-With': 'XMLHttpRequest'},
+})
```
-And handle method with s corresponding parameters in component.
+A handle method with the corresponding parameters in the component:
```php
-public function handleChangeCountBasket(int $id, int $count): void
+public function handleFoo(int $bar): void
{
-
}
```
diff --git a/application/en/bootstrap.texy b/application/en/bootstrap.texy
index c763e715a3..a950df1e88 100644
--- a/application/en/bootstrap.texy
+++ b/application/en/bootstrap.texy
@@ -20,18 +20,44 @@ use Nette\Bootstrap\Configurator;
class Bootstrap
{
- public static function boot(): Configurator
+ private Configurator $configurator;
+ private string $rootDir;
+
+ public function __construct()
+ {
+ $this->rootDir = dirname(__DIR__);
+ // The configurator is responsible for setting up the application environment and services.
+ $this->configurator = new Configurator;
+ // Set the directory for temporary files generated by Nette (e.g. compiled templates)
+ $this->configurator->setTempDirectory($this->rootDir . '/temp');
+ }
+
+ public function bootWebApplication(): Nette\DI\Container
{
- $appDir = dirname(__DIR__);
- $configurator = new Configurator;
- //$configurator->setDebugMode('secret@23.75.345.200');
- $configurator->enableTracy($appDir . '/log');
- $configurator->setTempDirectory($appDir . '/temp');
- $configurator->createRobotLoader()
+ $this->initializeEnvironment();
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+ }
+
+ private function initializeEnvironment(): void
+ {
+ // Nette is smart, and the development mode turns on automatically,
+ // or you can enable for a specific IP address it by uncommenting the following line:
+ // $this->configurator->setDebugMode('secret@23.75.345.200');
+
+ // Enables Tracy: the ultimate "swiss army knife" debugging tool.
+ $this->configurator->enableTracy($this->rootDir . '/log');
+
+ // RobotLoader: autoloads all classes in the given directory
+ $this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
- $configurator->addConfig($appDir . '/config/common.neon');
- return $configurator;
+ }
+
+ private function setupContainer(): void
+ {
+ // Load configuration files
+ $this->configurator->addConfig($this->rootDir . '/config/common.neon');
}
}
```
@@ -40,16 +66,15 @@ class Bootstrap
index.php
=========
-In the case of web applications, the initial file is `index.php`, which is located in the public directory `www/`. It lets the `Bootstrap` class to initialize the environment and return the `$configurator` which creates DI container. Then it obtains the `Application` service, that runs the web application:
+The initial file for web applications is `index.php`, located in the public directory `www/`. It uses the `Bootstrap` class to initialize the environment and create a DI container. Then, it obtains the `Application` service from the container, which launches the web application:
```php
-// initialize the environment + get Configurator object
-$configurator = App\Bootstrap::boot();
-// create a DI container
-$container = $configurator->createContainer();
+$bootstrap = new App\Bootstrap;
+// Initialize the environment + create a DI container
+$container = $bootstrap->bootWebApplication();
// DI container creates a Nette\Application\Application object
$application = $container->getByType(Nette\Application\Application::class);
-// start Nette application
+// Start the Nette application and handle the incoming request
$application->run();
```
@@ -66,19 +91,19 @@ Mode selection is done by autodetection, so there is usually no need to configur
If you want to enable development mode in other cases, for example, for programmers accessing from a specific IP address, you can use `setDebugMode()`:
```php
-$configurator->setDebugMode('23.75.345.200'); // one or more IP addresses
+$this->configurator->setDebugMode('23.75.345.200'); // one or more IP addresses
```
We definitely recommend combining an IP address with a cookie. We will store a secret token into the `nette-debug` cookie, e.g. `secret1234`, and the development mode will be activated for programmers with this combination of IP and cookie.
```php
-$configurator->setDebugMode('secret1234@23.75.345.200');
+$this->configurator->setDebugMode('secret1234@23.75.345.200');
```
We can also turn off developer mode completely, even for localhost:
```php
-$configurator->setDebugMode(false);
+$this->configurator->setDebugMode(false);
```
Note that the value `true` turns on developer mode by hard, which should never happen on a production server.
@@ -90,7 +115,7 @@ Debugging Tool Tracy
For easy debugging, we will turn on the great tool [Tracy |tracy:]. In developer mode it visualizes errors and in production mode it logs errors to the specified directory:
```php
-$configurator->enableTracy($appDir . '/log');
+$this->configurator->enableTracy($this->rootDir . '/log');
```
@@ -100,7 +125,7 @@ Temporary Files
Nette uses the cache for DI container, RobotLoader, templates, etc. Therefore it is necessary to set the path to the directory where the cache will be stored:
```php
-$configurator->setTempDirectory($appDir . '/temp');
+$this->configurator->setTempDirectory($this->rootDir . '/temp');
```
On Linux or macOS, set the [write permissions |nette:troubleshooting#Setting directory permissions] for directories `log/` and `temp/`.
@@ -112,7 +137,7 @@ RobotLoader
Usually, we will want to automatically load the classes using [RobotLoader |robot-loader:], so we have to start it up and let it load classes from the directory where `Bootstrap.php` is located (i.e. `__DIR__`) and all its subdirectories:
```php
-$configurator->createRobotLoader()
+$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
```
@@ -126,7 +151,7 @@ Timezone
Configurator allows you to specify a timezone for your application.
```php
-$configurator->setTimeZone('Europe/Prague');
+$this->configurator->setTimeZone('Europe/Prague');
```
@@ -143,16 +168,17 @@ In the development mode, the container is automatically updated each time you ch
Configuration files are loaded using `addConfig()`:
```php
-$configurator->addConfig($appDir . '/config/common.neon');
+$this->configurator->addConfig($this->rootDir . '/config/common.neon');
```
The method `addConfig()` can be called multiple times to add multiple files.
```php
-$configurator->addConfig($appDir . '/config/common.neon');
-$configurator->addConfig($appDir . '/config/local.neon');
+$configDir = $this->rootDir . '/config';
+$this->configurator->addConfig($configDir . '/common.neon');
+$this->configurator->addConfig($configDir . '/services.neon');
if (PHP_SAPI === 'cli') {
- $configurator->addConfig($appDir . '/config/cli.php');
+ $this->configurator->addConfig($configDir . '/cli.php');
}
```
@@ -169,7 +195,7 @@ Static Parameters
Parameters used in configuration files can be defined [in the section `parameters`|dependency-injection:configuration#parameters] and also passed (or overwritten) by the `addStaticParameters()` method (it has alias `addParameters()`). It is important that different parameter values cause the generation of additional DI containers, i.e. additional classes.
```php
-$configurator->addStaticParameters([
+$this->configurator->addStaticParameters([
'projectId' => 23,
]);
```
@@ -183,7 +209,7 @@ Dynamic Parameters
We can also add dynamic parameters to the container, their different values, unlike static parameters, will not cause the generation of new DI containers.
```php
-$configurator->addDynamicParameters([
+$this->configurator->addDynamicParameters([
'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
```
@@ -191,7 +217,7 @@ $configurator->addDynamicParameters([
Environment variables could be easily made available using dynamic parameters. We can access them via `%env.variable%` in configuration files.
```php
-$configurator->addDynamicParameters([
+$this->configurator->addDynamicParameters([
'env' => getenv(),
]);
```
@@ -206,6 +232,7 @@ You can use the following static parameters in the configuration files:
- `%wwwDir%` is the absolute path to the directory containing the `index.php` entry file
- `%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
- `%debugMode%` indicates whether the application is in debug mode
- `%consoleMode%` indicates whether the request came through the command line
@@ -225,7 +252,7 @@ services:
Create a new instance and insert it in bootstrap:
```php
-$configurator->addServices([
+$this->configurator->addServices([
'myservice' => new App\Model\MyCustomService('foobar'),
]);
```
@@ -234,13 +261,21 @@ $configurator->addServices([
Different Environments
======================
-Feel free to customize the `Bootstrap` class to suit your needs. You can add parameters to the `boot()` method to differentiate web projects, or add other methods, such as `bootForTests()`, which initializes the environment for unit tests, `bootForCli()` for scripts called from the command line, and so on.
+Don't hesitate to customize the `Bootstrap` class according to your needs. You can add parameters to the `bootWebApplication()` method to differentiate between web projects. Alternatively, you can add other methods, such as `bootTestEnvironment()` to initialize the environment for unit tests, `bootConsoleApplication()` for scripts called from the command line, and so on.
```php
-public static function bootForTests(): Configurator
+public function bootTestEnvironment(): Nette\DI\Container
{
- $configurator = self::boot();
Tester\Environment::setup(); // Nette Tester initialization
- return $configurator;
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+}
+
+public function bootConsoleApplication(): Nette\DI\Container
+{
+ $this->configurator->setDebugMode(false);
+ $this->initializeEnvironment();
+ $this->setupContainer();
+ return $this->configurator->createContainer();
}
```
diff --git a/application/en/components.texy b/application/en/components.texy
index b610a84739..2c79308bef 100644
--- a/application/en/components.texy
+++ b/application/en/components.texy
@@ -198,7 +198,7 @@ The link that calls the signal is created in the usual way, i.e. in the template
click here
```
-The signal is always called on the current presenter and view, so it is not possible to link to signal in different presenter / action.
+The signal is always called on the current presenter and action, it cannot be called on another presenter or action.
Thus, the signal causes the page to be reloaded in exactly the same way as in the original request, only in addition it calls the signal handling method with the appropriate parameters. If the method does not exist, exception [api:Nette\Application\UI\BadSignalException] is thrown, which is displayed to the user as error page 403 Forbidden.
@@ -230,6 +230,28 @@ In the template, these messages are available in the variable `$flashes` as obje
```
+Redirection After a Signal
+==========================
+
+After processing a component signal, redirection often follows. This situation is similar to forms—after submitting a form, we also redirect to prevent resubmission of data when the page is refreshed in the browser.
+
+```php
+$this->redirect('this') // redirects to the current presenter and action
+```
+
+Since a component is a reusable element and should not usually have a direct dependency on specific presenters, the `redirect()` and `link()` methods automatically interpret the parameter as a component signal:
+
+```php
+$this->redirect('click') // redirects to the 'click' signal of the same component
+```
+
+If you need to redirect to a different presenter or action, you can do so through the presenter:
+
+```php
+$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action
+```
+
+
Persistent Parameters
=====================
@@ -347,7 +369,7 @@ services:
Finally, we will use this factory in our presenter:
```php
-class PollPresenter extends Nette\UI\Application\Presenter
+class PollPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private PollControlFactory $pollControlFactory,
@@ -380,7 +402,7 @@ Components in Depth
Components in a Nette Application are the reusable parts of a web application that we embed in pages, which is the subject of this chapter. What exactly are the capabilities of such a component?
1) it is renderable in a template
-2) it knows which part of itself to render during an [AJAX request |ajax#invalidation] (snippets)
+2) it knows [which part of itself |ajax#snippets] to render during an AJAX request (snippets)
3) it has the ability to store its state in a URL (persistent parameters)
4) has the ability to respond to user actions (signals)
5) creates a hierarchical structure (where the root is the presenter)
@@ -430,7 +452,7 @@ class PaginatingControl extends Control
}
```
-The opposite process, that is, collecting values from persistent properites, is handled by the `saveState()` method.
+The opposite process, that is, collecting values from persistent properties, is handled by the `saveState()` method.
Signals in Depth
@@ -444,7 +466,7 @@ Signal can be received by any component, presenter of object which implements in
The main receivers of signals are `Presenters` and visual components extending `Control`. A signal is a sign for an object that it has to do something - poll counts in a vote from user, box with news has to unfold, form was sent and has to process data and so on.
-The URL for the signal is created using the method [Component::link() |api:Nette\Application\UI\Component::link()]. As parameter `$destination` we pass string `{signal}!` and as `$args` an array of arguments which we want to pass to the signal handler. Signal parameters are attached to the URL of the current presenter/view. **The parameter `?do` in the URL determines the signal called.**
+The URL for the signal is created using the [Component::link() |api:Nette\Application\UI\Component::link()] method. We pass the string `{signal}!` as the `$destination` parameter and the array of arguments we want to pass to the signal as `$args`. The signal is always called on the current presenter and action with the current parameters, the signal parameters are just added. In addition, the **parameter `?do`, which specifies the signal** is added right at the beginning.
Its format is `{signal}` or `{signalReceiver}-{signal}`. `{signalReceiver}` is the name of the component in the presenter. This is why hyphen (inaccurately dash) can't be present in the name of components - it is used to divide the name of the component and signal, but it's possible to compose several components.
diff --git a/application/en/configuration.texy b/application/en/configuration.texy
index 208194e78f..e9df62aef7 100644
--- a/application/en/configuration.texy
+++ b/application/en/configuration.texy
@@ -14,10 +14,14 @@ application:
debugger: ... # (bool) defaults to true
# will error-presenter be called on error?
- catchExceptions: ... # (bool) defaults to true in production mode
+ # has effect only in developer mode
+ catchExceptions: ... # (bool) defaults to true
# name of error-presenter
- errorPresenter: Error # (string) defaults to 'Nette:Error'
+ errorPresenter: Error # (string|array) defaults to 'Nette:Error'
+
+ # defines aliases for presenters and events
+ aliases: ...
# defines the rules for resolving the presenter name to a class
mapping: ...
@@ -27,10 +31,19 @@ application:
silentLinks: ... # (bool) defaults to false
```
-Because error-presenters are not called by default in development mode and the errors are displayed by Tracy, changing the value `catchExceptions` to `true` helps to verify that error-presenters works correct during development.
+As of `nette/application` version 3.2 it is possible to define a pair of error-presenters:
+
+```neon
+application:
+ errorPresenter:
+ 4xx: Error4xx # for Nette\Application\BadRequestException
+ 5xx: Error5xx # for other exceptions
+```
Option `silentLinks` determines how Nette behaves in developer mode when link generation fails (for example, because there is no presenter, etc). The default value `false` means that Nette triggers `E_USER_WARNING`. Setting to `true` suppresses this error message. In a production environment, `E_USER_WARNING` is always invoked. We can also influence this behavior by setting the presenter variable [$invalidLinkMode |creating-links#Invalid Links].
+[Aliases simplify referencing |creating-links#aliases] frequently used presenters.
+
The [mapping defines the rules |modules#mapping] by which the class name is derived from the presenter name.
@@ -82,6 +95,9 @@ latte:
# enables [checking generated code |latte:develop#Checking Generated Code]
phpLinter: ... # (string) default is null
+ # sets the locale
+ locale: cs_CZ # (string) default is null
+
# class of $this->template
templateClass: App\MyTemplateClass # defaults to Nette\Bridges\ApplicationLatte\DefaultTemplate
```
@@ -91,7 +107,7 @@ If you are using Latte version 3, you can add new [extension |latte:creating-ext
```neon
latte:
extensions:
- - Latte\Essential\TranslatorExtension
+ - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```
If you are using Latte version 2, you can register new tags either by entering the class name or by referring to the service. Method `install()` is called by default, but this can be changed by specifying the name of another method:
diff --git a/application/en/creating-links.texy b/application/en/creating-links.texy
index 0fb783c609..f92d2c5894 100644
--- a/application/en/creating-links.texy
+++ b/application/en/creating-links.texy
@@ -38,7 +38,7 @@ It is also possible to pass named parameters. The following link passes paramete
detail
```
-If method `ProductPresenter::renderShow()` does not have `$lang` in its signature, it can read the value of the parameter using `$lang = $this->getParameter('lang')`.
+If the method `ProductPresenter::renderShow()` does not have `$lang` in its signature, it can retrieve the value of the parameter using `$lang = $this->getParameter('lang')` or from the [property |presenters#Request Parameters].
If the parameters are stored in an array, they can be expanded with the `...` operator (or `(expand)` operator in Latte 2.x):
@@ -140,7 +140,7 @@ The target `this` will create a link to the current page:
refresh
```
-At the same time, all parameters specified in the signature of the `render
()` or `action()` method are transferred. So if we are on the `Product:show` and `id:123` pages, the link to `this` will also pass this parameter.
+At the same time, all parameters specified in the signature of the `action()` or `render()` method, if the `action()` is not defined, are transferred. So if we are on the `Product:show` and `id:123` pages, the link to `this` will also pass this parameter.
Of course, it is possible to specify the parameters directly:
@@ -213,7 +213,7 @@ Because [components] are separate reusable units that should have no relations t
If we want to link to presenters in the component template, we use the tag `{plink}`:
```latte
-home
+home
```
or in the code
@@ -223,6 +223,30 @@ $this->getPresenter()->link('Home:default')
```
+Aliases .{data-version:v3.2.2}
+==============================
+
+Sometimes it's useful to assign an easily memorable alias to a Presenter:action pair. For example, you could name the homepage `Front:Home:default` simply as `home` or `Admin:Dashboard:default` as `admin`.
+
+Aliases are defined in the [configuration|configuration] under the key `application › aliases`:
+
+```neon
+application:
+ aliases:
+ home: Front:Home:default
+ admin: Admin:Dashboard:default
+ sign: Front:Sign:in
+```
+
+In links, they are written using the at symbol, for example:
+
+```latte
+administration
+```
+
+They are supported in all methods that work with links, such as `redirect()` and similar.
+
+
Invalid Links
=============
diff --git a/application/en/how-it-works.texy b/application/en/how-it-works.texy
index 08d7c3eb8b..cb0f291e1b 100644
--- a/application/en/how-it-works.texy
+++ b/application/en/how-it-works.texy
@@ -22,18 +22,18 @@ The directory structure looks something like this:
/--pre
web-project/
├── app/ ← directory with application
-│ ├── Presenters/ ← presenter classes
-│ │ ├── HomePresenter.php ← Home presenter class
-│ │ └── templates/ ← templates directory
-│ │ ├── @layout.latte ← template of shared layout
-│ │ └── Home/ ← templates for Home presenter
-│ │ └── default.latte ← template for action `default`
-│ ├── Router/ ← configuration of URL addresses
+│ ├── Core/ ← basic necessary classes
+│ │ └── RouterFactory.php ← configuration of URL addresses
+│ ├── UI/ ← presenters, templates & co.
+│ │ ├── @layout.latte ← template of shared layout
+│ │ └── Home/ ← Home presenter directory
+│ │ ├── HomePresenter.php ← Home presenter class
+│ │ └── default.latte ← template for action default
│ └── Bootstrap.php ← booting class Bootstrap
├── bin/ ← scripts for the command line
├── config/ ← configuration files
│ ├── common.neon
-│ └── local.neon
+│ └── services.neon
├── log/ ← error logs
├── temp/ ← temporary files, cache, …
├── vendor/ ← libraries installed by Composer
@@ -91,7 +91,7 @@ Applications written in Nette are divided into many so-called presenters (in oth
The application starts by asking the so-called router to decide which of the presenters to pass the current request for processing. The router decides whose responsibility it is. It looks at the input URL `https://example.com/product/123` and, based on how it is set up, decides that this is a job, for example, for **presenter** `Product`, who wants to `show` a product with `id: 123` as an action. It is a good habit to write a pairs of presenter + action separated by a colon as `Product:show`.
-So the router transformed the URL into a pair `Presenter:action` + parameters, in our case `Product:show` + `id: 123`. You can see how a router looks like in file `app/Router/RouterFactory.php` and we will describe it in detail in chapter [Routing].
+So the router transformed the URL into a pair `Presenter:action` + parameters, in our case `Product:show` + `id: 123`. You can see how a router looks like in file `app/Core/RouterFactory.php` and we will describe it in detail in chapter [Routing].
Let's move on. The application already knows the name of the presenter and can continue. By creating an object `ProductPresenter`, which is the code of presenter `Product`. More precisely, it asks the DI container for creating the presenter, because producting objects is its job.
@@ -121,12 +121,9 @@ So, the method `renderShow(123)` was called, whose code is fictional example, bu
Subsequently, the presenter returns the answer. This can be an HTML page, an image, an XML document, sending a file from disk, JSON or redirecting to another page. Importantly, if we do not explicitly say how to respond (which is the case of `ProductPresenter`), the answer will be to render the template with an HTML page. Why? Well, because in 99% of cases we want to draw a template, so the presenter takes this behavior as the default and wants to make our work easier. That's Nette's point.
-We don't even have to state which template to draw, he derives the path to it according to simple logic. In the case of presenter `Product` and action `show`, it tries to see if one of these template files exists relative to the directory where class `ProductPresenter` is located:
+We don't even need to specify which template to render; the framework will deduce the path itself. In the case of the `show` action, it simply tries to load the `show.latte` template in the directory with the `ProductPresenter` class. It also attempts to find the layout in the `@layout.latte` file (more about [template searching |templates#Template Lookup]).
-- `templates/Product/show.latte`
-- `templates/Product.show.latte`
-
-It will also try to find the layout in file `@layout.latte` and then it renders the template. Now the task of the presenter and the entire application is completed. If the template does not exist, a page with error 404 will be returned. You can read more about presenters on the [Presenters] page.
+Subsequently, the templates are rendered. This completes the task of the presenter and the entire application, and the work is done. If the template did not exist, a 404 error page would be returned. You can read more about presenters on the page [Presenters|presenters].
[* request-flow.svg *]
@@ -137,7 +134,7 @@ Just to be sure, let's try to recap the whole process with a slightly different
3) the router decodes the URL as a pair `Home:default`
4) an `HomePresenter` object is created
5) method `renderDefault()` is called (if exists)
-6) a template `templates/Home/default.latte` with a layout `templates/@layout.latte` is rendered
+6) a template `default.latte` with a layout `@layout.latte` is rendered
You may have come across a lot of new concepts now, but we believe they make sense. Creating applications in Nette is a breeze.
diff --git a/application/en/modules.texy b/application/en/modules.texy
index 6312143fdd..528cc1600c 100644
--- a/application/en/modules.texy
+++ b/application/en/modules.texy
@@ -2,29 +2,31 @@ Modules
*******
.[perex]
-In Nette, modules represent the logical units that make up an application. They include presenters, templates, possibly also components and model classes.
+Modules bring clarity to Nette applications by facilitating easy division into logical units.
-One directory for presenters and one for templates would not be enough for real projects. Having dozens of files in one folder is at least unorganized. How to get out of it? We simply split them into subdirectories on disk and into namespaces in the code. And that's exactly what the Nette modules do.
-
-So let's forget about a single folder for presenters and templates and instead create modules, for example `Admin` and `Front`.
+Similar to organizing files into folders on a hard drive, in Nette we can divide presenters, templates, and other auxiliary classes into modules. How does this work in practice? Simply by incorporating new subdirectories into the structure. Here’s an example of a structure with two modules, Front and Admin:
/--pre
-app/
-├── Presenters/
-├── Modules/ ← directory with modules
-│ ├── Admin/ ← module Admin
-│ │ ├── Presenters/ ← its presenters
-│ │ │ ├── DashboardPresenter.php
-│ │ │ └── templates/
-│ └── Front/ ← module Front
-│ └── Presenters/ ← its presenters
-│ └── ...
+app/
+├── UI/
+│ ├── Admin/ ← Admin module
+│ │ ├── @layout.latte
+│ │ ├── Dashboard/
+│ │ │ ├── DashboardPresenter.php
+│ │ │ └── default.latte
+│ │ └── ...
+│ ├── Front/ ← Front module
+│ │ ├── @layout.latte
+│ │ ├── Home/
+│ │ │ ├── HomePresenter.php
+│ │ │ └── default.latte
+│ │ └── ...
\--
-This directory structure will be reflected by the class namespaces, so for example `DashboardPresenter` will be in the `App\Modules\Admin\Presenters` namespace:
+This directory structure is reflected in the namespaces of the classes, so for example, `DashboardPresenter` is located in the namespace `App\UI\Admin\Dashboard`:
```php
-namespace App\Modules\Admin\Presenters;
+namespace App\UI\Admin\Dashboard;
class DashboardPresenter extends Nette\Application\UI\Presenter
{
@@ -32,35 +34,49 @@ class DashboardPresenter extends Nette\Application\UI\Presenter
}
```
-The `Dashboard` presenter inside the `Admin` module is referenced within the application using the colon notation as `Admin:Dashboard`, and its `default` action as `Admin:Dashboard:default`.
-And how does Nette proper know that `Admin:Dashboard` represents the `App\Modules\Admin\Presenters\DashboardPresenter` class? This is determined by [#mapping] in the configuration.
-Thus, the given structure is not hard set and you can modify it according to your needs.
+In the application, we refer to the `Dashboard` presenter within the `Admin` module using colon notation as `Admin:Dashboard`. For its `default` action, we refer to it as `Admin:Dashboard:default`.
+
+The structure presented is not rigid; you can [fully customize it to your needs|#mapping] in the configuration. .[tip]
-Modules can of course contain all other items besides presenters and templates, such as components, model classes, etc.
+Modules can include all other files, such as components and auxiliary classes, in addition to presenters and templates. If you are considering where to place these, consider using an `Accessory` folder:
+
+/--pre
+app/
+├── UI/
+│ ├── Admin/
+│ │ ├── Accessory/
+│ │ │ ├── FormFactory.php
+│ │ │ └── AdminLayout.php
+│ │ ├── Dashboard/
+│ │ └── ...
+\--
Nested Modules
--------------
-Modules don't have to form only a flat structure, you can also create submodules, for example:
+Modules can have multiple levels of nesting, similar to a directory structure on a disk:
/--pre
-app/
-├── Modules/ ← directory with modules
-│ ├── Blog/ ← module Blog
-│ │ ├── Admin/ ← submodule Admin
-│ │ │ ├── Presenters/
+app/
+├── UI/
+│ ├── Blog/ ← Blog module
+│ │ ├── Admin/ ← Admin submodule
+│ │ │ ├── Dashboard/
+│ │ │ └── ...
+│ │ ├── Front/ ← Front submodule
+│ │ │ ├── @layout.latte
+│ │ │ ├── Home/
│ │ │ └── ...
-│ │ └── Front/ ← submodule Front
-│ │ ├── Presenters/
-│ │ └── ...
-│ ├── Forum/ ← module Forum
+│ ├── Forum/ ← Forum module
│ │ └── ...
\--
-Thus, the `Blog` module is divided into `Admin` and `Front` submodules. Again, this will be reflected in the namespaces, which will be `App\Modules\Blog\Admin\Presenters` etc. The presenter `Dashboard` inside the submodule is referred to as `Blog:Admin:Dashboard`.
+The `Blog` module is divided into `Admin` and `Front` submodules. This is also reflected in the namespaces, which then appear as `App\UI\Blog\Admin` and similarly. To refer to the `Dashboard` presenter within the `Admin` submodule, we refer to it as `Blog:Admin:Dashboard`.
+
+Nesting can be as deep as needed, allowing the creation of sub-submodules.
-The nesting can go as deep as you like, so sub-submodules can be created.
+For example, if in administration you have many presenters related to order management, such as `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., you might create an `Order` module in which presenters like `Detail`, `Edit`, `Dispatch`, and others will be organized.
Creating Links
@@ -102,47 +118,66 @@ See [chapter on routing |routing#Modules].
Mapping
-------
-Defines the rules by which the class name is derived from the presenter name. We write them in [configuration] under the `application › mapping` key.
+Mapping defines the rules for deriving the class name from the presenter name. These rules are specified in the [configuration|configuration] under the key `application › mapping`.
-Let's start with a sample that doesn't use modules. We'll just want the presenter classes to have the `App\Presenters` namespace. That means that a presenter such as `Home` should map to the `App\Presenters\HomePresenter` class. This can be achieved by the following configuration:
+The directory structures mentioned earlier on this page are based on the following mapping:
```neon
application:
- mapping:
- *: App\Presenters\*Presenter
+ mapping: App\UI\*\**Presenter
```
-The presenter name is replaced with the asterisk in the class mask and the result is the class name. Easy!
+How does the mapping work? For a better understanding, let's first imagine an application without modules. We want the presenter classes to fall under the namespace `App\UI`, so that the `Home` presenter maps to the class `App\UI\HomePresenter`. This can be achieved with this configuration:
-If we divide presenters into modules, we can have our own mapping for each module:
+```neon
+application:
+ mapping: App\UI\*Presenter
+```
+
+This mapping works by replacing the asterisk in the mask `App\UI\*Presenter` with the presenter name `Home`, resulting in the final class name `App\UI\HomePresenter`. Simple!
+
+However, as you can see in the examples in this and other chapters, we place presenter classes in eponymous subdirectories, e.g., the `Home` presenter is mapped to the class `App\UI\Home\HomePresenter`. This is achieved by doubling the asterisk (requires Nette Application 3.2):
+
+```neon
+application:
+ mapping: App\UI\**Presenter
+```
+
+Now, let's move on to mapping presenters into modules. We can define specific mappings for each module:
```neon
application:
mapping:
- Front: App\Modules\Front\Presenters\*Presenter
- Admin: App\Modules\Admin\Presenters\*Presenter
+ Front: App\UI\Front\**Presenter
+ Admin: App\UI\Admin\**Presenter
Api: App\Api\*Presenter
```
-Now presenter `Front:Home` maps to class `App\Modules\Front\Presenters\HomePresenter` and presenter `Admin:Dashboard` to class `App\Modules\Admin\Presenters\DashboardPresenter`.
+According to this configuration, the presenter `Front:Home` maps to the class `App\UI\Front\Home\HomePresenter`, while the presenter `Api:OAuth` maps to the class `App\Api\OAuthPresenter`.
-It is more practical to create a general (star) rule to replace the first two. The extra asterisk will be added to the class mask just for the module:
+Since the `Front` and `Admin` modules have a similar mapping approach and there are likely to be more such modules, it is possible to create a general rule that replaces them. A new asterisk for the module is added to the class mask:
```neon
application:
mapping:
- *: App\Modules\*\Presenters\*Presenter
+ *: App\UI\*\**Presenter
Api: App\Api\*Presenter
```
-But what if we use nested modules and have a presenter `Admin:User:Edit`? In this case, the segment with an asterisk representing the module for each level is simply repeated and the result is class `App\Modules\Admin\User\Presenters\EditPresenter`.
+For multi-level nested modules, such as the presenter `Admin:User:Edit`, the asterisk segment repeats for each level, resulting in the class `App\UI\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 composed of three segments instead of a string. This notation is equivalent to the previous one:
```neon
application:
mapping:
- *: [App\Modules, *, Presenters\*Presenter]
+ *: [App\UI, *, **Presenter]
+ Api: [App\Api, '', *Presenter]
```
-The default value is `*: *Module\*Presenter`.
+If we have only one rule in the configuration, the general one, we can write briefly:
+
+```neon
+application:
+ mapping: App\UI\*\**Presenter
+```
diff --git a/application/en/presenters.texy b/application/en/presenters.texy
index e653f0945c..2773f8486b 100644
--- a/application/en/presenters.texy
+++ b/application/en/presenters.texy
@@ -60,7 +60,7 @@ Similar to the method `render()`. While `render()` is intended to pr
It is important that `action()` is called before `render()`, so inside it we can possibly change the next course of life cycle, i.e. change the template that will be rendered and also the method `render()` that will be called, using `setView('otherView')`.
-The parameters from the request are passed to the method. It is possible and recommended to specify types for the parameters, e.g. `actionShow(int $id, string $slug = null)` - if parameter `id` is missing or if it is not an integer, the presenter returns [error 404|#Error 404 etc.] and terminates the operation.
+The parameters from the request are passed to the method. It is possible and recommended to specify types for the parameters, e.g. `actionShow(int $id, ?string $slug = null)` - if parameter `id` is missing or if it is not an integer, the presenter returns [error 404|#Error 404 etc.] and terminates the operation.
`handle(args...)` .{toc: handle()}
@@ -205,7 +205,7 @@ In the template, these messages are available in the variable `$flashes` as obje
Error 404 etc.
==============
-When we can't fulfill the request because for example the article we want to display does not exist in the database, we will throw out the 404 error using method `error(string $message = null, int $httpCode = 404)`, which represents HTTP error 404:
+When we can't fulfill the request because for example the article we want to display does not exist in the database, we will throw out the 404 error using method `error(?string $message = null, int $httpCode = 404)`, which represents HTTP error 404:
```php
public function renderShow(int $id): void
@@ -236,6 +236,32 @@ public function actionData(): void
```
+Request Parameters .{data-version:3.1.14}
+=========================================
+
+The presenter, as well as every component, obtains its parameters from the HTTP request. Their values can be retrieved using the `getParameter($name)` method or `getParameters()`. The values are strings or arrays of strings, essentially raw data obtained directly from the URL.
+
+For added convenience, we recommend making parameters accessible through properties. Simply annotate them with the `#[Parameter]` attribute:
+
+```php
+use Nette\Application\Attributes\Parameter; // this line is important
+
+class HomePresenter extends Nette\Application\UI\Presenter
+{
+ #[Parameter]
+ public string $theme; // must be public
+}
+```
+
+For properties, we suggest specifying the data type (e.g., `string`). Nette will then automatically cast the value based on it. Parameter values can be also [validated |#Validation of Parameters].
+
+When creating a link, you can directly set the value for the parameters:
+
+```latte
+click
+```
+
+
Persistent Parameters
=====================
@@ -257,7 +283,7 @@ class ProductPresenter extends Nette\Application\UI\Presenter
If `$this->lang` has a value such as `'en'`, then links created using `link()` or `n:href` will also contain the `lang=en` parameter. And when the link is clicked, it will again be `$this->lang = 'en'`.
-For properties, we recommend that you include the data type (e.g. `string`) and you can also include a default value. Parameter values can be [validated |#Validation of Persistent Parameters].
+For properties, we recommend that you include the data type (e.g. `string`) and you can also include a default value. Parameter values can be [validated |#Validation of Parameters].
Persistent parameters are passed between all actions of a given presenter by default. To pass them between multiple presenters, you need to define them either:
@@ -307,18 +333,12 @@ Going Deeper
What we have shown so far in this chapter will probably suffice. The following lines are intended for those who are interested in presenters in depth and want to know everything.
-Requirement and Parameters
---------------------------
+Validation of Parameters
+------------------------
-The request handled by the presenter is the [api:Nette\Application\Request] object and is returned by the presenter's method `getRequest()`. It includes an array of parameters and each of them belongs either to some of the components or directly to the presenter (which is actually also a component, albeit a special one). So Nette redistributes the parameters and passes between the individual components (and the presenter) by calling the method `loadState(array $params)`. The parameters can be obtained by the method `getParameters(): array`, individually using `getParameter($name)`. Parameter values are strings or arrays of strings, they are basically raw data obtained directly from a URL.
+The values of [#request parameters] and [#persistent parameters] received from URLs are written to properties by the `loadState()` method. It also checks if the data type specified in the property matches, otherwise it will respond with a 404 error and the page will not be displayed.
-
-Validation of Persistent Parameters
------------------------------------
-
-The values of [#persistent parameters] received from URLs are written to properties by the `loadState()` method. It also checks if the data type specified in the property matches, otherwise it will respond with a 404 error and the page will not be displayed.
-
-Never blindly trust persistent parameters, as they can easily be overwritten by the user in the URL. For example, this is how we check if `$this->lang` is among the supported languages. A good way to do this is to override the `loadState()` method mentioned above:
+Never blindly trust parameters, as they can easily be overwritten by the user in the URL. For example, this is how we check if `$this->lang` is among the supported languages. A good way to do this is to override the `loadState()` method mentioned above:
```php
class ProductPresenter extends Nette\Application\UI\Presenter
@@ -341,6 +361,8 @@ class ProductPresenter extends Nette\Application\UI\Presenter
Save and Restore the Request
----------------------------
+The request that the presenter handles is an object [api:Nette\Application\Request] and is returned by the presenter's method `getRequest()`.
+
You can save the current request to a session or restore it from the session and let the presenter execute it again. This is useful, for example, when a user fills out a form and its login expires. In order not to lose data, before redirecting to the sign-in page, we save the current request to the session using `$reqId = $this->storeRequest()`, which returns an identifier in the form of a short string and passes it as a parameter to the sign-in presenter.
After sign in, we call the method `$this->restoreRequest($reqId)`, which picks up the request from the session and forwards it to it. The method verifies that the request was created by the same user as now logged in is. If another user logs in or the key is invalid, it does nothing and the program continues.
@@ -362,7 +384,7 @@ Redirection does not occur with an AJAX or POST request because it would result
You can also invoke canonization manually using method `canonicalize()`, which, like method `link()`, receives the presenter, actions, and parameters as arguments. It creates a link and compares it to the current URL. If it is different, it redirects to the generated link.
```php
-public function actionShow(int $id, string $slug = null): void
+public function actionShow(int $id, ?string $slug = null): void
{
$realSlug = $this->facade->getSlugForId($id);
// redirects if $slug is different from $realSlug
@@ -425,6 +447,51 @@ $this->sendResponse(new Responses\CallbackResponse($callback));
```
+Access Restriction Using `#[Requires]` .{data-version:3.2.2}
+------------------------------------------------------------
+
+The `#[Requires]` attribute provides advanced options for restricting access to presenters and their methods. It can be used to specify HTTP methods, require AJAX requests, limit access to the same origin, and restrict access to forwarding only. The attribute can be applied to presenter classes as well as individual methods such as `action()`, `render()`, `handle()`, and `createComponent()`.
+
+You can specify these restrictions:
+- on HTTP methods: `#[Requires(methods: ['GET', 'POST'])]`
+- requiring an AJAX request: `#[Requires(ajax: true)]`
+- access only from the same origin: `#[Requires(sameOrigin: true)]`
+- access only via forwarding: `#[Requires(forward: true)]`
+- restrictions on specific actions: `#[Requires(actions: 'default')]`
+
+For details, see [How to use the Requires attribute |best-practices:attribute-requires].
+
+
+HTTP Method Check
+-----------------
+
+In Nette, presenters automatically verify the HTTP method of each incoming request primarily for security reasons. By default, the methods `GET`, `POST`, `HEAD`, `PUT`, `DELETE`, `PATCH` are allowed.
+
+If you want to enable additional methods such as `OPTIONS`, you can use the `#[Requires]` attribute (from Nette Application v3.2):
+
+```php
+#[Requires(methods: ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'PATCH', 'OPTIONS'])]
+class MyPresenter extends Nette\Application\UI\Presenter
+{
+}
+```
+
+In version 3.1, the verification is performed in `checkHttpMethod()`, which checks if the method specified in the request is included in the array `$presenter->allowedMethods`. Add a method like this:
+
+```php
+class MyPresenter extends Nette\Application\UI\Presenter
+{
+ protected function checkHttpMethod(): void
+ {
+ $this->allowedMethods[] = 'OPTIONS';
+ parent::checkHttpMethod();
+ }
+}
+```
+
+It's crucial to emphasize that if you enable the `OPTIONS` method, you must also properly handle it within your presenter. This method is often used as a so-called preflight request, which browsers automatically send before the actual request when it's necessary to determine if the request is allowed from the standpoint of CORS (Cross-Origin Resource Sharing) policy. If you allow this method but do not implement an appropriate response, it can lead to inconsistencies and potential security issues.
+
+
Further Reading
===============
diff --git a/application/en/routing.texy b/application/en/routing.texy
index c35c692adb..f9af18510b 100644
--- a/application/en/routing.texy
+++ b/application/en/routing.texy
@@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [
@@ -225,7 +225,7 @@ $router->addRoute('/[/]', [
]);
```
-Or we can use this form, notice the rewriting of the validation regular expression:
+For a more detailed specification, an even more extended form can be used, where in addition to default values, other parameter properties can be set, such as a validation regular expression (see the `id` parameter):
```php
use Nette\Routing\Route;
@@ -243,7 +243,7 @@ $router->addRoute('/[/]', [
]);
```
-These more talkative formats are useful for adding other metadata.
+It is important to note that if the parameters defined in the array are not included in the path mask, their values cannot be changed, not even using query parameters specified after a question mark in the URL.
Filters and Translations
@@ -477,10 +477,10 @@ $router->addRoute('index.html \.html?|\.php|>', /* ... */);
Integration
===========
-In order to connect the our router into the application, we must tell the DI container about it. The easiest way is to prepare the factory that will build the router object and tell the container configuration to use it. So let's say we write a method for this purpose `App\Router\RouterFactory::createRouter()`:
+In order to connect the our router into the application, we must tell the DI container about it. The easiest way is to prepare the factory that will build the router object and tell the container configuration to use it. So let's say we write a method for this purpose `App\Core\RouterFactory::createRouter()`:
```php
-namespace App\Router;
+namespace App\Core;
use Nette\Application\Routers\RouteList;
@@ -499,7 +499,7 @@ Then we write in [configuration |dependency-injection:services]:
```neon
services:
- - App\Router\RouterFactory::createRouter
+ - App\Core\RouterFactory::createRouter
```
Any dependencies, such as a database connection etc., are passed to the factory method as its parameters using [autowiring |dependency-injection:autowiring]:
@@ -663,7 +663,7 @@ By separated usage, we mean the use of the router's capabilities in an applicati
So again we will create a method that will build a router, for example:
```php
-namespace App\Router;
+namespace App\Core;
use Nette\Routing\RouteList;
@@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class);
Or we will create objects directly:
```php
-$router = App\Router\RouterFactory::createRouter();
+$router = App\Core\RouterFactory::createRouter();
$httpRequest = (new Nette\Http\RequestFactory)->fromGlobals();
```
diff --git a/application/en/templates.texy b/application/en/templates.texy
index 5107b4493a..bdc524c4e4 100644
--- a/application/en/templates.texy
+++ b/application/en/templates.texy
@@ -34,35 +34,81 @@ And this might be the action template:
It defines block `content`, which is inserted in place of `{include content}` in the layout, and also re-defines block `title`, which overwrites `{block title}` in the layout. Try to imagine the result.
-Search for Templates
---------------------
+Template Lookup
+---------------
-The path to the templates is deduced according to simple logic. It tries to see if one of these template files exists relative to the directory where presenter class is located, where `` is the name of the current presenter and `` is the name of the current action:
+In presenters, you don't need to specify which template should be rendered; the framework will automatically determine the path, making coding easier for you.
-- `templates//.latte`
-- `templates/..latte`
+If you use a directory structure where each presenter has its own directory, simply place the template in this directory under the name of the action (i.e. view). For example, for the `default` action, use the `default.latte` template:
-If the template is not found, it will try to search in the `templates` directory one level up, i.e., at the same level as the directory with the presenter class.
+/--pre
+app/
+└── UI/
+ └── Home/
+ ├── HomePresenter.php
+ └── default.latte
+\--
-If the template is not found there either, the response is a [404 error|presenters#Error 404 etc.].
+If you use a structure where presenters are together in one directory and templates in a `templates` folder, save it either in a file `..latte` or `/.latte`:
-You can also change the view using `$this->setView('otherView')`. Or, instead of searching, directly specify the name of the template file using `$this->template->setFile('/path/to/template.latte')`.
+/--pre
+app/
+└── Presenters/
+ ├── HomePresenter.php
+ └── templates/
+ ├── Home.default.latte ← 1st variant
+ └── Home/
+ └── default.latte ← 2nd variant
+\--
+
+The `templates` directory can also be placed one level higher, at the same level as the directory with presenter classes.
+
+If the template is not found, the presenter responds with [404 - page not found error|presenters#Error 404 etc].
+
+You can change the view using `$this->setView('anotherView')`. It is also possible to directly specify the template file with `$this->template->setFile('/path/to/template.latte')`.
.[note]
-You can change the paths where templates are searched by overriding the [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()] method, which returns an array of possible file paths.
+Files where templates are searched can be changed by overriding the method [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], which returns an array of possible file names.
+
+
+Layout Template Lookup
+----------------------
+
+Nette also automatically searches for the layout file.
+
+If you use a directory structure where each presenter has its own directory, place the layout either in the folder with the presenter, if it is specific only to them, or a level higher if it is common to multiple presenters:
+
+/--pre
+app/
+└── UI/
+ ├── @layout.latte ← common layout
+ └── Home/
+ ├── @layout.latte ← only for Home presenter
+ ├── HomePresenter.php
+ └── default.latte
+\--
+
+If you use a structure where presenters are grouped together in one directory and templates are in a `templates` folder, the layout will be expected in the following places:
-The layout is expected in the following files:
+/--pre
+app/
+└── Presenters/
+ ├── HomePresenter.php
+ └── templates/
+ ├── @layout.latte ← common layout
+ ├── Home.@layout.latte ← only for Home, 1st variant
+ └── Home/
+ └── @layout.latte ← only for Home, 2nd variant
+\--
-- `templates//@.latte`
-- `templates/.@.latte`
-- `templates/@.latte` layout common to multiple presenters
+If the presenter is in a [module|modules], it will also search further up the directory tree according to the module's nesting.
-`` is the name of the current presenter and `` is the name of the layout, which is by default `'layout'`. The name can be changed with `$this->setLayout('otherLayout')`, so that `@otherLayout.latte` files will be tried.
+The name of the layout can be changed using `$this->setLayout('layoutAdmin')` and then it will be expected in the file `@layoutAdmin.latte`. You can also directly specify the layout template file using `$this->setLayout('/path/to/template.latte')`.
-You can also directly specify the file name of the layout template using `$this->setLayout('/path/to/template.latte')`. Using `$this->setLayout(false)` will disable the layout searching.
+Using `$this->setLayout(false)` or the `{layout none}` tag inside the template disables layout search.
.[note]
-You can change the paths where templates are searched by overriding the [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] method, which returns an array of possible file paths.
+Files where layout templates are searched can be changed by overriding the method [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], which returns an array of possible file names.
Variables in the Template
@@ -104,7 +150,7 @@ The `@property-read` annotation is for IDE and static analysis, it will make aut
You can indulge in the luxury of whispering in templates too, just install the Latte plugin in PhpStorm and specify the class name at the beginning of the template, see the article "Latte: how to type system":https://blog.nette.org/en/latte-how-to-use-type-system:
```latte
-{templateType App\Presenters\ArticleTemplate}
+{templateType App\UI\Article\ArticleTemplate}
...
```
@@ -176,7 +222,7 @@ public function beforeRender(): void
Latte version 3 offers a more advanced way by creating an [extension |latte:creating-extension] for each web project. Here is a rough example of such a class:
```php
-namespace App\Templating;
+namespace App\UI\Accessory;
final class LatteExtension extends Latte\Extension
{
@@ -214,7 +260,7 @@ We register it using [configuration#Latte]:
```neon
latte:
extensions:
- - App\Templating\LatteExtension
+ - App\UI\Accessory\LatteExtension
```
@@ -239,7 +285,7 @@ Alternatively, the translator can be set using the [configuration |configuration
```neon
latte:
extensions:
- - Latte\Essential\TranslatorExtension
+ - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```
The translator can then be used, for example, as a filter `|translate`, with additional parameters passed to the `translate()` method (see `foo, bar`):
diff --git a/application/es/@home.texy b/application/es/@home.texy
index b2966f5062..0777da2986 100644
--- a/application/es/@home.texy
+++ b/application/es/@home.texy
@@ -28,8 +28,9 @@ composer require nette/application
| versión | compatible con PHP
|-----------|-------------------
-| Aplicación Nette 4.0 | PHP 8.0 - 8.2
-| Aplicación Nette 3.1 | PHP 7.2 - 8.2
+| Nette Application 4.0 | PHP 8.1 – 8.3
+| Nette Application 3.2 | PHP 8.1 – 8.3
+| Nette Application 3.1 | PHP 7.2 – 8.3
| Aplicación Nette 3.0 | PHP 7.1 - 8.0
| Aplicación Nette 2.4 | PHP 5.6 - 8.0
diff --git a/application/es/ajax.texy b/application/es/ajax.texy
index 0ba8886dd4..d1414e84c4 100644
--- a/application/es/ajax.texy
+++ b/application/es/ajax.texy
@@ -3,10 +3,10 @@ AJAX y fragmentos
-Hoy en día, las aplicaciones web modernas se ejecutan mitad en un servidor y mitad en un navegador. AJAX es un factor de unión vital. ¿Qué soporte ofrece Nette Framework?
-- envío de fragmentos de plantillas (los llamados *snippets*)
+En la era de las aplicaciones web modernas, donde la funcionalidad a menudo se extiende entre el servidor y el navegador, AJAX es un elemento de conexión esencial. ¿Qué opciones ofrece Nette Framework en este ámbito?
+- envío de partes de la plantilla, los llamados snippets
- paso de variables entre PHP y JavaScript
-- depuración de aplicaciones AJAX
+- herramientas para depurar peticiones AJAX
@@ -14,29 +14,32 @@ Hoy en día, las aplicaciones web modernas se ejecutan mitad en un servidor y mi
Solicitud AJAX .[#toc-ajax-request]
===================================
-Una petición AJAX no difiere de una petición clásica: se llama al presentador con una vista y unos parámetros específicos. También depende del presentador cómo responder a ella: puede utilizar su propia rutina, que devuelve un fragmento de código HTML (HTML snippet), un documento XML, un objeto JSON o código JavaScript.
+Una petición AJAX fundamentalmente no difiere de una petición HTTP clásica. Se llama a un presentador con parámetros específicos. Depende del presentador cómo responder a la petición - puede devolver datos en formato JSON, enviar una parte de código HTML, un documento XML, etc.
-En el lado del servidor, una petición AJAX puede detectarse utilizando el método de servicio [que encapsula la petición HTTP |http:request] `$httpRequest->isAjax()` (detecta basándose en la cabecera HTTP `X-Requested-With`). Dentro del presentador, se dispone de un acceso directo en forma del método `$this->isAjax()`.
+En el navegador, iniciamos una petición AJAX utilizando la función `fetch()`:
-Existe un objeto preprocesado llamado `payload` dedicado a enviar datos al navegador en JSON.
-
-```php
-public function actionDelete(int $id): void
-{
- if ($this->isAjax()) {
- $this->payload->message = 'Success';
- }
- // ...
-}
+```js
+fetch(url, {
+ headers: {'X-Requested-With': 'XMLHttpRequest'},
+})
+.then(response => response.json())
+.then(payload => {
+ // tratamiento de la respuesta
+});
```
-Para un control total sobre su salida JSON utilice el método `sendJson` en su presentador. Terminará el presentador inmediatamente y prescindirá de una plantilla:
+En el lado del servidor, una petición AJAX es reconocida por el método `$httpRequest->isAjax()` del servicio [que encapsula la petición HTTP |http:request]. Utiliza la cabecera HTTP `X-Requested-With`, por lo que es imprescindible enviarla. Dentro del presentador, puede utilizar el método `$this->isAjax()`.
+
+Si desea enviar datos en formato JSON, utilice el método [`sendJson()` |presenters#Sending a response] método. El método también finaliza la actividad del presentador.
```php
-$this->sendJson(['key' => 'value', /* ... */]);
+public function actionExport(): void
+{
+ $this->sendJson($this->model->getData);
+}
```
-Si queremos enviar HTML, podemos establecer una plantilla especial para peticiones AJAX:
+Si planea responder con una plantilla especial diseñada para AJAX, puede hacerlo de la siguiente manera:
```php
public function handleClick($param): void
@@ -44,27 +47,43 @@ public function handleClick($param): void
if ($this->isAjax()) {
$this->template->setFile('path/to/ajax.latte');
}
- // ...
+ //...
}
```
+Recortes .[#toc-snippets]
+=========================
+
+La herramienta más potente que ofrece Nette para conectar el servidor con el cliente son los snippets. Con ellos, puedes convertir una aplicación ordinaria en una AJAX con el mínimo esfuerzo y unas pocas líneas de código. El ejemplo Fifteen demuestra cómo funciona todo, y su código puede encontrarse en [GitHub |https://github.com/nette-examples/fifteen].
+
+Los snippets, o recortes, permiten actualizar sólo partes de la página, en lugar de recargarla entera. Esto es más rápido y eficiente, y también proporciona una experiencia de usuario más cómoda. Puede que los snippets te recuerden 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? Cuando se carga la página por primera vez (una 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 cargarse toda la página, se realiza una solicitud AJAX. El código del presentador ejecuta la acción y decide qué fragmentos deben actualizarse. Nette renderiza estos fragmentos y los envía en forma de matriz JSON. A continuación, el código de gestión del navegador vuelve a insertar en la página los fragmentos recibidos. Por lo tanto, sólo 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 de todo el contenido de la página.
+
+
Naja .[#toc-naja]
-=================
+-----------------
-K obsluze AJAXových požadavků na straně prohlížeče slouží [knihovna Naja |https://naja.js.org], [instálalo |https://naja.js.org/#/guide/01-install-setup-naja] como un paquete node.js (para usarlo con Webpack, Rollup, Vite, Parcel y más):
+Para manejar snippets en el lado del navegador, se utiliza la [librería Naja |https://naja.js.org]. [Instálala |https://naja.js.org/#/guide/01-install-setup-naja] como un paquete node.js (para usar con aplicaciones como Webpack, Rollup, Vite, Parcel, y otras):
```shell
npm install naja
```
-...o insertarlo directamente en la plantilla de la página:
+... o insértala directamente en la plantilla de la página:
```html
```
-Para crear una solicitud AJAX a partir de un enlace normal (señal) o el envío de un formulario, basta con marcar el enlace, formulario o botón correspondiente con la clase `ajax`:
+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`:
```html
Go
@@ -74,64 +93,39 @@ Para crear una solicitud AJAX a partir de un enlace normal (señal) o el envío
or
+
```
-Fragmentos .[#toc-snippets]
-===========================
-
-Existe una herramienta mucho más potente de soporte AJAX integrado: los snippets. Su uso permite convertir una aplicación normal en una aplicación AJAX utilizando sólo unas pocas líneas de código. Cómo funciona todo se demuestra en el ejemplo Fifteen cuyo código también está accesible en la compilación o en [GitHub |https://github.com/nette-examples/fifteen].
-
-La forma en que funcionan los snippets es que toda la página se transfiere durante la petición inicial (es decir, no AJAX) y luego con cada [subpetición |components#signal] AJAX (petición de la misma vista del mismo presentador) sólo se transfiere el código de las partes modificadas en el repositorio `payload` mencionado anteriormente.
-
-Puede que los snippets te recuerden a Hotwire para Ruby on Rails o a Symfony UX Turbo, pero Nette los inventó catorce años antes.
-
+Redibujar fragmentos .[#toc-redrawing-snippets]
+-----------------------------------------------
-Invalidación de Snippets .[#toc-invalidation-of-snippets]
-=========================================================
-
-Cada descendiente de la clase [Control |components] (que también es un Presentador) es capaz de recordar si hubo algún cambio durante una petición que requiera que se vuelva a renderizar. Hay un par de métodos para manejar esto: `redrawControl()` y `isControlInvalid()`. Un ejemplo:
+Cada objeto de la clase [Control |components] (incluido el propio Presentador) mantiene un registro de si se han producido cambios que requieran su redibujado. Para ello se emplea el método `redrawControl()`.
```php
public function handleLogin(string $user): void
{
- // The object has to re-render after the user has logged in
+ // después de iniciar la sesión, es necesario volver a dibujar la parte pertinente
$this->redrawControl();
- // ...
+ //...
}
```
-Nette, sin embargo, ofrece una resolución aún más fina que la de los componentes completos. Los métodos mencionados aceptan el nombre de un "fragmento" como parámetro opcional. Un "fragmento" es básicamente un elemento de su plantilla marcado para ese propósito por una tag Latte, más sobre esto más adelante. Así, es posible pedir a un componente que redibuje sólo *partes* de su plantilla. Si se invalida todo el componente, entonces se vuelven a renderizar todos sus fragmentos. Un componente es "inválido" también si cualquiera de sus subcomponentes es inválido.
-
-```php
-$this->isControlInvalid(); // -> false
-$this->redrawControl('header'); // invalidates the snippet named 'header'
-$this->isControlInvalid('header'); // -> true
-$this->isControlInvalid('footer'); // -> false
-$this->isControlInvalid(); // -> true, at least one snippet is invalid
+Nette también permite un control más preciso de lo que hay que redibujar. El método mencionado puede tomar el nombre del fragmento como argumento. Así, es posible invalidar (es decir: forzar un redibujado) a nivel de la parte de la plantilla. Si se invalida todo el componente, también se redibujan todos sus fragmentos:
-$this->redrawControl(); // invalidates the whole component, every snippet
-$this->isControlInvalid('footer'); // -> true
+```php
+// invalida el fragmento de cabecera
+$this->redrawControl('header');
```
-Un componente que recibe una señal se marca automáticamente para ser redibujado.
-
-Gracias al redibujado de fragmentos, sabemos exactamente qué partes de qué elementos deben redibujarse.
-
-
-Etiqueta `{snippet} … {/snippet}` .{toc: Tag snippet}
-=====================================================
-
-El renderizado de la página procede de forma muy similar a una petición normal: se cargan las mismas plantillas, etc. Sin embargo, lo esencial es dejar fuera las partes que no deben llegar a la salida; las demás partes se asociarán a un identificador y se enviarán al usuario en un formato comprensible para un manipulador JavaScript.
-
-Sintaxis .[#toc-syntax]
------------------------
+Fragmentos en Latte .[#toc-snippets-in-latte]
+---------------------------------------------
-Si hay un control o un fragmento en la plantilla, tenemos que envolverlo usando la etiqueta `{snippet} ... {/snippet}` pair - se asegurará de que el fragmento renderizado será "recortado" y enviado al navegador. También lo encerrará en una etiqueta helper `` (es posible utilizar otra). En el siguiente ejemplo se define un fragmento llamado `header`. También puede representar la plantilla de un componente:
+Utilizar snippets en Latte es extremadamente fácil. Para definir una parte de la plantilla como fragmento, basta con envolverla en las etiquetas `{snippet}` y `{/snippet}`:
```latte
{snippet header}
@@ -139,7 +133,9 @@ Si hay un control o un fragmento en la plantilla, tenemos que envolverlo usando
{/snippet}
```
-Si desea crear un fragmento con un elemento contenedor distinto de `
` o añadir atributos personalizados al elemento, puede utilizar la siguiente definición:
+El fragmento crea un elemento `
` en la página HTML con un `id` especialmente generado. Al redibujar un fragmento, se actualiza el contenido de este elemento. Por lo tanto, cuando la página se renderiza inicialmente, también deben renderizarse todos los fragmentos, aunque inicialmente puedan estar vacíos.
+
+También puede crear un fragmento con un elemento distinto de `
` mediante un atributo n:attribute:
```latte
@@ -148,138 +144,106 @@ Si desea crear un fragmento con un elemento contenedor distinto de `` o añ
```
-Fragmentos dinámicos .[#toc-dynamic-snippets]
-=============================================
+Áreas de recortes .[#toc-snippet-areas]
+---------------------------------------
-En Nette también puede definir fragmentos con un nombre dinámico basado en un parámetro de tiempo de ejecución. Esto es más adecuado para varias listas en las que necesitamos cambiar sólo una fila pero no queremos transferir toda la lista junto con ella. Un ejemplo de esto sería:
+Los nombres de los recortes también pueden ser expresiones:
```latte
-
- {foreach $list as $id => $item}
- - {$item} update
- {/foreach}
-
+{foreach $items as $id => $item}
+
{$item}
+{/foreach}
```
-Hay un fragmento estático llamado `itemsContainer`, que contiene varios fragmentos dinámicos: `item-0` `item-1` y así sucesivamente.
+De este modo, obtendremos varios fragmentos como `item-0`, `item-1`, etc. Si invalidáramos directamente un fragmento dinámico (por ejemplo, `item-1`), no se volvería a dibujar nada. La razón es que los fragmentos funcionan como verdaderos extractos y sólo ellos mismos se renderizan directamente. Sin embargo, en la plantilla no existe técnicamente un fragmento llamado `item-1`. Sólo aparece cuando se ejecuta el código que rodea al fragmento, en este caso, el bucle foreach. Por lo tanto, marcaremos la parte de la plantilla que necesita ser ejecutada con la etiqueta `{snippetArea}`:
-No puedes redibujar un fragmento dinámico directamente (redibujar `item-1` no tiene ningún efecto), tienes que redibujar su fragmento padre (en este ejemplo `itemsContainer`). Esto hace que se ejecute el código del fragmento padre, pero entonces sólo se envían al navegador sus sub fragmentos. Si desea enviar sólo uno de los sub fragmentos, debe modificar la entrada del fragmento padre para que no genere los otros sub fragmentos.
+```latte
+
+ {foreach $items as $id => $item}
+ - {$item}
+ {/foreach}
+
+```
-En el ejemplo anterior tiene que asegurarse de que para una petición AJAX sólo se añadirá un elemento a la matriz `$list`, por lo que el bucle `foreach` sólo imprimirá un fragmento dinámico.
+Y redibujaremos tanto el fragmento individual como toda el área general:
```php
-class HomePresenter extends Nette\Application\UI\Presenter
-{
- /**
- * This method returns data for the list.
- * Usually this would just request the data from a model.
- * For the purpose of this example, the data is hard-coded.
- */
- private function getTheWholeList(): array
- {
- return [
- 'First',
- 'Second',
- 'Third',
- ];
- }
-
- public function renderDefault(): void
- {
- if (!isset($this->template->list)) {
- $this->template->list = $this->getTheWholeList();
- }
- }
-
- public function handleUpdate(int $id): void
- {
- $this->template->list = $this->isAjax()
- ? []
- : $this->getTheWholeList();
- $this->template->list[$id] = 'Updated item';
- $this->redrawControl('itemsContainer');
- }
-}
+$this->redrawControl('itemsContainer');
+$this->redrawControl('item-1');
```
+También es esencial asegurarse de que la matriz `$items` contiene sólo los elementos que deben ser redibujados.
-Fragmentos en una plantilla incluida .[#toc-snippets-in-an-included-template]
-=============================================================================
-
-Puede ocurrir que el snippet esté en una plantilla que está siendo incluida desde una plantilla diferente. En ese caso necesitamos envolver el código de inclusión en la segunda plantilla con la tag `snippetArea`, entonces redibujamos tanto el snippetArea como el snippet real.
-
-La tag `snippetArea` garantiza que se ejecute el código que contiene, pero que sólo se envíe al navegador el fragmento real de la plantilla incluida.
+Cuando se inserta otra plantilla en la principal utilizando la etiqueta `{include}`, que tiene fragmentos, es necesario envolver de nuevo la plantilla incluida en un `snippetArea` e invalidar conjuntamente el fragmento y el área:
```latte
-{* parent.latte *}
-{snippetArea wrapper}
- {include 'child.latte'}
+{snippetArea include}
+ {include 'included.latte'}
{/snippetArea}
```
+
```latte
-{* child.latte *}
+{* included.latte *}
{snippet item}
-...
+ ...
{/snippet}
```
+
```php
-$this->redrawControl('wrapper');
+$this->redrawControl('include');
$this->redrawControl('item');
```
-También se puede combinar con fragmentos dinámicos.
+Fragmentos en componentes .[#toc-snippets-in-components]
+--------------------------------------------------------
-Añadir y eliminar .[#toc-adding-and-deleting]
-=============================================
-
-Si añades un nuevo elemento a la lista e invalidas `itemsContainer`, la petición AJAX devuelve fragmentos que incluyen el nuevo, pero el manejador javascript no podrá renderizarlo. Esto se debe a que no hay ningún elemento HTML con el ID recién creado.
-
-En este caso, la forma más sencilla es envolver toda la lista en un fragmento más e invalidarlo todo:
+Puede crear fragmentos dentro de los [componentes |components] y Nette los redibujará automáticamente. Sin embargo, hay una limitación específica: para redibujar fragmentos, llama al método `render()` sin ningún parámetro. Por lo tanto, pasar parámetros en la plantilla no funcionará:
```latte
-{snippet wholeList}
-
- {foreach $list as $id => $item}
- - {$item} update
- {/foreach}
-
-{/snippet}
-
Add
+OK
+{control productGrid}
+
+will not work:
+{control productGrid $arg, $arg}
+{control productGrid:paginator}
```
+
+Envío de datos de usuario .[#toc-sending-user-data]
+---------------------------------------------------
+
+Junto con los fragmentos, puede enviar cualquier dato adicional al cliente. Simplemente escríbalos en el objeto `payload`:
+
```php
-public function handleAdd(): void
+public function actionDelete(int $id): void
{
- $this->template->list = $this->getTheWholeList();
- $this->template->list[] = 'New one';
- $this->redrawControl('wholeList');
+ //...
+ if ($this->isAjax()) {
+ $this->payload->message = 'Success';
+ }
}
```
-Lo mismo ocurre con la eliminación de un elemento. Sería posible enviar un fragmento vacío, pero normalmente las listas pueden paginarse y sería complicado implementar la eliminación de un elemento y la carga de otro (que solía estar en una página diferente de la lista paginada).
-
-Envío de parámetros al componente .[#toc-sending-parameters-to-component]
-=========================================================================
+Envío de parámetros .[#toc-sending-parameters]
+==============================================
Cuando enviamos parámetros al componente a través de una petición AJAX, ya sean parámetros de señal o parámetros persistentes, debemos proporcionar su nombre global, que también contiene el nombre del componente. El nombre completo del parámetro devuelve el método `getParameterId()`.
```js
-$.getJSON(
- {link changeCountBasket!},
- {
- {$control->getParameterId('id')}: id,
- {$control->getParameterId('count')}: count
- }
-});
+let url = new URL({link //foo!});
+url.searchParams.set({$control->getParameterId('bar')}, bar);
+
+fetch(url, {
+ headers: {'X-Requested-With': 'XMLHttpRequest'},
+})
```
-Y manejar el método con s parámetros correspondientes en el componente.
+Un método handle con los parámetros correspondientes en el componente:
```php
-public function handleChangeCountBasket(int $id, int $count): void
+public function handleFoo(int $bar): void
{
-
}
```
diff --git a/application/es/bootstrap.texy b/application/es/bootstrap.texy
index 3b67d5c4b9..19bb56c0ba 100644
--- a/application/es/bootstrap.texy
+++ b/application/es/bootstrap.texy
@@ -20,18 +20,44 @@ use Nette\Bootstrap\Configurator;
class Bootstrap
{
- public static function boot(): Configurator
+ private Configurator $configurator;
+ private string $rootDir;
+
+ public function __construct()
+ {
+ $this->rootDir = dirname(__DIR__);
+ // El configurador se encarga de configurar el entorno y los servicios de la aplicación.
+ $this->configurator = new Configurator;
+ // Establecer el directorio para los archivos temporales generados por Nette (por ejemplo, plantillas compiladas).
+ $this->configurator->setTempDirectory($this->rootDir . '/temp');
+ }
+
+ public function bootWebApplication(): Nette\DI\Container
{
- $appDir = dirname(__DIR__);
- $configurator = new Configurator;
- //$configurator->setDebugMode('secret@23.75.345.200');
- $configurator->enableTracy($appDir . '/log');
- $configurator->setTempDirectory($appDir . '/temp');
- $configurator->createRobotLoader()
+ $this->initializeEnvironment();
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+ }
+
+ private function initializeEnvironment(): void
+ {
+ // Nette es inteligente, y el modo de desarrollo se activa automáticamente,
+ // o puede habilitarlo para una dirección IP específica descomentando la siguiente línea:
+ // $this->configurator->setDebugMode('secret@23.75.345.200');
+
+ // Habilita Tracy: la última herramienta de depuración "navaja suiza".
+ $this->configurator->enableTracy($this->rootDir . '/log');
+
+ // RobotLoader: carga automáticamente todas las clases en el directorio dado
+ $this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
- $configurator->addConfig($appDir . '/config/common.neon');
- return $configurator;
+ }
+
+ private function setupContainer(): void
+ {
+ // Carga archivos de configuración
+ $this->configurator->addConfig($this->rootDir . '/config/common.neon');
}
}
```
@@ -40,16 +66,15 @@ class Bootstrap
index.php .[#toc-index-php]
===========================
-En el caso de las aplicaciones web, el archivo inicial es `index.php`, que se encuentra en el directorio público `www/`. Permite a la clase `Bootstrap` inicializar el entorno y devolver el `$configurator` que crea el contenedor DI. Luego obtiene el servicio `Application`, que ejecuta la aplicación web:
+El archivo inicial para aplicaciones web es `index.php`, ubicado en el directorio público `www/`. Utiliza la clase `Bootstrap` para inicializar el entorno y crear un contenedor DI. A continuación, obtiene el servicio `Application` del contenedor, que lanza la aplicación web:
```php
-// initialize the environment + get Configurator object
-$configurator = App\Bootstrap::boot();
-// create a DI container
-$container = $configurator->createContainer();
-// DI container creates a Nette\Application\Application object
+$bootstrap = new App\Bootstrap;
+// Inicializar el entorno + crear un contenedor DI
+$container = $bootstrap->bootWebApplication();
+// El contenedor DI crea un objeto Nette\Application\Application
$application = $container->getByType(Nette\Application\Application::class);
-// start Nette application
+// Inicia la aplicación Nette y gestiona la petición entrante
$application->run();
```
@@ -66,19 +91,19 @@ La selección del modo se hace por autodetección, por lo que normalmente no hay
Si desea habilitar el modo de desarrollo en otros casos, por ejemplo, para los programadores que acceden desde una dirección IP específica, puede utilizar `setDebugMode()`:
```php
-$configurator->setDebugMode('23.75.345.200'); // one or more IP addresses
+$this->configurator->setDebugMode('23.75.345.200'); // one or more IP addresses
```
Recomendamos encarecidamente combinar una dirección IP con una cookie. Almacenaremos un token secreto en la cookie `nette-debug`, por ejemplo `secret1234`, y el modo de desarrollo se activará para los programadores con esta combinación de IP y cookie.
```php
-$configurator->setDebugMode('secret1234@23.75.345.200');
+$this->configurator->setDebugMode('secret1234@23.75.345.200');
```
También podemos desactivar completamente el modo de desarrollo, incluso para localhost:
```php
-$configurator->setDebugMode(false);
+$this->configurator->setDebugMode(false);
```
Nótese que el valor `true` activa el modo desarrollador por fuerza, lo que nunca debería ocurrir en un servidor de producción.
@@ -90,7 +115,7 @@ Herramienta de depuración Tracy .[#toc-debugging-tool-tracy]
Para facilitar la depuración, activaremos la gran herramienta [Tracy |tracy:]. En modo desarrollador visualiza los errores y en modo producción los registra en el directorio especificado:
```php
-$configurator->enableTracy($appDir . '/log');
+$this->configurator->enableTracy($this->rootDir . '/log');
```
@@ -100,7 +125,7 @@ Archivos temporales .[#toc-temporary-files]
Nette utiliza la caché para el contenedor DI, RobotLoader, plantillas, etc. Por lo tanto es necesario establecer la ruta al directorio donde se almacenará la caché:
```php
-$configurator->setTempDirectory($appDir . '/temp');
+$this->configurator->setTempDirectory($this->rootDir . '/temp');
```
En Linux o macOS, establezca los [permisos de escritura |nette:troubleshooting#Setting directory permissions] para los directorios `log/` y `temp/`.
@@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader]
Normalmente, querremos cargar automáticamente las clases usando [RobotLoader |robot-loader:], así que tenemos que iniciarlo y dejar que cargue las clases desde el directorio donde se encuentra `Bootstrap.php` (es decir, `__DIR__`) y todos sus subdirectorios:
```php
-$configurator->createRobotLoader()
+$this->configurator->createRobotLoader()
->addDirectory(__DIR__)
->register();
```
@@ -126,7 +151,7 @@ Zona horaria .[#toc-timezone]
Configurator le permite especificar una zona horaria para su aplicación.
```php
-$configurator->setTimeZone('Europe/Prague');
+$this->configurator->setTimeZone('Europe/Prague');
```
@@ -143,16 +168,17 @@ En el modo de desarrollo, el contenedor se actualiza automáticamente cada vez q
Los archivos de configuración se cargan utilizando `addConfig()`:
```php
-$configurator->addConfig($appDir . '/config/common.neon');
+$this->configurator->addConfig($this->rootDir . '/config/common.neon');
```
El método `addConfig()` se puede llamar varias veces para añadir varios archivos.
```php
-$configurator->addConfig($appDir . '/config/common.neon');
-$configurator->addConfig($appDir . '/config/local.neon');
+$configDir = $this->rootDir . '/config';
+$this->configurator->addConfig($configDir . '/common.neon');
+$this->configurator->addConfig($configDir . '/services.neon');
if (PHP_SAPI === 'cli') {
- $configurator->addConfig($appDir . '/config/cli.php');
+ $this->configurator->addConfig($configDir . '/cli.php');
}
```
@@ -169,7 +195,7 @@ Parámetros estáticos .[#toc-static-parameters]
Los parámetros utilizados en los archivos de configuración pueden definirse [en la sección `parameters` |dependency-injection:configuration#parameters] y también pasarse (o sobrescribirse) por el método `addStaticParameters()` (tiene el alias `addParameters()`). Es importante que los diferentes valores de los parámetros provoquen la generación de contenedores DI adicionales, es decir, clases adicionales.
```php
-$configurator->addStaticParameters([
+$this->configurator->addStaticParameters([
'projectId' => 23,
]);
```
@@ -183,7 +209,7 @@ Parámetros dinámicos .[#toc-dynamic-parameters]
También podemos añadir parámetros dinámicos al contenedor, sus diferentes valores, a diferencia de los parámetros estáticos, no provocarán la generación de nuevos contenedores DI.
```php
-$configurator->addDynamicParameters([
+$this->configurator->addDynamicParameters([
'remoteIp' => $_SERVER['REMOTE_ADDR'],
]);
```
@@ -191,7 +217,7 @@ $configurator->addDynamicParameters([
Las variables de entorno podrían estar fácilmente disponibles utilizando parámetros dinámicos. Podemos acceder a ellas a través de `%env.variable%` en archivos de configuración.
```php
-$configurator->addDynamicParameters([
+$this->configurator->addDynamicParameters([
'env' => getenv(),
]);
```
@@ -206,6 +232,7 @@ Puede utilizar los siguientes parámetros estáticos en los archivos de configur
- `%wwwDir%` es la ruta absoluta al directorio que contiene el archivo de entrada `index.php`
- `%tempDir%` es la ruta absoluta al directorio para 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
- `%debugMode%` indica si la aplicación está en modo depuración
- `%consoleMode%` indica si la solicitud llegó a través de la línea de comandos
@@ -225,7 +252,7 @@ services:
Creamos una nueva instancia y la insertamos en bootstrap:
```php
-$configurator->addServices([
+$this->configurator->addServices([
'myservice' => new App\Model\MyCustomService('foobar'),
]);
```
@@ -234,13 +261,21 @@ $configurator->addServices([
Diferentes entornos .[#toc-different-environments]
==================================================
-Siéntete libre de personalizar la clase `Bootstrap` para adaptarla a tus necesidades. Puedes añadir parámetros al método `boot()` para diferenciar proyectos web, o añadir otros métodos, como `bootForTests()`, que inicializa el entorno para pruebas unitarias, `bootForCli()` para scripts llamados desde la línea de comandos, etc.
+No dude en personalizar la clase `Bootstrap` según sus necesidades. Puedes añadir parámetros al método `bootWebApplication()` para diferenciar entre proyectos web. Alternativamente, puedes añadir otros métodos, como `bootTestEnvironment()` para inicializar el entorno para pruebas unitarias, `bootConsoleApplication()` para scripts llamados desde la línea de comandos, etc.
```php
-public static function bootForTests(): Configurator
+public function bootTestEnvironment(): Nette\DI\Container
+{
+ Tester\Environment::setup(); // Inicialización del comprobador de redes
+ $this->setupContainer();
+ return $this->configurator->createContainer();
+}
+
+public function bootConsoleApplication(): Nette\DI\Container
{
- $configurator = self::boot();
- Tester\Environment::setup(); // Nette Tester initialization
- return $configurator;
+ $this->configurator->setDebugMode(false);
+ $this->initializeEnvironment();
+ $this->setupContainer();
+ return $this->configurator->createContainer();
}
```
diff --git a/application/es/components.texy b/application/es/components.texy
index 7f736ea960..5e58b7e920 100644
--- a/application/es/components.texy
+++ b/application/es/components.texy
@@ -230,6 +230,28 @@ En la plantilla, estos mensajes están disponibles en la variable `$flashes` com
```
+Redirección tras una señal .[#toc-redirection-after-a-signal]
+=============================================================
+
+Después de procesar una señal de componente, a menudo se produce una redirección. Esta situación es similar a la de los formularios: después de enviar un formulario, también redirigimos para evitar que se vuelvan a enviar los datos cuando se actualiza la página en el navegador.
+
+```php
+$this->redirect('this') // redirects to the current presenter and action
+```
+
+Dado que un componente es un elemento reutilizable y, por lo general, no debería tener una dependencia directa de presentadores específicos, los métodos `redirect()` y `link()` interpretan automáticamente el parámetro como una señal de componente:
+
+```php
+$this->redirect('click') // redirects to the 'click' signal of the same component
+```
+
+Si necesita redirigir a un presentador o acción diferente, puede hacerlo a través del presentador:
+
+```php
+$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action
+```
+
+
Parámetros persistentes .[#toc-persistent-parameters]
=====================================================
@@ -347,7 +369,7 @@ services:
Por último, vamos a utilizar esta fábrica en nuestro presentador:
```php
-class PollPresenter extends Nette\UI\Application\Presenter
+class PollPresenter extends Nette\Application\UI\Presenter
{
public function __construct(
private PollControlFactory $pollControlFactory,
@@ -380,7 +402,7 @@ Componentes en profundidad .[#toc-components-in-depth]
Los componentes en una aplicación Nette son las partes reutilizables de una aplicación web que incrustamos en las páginas, que es el tema de este capítulo. ¿Cuáles son exactamente las capacidades de un componente?
1) es renderizable en una plantilla
-2) sabe qué parte de sí mismo renderizar durante una [petición AJAX |ajax#invalidation] (fragmentos)
+2) sabe [qué parte de sí mismo |ajax#snippets] debe representar durante una petición AJAX (fragmentos)
3) tiene la capacidad de almacenar su estado en una URL (parámetros persistentes)
4) tiene la capacidad de responder a las acciones del usuario (señales)
5) crea una estructura jerárquica (donde la raíz es el presentador)
diff --git a/application/es/configuration.texy b/application/es/configuration.texy
index 5dfa60ab36..aa5273344b 100644
--- a/application/es/configuration.texy
+++ b/application/es/configuration.texy
@@ -14,10 +14,14 @@ application:
debugger: ... # (bool) por defecto true
# ¿se llamará al presentador de errores en caso de error?
- catchExceptions: ... # (bool) por defecto a true en modo producción
+ # sólo tiene efecto en modo desarrollador
+ catchExceptions: ... # (bool) por defecto true
# nombre del presentador de errores
- errorPresenter: Error # (string) por defecto 'Nette:Error'
+ errorPresenter: Error # (string|array) por defecto 'Nette:Error'
+
+ # define alias para presentadores y eventos
+ aliases: ...
# define las reglas para resolver el nombre del presentador a una clase
mapping: ...
@@ -27,10 +31,19 @@ application:
silentLinks: ... # (bool) por defecto false
```
-Debido a que los presentadores de errores no son llamados por defecto en modo desarrollo y los errores son mostrados por Tracy, cambiar el valor `catchExceptions` a `true` ayuda a verificar que los presentadores de errores funcionan correctamente durante el desarrollo.
+A partir de la versión 3.2 de `nette/application` es posible definir un par de presentadores de errores:
+
+```neon
+application:
+ errorPresenter:
+ 4xx: Error4xx # para Nette\Application\BadRequestException
+ 5xx: Error5xx # para otras excepciones
+```
La opción `silentLinks` determina cómo se comporta Nette en modo desarrollo cuando falla la generación de enlaces (por ejemplo, porque no hay presentador, etc). El valor por defecto `false` significa que Nette activa `E_USER_WARNING`. El valor `true` suprime este mensaje de error. En un entorno de producción, siempre se invoca `E_USER_WARNING`. También podemos influir en este comportamiento configurando la variable del presentador [$invalidLinkMode |creating-links#Invalid Links].
+[Los alias simplifican las referencias a |creating-links#aliases] los presentadores más utilizados.
+
El [mapeo define las reglas |modules#mapping] por las cuales el nombre de la clase se deriva del nombre del presentador.
@@ -82,6 +95,9 @@ latte:
# habilita la [comprobación del código generado |latte:develop#Checking Generated Code]
phpLinter: ... # (string) por defecto es null
+ # establece la configuración regional
+ locale: cs_CZ # (string) por defecto es null
+
# clase de $this->plantilla
templateClass: App\MyTemplateClass # por defecto Nette\Bridges\ApplicationLatte\DefaultTemplate
```
@@ -91,7 +107,7 @@ Si está utilizando la versión 3 de Latte, puede añadir una nueva [extensión
```neon
latte:
extensions:
- - Latte\Essential\TranslatorExtension
+ - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator)
```
/--comment
diff --git a/application/es/creating-links.texy b/application/es/creating-links.texy
index cd4645f3a5..d22a004836 100644
--- a/application/es/creating-links.texy
+++ b/application/es/creating-links.texy
@@ -38,7 +38,7 @@ También es posible pasar parámetros con nombre. El siguiente enlace pasa el pa
detail
```
-Si el método `ProductPresenter::renderShow()` no tiene `$lang` en su firma, puede leer el valor del parámetro usando `$lang = $this->getParameter('lang')`.
+Si el método `ProductPresenter::renderShow()` no tiene `$lang` en su firma, puede recuperar el valor del parámetro utilizando `$lang = $this->getParameter('lang')` o desde la [propiedad |presenters#Request Parameters].
Si los parámetros se almacenan en una matriz, pueden expandirse con el operador `...` (o `(expand)` en Latte 2.x):
@@ -140,7 +140,7 @@ El destino `this` creará un enlace a la página actual:
refresh
```
-Al mismo tiempo, todos los parámetros especificados en la firma de la directiva `render
()` o `action()` se transfieren. Así, si estamos en las páginas `Product:show` y `id:123`, el enlace a `this` también pasará este parámetro.
+Al mismo tiempo, todos los parámetros especificados en la firma de la directiva `action()` o `render()` si el método `action()` no está definido, se transfieren. Así, si estamos en las páginas `Product:show` y `id:123`, el enlace a `this` también pasará este parámetro.
Por supuesto, es posible especificar los parámetros directamente:
@@ -213,7 +213,7 @@ Dado que los [componentes |components] son unidades reutilizables independientes
Si queremos enlazar con presentadores en la plantilla de componentes, utilizaremos la etiqueta `{plink}`:
```latte
-home
+home
```
o en el código
@@ -223,6 +223,30 @@ $this->getPresenter()->link('Home:default')
```
+Alias .[#toc-aliases]{data-version:v3.2.2}
+==========================================
+
+A veces resulta útil asignar un alias fácil de recordar a un par Presentador:acción. Por ejemplo, puede nombrar la página de inicio `Front:Home:default` simplemente como `home` o `Admin:Dashboard:default` como `admin`.
+
+Los alias se definen en la [configuración |configuration] con la clave `application › aliases`:
+
+```neon
+application:
+ aliases:
+ home: Front:Home:default
+ admin: Admin:Dashboard:default
+ sign: Front:Sign:in
+```
+
+En los enlaces, se escriben utilizando el símbolo arroba, por ejemplo:
+
+```latte
+administration
+```
+
+Se admiten en todos los métodos que trabajan con enlaces, como `redirect()` y similares.
+
+
Enlaces no válidos .[#toc-invalid-links]
========================================
diff --git a/application/es/how-it-works.texy b/application/es/how-it-works.texy
index c23895a22f..e4e0447853 100644
--- a/application/es/how-it-works.texy
+++ b/application/es/how-it-works.texy
@@ -22,18 +22,18 @@ La estructura de directorios se parece a esto
/--pre
web-project/
├── app/ ← directorio con la aplicación
-│ ├── Presenters/ ← clases para presentadores
-│ │ ├── HomePresenter.php ← Home de inicio de la clase de presentador
-│ │ └── templates/ ← directorio de plantillas
-│ │ ├── @layout.latte ← plantilla de diseño compartida
-│ │ └── Home/ ← plantillas para Home presentador de inicio
-│ │ └── default.latte ← plantilla para la acción `default`
-│ ├── Router/ ← configuración de direcciones URL
+│ ├── Core/ ← clases básicas necesarias.
+│ │ └── RouterFactory.php ← configuración de direcciones URL.
+│ ├── UI/ ← presentadores, plantillas & co.
+│ │ ├── @layout.latte ← plantilla de maquetación compartida
+│ │ └── Home/ ← Home directorio del presentador
+│ │ ├── HomePresenter.php ← Clase del presentador de inicio
+│ │ └── default.latte ← plantilla para la acción default
│ └── Bootstrap.php ← clase de arranque Bootstrap
├── bin/ ← scripts para la línea de comandos
├── config/ ← archivos de configuración
│ ├── common.neon
-│ └── local.neon
+│ └── services.neon
├── log/ ← registros de errores
├── temp/ ← archivos temporales, caché, ...
├── vendor/ ← libraries installed by Composer
@@ -91,7 +91,7 @@ Las aplicaciones escritas en Nette se dividen en muchos de los llamados presenta
La aplicación comienza pidiendo al llamado enrutador que decida a cuál de los presentadores debe pasar la petición actual para su procesamiento. El enrutador decide de quién es la responsabilidad. Mira la URL de entrada `https://example.com/product/123`, que quiere `show` un producto con `id: 123` como acción. Es una buena costumbre escribir pares de presentador + acción separados por dos puntos como `Product:show`.
-Así que el enrutador transforma la URL en un par `Presenter:action` + parámetros, en nuestro caso `Product:show` + `id: 123`. Puedes ver el aspecto de un enrutador en el archivo `app/Router/RouterFactory.php` y lo describiremos en detalle en el capítulo [Enrutamiento |Routing].
+Así que el enrutador transforma la URL en un par `Presenter:action` + parámetros, en nuestro caso `Product:show` + `id: 123`. Puedes ver el aspecto de un enrutador en el archivo `app/Core/RouterFactory.php` y lo describiremos en detalle en el capítulo [Enrutamiento |Routing].
Sigamos. La aplicación ya conoce el nombre del presentador y puede continuar. Creando un objeto `ProductPresenter`, que es el código del presentador `Product`. Más concretamente, le pide al contenedor DI que cree el presentador, porque producir objetos es su trabajo.
@@ -121,12 +121,9 @@ Así, se llamó al método `renderShow(123)`, cuyo código es ficticio ejemplo,
Posteriormente, el presentador devuelve la respuesta. Esta puede ser una página HTML, una imagen, un documento XML, el envío de un fichero desde disco, JSON o la redirección a otra página. Es importante destacar que, si no decimos explícitamente cómo responder (que es el caso de `ProductPresenter`), la respuesta será renderizar la plantilla con una página HTML. ¿Por qué? Pues porque en el 99% de los casos queremos dibujar una plantilla, así que el presentador toma este comportamiento por defecto y quiere facilitarnos el trabajo. Ese es el punto de Nette.
-Ni siquiera tenemos que indicar qué plantilla dibujar, él deriva la ruta hacia ella según una lógica simple. En el caso del presentador `Product` y la acción `show`, intenta ver si uno de estos archivos de plantilla existe en relación al directorio donde se encuentra la clase `ProductPresenter`:
+Ni siquiera necesitamos especificar qué plantilla renderizar; el framework deducirá la ruta por sí mismo. En el caso de la acción `show`, simplemente intenta cargar la plantilla `show.latte` en el directorio con la clase `ProductPresenter`. También intenta encontrar el diseño en el archivo `@layout.latte` (más información sobre la [búsqueda de plantillas |templates#Template Lookup]).
-- `templates/Product/show.latte`
-- `templates/Product.show.latte`
-
-También intentará encontrar el diseño en el archivo `@layout.latte` y luego renderizará la plantilla. Ahora se completa la tarea del presentador y de toda la aplicación. Si la plantilla no existe, se devolverá una página con el error 404. Puedes leer más sobre los presentadores en la página de [Presentadores |Presenters].
+Posteriormente, se renderizan las plantillas. Esto completa la tarea del presentador y de toda la aplicación, y el trabajo está hecho. Si la plantilla no existiera, se devolvería una página de error 404. Puede leer más sobre los presentadores en la página [Presentadores |presenters].
[* request-flow.svg *]
@@ -137,7 +134,7 @@ Sólo para estar seguros, intentemos recapitular todo el proceso con una URL lig
3) el router decodifica la URL como un par `Home:default`
4) se crea un objeto `HomePresenter`
5) se llama al método `renderDefault()` (si existe)
-6) se renderiza una plantilla `templates/Home/default.latte` con un diseño `templates/@layout.latte`
+6) se renderiza una plantilla `default.latte` con un diseño `@layout.latte`
Puede que ahora te hayas encontrado con un montón de conceptos nuevos, pero creemos que tienen sentido. Crear aplicaciones en Nette es pan comido.
diff --git a/application/es/modules.texy b/application/es/modules.texy
index e183346e5f..2827183beb 100644
--- a/application/es/modules.texy
+++ b/application/es/modules.texy
@@ -2,29 +2,31 @@ Módulos
*******
.[perex]
-En Nette, los módulos representan las unidades lógicas que componen una aplicación. Incluyen presentadores, plantillas, posiblemente también componentes y clases modelo.
+Los módulos aportan claridad a las aplicaciones Nette al facilitar su división en unidades lógicas.
-Un directorio para los presentadores y otro para las plantillas no serían suficientes para los proyectos reales. Tener docenas de archivos en una carpeta es, como mínimo, desorganizado. ¿Cómo salir de ello? Simplemente los dividimos en subdirectorios en el disco y en espacios de nombres en el código. Y eso es exactamente lo que hacen los módulos de Nette.
-
-Así que olvidémonos de una única carpeta para presentadores y plantillas y en su lugar creemos módulos, por ejemplo `Admin` y `Front`.
+De forma similar a la organización de archivos en carpetas en un disco duro, en Nette podemos dividir los presentadores, plantillas y otras clases auxiliares en módulos. ¿Cómo funciona esto en la práctica? Simplemente incorporando nuevos subdirectorios a la estructura. He aquí un ejemplo de estructura con dos módulos, Front y Admin:
/--pre
-app/
-├── Presenters/
-├── Modules/ ← directorio con módulos
-│ ├── Admin/ ← módulo Admin
-│ │ ├── Presenters/ ← sus presentadores
-│ │ │ ├── DashboardPresenter.php
-│ │ │ └── templates/
-│ └── Front/ ← módulo Front
-│ └── Presenters/ ← sus presentadores
-│ └── ...
+app/
+├── UI/
+│ ├── Admin/ ← Admin module
+│ │ ├── @layout.latte
+│ │ ├── Dashboard/
+│ │ │ ├── DashboardPresenter.php
+│ │ │ └── default.latte
+│ │ └── ...
+│ ├── Front/ ← Front module
+│ │ ├── @layout.latte
+│ │ ├── Home/
+│ │ │ ├── HomePresenter.php
+│ │ │ └── default.latte
+│ │ └── ...
\--
-Esta estructura de directorios se reflejará en los espacios de nombres de las clases, así por ejemplo `DashboardPresenter` estará en el espacio de nombres `App\Modules\Admin\Presenters`:
+Esta estructura de directorios se refleja en los espacios de nombres de las clases, así por ejemplo, `DashboardPresenter` se encuentra en el espacio de nombres `App\UI\Admin\Dashboard`:
```php
-namespace App\Modules\Admin\Presenters;
+namespace App\UI\Admin\Dashboard;
class DashboardPresenter extends Nette\Application\UI\Presenter
{
@@ -32,35 +34,49 @@ class DashboardPresenter extends Nette\Application\UI\Presenter
}
```
-El presentador `Dashboard` dentro del módulo `Admin` es referenciado dentro de la aplicación usando la notación de dos puntos como `Admin:Dashboard`, y su acción `default` como `Admin:Dashboard:default`.
-¿Y cómo sabe Nette que `Admin:Dashboard` representa la clase `App\Modules\Admin\Presenters\DashboardPresenter`? Esto se determina mediante el [mapeo |#mapping] en la configuración.
-Por lo tanto, la estructura dada no es rígida y puede modificarla según sus necesidades.
+En la aplicación, nos referimos al presentador `Dashboard` dentro del módulo `Admin` utilizando la notación de dos puntos como `Admin:Dashboard`. Para su acción `default`, nos referimos a él como `Admin:Dashboard:default`.
+
+La estructura presentada no es rígida; puede [adaptarla totalmente a sus necesidades |#mapping] en la configuración. .[tip]
-Por supuesto, los módulos pueden contener todos los demás elementos además de presentadores y plantillas, como componentes, clases modelo, etc.
+Los módulos pueden incluir todos los demás archivos, como componentes y clases auxiliares, además de presentadores y plantillas. Si está pensando dónde colocarlos, considere la posibilidad de utilizar una carpeta `Accessory`:
+
+/--pre
+app/
+├── UI/
+│ ├── Admin/
+│ │ ├── Accessory/
+│ │ │ ├── FormFactory.php
+│ │ │ └── AdminLayout.php
+│ │ ├── Dashboard/
+│ │ └── ...
+\--
Módulos anidados .[#toc-nested-modules]
---------------------------------------
-Los módulos no tienen por qué formar sólo una estructura plana, también puedes crear submódulos, por ejemplo:
+Los módulos pueden tener múltiples niveles de anidamiento, similar a una estructura de directorios en un disco:
/--pre
-app/
-├── Modules/ ← directorio con módulos
-│ ├── Blog/ ← módulo Blog
-│ │ ├── Admin/ ← submódulo Admin
-│ │ │ ├── Presenters/
+app/
+├── UI/
+│ ├── Blog/ ← Blog module
+│ │ ├── Admin/ ← Admin submodule
+│ │ │ ├── Dashboard/
+│ │ │ └── ...
+│ │ ├── Front/ ← Front submodule
+│ │ │ ├── @layout.latte
+│ │ │ ├── Home/
│ │ │ └── ...
-│ │ └── Front/ ← submódulo Front
-│ │ ├── Presenters/
-│ │ └── ...
-│ ├── Forum/ ← módulo Forum
+│ ├── Forum/ ← Forum module
│ │ └── ...
\--
-Así, el módulo `Blog` se divide en los submódulos `Admin` y `Front`. De nuevo, esto se reflejará en los espacios de nombres, que serán `App\Modules\Blog\Admin\Presenters`, etc. El presentador `Dashboard` dentro del submódulo se denomina `Blog:Admin:Dashboard`.
+El módulo `Blog` se divide en los submódulos `Admin` y `Front`. Esto también se refleja en los espacios de nombres, que aparecen como `App\UI\Blog\Admin` y similares. Para referirnos al presentador `Dashboard` dentro del submódulo `Admin`, lo denominamos `Blog:Admin:Dashboard`.
+
+El anidamiento puede ser tan profundo como sea necesario, permitiendo la creación de sub-submódulos.
-El anidamiento puede ser tan profundo como se desee, por lo que pueden crearse submódulos.
+Por ejemplo, si en administración tiene muchos presentadores relacionados con la gestión de pedidos, como `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., puede crear un módulo `Order` en el que se organizarán presentadores como `Detail`, `Edit`, `Dispatch`, y otros.
Creación de enlaces .[#toc-creating-links]
@@ -99,50 +115,69 @@ Enrutamiento .[#toc-routing]
Véase el [capítulo sobre en rutamiento|routing#Modules].
-Mapeo .[#toc-mapping]
----------------------
+Cartografía .[#toc-mapping]
+---------------------------
-Define las reglas por las que el nombre de la clase se deriva del nombre del presentador. Las escribimos en [configuración |configuration] bajo la clave `application › mapping`.
+El mapeo define las reglas para derivar el nombre de la clase del nombre del presentador. Estas reglas se especifican en la [configuración |configuration] bajo la clave `application › mapping`.
-Empecemos con un ejemplo que no utiliza módulos. Sólo querremos que las clases del presentador tengan el espacio de nombres `App\Presenters`. Eso significa que un presentador como `Home` debe mapearse a la clase `App\Presenters\HomePresenter`. Esto se puede lograr con la siguiente configuración:
+Las estructuras de directorios mencionadas anteriormente en esta página se basan en la siguiente asignación:
```neon
application:
- mapping:
- *: App\Presenters\*Presenter
+ mapping: App\UI\*\**Presenter
```
-El nombre del presentador se sustituye por el asterisco en la máscara de clase y el resultado es el nombre de la clase. Muy fácil.
+¿Cómo funciona el mapeo? Para entenderlo mejor, imaginemos primero una aplicación sin módulos. Queremos que las clases del presentador pertenezcan al espacio de nombres `App\UI`, de modo que el presentador `Home` se asigne a la clase `App\UI\HomePresenter`. Esto se puede lograr con esta configuración:
-Si dividimos a los presentadores en módulos, podemos tener nuestra propia asignación para cada módulo:
+```neon
+application:
+ mapping: App\UI\*Presenter
+```
+
+Este mapeo funciona reemplazando el asterisco en la máscara `App\UI\*Presenter` con el nombre del presentador `Home`, resultando en el nombre final de la clase `App\UI\HomePresenter`. Es muy sencillo.
+
+Sin embargo, como puede ver en los ejemplos de este y otros capítulos, colocamos las clases de presentador en subdirectorios epónimos, por ejemplo, el presentador `Home` se asigna a la clase `App\UI\Home\HomePresenter`. Esto se consigue duplicando el asterisco (requiere Nette Application 3.2):
+
+```neon
+application:
+ mapping: App\UI\**Presenter
+```
+
+Pasemos ahora a la asignación de presentadores a módulos. Podemos definir asignaciones específicas para cada módulo:
```neon
application:
mapping:
- Front: App\Modules\Front\Presenters\*Presenter
- Admin: App\Modules\Admin\Presenters\*Presenter
+ Front: App\UI\Front\**Presenter
+ Admin: App\UI\Admin\**Presenter
Api: App\Api\*Presenter
```
-Ahora el presentador `Front:Home` se asigna a la clase `App\Modules\Front\Presenters\HomePresenter` y el presentador `Admin:Dashboard` a la clase `App\Modules\Admin\Presenters\DashboardPresenter`.
+Según esta configuración, el presentador `Front:Home` se asigna a la clase `App\UI\Front\Home\HomePresenter`, mientras que el presentador `Api:OAuth` se asigna a la clase `App\Api\OAuthPresenter`.
-Es más práctico crear una regla general (estrella) para sustituir a las dos primeras. El asterisco adicional se añadirá a la máscara de clase sólo para el módulo:
+Puesto que los módulos `Front` y `Admin` tienen un enfoque de asignación similar y es probable que haya más módulos de este tipo, es posible crear una regla general que los sustituya. Se añade un nuevo asterisco para el módulo a la máscara de la clase:
```neon
application:
mapping:
- *: App\Modules\*\Presenters\*Presenter
+ *: App\UI\*\**Presenter
Api: App\Api\*Presenter
```
-Pero, ¿y si utilizamos módulos anidados y tenemos un presentador `Admin:User:Edit`? En este caso, el segmento con un asterisco que representa el módulo para cada nivel simplemente se repite y el resultado es la clase `App\Modules\Admin\User\Presenters\EditPresenter`.
+Para los módulos anidados de varios niveles, como el presentador `Admin:User:Edit`, el segmento del asterisco se repite para cada nivel, lo que da como resultado la clase `App\UI\Admin\User\Edit\EditPresenter`.
-Una notación alternativa es utilizar una matriz formada por tres segmentos en lugar de una cadena. Esta notación es equivalente a la anterior:
+Una notación alternativa consiste en utilizar una matriz compuesta por tres segmentos en lugar de una cadena. Esta notación es equivalente a la anterior:
```neon
application:
mapping:
- *: [App\Modules, *, Presenters\*Presenter]
+ *: [App\UI, *, **Presenter]
+ Api: [App\Api, '', *Presenter]
```
-El valor por defecto es `*: *Module\*Presenter`.
+Si sólo tenemos una regla en la configuración, la general, podemos escribir brevemente:
+
+```neon
+application:
+ mapping: App\UI\*\**Presenter
+```
diff --git a/application/es/presenters.texy b/application/es/presenters.texy
index c85c74305c..ee6f551d6c 100644
--- a/application/es/presenters.texy
+++ b/application/es/presenters.texy
@@ -60,7 +60,7 @@ Similar al método `render()`. Mientras que `render()` está destina
Es importante que `action