diff --git a/application/bg/ajax.texy b/application/bg/ajax.texy index 3b98231de7..04cc36448d 100644 --- a/application/bg/ajax.texy +++ b/application/bg/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Първо трябва да [инициализирате |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] библиотеката: + +```js +naja.initialize(); +``` + За да превърнете обикновена връзка (сигнал) или подаване на форма в AJAX заявка, просто маркирайте съответната връзка, форма или бутон с класа `ajax`: ```html diff --git a/application/bg/bootstrap.texy b/application/bg/bootstrap.texy index a7989b2d15..2ac3dc2e99 100644 --- a/application/bg/bootstrap.texy +++ b/application/bg/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. След това тя извлича услугата `Application`, която стартира уеб приложението: +Началният файл за уеб приложенията е `index.php`, разположен в публичната директория `www/`. Той използва класа `Bootstrap` за инициализиране на средата и създаване на контейнер DI. След това получава услугата `Application` от контейнера, която стартира уеб приложението: ```php -// инициализиране на средата + получаване на обект Configurator -$configurator = App\Bootstrap::boot(); -// създаване на DI-контейнер -$container = $configurator->createContainer(); -// DI-контейнерът ще създаде обект Nette\Application\Application +$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 @@ Nette прави разграничение между два основни р Ако искате да активирате режима за разработка в други случаи, например за програмисти, които имат достъп от определен IP адрес, можете да използвате `setDebugMode()`: ```php -$configurator->setDebugMode('23.75.345.200'); // един или повече IP адреси +$this->configurator->setDebugMode('23.75.345.200'); // един или повече IP адреси ``` Определено препоръчваме да комбинирате IP адреса с "бисквитка". Ще съхраним тайния токен в "бисквитката" `nette-debug', например, `secret1234`, а режимът за разработка ще бъде активиран за програмистите с тази комбинация от IP и "бисквитка". ```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-контейнер, 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() Конфигураторът ви позволява да зададете часовата зона за вашето приложение. ```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/services.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()`, за да разграничите отделните уеб проекти. Като алтернатива можете да добавите и други методи, например `bootTestEnvironment()` за инициализиране на средата за unit тестове, `bootConsoleApplication()` за скриптове, извикани от командния ред, и т.н. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Инициализация на Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container { - $configurator = self::boot(); - Tester\Environment::setup(); // Инициализация Nette Tester - return $configurator; + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); } ``` diff --git a/application/bg/components.texy b/application/bg/components.texy index e5ccab94e7..ab5c216e1c 100644 --- a/application/bg/components.texy +++ b/application/bg/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] ================================================= diff --git a/application/bg/configuration.texy b/application/bg/configuration.texy index 30bc0d30c0..0e2aeb11b1 100644 --- a/application/bg/configuration.texy +++ b/application/bg/configuration.texy @@ -95,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 ``` @@ -104,7 +107,7 @@ latte: ```neon latte: расширения: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/bg/how-it-works.texy b/application/bg/how-it-works.texy index 3cdb0786e2..5ca8332a47 100644 --- a/application/bg/how-it-works.texy +++ b/application/bg/how-it-works.texy @@ -22,13 +22,13 @@ /--pre web-project/ ├── app/ ← каталог с приложением -│ ├── Presenters/ ← классы презентеров -│ │ ├── HomePresenter.php ← Класс презентера главной страницы -│ │ └── templates/ ← директория шаблонов -│ │ ├── @layout.latte ← шаблон общего макета -│ │ └── Home/ ← шаблоны презентера главной страницы -│ │ └── default.latte ← шаблон действия `default` -│ ├── Router/ ← конфигурация URL-адресов +│ ├── Основни/ ← основни необходими класове +│ │ └── RouterFactory.php ← конфигуриране на URL адреси +│ ├── UI/ ← презентатори, шаблони и др. +│ │ ├── @layout.latte ← шаблон на споделено оформление +│ │ └── Home/ ← Директория за водещи +│ │ ├── HomePresenter.php ← Клас на Home Presenter +│ │ └── default.latte ← шаблон за действие default │ └── Bootstrap.php ← загрузочный класс Bootstrap ├── bin/ ← скрипты командной строки ├── config/ ← файлы конфигурации @@ -91,7 +91,7 @@ Nette е наставник, който ви напътства да пишет Приложението започва с искане към т.нар. маршрутизатор да реши на кой от презентаторите да изпрати текущата заявка за обработка. Маршрутизаторът решава чия е отговорността. Той разглежда входния URL адрес `https://example.com/product/123`, който иска продукт `показать` с `id: 123` като действие. Добър навик е да записвате двойките водещ + действие, разделени с двоеточие: `Продукт:показать`. -Следователно маршрутизаторът е преобразувал 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 да създаде презентатора, тъй като създаването на обекти е негова работа. @@ -121,12 +121,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter След това водещият връща отговор. Това може да бъде HTML страница, изображение, XML документ, файл, изпратен от диска, JSON или пренасочване към друга страница. Важно е да се отбележи, че ако не посочим изрично как да се отговори (какъвто е случаят с `ProductPresenter`), отговорът ще бъде шаблон, показващ HTML страница. Защо? Ами защото в 99% от случаите искаме да покажем шаблон, водещият приема това поведение по подразбиране и иска да улесни работата ни. Това е гледната точка на Нете. -Дори не е необходимо да указваме кой шаблон да се покаже, той сам извежда пътя до него според проста логика. В случая с водещия `Product` и действието `show`, той се опитва да провери дали някой от тези файлове с шаблони съществува спрямо директорията, в която се намира класът `ProductPresenter`: +Дори не е необходимо да посочваме кой шаблон да се визуализира; рамката сама ще определи пътя. В случая с действието `show` тя просто се опитва да зареди шаблона `show.latte` в директорията с класа `ProductPresenter`. Тя също така се опитва да намери оформлението във файла `@layout.latte` (повече за [търсенето на шаблони |templates#Template Lookup]). -- `templates/Product/show.latte` -- `templates/Product.show.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/bg/modules.texy b/application/bg/modules.texy index ddecd3f1a8..b675e91dd1 100644 --- a/application/bg/modules.texy +++ b/application/bg/modules.texy @@ -2,29 +2,31 @@ ****** .[perex] -В Nette модулите са логическите единици, от които се състои едно приложение. Те включват главни модули, шаблони, евентуално компоненти и класове модели. +Модулите внасят яснота в приложенията на Nette, като улесняват лесното им разделяне на логически единици. -Един компонент за презентатори и един за шаблони няма да са достатъчни за реални проекти. Натрупването на десетки файлове в една папка е меко казано неорганизирано. Как да излезем от тази ситуация? Просто ги разделяме на поддиректории на диска и на пространства от имена в кода. Точно това правят модулите Nette. - -Така че нека забравим за една папка за презентатори и шаблони и вместо това да създадем модули като `Admin` и `Front`. +Подобно на организирането на файловете в папки на твърдия диск, в Nette можем да разделим презентатори, шаблони и други спомагателни класове на модули. Как работи това на практика? Просто чрез включване на нови поддиректории в структурата. Ето един пример за структура с два модула - Front и Admin: /--pre -app/ -├── Presenters/ -├── Modules/ ← директория с модулями -│ ├── Admin/ ← модуль Admin -│ │ ├── Presenters/ ← его презентеры -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← модуль Front -│ └── Presenters/ ← его презентеры -│ └── ... +app/ +├── UI/ +│ ├── Admin/ ← Admin module +│ │ ├── @layout.latte +│ │ ├── Dashboard/ +│ │ │ ├── DashboardPresenter.php +│ │ │ └── default.latte +│ │ └── ... +│ ├── Front/ ← Front module +│ │ ├── @layout.latte +│ │ ├── Home/ +│ │ │ ├── HomePresenter.php +│ │ │ └── default.latte +│ │ └── ... \-- -Тази структура на директориите ще бъде отразена в пространствата за имена на класовете, така че например `DashboardPresenter` ще бъде в пространството `App\Modules\Admin\Presenters`: +Тази структура на директориите е отразена в пространствата от имена на класовете, така че например `DashboardPresenter` се намира в пространството от имена `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 знае, че `Admin:Dashboard` представлява класа `App\Modules\Admin\Presenters\DashboardPresenter`? Говорим за това, като използваме [картографирането |#Mapping] в конфигурацията. -Така че дадената структура не е фиксирана и можете да я променяте по свое усмотрение. +В приложението се позоваваме на презентатора `Dashboard` в рамките на модула `Admin`, като използваме запис с двоеточие като `Admin:Dashboard`. За неговото действие `default` го наричаме `Admin:Dashboard:default`. -Модулите, разбира се, могат да съдържат всички други части, освен презентатори и шаблони, като компоненти, класове модели и др. +Представената структура не е твърда; в конфигурацията можете [напълно |#mapping] да [я адаптирате към вашите нужди |#mapping]. .[tip] + +Модулите могат да включват всички други файлове, като компоненти и спомагателни класове, в допълнение към презентаторите и шаблоните. Ако обмисляте къде да ги поставите, помислете за използването на папка `Accessory`: + +/--pre +app/ +├── UI/ +│ ├── Admin/ +│ │ ├── Accessory/ +│ │ │ ├── FormFactory.php +│ │ │ └── AdminLayout.php +│ │ ├── Dashboard/ +│ │ └── ... +\-- Вложени модули .[#toc-nested-modules] ------------------------------------- -Модулите не трябва да образуват само плоска структура, можете да създавате и подмодули, например: +Модулите могат да имат няколко нива на влагане, подобно на структурата на директориите на диска: /--pre -app/ -├── Modules/ ← директория с модулями -│ ├── Blog/ ← модуль Blog -│ │ ├── Admin/ ← подмодуль Admin -│ │ │ ├── Presenters/ +app/ +├── UI/ +│ ├── Blog/ ← Blog module +│ │ ├── Admin/ ← Admin submodule +│ │ │ ├── Dashboard/ +│ │ │ └── ... +│ │ ├── Front/ ← Front submodule +│ │ │ ├── @layout.latte +│ │ │ ├── Home/ │ │ │ └── ... -│ │ └── Front/ ← подмодуль Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← модуль Forum +│ ├── Forum/ ← Forum module │ │ └── ... \-- -Така модулът `Blog` се разделя на подмодули `Admin` и `Front`. Това отново ще бъде отразено в пространствата от имена, които ще бъдат `App\Modules\Blog\Admin\Presenters` и т.н. Главният модул `Dashboard` в рамките на подмодула се нарича `Blog:Admin:Dashboard`. +Модулът `Blog` е разделен на подмодули `Admin` и `Front`. Това е отразено и в пространствата от имена, които след това се появяват като `App\UI\Blog\Admin` и по подобен начин. За да се позовем на презентатора `Dashboard` в рамките на подмодула `Admin`, го наричаме `Blog:Admin:Dashboard`. -Разклоненията могат да бъдат толкова дълбоки, колкото искате, така че можете да създавате подмодули. +Влагането може да бъде толкова дълбоко, колкото е необходимо, като позволява създаването на подмодули. + +Например, ако в администрацията имате много презентатори, свързани с управлението на поръчки, като `OrderDetail`, `OrderEdit`, `OrderDispatch` и т.н., може да създадете модул `Order`, в който ще бъдат организирани презентатори като `Detail`, `Edit`, `Dispatch` и други. Създаване на връзки .[#toc-creating-links] @@ -102,46 +118,66 @@ class DashboardPresenter extends Nette\Application\UI\Presenter Картографиране .[#toc-mapping] ------------------------------ -Определя правилата, по които името на класа се извежда от главното име. Записваме ги в [конфигурацията |configuration] под ключа `application › mapping`. +Съпоставянето определя правилата за извеждане на името на класа от името на водещия. Тези правила се посочват в [конфигурацията |configuration] под ключа `application › mapping`. + +Структурите на директориите, споменати по-рано на тази страница, се основават на следното съпоставяне: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Нека започнем с пример, при който не се използват модули. Искаме само главните класове да имат пространството от имена `App\Presenters`. Това означава, че искаме главното име, например `Home`, да се съпостави с класа `App\Presenters\HomePresenter`. Това може да се постигне със следната конфигурация: +Как работи картографирането? За по-добро разбиране нека първо си представим приложение без модули. Искаме класовете на презентаторите да попадат в пространството от имена `App\UI`, така че презентаторът `Home` да се съпостави с класа `App\UI\HomePresenter`. Това може да се постигне с тази конфигурация: ```neon application: - mapping: App\Presenters\*Presenter + 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\HomePresenter`, а презентер `Admin:Dashboard` - `App\AdminModule\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/bg/presenters.texy b/application/bg/presenters.texy index efcce1d904..bf2aeb04fc 100644 --- a/application/bg/presenters.texy +++ b/application/bg/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 @@ -384,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 @@ -452,17 +452,6 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); В `#[Requires]` предоставя разширени опции за ограничаване на достъпа до презентаторите и техните методи. Той може да се използва за определяне на HTTP методи, изискване на AJAX заявки, ограничаване на достъпа до същия произход и ограничаване на достъпа само до препращане. Атрибутът може да се прилага към класове на презентатори, както и към отделни методи, като например `action()`, `render()`, `handle()`, и `createComponent()`. -Ето един пример за използването му за ограничаване на достъпа само до метода HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Можете да зададете тези ограничения: - на HTTP методите: `#[Requires(methods: ['GET', 'POST'])]` - изискващи AJAX заявка: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ class MyPresenter extends Nette\Application\UI\Presenter - достъп само чрез препращане: `#[Requires(forward: true)]` - ограничения за конкретни действия: `#[Requires(actions: 'default')]` -Условията могат да се комбинират чрез изброяване на няколко атрибута или чрез обединяването им в един: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +За подробности вижте [Как да използвате Requires атрибут |best-practices:attribute-requires]. Проверка на метода HTTP .[#toc-http-method-check] diff --git a/application/bg/routing.texy b/application/bg/routing.texy index 300a7755e2..26abc2fb5d 100644 --- a/application/bg/routing.texy +++ b/application/bg/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', /* ... */); Интеграция .[#toc-integration] ============================== -За да свържем маршрутизатора си с приложението, трябва да информираме за това контейнера DI. Най-лесният начин е да се подготви фабрика, която ще създаде обект маршрутизатор, и да се каже на конфигурацията на контейнера да го използва. Да предположим, че напишем метод за това, `App\Router\RouterFactory::createRouter()`: +За да свържем маршрутизатора си с приложението, трябва да информираме за това контейнера DI. Най-лесният начин е да се подготви фабрика, която ще създаде обект маршрутизатор, и да се каже на конфигурацията на контейнера да го използва. Да предположим, че напишем метод за това, `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 ``` Всички зависимости, като например връзки към бази данни и т.н., се предават на метода на фабриката като параметри, като се използва [автоматично свързване |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/bg/templates.texy b/application/bg/templates.texy index 850d3e2ecd..0be4f81857 100644 --- a/application/bg/templates.texy +++ b/application/bg/templates.texy @@ -34,35 +34,81 @@ Nette използва системата за шаблони [Latte |latte:]. L Той дефинира блок `content`, който се вмъква вместо `{include content}` в оформлението, и замества блока `title`, който презаписва `{block title}` в оформлението. Опитайте се да си представите резултата. -Търсене на шаблони .[#toc-search-for-templates] ------------------------------------------------ +Търсене на шаблони .[#toc-template-lookup] +------------------------------------------ + +В презентаторите не е необходимо да посочвате кой шаблон трябва да бъде визуализиран; рамката автоматично ще определи пътя, което ще ви улесни при кодирането. + +Ако използвате структура от директории, в която всеки презентатор има своя собствена директория, просто поставете шаблона в тази директория под името на действието (т.е. изглед). Например, за действието `default` използвайте шаблона `default.latte`: -Пътят към шаблоните се определя от главния модул с помощта на проста логика. Той ще се опита да провери дали има някой от тези файлове, разположен спрямо главната директория на класа, където `` е името на текущия главен модул, а `` е името на текущото събитие: +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -- `templates//.latte` -- `templates/..latte` +Ако използвате структура, в която презентаторите са заедно в една директория, а шаблоните - в папка `templates`, запишете я или във файл `..latte` или `/.latte`: -Ако шаблонът не бъде намерен, ще се опита да търси в директорията `templates` едно ниво по-нагоре, т.е. на същото ниво като директорията с класа на водещия. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- -Ако шаблонът не бъде намерен и там, отговорът ще бъде [грешка 404 |presenters#Error 404 etc.]. +Директорията `templates` може да бъде поставена и едно ниво по-нагоре, на същото ниво като директорията с класовете на водещите. -Можете също така да промените изгледа с помощта на `$this->setView('jineView')`. Или вместо да търсите директно, посочете името на файла с шаблона, като използвате `$this->template->setFile('/path/to/template.latte')`. +Ако шаблонът не бъде намерен, презентаторът отговаря с [грешка 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('jinyLayout')`, така че ще бъдат изпробвани файлове `@jinyLayout.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/bg/latte-kak-da-izpolzvame-sistemata-ot-tipove: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void Версия 3 на Latte предлага по-усъвършенстван начин за създаване на [разширение за |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/cs/ajax.texy b/application/cs/ajax.texy index 407622ab95..327977ea19 100644 --- a/application/cs/ajax.texy +++ b/application/cs/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Nejprve je potřeba knihovnu [inicializovat |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: + +```js +naja.initialize(); +``` + Aby se z obyčejného odkazu (signálu) nebo odeslání formuláře vytvořil AJAXový požadavek, stačí označit příslušný odkaz, formulář nebo tlačítko třídou `ajax`: ```html diff --git a/application/cs/bootstrap.texy b/application/cs/bootstrap.texy index a0b9db0792..6357fe0d4d 100644 --- a/application/cs/bootstrap.texy +++ b/application/cs/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__); + // Konfigurátor je zodpovědný za nastavení prostředí aplikace a služeb. + $this->configurator = new Configurator; + // Nastaví adresář pro dočasné soubory generované Nette (např. zkompilované šablony) + $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 je chytré a vývojový režim se zapíná automaticky, + // nebo jej můžete povolit pro konkrétní IP adresu odkomentováním následujícího řádku: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Aktivuje Tracy: ultimátní "švýcarský nůž" pro ladění. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: automaticky načítá všechny třídy ve zvoleném adresáři + $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; + } + + private function setupContainer(): void + { + // Načte konfigurační soubory + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } ``` @@ -40,16 +66,15 @@ class Bootstrap index.php ========= -Prvotní soubor je v případě webových aplikací `index.php`, který se nachází ve veřejném adresáři `www/`. Ten si nechá od třídy Bootstrap inicializovat prostředí a vrátit `$configurator` a následně vyrobí DI kontejner. Poté z něj získá službu `Application`, kterou spustí webovou aplikaci: +Prvotní soubor je v případě webových aplikací `index.php`, který se nachází ve veřejném adresáři `www/`. Ten si nechá od třídy Bootstrap inicializovat prostředí a vyrobit DI kontejner. Poté z něj získá službu `Application`, která spustí webovou aplikaci: ```php -// inicializace prostředí + získání objektu Configurator -$configurator = App\Bootstrap::boot(); -// vytvoření DI kontejneru -$container = $configurator->createContainer(); +$bootstrap = new App\Bootstrap; +// Inicializace prostředí + vytvoření DI kontejneru +$container = $bootstrap->bootWebApplication(); // DI kontejner vytvoří objekt Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// spuštění Nette aplikace +// Spuštění aplikace Nette a zpracování příchozího požadavku $application->run(); ``` @@ -66,19 +91,19 @@ Volba režimu se provádí autodetekcí, takže obvykle není potřeba nic konfi Pokud chceme vývojářský režim povolit i v dalších případech, například programátorům přistupujícím z konkrétní IP adresy, použijeme `setDebugMode()`: ```php -$configurator->setDebugMode('23.75.345.200'); // lze uvést i pole IP adres +$this->configurator->setDebugMode('23.75.345.200'); // lze uvést i pole IP adres ``` Rozhodně doporučujeme kombinovat IP adresu s cookie. Do cookie `nette-debug` uložíme tajný token, např. `secret1234`, a tímto způsobem aktivujeme vývojářský režim pro programátory přistupující z konkrétní IP adresy a zároveň mající v cookie zmíněný token: ```php -$configurator->setDebugMode('secret1234@23.75.345.200'); +$this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` Vývojářský režim můžeme také vypnout úplně, i pro localhost: ```php -$configurator->setDebugMode(false); +$this->configurator->setDebugMode(false); ``` Pozor, hodnota `true` zapne vývojářský režim natvrdo, což se nikdy nesmí stát na produkčním serveru. @@ -90,7 +115,7 @@ Debugovací nástroj Tracy Pro snadné debugování ještě zapneme skvělý nástroj [Tracy |tracy:]. Ve vývojářském režimu chyby vizualizuje a v produkčním režimu chyby loguje do uvedeného adresáře: ```php -$configurator->enableTracy($appDir . '/log'); +$this->configurator->enableTracy($this->rootDir . '/log'); ``` @@ -100,7 +125,7 @@ Dočasné soubory Nette využívá cache pro DI kontejner, RobotLoader, šablony atd. Proto je nutné nastavit cestu k adresáři, kam se bude cache ukládat: ```php -$configurator->setTempDirectory($appDir . '/temp'); +$this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` Na Linuxu nebo macOS nastavte adresářům `log/` a `temp/` [práva pro zápis |nette:troubleshooting#Nastavení práv adresářů]. @@ -112,7 +137,7 @@ RobotLoader Zpravidla budeme chtít automaticky načítat třídy pomocí [RobotLoaderu |robot-loader:], musíme ho tedy nastartovat a necháme jej načítat třídy z adresáře, kde je umístěný `Bootstrap.php` (tj. `__DIR__`), a všech podadresářů: ```php -$configurator->createRobotLoader() +$this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); ``` @@ -126,7 +151,7 @@ Timezone Přes konfigurátor můžete nastavit výchozí časovou zónu. ```php -$configurator->setTimeZone('Europe/Prague'); +$this->configurator->setTimeZone('Europe/Prague'); ``` @@ -143,16 +168,17 @@ Ve vývojářském režimu se kontejner automaticky aktualizuje při každé zm Konfigurační soubory načteme pomocí `addConfig()`: ```php -$configurator->addConfig($appDir . '/config/common.neon'); +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` Pokud chceme přidat více konfiguračních souborů, můžeme funkci `addConfig()` zavolat vícekrát. ```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/services.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 @@ Statické parametry Parametry používané v konfiguračních souborech můžeme definovat [v sekci `parameters`|dependency-injection:configuration#parametry] a také je předávat (či přepisovat) metodou `addStaticParameters()` (má alias `addParameters()`). Důležité je, že různé hodnoty parametrů způsobí vygenerování dalších DI kontejnerů, tedy dalších tříd. ```php -$configurator->addStaticParameters([ +$this->configurator->addStaticParameters([ 'projectId' => 23, ]); ``` @@ -183,7 +209,7 @@ Dynamické parametry Do kontejneru můžeme přidat i dynamické parametry, jejichž různé hodnoty na rozdíl od statických parameterů nezpůsobí generování nových DI kontejnerů. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'remoteIp' => $_SERVER['REMOTE_ADDR'], ]); ``` @@ -191,7 +217,7 @@ $configurator->addDynamicParameters([ Jednoduše tak můžeme přidat např. environmentální proměnné, na které se pak lze v konfiguraci odkázat zápisem `%env.variable%`. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'env' => getenv(), ]); ``` @@ -206,6 +232,7 @@ V konfiguračních souborech můžete využít tyto statické parametry: - `%wwwDir%` je absolutní cesta k adresáři se vstupním souborem `index.php` - `%tempDir%` je absolutní cesta k adresáři pro dočasné soubory - `%vendorDir%` je absolutní cesta k adresáři, kam Composer instaluje knihovny +- `%rootDir%` je absolutní cesta ke kořenovému adresáři projektu - `%debugMode%` udává, zda je aplikace v debugovacím režimu - `%consoleMode%` udává, zda request přišel přes příkazovou řádku @@ -225,7 +252,7 @@ services: A v bootstrapu do kontejneru vložíme objekt: ```php -$configurator->addServices([ +$this->configurator->addServices([ 'myservice' => new App\Model\MyCustomService('foobar'), ]); ``` @@ -234,13 +261,21 @@ $configurator->addServices([ Odlišné prostředí ================= -Nebojte se upravit třídu Bootstrap podle svých potřeb. Metodě `boot()` můžete přidat parametry pro rozlišení webových projektů nebo doplnit další metody, například `bootForTests()`, která inicializuje prostředí pro jednotkové testy, `bootForCli()` pro skripty volané z příkazové řádky atd. +Nebojte se upravit třídu Bootstrap podle svých potřeb. Metodě `bootWebApplication()` můžete přidat parametry pro rozlišení webových projektů. Nebo můžeme doplnit další metody, například `bootTestEnvironment()`, která inicializuje prostředí pro jednotkové testy, `bootConsoleApplication()` pro skripty volané z příkazové řádky atd. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container { - $configurator = self::boot(); Tester\Environment::setup(); // inicializace Nette Testeru - 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/cs/components.texy b/application/cs/components.texy index d60ee2b7fd..db86af248c 100644 --- a/application/cs/components.texy +++ b/application/cs/components.texy @@ -198,7 +198,7 @@ Odkaz, který zavolá signál, vytvoříme obvyklým způsobem, tedy v šabloně click here ``` -Signál se vždy volá na aktuálním presenteru a view, tudíž není možné jej vyvolat na jiném presenteru nebo view. +Signál se vždy volá na aktuálním presenteru a action, není možné jej vyvolat na jiném presenteru nebo jiné action. Signál tedy způsobí znovunačtení stránky úplně stejně jako při původním požadavku, jen navíc zavolá obslužnou metodu signálu s příslušnými parametry. Pokud metoda neexistuje, vyhodí se výjimka [api:Nette\Application\UI\BadSignalException], která se uživateli zobrazí jako chybová stránka 403 Forbidden. @@ -230,6 +230,28 @@ $this->redirect(/* ... */); // a přesměrujeme ``` +Přesměrování po signálu +======================= + +Po zpracování signálu komponenty často následuje přesměrování. Je to podobná situace jako u formulářů - po jejich odeslání také přesměrováváme, aby při obnovení stránky v prohlížeči nedošlo k opětovnému odeslání dat. + +```php +$this->redirect('this') // přesměruje na aktuální presenter a action +``` + +Protože komponenta je znovupoužitelný prvek a obvykle by neměla mít přímou vazbu na konkrétní presentery, metody `redirect()` a `link()` automaticky interpretují parametr jako signál komponenty: + +```php +$this->redirect('click') // přesměruje na signál 'click' téže komponenty +``` + +Pokud potřebujete přesměrovat na jiný presenter či akci, můžete to udělat prostřednictvím presenteru: + +```php +$this->getPresenter()->redirect('Product:show'); // přesměruje na jiný presenter/action +``` + + Persistentní parametry ====================== @@ -430,7 +452,7 @@ class PaginatingControl extends Control } ``` -Opačný proces, tedy sesbírání hodnot z persistentních properites, má na starosti metoda `saveState()`. +Opačný proces, tedy sesbírání hodnot z persistentních properties, má na starosti metoda `saveState()`. Signály do hloubky @@ -444,7 +466,7 @@ Signál může přijímat jakákoliv komponenta, presenter nebo objekt, který i Mezi hlavní příjemce signálů budou patřit `Presentery` a vizuální komponenty dědící od `Control`. Signál má sloužit jako znamení pro objekt, že má něco udělat – anketa si má započítat hlas od uživatele, blok s novinkami se má rozbalit a zobrazit dvakrát tolik novinek, formulář byl odeslán a má zpracovat data a podobně. -URL pro signál vytváříme pomocí metody [Component::link() |api:Nette\Application\UI\Component::link()]. Jako parametr `$destination` předáme řetězec `{signal}!` a jako `$args` pole argumentů, které chceme signálu předat. Signál se vždy volá na aktuální view s aktuálními parametry, parametry signálu se jen přidají. Navíc se přidává hned na začátku **parametr `?do`, který určuje signál**. +URL pro signál vytváříme pomocí metody [Component::link() |api:Nette\Application\UI\Component::link()]. Jako parametr `$destination` předáme řetězec `{signal}!` a jako `$args` pole argumentů, které chceme signálu předat. Signál se vždy volá na aktuálním presenteru a action s aktuálními parametry, parametry signálu se jen přidají. Navíc se přidává hned na začátku **parametr `?do`, který určuje signál**. Jeho formát je buď `{signal}`, nebo `{signalReceiver}-{signal}`. `{signalReceiver}` je název komponenty v presenteru. Proto nemůže být v názvu komponenty pomlčka – používá se k oddělení názvu komponenty a signálu, je ovšem možné takto zanořit několik komponent. diff --git a/application/cs/configuration.texy b/application/cs/configuration.texy index acab7d7adf..84954e2e59 100644 --- a/application/cs/configuration.texy +++ b/application/cs/configuration.texy @@ -95,6 +95,9 @@ latte: # aktivuje [kontrolu vygenerovaného kódu |latte:develop#Kontrola vygenerovaného kódu] phpLinter: ... # (string) výchozí je null + # nastaví locale + locale: cs_CZ # (string) výchozí je null + # třída objektu $this->template templateClass: App\MyTemplateClass # výchozí je Nette\Bridges\ApplicationLatte\DefaultTemplate ``` @@ -104,7 +107,7 @@ Pokud používáte Latte verze 3, můžete přidávat nové [rozšíření |latt ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` Pokud používáte Latte verze 2, můžete registrovat nové tagy (makra) buď uvedením jména třídy, nebo referencí na službu. Jako výchozí je zavolána metoda `install()`, ale to lze změnit tím, že uvedeme jméno jiné metody: diff --git a/application/cs/how-it-works.texy b/application/cs/how-it-works.texy index 92ff5e9d9b..d58cd6c833 100644 --- a/application/cs/how-it-works.texy +++ b/application/cs/how-it-works.texy @@ -22,13 +22,13 @@ Adresářová struktura vypadá nějak takto: /--pre web-project/ ├── app/ ← adresář s aplikací -│ ├── Presenters/ ← presentery a šablony -│ │ ├── HomePresenter.php ← třída presenteru Home -│ │ └── templates/ ← adresář se šablonami -│ │ ├── @layout.latte ← šablona layoutu -│ │ └── Home/ ← šablony presenteru Home -│ │ └── default.latte ← šablona akce 'default' -│ ├── Router/ ← konfigurace URL adres +│ ├── Core/ ← základní třídy nutné pro chod +│ │ └── RouterFactory.php ← konfigurace URL adres +│ ├── UI/ ← presentery, šablony & spol. +│ │ ├── @layout.latte ← šablona layoutu +│ │ └── Home/ ← adresář presenteru Home +│ │ ├── HomePresenter.php ← třída presenteru Home +│ │ └── default.latte ← šablona akce default │ └── Bootstrap.php ← zaváděcí třída Bootstrap ├── bin/ ← skripty spouštěné z příkazové řádky ├── config/ ← konfigurační soubory @@ -91,7 +91,7 @@ Aplikace psané v Nette se člení do spousty tzv. presenterů (v jiných framew Application začne tím, že požádá tzv. router, aby rozhodl, kterému z presenterů předat aktuální požadavek k vyřízení. Router rozhodne, čí je to zodpovědnost. Podívá se na vstupní URL `https://example.com/product/123` a na základě toho, jak je nastavený, rozhodne, že tohle je práce např. pro **presenter** `Product`, po kterém bude chtít jako **akci** zobrazení (`show`) produktu s `id: 123`. Dvojici presenter + akce je dobrým zvykem zapisovat oddělené dvojtečkou jako `Product:show`. -Tedy router transformoval URL na dvojici `Presenter:action` + parametry, v našem případě `Product:show` + `id: 123`. Jak takový router vypadá se můžete podívat v souboru `app/Router/RouterFactory.php` a podrobně ho popisujeme v kapitole [Routing]. +Tedy router transformoval URL na dvojici `Presenter:action` + parametry, v našem případě `Product:show` + `id: 123`. Jak takový router vypadá se můžete podívat v souboru `app/Core/RouterFactory.php` a podrobně ho popisujeme v kapitole [Routing]. Pojďme dál. Application už zná jméno presenteru a může pokračovat dál. Tím že vyrobí objekt třídy `ProductPresenter`, což je kód presenteru `Product`. Přesněji řečeno, požádá DI kontejner, aby presenter vyrobil, protože od vyrábění je tu on. @@ -121,12 +121,9 @@ Takže, zavolala se metoda `renderShow(123)`, jejíž kód je sice smyšlený p Následně presenter vrátí odpověď. Tou může být HTML stránka, obrázek, XML dokument, odeslání souboru z disku, JSON nebo třeba přesměrování na jinou stránku. Důležité je, že pokud explicitně neřekneme, jak má odpovědět (což je případ `ProductPresenter`), bude odpovědí vykreslení šablony s HTML stránkou. Proč? Protože v 99 % případů chceme vykreslit šablonu, tudíž presenter tohle chování bere jako výchozí a chce nám ulehčit práci. To je smyslem Nette. -Nemusíme ani uvádět, jakou šablonu vykreslit, cestu k ní si odvodí podle jednoduché logiky. V případě presenteru `Product` a akce `show` zkusí, zda existuje jeden z těchto souborů se šablonou uložených relativně od adresáře s třídou `ProductPresenter`: +Nemusíme ani uvádět, jakou šablonu vykreslit, cestu k ní si odvodí sám. V případě akce `show` jednodušše zkusí načíst šablonu `show.latte` v adresáři s třídou `ProductPresenter`. Taktéž se pokusí dohledat layout v souboru `@layout.latte` (podrobněji o [dohledávání šablon|templates#hledani-sablon]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Taktéž se pokusí dohledat layout v souboru `@layout.latte` a následně šablonu vykreslí. Tím je úkol presenteru i celé aplikace dokonán a dílo jest završeno. Pokud by šablona neexistovala, vrátí se stránka s chybou 404. Více se o presenterech dočtete na stránce [Presentery|presenters]. +A následně šablony vykreslí. Tím je úkol presenteru i celé aplikace dokonán a dílo jest završeno. Pokud by šablona neexistovala, vrátí se stránka s chybou 404. Více se o presenterech dočtete na stránce [Presentery|presenters]. [* request-flow.svg *] @@ -137,7 +134,7 @@ Pro jistotu, zkusme si zrekapitulovat celý proces s trošku jinou URL: 3) router URL dekóduje jako dvojici `Home:default` 4) vytvoří se objekt třídy `HomePresenter` 5) zavolá se metoda `renderDefault()` (pokud existuje) -6) vykreslí se šablona např. `templates/Home/default.latte` s layoutem např. `templates/@layout.latte` +6) vykreslí se šablona např. `default.latte` s layoutem např. `@layout.latte` Možná jste se teď setkali s velkou spoustou nových pojmů, ale věříme, že dávají smysl. Tvorba aplikací v Nette je ohromná pohodička. diff --git a/application/cs/modules.texy b/application/cs/modules.texy index dd58b910d4..f215baec63 100644 --- a/application/cs/modules.texy +++ b/application/cs/modules.texy @@ -2,29 +2,31 @@ Moduly ****** .[perex] -Moduly představují v Nette logické celky, ze kterých se aplikace skládá. Jejich součástí jsou presentery, šablony, případně i komponenty a modelové třídy. +Moduly vnášejí do Nette aplikací přehlednost díky snadnému členění do logických celků. -S jednou složkou pro presentery a jednou pro šablony bychom si u reálných projektů nevystačili. Mít v jedné složce desítky souborů je minimálně nepřehledné. Jak z toho ven? Jednoduše je na disku rozdělíme do podadresářů a v kódu do jmenných prostorů. A přesně to jsou v Nette moduly. - -Zapomeňme tedy na jednu složku pro presentery a šablony a místo toho vytvoříme moduly, například `Admin` a `Front`. +Podobně jako na pevném disku organizujeme soubory do jednotlivých složek, tak i v Nette můžeme presentery, šablony a další pomocné třídy rozdělovat do modulů. Jak to funguje v praxi? Jednoduše začleníme do struktury nové podadresáře. Příklad takové struktury se dvěma moduly Front a Admin: /--pre -app/ -├── Presenters/ -├── Modules/ ← adresář s moduly +app/ +├── UI/ │ ├── Admin/ ← modul Admin -│ │ ├── Presenters/ ← jeho presentery -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← modul Front -│ └── Presenters/ ← jeho presentery -│ └── ... +│ │ ├── @layout.latte +│ │ ├── Dashboard/ +│ │ │ ├── DashboardPresenter.php +│ │ │ └── default.latte +│ │ └── ... +│ ├── Front/ ← modul Front +│ │ ├── @layout.latte +│ │ ├── Home/ +│ │ │ ├── HomePresenter.php +│ │ │ └── default.latte +│ │ └── ... \-- -Tuto adresářovou strukturu budou reflektovat jmenné prostory tříd, takže třeba `DashboardPresenter` bude v prostoru `App\Modules\Admin\Presenters`: +Tato adresářová struktura se odráží ve jmenných prostorech tříd, takže například `DashboardPresenter` se nachází ve jmenném prostoru `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 } ``` -Na presenter `Dashboard` uvnitř modulu `Admin` se v rámci aplikace odkazujeme pomocí dvojtečkové notace jako na `Admin:Dashboard`, na jeho akci `default` potom jako na `Admin:Dashboard:default`. -A jak Nette vlastní ví, že `Admin:Dashboard` představuje třídu `App\Modules\Admin\Presenters\DashboardPresenter`? To mu řekneme pomocí [#mapování] v konfiguraci. -Tedy uvedená struktura není pevná a můžete si ji upravit podle potřeb. +Na presenter `Dashboard` uvnitř modulu `Admin` odkazujeme v aplikaci pomocí dvojtečkové notace jako na `Admin:Dashboard`. Na jeho akci `default` potom jako na `Admin:Dashboard:default`. -Moduly mohou kromě presenterů a šablon samozřejmě obsahovat všechny další součásti, jako jsou třeba komponenty, modelové třídy, atd. +Představená struktura není pevná; můžete si ji zcela [přizpůsobit dle svých potřeb|#mapování] v konfiguraci. .[tip] + +Moduly mohou kromě presenterů a šablon samozřejmě zahrnovat všechny ostatní soubory, jako jsou například komponenty a pomocné třídy. Pokud uvažujete, kam je zařadit, zvažte využití složky `Accessory`: + +/--pre +app/ +├── UI/ +│ ├── Admin/ +│ │ ├── Accessory/ +│ │ │ ├── FormFactory.php +│ │ │ └── AdminLayout.php +│ │ ├── Dashboard/ +│ │ └── ... +\-- Vnořené moduly -------------- -Moduly nemusí tvořit jen plochou strukturu, lze vytvářet i submoduly, například: +Moduly mohou mít více úrovní zanoření, podobně jako adresářová struktura na disku: /--pre -app/ -├── Modules/ ← adresář s moduly +app/ +├── UI/ │ ├── Blog/ ← modul Blog │ │ ├── Admin/ ← submodul Admin -│ │ │ ├── Presenters/ +│ │ │ ├── Dashboard/ +│ │ │ └── ... +│ │ ├── Front/ ← submodul Front +│ │ │ ├── @layout.latte +│ │ │ ├── Home/ │ │ │ └── ... -│ │ └── Front/ ← submodul Front -│ │ ├── Presenters/ -│ │ └── ... │ ├── Forum/ ← modul Forum │ │ └── ... \-- -Tedy modul `Blog` je rozdělen do submodulů `Admin` a `Front`. A opět se to odrazí na jmenných prostorech, které budou `App\Modules\Blog\Admin\Presenters` apod. Na presenter `Dashboard` uvnitř submodulu se odkazujeme jako `Blog:Admin:Dashboard`. +Modul `Blog` je rozdělen na submoduly `Admin` a `Front`. To se projeví i ve jmenných prostorech, které pak budou vypadat jako `App\UI\Blog\Admin` a podobně. Na presenter `Dashboard` v rámci submodulu odkazujeme jako na `Blog:Admin:Dashboard`. -Zanořování může pokračovat libovolně hluboko, lze tedy vytvářet sub-submoduly. +Zanoření může být libovolně hluboké, což umožňuje vytvářet sub-submoduly. + +Pokud například v administraci máte mnoho presenterů týkajících se správy objednávek, jako jsou `OrderDetail`, `OrderEdit`, `OrderDispatch` atd., můžete pro lepší organizovanost vytvořit modul `Order`, ve kterém budou presentery `Detail`, `Edit`, `Dispatch` a další. Vytváření odkazů @@ -102,46 +118,66 @@ Viz [kapitola o routování |routing#Moduly]. Mapování -------- -Definuje pravidla, podle kterých se z názvu presenteru odvodí název třídy. Zapisujeme je v [konfiguraci|configuration] pod klíčem `application › mapping`. +Mapování definuje pravidla pro odvozování názvu třídy z názvu presenteru. Specifikujeme je v [konfiguraci|configuration] pod klíčem `application › mapping`. + +Adresářové struktury uváděné výše na této stránce vycházejí z tohoto mapování: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Začněme ukázkou, která moduly nepoužívá. Budeme jen chtít, aby třídy presenterů měly jmenný prostor `App\Presenters`. Tedy aby se presenter například `Home` mapoval na třídu `App\Presenters\HomePresenter`. Toho lze docílit následující konfigurací: +Jak mapování funguje? Pro lepší pochopení si nejprve představme aplikaci bez modulů. Chceme, aby třídy presenterů spadaly do jmenného prostoru `App\UI`, aby se presenter `Home` mapoval na třídu `App\UI\HomePresenter`. Což dosáhneme touto konfigurací: ```neon application: - mapping: App\Presenters\*Presenter + mapping: App\UI\*Presenter ``` -Název presenteru se nahradí za hvezdičku v masce třídy a výsledkem je název třídy. Snadné! +Mapování funguje tak, že název presenteru `Home` nahradí hvězdičku v masce `App\UI\*Presenter`, čímž získáme výsledný název třídy `App\UI\HomePresenter`. Jednoduché! + +Jak ale vidíte v ukázkách v této a dalších kapitolách, třídy presenterů umisťujeme do eponymních podadresářů, například presenter `Home` se mapuje na třídu `App\UI\Home\HomePresenter`. Toho dosáhneme zdvojením dvojtečky (vyžaduje Nette Application 3.2): + +```neon +application: + mapping: App\UI\**Presenter +``` -Pokud presentery členíme do modulů, můžeme pro každý modul mít vlastní mapování: +Nyní přistoupíme k mapování presenterů do modulů. Pro každý modul můžeme definovat specifické mapování: ```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 ``` -Nyní se presenter `Front:Home` mapuje na třídu `App\Modules\Front\Presenters\HomePresenter` a presenter `Admin:Dashboard` na třídu `App\Modules\Admin\Presenters\DashboardPresenter`. +Podle této konfigurace se presenter `Front:Home` mapuje na třídu `App\UI\Front\Home\HomePresenter`, zatímco presenter `Api:OAuth` na třídu `App\Api\OAuthPresenter`. -Praktičtější bude vytvořit obecné (hvězdičkové) pravidlo, které první dvě nahradí. V masce třídy přibude hvezdička navíc právě pro modul: +Protože moduly `Front` i `Admin` mají podobný způsob mapování a takových modulů bude nejspíš více, je možné vytvořit obecné pravidlo, které je nahradí. Do masky třídy tak přibude nová hvězdička pro modul: ```neon application: mapping: - *: App\Modules\*\Presenters\*Presenter + *: App\UI\*\**Presenter Api: App\Api\*Presenter ``` -Ale co když používáme vícenásobně zanořené moduly a máme třeba presenter `Admin:User:Edit`? V takovém případě se segment s hvězdičkou představující modul pro každou úroveň jednoduše zopakuje a výsledkem bude třída `App\Modules\Admin\User\Presenters\EditPresenter`. +Pro vícenásobně zanořené moduly, jako je například presenter `Admin:User:Edit`, se segment s hvězdičkou opakuje pro každou úroveň a výsledkem je třída `App\UI\Admin\User\Edit\EditPresenter`. Alternativním zápisem je místo řetězce použít pole skládající se ze tří segmentů. Tento zápis je ekvivaletní s předchozím: ```neon application: mapping: - *: [App\Modules, *, Presenters\*Presenter] + *: [App\UI, *, **Presenter] + Api: [App\Api, '', *Presenter] ``` -Výchozí hodnotou je `*Module\*Presenter`. +Pokud bychom měli v konfiguraci jen jediné pravidlo, ono obecné, můžeme zkráceně zapsat: + +```neon +application: + mapping: App\UI\*\**Presenter +``` diff --git a/application/cs/presenters.texy b/application/cs/presenters.texy index 4936a85634..06ff5d50b1 100644 --- a/application/cs/presenters.texy +++ b/application/cs/presenters.texy @@ -60,7 +60,7 @@ Obdoba metody `render()`. Zatímco `render()` je určená k tomu, ab Důležité je, že `action()` se volá dříve než `render()`, takže v ní můžeme případně změnit další běh dějin, tj. změnit šablonu, která se bude kreslit, a také metodu `render()`, která se bude volat. A to pomocí `setView('jineView')`. -Metodě se předávají parametry z požadavku. Je možné a doporučené uvést parametrům typy, např. `actionShow(int $id, string $slug = null)` - pokud bude parametr `id` chybět nebo pokud nebude integer, presenter vrátí [chybu 404|#Chyba 404 a spol.] a ukončí činnost. +Metodě se předávají parametry z požadavku. Je možné a doporučené uvést parametrům typy, např. `actionShow(int $id, ?string $slug = null)` - pokud bude parametr `id` chybět nebo pokud nebude integer, presenter vrátí [chybu 404|#Chyba 404 a spol.] a ukončí činnost. `handle(args...)` .{toc: handle()} @@ -205,7 +205,7 @@ $this->redirect(/* ... */); // a přesměrujeme Chyba 404 a spol. ================= -Pokud nelze splnit požadavek, třeba z důvodu, že článek který chceme zobrazit neexistuje v databázi, vyhodíme chybu 404 metodou `error(string $message = null, int $httpCode = 404)`. +Pokud nelze splnit požadavek, třeba z důvodu, že článek který chceme zobrazit neexistuje v databázi, vyhodíme chybu 404 metodou `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -384,7 +384,7 @@ K přesměrování nedojde při AJAXovém nebo POST požadavku, protože by doš Kanonizaci můžete vyvolat i manuálně pomocí metody `canonicalize()`, které se podobně jako metodě `link()` předá presenter, akce a parametry. Vyrobí odkaz a porovná ho s aktuální URL adresou. Pokud se liší, tak přesměruje na vygenerovaný odkaz. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // přesměruje, pokud $slug se liší od $realSlug @@ -452,17 +452,6 @@ Omezení přístupu pomocí `#[Requires]` .{data-version:3.2.2} Atribut `#[Requires]` poskytuje pokročilé možnosti pro omezení přístupu k presenterům a jejich metodám. Lze jej použít pro specifikaci HTTP metod, vyžadování AJAXového požadavku, omezení na stejný původ (same origin), a přístup pouze přes forwardování. Atribut lze aplikovat jak na třídy presenterů, tak na jednotlivé metody `action()`, `render()`, `handle()` a `createComponent()`. -Příklad použití pro omezení přístupu pouze HTTP metodou `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Můžete určit tyto omezení: - na HTTP metody: `#[Requires(methods: ['GET', 'POST'])]` - vyžadování AJAXového požadavku: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ Můžete určit tyto omezení: - přístup pouze přes forward: `#[Requires(forward: true)]` - omezení na konkrétní akce: `#[Requires(actions: 'default')]` -Kombinovat podmínky lze uvedením více atributů nebo spojením do jednoho: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// nebo - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Podrobnosti najdete v návodu [Jak používat atribut Requires |best-practices:attribute-requires]. Kontrola HTTP metody diff --git a/application/cs/routing.texy b/application/cs/routing.texy index 3f58166681..ac3cc3aa7a 100644 --- a/application/cs/routing.texy +++ b/application/cs/routing.texy @@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +225,7 @@ $router->addRoute('/[/]', [ ]); ``` -Nebo můžeme použít tuto formu, všimněte si přepisu validačního regulárního výrazu: +Pro detailnější specifikaci lze použít ještě rozšířenější formu, kde kromě výchozích hodnot můžeme nastavit i další vlastnosti parametrů, jako třeba validační regulární výraz (viz parametr `id`): ```php use Nette\Routing\Route; @@ -243,7 +243,7 @@ $router->addRoute('/[/]', [ ]); ``` -Tyto upovídanější formáty se hodí pro doplnění dalších metadat. +Je důležité poznamenat, že pokud parametry definované v poli nejsou uvedeny v masce cesty, jejich hodnoty nelze změnit, ani pomocí query parametrů uvedených za otazníkem v URL. Filtry a překlady @@ -477,10 +477,10 @@ $router->addRoute('index', /* ... */); Začlenění do aplikace ===================== -Abychom vytvořený router zapojili do aplikace, musíme o něm říci DI kontejneru. Nejsnazší cesta je připravit továrnu, která objekt routeru vyrobí, a sdělit v konfiguraci kontejneru, že ji má použít. Dejme tomu, že k tomu účelu napíšeme metodu `App\Router\RouterFactory::createRouter()`: +Abychom vytvořený router zapojili do aplikace, musíme o něm říci DI kontejneru. Nejsnazší cesta je připravit továrnu, která objekt routeru vyrobí, a sdělit v konfiguraci kontejneru, že ji má použít. Dejme tomu, že k tomu účelu napíšeme metodu `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -499,7 +499,7 @@ Do [konfigurace |dependency-injection:services] pak zapíšeme: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Jakékoliv závislosti, třeba na databázi atd, se předají tovární metodě jako její parametry pomocí [autowiringu|dependency-injection:autowiring]: @@ -663,7 +663,7 @@ Samostatným použitím myslíme využití schopností routeru v aplikaci, kter Takže opět si vytvoříme metodu, která nám sestaví router, např.: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Anebo objekty přímo vyrobíme: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/cs/templates.texy b/application/cs/templates.texy index 5d0de8dc1a..470c68748b 100644 --- a/application/cs/templates.texy +++ b/application/cs/templates.texy @@ -37,29 +37,75 @@ Ta definuje blok `content`, který se vloží na místo `{include content}` v la Hledání šablon -------------- -Cestu k šablonám odvodí presenter podle jednoduché logiky. Zkusí, zda existuje jeden z těchto souborů umístěných relativně od adresáře s třídou presenteru, kde `` je název aktuálního presenteru a `` je název aktuální akce: +Nemusíte v presenterech uvádět, jaká šablona se má vykreslit, framework cestu odvodí sám a ušetří vám psaní. -- `templates//.latte` -- `templates/..latte` +Pokud používáte adresářovou strukturu, kde každý presenter má vlastní adresář, jednodušše umístěte šablonu do tohoto adresáře pod jménem akce (resp. view), tj. pro akci `default` použijte šablonu `default.latte`: -Pokud šablonu nenajde, zkusí hledat ještě v adresáři `templates` o úroveň výš, tj. na stejné úrovni, jako je adresář s třídou presenteru. +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Pokud ani tam šablonu nenajde, je odpovědí [chyba 404|presenters#Chyba 404 a spol.]. +Pokud používáte strukturu, kde jsou společně presentery v jednom adresáři a šablony ve složce `templates`, uložte ji buď do souboru `..latte` nebo `/.latte`: -Můžete také změnit view pomocí `$this->setView('jineView')`. Nebo místo dohledávání přímo určit jméno souboru se šablonou pomocí `$this->template->setFile('/path/to/template.latte')`. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1. varianta + └── Home/ + └── default.latte ← 2. varianta +\-- + +Adresář `templates` může být umístěn také o úroveň výš, tj. na stejné úrovni, jako je adresář s třídami presenterů. + +Pokud se šablona nenajde, presenter odpoví [chybou 404 - page not found|presenters#Chyba 404 a spol]. + +View změníte pomocí `$this->setView('jineView')`. Také lze přímo určit soubor se šablonou pomocí `$this->template->setFile('/path/to/template.latte')`. .[note] Soubory, kde se dohledávají šablony, lze změnit překrytím metody [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], která vrací pole možných názvů souborů. -Layout se očekává v těchto souborech: -- `templates//@.latte` -- `templates/.@.latte` -- `templates/@.latte` layout společný pro více presenterů +Hledání šablony layoutu +----------------------- + +Nette také automaticky dohledává soubor s layoutem. + +Pokud používáte adresářovou strukturu, kde každý presenter má vlastní adresář, umístěte layout buď do složky s presenterem, pokud je specifický jen pro něj, nebo o úroveň výš, pokud je společný pro více presenterů: + +/--pre +app/ +└── UI/ + ├── @layout.latte ← společný layout + └── Home/ + ├── @layout.latte ← jen pro presenter Home + ├── HomePresenter.php + └── default.latte +\-- + +Pokud používáte strukturu, kde jsou společně presentery v jednom adresáři a šablony ve složce `templates`, bude se layout očekávat na těchto místech: + +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── @layout.latte ← společný layout + ├── Home.@layout.latte ← jen pro Home, 1. varianta + └── Home/ + └── @layout.latte ← jen pro Home, 2. varianta +\-- + +Pokud se presenter nachází v [modulu|modules], bude se dohledávat i o další adresářové úrovně výš, podle zanoření modulu. -Kde `` je název aktuálního presenteru a `` je název layoutu, což je standardně `'layout'`. Název lze změnit pomocí `$this->setLayout('jinyLayout')`, takže se budou zkoušet soubory `@jinyLayout.latte`. +Název layoutu lze změnit pomocí `$this->setLayout('layoutAdmin')` a pak se bude očekávat v souboru `@layoutAdmin.latte`. Také lze přímo určit soubor se šablonou layoutu pomocí `$this->setLayout('/path/to/template.latte')`. -Můžete také přímo určit jméno souboru se šablonou layoutu pomocí `$this->setLayout('/path/to/template.latte')`. Pomocí `$this->setLayout(false)` se dohledávání layoutu vypne. +Pomocí `$this->setLayout(false)` nebo značky `{layout none}` uvnitř šablony se dohledávání layoutu vypne. .[note] Soubory, kde se dohledávají šablony layoutu, lze změnit překrytím metody [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], která vrací pole možných názvů souborů. @@ -104,7 +150,7 @@ Anotace `@property-read` je určená pro IDE a statickou analýzu, díky ní bud Luxusu našeptávání si můžete dopřát i v šablonách, stačí do PhpStorm nainstalovat plugin pro Latte a uvést na začátek šablony název třídy, více v článku "Latte: jak na typový systém":https://blog.nette.org/cs/latte-jak-na-typovy-system: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void Latte ve verzi 3 nabízí pokročilejší způsob a to vytvoření si [extension |latte:creating-extension] pro každý webový projekt. Kusý příklad takové třídy: ```php -namespace App\Templating; +namespace App\UI\Accessory; final class LatteExtension extends Latte\Extension { @@ -214,7 +260,7 @@ Zaregistrujeme ji pomocí [konfigurace |configuration#Šablony Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\UI\Accessory\LatteExtension ``` @@ -239,7 +285,7 @@ Translator je alternativně možné nastavit pomocí [konfigurace |configuration ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` Poté lze překladač používat například jako filtr `|translate`, a to včetně doplňujících parametrů, které se předají metodě `translate()` (viz `foo, bar`): diff --git a/application/de/ajax.texy b/application/de/ajax.texy index 9b97c2db0e..fe36f40a83 100644 --- a/application/de/ajax.texy +++ b/application/de/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Zunächst müssen Sie die Bibliothek [initialisieren |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: + +```js +naja.initialize(); +``` + Um einen gewöhnlichen Link (Signal) oder eine Formularübermittlung zu einer AJAX-Anfrage zu machen, markieren Sie einfach den entsprechenden Link, das Formular oder die Schaltfläche mit der Klasse `ajax`: ```html diff --git a/application/de/bootstrap.texy b/application/de/bootstrap.texy index a5ddc1e604..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/services.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 a335f73618..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] ================================================== diff --git a/application/de/configuration.texy b/application/de/configuration.texy index d5b665b684..848f7abb64 100644 --- a/application/de/configuration.texy +++ b/application/de/configuration.texy @@ -95,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 ``` @@ -104,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/how-it-works.texy b/application/de/how-it-works.texy index 010ba04907..fde1735364 100644 --- a/application/de/how-it-works.texy +++ b/application/de/how-it-works.texy @@ -22,13 +22,13 @@ 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 @@ -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 090bd4702e..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. -Module können neben Presentern und Templates natürlich auch alle anderen Elemente enthalten, wie z.B. Komponenten, Modellklassen, etc. +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 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 beliebig tief gehen, so dass Sub-Submodule erstellt werden können. +Die Verschachtelung kann so tief wie nötig sein und erlaubt die Erstellung von Sub-Submodulen. + +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,49 +115,69 @@ Routing .[#toc-routing] Siehe [Kapitel über Routing |routing#Modules]. -Abbildung .[#toc-mapping] -------------------------- +Kartierung .[#toc-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. + +Die oben auf dieser Seite erwähnten Verzeichnisstrukturen basieren auf der folgenden Zuordnung: -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`. +```neon +application: + mapping: App\UI\*\**Presenter +``` -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: +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: ```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! +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 +``` -Wenn wir die Vortragenden in Module unterteilen, können wir für jedes Modul eine eigene Zuordnung vornehmen: +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 2407021005..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 @@ -384,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 @@ -452,17 +452,6 @@ Zugangsbeschränkung mit `#[Requires]` .[#toc-access-restriction-using-requires] 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()`. -Hier ein Beispiel für die Beschränkung des Zugriffs nur auf die Methode HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Sie können diese Einschränkungen angeben: - auf HTTP-Methoden: `#[Requires(methods: ['GET', 'POST'])]` - die eine AJAX-Anfrage erfordern: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ Sie können diese Einschränkungen angeben: - Zugriff nur über Weiterleitung: `#[Requires(forward: true)]` - Einschränkungen für bestimmte Aktionen: `#[Requires(actions: 'default')]` -Bedingungen können kombiniert werden, indem mehrere Attribute aufgelistet oder zu einem einzigen zusammengefasst werden: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Für Einzelheiten siehe [Verwendung des Requires Attributs |best-practices:attribute-requires]. HTTP-Methodenprüfung .[#toc-http-method-check] 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', /* ... */); 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/ajax.texy b/application/el/ajax.texy index de4975d290..ab0e2f48f5 100644 --- a/application/el/ajax.texy +++ b/application/el/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Πρώτα πρέπει να [αρχικοποιήσετε |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] τη βιβλιοθήκη: + +```js +naja.initialize(); +``` + Για να μετατρέψετε έναν συνηθισμένο σύνδεσμο (σήμα) ή την υποβολή φόρμας σε αίτηση AJAX, απλά σημειώστε τον αντίστοιχο σύνδεσμο, φόρμα ή κουμπί με την κλάση `ajax`: ```html diff --git a/application/el/bootstrap.texy b/application/el/bootstrap.texy index 7ee2f81c13..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/services.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 ed46dc4e74..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] ================================================ @@ -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 99bea2563d..dcf8543ece 100644 --- a/application/el/configuration.texy +++ b/application/el/configuration.texy @@ -95,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 ``` @@ -104,7 +107,7 @@ latte: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/el/how-it-works.texy b/application/el/how-it-works.texy index f7a4be0a0b..8593798f00 100644 --- a/application/el/how-it-works.texy +++ b/application/el/how-it-works.texy @@ -22,13 +22,13 @@ /--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 @@ -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 3277cccce0..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,46 +118,66 @@ class DashboardPresenter extends Nette\Application\UI\Presenter Χαρτογράφηση .[#toc-mapping] ---------------------------- -Καθορίζει τους κανόνες με τους οποίους το όνομα της κλάσης προκύπτει από το όνομα του παρουσιαστή. Τους γράφουμε στη [διαμόρφωση |configuration] κάτω από το κλειδί `application › mapping`. +Η αντιστοίχιση ορίζει τους κανόνες για την εξαγωγή του ονόματος της κλάσης από το όνομα του παρουσιαστή. Οι κανόνες αυτοί καθορίζονται στη [διαμόρφωση |configuration] στο κλειδί `application › mapping`. + +Οι δομές καταλόγων που αναφέρθηκαν νωρίτερα σε αυτή τη σελίδα βασίζονται στην ακόλουθη αντιστοίχιση: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Ας ξεκινήσουμε με ένα δείγμα που δεν χρησιμοποιεί ενότητες. Θα θέλουμε απλώς οι κλάσεις presenter να έχουν το namespace `App\Presenters`. Αυτό σημαίνει ότι ένας παρουσιαστής όπως το `Home` θα πρέπει να αντιστοιχίζεται στην κλάση `App\Presenters\HomePresenter`. Αυτό μπορεί να επιτευχθεί με την ακόλουθη διαμόρφωση: +Πώς λειτουργεί η χαρτογράφηση; Για καλύτερη κατανόηση, ας φανταστούμε πρώτα μια εφαρμογή χωρίς ενότητες. Θέλουμε οι κλάσεις του παρουσιαστή να υπάγονται στο χώρο ονομάτων `App\UI`, έτσι ώστε ο παρουσιαστής `Home` να αντιστοιχίζεται στην κλάση `App\UI\HomePresenter`. Αυτό μπορεί να επιτευχθεί με αυτή τη διαμόρφωση: ```neon application: - mapping: App\Presenters\*Presenter + 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 3f55886ec4..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 @@ -384,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 @@ -452,17 +452,6 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); Το `#[Requires]` παρέχει προηγμένες επιλογές για τον περιορισμό της πρόσβασης στους παρουσιαστές και τις μεθόδους τους. Μπορεί να χρησιμοποιηθεί για τον προσδιορισμό μεθόδων HTTP, την απαίτηση αιτήσεων AJAX, τον περιορισμό της πρόσβασης στην ίδια προέλευση και τον περιορισμό της πρόσβασης μόνο στην προώθηση. Το χαρακτηριστικό μπορεί να εφαρμοστεί σε κλάσεις παρουσιαστών καθώς και σε μεμονωμένες μεθόδους όπως οι `action()`, `render()`, `handle()`, και `createComponent()`. -Ακολουθεί ένα παράδειγμα χρήσης της για να περιορίσετε την πρόσβαση μόνο στη μέθοδο HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Μπορείτε να καθορίσετε αυτούς τους περιορισμούς: - σε μεθόδους HTTP: `#[Requires(methods: ['GET', 'POST'])]` - AJAX: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ class MyPresenter extends Nette\Application\UI\Presenter - πρόσβαση μόνο μέσω προώθησης: `#[Requires(forward: true)]` - περιορισμοί σε συγκεκριμένες ενέργειες: `#[Requires(actions: 'default')]` -Οι συνθήκες μπορούν να συνδυαστούν με την απαρίθμηση πολλαπλών χαρακτηριστικών ή με τη συνένωσή τους σε ένα: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Για λεπτομέρειες, ανατρέξτε στην ενότητα [Πώς να χρησιμοποιήσετε το Requires attribute |best-practices:attribute-requires]. Έλεγχος μεθόδου HTTP .[#toc-http-method-check] 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', /* ... */); Ενσωμάτωση .[#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/ajax.texy b/application/en/ajax.texy index a62ed95cfc..819a6e4627 100644 --- a/application/en/ajax.texy +++ b/application/en/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +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 diff --git a/application/en/bootstrap.texy b/application/en/bootstrap.texy index 14a412cd6f..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/services.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 42b40aaacb..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 ===================== @@ -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 fba71aa4fd..e9df62aef7 100644 --- a/application/en/configuration.texy +++ b/application/en/configuration.texy @@ -95,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 ``` @@ -104,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/how-it-works.texy b/application/en/how-it-works.texy index 612d192393..cb0f291e1b 100644 --- a/application/en/how-it-works.texy +++ b/application/en/how-it-works.texy @@ -22,13 +22,13 @@ 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 @@ -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 376ac872b9..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`. -Modules can of course contain all other items besides presenters and templates, such as components, model classes, etc. +The structure presented is not rigid; you can [fully customize it to your needs|#mapping] in the configuration. .[tip] + +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`. -The nesting can go as deep as you like, so sub-submodules can be created. +Nesting can be as deep as needed, allowing the creation of sub-submodules. + +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,46 +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`. + +The directory structures mentioned earlier on this page are based on the following mapping: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -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: +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: ```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! +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 +``` -If we divide presenters into modules, we can have our own mapping for each module: +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 324d00441d..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 @@ -384,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 @@ -452,17 +452,6 @@ 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()`. -Here’s an example of using it to restrict access to only the HTTP `POST` method: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - You can specify these restrictions: - on HTTP methods: `#[Requires(methods: ['GET', 'POST'])]` - requiring an AJAX request: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ You can specify these restrictions: - access only via forwarding: `#[Requires(forward: true)]` - restrictions on specific actions: `#[Requires(actions: 'default')]` -Conditions can be combined by listing multiple attributes or by joining them into one: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +For details, see [How to use the Requires attribute |best-practices:attribute-requires]. HTTP Method Check 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', /* ... */); 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/ajax.texy b/application/es/ajax.texy index a90acb698b..d1414e84c4 100644 --- a/application/es/ajax.texy +++ b/application/es/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +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 diff --git a/application/es/bootstrap.texy b/application/es/bootstrap.texy index bed14d1d22..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/services.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 0482a246a3..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] ===================================================== diff --git a/application/es/configuration.texy b/application/es/configuration.texy index bbde2bac4e..aa5273344b 100644 --- a/application/es/configuration.texy +++ b/application/es/configuration.texy @@ -95,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 ``` @@ -104,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/how-it-works.texy b/application/es/how-it-works.texy index 1706cb62a5..e4e0447853 100644 --- a/application/es/how-it-works.texy +++ b/application/es/how-it-works.texy @@ -22,13 +22,13 @@ 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 @@ -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 644cab5c1f..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`. -Por supuesto, los módulos pueden contener todos los demás elementos además de presentadores y plantillas, como componentes, clases modelo, etc. +La estructura presentada no es rígida; puede [adaptarla totalmente a sus necesidades |#mapping] en la configuración. .[tip] + +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 se desee, por lo que pueden crearse submódulos. +El anidamiento puede ser tan profundo como sea necesario, permitiendo la creación de sub-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,49 +115,69 @@ Enrutamiento .[#toc-routing] Véase el [capítulo sobre en rutamiento|routing#Modules]. -Mapeo .[#toc-mapping] ---------------------- +Cartografía .[#toc-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`. + +Las estructuras de directorios mencionadas anteriormente en esta página se basan en la siguiente asignación: -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`. +```neon +application: + mapping: App\UI\*\**Presenter +``` -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: +¿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: ```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. +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 +``` -Si dividimos a los presentadores en módulos, podemos tener nuestra propia asignación para cada módulo: +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 22cb178aa9..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()` se llame antes que `render()`para que dentro de él podamos posiblemente cambiar el siguiente curso del ciclo de vida, es decir, cambiar la plantilla que será renderizada y también el método `render()` que será llamado, usando `setView('otherView')`. -Los parámetros de la petición se pasan al método. Es posible y recomendable especificar tipos para los parámetros, por ejemplo `actionShow(int $id, string $slug = null)` - si el parámetro `id` falta o si no es un entero, el presentador devuelve [el error 404 |#Error 404 etc.] y termina la operación. +Los parámetros de la petición se pasan al método. Es posible y recomendable especificar tipos para los parámetros, por ejemplo `actionShow(int $id, ?string $slug = null)` - si el parámetro `id` falta o si no es un entero, el presentador devuelve [el error 404 |#Error 404 etc.] y termina la operación. `handle(args...)` .{toc: handle()} @@ -205,7 +205,7 @@ En la plantilla, estos mensajes están disponibles en la variable `$flashes` com Error 404 etc. .[#toc-error-404-etc] ==================================== -Cuando no podamos satisfacer la petición porque por ejemplo el artículo que queremos mostrar no existe en la base de datos, lanzaremos el error 404 utilizando el método `error(string $message = null, int $httpCode = 404)`, que representa el error HTTP 404: +Cuando no podamos satisfacer la petición porque por ejemplo el artículo que queremos mostrar no existe en la base de datos, lanzaremos el error 404 utilizando el método `error(?string $message = null, int $httpCode = 404)`, que representa el error HTTP 404: ```php public function renderShow(int $id): void @@ -384,7 +384,7 @@ La redirección no se produce con una solicitud AJAX o POST porque provocaría u También puede invocar la canonización manualmente mediante el método `canonicalize()`, que, al igual que el método `link()`, recibe el presentador, las acciones y los parámetros como argumentos. Crea un enlace y lo compara con la URL actual. Si es diferente, redirige al enlace generado. ```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 @@ -452,17 +452,6 @@ Restricción de acceso mediante `#[Requires]` .[#toc-access-restriction-using-re El atributo `#[Requires]` ofrece opciones avanzadas para restringir el acceso a los presentadores y sus métodos. Puede utilizarse para especificar métodos HTTP, requerir solicitudes AJAX, limitar el acceso al mismo origen y restringir el acceso sólo al reenvío. El atributo puede aplicarse a clases de presentadores, así como a métodos individuales como `action()`, `render()`, `handle()`y `createComponent()`. -He aquí un ejemplo de su uso para restringir el acceso únicamente al método HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Puede especificar estas restricciones - en los métodos HTTP: `#[Requires(methods: ['GET', 'POST'])]` - que requieren una petición AJAX: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ Puede especificar estas restricciones - acceso sólo mediante reenvío: `#[Requires(forward: true)]` - restricciones sobre acciones específicas: `#[Requires(actions: 'default')]` -Las condiciones pueden combinarse enumerando varios atributos o uniéndolos en uno solo: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Para obtener más información, consulte [Cómo utilizar el atributo Requires atributo |best-practices:attribute-requires]. Comprobación del método HTTP .[#toc-http-method-check] diff --git a/application/es/routing.texy b/application/es/routing.texy index c6ed0f3950..417438e650 100644 --- a/application/es/routing.texy +++ b/application/es/routing.texy @@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +225,7 @@ $router->addRoute('/[/]', [ ]); ``` -O podemos utilizar esta forma, observe la reescritura de la expresión regular de validación: +Para una especificación más detallada, se puede utilizar una forma aún más extendida, en la que además de los valores por defecto, se pueden establecer otras propiedades de los parámetros, como una expresión regular de validación (véase el parámetro `id` ): ```php use Nette\Routing\Route; @@ -243,7 +243,7 @@ $router->addRoute('/[/]', [ ]); ``` -Estos formatos más locuaces son útiles para añadir otros metadatos. +Es importante señalar que si los parámetros definidos en la matriz no se incluyen en la máscara de ruta, sus valores no podrán modificarse, ni siquiera utilizando parámetros de consulta especificados tras un signo de interrogación en la URL. Filtros y traducciones .[#toc-filters-and-translations] @@ -477,10 +477,10 @@ $router->addRoute('index', /* ... */); Integración .[#toc-integration] =============================== -Para conectar nuestro router a la aplicación, debemos informar al contenedor DI sobre él. La forma más sencilla es preparar la fábrica que construirá el objeto router y decirle a la configuración del contenedor que lo utilice. Digamos que escribimos un método para este propósito `App\Router\RouterFactory::createRouter()`: +Para conectar nuestro router a la aplicación, debemos informar al contenedor DI sobre él. La forma más sencilla es preparar la fábrica que construirá el objeto router y decirle a la configuración del contenedor que lo utilice. Digamos que escribimos un método para este propósito `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -499,7 +499,7 @@ Luego escribimos en [configuración |dependency-injection:services]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Cualquier dependencia, como una conexión de base de datos, etc., se pasa al método de fábrica como sus parámetros utilizando [autowiring |dependency-injection:autowiring]: @@ -663,7 +663,7 @@ Por uso separado, nos referimos al uso de las capacidades del router en una apli Así que de nuevo crearemos un método que construirá un enrutador, por ejemplo ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); O crearemos los objetos directamente: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/es/templates.texy b/application/es/templates.texy index 761f7a648e..af06db6505 100644 --- a/application/es/templates.texy +++ b/application/es/templates.texy @@ -34,35 +34,81 @@ Y esta podría ser la plantilla de acción: Define el bloque `content`, que se inserta en lugar de `{include content}` en el diseño, y también redefine el bloque `title`, que sobrescribe `{block title}` en el diseño. Intenta imaginar el resultado. -Búsqueda de plantillas .[#toc-search-for-templates] ---------------------------------------------------- +Búsqueda de plantillas .[#toc-template-lookup] +---------------------------------------------- -La ruta a las plantillas se deduce según una lógica simple. Se intenta ver si uno de estos archivos de plantilla existe en relación con el directorio donde se encuentra la clase de presentador, donde `` es el nombre del presentador actual y `` es el nombre de la acción actual: +En los presentadores, no es necesario especificar qué plantilla debe renderizarse; el framework determinará automáticamente la ruta, facilitándole la codificación. -- `templates//.latte` -- `templates/..latte` +Si utiliza una estructura de directorios donde cada presentador tiene su propio directorio, simplemente coloque la plantilla en este directorio bajo el nombre de la acción (es decir, vista). Por ejemplo, para la acción `default`, utilice la plantilla `default.latte`: -Si no se encuentra la plantilla, se intentará buscar en el directorio `templates` un nivel más arriba, es decir, al mismo nivel que el directorio con la clase presentadora. +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Si la plantilla tampoco se encuentra allí, la respuesta es un [error 404 |presenters#Error 404 etc.]. +Si utiliza una estructura en la que los presentadores están juntos en un directorio y las plantillas en una carpeta `templates`, guárdela en un archivo `..latte` o en `/.latte`: -También puede cambiar la vista utilizando `$this->setView('otherView')`. O, en lugar de buscar, especifique directamente el nombre del archivo de plantilla utilizando `$this->template->setFile('/path/to/template.latte')`. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- + +El directorio `templates` también puede colocarse un nivel más arriba, al mismo nivel que el directorio con las clases de presentador. + +Si no se encuentra la plantilla, el presentador responde con el [error 404 - página no encontrada |presenters#Error 404 etc]. + +Puede cambiar la vista utilizando `$this->setView('anotherView')`. También es posible especificar directamente el archivo de plantilla con `$this->template->setFile('/path/to/template.latte')`. .[note] -Puede cambiar las rutas donde se buscan las plantillas anulando el método [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], que devuelve una matriz de posibles rutas de archivo. +Los archivos en los que se buscan las plantillas pueden cambiarse anulando el método [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], que devuelve una matriz de posibles nombres de archivo. + + +Búsqueda de plantillas de diseño .[#toc-layout-template-lookup] +--------------------------------------------------------------- + +Nette también busca automáticamente el archivo de diseño. + +Si utiliza una estructura de directorios en la que cada presentador tiene su propio directorio, coloque la maqueta en la carpeta con el presentador, si es específica sólo para él, o en un nivel superior si es común a varios presentadores: + +/--pre +app/ +└── UI/ + ├── @layout.latte ← common layout + └── Home/ + ├── @layout.latte ← only for Home presenter + ├── HomePresenter.php + └── default.latte +\-- + +Si utiliza una estructura en la que los presentadores están agrupados en un directorio y las plantillas se encuentran en una carpeta `templates`, la maquetación se esperará en los siguientes lugares: -El diseño se espera en los siguientes archivos: +/--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` diseño común a varios presentadores +Si el presentador está en un [módulo |modules], también buscará más arriba en el árbol de directorios según el anidamiento del módulo. -`` es el nombre del presentador actual y `` es el nombre de la maquetación, que por defecto es `'layout'`. El nombre puede cambiarse con `$this->setLayout('otherLayout')`, de modo que se intentarán los archivos `@otherLayout.latte`. +El nombre de la presentación puede cambiarse utilizando `$this->setLayout('layoutAdmin')` y entonces se esperará en el archivo `@layoutAdmin.latte`. También puede especificar directamente el archivo de plantilla de presentación utilizando `$this->setLayout('/path/to/template.latte')`. -También puede especificar directamente el nombre de archivo de la plantilla de maquetación con `$this->setLayout('/path/to/template.latte')`. El uso de `$this->setLayout(false)` desactivará la búsqueda de diseños. +El uso de `$this->setLayout(false)` o de la etiqueta `{layout none}` dentro de la plantilla desactiva la búsqueda de diseños. .[note] -Puede cambiar las rutas donde se buscan las plantillas anulando el método [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], que devuelve una matriz de posibles rutas de archivo. +Los archivos en los que se buscan las plantillas de diseño pueden modificarse modificando el método [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], que devuelve una matriz de posibles nombres de archivo. Variables en la plantilla .[#toc-variables-in-the-template] @@ -104,7 +150,7 @@ La anotación `@property-read` es para IDE y análisis estático, hará que func Puedes permitirte el lujo de susurrar en las plantillas también, simplemente instala el plugin Latte en PhpStorm y especifica el nombre de la clase al principio de la plantilla, ver el artículo "Latte: cómo escribir sistema:https://blog.nette.org/es/latte-como-utilizar-el-sistema-de-tipos": ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void La versión 3 de Latte ofrece una forma más avanzada creando una [extensión |latte:creating-extension] para cada proyecto web. He aquí un ejemplo aproximado de una clase de este tipo: ```php -namespace App\Templating; +namespace App\UI\Accessory; final class LatteExtension extends Latte\Extension { @@ -214,7 +260,7 @@ La registramos usando [configuration|configuration#Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\UI\Accessory\LatteExtension ``` @@ -239,7 +285,7 @@ Alternativamente, el traductor se puede establecer utilizando la [configuración ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` El traductor puede utilizarse, por ejemplo, como un filtro `|translate`, con parámetros adicionales pasados al método `translate()` (véase `foo, bar`): diff --git a/application/fr/ajax.texy b/application/fr/ajax.texy index 6ef5b2bf32..d93abac2f4 100644 --- a/application/fr/ajax.texy +++ b/application/fr/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Vous devez d'abord [initialiser |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] la bibliothèque : + +```js +naja.initialize(); +``` + Pour faire d'un lien ordinaire (signal) ou d'une soumission de formulaire une requête AJAX, il suffit de marquer le lien, le formulaire ou le bouton correspondant avec la classe `ajax`: ```html diff --git a/application/fr/bootstrap.texy b/application/fr/bootstrap.texy index 3b80608880..32c12e829b 100644 --- a/application/fr/bootstrap.texy +++ b/application/fr/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__); + // Le configurateur est chargé de configurer l'environnement et les services de l'application. + $this->configurator = new Configurator; + // Définir le répertoire pour les fichiers temporaires générés par Nette (par exemple, les modèles compilés) + $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 est intelligent, et le mode développement est activé automatiquement, + // ou vous pouvez l'activer pour une adresse IP spécifique en décommentant la ligne suivante: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Active Tracy: l'ultime outil de débogage "couteau suisse". + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: charge automatiquement toutes les classes dans le répertoire donné + $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; + } + + private function setupContainer(): void + { + // Chargement des fichiers de configuration + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } ``` @@ -40,16 +66,15 @@ class Bootstrap index.php .[#toc-index-php] =========================== -Dans le cas des applications web, le fichier initial est `index.php`, qui se trouve dans le répertoire public `www/`. Il laisse la classe `Bootstrap` pour initialiser l'environnement et retourne la classe `$configurator` qui crée le conteneur DI. Ensuite, il obtient le service `Application`, qui exécute l'application web : +Le fichier initial des applications web est `index.php`, situé dans le répertoire public `www/`. Il utilise la classe `Bootstrap` pour initialiser l'environnement et créer un conteneur DI. Ensuite, il obtient le service `Application` du conteneur, ce qui lance l'application web : ```php -// initialisation de l'environnement + obtention de l'objet Configurateur -$configurator = App\Bootstrap::boot(); -// créer un conteneur DI -$container = $configurator->createContainer(); +$bootstrap = new App\Bootstrap; +// Initialiser l'environnement + créer un conteneur DI +$container = $bootstrap->bootWebApplication(); // Le conteneur DI crée un objet Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// Démarrage de l'application Nette +// Démarrer l'application Nette et traiter la demande entrante $application->run(); ``` @@ -66,19 +91,19 @@ La sélection du mode se fait par autodétection, il n'est donc généralement p Si vous souhaitez activer le mode développement dans d'autres cas, par exemple pour les programmeurs accédant depuis une adresse IP spécifique, vous pouvez utiliser `setDebugMode()`: ```php -$configurator->setDebugMode('23.75.345.200'); // une ou plusieurs adresses IP +$this->configurator->setDebugMode('23.75.345.200'); // une ou plusieurs adresses IP ``` Nous recommandons vivement de combiner une adresse IP avec un cookie. Nous stockerons un jeton secret dans le cookie `nette-debug`, par exemple `secret1234`, et le mode de développement sera activé pour les programmeurs avec cette combinaison d'IP et de cookie. ```php -$configurator->setDebugMode('secret1234@23.75.345.200'); +$this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` Nous pouvons également désactiver complètement le mode de développement, même pour localhost : ```php -$configurator->setDebugMode(false); +$this->configurator->setDebugMode(false); ``` Notez que la valeur `true` active le mode développeur par défaut, ce qui ne devrait jamais arriver sur un serveur de production. @@ -90,7 +115,7 @@ Outil de débogage Tracy .[#toc-debugging-tool-tracy] Pour faciliter le débogage, nous allons activer l'excellent outil [Tracy |tracy:]. En mode développeur, il visualise les erreurs et en mode production, il enregistre les erreurs dans le répertoire spécifié : ```php -$configurator->enableTracy($appDir . '/log'); +$this->configurator->enableTracy($this->rootDir . '/log'); ``` @@ -100,7 +125,7 @@ Fichiers temporaires .[#toc-temporary-files] Nette utilise le cache pour le conteneur DI, RobotLoader, les modèles, etc. Il est donc nécessaire de définir le chemin d'accès au répertoire où le cache sera stocké : ```php -$configurator->setTempDirectory($appDir . '/temp'); +$this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` Sous Linux ou macOS, définissez les [droits d'écriture |nette:troubleshooting#Setting directory permissions] pour les répertoires `log/` et `temp/`. @@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader] En général, nous voulons charger automatiquement les classes à l'aide de [RobotLoader |robot-loader:], nous devons donc le lancer et le laisser charger les classes du répertoire où se trouve `Bootstrap.php` (c'est-à-dire `__DIR__`) et de tous ses sous-répertoires : ```php -$configurator->createRobotLoader() +$this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); ``` @@ -126,7 +151,7 @@ Fuseau horaire .[#toc-timezone] Le configurateur vous permet de spécifier un fuseau horaire pour votre application. ```php -$configurator->setTimeZone('Europe/Prague'); +$this->configurator->setTimeZone('Europe/Prague'); ``` @@ -143,16 +168,17 @@ En mode développement, le conteneur est automatiquement mis à jour chaque fois Les fichiers de configuration sont chargés à l'aide de `addConfig()`: ```php -$configurator->addConfig($appDir . '/config/common.neon'); +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` La méthode `addConfig()` peut être appelée plusieurs fois pour ajouter plusieurs fichiers. ```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/services.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 @@ Paramètres statiques .[#toc-static-parameters] Les paramètres utilisés dans les fichiers de configuration peuvent être définis [dans la section `parameters` |dependency-injection:configuration#parameters] et également transmis (ou écrasés) par la méthode `addStaticParameters()` (qui a un alias `addParameters()`). Il est important que les différentes valeurs des paramètres entraînent la génération de conteneurs DI supplémentaires, c'est-à-dire de classes supplémentaires. ```php -$configurator->addStaticParameters([ +$this->configurator->addStaticParameters([ 'projectId' => 23, ]); ``` @@ -183,7 +209,7 @@ Paramètres dynamiques .[#toc-dynamic-parameters] Nous pouvons également ajouter des paramètres dynamiques au conteneur, leurs différentes valeurs, contrairement aux paramètres statiques, ne provoqueront pas la génération de nouveaux conteneurs DI. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'remoteIp' => $_SERVER['REMOTE_ADDR'], ]); ``` @@ -191,7 +217,7 @@ $configurator->addDynamicParameters([ Les variables d'environnement peuvent être facilement mises à disposition à l'aide de paramètres dynamiques. Nous pouvons y accéder via `%env.variable%` dans les fichiers de configuration. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'env' => getenv(), ]); ``` @@ -206,6 +232,7 @@ Vous pouvez utiliser les paramètres statiques suivants dans les fichiers de con - `%wwwDir%` est le chemin absolu vers le répertoire contenant le fichier d'entrée `index.php` - `%tempDir%` est le chemin absolu vers le répertoire des fichiers temporaires - `%vendorDir%` est le chemin absolu vers le répertoire où Composer installe les bibliothèques +- `%rootDir%` est le chemin absolu vers le répertoire racine du projet - `%debugMode%` indique si l'application est en mode débogage - `%consoleMode%` indique si la demande provient de la ligne de commande @@ -225,7 +252,7 @@ services: Créez une nouvelle instance et insérez-la dans bootstrap : ```php -$configurator->addServices([ +$this->configurator->addServices([ 'myservice' => new App\Model\MyCustomService('foobar'), ]); ``` @@ -234,13 +261,21 @@ $configurator->addServices([ Différents environnements .[#toc-different-environments] ======================================================== -N'hésitez pas à personnaliser la classe `Bootstrap` en fonction de vos besoins. Vous pouvez ajouter des paramètres à la méthode `boot()` pour différencier les projets Web, ou ajouter d'autres méthodes, comme `bootForTests()`, qui initialise l'environnement pour les tests unitaires, `bootForCli()` pour les scripts appelés depuis la ligne de commande, etc. +N'hésitez pas à personnaliser la classe `Bootstrap` en fonction de vos besoins. Vous pouvez ajouter des paramètres à la méthode `bootWebApplication()` pour différencier les projets web. Vous pouvez également ajouter d'autres méthodes, telles que `bootTestEnvironment()` pour initialiser l'environnement des tests unitaires, `bootConsoleApplication()` pour les scripts appelés à partir de la ligne de commande, etc. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Initialisation du testeur Nette + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container { - $configurator = self::boot(); - Tester\Environment::setup(); // Initialisation du testeur de nappe - return $configurator; + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); } ``` diff --git a/application/fr/components.texy b/application/fr/components.texy index 0b261bac97..a9472f1af9 100644 --- a/application/fr/components.texy +++ b/application/fr/components.texy @@ -230,6 +230,28 @@ Dans le modèle, ces messages sont disponibles dans la variable `$flashes` sous ``` +Redirection après un signal .[#toc-redirection-after-a-signal] +============================================================== + +Le traitement d'un signal de composant est souvent suivi d'une redirection. Cette situation est similaire à celle des formulaires : après avoir soumis un formulaire, nous redirigeons également les données pour éviter qu'elles ne soient soumises à nouveau lorsque la page est rafraîchie dans le navigateur. + +```php +$this->redirect('this') // redirects to the current presenter and action +``` + +Étant donné qu'un composant est un élément réutilisable et qu'il ne doit généralement pas dépendre directement de présentateurs spécifiques, les méthodes `redirect()` et `link()` interprètent automatiquement le paramètre comme un signal de composant : + +```php +$this->redirect('click') // redirects to the 'click' signal of the same component +``` + +Si vous devez rediriger vers un autre présentateur ou une autre action, vous pouvez le faire par l'intermédiaire du présentateur : + +```php +$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action +``` + + Paramètres persistants .[#toc-persistent-parameters] ==================================================== diff --git a/application/fr/configuration.texy b/application/fr/configuration.texy index 4398c0e7fe..d180bce83a 100644 --- a/application/fr/configuration.texy +++ b/application/fr/configuration.texy @@ -90,10 +90,13 @@ latte: strictTypes: ... # (bool) vaut false par défaut # active le [mode strict de l'analyseur |latte:develop#strict mode] - strictParsing : ... # (bool) la valeur par défaut est false + strictParsing: ... # (bool) la valeur par défaut est false # permet de [vérifier le code généré |latte:develop#Checking Generated Code] - phpLinter : ... # (string) la valeur par défaut est null + phpLinter: ... # (string) la valeur par défaut est null + + # définit la locale + locale: cs_CZ # (string) la valeur par défaut est null # classe de $this->template templateClass: App\MyTemplateClass # Valeur par défaut: Nette\Bridges\ApplicationLatte\DefaultTemplate @@ -104,7 +107,7 @@ Si vous utilisez la version 3 de Latte, vous pouvez ajouter une nouvelle [extens ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/fr/how-it-works.texy b/application/fr/how-it-works.texy index 9c4358110a..57aeb786a4 100644 --- a/application/fr/how-it-works.texy +++ b/application/fr/how-it-works.texy @@ -22,13 +22,13 @@ La structure des répertoires ressemble à ceci : /--pre web-project/ ├── app/ ← répertoire avec application -│ ├── Presenters/ ← classes d'presenter -│ │ ├── HomePresenter.php ← Home classe des présentateurs -│ │ └── templates/ ← répertoire des modèles -│ │ ├── @layout.latte ← modèle de disposition partagée -│ │ └── Home/ ← Modèles pour le présentateur de la page d'accueil -│ │ └── default.latte ← modèle pour l'action `default` -│ ├── Router/ ← configuration des adresses URL +│ ├── Core/ ← basic necessary classes +│ │ └── RouterFactory.php ← configuration des adresses URL +│ ├── UI/ ← presenters, templates & co. +│ │ ├─── @layout.latte ← template of shared layout +│ │ └── Home/ ← Home presenter directory +│ │ ├── HomePresenter.php ← Classe Home Presenter +│ │ └── default.latte ← template for action default │ └── Bootstrap.php ← classe de démarrage Bootstrap ├── bin/ ← scripts pour la ligne de commande ├── config/ ← configuration files @@ -91,7 +91,7 @@ Les applications écrites dans Nette sont divisées en plusieurs présentateurs L'application commence par demander à ce qu'on appelle le routeur de décider lequel des présentateurs doit transmettre la demande actuelle pour traitement. Le routeur décide de la responsabilité qui lui incombe. Il examine l'URL d'entrée `https://example.com/product/123`, qui veut `show` un produit avec `id: 123` comme action. C'est une bonne habitude d'écrire une paire présentateur + action séparée par un deux-points comme `Product:show`. -Le routeur transforme donc l'URL en une paire `Presenter:action` + paramètres, dans notre cas `Product:show` + `id: 123`. Vous pouvez voir à quoi ressemble un routeur dans le fichier `app/Router/RouterFactory.php` et nous le décrirons en détail dans le chapitre [Routage |Routing]. +Le routeur transforme donc l'URL en une paire `Presenter:action` + paramètres, dans notre cas `Product:show` + `id: 123`. Vous pouvez voir à quoi ressemble un routeur dans le fichier `app/Core/RouterFactory.php` et nous le décrirons en détail dans le chapitre [Routage |Routing]. Continuons. L'application connaît déjà le nom du présentateur et peut continuer. En créant un objet `ProductPresenter`, qui est le code du présentateur `Product`. Plus précisément, elle demande au conteneur DI de créer le présentateur, car la production d'objets est son travail. @@ -121,12 +121,9 @@ Ainsi, la méthode `renderShow(123)` a été appelée, dont le code est un exemp Ensuite, le présentateur renvoie la réponse. Cela peut être une page HTML, une image, un document XML, l'envoi d'un fichier depuis le disque, JSON ou la redirection vers une autre page. Il est important de noter que si nous ne disons pas explicitement comment répondre (ce qui est le cas de `ProductPresenter`), la réponse sera de rendre le modèle avec une page HTML. Pourquoi ? Eh bien, parce que dans 99% des cas, nous voulons dessiner un modèle, donc le présentateur prend ce comportement par défaut et veut nous faciliter le travail. C'est le point de vue de Nette. -Nous n'avons même pas besoin d'indiquer quel modèle dessiner, il dérive le chemin vers celui-ci selon une logique simple. Dans le cas du présentateur `Product` et de l'action `show`, il essaie de voir si l'un de ces fichiers modèles existe par rapport au répertoire où se trouve la classe `ProductPresenter`: +Il n'est même pas nécessaire de spécifier le modèle à rendre ; le framework déduira lui-même le chemin d'accès. Dans le cas de l'action `show`, il essaie simplement de charger le modèle `show.latte` dans le répertoire contenant la classe `ProductPresenter`. Il tente également de trouver la mise en page dans le fichier `@layout.latte` (plus d'informations sur la [recherche de modèles |templates#Template Lookup]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Il essaiera également de trouver la mise en page dans le fichier `@layout.latte`, puis il effectuera le rendu du modèle. La tâche du présentateur et de l'ensemble de l'application est maintenant terminée. Si le modèle n'existe pas, une page d'erreur 404 sera renvoyée. Vous pouvez en savoir plus sur les présentateurs sur la page [Présentateurs |Presenters]. +Ensuite, les modèles sont rendus. La tâche du présentateur et de l'ensemble de l'application est ainsi achevée et le travail est terminé. Si le modèle n'existait pas, une page d'erreur 404 serait renvoyée. Pour en savoir plus sur les présentateurs, consultez la page [Présentateurs |presenters]. [* request-flow.svg *] @@ -137,7 +134,7 @@ Juste pour être sûr, essayons de récapituler l'ensemble du processus avec une 3) le routeur décode l'URL comme une paire `Home:default` 4) un objet `HomePresenter` est créé 5) la méthode `renderDefault()` est appelée (si elle existe) -6) un modèle `templates/Home/default.latte` avec une mise en page `templates/@layout.latte` est rendu +6) un modèle `default.latte` avec une mise en page `@layout.latte` est rendu Vous avez peut-être rencontré beaucoup de nouveaux concepts maintenant, mais nous pensons qu'ils ont un sens. Créer des applications dans Nette est un jeu d'enfant. diff --git a/application/fr/modules.texy b/application/fr/modules.texy index 754e97b5ad..7734341840 100644 --- a/application/fr/modules.texy +++ b/application/fr/modules.texy @@ -2,29 +2,31 @@ Modules ******* .[perex] -Dans Nette, les modules représentent les unités logiques qui composent une application. Ils comprennent des présentateurs, des modèles, éventuellement aussi des composants et des classes de modèles. +Les modules apportent de la clarté aux applications Nette en facilitant la division en unités logiques. -Un répertoire pour les présentateurs et un autre pour les modèles ne seraient pas suffisants pour les projets réels. Avoir des dizaines de fichiers dans un seul dossier est pour le moins inorganisé. Comment s'en sortir ? Il suffit de les répartir en sous-répertoires sur le disque et en espaces de noms dans le code. Et c'est exactement ce que font les modules Nette. - -Oublions donc le dossier unique pour les présentateurs et les modèles et créons plutôt des modules, par exemple `Admin` et `Front`. +À l'instar de l'organisation des fichiers en dossiers sur un disque dur, Nette permet de diviser les présentateurs, les modèles et les autres classes auxiliaires en modules. Comment cela fonctionne-t-il en pratique ? Simplement en incorporant de nouveaux sous-répertoires dans la structure. Voici un exemple de structure avec deux modules, Front et Admin : /--pre -app/ -├── Presenters/ -├── Modules/ ← répertoire avec les modules -│ ├── Admin/ ← module Admin -│ │ ├── Presenters/ ← ses présentateurs -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← module Front -│ └── Presenters/ ← ses présentateurs -│ └── ... +app/ +├── UI/ +│ ├── Admin/ ← Admin module +│ │ ├── @layout.latte +│ │ ├── Dashboard/ +│ │ │ ├── DashboardPresenter.php +│ │ │ └── default.latte +│ │ └── ... +│ ├── Front/ ← Front module +│ │ ├── @layout.latte +│ │ ├── Home/ +│ │ │ ├── HomePresenter.php +│ │ │ └── default.latte +│ │ └── ... \-- -Cette structure de répertoire sera reflétée par les espaces de noms des classes, ainsi par exemple `DashboardPresenter` sera dans l'espace de noms `App\Modules\Admin\Presenters`: +Cette structure de répertoires se reflète dans les espaces de noms des classes. Ainsi, par exemple, `DashboardPresenter` est situé dans l'espace de noms `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 } ``` -Le présentateur `Dashboard` dans le module `Admin` est référencé dans l'application en utilisant la notation deux points comme `Admin:Dashboard`, et son action `default` comme `Admin:Dashboard:default`. -Et comment Nette proper sait-elle que `Admin:Dashboard` représente la classe `App\Modules\Admin\Presenters\DashboardPresenter`? Cela est déterminé par le [mappage |#mapping] dans la configuration. -Ainsi, la structure donnée n'est pas figée et vous pouvez la modifier en fonction de vos besoins. +Dans l'application, nous faisons référence au présentateur `Dashboard` dans le module `Admin` en utilisant la notation des deux points comme `Admin:Dashboard`. Pour son action `default`, nous l'appelons `Admin:Dashboard:default`. -Les modules peuvent bien sûr contenir tous les éléments autres que les présentateurs et les modèles, tels que les composants, les classes de modèles, etc. +La structure présentée n'est pas rigide ; vous pouvez [l'adapter entièrement à vos besoins |#mapping] dans la configuration. .[tip] + +Les modules peuvent inclure tous les autres fichiers, tels que les composants et les classes auxiliaires, en plus des présentateurs et des modèles. Si vous vous demandez où placer ces derniers, envisagez d'utiliser un dossier `Accessory`: + +/--pre +app/ +├── UI/ +│ ├── Admin/ +│ │ ├── Accessory/ +│ │ │ ├── FormFactory.php +│ │ │ └── AdminLayout.php +│ │ ├── Dashboard/ +│ │ └── ... +\-- Modules imbriqués .[#toc-nested-modules] ---------------------------------------- -Les modules ne doivent pas uniquement former une structure plate, vous pouvez également créer des sous-modules, par exemple : +Les modules peuvent avoir plusieurs niveaux d'imbrication, comme la structure d'un répertoire sur un disque : /--pre -app/ -├── Modules/ ← répertoire avec les modules -│ ├── Blog/ ← module Blog -│ │ ├── Admin/ ← sous-module Admin -│ │ │ ├── Presenters/ +app/ +├── UI/ +│ ├── Blog/ ← Blog module +│ │ ├── Admin/ ← Admin submodule +│ │ │ ├── Dashboard/ +│ │ │ └── ... +│ │ ├── Front/ ← Front submodule +│ │ │ ├── @layout.latte +│ │ │ ├── Home/ │ │ │ └── ... -│ │ └── Front/ ← sous-module Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← module Forum +│ ├── Forum/ ← Forum module │ │ └── ... \-- -Ainsi, le module `Blog` est divisé en sous-modules `Admin` et `Front`. Là encore, cela se reflétera dans les espaces de noms, qui seront `App\Modules\Blog\Admin\Presenters` etc. Le présentateur `Dashboard` à l'intérieur du sous-module est désigné par `Blog:Admin:Dashboard`. +Le module `Blog` est divisé en sous-modules `Admin` et `Front`. Cela se reflète également dans les espaces de noms, qui apparaissent alors comme `App\UI\Blog\Admin` et similaires. Pour désigner le présentateur `Dashboard` au sein du sous-module `Admin`, nous l'appelons `Blog:Admin:Dashboard`. -L'imbrication peut aller aussi loin que vous le souhaitez, de sorte que des sous-sous-modules peuvent être créés. +L'imbrication peut être aussi poussée que nécessaire, ce qui permet de créer des sous-sous-modules. + +Par exemple, si dans l'administration vous avez de nombreux présentateurs liés à la gestion des commandes, tels que `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., vous pouvez créer un module `Order` dans lequel les présentateurs tels que `Detail`, `Edit`, `Dispatch`, et d'autres seront organisés. Création de liens .[#toc-creating-links] @@ -102,46 +118,66 @@ Voir le [chapitre sur le routage |routing#Modules]. Cartographie .[#toc-mapping] ---------------------------- -Définit les règles par lesquelles le nom de la classe est dérivé du nom du présentateur. On les inscrit dans la [configuration] sous la clé `application › mapping`. +Le mappage définit les règles permettant de dériver le nom de la classe à partir du nom du présentateur. Ces règles sont spécifiées dans la [configuration |configuration] sous la clé `application › mapping`. + +Les structures de répertoire mentionnées plus haut sur cette page sont basées sur la correspondance suivante : + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Commençons par un exemple qui n'utilise pas de modules. Nous voulons simplement que les classes du présentateur aient l'espace de nom `App\Presenters`. Cela signifie qu'un présentateur tel que `Home` doit correspondre à la classe `App\Presenters\HomePresenter`. Ceci peut être réalisé par la configuration suivante : +Comment fonctionne la cartographie ? Pour mieux comprendre, imaginons d'abord une application sans modules. Nous voulons que les classes de présentateurs relèvent de l'espace de noms `App\UI`, de sorte que le présentateur `Home` soit associé à la classe `App\UI\HomePresenter`. Cette configuration permet d'atteindre cet objectif : ```neon application: - mapping: App\Presenters\*Presenter + mapping: App\UI\*Presenter ``` -Le nom du présentateur est remplacé par l'astérisque dans le masque de classe et le résultat est le nom de la classe. Facile ! +Ce mappage fonctionne en remplaçant l'astérisque du masque `App\UI\*Presenter` par le nom du présentateur `Home`, ce qui donne le nom de classe final `App\UI\HomePresenter`. C'est simple ! + +Cependant, comme vous pouvez le voir dans les exemples de ce chapitre et d'autres chapitres, nous plaçons les classes de présentateurs dans des sous-répertoires éponymes, par exemple, le présentateur `Home` est associé à la classe `App\UI\Home\HomePresenter`. Pour ce faire, il suffit de doubler l'astérisque (Nette Application 3.2 requise) : + +```neon +application: + mapping: App\UI\**Presenter +``` -Si nous divisons les présentateurs en modules, nous pouvons avoir notre propre mappage pour chaque module : +Passons maintenant au mappage des présentateurs dans les modules. Nous pouvons définir des correspondances spécifiques pour chaque 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 ``` -Maintenant, le présentateur `Front:Home` correspond à la classe `App\Modules\Front\Presenters\HomePresenter` et le présentateur `Admin:Dashboard` à la classe `App\Modules\Admin\Presenters\DashboardPresenter`. +Selon cette configuration, le présentateur `Front:Home` correspond à la classe `App\UI\Front\Home\HomePresenter`, tandis que le présentateur `Api:OAuth` correspond à la classe `App\Api\OAuthPresenter`. -Il est plus pratique de créer une règle générale (étoile) pour remplacer les deux premières. L'astérisque supplémentaire sera ajouté au masque de classe uniquement pour le module : +Étant donné que les modules `Front` et `Admin` ont une approche de mappage similaire et qu'il est probable qu'il y ait d'autres modules de ce type, il est possible de créer une règle générale qui les remplace. Un nouvel astérisque pour le module est ajouté au masque de classe : ```neon application: mapping: - *: App\Modules\*\Presenters\*Presenter + *: App\UI\*\**Presenter Api: App\Api\*Presenter ``` -Mais qu'en est-il si nous utilisons des modules imbriqués et que nous avons un présentateur `Admin:User:Edit`? Dans ce cas, le segment avec un astérisque représentant le module pour chaque niveau est simplement répété et le résultat est la classe `App\Modules\Admin\User\Presenters\EditPresenter`. +Pour les modules imbriqués à plusieurs niveaux, tels que le présentateur `Admin:User:Edit`, le segment astérisque se répète pour chaque niveau, ce qui donne la classe `App\UI\Admin\User\Edit\EditPresenter`. -Une notation alternative consiste à utiliser un tableau composé de trois segments au lieu d'une chaîne de caractères. Cette notation est équivalente à la précédente : +Une autre notation consiste à utiliser un tableau composé de trois segments au lieu d'une chaîne. Cette notation est équivalente à la précédente : ```neon application: mapping: - *: [App\Modules, *, Presenters\*Presenter] + *: [App\UI, *, **Presenter] + Api: [App\Api, '', *Presenter] ``` -La valeur par défaut est `*Module\*Presenter`. +Si nous n'avons qu'une seule règle dans la configuration, la règle générale, nous pouvons l'écrire brièvement : + +```neon +application: + mapping: App\UI\*\**Presenter +``` diff --git a/application/fr/presenters.texy b/application/fr/presenters.texy index bf1050cfa2..538f20ab44 100644 --- a/application/fr/presenters.texy +++ b/application/fr/presenters.texy @@ -60,7 +60,7 @@ Similaire à la méthode `render()`. Alors que `render()` a pour but Il est important que `action()` soit appelé avant `render()`afin qu'à l'intérieur de celui-ci, nous puissions éventuellement modifier le cours suivant du cycle de vie, c'est-à-dire changer le modèle qui sera rendu et également la méthode `render()` qui sera appelée, en utilisant `setView('otherView')`. -Les paramètres de la requête sont transmis à la méthode. Il est possible et recommandé de spécifier des types pour les paramètres, par exemple `actionShow(int $id, string $slug = null)` - si le paramètre `id` est manquant ou s'il ne s'agit pas d'un nombre entier, le présentateur renvoie l'[erreur 404 |#Error 404 etc.] et met fin à l'opération. +Les paramètres de la requête sont transmis à la méthode. Il est possible et recommandé de spécifier des types pour les paramètres, par exemple `actionShow(int $id, ?string $slug = null)` - si le paramètre `id` est manquant ou s'il ne s'agit pas d'un nombre entier, le présentateur renvoie l'[erreur 404 |#Error 404 etc.] et met fin à l'opération. `handle(args...)` .{toc: handle()} @@ -205,7 +205,7 @@ Dans le modèle, ces messages sont disponibles dans la variable `$flashes` en ta Erreur 404 etc. .[#toc-error-404-etc] ===================================== -Lorsque nous ne pouvons pas répondre à la demande, par exemple parce que l'article que nous voulons afficher n'existe pas dans la base de données, nous envoyons l'erreur 404 en utilisant la méthode `error(string $message = null, int $httpCode = 404)`, qui représente l'erreur HTTP 404 : +Lorsque nous ne pouvons pas répondre à la demande, par exemple parce que l'article que nous voulons afficher n'existe pas dans la base de données, nous envoyons l'erreur 404 en utilisant la méthode `error(?string $message = null, int $httpCode = 404)`, qui représente l'erreur HTTP 404 : ```php public function renderShow(int $id): void @@ -384,7 +384,7 @@ La redirection ne se produit pas avec une demande AJAX ou POST, car elle entraî Vous pouvez également invoquer la canonisation manuellement à l'aide de la méthode `canonicalize()`, qui, comme la méthode `link()`, reçoit le présentateur, les actions et les paramètres comme arguments. Elle crée un lien et le compare à l'URL actuelle. Si elle est différente, elle redirige vers le lien généré. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // redirige si $slug est différent de $realSlug @@ -452,17 +452,6 @@ Restriction d'accès à l'aide de `#[Requires]` .[#toc-access-restriction-using- L'attribut `#[Requires]` fournit des options avancées pour restreindre l'accès aux présentateurs et à leurs méthodes. Il peut être utilisé pour spécifier des méthodes HTTP, exiger des requêtes AJAX, limiter l'accès à la même origine et restreindre l'accès à la transmission uniquement. L'attribut peut être appliqué aux classes de présentateurs ainsi qu'aux méthodes individuelles telles que `action()`, `render()`, `handle()`, et `createComponent()`. -Voici un exemple d'utilisation pour restreindre l'accès à la seule méthode HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Vous pouvez spécifier ces restrictions : - sur les méthodes HTTP : `#[Requires(methods: ['GET', 'POST'])]` - nécessitant une requête AJAX : `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ Vous pouvez spécifier ces restrictions : - accès uniquement par le biais d'une redirection : `#[Requires(forward: true)]` - restrictions sur des actions spécifiques : `#[Requires(actions: 'default')]` -Les conditions peuvent être combinées en énumérant plusieurs attributs ou en les réunissant en un seul : - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Pour plus de détails, voir [Comment utiliser l'attribut Requires |best-practices:attribute-requires]. Vérification de la méthode HTTP .[#toc-http-method-check] diff --git a/application/fr/routing.texy b/application/fr/routing.texy index 9fe5d5e4a6..29541d7fb9 100644 --- a/application/fr/routing.texy +++ b/application/fr/routing.texy @@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +225,7 @@ $router->addRoute('/[/]', [ ]); ``` -Ou nous pouvons utiliser cette forme, remarquez la réécriture de l'expression régulière de validation : +Pour une spécification plus détaillée, une forme encore plus étendue peut être utilisée, où en plus des valeurs par défaut, d'autres propriétés de paramètres peuvent être définies, telles qu'une expression régulière de validation (voir le paramètre `id` ) : ```php use Nette\Routing\Route; @@ -243,7 +243,7 @@ $router->addRoute('/[/]', [ ]); ``` -Ces formats plus bavards sont utiles pour ajouter d'autres métadonnées. +Il est important de noter que si les paramètres définis dans le tableau ne sont pas inclus dans le masque de chemin, leurs valeurs ne peuvent pas être modifiées, même en utilisant des paramètres de requête spécifiés après un point d'interrogation dans l'URL. Filtres et traductions .[#toc-filters-and-translations] @@ -477,10 +477,10 @@ $router->addRoute('index', /* ... */); Intégration .[#toc-integration] =============================== -Afin de connecter notre routeur à l'application, nous devons en informer le conteneur DI. Le moyen le plus simple est de préparer la fabrique qui construira l'objet routeur et de dire à la configuration du conteneur de l'utiliser. Disons que nous écrivons une méthode dans ce but `App\Router\RouterFactory::createRouter()`: +Afin de connecter notre routeur à l'application, nous devons en informer le conteneur DI. Le moyen le plus simple est de préparer la fabrique qui construira l'objet routeur et de dire à la configuration du conteneur de l'utiliser. Disons que nous écrivons une méthode dans ce but `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -499,7 +499,7 @@ Puis nous écrivons dans la [configuration |dependency-injection:services]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Toutes les dépendances, telles qu'une connexion à une base de données, etc., sont transmises à la méthode factory en tant que paramètres en utilisant le [câblage automatique |dependency-injection:autowiring]: @@ -663,7 +663,7 @@ Par utilisation séparée, nous entendons l'utilisation des capacités du routeu Donc encore une fois nous allons créer une méthode qui va construire un routeur, par exemple : ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Ou bien nous créerons directement des objets : ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/fr/templates.texy b/application/fr/templates.texy index 8ecd733495..494ea2da09 100644 --- a/application/fr/templates.texy +++ b/application/fr/templates.texy @@ -34,35 +34,81 @@ Et ceci pourrait être le modèle d'action : Il définit le bloc `content`, qui est inséré à la place de `{include content}` dans la mise en page, et redéfinit également le bloc `title`, qui écrase `{block title}` dans la mise en page. Essayez d'imaginer le résultat. -Recherche de modèles .[#toc-search-for-templates] -------------------------------------------------- +Recherche de modèles .[#toc-template-lookup] +-------------------------------------------- -Le chemin vers les modèles est déduit selon une logique simple. Il essaie de voir si l'un de ces fichiers modèles existe par rapport au répertoire où se trouve la classe du présentateur, où `` est le nom du présentateur actuel et `` est le nom de l'action en cours : +Dans les présentateurs, vous n'avez pas besoin de spécifier quel modèle doit être rendu ; le cadre détermine automatiquement le chemin, ce qui facilite le codage. -- `templates//.latte` -- `templates/..latte` +Si vous utilisez une structure de répertoires dans laquelle chaque présentateur a son propre répertoire, placez simplement le modèle dans ce répertoire sous le nom de l'action (c'est-à-dire de la vue). Par exemple, pour l'action `default`, utilisez le modèle `default.latte`: -Si le modèle n'est pas trouvé, il essaiera de chercher dans le répertoire `templates` au niveau supérieur, c'est-à-dire au même niveau que le répertoire contenant la classe du présentateur. +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Si le modèle n'y est pas trouvé non plus, la réponse est une [erreur 404 |presenters#Error 404 etc.]. +Si vous utilisez une structure dans laquelle les présentateurs sont regroupés dans un répertoire et les modèles dans un dossier `templates`, enregistrez-les dans un fichier `..latte` soit dans un fichier `/.latte`: -Vous pouvez également changer la vue en utilisant `$this->setView('otherView')`. Ou, au lieu de chercher, spécifiez directement le nom du fichier de modèle en utilisant `$this->template->setFile('/path/to/template.latte')`. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- + +Le répertoire `templates` peut également être placé un niveau plus haut, au même niveau que le répertoire des classes de présentateurs. + +Si le modèle n'est pas trouvé, le présentateur répond par l'[erreur 404 - page non trouvée |presenters#Error 404 etc]. + +Vous pouvez changer la vue en utilisant `$this->setView('anotherView')`. Il est également possible de spécifier directement le fichier de modèle avec `$this->template->setFile('/path/to/template.latte')`. .[note] -Vous pouvez modifier les chemins dans lesquels les modèles sont recherchés en remplaçant la méthode [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], qui renvoie un tableau de chemins de fichiers possibles. +Les fichiers dans lesquels les modèles sont recherchés peuvent être modifiés en remplaçant la méthode [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], qui renvoie un tableau de noms de fichiers possibles. + + +Recherche de modèles de mise en page .[#toc-layout-template-lookup] +------------------------------------------------------------------- + +Nette recherche également automatiquement le fichier de mise en page. + +Si vous utilisez une structure de répertoires dans laquelle chaque présentateur a son propre répertoire, placez le modèle soit dans le dossier du présentateur, s'il lui est propre, soit à un niveau supérieur s'il est commun à plusieurs présentateurs : + +/--pre +app/ +└── UI/ + ├── @layout.latte ← common layout + └── Home/ + ├── @layout.latte ← only for Home presenter + ├── HomePresenter.php + └── default.latte +\-- + +Si vous utilisez une structure dans laquelle les présentateurs sont regroupés dans un répertoire et les modèles dans un dossier `templates`, la mise en page sera attendue aux endroits suivants : -Le modèle est attendu dans les fichiers suivants : +/--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` mise en page commune à plusieurs présentateurs +Si le présentateur se trouve dans un [module |modules], il cherchera également plus haut dans l'arborescence du répertoire en fonction de l'imbrication du module. -`` est le nom du présentateur actuel et `` est le nom de la mise en page, qui est par défaut `'layout'`. Le nom peut être modifié avec `$this->setLayout('otherLayout')`, de sorte que les fichiers `@otherLayout.latte` seront essayés. +Le nom de la présentation peut être modifié à l'aide de `$this->setLayout('layoutAdmin')` et sera alors attendu dans le fichier `@layoutAdmin.latte`. Vous pouvez également spécifier directement le fichier de modèle de présentation en utilisant `$this->setLayout('/path/to/template.latte')`. -Vous pouvez également spécifier directement le nom du fichier du modèle de présentation en utilisant `$this->setLayout('/path/to/template.latte')`. L'utilisation de `$this->setLayout(false)` désactivera la recherche de la mise en page. +L'utilisation de `$this->setLayout(false)` ou de la balise `{layout none}` à l'intérieur du modèle désactive la recherche de modèle. .[note] -Vous pouvez modifier les chemins dans lesquels les modèles sont recherchés en remplaçant la méthode [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], qui renvoie un tableau de chemins de fichiers possibles. +Les fichiers dans lesquels les modèles de présentation sont recherchés peuvent être modifiés en remplaçant la méthode [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], qui renvoie un tableau de noms de fichiers possibles. Variables dans le modèle .[#toc-variables-in-the-template] @@ -104,7 +150,7 @@ L'annotation `@property-read` est pour les IDE et l'analyse statique, elle fera Vous pouvez aussi vous offrir le luxe de chuchoter dans les templates, il suffit d'installer le plugin Latte dans PhpStorm et de spécifier le nom de la classe au début du template, voir l'article "Latte : how to type system":https://blog.nette.org/fr/latte-comment-utiliser-le-systeme-de-type: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void Latte version 3 propose un moyen plus avancé en créant une [extension |latte:creating-extension] pour chaque projet web. Voici un exemple approximatif d'une telle classe : ```php -namespace App\Templating; +namespace App\UI\Accessory; final class LatteExtension extends Latte\Extension { @@ -214,7 +260,7 @@ Nous l'enregistrons en utilisant la [configuration |configuration#Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\UI\Accessory\LatteExtension ``` @@ -239,7 +285,7 @@ Alternativement, le traducteur peut être défini à l'aide de la [configuration ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` Le traducteur peut alors être utilisé, par exemple, comme un filtre `|translate`, avec des paramètres supplémentaires transmis à la méthode `translate()` (voir `foo, bar`) : diff --git a/application/hu/ajax.texy b/application/hu/ajax.texy index 24ec5880e0..b2a6f90cc9 100644 --- a/application/hu/ajax.texy +++ b/application/hu/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Először [inicializálni |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] kell a könyvtárat: + +```js +naja.initialize(); +``` + Ahhoz, hogy egy közönséges linket (jelet) vagy űrlapküldést AJAX-kérelemmé tegyen, egyszerűen jelölje meg az adott linket, űrlapot vagy gombot a `ajax` osztállyal: ```html diff --git a/application/hu/bootstrap.texy b/application/hu/bootstrap.texy index 1ef07fd756..a05ab01083 100644 --- a/application/hu/bootstrap.texy +++ b/application/hu/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__); + // A konfigurátor felelős az alkalmazási környezet és a szolgáltatások beállításáért. + $this->configurator = new Configurator; + // A Nette által generált ideiglenes fájlok (pl. lefordított sablonok) könyvtárának beállítása. + $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 + { + // A Nette intelligens, és a fejlesztői üzemmód automatikusan bekapcsol, + // vagy engedélyezheti egy adott IP-címre a következő sor megjegyzésének feloldásával: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Bekapcsolja a Tracy-t: a végső "svájci bicska" hibakeresési eszköz. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: automatikusan feltölti az összes osztályt a megadott könyvtárban. + $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; + } + + private function setupContainer(): void + { + // Konfigurációs fájlok betöltése + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } ``` @@ -40,16 +66,15 @@ class Bootstrap index.php .[#toc-index-php] =========================== -A webes alkalmazások esetében a kezdő fájl a `index.php`, amely a `www/` nyilvános könyvtárban található. Ez lehetővé teszi a `Bootstrap` osztály számára, hogy inicializálja a környezetet, és visszaadja a `$configurator`, amely létrehozza a DI konténert. Ezután megszerzi a `Application` szolgáltatást, amely a webalkalmazást futtatja: +A webes alkalmazások kezdőfájlja a `index.php`, amely a `www/` nyilvános könyvtárban található. A `Bootstrap` osztályt használja a környezet inicializálásához és a DI konténer létrehozásához. Ezután megszerzi a `Application` szolgáltatást a konténerből, amely elindítja a webalkalmazást: ```php -// a környezet inicializálása + konfigurátor objektum kinyerése -$configurator = App\Bootstrap::boot(); -// DI konténer létrehozása -$container = $configurator->createContainer(); +$bootstrap = new App\Bootstrap; +// A környezet inicializálása + DI konténer létrehozása +$container = $bootstrap->bootWebApplication(); // A DI konténer létrehoz egy Nette\Application\Application objektumot. $application = $container->getByType(Nette\Application\Application::class); -// Nette alkalmazás indítása +// A Nette-alkalmazás elindítása és a bejövő kérések kezelése. $application->run(); ``` @@ -66,19 +91,19 @@ A mód kiválasztása automatikus felismeréssel történik, így általában ne Ha más esetekben, például egy adott IP-címről hozzáférő programozók számára szeretné engedélyezni a fejlesztési üzemmódot, akkor a `setDebugMode()` címet használhatja: ```php -$configurator->setDebugMode('23.75.345.200'); // egy vagy több IP-cím +$this->configurator->setDebugMode('23.75.345.200'); // egy vagy több IP-cím ``` Mindenképpen javasoljuk az IP-cím és a cookie kombinálását. A `nette-debug` cookie-ban tárolunk egy titkos tokent, pl. `secret1234`, és a fejlesztési mód az IP és a cookie ilyen kombinációjával rendelkező programozók számára aktiválódik. ```php -$configurator->setDebugMode('secret1234@23.75.345.200'); +$this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` A fejlesztői módot teljesen ki is kapcsolhatjuk, akár a localhost esetében is: ```php -$configurator->setDebugMode(false); +$this->configurator->setDebugMode(false); ``` A `true` érték keményen bekapcsolja a fejlesztői módot, ami soha nem történhet meg egy termelő szerveren. @@ -90,7 +115,7 @@ Hibakereső eszköz Tracy .[#toc-debugging-tool-tracy] Az egyszerű hibakeresés érdekében bekapcsoljuk a [Tracy |tracy:] nevű nagyszerű eszközt. Fejlesztői módban megjeleníti a hibákat, termelési módban pedig a megadott könyvtárba naplózza a hibákat: ```php -$configurator->enableTracy($appDir . '/log'); +$this->configurator->enableTracy($this->rootDir . '/log'); ``` @@ -100,7 +125,7 @@ Ideiglenes fájlok .[#toc-temporary-files] A Nette a DI konténer, a RobotLoader, a sablonok stb. számára használja a gyorsítótárat. Ezért szükséges annak a könyvtárnak az elérési útvonalát beállítani, ahol a gyorsítótár tárolásra kerül: ```php -$configurator->setTempDirectory($appDir . '/temp'); +$this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` Linuxon vagy macOS-en állítsa be a `log/` és a `temp/` könyvtárak [írási engedélyeit |nette:troubleshooting#Setting directory permissions]. @@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader] Általában a [RobotLoader |robot-loader:] segítségével szeretnénk automatikusan betölteni az osztályokat, ezért el kell indítanunk, és hagynunk kell, hogy betöltse az osztályokat abból a könyvtárból, ahol a `Bootstrap.php` található (azaz `__DIR__`) és annak összes alkönyvtárából: ```php -$configurator->createRobotLoader() +$this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); ``` @@ -126,7 +151,7 @@ Időzóna .[#toc-timezone] A Configurator lehetővé teszi, hogy megadjon egy időzónát az alkalmazásához. ```php -$configurator->setTimeZone('Europe/Prague'); +$this->configurator->setTimeZone('Europe/Prague'); ``` @@ -143,16 +168,17 @@ Fejlesztői üzemmódban a konténer automatikusan frissül minden alkalommal, a A konfigurációs fájlok betöltése a `addConfig()` segítségével történik: ```php -$configurator->addConfig($appDir . '/config/common.neon'); +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` A `addConfig()` metódus többször is meghívható több fájl hozzáadásához. ```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/services.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 @@ Statikus paraméterek .[#toc-static-parameters] A konfigurációs fájlokban használt paramétereket a [`parameters` szakaszban |dependency-injection:configuration#parameters] lehet definiálni, és a `addStaticParameters()` metódus (amelynek alias neve `addParameters()`) is átadhatja (vagy felülírhatja). Fontos, hogy a különböző paraméterértékek további DI-konténerek, azaz további osztályok generálását okozzák. ```php -$configurator->addStaticParameters([ +$this->configurator->addStaticParameters([ 'projectId' => 23, ]); ``` @@ -183,7 +209,7 @@ Dinamikus paraméterek .[#toc-dynamic-parameters] A konténerhez dinamikus paramétereket is hozzáadhatunk, ezek eltérő értékei a statikus paraméterekkel ellentétben nem okoznak új DI-konténerek generálását. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'remoteIp' => $_SERVER['REMOTE_ADDR'], ]); ``` @@ -191,7 +217,7 @@ $configurator->addDynamicParameters([ A környezeti változókat könnyen elérhetővé tehetnénk dinamikus paraméterek segítségével. A konfigurációs fájlokban a `%env.variable%` címen keresztül érhetjük el őket. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'env' => getenv(), ]); ``` @@ -206,6 +232,7 @@ A konfigurációs fájlokban a következő statikus paramétereket használhatja - `%wwwDir%` a `index.php` beviteli fájlt tartalmazó könyvtár abszolút elérési útja. - `%tempDir%` az ideiglenes fájlok könyvtárának abszolút elérési útja. - `%vendorDir%` az abszolút elérési út a könyvtárak Composer általi telepítésének könyvtárához. +- `%rootDir%` a projekt gyökérkönyvtárának abszolút elérési útvonala. - `%debugMode%` jelzi, hogy az alkalmazás hibakeresési módban van-e. - `%consoleMode%` jelzi, hogy a kérés a parancssoron keresztül érkezett-e. @@ -225,7 +252,7 @@ services: Hozzunk létre egy új példányt, és illesszük be a bootstrapbe: ```php -$configurator->addServices([ +$this->configurator->addServices([ 'myservice' => new App\Model\MyCustomService('foobar'), ]); ``` @@ -234,13 +261,21 @@ $configurator->addServices([ Különböző környezetek .[#toc-different-environments] ==================================================== -Nyugodtan testre szabhatja a `Bootstrap` osztályt, hogy megfeleljen az igényeinek. A `boot()` metódushoz paramétereket adhat a webes projektek megkülönböztetéséhez, vagy más metódusokat is hozzáadhat, például a `bootForTests()`, amely inicializálja a környezetet a unit tesztekhez, a `bootForCli()` a parancssorból hívott szkriptekhez, és így tovább. +Ne habozzon, ha a `Bootstrap` osztályt saját igényei szerint alakíthatja. A `bootWebApplication()` metódushoz paramétereket adhat hozzá a webes projektek megkülönböztetése érdekében. Alternatívaként más metódusokat is hozzáadhat, például a `bootTestEnvironment()` a környezet inicializálásához a unit tesztekhez, a `bootConsoleApplication()` a parancssorból hívott szkriptekhez stb. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container { - $configurator = self::boot(); Tester\Environment::setup(); // Nette Tester inicializálása - 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/hu/components.texy b/application/hu/components.texy index 96d0ee1e96..0e10d16126 100644 --- a/application/hu/components.texy +++ b/application/hu/components.texy @@ -230,6 +230,28 @@ A sablonban ezek az üzenetek a `$flashes` változóban állnak rendelkezésre, ``` +Átirányítás jelzést követően .[#toc-redirection-after-a-signal] +=============================================================== + +Egy komponensjel feldolgozása után gyakran következik az átirányítás. Ez a helyzet hasonló az űrlapokhoz - egy űrlap elküldése után mi is átirányítunk, hogy megakadályozzuk az adatok újbóli elküldését, amikor az oldal frissül a böngészőben. + +```php +$this->redirect('this') // redirects to the current presenter and action +``` + +Mivel a komponens egy újrafelhasználható elem, és általában nem szabad, hogy közvetlen függőségben álljon az egyes prezenterektől, a `redirect()` és a `link()` metódusok automatikusan komponensjelként értelmezik a paramétert: + +```php +$this->redirect('click') // redirects to the 'click' signal of the same component +``` + +Ha át kell irányítani egy másik prezenterre vagy műveletre, akkor ezt a prezenteren keresztül teheti meg: + +```php +$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action +``` + + Állandó paraméterek .[#toc-persistent-parameters] ================================================= diff --git a/application/hu/configuration.texy b/application/hu/configuration.texy index 2dc8e2a87a..8c07c79a3a 100644 --- a/application/hu/configuration.texy +++ b/application/hu/configuration.texy @@ -95,6 +95,9 @@ latte: # engedélyezi a [generált kód ellenőrzését |latte:develop#Checking Generated Code] phpLinter: ... # (string) alapértelmezett a null + # beállítja a nyelvjárást + locale: cs_CZ # (string) alapértelmezett érték nulla + # $this->template osztálya templateClass: # alapértelmezett értéke Nette\Bridges\ApplicationLatte\DefaultTemplate ``` @@ -104,7 +107,7 @@ Ha a Latte 3. verzióját használja, akkor új [bővítményt |latte:creating-e ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/hu/how-it-works.texy b/application/hu/how-it-works.texy index 5101363032..24d0e0cba1 100644 --- a/application/hu/how-it-works.texy +++ b/application/hu/how-it-works.texy @@ -22,13 +22,13 @@ A könyvtárszerkezet valahogy így néz ki: /--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/ ← alapvető szükséges osztályok +│ │ └── RouterFactory.php ← URL címek konfigurálása +│ ├── UI/ ← prezenterek, sablonok és társai. +│ │ ├── @layout.latte ← a megosztott elrendezés sablonja +│ │ └── Home/ ← Főoldal bemutatókönyvtár +│ │ ├── HomePresenter.php ← Home prezenter osztály +│ │ └── default.latte ← cselekvési sablon default │ └── Bootstrap.php ← booting class Bootstrap ├── bin/ ← scripts for the command line ├── config/ ← configuration files @@ -91,7 +91,7 @@ A Nette-ben írt alkalmazások sok úgynevezett prezenterre oszlanak (más keret Az alkalmazás azzal indul, hogy az ún. routertől kéri, hogy döntse el, hogy az aktuális kérést melyik prezenternek adja át feldolgozásra. A router dönti el, hogy kinek a felelőssége. Megnézi a bemeneti URL-t `https://example.com/product/123`, aki a `show` egy terméket `id: 123` művelettel akarja ellátni. Jó szokás a prezenter + akció párokat kettősponttal elválasztva `Product:show`-ként írni. -Tehát a router az URL-t átalakította `Presenter:action` + paraméterek párrá, esetünkben `Product:show` + `id: 123`. A `app/Router/RouterFactory.php` fájlban láthatjuk, hogyan néz ki egy útválasztó, és ezt részletesen az [Útválasztás |Routing] fejezetben fogjuk leírni. +Tehát a router az URL-t átalakította `Presenter:action` + paraméterek párrá, esetünkben `Product:show` + `id: 123`. A `app/Core/RouterFactory.php` fájlban láthatjuk, hogyan néz ki egy útválasztó, és ezt részletesen az [Útválasztás |Routing] fejezetben fogjuk leírni. Lépjünk tovább. Az alkalmazás már ismeri a bemutató nevét, és folytathatja. Egy `ProductPresenter` objektum létrehozásával, amely a `Product` bemutató kódja. Pontosabban megkéri a DI konténert a prezenter létrehozására, mert az objektumok előállítása az ő feladata. @@ -121,12 +121,9 @@ Tehát a `renderShow(123)` metódust hívtuk meg, amelynek kódja fiktív példa Ezt követően a prezenter visszaadja a választ. Ez lehet egy HTML oldal, egy kép, egy XML dokumentum, egy fájl elküldése a lemezről, JSON vagy egy másik oldalra való átirányítás. Fontos, hogy ha nem mondjuk meg kifejezetten, hogyan kell válaszolni (ami a `ProductPresenter` esetében a helyzet), akkor a válasz az lesz, hogy a sablon egy HTML-oldallal jeleníti meg a sablont. Hogy miért? Nos, mert az esetek 99%-ában egy sablont szeretnénk kirajzolni, így a prezentáló ezt a viselkedést veszi alapértelmezettnek, és a mi munkánkat akarja megkönnyíteni. Ez a Nette lényege. -Még csak meg sem kell adnunk, hogy melyik sablont rajzoljuk ki, egyszerű logika szerint levezeti az oda vezető utat. A presenter `Product` és az action `show` esetében megnézi, hogy létezik-e valamelyik sablonfájl a `ProductPresenter` osztály könyvtárához képest, ahol a osztály található: +Még azt sem kell megadnunk, hogy melyik sablont kell megjelenítenünk; a keretrendszer magától levonja az útvonalat. A `show` akció esetében egyszerűen megpróbálja betölteni a `show.latte` sablont a `ProductPresenter` osztályt tartalmazó könyvtárban. Megpróbálja megtalálni az elrendezést is a `@layout.latte` fájlban (a [sablonkeresésről |templates#Template Lookup] bővebben). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Megpróbálja megtalálni az elrendezést is a `@layout.latte` fájlban, majd rendereli a sablont. Ezzel a prezenter és az egész alkalmazás feladata befejeződött. Ha a sablon nem létezik, akkor egy 404-es hibaüzenetű oldal fog visszakerülni. A prezenterekről bővebben a [Prezenterek |Presenters] oldalon olvashat. +Ezt követően a sablonok megjelenítésre kerülnek. Ezzel a bemutató és az egész alkalmazás feladata befejeződik, és a munka elvégeztetett. Ha a sablon nem létezne, akkor egy 404-es hibaoldalt kapna vissza. A prezenterekről bővebben a [Prezenterek |presenters] oldalon olvashat. [* request-flow.svg *] @@ -137,7 +134,7 @@ A biztonság kedvéért próbáljuk meg az egész folyamatot egy kicsit más URL 3) a router dekódolja az URL-t, mint egy párat `Home:default` 4) létrejön egy `HomePresenter` objektum 5) a `renderDefault()` metódust meghívjuk (ha létezik) -6) egy `templates/Home/default.latte` sablon `templates/@layout.latte` elrendezéssel megjelenik. +6) egy `default.latte` sablon `@layout.latte` elrendezéssel megjelenik. Lehet, hogy most sok új fogalommal találkoztál, de úgy gondoljuk, hogy van értelme. Az alkalmazások létrehozása a Nette-ben gyerekjáték. diff --git a/application/hu/modules.texy b/application/hu/modules.texy index e4ec0d80aa..a44b6ec874 100644 --- a/application/hu/modules.texy +++ b/application/hu/modules.texy @@ -2,29 +2,31 @@ Modulok ******* .[perex] -A Nette-ben a modulok az alkalmazást alkotó logikai egységeket jelentik. Ide tartoznak a prezenterek, sablonok, esetleg komponensek és modellosztályok. +A modulok a Nette-alkalmazások áttekinthetőségét segítik elő azáltal, hogy megkönnyítik a logikai egységekre való egyszerű felosztást. -Egy könyvtár a bemutatóknak és egy a sablonoknak nem lenne elég a valódi projektekhez. Több tucat fájl egy mappában való elhelyezése legalábbis rendezetlen. Hogyan szabadulhatunk meg ettől? Egyszerűen felosztjuk őket alkönyvtárakra a lemezen és névterekre a kódban. És pontosan ezt teszik a Nette modulok. - -Felejtsük el tehát az egyetlen mappát az előadóknak és a sablonoknak, és helyette hozzunk létre modulokat, például a `Admin` és a `Front`. +Hasonlóan a fájlok mappákba rendezéséhez a merevlemezen, a Nette-ben modulokba oszthatjuk a prezentereket, sablonokat és más segédosztályokat. Hogyan működik ez a gyakorlatban? Egyszerűen úgy, hogy új alkönyvtárakat építünk be a struktúrába. Íme egy példa egy struktúrára két modullal, a Front és az Admin modulokkal: /--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 +│ │ └── ... \-- -Ezt a könyvtárszerkezetet az osztályok névterei is tükrözni fogják, így például a `DashboardPresenter` a `App\Modules\Admin\Presenters` névtérben lesz: +Ez a könyvtárszerkezet tükröződik az osztályok névterében, így például a `DashboardPresenter` a `App\UI\Admin\Dashboard` névtérben található: ```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 } ``` -A `Dashboard` prezenterre a `Admin` modulon belül az alkalmazáson belül a kettőspont jelöléssel `Admin:Dashboard`, a `default` műveletre pedig `Admin:Dashboard:default` néven hivatkozunk. -És honnan tudja a Nette proper, hogy a `Admin:Dashboard` a `App\Modules\Admin\Presenters\DashboardPresenter` osztályt képviseli? Ezt a konfigurációban történő [leképezéssel |#mapping] határozzuk meg. -A megadott struktúra tehát nem keményen meghatározott, és Ön az igényeinek megfelelően módosíthatja. +Az alkalmazásban a `Dashboard` bemutatóra a `Admin` modulon belül a `Admin:Dashboard` kettőspont jelöléssel hivatkozunk a . A `default` műveletére `Admin:Dashboard:default` néven hivatkozunk. -A modulok természetesen a prezentereken és sablonokon kívül minden más elemet is tartalmazhatnak, például komponenseket, modellosztályokat stb. +A bemutatott struktúra nem merev; a konfigurációban [teljes mértékben az igényeihez igazíthatja |#mapping]. .[tip] + +A modulok a prezentereken és sablonokon kívül minden más fájlt, például komponenseket és segédosztályokat is tartalmazhatnak. Ha mérlegeli, hogy hol helyezze el ezeket, fontolja meg a `Accessory` mappa használatát: + +/--pre +app/ +├── UI/ +│ ├── Admin/ +│ │ ├── Accessory/ +│ │ │ ├── FormFactory.php +│ │ │ └── AdminLayout.php +│ │ ├── Dashboard/ +│ │ └── ... +\-- Beágyazott modulok .[#toc-nested-modules] ----------------------------------------- -A moduloknak nem kell csak sima struktúrát alkotniuk, létrehozhatunk például almodulokat is: +A modulok több szinten is egymásba ágyazhatók, hasonlóan a lemezen lévő könyvtárstruktúrához: /--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 │ │ └── ... \-- -Így a `Blog` modul `Admin` és `Front` almodulokra oszlik. Ez ismét tükröződik a névterekben, amelyek a `App\Modules\Blog\Admin\Presenters` stb. lesznek. Az almodulon belüli `Dashboard` bemutatót `Blog:Admin:Dashboard` néven említjük. +A `Blog` modul `Admin` és `Front` almodulokra oszlik. Ez tükröződik a névterekben is, amelyek aztán `App\UI\Blog\Admin` és hasonló módon jelennek meg. A `Dashboard` bemutatóra a `Admin` almodulon belül `Blog:Admin:Dashboard` néven hivatkozunk. -A beágyazás tetszőlegesen mélyre mehet, így al-almodulok hozhatók létre. +A beágyazás olyan mély lehet, amilyen mélyre csak szükséges, lehetővé téve al-almodulok létrehozását. + +Például, ha az adminisztrációban sok, a rendeléskezeléshez kapcsolódó prezenter van, mint például `OrderDetail`, `OrderEdit`, `OrderDispatch`, stb., létrehozhat egy `Order` modult, amelyben a `Detail`, `Edit`, `Dispatch`, stb. típusú prezentereket szervezi. Linkek létrehozása .[#toc-creating-links] @@ -99,49 +115,69 @@ Hogy megtudjuk, hogy egy adott modulban vagy annak almoduljában vagyunk-e, hasz Lásd [az útválasztásról szóló fejezetet |routing#Modules]. -Feltérképezés .[#toc-mapping] ------------------------------ +A feltérképezése .[#toc-mapping] +--------------------------------- + +A leképezés határozza meg az osztálynévnek a bemutató nevéből való származtatásának szabályait. Ezek a szabályok a [konfigurációban |configuration] a `application › mapping` kulcs alatt vannak megadva. + +Az ezen az oldalon korábban említett könyvtárszerkezetek a következő leképezésen alapulnak: -Meghatározza azokat a szabályokat, amelyek alapján az osztály neve az előadó nevéből származik. Ezeket a [konfigurációban |configuration] a `application › mapping` kulcs alatt írjuk le. +```neon +application: + mapping: App\UI\*\**Presenter +``` -Kezdjük egy olyan példával, amely nem használ modulokat. Csak azt akarjuk, hogy a prezenter osztályok a `App\Presenters` névtérrel rendelkezzenek. Ez azt jelenti, hogy egy olyan prezenternek, mint a `Home`, a `App\Presenters\HomePresenter` osztályhoz kell kapcsolódnia. Ezt a következő konfigurációval érhetjük el: +Hogyan működik a térképezés? A jobb megértés érdekében először képzeljünk el egy modulok nélküli alkalmazást. Szeretnénk, ha a prezenter osztályok a `App\UI` névtérbe tartoznának, így a `Home` prezenter a `App\UI\HomePresenter` osztályhoz tartozik. Ezt a következő konfigurációval érhetjük el: ```neon application: - mapping: App\Presenters\*Presenter + mapping: App\UI\*Presenter ``` -Az osztálymaszkban a prezenter nevét csillaggal helyettesítjük, és az eredmény az osztály neve lesz. Easy! +Ez a leképezés úgy működik, hogy a `App\UI\*Presenter` maszkban a csillagot a `Home` prezenter névvel helyettesítjük, így kapjuk a `App\UI\HomePresenter` végső osztálynevet. Egyszerű! + +Azonban, ahogyan azt az ebben és más fejezetekben található példákban láthatjuk, a prezenter osztályokat névadó alkönyvtárakba helyezzük, például a `Home` prezenter a `App\UI\Home\HomePresenter` osztályra van leképezve. Ezt a csillag megduplázásával érjük el (Nette Application 3.2 szükséges): + +```neon +application: + mapping: App\UI\**Presenter +``` -Ha az előadókat modulokra osztjuk, akkor minden modulhoz saját leképezésünk lehet: +Most pedig térjünk rá az előadók modulokba való leképezésére. Minden egyes modulhoz sajátos hozzárendeléseket határozhatunk meg: ```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 ``` -Most a `Front:Home` bemutatót a `App\Modules\Front\Presenters\HomePresenter` osztályra, a `Admin:Dashboard` bemutatót pedig a `App\Modules\Admin\Presenters\DashboardPresenter` osztályra képezzük le. +E konfiguráció szerint a `Front:Home` bemutató a `App\UI\Front\Home\HomePresenter` osztályhoz, míg a `Api:OAuth` bemutató a `App\Api\OAuthPresenter` osztályhoz tartozik. -Praktikusabb egy általános (csillag) szabályt létrehozni az első kettő helyett. Az extra csillagot csak a modul számára adjuk hozzá az osztálymaszkhoz: +Mivel a `Front` és a `Admin` modulok hasonló leképezési megközelítéssel rendelkeznek, és valószínűleg több ilyen modul is létezik, létrehozható egy általános szabály, amely ezeket helyettesíti. Az osztálymaszkhoz egy új csillagot adunk a modulhoz: ```neon application: mapping: - *: App\Modules\*\Presenters\*Presenter + *: App\UI\*\**Presenter Api: App\Api\*Presenter ``` -De mi van akkor, ha egymásba ágyazott modulokat használunk, és van egy bemutató `Admin:User:Edit`? Ebben az esetben a modult jelképező csillaggal ellátott szegmens minden szinten egyszerűen megismétlődik, és az eredmény a `App\Modules\Admin\User\Presenters\EditPresenter` osztály lesz. +A többszintű, egymásba ágyazott modulok esetében, mint például a `Admin:User:Edit` bemutató, a csillagszegmens minden szinten megismétlődik, így a `App\UI\Admin\User\Edit\EditPresenter` osztály lesz az eredmény. -Egy alternatív jelölés az, hogy a karakterlánc helyett egy három szegmensből álló tömböt használunk. Ez a jelölés egyenértékű az előzővel: +Egy alternatív jelölés a karakterlánc helyett egy három szegmensből álló tömb használata. Ez a jelölés egyenértékű az előzővel: ```neon application: mapping: - *: [App\Modules, *, Presenters\*Presenter] + *: [App\UI, *, **Presenter] + Api: [App\Api, '', *Presenter] ``` -Az alapértelmezett érték a `*Module\*Presenter`. +Ha csak egy szabály van a konfigurációban, az általános szabály, akkor röviden leírhatjuk: + +```neon +application: + mapping: App\UI\*\**Presenter +``` diff --git a/application/hu/presenters.texy b/application/hu/presenters.texy index f66a220e73..076c632a4c 100644 --- a/application/hu/presenters.texy +++ b/application/hu/presenters.texy @@ -60,7 +60,7 @@ Hasonlóan a módszerhez `render()`. Míg a `render()` célja, hogy Fontos, hogy `action()` előbb hívódik meg, mint a `render()`, így ezen belül esetleg megváltoztathatjuk az életciklus következő menetét, azaz megváltoztathatjuk a megjelenítendő sablont és a metódust is. `render()` ami meghívásra kerül, a `setView('otherView')` segítségével. -A kérésből származó paramétereket átadjuk a metódusnak. Lehetséges és ajánlott a paraméterek típusainak megadása, pl. `actionShow(int $id, string $slug = null)` - ha a `id` paraméter hiányzik, vagy nem egész szám, a prezenter [404-es hibát |#Error 404 etc.] ad vissza, és megszakítja a műveletet. +A kérésből származó paramétereket átadjuk a metódusnak. Lehetséges és ajánlott a paraméterek típusainak megadása, pl. `actionShow(int $id, ?string $slug = null)` - ha a `id` paraméter hiányzik, vagy nem egész szám, a prezenter [404-es hibát |#Error 404 etc.] ad vissza, és megszakítja a műveletet. `handle(args...)` .{toc: handle()} @@ -205,7 +205,7 @@ A sablonban ezek az üzenetek a `$flashes` változóban `stdClass` objektumként 404-es hiba stb. .[#toc-error-404-etc] ====================================== -Ha nem tudjuk teljesíteni a kérést, mert például a megjeleníteni kívánt cikk nem létezik az adatbázisban, akkor a 404-es hibát dobjuk ki a `error(string $message = null, int $httpCode = 404)` módszerrel, amely a 404-es HTTP hibát jelenti: +Ha nem tudjuk teljesíteni a kérést, mert például a megjeleníteni kívánt cikk nem létezik az adatbázisban, akkor a 404-es hibát dobjuk ki a `error(?string $message = null, int $httpCode = 404)` módszerrel, amely a 404-es HTTP hibát jelenti: ```php public function renderShow(int $id): void @@ -384,7 +384,7 @@ Az átirányítás nem történik AJAX vagy POST kérés esetén, mivel az adatv A kanonizálás manuálisan is meghívható a `canonicalize()` módszerrel, amely a `link()` módszerhez hasonlóan a prezentálót, a műveleteket és a paramétereket kapja argumentumként. Létrehoz egy linket, és összehasonlítja azt az aktuális URL-lel. Ha eltér, akkor átirányít a létrehozott linkre. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // átirányít, ha a $slug különbözik a $realSlug-tól. @@ -452,17 +452,6 @@ Hozzáférés korlátozása `#[Requires]` .[#toc-access-restriction-using-requir A `#[Requires]` attribútum speciális lehetőségeket biztosít az előadókhoz és módszereikhez való hozzáférés korlátozására. Használható HTTP-módszerek megadására, AJAX-kérések megkövetelésére, az azonos eredetű hozzáférések korlátozására és a hozzáférésnek csak a továbbításra való korlátozására. Az attribútum alkalmazható a prezenter osztályokra, valamint az egyes metódusokra, mint például a `action()`, `render()`, `handle()`, és `createComponent()`. -Íme egy példa arra, hogy csak a HTTP `POST` módszerre korlátozzuk a hozzáférést: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Ezeket a korlátozásokat megadhatja: - a HTTP-módszerekre: `#[Requires(methods: ['GET', 'POST'])]` - AJAX-kérést igényel: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ Ezeket a korlátozásokat megadhatja: - hozzáférés csak továbbítással: `#[Requires(forward: true)]` - korlátozások bizonyos műveletekre: `#[Requires(actions: 'default')]` -A feltételek kombinálhatók több attribútum felsorolásával vagy azok egyesítésével: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +A részletekért lásd [Hogyan használjuk a Requires attribútum használata |best-practices:attribute-requires]. HTTP módszer ellenőrzése .[#toc-http-method-check] diff --git a/application/hu/routing.texy b/application/hu/routing.texy index 5cf399296b..bb4a347148 100644 --- a/application/hu/routing.texy +++ b/application/hu/routing.texy @@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +225,7 @@ $router->addRoute('/[/]', [ ]); ``` -Vagy használhatjuk ezt a formát, vegyük észre az érvényesítési reguláris kifejezés átírását: +Részletesebb specifikációhoz egy még bővebb formát lehet használni, ahol az alapértelmezett értékek mellett más paramétertulajdonságok is megadhatók, például egy érvényesítési reguláris kifejezés (lásd a `id` paramétert): ```php use Nette\Routing\Route; @@ -243,7 +243,7 @@ $router->addRoute('/[/]', [ ]); ``` -Ezek a beszédesebb formátumok hasznosak más metaadatok hozzáadásához. +Fontos megjegyezni, hogy ha a tömbben definiált paraméterek nem szerepelnek az elérési útvonal maszkjában, akkor értékük nem módosítható, még az URL-ben kérdőjel után megadott lekérdezési paraméterekkel sem. Szűrők és fordítások .[#toc-filters-and-translations] @@ -477,10 +477,10 @@ $router->addRoute('index', /* ... */); Integráció .[#toc-integration] ============================== -Ahhoz, hogy a routerünket az alkalmazásba kapcsoljuk, tájékoztatnunk kell róla a DI konténert. A legegyszerűbb, ha elkészítjük a gyárat, amely a router objektumot fogja felépíteni, és megmondjuk a konténer konfigurációjának, hogy használja azt. Tegyük fel, hogy írunk egy metódust erre a célra `App\Router\RouterFactory::createRouter()`: +Ahhoz, hogy a routerünket az alkalmazásba kapcsoljuk, tájékoztatnunk kell róla a DI konténert. A legegyszerűbb, ha elkészítjük a gyárat, amely a router objektumot fogja felépíteni, és megmondjuk a konténer konfigurációjának, hogy használja azt. Tegyük fel, hogy írunk egy metódust erre a célra `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -499,7 +499,7 @@ Ezután beírjuk a [konfigurációba |dependency-injection:services]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Az esetleges függőségeket, például az adatbázis-kapcsolatot stb., az [autowiring |dependency-injection:autowiring] segítségével paraméterként átadjuk a gyári metódusnak: @@ -663,7 +663,7 @@ A szeparált használat alatt a router képességeinek olyan alkalmazásban tör Tehát ismét létrehozunk egy metódust, amely létrehoz egy útválasztót, például: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Vagy közvetlenül hozzuk létre az objektumokat: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/hu/templates.texy b/application/hu/templates.texy index 71b205b7d1..3f46d2d377 100644 --- a/application/hu/templates.texy +++ b/application/hu/templates.texy @@ -34,35 +34,81 @@ Az a szokásos, hogy az oldal az elrendezési sablonból + az akció sablonból Meghatározza a `content` blokkot, amely az elrendezésben a `{include content}` helyére kerül, és újra definiálja a `title` blokkot is, amely felülírja az elrendezésben a `{block title}` blokkot. Próbálja meg elképzelni az eredményt. -Sablonok keresése .[#toc-search-for-templates] ----------------------------------------------- +Sablon keresés .[#toc-template-lookup] +-------------------------------------- -A sablonok elérési útvonalát egyszerű logika szerint vezetjük le. Megpróbálja megnézni, hogy létezik-e valamelyik sablonfájl ahhoz a könyvtárhoz képest, ahol a prezenter osztály található, ahol a `` az aktuális prezenter neve és `` az aktuális művelet neve: +A prezenterekben nem kell megadnia, hogy melyik sablont kell megjeleníteni; a keretrendszer automatikusan meghatározza az útvonalat, megkönnyítve ezzel a kódolást. -- `templates//.latte` -- `templates/..latte` +Ha olyan könyvtárstruktúrát használ, ahol minden prezenternek saját könyvtára van, egyszerűen helyezze el a sablont ebben a könyvtárban az akció (pl. nézet) neve alatt. Például a `default` művelethez használja a `default.latte` sablont: -Ha a sablon nem található, a program megpróbál a `templates` könyvtárban keresni egy szinttel feljebb, azaz ugyanazon a szinten, mint a bemutató osztályt tartalmazó könyvtár. +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Ha a sablon ott sem található, a válasz [404-es hiba |presenters#Error 404 etc.]. +Ha olyan struktúrát használ, ahol az előadók együttesen egy könyvtárban vannak, a sablonok pedig a `templates` mappában, mentse el vagy egy fájlban `..latte` vagy a `/.latte`: -A nézetet a `$this->setView('otherView')` segítségével is megváltoztathatja. Vagy a keresés helyett közvetlenül megadhatja a sablonfájl nevét a `$this->template->setFile('/path/to/template.latte')` segítségével. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- + +A `templates` könyvtár egy szinttel feljebb is elhelyezhető, ugyanazon a szinten, mint az előadói osztályokat tartalmazó könyvtár. + +Ha a sablon nem található, a prezenter [404 - page not found hibával |presenters#Error 404 etc] válaszol. + +A nézetet a `$this->setView('anotherView')` segítségével lehet megváltoztatni. Lehetőség van a sablonfájl közvetlen megadására is a `$this->template->setFile('/path/to/template.latte')` segítségével. .[note] -A [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()] metódus felülbírálásával módosíthatja azokat az elérési utakat, ahol a sablonok keresése történik, amely a lehetséges fájl elérési utak tömbjét adja vissza. +A fájlokat, amelyekben a sablonok keresése történik, a [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] metódus felülbírálásával lehet megváltoztatni, amely a lehetséges fájlnevek tömbjét adja vissza. + + +Layout sablon keresés .[#toc-layout-template-lookup] +---------------------------------------------------- + +A Nette automatikusan megkeresi az elrendezési fájlt is. + +Ha olyan könyvtárstruktúrát használ, ahol minden előadónak saját könyvtára van, akkor az elrendezést vagy az előadóval közös mappába helyezze el, ha csak rá jellemző, vagy egy szinttel feljebb, ha több előadó számára közös: + +/--pre +app/ +└── UI/ + ├── @layout.latte ← common layout + └── Home/ + ├── @layout.latte ← only for Home presenter + ├── HomePresenter.php + └── default.latte +\-- + +Ha olyan struktúrát használ, ahol az előadók egy könyvtárban vannak csoportosítva, a sablonok pedig a `templates` mappában találhatók, az elrendezés a következő helyeken várható: -Az elrendezés a következő fájlokban várható: +/--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` több előadónál közös elrendezés +Ha a bemutató egy [modulban |modules] van, akkor a modul beágyazottságának megfelelően a könyvtárfában feljebb is keresni fog. -`` az aktuális előadó neve és `` az elrendezés neve, amely alapértelmezés szerint `'layout'`. A név megváltoztatható a `$this->setLayout('otherLayout')` segítségével, így a `@otherLayout.latte` fájlokat próbálja meg. +Az elrendezés nevét a `$this->setLayout('layoutAdmin')` segítségével lehet megváltoztatni, majd a `@layoutAdmin.latte` fájlban várjuk el. Az elrendezés sablonfájlt közvetlenül is megadhatja a `$this->setLayout('/path/to/template.latte')` segítségével. -Az elrendezéssablon fájlnevét közvetlenül is megadhatja a `$this->setLayout('/path/to/template.latte')` segítségével. A `$this->setLayout(false)` használata letiltja az elrendezés keresését. +A `$this->setLayout(false)` vagy a `{layout none}` címke használata a sablonon belül kikapcsolja az elrendezéskeresést. .[note] -A [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] metódus felülbírálásával módosíthatja a sablonok keresési útvonalait, amely a lehetséges fájlútvonalak tömbjét adja vissza. +A fájlok, amelyekben az elrendezési sablonok keresése történik, megváltoztathatók a [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] metódus felülbírálásával, amely a lehetséges fájlnevek tömbjét adja vissza. Változók a sablonban .[#toc-variables-in-the-template] @@ -104,7 +150,7 @@ A `@property-read` annotáció az IDE és a statikus elemzés számára készül A sablonokban is megengedheted magadnak a suttogás luxusát, csak telepítsd a Latte plugint a PhpStormban, és add meg az osztály nevét a sablon elején, lásd a "Latte: hogyan kell tipizálni a rendszert":https://blog.nette.org/hu/latte-hogyan-kell-hasznalni-a-tipusrendszert című cikket: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void A Latte 3. verziója egy fejlettebb módszert kínál, amely minden egyes webes projekthez egy [bővítményt |latte:creating-extension] hoz létre. Íme egy durva példa egy ilyen osztályra: ```php -namespace App\Templating; +namespace App\UI\Accessory; final class LatteExtension extends Latte\Extension { @@ -214,7 +260,7 @@ A [configuration |configuration#Latte] segítségével regisztráljuk: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\UI\Accessory\LatteExtension ``` @@ -239,7 +285,7 @@ Alternatívaként a fordítót a [konfiguráció |configuration#Latte] segítsé ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` A fordító ekkor például a `|translate` szűrőként használható, a `translate()` metódusnak átadott további paraméterekkel (lásd `foo, bar`): diff --git a/application/it/ajax.texy b/application/it/ajax.texy index 76658ce100..f15f19b518 100644 --- a/application/it/ajax.texy +++ b/application/it/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Per prima cosa è necessario [inizializzare |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] la libreria: + +```js +naja.initialize(); +``` + Per rendere un normale link (segnale) o l'invio di un modulo una richiesta AJAX, è sufficiente contrassegnare il rispettivo link, modulo o pulsante con la classe `ajax`: ```html diff --git a/application/it/bootstrap.texy b/application/it/bootstrap.texy index b2c87a4373..b338b46064 100644 --- a/application/it/bootstrap.texy +++ b/application/it/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__); + // Il configuratore è responsabile dell'impostazione dell'ambiente applicativo e dei servizi. + $this->configurator = new Configurator; + // Impostare la directory per i file temporanei generati da Nette (ad esempio, i modelli compilati). + $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 è intelligente e la modalità di sviluppo si attiva automaticamente, + // oppure si può attivare per un indirizzo IP specifico decommentando la seguente riga: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Abilita Tracy: lo strumento di debug per eccellenza, il "coltellino svizzero". + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: carica automaticamente tutte le classi nella cartella data + $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; + } + + private function setupContainer(): void + { + // Carica i file di configurazione + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } ``` @@ -40,16 +66,15 @@ class Bootstrap index.php .[#toc-index-php] =========================== -Nel caso delle applicazioni web, il file iniziale è `index.php`, che si trova nella cartella pubblica `www/`. Permette alla classe `Bootstrap` di inizializzare l'ambiente e restituisce la classe `$configurator` che crea il contenitore DI. Quindi ottiene il servizio `Application`, che esegue l'applicazione web: +Il file iniziale per le applicazioni web è `index.php`, situato nella cartella pubblica `www/`. Utilizza la classe `Bootstrap` per inizializzare l'ambiente e creare un contenitore DI. Quindi, ottiene il servizio `Application` dal contenitore, che lancia l'applicazione web: ```php -// inizializzare l'ambiente + ottenere l'oggetto Configuratore -$configurator = App\Bootstrap::boot(); -// creare un contenitore DI -$container = $configurator->createContainer(); -// Il contenitore DI crea un oggetto Nette\Application\Application +$bootstrap = new App\Bootstrap; +// Inizializzare l'ambiente + creare un contenitore DI +$container = $bootstrap->bootWebApplication(); +// Il contenitore DI crea un oggetto NetteApplicationApplication $application = $container->getByType(Nette\Application\Application::class); -// avvia l'applicazione Nette +// Avviare l'applicazione Nette e gestire la richiesta in arrivo $application->run(); ``` @@ -66,19 +91,19 @@ La selezione della modalità avviene tramite il rilevamento automatico, quindi d Se si vuole abilitare la modalità di sviluppo in altri casi, ad esempio per i programmatori che accedono da un indirizzo IP specifico, si può usare `setDebugMode()`: ```php -$configurator->setDebugMode('23.75.345.200'); // uno o più indirizzi IP +$this->configurator->setDebugMode('23.75.345.200'); // uno o più indirizzi IP ``` Consigliamo assolutamente di combinare un indirizzo IP con un cookie. Nel cookie `nette-debug` verrà memorizzato un token segreto, ad esempio `secret1234`, e la modalità di sviluppo verrà attivata per i programmatori con questa combinazione di IP e cookie. ```php -$configurator->setDebugMode('secret1234@23.75.345.200'); +$this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` Possiamo anche disattivare completamente la modalità sviluppatore, anche per localhost: ```php -$configurator->setDebugMode(false); +$this->configurator->setDebugMode(false); ``` Si noti che il valore `true` attiva la modalità sviluppatore, cosa che non dovrebbe mai accadere su un server di produzione. @@ -90,7 +115,7 @@ Strumento di debug Tracy .[#toc-debugging-tool-tracy] Per facilitare il debug, attiviamo l'ottimo strumento [Tracy |tracy:]. In modalità sviluppatore visualizza gli errori e in modalità produzione li registra nella directory specificata: ```php -$configurator->enableTracy($appDir . '/log'); +$this->configurator->enableTracy($this->rootDir . '/log'); ``` @@ -100,7 +125,7 @@ File temporanei .[#toc-temporary-files] Nette utilizza la cache per il contenitore DI, il RobotLoader, i modelli, ecc. Per questo motivo è necessario impostare il percorso della cartella in cui verrà memorizzata la cache: ```php -$configurator->setTempDirectory($appDir . '/temp'); +$this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` Su Linux o macOS, impostare i [permessi di scrittura |nette:troubleshooting#Setting directory permissions] per le directory `log/` e `temp/`. @@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader] Di solito, vogliamo caricare automaticamente le classi usando [RobotLoader |robot-loader:], quindi dobbiamo avviarlo e fargli caricare le classi dalla directory in cui si trova `Bootstrap.php` (cioè `__DIR__`) e da tutte le sue sottodirectory: ```php -$configurator->createRobotLoader() +$this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); ``` @@ -126,7 +151,7 @@ Fuso orario .[#toc-timezone] Il configuratore consente di specificare un fuso orario per l'applicazione. ```php -$configurator->setTimeZone('Europe/Prague'); +$this->configurator->setTimeZone('Europe/Prague'); ``` @@ -143,16 +168,17 @@ In modalità di sviluppo, il contenitore viene aggiornato automaticamente ogni v I file di configurazione vengono caricati usando `addConfig()`: ```php -$configurator->addConfig($appDir . '/config/common.neon'); +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` Il metodo `addConfig()` può essere richiamato più volte per aggiungere più file. ```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/services.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 @@ Parametri statici .[#toc-static-parameters] I parametri usati nei file di configurazione possono essere definiti [nella sezione `parameters` |dependency-injection:configuration#parameters] e anche passati (o sovrascritti) dal metodo `addStaticParameters()` (ha l'alias `addParameters()`). È importante che valori diversi dei parametri causino la generazione di contenitori DI aggiuntivi, cioè di classi aggiuntive. ```php -$configurator->addStaticParameters([ +$this->configurator->addStaticParameters([ 'projectId' => 23, ]); ``` @@ -183,7 +209,7 @@ Parametri dinamici .[#toc-dynamic-parameters] Possiamo anche aggiungere parametri dinamici al contenitore; i loro diversi valori, a differenza dei parametri statici, non causeranno la generazione di nuovi contenitori DI. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'remoteIp' => $_SERVER['REMOTE_ADDR'], ]); ``` @@ -191,7 +217,7 @@ $configurator->addDynamicParameters([ Le variabili d'ambiente possono essere facilmente rese disponibili usando parametri dinamici. Possiamo accedervi tramite `%env.variable%` nei file di configurazione. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'env' => getenv(), ]); ``` @@ -206,6 +232,7 @@ Parametri predefiniti .[#toc-default-parameters] - `%wwwDir%` è il percorso assoluto della directory contenente il file di ingresso `index.php` - `%tempDir%` è il percorso assoluto della directory per i file temporanei - `%vendorDir%` è il percorso assoluto della directory in cui Composer installa le librerie +- `%rootDir%` è il percorso assoluto della directory principale del progetto - `%debugMode%` indica se l'applicazione è in modalità debug - `%consoleMode%` indica se la richiesta è arrivata attraverso la riga di comando @@ -225,7 +252,7 @@ services: Creare una nuova istanza e inserirla in bootstrap: ```php -$configurator->addServices([ +$this->configurator->addServices([ 'myservice' => new App\Model\MyCustomService('foobar'), ]); ``` @@ -234,13 +261,21 @@ $configurator->addServices([ Ambienti diversi .[#toc-different-environments] =============================================== -Sentitevi liberi di personalizzare la classe `Bootstrap` in base alle vostre esigenze. Si possono aggiungere parametri al metodo `boot()` per differenziare i progetti web, oppure aggiungere altri metodi, come `bootForTests()`, che inizializza l'ambiente per i test unitari, `bootForCli()` per gli script chiamati dalla riga di comando e così via. +Non esitate a personalizzare la classe `Bootstrap` in base alle vostre esigenze. Si possono aggiungere parametri al metodo `bootWebApplication()` per differenziare i progetti web. In alternativa, si possono aggiungere altri metodi, come `bootTestEnvironment()` per inizializzare l'ambiente per i test unitari, `bootConsoleApplication()` per gli script chiamati dalla riga di comando e così via. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Inizializzazione del tester Nette + $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/it/components.texy b/application/it/components.texy index c623b0b7cd..a4198af736 100644 --- a/application/it/components.texy +++ b/application/it/components.texy @@ -230,6 +230,28 @@ Nel modello, questi messaggi sono disponibili nella variabile `$flashes` come og ``` +Reindirizzamento dopo un segnale .[#toc-redirection-after-a-signal] +=================================================================== + +Dopo l'elaborazione di un segnale di un componente, spesso segue un reindirizzamento. Questa situazione è simile a quella dei moduli: dopo l'invio di un modulo, si effettua un reindirizzamento per evitare che i dati vengano inviati nuovamente quando la pagina viene aggiornata nel browser. + +```php +$this->redirect('this') // redirects to the current presenter and action +``` + +Poiché un componente è un elemento riutilizzabile e di solito non dovrebbe avere una dipendenza diretta da presentatori specifici, i metodi `redirect()` e `link()` interpretano automaticamente il parametro come un segnale di componente: + +```php +$this->redirect('click') // redirects to the 'click' signal of the same component +``` + +Se è necessario reindirizzare a un presentatore o a un'azione diversa, lo si può fare attraverso il presentatore: + +```php +$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action +``` + + Parametri persistenti .[#toc-persistent-parameters] =================================================== diff --git a/application/it/configuration.texy b/application/it/configuration.texy index be8d2ebc12..c4703d5632 100644 --- a/application/it/configuration.texy +++ b/application/it/configuration.texy @@ -93,7 +93,10 @@ latte: strictParsing: ... # (bool) l'impostazione predefinita è false # abilita il [controllo del codice generato |latte:develop#Checking Generated Code] - phpLinter: ... # (stringa) il valore predefinito è null + phpLinter: ... # (string) il valore predefinito è null + + # imposta il locale + locale: cs_CZ # (string) il valore predefinito è null # classe di $this->template templateClass: App\MyTemplateClass # predefinita a Nette\Bridges\ApplicationLatte\DefaultTemplate @@ -104,7 +107,7 @@ Se si utilizza la versione 3 di Latte, è possibile aggiungere nuove [estensioni ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/it/how-it-works.texy b/application/it/how-it-works.texy index 91e4f4e5ef..3222b1f408 100644 --- a/application/it/how-it-works.texy +++ b/application/it/how-it-works.texy @@ -22,13 +22,13 @@ La struttura delle directory è simile a questa: /--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/ ← classi di base necessarie +│ │ └── RouterFactory.php ← configurazione degli indirizzi URL +│ ├── UI/ ← presenter, template & co. +│ │ ├── @layout.latte ← modello di layout condiviso +│ │ └── Home/ ← cartella del presentatore Home +│ │ ├── HomePresenter.php ← Classe del presentatore della casa +│ │ └── default.latte ← template per l'azione default │ └── Bootstrap.php ← booting class Bootstrap ├── bin/ ← scripts for the command line ├── config/ ← configuration files @@ -91,7 +91,7 @@ Le applicazioni scritte in Nette sono suddivise in molti cosiddetti presenter (i L'applicazione inizia chiedendo al cosiddetto router di decidere a quale dei presenter passare la richiesta corrente per l'elaborazione. Il router decide di chi è la responsabilità. Osserva l'URL di ingresso `https://example.com/product/123`, che vuole `show` un prodotto con `id: 123` come azione. È buona abitudine scrivere le coppie presentatore + azione separate da due punti come `Product:show`. -Quindi il router ha trasformato l'URL in una coppia `Presenter:action` + parametri, nel nostro caso `Product:show` + `id: 123`. Si può vedere l'aspetto di un router nel file `app/Router/RouterFactory.php` e lo descriveremo in dettaglio nel capitolo [Routing |Routing]. +Quindi il router ha trasformato l'URL in una coppia `Presenter:action` + parametri, nel nostro caso `Product:show` + `id: 123`. Si può vedere l'aspetto di un router nel file `app/Core/RouterFactory.php` e lo descriveremo in dettaglio nel capitolo [Routing |Routing]. Andiamo avanti. L'applicazione conosce già il nome del presentatore e può continuare. Creando un oggetto `ProductPresenter`, che è il codice del presentatore `Product`. Più precisamente, chiede al contenitore DI di creare il presentatore, perché produrre oggetti è il suo lavoro. @@ -121,12 +121,9 @@ Quindi, è stato chiamato il metodo `renderShow(123)`, il cui codice è un esemp Successivamente, il presentatore restituisce la risposta. Questa può essere una pagina HTML, un'immagine, un documento XML, l'invio di un file dal disco, JSON o il reindirizzamento a un'altra pagina. È importante notare che, se non si dice esplicitamente come rispondere (come nel caso di `ProductPresenter`), la risposta sarà il rendering del modello con una pagina HTML. Perché? Perché nel 99% dei casi vogliamo disegnare un modello, quindi il presentatore prende questo comportamento come predefinito e vuole semplificarci il lavoro. Questo è il punto di Nette. -Non dobbiamo nemmeno dichiarare quale modello disegnare, lui ricava il percorso per raggiungerlo secondo una semplice logica. Nel caso del presenter `Product` e dell'azione `show`, cerca di vedere se esiste uno di questi file di template relativi alla directory in cui si trova la classe `ProductPresenter`: +Non è nemmeno necessario specificare quale template rendere; il framework dedurrà da solo il percorso. Nel caso dell'azione `show`, cerca semplicemente di caricare il template `show.latte` nella cartella con la classe `ProductPresenter`. Cerca anche di trovare il layout nel file `@layout.latte` (maggiori informazioni sulla [ricerca dei template |templates#Template Lookup]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Cercherà anche di trovare il layout nel file `@layout.latte` e quindi eseguirà il rendering del modello. Ora il compito del presentatore e dell'intera applicazione è completato. Se il modello non esiste, verrà restituita una pagina con errore 404. Per saperne di più sui presentatori, consultare la pagina [Presentatori |Presenters]. +Successivamente, i modelli vengono renderizzati. Questo completa il compito del presentatore e dell'intera applicazione e il lavoro è terminato. Se il modello non esistesse, verrebbe restituita una pagina di errore 404. Per saperne di più sui presentatori, consultare la pagina [Presentatori |presenters]. [* request-flow.svg *] @@ -137,7 +134,7 @@ Per sicurezza, proviamo a riepilogare l'intero processo con un URL leggermente d 3) il router decodifica l'URL come una coppia di oggetti `Home:default` 4) viene creato un oggetto `HomePresenter` 5) viene richiamato il metodo `renderDefault()` (se esiste) -6) viene reso un modello `templates/Home/default.latte` con un layout `templates/@layout.latte` +6) viene reso un modello `default.latte` con un layout `@layout.latte` Potreste esservi imbattuti in molti concetti nuovi, ma crediamo che abbiano un senso. Creare applicazioni in Nette è un gioco da ragazzi. diff --git a/application/it/modules.texy b/application/it/modules.texy index 76b8af4e23..4a4e9c1539 100644 --- a/application/it/modules.texy +++ b/application/it/modules.texy @@ -2,29 +2,31 @@ Moduli ****** .[perex] -In Nette, i moduli rappresentano le unità logiche che compongono un'applicazione. Comprendono presentatori, modelli, eventualmente anche componenti e classi di modelli. +I moduli conferiscono chiarezza alle applicazioni Nette, facilitando la suddivisione in unità logiche. -Una cartella per i presentatori e una per i modelli non sarebbe sufficiente per i progetti reali. Avere decine di file in una cartella è quantomeno disorganizzato. Come uscirne? Semplicemente dividendoli in sottodirectory su disco e in spazi dei nomi nel codice. E questo è esattamente ciò che fanno i moduli Nette. - -Dimentichiamo quindi un'unica cartella per i presentatori e i modelli e creiamo invece dei moduli, ad esempio `Admin` e `Front`. +Analogamente all'organizzazione dei file in cartelle su un disco rigido, in Nette possiamo dividere presentatori, modelli e altre classi ausiliarie in moduli. Come funziona in pratica? Semplicemente incorporando nuove sottodirectory nella struttura. Ecco un esempio di struttura con due moduli, Front e 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 +│ │ └── ... \-- -Questa struttura di cartelle si rifletterà negli spazi dei nomi delle classi, quindi ad esempio `DashboardPresenter` sarà nello spazio dei nomi `App\Modules\Admin\Presenters`: +Questa struttura di directory si riflette negli spazi dei nomi delle classi, per cui, ad esempio, `DashboardPresenter` si trova nello spazio dei nomi `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 } ``` -Il presentatore `Dashboard` all'interno del modulo `Admin` è referenziato all'interno dell'applicazione usando la notazione dei due punti come `Admin:Dashboard`, e la sua azione `default` come `Admin:Dashboard:default`. -E come fa Nette a sapere che `Admin:Dashboard` rappresenta la classe `App\Modules\Admin\Presenters\DashboardPresenter`? Questo è determinato dalla [mappatura |#mapping] nella configurazione. -Pertanto, la struttura data non è rigida e può essere modificata in base alle proprie esigenze. +Nell'applicazione, ci si riferisce al presentatore `Dashboard` all'interno del modulo `Admin` usando la notazione dei due punti come `Admin:Dashboard`. Per l'azione `default`, ci si riferisce ad essa come `Admin:Dashboard:default`. -I moduli possono naturalmente contenere tutti gli altri elementi oltre ai presentatori e ai modelli, come componenti, classi di modelli, ecc. +La struttura presentata non è rigida; è possibile [personalizzarla completamente in base alle proprie esigenze |#mapping] nella configurazione. .[tip] + +I moduli possono includere tutti gli altri file, come i componenti e le classi ausiliarie, oltre ai presentatori e ai modelli. Se si sta valutando dove collocare questi ultimi, si può prendere in considerazione l'uso di una cartella `Accessory`: + +/--pre +app/ +├── UI/ +│ ├── Admin/ +│ │ ├── Accessory/ +│ │ │ ├── FormFactory.php +│ │ │ └── AdminLayout.php +│ │ ├── Dashboard/ +│ │ └── ... +\-- Moduli annidati .[#toc-nested-modules] -------------------------------------- -I moduli non devono formare solo una struttura piatta, ma si possono anche creare sottomoduli, ad esempio: +I moduli possono avere più livelli di annidamento, simili a una struttura di directory su disco: /--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 │ │ └── ... \-- -Così, il modulo `Blog` è suddiviso nei sottomoduli `Admin` e `Front`. Anche in questo caso, ciò si rifletterà negli spazi dei nomi, che saranno `App\Modules\Blog\Admin\Presenters` e così via. Il presentatore `Dashboard` all'interno del sottomodulo viene chiamato `Blog:Admin:Dashboard`. +Il modulo `Blog` è diviso nei sottomoduli `Admin` e `Front`. Questo si riflette anche negli spazi dei nomi, che appaiono come `App\UI\Blog\Admin` e simili. Per riferirsi al presentatore `Dashboard` all'interno del sottomodulo `Admin`, ci si riferisce ad esso come `Blog:Admin:Dashboard`. -L'annidamento può andare in profondità quanto si vuole, quindi si possono creare dei sottomoduli. +L'annidamento può essere profondo quanto necessario, consentendo la creazione di sottomoduli. + +Ad esempio, se nell'amministrazione sono presenti molti presentatori relativi alla gestione degli ordini, come `OrderDetail`, `OrderEdit`, `OrderDispatch`, ecc. si può creare un modulo `Order` in cui saranno organizzati presentatori come `Detail`, `Edit`, `Dispatch`, e altri. Creazione di collegamenti .[#toc-creating-links] @@ -102,46 +118,66 @@ Vedere il [capitolo sull'instradamento |routing#Modules]. Mappatura .[#toc-mapping] ------------------------- -Definisce le regole con cui il nome della classe viene derivato dal nome del presentatore. Vengono scritte nella [configurazione |configuration] sotto la chiave `application › mapping`. +La mappatura definisce le regole per derivare il nome della classe dal nome del presentatore. Queste regole sono specificate nella [configurazione |configuration] sotto la chiave `application › mapping`. + +Le strutture di directory menzionate in precedenza in questa pagina si basano sulla seguente mappatura: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Cominciamo con un esempio che non usa moduli. Vogliamo solo che le classi del presentatore abbiano lo spazio dei nomi `App\Presenters`. Ciò significa che un presentatore come `Home` deve mappare alla classe `App\Presenters\HomePresenter`. Questo si può ottenere con la seguente configurazione: +Come funziona la mappatura? Per capire meglio, immaginiamo prima un'applicazione senza moduli. Vogliamo che le classi del presentatore rientrino nello spazio dei nomi `App\UI`, in modo che il presentatore `Home` sia mappato nella classe `App\UI\HomePresenter`. Ciò può essere ottenuto con questa configurazione: ```neon application: - mapping: App\Presenters\*Presenter + mapping: App\UI\*Presenter ``` -Il nome del presentatore viene sostituito con l'asterisco nella maschera della classe e il risultato è il nome della classe. Facile! +Questa mappatura funziona sostituendo l'asterisco nella maschera `App\UI\*Presenter` con il nome del presenter `Home`, ottenendo il nome finale della classe `App\UI\HomePresenter`. Semplice! + +Tuttavia, come si può vedere negli esempi di questo e di altri capitoli, le classi dei presentatori sono collocate in sottodirectory eponime, ad esempio il presentatore `Home` è mappato nella classe `App\UI\Home\HomePresenter`. Questo si ottiene raddoppiando l'asterisco (richiede Nette Application 3.2): + +```neon +application: + mapping: App\UI\**Presenter +``` -Se dividiamo i presentatori in moduli, possiamo avere la nostra mappatura per ogni modulo: +Passiamo ora alla mappatura dei presentatori nei moduli. Possiamo definire mappature specifiche per ogni modulo: ```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 ``` -Ora il presentatore `Front:Home` mappa alla classe `App\Modules\Front\Presenters\HomePresenter` e il presentatore `Admin:Dashboard` alla classe `App\Modules\Admin\Presenters\DashboardPresenter`. +In base a questa configurazione, il presentatore `Front:Home` si riferisce alla classe `App\UI\Front\Home\HomePresenter`, mentre il presentatore `Api:OAuth` si riferisce alla classe `App\Api\OAuthPresenter`. -È più pratico creare una regola generale (asterisco) per sostituire le prime due. L'asterisco in più sarà aggiunto alla maschera di classe solo per il modulo: +Poiché i moduli `Front` e `Admin` hanno un approccio di mappatura simile e probabilmente ci saranno altri moduli di questo tipo, è possibile creare una regola generale che li sostituisca. Un nuovo asterisco per il modulo viene aggiunto alla maschera della classe: ```neon application: mapping: - *: App\Modules\*\Presenters\*Presenter + *: App\UI\*\**Presenter Api: App\Api\*Presenter ``` -Ma cosa succede se utilizziamo moduli annidati e abbiamo un presentatore `Admin:User:Edit`? In questo caso, il segmento con l'asterisco che rappresenta il modulo per ogni livello viene semplicemente ripetuto e il risultato è la classe `App\Modules\Admin\User\Presenters\EditPresenter`. +Per i moduli annidati a più livelli, come il presentatore `Admin:User:Edit`, il segmento dell'asterisco si ripete per ogni livello, dando luogo alla classe `App\UI\Admin\User\Edit\EditPresenter`. Una notazione alternativa consiste nell'utilizzare un array composto da tre segmenti invece di una stringa. Questa notazione è equivalente alla precedente: ```neon application: mapping: - *: [App\Modules, *, Presenters\*Presenter] + *: [App\UI, *, **Presenter] + Api: [App\Api, '', *Presenter] ``` -Il valore predefinito è `*Module\*Presenter`. +Se abbiamo una sola regola nella configurazione, quella generale, possiamo scrivere brevemente: + +```neon +application: + mapping: App\UI\*\**Presenter +``` diff --git a/application/it/presenters.texy b/application/it/presenters.texy index 874bdb7ccf..7f12f07c7f 100644 --- a/application/it/presenters.texy +++ b/application/it/presenters.texy @@ -60,7 +60,7 @@ Simile al metodo `render()`. Mentre `render()` è destinato a prepar È importante che `action()` sia chiamato prima di `render()`quindi al suo interno si può eventualmente modificare il corso successivo del ciclo di vita, cioè cambiare il template che sarà reso e anche il metodo `render()` che sarà chiamato, utilizzando `setView('otherView')`. -I parametri della richiesta vengono passati al metodo. È possibile e consigliabile specificare i tipi di parametri, ad esempio `actionShow(int $id, string $slug = null)` - se il parametro `id` manca o non è un intero, il presentatore restituisce l'[errore 404 |#Error 404 etc.] e termina l'operazione. +I parametri della richiesta vengono passati al metodo. È possibile e consigliabile specificare i tipi di parametri, ad esempio `actionShow(int $id, ?string $slug = null)` - se il parametro `id` manca o non è un intero, il presentatore restituisce l'[errore 404 |#Error 404 etc.] e termina l'operazione. `handle(args...)` .{toc: handle()} @@ -205,7 +205,7 @@ Nel modello, questi messaggi sono disponibili nella variabile `$flashes` come og Errore 404 ecc. .[#toc-error-404-etc] ===================================== -Quando non possiamo soddisfare la richiesta, perché ad esempio l'articolo che vogliamo visualizzare non esiste nel database, lanceremo l'errore 404 usando il metodo `error(string $message = null, int $httpCode = 404)`, che rappresenta l'errore HTTP 404: +Quando non possiamo soddisfare la richiesta, perché ad esempio l'articolo che vogliamo visualizzare non esiste nel database, lanceremo l'errore 404 usando il metodo `error(?string $message = null, int $httpCode = 404)`, che rappresenta l'errore HTTP 404: ```php public function renderShow(int $id): void @@ -384,7 +384,7 @@ Il reindirizzamento non avviene con una richiesta AJAX o POST, perché comporter Si può anche invocare la canonizzazione manualmente con il metodo `canonicalize()`, che, come il metodo `link()`, riceve come argomenti il presentatore, le azioni e i parametri. Crea un link e lo confronta con l'URL corrente. Se è diverso, reindirizza al link generato. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // reindirizza se $slug è diverso da $realSlug @@ -452,17 +452,6 @@ Limitazione dell'accesso tramite `#[Requires]` .[#toc-access-restriction-using-r L'attributo `#[Requires]` fornisce opzioni avanzate per limitare l'accesso ai presentatori e ai loro metodi. Può essere usato per specificare metodi HTTP, richiedere richieste AJAX, limitare l'accesso alla stessa origine e limitare l'accesso al solo inoltro. L'attributo può essere applicato alle classi di presentatori e ai singoli metodi, come ad esempio `action()`, `render()`, `handle()`, e `createComponent()`. -Ecco un esempio di utilizzo per limitare l'accesso al solo metodo HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - È possibile specificare queste restrizioni: - sui metodi HTTP: `#[Requires(methods: ['GET', 'POST'])]` - che richiedono una richiesta AJAX: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ class MyPresenter extends Nette\Application\UI\Presenter - accesso solo tramite inoltro: `#[Requires(forward: true)]` - restrizioni su azioni specifiche: `#[Requires(actions: 'default')]` -Le condizioni possono essere combinate elencando più attributi o unendoli in uno solo: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Per i dettagli, vedere [Come usare l'attributo Requires |best-practices:attribute-requires]. Controllo del metodo HTTP .[#toc-http-method-check] diff --git a/application/it/routing.texy b/application/it/routing.texy index c01d4ac823..f938a3e902 100644 --- a/application/it/routing.texy +++ b/application/it/routing.texy @@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +225,7 @@ $router->addRoute('/[/]', [ ]); ``` -Oppure possiamo usare questa forma, notando la riscrittura dell'espressione regolare di validazione: +Per una specifica più dettagliata, si può usare una forma ancora più estesa, in cui oltre ai valori predefiniti si possono impostare altre proprietà dei parametri, come un'espressione regolare di validazione (vedere il parametro `id` ): ```php use Nette\Routing\Route; @@ -243,7 +243,7 @@ $router->addRoute('/[/]', [ ]); ``` -Questi formati più loquaci sono utili per aggiungere altri metadati. +È importante notare che se i parametri definiti nell'array non sono inclusi nella maschera del percorso, i loro valori non possono essere modificati, nemmeno utilizzando i parametri di query specificati dopo un punto interrogativo nell'URL. Filtri e traduzioni .[#toc-filters-and-translations] @@ -477,10 +477,10 @@ $router->addRoute('index', /* ... */); Integrazione .[#toc-integration] ================================ -Per collegare il nostro router all'applicazione, dobbiamo comunicarlo al contenitore DI. Il modo più semplice è preparare il factory che costruirà l'oggetto router e dire al contenitore di configurazione di usarlo. Diciamo quindi di scrivere un metodo a questo scopo `App\Router\RouterFactory::createRouter()`: +Per collegare il nostro router all'applicazione, dobbiamo comunicarlo al contenitore DI. Il modo più semplice è preparare il factory che costruirà l'oggetto router e dire al contenitore di configurazione di usarlo. Diciamo quindi di scrivere un metodo a questo scopo `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -499,7 +499,7 @@ Poi scriviamo nella [configurazione |dependency-injection:services]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Tutte le dipendenze, come la connessione al database, ecc. vengono passate al metodo factory come parametri, utilizzando l'[autowiring |dependency-injection:autowiring]: @@ -663,7 +663,7 @@ Per uso separato si intende l'uso delle funzionalità del router in un'applicazi Quindi creeremo di nuovo un metodo che costruirà un router, ad esempio: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Oppure creeremo direttamente gli oggetti: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/it/templates.texy b/application/it/templates.texy index bb704a88f2..55e31fa8b9 100644 --- a/application/it/templates.texy +++ b/application/it/templates.texy @@ -34,35 +34,81 @@ E questo potrebbe essere il modello di azione: Definisce il blocco `content`, che viene inserito al posto di `{include content}` nel layout, e ridefinisce anche il blocco `title`, che sovrascrive `{block title}` nel layout. Provate a immaginare il risultato. -Ricerca dei modelli .[#toc-search-for-templates] ------------------------------------------------- +Ricerca di modelli .[#toc-template-lookup] +------------------------------------------ + +Nei presentatori, non è necessario specificare quale template debba essere reso; il framework determinerà automaticamente il percorso, semplificando la codifica. + +Se si utilizza una struttura di cartelle in cui ogni presentatore ha una propria cartella, è sufficiente posizionare il template in questa cartella sotto il nome dell'azione (cioè della vista). Ad esempio, per l'azione `default`, utilizzare il modello `default.latte`: -Il percorso dei modelli viene dedotto secondo una semplice logica. Si cerca di vedere se uno di questi file di template esiste relativamente alla directory in cui si trova la classe del presentatore, dove `` è il nome del presentatore corrente e `` è il nome dell'azione corrente: +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -- `templates//.latte` -- `templates/..latte` +Se si utilizza una struttura in cui i presentatori sono riuniti in una directory e i modelli in una cartella `templates`, salvare il tutto in un file `..latte` oppure `/.latte`: -Se il modello non viene trovato, si cercherà nella cartella `templates` a un livello superiore, cioè allo stesso livello della cartella con la classe del presentatore. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- -Se il modello non viene trovato nemmeno lì, la risposta è un [errore 404 |presenters#Error 404 etc.]. +La directory `templates` può anche essere collocata un livello più in alto, allo stesso livello della directory con le classi dei presentatori. -Si può anche cambiare la vista usando `$this->setView('otherView')`. Oppure, invece di cercare, specificare direttamente il nome del file del template usando `$this->template->setFile('/path/to/template.latte')`. +Se il modello non viene trovato, il presentatore risponde con un [errore 404 - pagina non trovata |presenters#Error 404 etc]. + +È possibile modificare la vista utilizzando `$this->setView('anotherView')`. È anche possibile specificare direttamente il file del modello con `$this->template->setFile('/path/to/template.latte')`. .[note] -È possibile modificare i percorsi in cui vengono cercati i modelli sovrascrivendo il metodo [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], che restituisce un array di possibili percorsi di file. +I file in cui vengono cercati i modelli possono essere modificati sovrascrivendo il metodo [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], che restituisce un array di possibili nomi di file. + + +Ricerca dei modelli di layout .[#toc-layout-template-lookup] +------------------------------------------------------------ + +Nette cerca automaticamente anche il file di layout. + +Se si utilizza una struttura di directory in cui ogni presentatore ha una propria directory, collocare il layout nella cartella del presentatore, se è specifico solo per lui, oppure a un livello superiore se è comune a più presentatori: + +/--pre +app/ +└── UI/ + ├── @layout.latte ← common layout + └── Home/ + ├── @layout.latte ← only for Home presenter + ├── HomePresenter.php + └── default.latte +\-- + +Se si utilizza una struttura in cui i presentatori sono raggruppati in una directory e i modelli si trovano in una cartella `templates`, il layout sarà previsto nei seguenti punti: -Il layout è previsto nei seguenti file: +/--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 comune a più presentatori +Se il presentatore si trova in un [modulo |modules], cercherà anche più in alto nell'albero della directory, in base alla nidificazione del modulo. -`` è il nome del presentatore corrente e `` è il nome del layout, che per impostazione predefinita è `'layout'`. Il nome può essere modificato con `$this->setLayout('otherLayout')`, in modo da provare i file `@otherLayout.latte`. +Il nome del layout può essere modificato con `$this->setLayout('layoutAdmin')` e sarà previsto nel file `@layoutAdmin.latte`. È anche possibile specificare direttamente il file del modello di layout usando `$this->setLayout('/path/to/template.latte')`. -È anche possibile specificare direttamente il nome del file del modello di layout con `$this->setLayout('/path/to/template.latte')`. L'uso di `$this->setLayout(false)` disabilita la ricerca dei layout. +L'uso di `$this->setLayout(false)` o del tag `{layout none}` all'interno del template disabilita la ricerca del layout. .[note] -È possibile modificare i percorsi in cui vengono cercati i modelli sovrascrivendo il metodo [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], che restituisce un array di possibili percorsi di file. +I file in cui vengono cercati i modelli di layout possono essere modificati sovrascrivendo il metodo [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], che restituisce un array di possibili nomi di file. Variabili nel modello .[#toc-variables-in-the-template] @@ -104,7 +150,7 @@ L'annotazione `@property-read` è per l'IDE e l'analisi statica, farà funzionar Ci si può concedere il lusso di sussurrare anche nei template, basta installare il plugin Latte in PhpStorm e specificare il nome della classe all'inizio del template, si veda l'articolo "Latte: come digitare il sistema":https://blog.nette.org/it/latte-come-usare-il-sistema-di-tipi: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void La versione 3 di Latte offre un metodo più avanzato, creando un'[estensione |latte:creating-extension] per ogni progetto web. Ecco un esempio approssimativo di una classe di questo tipo: ```php -namespace App\Templating; +namespace App\UI\Accessory; final class LatteExtension extends Latte\Extension { @@ -214,7 +260,7 @@ La registriamo usando [configuration |configuration#Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\UI\Accessory\LatteExtension ``` @@ -239,7 +285,7 @@ In alternativa, il traduttore può essere impostato utilizzando la [configurazio ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` Il traduttore può essere utilizzato, ad esempio, come filtro `|translate`, con parametri aggiuntivi passati al metodo `translate()` (vedere `foo, bar`): diff --git a/application/pl/ajax.texy b/application/pl/ajax.texy index e38121342e..9a6f09bd03 100644 --- a/application/pl/ajax.texy +++ b/application/pl/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Najpierw należy [zainicjować |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] bibliotekę: + +```js +naja.initialize(); +``` + Aby uczynić zwykły link (sygnał) lub przesłanie formularza żądaniem AJAX, wystarczy oznaczyć odpowiedni link, formularz lub przycisk klasą `ajax`: ```html diff --git a/application/pl/bootstrap.texy b/application/pl/bootstrap.texy index 4b23e1f658..084d93b416 100644 --- a/application/pl/bootstrap.texy +++ b/application/pl/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__); + // Konfigurator jest odpowiedzialny za konfigurację środowiska aplikacji i usług. + $this->configurator = new Configurator; + // Ustawienie katalogu dla plików tymczasowych generowanych przez Nette (np. skompilowanych szablonów). + $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 jest inteligentny i tryb deweloperski włącza się automatycznie, + // lub można go włączyć dla określonego adresu IP, odkomentowując następującą linię: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Włącza Tracy: najlepsze narzędzie do debugowania. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: automatycznie ładuje wszystkie klasy w podanym katalogu + $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; + } + + private function setupContainer(): void + { + // Ładowanie plików konfiguracyjnych + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } ``` @@ -40,16 +66,15 @@ class Bootstrap index.php .[#toc-index-php] =========================== -Podstawowym plikiem w przypadku aplikacji internetowych jest `index.php`, który znajduje się w katalogu publicznym `www/`. Spowoduje to, że klasa Bootstrap zainicjuje środowisko i zwróci `$configurator`, a następnie wyprodukuje kontener DI. Następnie pobiera z niego usługę `Application`, która uruchamia aplikację internetową: +Początkowym plikiem dla aplikacji internetowych jest `index.php`, znajdujący się w publicznym katalogu `www/`. Używa on klasy `Bootstrap` do zainicjowania środowiska i utworzenia kontenera DI. Następnie uzyskuje usługę `Application` z kontenera, który uruchamia aplikację internetową: ```php -// inicjalizacja środowiska + uzyskanie obiektu Configurator -$configurator = App\Bootstrap::boot(); -// tworzenie kontenera DI -$container = $configurator->createContainer(); -// Kontener DI tworzy obiekt "Nette +$bootstrap = new App\Bootstrap; +// Inicjalizacja środowiska + utworzenie kontenera DI +$container = $bootstrap->bootWebApplication(); +// Kontener DI tworzy obiekt Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// uruchomienie aplikacji Nette +// Uruchom aplikację Nette i obsłuż przychodzące żądanie $application->run(); ``` @@ -66,19 +91,19 @@ Wybór trybu odbywa się poprzez autodetekcję, więc zazwyczaj nie ma potrzeby Jeśli chcemy włączyć tryb deweloperski w innych przypadkach, takich jak programiści uzyskujący dostęp z określonego adresu IP, używamy `setDebugMode()`: ```php -$configurator->setDebugMode('23.75.345.200'); // można również określić pole adresu IP +$this->configurator->setDebugMode('23.75.345.200'); // można również określić pole adresu IP ``` Zdecydowanie zalecamy połączenie adresu IP z plikiem cookie. W pliku cookie `nette-debug` przechowujemy tajny token, np. `secret1234`, i w ten sposób umożliwiamy tryb deweloperski dla programistów uzyskujących dostęp z określonego adresu IP i posiadających token w pliku cookie: ```php -$configurator->setDebugMode('secret1234@23.75.345.200'); +$this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` Możemy również całkowicie wyłączyć tryb deweloperski, nawet dla localhost: ```php -$configurator->setDebugMode(false); +$this->configurator->setDebugMode(false); ``` Uwaga, wartość `true` domyślnie włącza tryb deweloperski, co nigdy nie może mieć miejsca na serwerze produkcyjnym. @@ -90,7 +115,7 @@ Narzędzie do debugowania Tracy .[#toc-debugging-tool-tracy] Aby ułatwić debugowanie, włączmy wspaniałe narzędzie [Tracy |tracy:]. Wizualizuje błędy w trybie deweloperskim i loguje błędy w trybie produkcyjnym do podanego katalogu: ```php -$configurator->enableTracy($appDir . '/log'); +$this->configurator->enableTracy($this->rootDir . '/log'); ``` @@ -100,7 +125,7 @@ Pliki tymczasowe .[#toc-temporary-files] Nette używa buforowania dla kontenera DI, RobotLoader, szablonów itp. Dlatego musisz ustawić ścieżkę do katalogu, w którym będzie przechowywany cache: ```php -$configurator->setTempDirectory($appDir . '/temp'); +$this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` W systemach Linux lub macOS ustaw katalogi `log/` i `temp/` na uprawnienia do [zapisu |nette:troubleshooting#Setting-Directory-Permissions]. @@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader] Zazwyczaj będziemy chcieli automatycznie załadować klasy za pomocą [RobotLoader |robot-loader:], więc musimy go uruchomić i kazać mu załadować klasy z katalogu, w którym znajduje się `Bootstrap.php` (czyli `__DIR__`), oraz z wszelkich podkatalogów: ```php -$configurator->createRobotLoader() +$this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); ``` @@ -126,7 +151,7 @@ Strefa czasowa .[#toc-timezone] Domyślną strefę czasową można ustawić za pośrednictwem konfiguratora. ```php -$configurator->setTimeZone('Europe/Prague'); +$this->configurator->setTimeZone('Europe/Prague'); ``` @@ -143,16 +168,17 @@ W trybie deweloperskim kontener jest automatycznie aktualizowany przy każdej zm Pliki konfiguracyjne są ładowane za pomocą `addConfig()`: ```php -$configurator->addConfig($appDir . '/config/common.neon'); +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` Jeśli chcemy dodać więcej plików konfiguracyjnych, możemy wywołać funkcję `addConfig()` wielokrotnie. ```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/services.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 @@ Parametry statyczne .[#toc-static-parameters] Parametry wykorzystywane w plikach konfiguracyjnych można zdefiniować [w sekcji `parameters` |dependency-injection:configuration#parameters], a także przekazać (lub nadpisać) za pomocą metody `addStaticParameters()` (posiada ona alias `addParameters()`). Co ważne, różne wartości parametrów spowodują wygenerowanie dodatkowych kontenerów DI, czyli dodatkowych klas. ```php -$configurator->addStaticParameters([ +$this->configurator->addStaticParameters([ 'projectId' => 23, ]); ``` @@ -183,7 +209,7 @@ Parametry dynamiczne .[#toc-dynamic-parameters] Do kontenera możemy również dodać parametry dynamiczne, których różne wartości, w przeciwieństwie do parametrów statycznych, nie będą powodowały generowania nowych kontenerów DI. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'remoteIp' => $_SERVER['REMOTE_ADDR'], ]); ``` @@ -191,7 +217,7 @@ $configurator->addDynamicParameters([ Możemy po prostu dodać np. zmienne środowiskowe, do których następnie możemy się odwołać w konfiguracji pisząc `%env.variable%`. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'env' => getenv(), ]); ``` @@ -206,6 +232,7 @@ W plikach konfiguracyjnych można używać następujących parametrów statyczny - `%wwwDir%` jest bezwzględną ścieżką do katalogu zawierającego plik wejściowy `index.php` - `%tempDir%` jest bezwzględną ścieżką do katalogu plików tymczasowych - `%vendorDir%` to bezwzględna ścieżka do katalogu, w którym Composer instaluje biblioteki +- `%rootDir%` to bezwzględna ścieżka do katalogu głównego projektu - `%debugMode%` wskazuje, czy aplikacja jest w trybie debugowania - `%consoleMode%` wskazuje, czy żądanie przyszło z linii poleceń @@ -225,7 +252,7 @@ services: A w bootstrapie wstawiamy obiekt do kontenera: ```php -$configurator->addServices([ +$this->configurator->addServices([ 'myservice' => new App\Model\MyCustomService('foobar'), ]); ``` @@ -234,13 +261,21 @@ $configurator->addServices([ Różne środowiska .[#toc-different-environments] =============================================== -Zapraszamy do dostosowania klasy Bootstrap do swoich potrzeb. Możesz dodać parametry do metody `boot()`, aby odróżnić projekty internetowe, lub dodać inne metody, takie jak `bootForTests()`, która inicjalizuje środowisko dla testów jednostkowych, `bootForCli()` dla skryptów wywoływanych z linii poleceń itp. +Nie wahaj się dostosować klasy `Bootstrap` do swoich potrzeb. Możesz dodać parametry do metody `bootWebApplication()`, aby rozróżnić projekty internetowe. Alternatywnie można dodać inne metody, takie jak `bootTestEnvironment()` do inicjalizacji środowiska dla testów jednostkowych, `bootConsoleApplication()` dla skryptów wywoływanych z wiersza poleceń itp. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Inicjalizacja testera sieci + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container { - $configurator = self::boot(); - Tester\Environment::setup(); // inicializace Nette Testeru - return $configurator; + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); } ``` diff --git a/application/pl/components.texy b/application/pl/components.texy index 740e5f919e..21cbd673e9 100644 --- a/application/pl/components.texy +++ b/application/pl/components.texy @@ -230,6 +230,28 @@ Do szablonu wiadomości te są dostępne w zmiennej `$flashes` jako obiekty `std ``` +Przekierowanie po sygnale .[#toc-redirection-after-a-signal] +============================================================ + +Po przetworzeniu sygnału komponentu często następuje przekierowanie. Sytuacja ta jest podobna do formularzy - po przesłaniu formularza również przekierowujemy, aby zapobiec ponownemu przesłaniu danych po odświeżeniu strony w przeglądarce. + +```php +$this->redirect('this') // redirects to the current presenter and action +``` + +Ponieważ komponent jest elementem wielokrotnego użytku i zwykle nie powinien mieć bezpośredniej zależności od konkretnych prezenterów, metody `redirect()` i `link()` automatycznie interpretują parametr jako sygnał komponentu: + +```php +$this->redirect('click') // redirects to the 'click' signal of the same component +``` + +Jeśli konieczne jest przekierowanie do innego prezentera lub akcji, można to zrobić za pośrednictwem prezentera: + +```php +$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action +``` + + Trwałe parametry .[#toc-persistent-parameters] ============================================== @@ -430,7 +452,7 @@ class PaginatingControl extends Control } ``` -Procesem przeciwnym, czyli pobieraniem wartości z persistent properites, zajmuje się metoda `saveState()`. +Procesem przeciwnym, czyli pobieraniem wartości z persistent properties, zajmuje się metoda `saveState()`. Sygnały w głąb .[#toc-signaly-do-hloubky] diff --git a/application/pl/configuration.texy b/application/pl/configuration.texy index 7986b64e24..d59b60bb8e 100644 --- a/application/pl/configuration.texy +++ b/application/pl/configuration.texy @@ -95,6 +95,9 @@ latte: # włącza [sprawdzanie wygenerowanego kodu |latte:develop#Checking Generated Code] phpLinter: ... # (string) domyślnie null + # ustawia ustawienia regionalne + locale: cs_CZ # (string) domyślnie null + # klasa obiektów $this->template templateClass: AppMyTemplateClass # domyślnie jest Nette\Bridges\ApplicationLatte\DefaultTemplate ``` @@ -104,7 +107,7 @@ Jeśli używasz Latte w wersji 3, możesz dodać nowe [rozszerzenia |latte:creat ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/pl/how-it-works.texy b/application/pl/how-it-works.texy index 9cbbb61f69..a2b0c8b57c 100644 --- a/application/pl/how-it-works.texy +++ b/application/pl/how-it-works.texy @@ -22,13 +22,13 @@ Struktura katalogów wygląda mniej więcej tak: /--pre web-project/ ├── app/ ← adresář s aplikací -│ ├── Presenters/ ← presentery a šablony -│ │ ├── HomePresenter.php ← třída presenteru Home -│ │ └── templates/ ← adresář se šablonami -│ │ ├── @layout.latte ← šablona layoutu -│ │ └── Home/ ← šablony presenteru Home -│ │ └── default.latte ← šablona akce 'default' -│ ├── Router/ ← konfigurace URL adres +│ ├── Core/ ← podstawowe niezbędne klasy +│ │ └── RouterFactory.php ← konfiguracja adresów URL +│ ├── UI/ ← prezentery, szablony & co. +│ │ ├── @layout.latte ← szablon udostępnionego layoutu +│ │ └── Home/ ← Katalog główny prezentera +│ │ ├── HomePresenter.php ← Klasa prezentera strony głównej +│ │ └── default.latte ← szablon dla akcji default │ └── Bootstrap.php ← zaváděcí třída Bootstrap ├── bin/ ← skripty spouštěné z příkazové řádky ├── config/ ← konfigurační soubory @@ -91,7 +91,7 @@ Aplikacje napisane w Nette są podzielone na wiele prezenterów (w innych framew Aplikacja rozpoczyna się od zapytania tzw. routera o decyzję, do którego prezentera przekazać bieżące żądanie do przetworzenia. Router decyduje o tym, czyja to odpowiedzialność. Patrzy na wejściowy URL `https://example.com/product/123`, którego poprosi o wyświetlenie (`show`) produktu z `id: 123` jako **akcji** . Dobrą praktyką jest zapisanie pary prezenter + akcja oddzielonej dwukropkiem jako `Product:show`. -W ten sposób router przekształca adres URL w parę `Presenter:action` + parametry, w naszym przypadku `Product:show` + `id: 123`. Jak wygląda taki router, można zobaczyć w pliku `app/Router/RouterFactory.php`, a szczegółowo opisujemy go w rozdziale [Routing |Routing]. +W ten sposób router przekształca adres URL w parę `Presenter:action` + parametry, w naszym przypadku `Product:show` + `id: 123`. Jak wygląda taki router, można zobaczyć w pliku `app/Core/RouterFactory.php`, a szczegółowo opisujemy go w rozdziale [Routing |Routing]. Ruszajmy dalej. Aplikacja zna teraz nazwę prezentera i może przejść dalej. Produkując obiekt klasy `ProductPresenter`, który jest kodem prezentera `Product`. Dokładniej, prosi kontener DI o wyprodukowanie prezentera, ponieważ jest tam, aby go wyprodukować. @@ -121,12 +121,9 @@ Wywołana jest więc metoda `renderShow(123)`, której kod jest fikcyjnym przyk Następnie prezenter zwraca odpowiedź. Może to być strona HTML, obraz, dokument XML, wysłanie pliku z dysku, JSON, a nawet przekierowanie na inną stronę. Co ważne, o ile nie powiemy wprost prezenterowi, jak ma odpowiedzieć (co ma miejsce w przypadku `ProductPresenter`), odpowiedzią będzie renderowanie szablonu ze stroną HTML. Dlaczego? Ponieważ w 99% przypadków chcemy renderować szablon, więc prezenter przyjmuje to zachowanie jako domyślne i chce ułatwić nam pracę. O to właśnie chodzi w Nette. -Nie musimy nawet określać, jaki szablon ma być renderowany, sam wywnioskuje ścieżkę do niego za pomocą prostej logiki. W przypadku prezentera `Product` i akcji `show`, spróbuje sprawdzić, czy istnieje jeden z tych plików szablonów przechowywanych względnie z katalogu klasy `ProductPresenter`: +Nie musimy nawet określać, który szablon ma być renderowany; framework sam wydedukuje ścieżkę. W przypadku akcji `show`, po prostu próbuje załadować szablon `show.latte` w katalogu z klasą `ProductPresenter`. Próbuje również znaleźć układ w pliku `@layout.latte` (więcej o [wyszukiwaniu szablonów |templates#Template Lookup]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Spróbuje również prześledzić układ w pliku `@layout.latte`, a następnie wyrenderować szablon. W ten sposób kończy się zadanie prezentera i aplikacji, a praca zostaje zakończona. Jeśli szablon nie istnieje, zwrócona zostanie strona błędu 404. Więcej o prezenterach można przeczytać na stronie [Prezenterzy |presenters]. +Następnie szablony są renderowane. To kończy zadanie prezentera i całej aplikacji, a praca jest wykonywana. Jeśli szablon nie istnieje, zostanie zwrócona strona błędu 404. Więcej informacji na temat prezenterów można znaleźć na stronie [Prezenterzy |presenters]. [* request-flow.svg *] @@ -137,7 +134,7 @@ Aby być bezpiecznym, spróbujmy podsumować cały proces z nieco innym adresem 3) router dekoduje adres URL jako parę `Home:default` 4) tworzony jest obiekt klasy `HomePresenter` 5) wywoływana jest metoda `renderDefault()` (jeśli istnieje) -6) wyrenderować szablon np. `templates/Home/default.latte` z układem np. `templates/@layout.latte` +6) wyrenderować szablon np. `default.latte` z układem np. `@layout.latte` Teraz być może spotkałeś się z wieloma nowymi pojęciami, ale wierzymy, że mają one sens. Tworzenie aplikacji w Nette to ogromna bułka z masłem. diff --git a/application/pl/modules.texy b/application/pl/modules.texy index 488d8f50a3..ecca26c3dc 100644 --- a/application/pl/modules.texy +++ b/application/pl/modules.texy @@ -2,29 +2,31 @@ Moduły ****** .[perex] -W Nette moduły reprezentują logiczne jednostki, które tworzą aplikację. Należą do nich prezentery, szablony, ewentualnie komponenty i klasy modeli. +Moduły zapewniają przejrzystość aplikacji Nette, ułatwiając podział na logiczne jednostki. -Jeden komponent dla prezenterów i jeden dla szablonów nie wystarczyłby dla prawdziwych projektów. Posiadanie kilkudziesięciu plików w jednym folderze jest co najmniej niezorganizowane. Jak się z niego wydostać? Po prostu dzielimy je na podkatalogi na dysku i na przestrzenie nazw w kodzie. I właśnie to robią moduły Nette. - -Zapomnijmy więc o jednym folderze dla prezenterów i szablonów, a zamiast tego stwórzmy moduły, na przykład `Admin` i `Front`. +Podobnie jak w przypadku organizowania plików w foldery na dysku twardym, w Nette możemy podzielić prezenterów, szablony i inne klasy pomocnicze na moduły. Jak to działa w praktyce? Po prostu poprzez włączenie nowych podkatalogów do struktury. Oto przykład struktury z dwoma modułami, Front i Admin: /--pre -app/ -├── Presenters/ -├── Modules/ ← adresář s moduly -│ ├── Admin/ ← modul Admin -│ │ ├── Presenters/ ← jeho presentery -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← modul Front -│ └── Presenters/ ← jeho presentery -│ └── ... +app/ +├── UI/ +│ ├── Admin/ ← Admin module +│ │ ├── @layout.latte +│ │ ├── Dashboard/ +│ │ │ ├── DashboardPresenter.php +│ │ │ └── default.latte +│ │ └── ... +│ ├── Front/ ← Front module +│ │ ├── @layout.latte +│ │ ├── Home/ +│ │ │ ├── HomePresenter.php +│ │ │ └── default.latte +│ │ └── ... \-- -Ta struktura katalogów będzie odzwierciedlona w przestrzeni nazw klas, więc na przykład `DashboardPresenter` będzie w przestrzeni `App\Modules\Admin\Presenters`: +Ta struktura katalogów jest odzwierciedlona w przestrzeniach nazw klas, więc na przykład `DashboardPresenter` znajduje się w przestrzeni nazw `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 } ``` -Prezenter `Dashboard` wewnątrz modułu `Admin` jest określany w aplikacji za pomocą notacji z podwójną kropką jako `Admin:Dashboard`, a jego działanie `default` jest określane jako `Admin:Dashboard:default`. -A skąd Nette own wie, że `Admin:Dashboard` reprezentuje klasę `App\Modules\Admin\Presenters\DashboardPresenter`? Mówimy to za pomocą [mapowania |#Mappings] w konfiguracji. -Tak więc podana struktura nie jest stała i można ją modyfikować według potrzeb. +W aplikacji odnosimy się do prezentera `Dashboard` w module `Admin` używając notacji dwukropka jako `Admin:Dashboard`. W przypadku akcji `default` odnosimy się do niej jako `Admin:Dashboard:default`. -Moduły mogą oczywiście zawierać wszystkie inne części oprócz prezenterów i szablonów, jak komponenty, klasy modeli itp. +Przedstawiona struktura nie jest sztywna; można [ją w pełni dostosować do własnych potrzeb |#mapping] w konfiguracji. .[tip] + +Moduły mogą zawierać wszystkie inne pliki, takie jak komponenty i klasy pomocnicze, oprócz prezenterów i szablonów. Jeśli zastanawiasz się, gdzie je umieścić, rozważ użycie folderu `Accessory`: + +/--pre +app/ +├── UI/ +│ ├── Admin/ +│ │ ├── Accessory/ +│ │ │ ├── FormFactory.php +│ │ │ └── AdminLayout.php +│ │ ├── Dashboard/ +│ │ └── ... +\-- Moduły zagnieżdżone .[#toc-nested-modules] ------------------------------------------ -Moduły nie muszą tworzyć tylko płaskiej struktury, można też tworzyć np. submoduły: +Moduły mogą mieć wiele poziomów zagnieżdżenia, podobnie jak struktura katalogów na dysku: /--pre -app/ -├── Modules/ ← adresář s moduly -│ ├── 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 │ │ └── ... \-- -Tak więc moduł `Blog` jest podzielony na podmoduły `Admin` i `Front`. I znowu będzie to odzwierciedlone w przestrzeni nazw, która będzie `App\Modules\Blog\Admin\Presenters` itd. Prezenter `Dashboard` wewnątrz submodułu jest określany jako `Blog:Admin:Dashboard`. +Moduł `Blog` jest podzielony na podmoduły `Admin` i `Front`. Jest to również odzwierciedlone w przestrzeniach nazw, które następnie pojawiają się jako `App\UI\Blog\Admin` i podobnie. Aby odnieść się do prezentera `Dashboard` w podmodule `Admin`, odnosimy się do niego jako `Blog:Admin:Dashboard`. -Rozgałęzienie może iść tak głęboko, jak chcesz, więc możesz tworzyć podmoduły. +Zagnieżdżanie może być tak głębokie, jak potrzeba, umożliwiając tworzenie podmodułów. + +Na przykład, jeśli w administracji masz wiele prezenterów związanych z zarządzaniem zamówieniami, takich jak `OrderDetail`, `OrderEdit`, `OrderDispatch`, itp., możesz utworzyć moduł `Order`, w którym będą zorganizowane prezentery takie jak `Detail`, `Edit`, `Dispatch`, i inne. Tworzenie linków .[#toc-creating-links] @@ -102,46 +118,66 @@ Patrz [rozdział dotyczący routingu |routing#Modules]. Mapowanie .[#toc-mapping] ------------------------- -Określa zasady, według których nazwa klasy jest wyprowadzana z nazwy prezentera. Zapisujemy je w [konfiguracji |configuration] pod kluczem `application › mapping`. +Mapowanie definiuje zasady wyprowadzania nazwy klasy z nazwy prezentera. Reguły te są określone w [konfiguracji |configuration] pod kluczem `application › mapping`. + +Struktury katalogów wspomniane wcześniej na tej stronie są oparte na następującym mapowaniu: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Zacznijmy od próbki, która nie korzysta z modułów. Będziemy chcieli, aby klasy prezentera miały przestrzeń nazw `App\Presenters`. To znaczy, będziemy chcieli, aby prezenter, na przykład, `Home` mapował do klasy `App\Presenters\HomePresenter`. Można to osiągnąć dzięki następującej konfiguracji: +Jak działa mapowanie? Dla lepszego zrozumienia, wyobraźmy sobie najpierw aplikację bez modułów. Chcemy, aby klasy prezenterów należały do przestrzeni nazw `App\UI`, tak aby prezenter `Home` był mapowany na klasę `App\UI\HomePresenter`. Można to osiągnąć za pomocą tej konfiguracji: ```neon application: - mapping: App\Presenters\*Presenter + mapping: App\UI\*Presenter ``` -Zastąp nazwę prezentera gwiazdką w masce klasy, a wynikiem będzie nazwa klasy. Spokojnie! +Mapowanie to polega na zastąpieniu gwiazdki w masce `App\UI\*Presenter` nazwą prezentera `Home`, co daje końcową nazwę klasy `App\UI\HomePresenter`. Proste! + +Jednakże, jak widać w przykładach w tym i innych rozdziałach, umieszczamy klasy prezenterów w podkatalogach o tej samej nazwie, np. prezenter `Home` jest mapowany na klasę `App\UI\Home\HomePresenter`. Osiąga się to poprzez podwojenie gwiazdki (wymaga Nette Application 3.2): + +```neon +application: + mapping: App\UI\**Presenter +``` -Jeśli złamiemy prezenterów na moduły, możemy mieć niestandardowe mapowanie dla każdego modułu: +Przejdźmy teraz do mapowania prezenterów na moduły. Możemy zdefiniować konkretne mapowania dla każdego modułu: ```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 ``` -Teraz prezenter `Front:Home` mapuje do klasy `App\Modules\Front\Presenters\HomePresenter`, a prezenter `Admin:Dashboard` mapuje do klasy `App\Modules\Admin\Presenters\DashboardPresenter`. +Zgodnie z tą konfiguracją, prezenter `Front:Home` mapuje się do klasy `App\UI\Front\Home\HomePresenter`, podczas gdy prezenter `Api:OAuth` mapuje się do klasy `App\Api\OAuthPresenter`. -Bardziej praktyczne będzie stworzenie ogólnej (gwiazdkowej) reguły, która zastąpi pierwsze dwie. W masce klasy zostanie dodana dodatkowa gwiazdka tylko dla tego modułu: +Ponieważ moduły `Front` i `Admin` mają podobne podejście do mapowania i prawdopodobnie będzie więcej takich modułów, możliwe jest utworzenie ogólnej reguły, która je zastąpi. Nowa gwiazdka dla modułu jest dodawana do maski klasy: ```neon application: mapping: - *: App\Modules\*\Presenters\*Presenter + *: App\UI\*\**Presenter Api: App\Api\*Presenter ``` -Ale co w przypadku, gdy korzystamy z wielu zagnieżdżonych modułów i mamy np. prezentera `Admin:User:Edit`? W takim przypadku segment z gwiazdką reprezentujący moduł dla każdego poziomu zostanie po prostu powtórzony, a wynikiem będzie klasa `App\Modules\Admin\User\Presenters\EditPresenter`. +W przypadku wielopoziomowych zagnieżdżonych modułów, takich jak prezenter `Admin:User:Edit`, segment gwiazdki powtarza się dla każdego poziomu, dając w rezultacie klasę `App\UI\Admin\User\Edit\EditPresenter`. -Alternatywną notacją jest użycie tablicy składającej się z trzech segmentów zamiast ciągu. Ta notacja jest równoważna z poprzednią: +Alternatywnym zapisem jest użycie tablicy złożonej z trzech segmentów zamiast łańcucha. Ta notacja jest równoważna poprzedniej: ```neon application: mapping: - *: [App\Modules, *, Presenters\*Presenter] + *: [App\UI, *, **Presenter] + Api: [App\Api, '', *Presenter] ``` -Wartość domyślna to `*Module\*Presenter`. +Jeśli mamy tylko jedną regułę w konfiguracji, tę ogólną, możemy napisać krótko: + +```neon +application: + mapping: App\UI\*\**Presenter +``` diff --git a/application/pl/presenters.texy b/application/pl/presenters.texy index 62d76b71a0..f35bddf004 100644 --- a/application/pl/presenters.texy +++ b/application/pl/presenters.texy @@ -60,7 +60,7 @@ Podobna metoda `render()`. Podczas gdy `render()` Metoda ma na celu Ważne jest to, że `action()` jest wywoływany przed `render()`więc możemy w nim potencjalnie zmienić dalszy bieg historii, czyli zmienić szablon do wylosowania, a także metodę `render()`który zostanie wywołany. Odbywa się to za pomocą strony `setView('jineView')`. -Do metody przekazywane są parametry z żądania. Możliwe i zalecane jest podawanie typów do parametrów, np. `actionShow(int $id, string $slug = null)` - jeśli w parametrze `id` zabraknie lub nie będzie on liczbą całkowitą, prezenter zwróci [błąd 404 |#Error-404-etc] i wyjdzie. +Do metody przekazywane są parametry z żądania. Możliwe i zalecane jest podawanie typów do parametrów, np. `actionShow(int $id, ?string $slug = null)` - jeśli w parametrze `id` zabraknie lub nie będzie on liczbą całkowitą, prezenter zwróci [błąd 404 |#Error-404-etc] i wyjdzie. `handle(args...)` .{toc: handle()} @@ -205,7 +205,7 @@ Wiadomości te są dostępne dla szablonu w zmiennej `$flashes` jako obiekty `st Error 404 i co. .[#toc-error-404-etc] ===================================== -Jeśli żądanie nie może zostać spełnione, na przykład dlatego, że artykuł, który chcemy wyświetlić, nie istnieje w bazie danych, rzucamy błąd 404 za pomocą metody `error(string $message = null, int $httpCode = 404)`. +Jeśli żądanie nie może zostać spełnione, na przykład dlatego, że artykuł, który chcemy wyświetlić, nie istnieje w bazie danych, rzucamy błąd 404 za pomocą metody `error(?string $message = null, int $httpCode = 404)`. ```php public function renderShow(int $id): void @@ -384,7 +384,7 @@ Przekierowanie nie nastąpi w przypadku żądań AJAX lub POST, ponieważ spowod Możesz również wywołać kanonizację ręcznie za pomocą metody `canonicalize()`, która przekaże prezentera, akcję i parametry podobnie jak w przypadku metody `link()`. Tworzy link i porównuje go z bieżącym adresem URL. Jeśli się różni, przekierowuje na wygenerowany 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); // přesměruje, pokud $slug se liší od $realSlug @@ -452,17 +452,6 @@ Ograniczenie dostępu przy użyciu `#[Requires]` .[#toc-access-restriction-using Atrybut `#[Requires]` zapewnia zaawansowane opcje ograniczania dostępu do prezenterów i ich metod. Można go użyć do określenia metod HTTP, wymagania żądań AJAX, ograniczenia dostępu do tego samego źródła i ograniczenia dostępu tylko do przekazywania. Atrybut może być stosowany do klas prezenterów, jak również poszczególnych metod, takich jak `action()`, `render()`, `handle()`, i `createComponent()`. -Oto przykład użycia go do ograniczenia dostępu tylko do metody HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Można określić te ograniczenia: - na metodach HTTP: `#[Requires(methods: ['GET', 'POST'])]` - wymagające żądania AJAX: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ Można określić te ograniczenia: - dostęp tylko przez przekierowanie: `#[Requires(forward: true)]` - ograniczenia dotyczące określonych działań: `#[Requires(actions: 'default')]` -Warunki można łączyć, wymieniając wiele atrybutów lub łącząc je w jeden: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Aby uzyskać szczegółowe informacje, zobacz [Jak używać atrybutu Requires atrybut |best-practices:attribute-requires]. Sprawdzanie metod HTTP .[#toc-http-method-check] diff --git a/application/pl/routing.texy b/application/pl/routing.texy index 4ce1af8644..ede7479898 100644 --- a/application/pl/routing.texy +++ b/application/pl/routing.texy @@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +225,7 @@ $router->addRoute('/[/]', [ ]); ``` -Możemy też użyć tego formularza, zauważając nadpisanie wyrażenia regularnego walidacji: +Aby uzyskać bardziej szczegółową specyfikację, można użyć jeszcze bardziej rozszerzonej formy, w której oprócz wartości domyślnych można ustawić inne właściwości parametru, takie jak wyrażenie regularne walidacji (patrz parametr `id` ): ```php use Nette\Routing\Route; @@ -243,7 +243,7 @@ $router->addRoute('/[/]', [ ]); ``` -Te bardziej verbose formaty są przydatne do dodawania dodatkowych metadanych. +Ważne jest, aby pamiętać, że jeśli parametry zdefiniowane w tablicy nie są zawarte w masce ścieżki, ich wartości nie mogą zostać zmienione, nawet przy użyciu parametrów zapytania określonych po znaku zapytania w adresie URL. Filtry i tłumaczenia .[#toc-filters-and-translations] @@ -477,10 +477,10 @@ $router->addRoute('index', /* ... */); Integracja aplikacji .[#toc-integration] ======================================== -Aby zintegrować stworzony router z aplikacją, musimy powiedzieć o nim kontenerowi DI. Najprostszym sposobem na to jest przygotowanie fabryki tworzącej obiekt routera i powiedzenie konfiguracji kontenera, aby go użyć. Załóżmy, że w tym celu napiszemy metodę `App\Router\RouterFactory::createRouter()`: +Aby zintegrować stworzony router z aplikacją, musimy powiedzieć o nim kontenerowi DI. Najprostszym sposobem na to jest przygotowanie fabryki tworzącej obiekt routera i powiedzenie konfiguracji kontenera, aby go użyć. Załóżmy, że w tym celu napiszemy metodę `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -499,7 +499,7 @@ Następnie wpisujemy do [konfiguracji |dependency-injection:services]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Wszelkie zależności, na przykład od bazy danych itp, są przekazywane do metody fabrycznej jako jej parametry przez [autowiring |dependency-injection:autowiring]: @@ -663,7 +663,7 @@ Przez użycie samodzielne rozumiemy wykorzystanie możliwości routera w aplikac Czyli znowu tworzymy metodę, która buduje dla nas np. router: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Możemy też wykonać obiekty bezpośrednio: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/pl/templates.texy b/application/pl/templates.texy index a7dd044914..51705f00d3 100644 --- a/application/pl/templates.texy +++ b/application/pl/templates.texy @@ -34,35 +34,81 @@ I to będzie szablon akcji: Definiuje blok `content`, który zostanie wstawiony w miejsce `{include content}` w układzie, a także redefiniuje blok `title`, który zastąpi `{block title}` w układzie. Spróbujcie sobie wyobrazić ten rezultat. -Wyszukiwanie szablonów .[#toc-search-for-templates] ---------------------------------------------------- +Wyszukiwanie szablonów .[#toc-template-lookup] +---------------------------------------------- -Ścieżka do szablonów jest wyprowadzana przez prezentera za pomocą prostej logiki. Spróbuje sprawdzić, czy jeden z tych plików znajduje się relatywnie od katalogu klasy prezentera, gdzie `` to nazwa aktualnego prezentera, a `` jest nazwą bieżącego zdarzenia: +W prezenterach nie trzeba określać, który szablon ma być renderowany; framework automatycznie określi ścieżkę, ułatwiając kodowanie. -- `templates//.latte` -- `templates/..latte` +Jeśli używasz struktury katalogów, w której każdy prezenter ma swój własny katalog, po prostu umieść szablon w tym katalogu pod nazwą akcji (tj. widoku). Na przykład dla akcji `default` należy użyć szablonu `default.latte`: -Jeśli szablon nie zostanie znaleziony, spróbuje poszukać w katalogu `templates` o jeden poziom wyżej, czyli na tym samym poziomie co katalog z klasą prezentera. +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Jeśli tam również nie zostanie znaleziony szablon, odpowiedzią będzie [błąd 404 |presenters#Error 404 etc.]. +Jeśli używasz struktury, w której prezenterzy znajdują się w jednym katalogu, a szablony w folderze `templates`, zapisz je w pliku `..latte` lub `/.latte`: -Widok można również zmienić za pomocą strony `$this->setView('jineView')`. Lub, zamiast szukać bezpośrednio, określ nazwę pliku szablonu za pomocą `$this->template->setFile('/path/to/template.latte')`. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- + +Katalog `templates` może być również umieszczony o jeden poziom wyżej, na tym samym poziomie co katalog z klasami prezenterów. + +Jeśli szablon nie zostanie znaleziony, prezenter odpowie [błędem 404 - nie znaleziono strony |presenters#Error 404 etc]. + +Widok można zmienić za pomocą `$this->setView('anotherView')`. Możliwe jest również bezpośrednie określenie pliku szablonu za pomocą `$this->template->setFile('/path/to/template.latte')`. .[note] -Pliki, w których wyszukiwane są szablony można zmienić nakładając na metodę [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], która zwraca tablicę możliwych nazw plików. +Pliki, w których wyszukiwane są szablony, można zmienić, nadpisując metodę [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], która zwraca tablicę możliwych nazw plików. + + +Wyszukiwanie szablonu układu .[#toc-layout-template-lookup] +----------------------------------------------------------- + +Nette automatycznie wyszukuje również plik układu. + +Jeśli używasz struktury katalogów, w której każdy prezenter ma swój własny katalog, umieść układ w folderze z prezenterem, jeśli jest on specyficzny tylko dla niego, lub poziom wyżej, jeśli jest wspólny dla wielu prezenterów: + +/--pre +app/ +└── UI/ + ├── @layout.latte ← common layout + └── Home/ + ├── @layout.latte ← only for Home presenter + ├── HomePresenter.php + └── default.latte +\-- + +Jeśli używasz struktury, w której prezenterzy są zgrupowani w jednym katalogu, a szablony znajdują się w folderze `templates`, layout będzie oczekiwany w następujących miejscach: -W tych plikach spodziewany jest układ: +/--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` układ wspólny dla wielu prezenterów +Jeśli prezenter znajduje się w [module |modules], będzie on również wyszukiwany dalej w drzewie katalogów zgodnie z zagnieżdżeniem modułu. -Gdzie `` to nazwa aktualnego prezentera, a `` jest nazwą układu, którą domyślnie jest `'layout'`. Nazwę można zmienić za pomocą `$this->setLayout('jinyLayout')`, więc wypróbowane zostaną pliki `@jinyLayout.latte`. +Nazwę layoutu można zmienić za pomocą `$this->setLayout('layoutAdmin')`, a następnie będzie ona oczekiwana w pliku `@layoutAdmin.latte`. Można również bezpośrednio określić plik szablonu układu za pomocą `$this->setLayout('/path/to/template.latte')`. -Możesz również bezpośrednio określić nazwę pliku szablonu układu, używając `$this->setLayout('/path/to/template.latte')`. Użycie `$this->setLayout(false)` wyłączy śledzenie układu. +Użycie `$this->setLayout(false)` lub znacznika `{layout none}` wewnątrz szablonu wyłącza wyszukiwanie układu. .[note] -Pliki, w których wyszukiwane są szablony układów można zmienić nakładając na metodę [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], która zwraca tablicę możliwych nazw plików. +Pliki, w których przeszukiwane są szablony układu, można zmienić, nadpisując metodę [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], która zwraca tablicę możliwych nazw plików. Zmienne w szablonie .[#toc-variables-in-the-template] @@ -104,7 +150,7 @@ Adnotacja `@property-read` jest dla IDE i analizy statycznej, sprawi, że szepta Możesz również mieć luksus szeptania w szablonach, wystarczy zainstalować wtyczkę Latte w PhpStorm i umieścić nazwę klasy na początku szablonu, zobacz artykuł "Latte: jak wpisać system":https://blog.nette.org/pl/latte-jak-korzystac-z-systemu-typow, aby uzyskać więcej informacji: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void Latte w wersji 3 oferuje bardziej zaawansowany sposób tworzenia [rozszerzenia |latte:creating-extension] dla każdego projektu internetowego. Oto krótki przykład takiej klasy: ```php -namespace App\Templating; +namespace App\UI\Accessory; final class LatteExtension extends Latte\Extension { @@ -214,7 +260,7 @@ Rejestrujemy go za pomocą [konfiguracji |configuration#Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\UI\Accessory\LatteExtension ``` @@ -239,7 +285,7 @@ Alternatywnie, tłumacz może być ustawiony za pomocą [konfiguracji |configura ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` Translator może być wtedy użyty np. jako filtr `|translate`, z dodatkowymi parametrami przekazywanymi do metody `translate()` (patrz `foo, bar`): diff --git a/application/pt/ajax.texy b/application/pt/ajax.texy index 7eace28064..c2159c60f4 100644 --- a/application/pt/ajax.texy +++ b/application/pt/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Primeiro, você precisa [inicializar |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] a biblioteca: + +```js +naja.initialize(); +``` + Para transformar um link comum (sinal) ou o envio de um formulário em uma solicitação AJAX, basta marcar o respectivo link, formulário ou botão com a classe `ajax`: ```html diff --git a/application/pt/bootstrap.texy b/application/pt/bootstrap.texy index 85dca58e88..1428498866 100644 --- a/application/pt/bootstrap.texy +++ b/application/pt/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__); + // O configurador é responsável pela configuração do ambiente e dos serviços do aplicativo. + $this->configurator = new Configurator; + // Defina o diretório para os arquivos temporários gerados pelo Nette (por exemplo, modelos compilados) + $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 + { + // O Nette é inteligente e o modo de desenvolvimento é ativado automaticamente, + // ou você pode ativá-lo para um endereço IP específico, descomentando a seguinte linha: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Habilita o Tracy: a melhor ferramenta de depuração do tipo "canivete suíço". + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: carrega automaticamente todas as classes no diretório fornecido + $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; + } + + private function setupContainer(): void + { + // Carregar arquivos de configuração + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } ``` @@ -40,16 +66,15 @@ class Bootstrap index.php .[#toc-index-php] =========================== -No caso de aplicações web, o arquivo inicial é `index.php`, que está localizado no diretório público `www/`. Ele permite que a classe `Bootstrap` inicialize o ambiente e devolva o `$configurator` que cria o contêiner DI. Em seguida, obtém o serviço `Application`, que executa a aplicação web: +O arquivo inicial para aplicativos Web é `index.php`, localizado no diretório público `www/`. Ele usa a classe `Bootstrap` para inicializar o ambiente e criar um contêiner DI. Em seguida, ele obtém o serviço `Application` do contêiner, que inicia o aplicativo Web: ```php -// inicializar o ambiente + obter objeto Configurador -$configurator = App\Bootstrap::boot(); -// criar um recipiente DI -$container = $configurator->createContainer(); -// Recipiente DI cria um objeto de aplicação Nette +$bootstrap = new App\Bootstrap; +// Inicializar o ambiente + criar um contêiner DI +$container = $bootstrap->bootWebApplication(); +// O contêiner DI cria um objeto Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// iniciar a aplicação Nette +// Iniciar o aplicativo Nette e tratar a solicitação de entrada $application->run(); ``` @@ -66,19 +91,19 @@ A seleção do modo é feita por autodetecção, de modo que normalmente não h Se você quiser ativar o modo de desenvolvimento em outros casos, por exemplo, para programadores que acessam de um endereço IP específico, você pode usar `setDebugMode()`: ```php -$configurator->setDebugMode('23.75.345.200'); // um ou mais endereços IP +$this->configurator->setDebugMode('23.75.345.200'); // um ou mais endereços IP ``` Definitivamente, recomendamos combinar um endereço IP com um cookie. Armazenaremos um token secreto no cookie `nette-debug`, por exemplo `secret1234`, e o modo de desenvolvimento será ativado para programadores com esta combinação de IP e cookie. ```php -$configurator->setDebugMode('secret1234@23.75.345.200'); +$this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` Também podemos desligar completamente o modo desenvolvedor, mesmo para o localhost: ```php -$configurator->setDebugMode(false); +$this->configurator->setDebugMode(false); ``` Note que o valor `true` liga o modo desenvolvedor por hard, o que nunca deveria acontecer em um servidor de produção. @@ -90,7 +115,7 @@ Ferramenta de depuração Tracy .[#toc-debugging-tool-tracy] Para facilitar a depuração, vamos acionar a grande ferramenta [Tracy |tracy:]. No modo desenvolvedor ela visualiza os erros e no modo de produção registra os erros no diretório especificado: ```php -$configurator->enableTracy($appDir . '/log'); +$this->configurator->enableTracy($this->rootDir . '/log'); ``` @@ -100,7 +125,7 @@ Arquivos temporários .[#toc-temporary-files] Nette utiliza o cache para contêiner DI, RobotLoader, modelos, etc. Portanto, é necessário definir o caminho para o diretório onde o cache será armazenado: ```php -$configurator->setTempDirectory($appDir . '/temp'); +$this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` No Linux ou macOS, defina as [permissões de escrita |nette:troubleshooting#Setting directory permissions] para os diretórios `log/` e `temp/`. @@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader] Normalmente, queremos carregar automaticamente as classes usando [o RobotLoader |robot-loader:], então temos que iniciá-lo e deixá-lo carregar classes do diretório onde se encontra `Bootstrap.php` (ou seja, `__DIR__`) e todos os seus subdiretórios: ```php -$configurator->createRobotLoader() +$this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); ``` @@ -126,7 +151,7 @@ Fuso horário .[#toc-timezone] O Configurador permite que você especifique um fuso horário para sua aplicação. ```php -$configurator->setTimeZone('Europe/Prague'); +$this->configurator->setTimeZone('Europe/Prague'); ``` @@ -143,16 +168,17 @@ No modo de desenvolvimento, o recipiente é atualizado automaticamente cada vez Os arquivos de configuração são carregados usando `addConfig()`: ```php -$configurator->addConfig($appDir . '/config/common.neon'); +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` O método `addConfig()` pode ser chamado várias vezes para adicionar vários arquivos. ```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/services.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] Os parâmetros usados nos arquivos de configuração podem ser definidos [na seção `parameters` |dependency-injection:configuration#parameters] e também passados (ou sobrescritos) pelo método `addStaticParameters()` (tem o pseudônimo `addParameters()`). É importante que diferentes valores de parâmetros causem a geração de recipientes DI adicionais, ou seja, classes adicionais. ```php -$configurator->addStaticParameters([ +$this->configurator->addStaticParameters([ 'projectId' => 23, ]); ``` @@ -183,7 +209,7 @@ Parâmetros dinâmicos .[#toc-dynamic-parameters] Também podemos adicionar parâmetros dinâmicos ao recipiente, seus diferentes valores, ao contrário dos parâmetros estáticos, não causarão a geração de novos recipientes DI. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'remoteIp' => $_SERVER['REMOTE_ADDR'], ]); ``` @@ -191,7 +217,7 @@ $configurator->addDynamicParameters([ As variáveis ambientais poderiam ser facilmente disponibilizadas usando parâmetros dinâmicos. Podemos acessá-las via `%env.variable%` em arquivos de configuração. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'env' => getenv(), ]); ``` @@ -206,6 +232,7 @@ Você pode usar os seguintes parâmetros estáticos nos arquivos de configuraç - `%wwwDir%` é o caminho absoluto para o diretório que contém o arquivo de entrada `index.php` - `%tempDir%` é o caminho absoluto para o diretório de arquivos temporários - `%vendorDir%` é o caminho absoluto para o diretório onde o Composer instala as bibliotecas +- `%rootDir%` é o caminho absoluto para o diretório raiz do projeto - `%debugMode%` indica se a aplicação está em modo de depuração - `%consoleMode%` indica se o pedido veio através da linha de comando @@ -225,7 +252,7 @@ services: Criar uma nova instância e inseri-la no bootstrap: ```php -$configurator->addServices([ +$this->configurator->addServices([ 'myservice' => new App\Model\MyCustomService('foobar'), ]); ``` @@ -234,13 +261,21 @@ $configurator->addServices([ Diferentes Ambientes .[#toc-different-environments] =================================================== -Sinta-se à vontade para personalizar a classe `Bootstrap` de acordo com suas necessidades. Você pode adicionar parâmetros ao método `boot()` para diferenciar projetos web, ou adicionar outros métodos, tais como `bootForTests()`, que inicializa o ambiente para testes unitários, `bootForCli()` para scripts chamados a partir da linha de comando, e assim por diante. +Não hesite em personalizar a classe `Bootstrap` de acordo com suas necessidades. Você pode adicionar parâmetros ao método `bootWebApplication()` para diferenciar os projetos da Web. Como alternativa, você pode adicionar outros métodos, como `bootTestEnvironment()` para inicializar o ambiente para testes de unidade, `bootConsoleApplication()` para scripts chamados pela linha de comando e assim por diante. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Inicialização do Nette Tester + $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/pt/components.texy b/application/pt/components.texy index 9cfe7557f5..502dc15cdc 100644 --- a/application/pt/components.texy +++ b/application/pt/components.texy @@ -230,6 +230,28 @@ No modelo, estas mensagens estão disponíveis na variável `$flashes` como obje ``` +Redirecionamento após um sinal .[#toc-redirection-after-a-signal] +================================================================= + +Depois de processar um sinal de componente, o redirecionamento geralmente é feito. Essa situação é semelhante à dos formulários: após o envio de um formulário, também redirecionamos para evitar o reenvio de dados quando a página é atualizada no navegador. + +```php +$this->redirect('this') // redirects to the current presenter and action +``` + +Como um componente é um elemento reutilizável e normalmente não deve ter uma dependência direta de apresentadores específicos, os métodos `redirect()` e `link()` interpretam automaticamente o parâmetro como um sinal de componente: + +```php +$this->redirect('click') // redirects to the 'click' signal of the same component +``` + +Se precisar redirecionar para um apresentador ou ação diferente, você poderá fazer isso por meio do apresentador: + +```php +$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action +``` + + Parâmetros Persistentes .[#toc-persistent-parameters] ===================================================== @@ -430,7 +452,7 @@ class PaginatingControl extends Control } ``` -O processo oposto, ou seja, a coleta de valores de properites persistentes, é tratado pelo método `saveState()`. +O processo oposto, ou seja, a coleta de valores de properties persistentes, é tratado pelo método `saveState()`. Sinais em profundidade .[#toc-signals-in-depth] diff --git a/application/pt/configuration.texy b/application/pt/configuration.texy index d83543aca8..55e3c0cb5d 100644 --- a/application/pt/configuration.texy +++ b/application/pt/configuration.texy @@ -95,6 +95,9 @@ latte: # Permite [verificar o código gerado |latte:develop#Checking Generated Code] phpLinter: ... # (string) o padrão é null + # Define a localidade + locale: cs_CZ # (string) o padrão é nulo + # classe de $this->template templateClass: App\MyTemplateClass # defaults to Nette\Bridges\ApplicationLatte\DefaultTemplate ``` @@ -104,7 +107,7 @@ Se você estiver usando Latte versão 3, você pode adicionar uma nova [extensã ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/pt/how-it-works.texy b/application/pt/how-it-works.texy index a73637f71e..c55012ce62 100644 --- a/application/pt/how-it-works.texy +++ b/application/pt/how-it-works.texy @@ -22,13 +22,13 @@ A estrutura do diretório é algo parecido com isto: /--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/ ← classes básicas necessárias +│ │ └── RouterFactory.php ← configuração de endereços URL +│ ├── UI/ ← apresentadores, modelos e outros +│ │ ├── @layout.latte ← modelo de layout compartilhado +│ │ └── Home/ ← Diretório do apresentador Home +│ │ ├── HomePresenter.php ← Classe do apresentador da página inicial +│ │ └── default.latte ← template for action default │ └── Bootstrap.php ← booting class Bootstrap ├── bin/ ← scripts for the command line ├── config/ ← configuration files @@ -91,7 +91,7 @@ As aplicações escritas em Nette são divididas em muitos dos chamados apresent A aplicação começa pedindo ao chamado roteador que decida qual dos apresentadores deve passar o atual pedido de processamento. O roteador decide de quem é a responsabilidade. Ele analisa a URL de entrada `https://example.com/product/123`, que quer `show` um produto com `id: 123` como uma ação. É um bom hábito escrever um par de apresentador + ação separado por dois pontos como `Product:show`. -Assim, o roteador transformou a URL em um par `Presenter:action` + parâmetros, em nosso caso `Product:show` + `id: 123`. Você pode ver como é um roteador no arquivo `app/Router/RouterFactory.php` e nós o descreveremos em detalhes no capítulo [Roteamento |Routing]. +Assim, o roteador transformou a URL em um par `Presenter:action` + parâmetros, em nosso caso `Product:show` + `id: 123`. Você pode ver como é um roteador no arquivo `app/Core/RouterFactory.php` e nós o descreveremos em detalhes no capítulo [Roteamento |Routing]. Vamos em frente. O pedido já conhece o nome do apresentador e pode continuar. Ao criar um objeto `ProductPresenter`, que é o código do apresentador `Product`. Mais precisamente, pede ao contêiner DI para criar o apresentador, pois a produção de objetos é sua função. @@ -121,12 +121,9 @@ Então, o método `renderShow(123)` foi chamado, cujo código é um exemplo fict Posteriormente, o apresentador retorna a resposta. Pode ser uma página HTML, uma imagem, um documento XML, o envio de um arquivo do disco, JSON ou o redirecionamento para outra página. É importante ressaltar que se não dissermos explicitamente como responder (que é o caso de `ProductPresenter`), a resposta será renderizar o template com uma página HTML. Por quê? Bem, porque em 99% dos casos queremos desenhar um template, então o apresentador toma este comportamento como padrão e quer tornar nosso trabalho mais fácil. Esse é o ponto de vista da Nette. -Não temos nem mesmo que declarar qual modelo desenhar, ele deriva o caminho para ele de acordo com uma lógica simples. No caso do apresentador `Product` e da ação `show`, ele tenta ver se um destes arquivos de modelo existe em relação ao diretório onde se encontra a classe `ProductPresenter`: +Não precisamos nem mesmo especificar o modelo a ser renderizado; a estrutura deduzirá o caminho por si só. No caso da ação `show`, ele simplesmente tenta carregar o modelo `show.latte` no diretório com a classe `ProductPresenter`. Ele também tenta encontrar o layout no arquivo `@layout.latte` (mais informações sobre a [pesquisa de modelos |templates#Template Lookup]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Ele também tentará encontrar o layout no arquivo `@layout.latte` e depois renderiza o modelo. Agora a tarefa do apresentador e de todo o pedido está concluída. Se o modelo não existir, uma página com erro 404 será devolvida. Você pode ler mais sobre os apresentadores na página [Apresentadores |Presenters]. +Em seguida, os modelos são renderizados. Isso conclui a tarefa do apresentador e de todo o aplicativo, e o trabalho está concluído. Se o modelo não existir, será retornada uma página de erro 404. Você pode ler mais sobre apresentadores na página [Apresentadores |presenters]. [* request-flow.svg *] @@ -137,7 +134,7 @@ Só para ter certeza, vamos tentar recapitular todo o processo com uma URL ligei 3) o roteador decodifica a URL como um par `Home:default` 4) um objeto `HomePresenter` é criado 5) método `renderDefault()` é chamado (se existir) -6) um modelo `templates/Home/default.latte` com um layout `templates/@layout.latte` é apresentado +6) um modelo `default.latte` com um layout `@layout.latte` é apresentado Você pode ter se deparado com muitos conceitos novos agora, mas acreditamos que eles fazem sentido. Criar aplicações em Nette é uma brisa. diff --git a/application/pt/modules.texy b/application/pt/modules.texy index 372dd0e467..a2e02d0257 100644 --- a/application/pt/modules.texy +++ b/application/pt/modules.texy @@ -2,29 +2,31 @@ Módulos ******* .[perex] -Em Nette, os módulos representam as unidades lógicas que compõem uma aplicação. Eles incluem apresentadores, gabaritos, possivelmente também componentes e classes de modelos. +Os módulos dão clareza aos aplicativos Nette, facilitando a divisão em unidades lógicas. -Um diretório para apresentadores e um para modelos não seria suficiente para projetos reais. Ter dezenas de arquivos em uma pasta é pelo menos desorganizado. Como sair dela? Nós simplesmente os dividimos em subdiretórios em disco e em namespaces no código. E é exatamente isso que os módulos Nette fazem. - -Portanto, vamos esquecer uma única pasta para apresentadores e modelos e, em vez disso, criar módulos, por exemplo `Admin` e `Front`. +Da mesma forma que organizamos os arquivos em pastas em um disco rígido, na Nette podemos dividir apresentadores, modelos e outras classes auxiliares em módulos. Como isso funciona na prática? Simplesmente incorporando novos subdiretórios à estrutura. Aqui está um exemplo de uma estrutura com dois módulos, Front e 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 +│ │ └── ... \-- -Esta estrutura de diretório será refletida pelos namespaces de classe, portanto, por exemplo `DashboardPresenter` estará no namespace `App\Modules\Admin\Presenters`: +Essa estrutura de diretórios é refletida nos namespaces das classes, portanto, por exemplo, `DashboardPresenter` está localizado no 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 } ``` -O `Dashboard` apresentador dentro do módulo `Admin` é referenciado dentro da aplicação usando a notação de cólon como `Admin:Dashboard`, e sua ação `default` como `Admin:Dashboard:default`. -E como a Nette propriamente dita sabe que `Admin:Dashboard` representa a classe `App\Modules\Admin\Presenters\DashboardPresenter`? Isto é determinado pelo [mapeamento |#mapping] na configuração. -Assim, a estrutura dada não é difícil de definir e você pode modificá-la de acordo com suas necessidades. +No aplicativo, nos referimos ao apresentador `Dashboard` dentro do módulo `Admin` usando a notação de dois pontos como `Admin:Dashboard`. Para sua ação `default`, nos referimos a ela como `Admin:Dashboard:default`. -Os módulos podem naturalmente conter todos os outros itens além de apresentadores e modelos, tais como componentes, classes de modelos, etc. +A estrutura apresentada não é rígida; você pode [personalizá-la totalmente de acordo com suas necessidades |#mapping] na configuração. .[tip] + +Os módulos podem incluir todos os outros arquivos, como componentes e classes auxiliares, além de apresentadores e modelos. Se estiver pensando em onde colocá-los, considere o uso de uma pasta `Accessory`: + +/--pre +app/ +├── UI/ +│ ├── Admin/ +│ │ ├── Accessory/ +│ │ │ ├── FormFactory.php +│ │ │ └── AdminLayout.php +│ │ ├── Dashboard/ +│ │ └── ... +\-- Módulos aninhados .[#toc-nested-modules] ---------------------------------------- -Os módulos não precisam formar apenas uma estrutura plana, você também pode criar submódulos, por exemplo: +Os módulos podem ter vários níveis de aninhamento, semelhante a uma estrutura de diretórios em um disco: /--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 │ │ └── ... \-- -Assim, o módulo `Blog` está dividido em `Admin` e `Front` submódulos. Mais uma vez, isto se refletirá nos namespaces, que serão `App\Modules\Blog\Admin\Presenters` etc. O apresentador `Dashboard` dentro do submódulo é referido como `Blog:Admin:Dashboard`. +O módulo `Blog` é dividido em submódulos `Admin` e `Front`. Isso também se reflete nos namespaces, que aparecem como `App\UI\Blog\Admin` e similares. Para nos referirmos ao apresentador `Dashboard` dentro do submódulo `Admin`, nos referimos a ele como `Blog:Admin:Dashboard`. -O ninho pode ir tão fundo quanto você desejar, de modo que sub-submódulos podem ser criados. +O aninhamento pode ser tão profundo quanto necessário, permitindo a criação de sub-submódulos. + +Por exemplo, se na administração você tem muitos apresentadores relacionados ao gerenciamento de pedidos, como `OrderDetail`, `OrderEdit`, `OrderDispatch`, etc., você pode criar um módulo `Order` no qual apresentadores como `Detail`, `Edit`, `Dispatch` e outros serão organizados. Criação de links .[#toc-creating-links] @@ -102,46 +118,66 @@ Ver [capítulo sobre roteamento |routing#Modules]. Mapeamento .[#toc-mapping] -------------------------- -Define as regras pelas quais o nome da classe é derivado do nome do apresentador. Nós as escrevemos na [configuração |configuration] sob a chave `application › mapping`. +O mapeamento define as regras para derivar o nome da classe do nome do apresentador. Essas regras são especificadas na [configuração |configuration] sob a chave `application › mapping`. + +As estruturas de diretório mencionadas anteriormente nesta página são baseadas no seguinte mapeamento: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Vamos começar com uma amostra que não utiliza módulos. Queremos apenas que as classes de apresentadores tenham o namespace `App\Presenters`. Isso significa que um apresentador como o `Home` deve mapear para a classe `App\Presenters\HomePresenter`. Isto pode ser conseguido através da seguinte configuração: +Como funciona o mapeamento? Para entender melhor, vamos primeiro imaginar um aplicativo sem módulos. Queremos que as classes do apresentador se enquadrem no namespace `App\UI`, de modo que o apresentador `Home` mapeie para a classe `App\UI\HomePresenter`. Isso pode ser obtido com esta configuração: ```neon application: - mapping: App\Presenters\*Presenter + mapping: App\UI\*Presenter ``` -O nome do apresentador é substituído pelo asterisco na máscara da classe e o resultado é o nome da classe. Fácil! +Esse mapeamento funciona substituindo o asterisco na máscara `App\UI\*Presenter` pelo nome do apresentador `Home`, resultando no nome final da classe `App\UI\HomePresenter`. Simples! + +Entretanto, como você pode ver nos exemplos deste e de outros capítulos, colocamos as classes de apresentador em subdiretórios homônimos, por exemplo, o apresentador `Home` é mapeado para a classe `App\UI\Home\HomePresenter`. Isso é feito duplicando-se o asterisco (requer o Nette Application 3.2): + +```neon +application: + mapping: App\UI\**Presenter +``` -Se dividirmos os apresentadores em módulos, podemos ter nosso próprio mapeamento para cada módulo: +Agora, vamos passar a mapear os apresentadores em módulos. Podemos definir mapeamentos específicos 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 ``` -Agora o apresentador `Front:Home` mapeia para a classe `App\Modules\Front\Presenters\HomePresenter` e o apresentador `Admin:Dashboard` para a classe `App\Modules\Admin\Presenters\DashboardPresenter`. +De acordo com essa configuração, o apresentador `Front:Home` mapeia para a classe `App\UI\Front\Home\HomePresenter`, enquanto o apresentador `Api:OAuth` mapeia para a classe `App\Api\OAuthPresenter`. -É mais prático criar uma regra geral (estrela) para substituir as duas primeiras. O asterisco extra será adicionado à máscara de classe apenas para o módulo: +Como os módulos `Front` e `Admin` têm uma abordagem de mapeamento semelhante e é provável que haja mais módulos desse tipo, é possível criar uma regra geral que os substitua. Um novo asterisco para o módulo é adicionado à máscara de classe: ```neon application: mapping: - *: App\Modules\*\Presenters\*Presenter + *: App\UI\*\**Presenter Api: App\Api\*Presenter ``` -Mas e se usarmos módulos aninhados e tivermos um apresentador `Admin:User:Edit`? Neste caso, o segmento com um asterisco representando o módulo para cada nível é simplesmente repetido e o resultado é a classe `App\Modules\Admin\User\Presenters\EditPresenter`. +Para módulos aninhados em vários níveis, como o apresentador `Admin:User:Edit`, o segmento de asterisco se repete para cada nível, resultando na classe `App\UI\Admin\User\Edit\EditPresenter`. -Uma notação alternativa é utilizar um conjunto composto de três segmentos em vez de um fio. Esta notação é equivalente à anterior: +Uma notação alternativa é usar uma matriz composta de três segmentos em vez de uma cadeia de caracteres. Essa notação é equivalente à anterior: ```neon application: mapping: - *: [App\Modules, *, Presenters\*Presenter] + *: [App\UI, *, **Presenter] + Api: [App\Api, '', *Presenter] ``` -O valor padrão é `*Module\*Presenter`. +Se tivermos apenas uma regra na configuração, a geral, podemos escrever de forma resumida: + +```neon +application: + mapping: App\UI\*\**Presenter +``` diff --git a/application/pt/presenters.texy b/application/pt/presenters.texy index 655e650b6f..b6537f16c6 100644 --- a/application/pt/presenters.texy +++ b/application/pt/presenters.texy @@ -60,7 +60,7 @@ Semelhante ao método `render()`. Enquanto `render()` é destinado a É importante que `action()` é chamado antes `render()`Assim, dentro dele podemos possivelmente mudar o próximo ciclo de vida, ou seja, mudar o modelo que será apresentado e também o método `render()` que será chamado, usando `setView('otherView')`. -Os parâmetros do pedido são passados para o método. É possível e recomendado especificar tipos para os parâmetros, por exemplo `actionShow(int $id, string $slug = null)` - se o parâmetro `id` estiver faltando ou se não for um número inteiro, o apresentador retorna o [erro 404 |#Error 404 etc.] e encerra a operação. +Os parâmetros do pedido são passados para o método. É possível e recomendado especificar tipos para os parâmetros, por exemplo `actionShow(int $id, ?string $slug = null)` - se o parâmetro `id` estiver faltando ou se não for um número inteiro, o apresentador retorna o [erro 404 |#Error 404 etc.] e encerra a operação. `handle(args...)` .{toc: handle()} @@ -205,7 +205,7 @@ No modelo, estas mensagens estão disponíveis na variável `$flashes` como obje Erro 404 etc. .[#toc-error-404-etc] =================================== -Quando não pudermos atender ao pedido porque, por exemplo, o artigo que queremos exibir não existe no banco de dados, vamos jogar fora o erro 404 usando o método `error(string $message = null, int $httpCode = 404)`, que representa o erro HTTP 404: +Quando não pudermos atender ao pedido porque, por exemplo, o artigo que queremos exibir não existe no banco de dados, vamos jogar fora o erro 404 usando o método `error(?string $message = null, int $httpCode = 404)`, que representa o erro HTTP 404: ```php public function renderShow(int $id): void @@ -384,7 +384,7 @@ O redirecionamento não ocorre com um pedido AJAX ou POST porque resultaria em p Você também pode invocar a canonização manualmente usando o método `canonicalize()`, que, como o método `link()`, recebe o apresentador, ações e parâmetros como argumentos. Ele cria um link e o compara com a URL atual. Se for diferente, ele se redireciona para o link gerado. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // redireciona se $slug for diferente de $realSlug @@ -452,17 +452,6 @@ Restrição de acesso usando `#[Requires]` .[#toc-access-restriction-using-requi O atributo `#[Requires]` fornece opções avançadas para restringir o acesso aos apresentadores e seus métodos. Ele pode ser usado para especificar métodos HTTP, exigir solicitações AJAX, limitar o acesso à mesma origem e restringir o acesso somente ao encaminhamento. O atributo pode ser aplicado a classes de apresentadores, bem como a métodos individuais, como `action()`, `render()`, `handle()`, e `createComponent()`. -Veja a seguir um exemplo de uso para restringir o acesso apenas ao método HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Você pode especificar essas restrições: - em métodos HTTP: `#[Requires(methods: ['GET', 'POST'])]` - que exigem uma solicitação AJAX: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ Você pode especificar essas restrições: - acesso somente por meio de encaminhamento: `#[Requires(forward: true)]` - restrições a ações específicas: `#[Requires(actions: 'default')]` -As condições podem ser combinadas listando vários atributos ou unindo-os em um só: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Para obter detalhes, consulte [Como usar o atributo Requires |best-practices:attribute-requires]. Verificação do método HTTP .[#toc-http-method-check] diff --git a/application/pt/routing.texy b/application/pt/routing.texy index 6caf887e2d..6a81076903 100644 --- a/application/pt/routing.texy +++ b/application/pt/routing.texy @@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +225,7 @@ $router->addRoute('/[/]', [ ]); ``` -Ou podemos usar este formulário, observando a reescrita da expressão regular de validação: +Para uma especificação mais detalhada, é possível usar um formulário ainda mais extenso, no qual, além dos valores padrão, outras propriedades de parâmetro podem ser definidas, como uma expressão regular de validação (consulte o parâmetro `id` ): ```php use Nette\Routing\Route; @@ -243,7 +243,7 @@ $router->addRoute('/[/]', [ ]); ``` -Estes formatos mais faladores são úteis para adicionar outros metadados. +É importante observar que, se os parâmetros definidos na matriz não estiverem incluídos na máscara de caminho, seus valores não poderão ser alterados, nem mesmo usando parâmetros de consulta especificados após um ponto de interrogação no URL. Filtros e Traduções .[#toc-filters-and-translations] @@ -477,10 +477,10 @@ $router->addRoute('index', /* ... */); Integração .[#toc-integration] ============================== -A fim de conectar nosso roteador à aplicação, devemos informar o recipiente DI sobre isso. A maneira mais fácil é preparar a fábrica que irá construir o objeto roteador e dizer à configuração do contêiner para usá-lo. Portanto, digamos que escrevemos um método para este fim `App\Router\RouterFactory::createRouter()`: +A fim de conectar nosso roteador à aplicação, devemos informar o recipiente DI sobre isso. A maneira mais fácil é preparar a fábrica que irá construir o objeto roteador e dizer à configuração do contêiner para usá-lo. Portanto, digamos que escrevemos um método para este fim `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -499,7 +499,7 @@ Em seguida, escrevemos em [configuração |dependency-injection:services]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Quaisquer dependências, tais como uma conexão de banco de dados, etc., são passadas para o método de fábrica como seus parâmetros usando [a fiação automática |dependency-injection:autowiring]: @@ -663,7 +663,7 @@ Por uso separado, entendemos o uso das capacidades do roteador em uma aplicaçã Assim, mais uma vez, criaremos um método que construirá um roteador, por exemplo: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Ou criaremos objetos diretamente: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/pt/templates.texy b/application/pt/templates.texy index 059e35178e..2886c6740e 100644 --- a/application/pt/templates.texy +++ b/application/pt/templates.texy @@ -34,35 +34,81 @@ E este poderia ser o modelo de ação: Ele define o bloco `content`, que é inserido no lugar de `{include content}` no layout, e também redefine o bloco `title`, que sobrescreve `{block title}` no layout. Tente imaginar o resultado. -Busca de modelos .[#toc-search-for-templates] ---------------------------------------------- +Pesquisa de modelos .[#toc-template-lookup] +------------------------------------------- -O caminho para os modelos é deduzido de acordo com uma lógica simples. Ele tenta ver se um destes arquivos de gabaritos existe em relação ao diretório onde se encontra a classe apresentadora, onde `` é o nome do atual apresentador e `` é o nome da ação atual: +Nos apresentadores, você não precisa especificar qual modelo deve ser renderizado; a estrutura determinará automaticamente o caminho, facilitando a codificação para você. -- `templates//.latte` -- `templates/..latte` +Se você usar uma estrutura de diretórios em que cada apresentador tenha seu próprio diretório, basta colocar o modelo nesse diretório com o nome da ação (ou seja, visualização). Por exemplo, para a ação `default`, use o modelo `default.latte`: -Se o modelo não for encontrado, ele tentará procurar no diretório `templates` um nível acima, ou seja, no mesmo nível que o diretório com a classe apresentadora. +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Se o modelo também não for encontrado lá, a resposta é um [erro 404 |presenters#Error 404 etc.]. +Se você usar uma estrutura em que os apresentadores estejam juntos em um diretório e os modelos em uma pasta `templates`, salve-os em um arquivo `..latte` ou `/.latte`: -Você também pode mudar a visão usando `$this->setView('otherView')`. Ou, em vez de procurar, especifique diretamente o nome do arquivo modelo usando `$this->template->setFile('/path/to/template.latte')`. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- + +O diretório `templates` também pode ser colocado um nível acima, no mesmo nível do diretório com as classes de apresentador. + +Se o modelo não for encontrado, o apresentador responderá com o [erro 404 - página não encontrada |presenters#Error 404 etc]. + +Você pode alterar a visualização usando `$this->setView('anotherView')`. Também é possível especificar diretamente o arquivo de modelo com `$this->template->setFile('/path/to/template.latte')`. .[note] -Você pode alterar os caminhos onde os modelos são pesquisados substituindo o método de [formataçãoTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], que retorna um conjunto de possíveis caminhos de arquivos. +Os arquivos onde os modelos são pesquisados podem ser alterados substituindo o método [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], que retorna uma matriz de possíveis nomes de arquivos. + + +Pesquisa de modelos de layout .[#toc-layout-template-lookup] +------------------------------------------------------------ + +O Nette também procura automaticamente o arquivo de layout. + +Se você usar uma estrutura de diretórios em que cada apresentador tenha seu próprio diretório, coloque o layout na pasta com o apresentador, se for específico apenas para ele, ou em um nível superior, se for comum a vários apresentadores: + +/--pre +app/ +└── UI/ + ├── @layout.latte ← common layout + └── Home/ + ├── @layout.latte ← only for Home presenter + ├── HomePresenter.php + └── default.latte +\-- + +Se você usar uma estrutura em que os apresentadores estejam agrupados em um diretório e os modelos estejam em uma pasta `templates`, o layout será esperado nos seguintes locais: -O layout é esperado nos seguintes arquivos: +/--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 comum a vários apresentadores +Se o apresentador estiver em um [módulo |modules], ele também pesquisará mais acima na árvore de diretórios, de acordo com o aninhamento do módulo. -`` é o nome do atual apresentador e `` é o nome do layout, que é, por padrão, `'layout'`. O nome pode ser alterado com `$this->setLayout('otherLayout')`, para que os arquivos `@otherLayout.latte` sejam experimentados. +O nome do layout pode ser alterado usando `$this->setLayout('layoutAdmin')` e, em seguida, ele será esperado no arquivo `@layoutAdmin.latte`. Você também pode especificar diretamente o arquivo de modelo de layout usando `$this->setLayout('/path/to/template.latte')`. -Você também pode especificar diretamente o nome do arquivo do modelo de layout usando `$this->setLayout('/path/to/template.latte')`. O uso do `$this->setLayout(false)` desabilitará a busca do layout. +O uso de `$this->setLayout(false)` ou da tag `{layout none}` dentro do modelo desativa a pesquisa de layout. .[note] -Você pode alterar os caminhos onde os modelos são pesquisados, substituindo o método [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], que retorna uma série de possíveis caminhos de arquivos. +Os arquivos em que os modelos de layout são pesquisados podem ser alterados substituindo o método [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], que retorna uma matriz de possíveis nomes de arquivos. Variáveis no modelo .[#toc-variables-in-the-template] @@ -104,7 +150,7 @@ A anotação `@property-read` é para IDE e análise estática, fará com que o Você também pode se dar ao luxo de sussurrar nos modelos, basta instalar o plugin Latte no PhpStorm e especificar o nome da classe no início do modelo, veja o artigo "Latte: como digitar sistema":https://blog.nette.org/pt/latte-como-usar-o-sistema-de-tipo: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void A versão 3 do Latte oferece uma maneira mais avançada, criando uma [extensão |latte:creating-extension] para cada projeto web. Aqui está um exemplo rudimentar de tal classe: ```php -namespace App\Templating; +namespace App\UI\Accessory; final class LatteExtension extends Latte\Extension { @@ -214,7 +260,7 @@ Registramos usando [a configuração#Latte |configuration#Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\UI\Accessory\LatteExtension ``` @@ -239,7 +285,7 @@ Alternativamente, o tradutor pode ser definido usando a [configuração |configu ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` O tradutor pode então ser usado, por exemplo, como um filtro `|translate`, com parâmetros adicionais passados para o método `translate()` (ver `foo, bar`): diff --git a/application/ro/ajax.texy b/application/ro/ajax.texy index 3f20c912bc..03eec3fac5 100644 --- a/application/ro/ajax.texy +++ b/application/ro/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Mai întâi trebuie să [inițializați |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] biblioteca: + +```js +naja.initialize(); +``` + Pentru a transforma un link obișnuit (semnal) sau un formular de trimitere într-o cerere AJAX, este suficient să marcați link-ul, formularul sau butonul respectiv cu clasa `ajax`: ```html diff --git a/application/ro/bootstrap.texy b/application/ro/bootstrap.texy index a13e217c3c..97b1a1fe30 100644 --- a/application/ro/bootstrap.texy +++ b/application/ro/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__); + // Configuratorul este responsabil de configurarea mediului și a serviciilor aplicației. + $this->configurator = new Configurator; + // Setați directorul pentru fișierele temporare generate de Nette (de exemplu, șabloane compilate) + $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 este inteligent, iar modul de dezvoltare se activează automat, + // sau îl puteți activa pentru o anumită adresă IP prin decomentarea următoarei linii: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Activează Tracy: instrumentul suprem de depanare "swiss army knife". + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: Încarcă automat toate clasele din directorul dat + $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; + } + + private function setupContainer(): void + { + // Încarcă fișierele de configurare + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } ``` @@ -40,16 +66,15 @@ class Bootstrap index.php .[#toc-index-php] =========================== -În cazul aplicațiilor web, fișierul inițial este `index.php`, care se află în directorul public `www/`. Acesta permite clasei `Bootstrap` să inițializeze mediul și să returneze `$configurator` care creează containerul DI. Apoi se obține serviciul `Application`, care execută aplicația web: +Fișierul inițial pentru aplicațiile web este `index.php`, situat în directorul public `www/`. Acesta utilizează clasa `Bootstrap` pentru a inițializa mediul și a crea un container DI. Apoi, obține serviciul `Application` din container, care lansează aplicația web: ```php -// inițializarea mediului + obținerea obiectului Configurator -$configurator = App\Bootstrap::boot(); -// creați un container DI -$container = $configurator->createContainer(); -// containerul DI creează un obiect Nette\Application\Application +$bootstrap = new App\Bootstrap; +// Inițializarea mediului + crearea unui container DI +$container = $bootstrap->bootWebApplication(); +// Containerul DI creează un obiect Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// pornește aplicația Nette +// Porniți aplicația Nette și tratați cererea de intrare $application->run(); ``` @@ -66,19 +91,19 @@ Selectarea modului se face prin autodetecție, astfel încât, de obicei, nu est Dacă doriți să activați modul de dezvoltare în alte cazuri, de exemplu, pentru programatorii care accesează de la o anumită adresă IP, puteți utiliza `setDebugMode()`: ```php -$configurator->setDebugMode('23.75.345.200'); // una sau mai multe adrese IP +$this->configurator->setDebugMode('23.75.345.200'); // una sau mai multe adrese IP ``` Vă recomandăm cu siguranță să combinați o adresă IP cu un cookie. Vom stoca un token secret în cookie-ul `nette-debug`, de exemplu `secret1234`, iar modul de dezvoltare va fi activat pentru programatorii cu această combinație de IP și cookie. ```php -$configurator->setDebugMode('secret1234@23.75.345.200'); +$this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` De asemenea, putem dezactiva complet modul de dezvoltare, chiar și pentru localhost: ```php -$configurator->setDebugMode(false); +$this->configurator->setDebugMode(false); ``` Rețineți că valoarea `true` activează modul dezvoltator din greu, ceea ce nu ar trebui să se întâmple niciodată pe un server de producție. @@ -90,7 +115,7 @@ Instrumentul de depanare Tracy .[#toc-debugging-tool-tracy] Pentru o depanare mai ușoară, vom porni minunata unealtă [Tracy |tracy:]. În modul dezvoltator, acesta vizualizează erorile, iar în modul de producție înregistrează erorile în directorul specificat: ```php -$configurator->enableTracy($appDir . '/log'); +$this->configurator->enableTracy($this->rootDir . '/log'); ``` @@ -100,7 +125,7 @@ Temporary Files .[#toc-temporary-files] Nette utilizează memoria cache pentru DI container, RobotLoader, șabloane etc. Prin urmare, este necesar să setați calea către directorul în care va fi stocată memoria cache: ```php -$configurator->setTempDirectory($appDir . '/temp'); +$this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` Pe Linux sau macOS, setați [permisiunile de scriere |nette:troubleshooting#Setting directory permissions] pentru directoarele `log/` și `temp/`. @@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader] De obicei, vom dori să încărcăm automat clasele folosind [RobotLoader |robot-loader:], așa că trebuie să îl pornim și să îl lăsăm să încarce clasele din directorul în care se află `Bootstrap.php` (adică `__DIR__`) și din toate subdirectoarele sale: ```php -$configurator->createRobotLoader() +$this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); ``` @@ -126,7 +151,7 @@ Fusul orar .[#toc-timezone] Configurator vă permite să specificați un fus orar pentru aplicația dumneavoastră. ```php -$configurator->setTimeZone('Europe/Prague'); +$this->configurator->setTimeZone('Europe/Prague'); ``` @@ -143,16 +168,17 @@ Fișierele de configurare sunt de obicei scrise în [formatul NEON |neon:format] Fișierele de configurare sunt încărcate utilizând `addConfig()`: ```php -$configurator->addConfig($appDir . '/config/common.neon'); +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` Metoda `addConfig()` poate fi apelată de mai multe ori pentru a adăuga mai multe fișiere. ```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/services.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 @@ Parametrii statici .[#toc-static-parameters] Parametrii utilizați în fișierele de configurare pot fi definiți [în secțiunea `parameters` |dependency-injection:configuration#parameters] și, de asemenea, pot fi trecuți (sau suprascriși) prin metoda `addStaticParameters()` (are pseudonimul `addParameters()`). Este important ca valorile diferite ale parametrilor să determine generarea de containere DI suplimentare, adică de clase suplimentare. ```php -$configurator->addStaticParameters([ +$this->configurator->addStaticParameters([ 'projectId' => 23, ]); ``` @@ -183,7 +209,7 @@ Parametrii dinamici .[#toc-dynamic-parameters] De asemenea, putem adăuga parametri dinamici la container, valorile lor diferite, spre deosebire de parametrii statici, nu vor determina generarea de noi containere DI. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'remoteIp' => $_SERVER['REMOTE_ADDR'], ]); ``` @@ -191,7 +217,7 @@ $configurator->addDynamicParameters([ Variabilele de mediu ar putea fi puse cu ușurință la dispoziție prin intermediul parametrilor dinamici. Le putem accesa prin intermediul `%env.variable%` în fișierele de configurare. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'env' => getenv(), ]); ``` @@ -206,6 +232,7 @@ Puteți utiliza următorii parametri statici în fișierele de configurare: - `%wwwDir%` este calea absolută către directorul care conține fișierul de intrare `index.php` - `%tempDir%` este calea absolută către directorul pentru fișierele temporare - `%vendorDir%` este calea absolută către directorul în care Composer instalează bibliotecile +- `%rootDir%` este calea absolută către directorul rădăcină al proiectului - `%debugMode%` indică dacă aplicația se află în modul de depanare - `%consoleMode%` indică dacă cererea a venit prin linia de comandă @@ -225,7 +252,7 @@ services: Creați o nouă instanță și inserați-o în bootstrap: ```php -$configurator->addServices([ +$this->configurator->addServices([ 'myservice' => new App\Model\MyCustomService('foobar'), ]); ``` @@ -234,13 +261,21 @@ $configurator->addServices([ Diferite medii .[#toc-different-environments] ============================================= -Nu ezitați să personalizați clasa `Bootstrap` pentru a se potrivi nevoilor dumneavoastră. Puteți adăuga parametri la metoda `boot()` pentru a diferenția proiectele web sau puteți adăuga alte metode, cum ar fi `bootForTests()`, care inițializează mediul pentru testele unitare, `bootForCli()` pentru scripturile apelate din linia de comandă și așa mai departe. +Nu ezitați să personalizați clasa `Bootstrap` în funcție de nevoile dumneavoastră. Puteți adăuga parametri la metoda `bootWebApplication()` pentru a face diferența între proiectele web. Alternativ, puteți adăuga alte metode, cum ar fi `bootTestEnvironment()` pentru a inițializa mediul pentru testele unitare, `bootConsoleApplication()` pentru scripturile apelate din linia de comandă și așa mai departe. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container { - $configurator = self::boot(); Tester\Environment::setup(); // Inițializarea 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/ro/components.texy b/application/ro/components.texy index d988ba261f..1d48351bbd 100644 --- a/application/ro/components.texy +++ b/application/ro/components.texy @@ -230,6 +230,28 @@ $this->redirect(/* ... */); // și redirecționarea ``` +Redirecționarea după un semnal .[#toc-redirection-after-a-signal] +================================================================= + +După procesarea unui semnal de componentă, urmează adesea redirecționarea. Această situație este similară formularelor - după trimiterea unui formular, redirecționăm, de asemenea, pentru a preveni retrimiterea datelor atunci când pagina este reîmprospătată în browser. + +```php +$this->redirect('this') // redirects to the current presenter and action +``` + +Deoarece o componentă este un element reutilizabil și de obicei nu ar trebui să aibă o dependență directă de anumiți prezentatori, metodele `redirect()` și `link()` interpretează automat parametrul ca fiind un semnal de componentă: + +```php +$this->redirect('click') // redirects to the 'click' signal of the same component +``` + +Dacă trebuie să redirecționați către un alt prezentator sau acțiune, puteți face acest lucru prin intermediul prezentatorului: + +```php +$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action +``` + + Parametrii persistenți .[#toc-persistent-parameters] ==================================================== diff --git a/application/ro/configuration.texy b/application/ro/configuration.texy index 4783df2ade..d5965b0e36 100644 --- a/application/ro/configuration.texy +++ b/application/ro/configuration.texy @@ -95,6 +95,9 @@ latte: # activează [verificarea codului generat |latte:develop#Checking Generated Code] phpLinter: ... # (string) implicit este null + # stabilește localul + locale: cs_CZ # (string) implicit este nul + # clasa lui $this->template templateClass: App\MyTemplateClass # valoarea implicită este Nette\Bridges\ApplicationLatte\DefaultTemplate ``` @@ -104,7 +107,7 @@ Dacă utilizați Latte versiunea 3, puteți adăuga o nouă [extensie |latte:cre ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/ro/how-it-works.texy b/application/ro/how-it-works.texy index 75291693ee..c1cfda33f3 100644 --- a/application/ro/how-it-works.texy +++ b/application/ro/how-it-works.texy @@ -22,13 +22,13 @@ Structura directoarelor arată cam așa: /--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/ ← cursuri de bază necesare +│ │ └── RouterFactory.php ← configurare adrese URL +│ ├── UI/ ← prezentări, șabloane & co. +│ │ ├── @layout.latte ← șablon de prezentare partajată +│ │ └── Home/ ← Directorul de prezentatori de acasă +│ │ ├── HomePresenter.php ← Clasa de prezentator de acasă +│ │ └── default.latte ← șablon pentru acțiune default │ └── Bootstrap.php ← booting class Bootstrap ├── bin/ ← scripts for the command line ├── config/ ← configuration files @@ -91,7 +91,7 @@ Aplicațiile scrise în Nette sunt împărțite în mai multe așa-numite prezen Aplicația începe prin a cere așa-numitului router să decidă care dintre prezentatori să transmită cererea curentă pentru procesare. Routerul decide a cui este responsabilitatea. Acesta se uită la URL-ul de intrare `https://example.com/product/123`, care dorește să `show` un produs cu `id: 123` ca acțiune. Este un bun obicei să se scrie perechile prezentator + acțiune separate de două puncte ca `Product:show`. -Astfel, routerul a transformat URL-ul într-o pereche `Presenter:action` + parametri, în cazul nostru `Product:show` + `id: 123`. Puteți vedea cum arată un router în fișierul `app/Router/RouterFactory.php` și îl vom descrie în detaliu în capitolul [Routing |Routing]. +Astfel, routerul a transformat URL-ul într-o pereche `Presenter:action` + parametri, în cazul nostru `Product:show` + `id: 123`. Puteți vedea cum arată un router în fișierul `app/Core/RouterFactory.php` și îl vom descrie în detaliu în capitolul [Routing |Routing]. Să mergem mai departe. Aplicația cunoaște deja numele prezentatorului și poate continua. Prin crearea unui obiect `ProductPresenter`, care este codul prezentatorului `Product`. Mai exact, solicită containerului DI crearea prezentatorului, deoarece producerea de obiecte este sarcina acestuia. @@ -121,12 +121,9 @@ Așadar, a fost apelată metoda `renderShow(123)`, al cărei cod este un exemplu Ulterior, prezentatorul returnează răspunsul. Acesta poate fi o pagină HTML, o imagine, un document XML, trimiterea unui fișier de pe disc, JSON sau redirecționarea către o altă pagină. Este important de reținut că, dacă nu spunem în mod explicit cum să răspundem (ceea ce este cazul `ProductPresenter`), răspunsul va fi redarea șablonului cu o pagină HTML. De ce? Ei bine, pentru că în 99% din cazuri dorim să desenăm un șablon, așa că prezentatorul ia acest comportament ca fiind implicit și dorește să ne ușureze munca. Acesta este punctul de vedere al lui Nette. -Nici măcar nu trebuie să precizăm ce șablon să desenăm, el derivă calea către acesta conform unei logici simple. În cazul prezentatorului `Product` și al acțiunii `show`, el încearcă să vadă dacă unul dintre aceste fișiere șablon există în raport cu directorul în care se află clasa `ProductPresenter`: +Nici măcar nu trebuie să specificăm ce șablon să redăm; cadrul va deduce singur calea. În cazul acțiunii `show`, acesta încearcă pur și simplu să încarce șablonul `show.latte` din directorul cu clasa `ProductPresenter`. De asemenea, încearcă să găsească aspectul în fișierul `@layout.latte` (mai multe informații despre [căutarea șabloanelor |templates#Template Lookup]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -De asemenea, va încerca să găsească aspectul în fișierul `@layout.latte` și apoi va reda șablonul. Acum sarcina prezentatorului și a întregii aplicații este finalizată. În cazul în care șablonul nu există, va fi returnată o pagină cu eroarea 404. Puteți citi mai multe despre prezentatori pe pagina [Prezentatori |Presenters]. +Ulterior, șabloanele sunt redate. Acest lucru încheie sarcina prezentatorului și a întregii aplicații, iar activitatea este finalizată. În cazul în care șablonul nu există, ar fi returnată o pagină de eroare 404. Puteți citi mai multe despre prezentatori pe pagina [Prezentatori |presenters]. [* request-flow.svg *] @@ -137,7 +134,7 @@ Doar pentru a fi siguri, să încercăm să recapitulăm întregul proces cu un 3) routerul decodifică URL-ul ca o pereche `Home:default` 4) este creat un obiect `HomePresenter` 5) se apelează metoda `renderDefault()` (dacă există) -6) este redat un șablon `templates/Home/default.latte` cu un layout `templates/@layout.latte` +6) este redat un șablon `default.latte` cu un layout `@layout.latte` Este posibil să fi întâlnit acum o mulțime de concepte noi, dar noi credem că acestea au sens. Crearea de aplicații în Nette este o joacă de copii. diff --git a/application/ro/modules.texy b/application/ro/modules.texy index 1e7cd0fee4..300b63f579 100644 --- a/application/ro/modules.texy +++ b/application/ro/modules.texy @@ -2,29 +2,31 @@ Module ****** .[perex] -În Nette, modulele reprezintă unitățile logice care alcătuiesc o aplicație. Acestea includ prezentatori, șabloane, eventual și componente și clase de model. +Modulele aduc claritate aplicațiilor Nette, facilitând divizarea ușoară în unități logice. -Un singur director pentru prezentatori și unul pentru șabloane nu ar fi suficient pentru proiectele reale. A avea zeci de fișiere într-un singur dosar este cel puțin neorganizat. Cum se poate ieși din asta? Pur și simplu le împărțim în subdirectoare pe disc și în spații de nume în cod. Și asta este exact ceea ce fac modulele Nette. - -Așadar, să uităm de un singur dosar pentru prezentatori și șabloane și să creăm în schimb module, de exemplu `Admin` și `Front`. +Similar cu organizarea fișierelor în foldere pe un hard disk, în Nette putem împărți în module prezentatorii, șabloanele și alte clase auxiliare. Cum funcționează acest lucru în practică? Pur și simplu prin încorporarea de noi subdirectoare în structură. Iată un exemplu de structură cu două module, Front și 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 +│ │ └── ... \-- -Această structură de directoare va fi reflectată de spațiile de nume ale claselor, astfel încât, de exemplu, `DashboardPresenter` va fi în spațiul de nume `App\Modules\Admin\Presenters`: +Această structură de directoare se reflectă în spațiile de nume ale claselor, astfel încât, de exemplu, `DashboardPresenter` se află în spațiul de nume `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 } ``` -Prezentatorul `Dashboard` din cadrul modulului `Admin` este referit în cadrul aplicației folosind notația de două puncte ca `Admin:Dashboard`, iar acțiunea `default` ca `Admin:Dashboard:default`. -Și de unde știe Nette proper că `Admin:Dashboard` reprezintă clasa `App\Modules\Admin\Presenters\DashboardPresenter`? Acest lucru este determinat de [cartografierea |#mapping] din configurație. -Așadar, structura dată nu este prestabilită și o puteți modifica în funcție de nevoile dumneavoastră. +În aplicație, ne referim la prezentatorul `Dashboard` din cadrul modulului `Admin` folosind notația două puncte ca `Admin:Dashboard`. Pentru acțiunea `default`, ne referim la acesta ca `Admin:Dashboard:default`. -Modulele pot conține, bineînțeles, toate celelalte elemente în afară de prezentatori și șabloane, cum ar fi componente, clase de modele etc. +Structura prezentată nu este rigidă; [o |#mapping] puteți [personaliza complet |#mapping] în funcție [de nevoile dumneavoastră |#mapping] în cadrul configurației. .[tip] + +Modulele pot include toate celelalte fișiere, cum ar fi componentele și clasele auxiliare, pe lângă prezentatori și șabloane. Dacă vă gândiți unde să le plasați pe acestea, luați în considerare utilizarea unui dosar `Accessory`: + +/--pre +app/ +├── UI/ +│ ├── Admin/ +│ │ ├── Accessory/ +│ │ │ ├── FormFactory.php +│ │ │ └── AdminLayout.php +│ │ ├── Dashboard/ +│ │ └── ... +\-- Module imbricate .[#toc-nested-modules] --------------------------------------- -Modulele nu trebuie să formeze doar o structură plată, ci puteți crea și submodule, de exemplu: +Modulele pot avea mai multe niveluri de anvelopare, similar cu o structură de directoare pe un disc: /--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 │ │ └── ... \-- -Astfel, modulul `Blog` este împărțit în submodulele `Admin` și `Front`. Din nou, acest lucru se va reflecta în spațiile de nume, care vor fi `App\Modules\Blog\Admin\Presenters` etc. Prezentatorul `Dashboard` din interiorul submodulului este denumit `Blog:Admin:Dashboard`. +Modulul `Blog` este împărțit în submodulele `Admin` și `Front`. Acest lucru se reflectă și în spațiile de nume, care apar apoi ca `App\UI\Blog\Admin` și similar. Pentru a ne referi la prezentatorul `Dashboard` din cadrul submodulului `Admin`, ne referim la acesta ca `Blog:Admin:Dashboard`. -Anveloparea poate merge cât de adânc doriți, astfel încât pot fi create sub-submodule. +Nesting-ul poate fi atât de adânc cât este necesar, permițând crearea de sub-submodule. + +De exemplu, dacă în administrație aveți mulți prezentatori legați de gestionarea comenzilor, cum ar fi `OrderDetail`, `OrderEdit`, , `OrderDispatch`, etc., puteți crea un modul `Order` în care vor fi organizați prezentatori precum `Detail`, `Edit`, `Dispatch`, și alții. Crearea de legături .[#toc-creating-links] @@ -102,46 +118,66 @@ A se vedea [capitolul privind rutarea |routing#Modules]. Cartografiere .[#toc-mapping] ----------------------------- -Definește regulile prin care numele clasei este derivat din numele prezentatorului. Le scriem în [configurație |configuration] sub cheia `application › mapping`. +Cartografierea definește regulile de derivare a numelui clasei din numele prezentatorului. Aceste reguli sunt specificate în [configurație |configuration] la cheia `application › mapping`. + +Structurile de directoare menționate anterior pe această pagină se bazează pe următoarea cartografiere: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Să începem cu un exemplu care nu folosește module. Vom dori doar ca clasele de prezentator să aibă spațiul de nume `App\Presenters`. Aceasta înseamnă că un prezentator precum `Home` ar trebui să se mapeze la clasa `App\Presenters\HomePresenter`. Acest lucru poate fi realizat prin următoarea configurație: +Cum funcționează cartografierea? Pentru o mai bună înțelegere, să ne imaginăm mai întâi o aplicație fără module. Dorim ca clasele de prezentatori să se încadreze în spațiul de nume `App\UI`, astfel încât prezentatorul `Home` să fie asociat cu clasa `App\UI\HomePresenter`. Acest lucru poate fi realizat cu această configurație: ```neon application: - mapping: App\Presenters\*Presenter + mapping: App\UI\*Presenter ``` -Numele prezentatorului este înlocuit cu un asterisc în masca clasei, iar rezultatul este numele clasei. Ușor! +Această cartografiere funcționează prin înlocuirea asteriscului din masca `App\UI\*Presenter` cu numele prezentatorului `Home`, rezultând numele final al clasei `App\UI\HomePresenter`. Simplu! + +Cu toate acestea, după cum puteți vedea în exemplele din acest capitol și din alte capitole, plasăm clasele de prezentatori în subdirectoare eponime, de exemplu, prezentatorul `Home` este mapat în clasa `App\UI\Home\HomePresenter`. Acest lucru se realizează prin dublarea asteriscului (necesită Nette Application 3.2): + +```neon +application: + mapping: App\UI\**Presenter +``` -Dacă împărțim prezentatorii în module, putem avea propria mapare pentru fiecare modul: +Acum, să trecem la maparea prezentatorilor în module. Putem defini mape specifice pentru fiecare modul: ```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 ``` -Acum, prezentatorul `Front:Home` se referă la clasa `App\Modules\Front\Presenters\HomePresenter` și prezentatorul `Admin:Dashboard` la clasa `App\Modules\Admin\Presenters\DashboardPresenter`. +În conformitate cu această configurație, prezentatorul `Front:Home` se mapează la clasa `App\UI\Front\Home\HomePresenter`, în timp ce prezentatorul `Api:OAuth` se mapează la clasa `App\Api\OAuthPresenter`. -Este mai practic să creăm o regulă generală (stea) care să le înlocuiască pe primele două. Asteriscul suplimentar va fi adăugat la masca clasei doar pentru modul: +Deoarece modulele `Front` și `Admin` au o abordare similară de cartografiere și este posibil să existe mai multe astfel de module, este posibil să se creeze o regulă generală care să le înlocuiască. Un nou asterisc pentru modul este adăugat la masca clasei: ```neon application: mapping: - *: App\Modules\*\Presenters\*Presenter + *: App\UI\*\**Presenter Api: App\Api\*Presenter ``` -Dar ce se întâmplă dacă folosim module imbricate și avem un prezentator `Admin:User:Edit`? În acest caz, segmentul cu un asterisc care reprezintă modulul pentru fiecare nivel se repetă pur și simplu, iar rezultatul este clasa `App\Modules\Admin\User\Presenters\EditPresenter`. +Pentru modulele imbricate pe mai multe niveluri, cum ar fi prezentatorul `Admin:User:Edit`, segmentul de asterisc se repetă pentru fiecare nivel, rezultând clasa `App\UI\Admin\User\Edit\EditPresenter`. -O notație alternativă este utilizarea unui array format din trei segmente în loc de un șir de caractere. Această notație este echivalentă cu cea anterioară: +O notație alternativă constă în utilizarea unei matrice compuse din trei segmente în locul unui șir de caractere. Această notație este echivalentă cu cea anterioară: ```neon application: mapping: - *: [App\Modules, *, Presenters\*Presenter] + *: [App\UI, *, **Presenter] + Api: [App\Api, '', *Presenter] ``` -Valoarea implicită este `*Module\*Presenter`. +Dacă avem doar o singură regulă în configurație, cea generală, putem scrie pe scurt: + +```neon +application: + mapping: App\UI\*\**Presenter +``` diff --git a/application/ro/presenters.texy b/application/ro/presenters.texy index 76c214a56a..56eef37cb0 100644 --- a/application/ro/presenters.texy +++ b/application/ro/presenters.texy @@ -60,7 +60,7 @@ Similar cu metoda `render()`. În timp ce `render()` este destinată Este important ca `action()` este apelat înainte de `render()`, astfel încât în interiorul acesteia să putem eventual să schimbăm următorul curs al ciclului de viață, adică să schimbăm șablonul care va fi redat și, de asemenea, metoda `render()` care va fi apelată, folosind `setView('otherView')`. -Parametrii din cerere sunt trecuți către metodă. Este posibil și recomandat să se precizeze tipurile pentru parametri, de exemplu `actionShow(int $id, string $slug = null)` - dacă parametrul `id` lipsește sau dacă nu este un număr întreg, prezentatorul returnează [eroarea 404 |#Error 404 etc.] și încheie operațiunea. +Parametrii din cerere sunt trecuți către metodă. Este posibil și recomandat să se precizeze tipurile pentru parametri, de exemplu `actionShow(int $id, ?string $slug = null)` - dacă parametrul `id` lipsește sau dacă nu este un număr întreg, prezentatorul returnează [eroarea 404 |#Error 404 etc.] și încheie operațiunea. `handle(args...)` .{toc: handle()} @@ -205,7 +205,7 @@ $this->redirect(/* ... */); Eroare 404 etc. .[#toc-error-404-etc] ===================================== -Atunci când nu putem îndeplini cererea deoarece, de exemplu, articolul pe care dorim să îl afișăm nu există în baza de date, vom arunca eroarea 404 folosind metoda `error(string $message = null, int $httpCode = 404)`, care reprezintă eroarea HTTP 404: +Atunci când nu putem îndeplini cererea deoarece, de exemplu, articolul pe care dorim să îl afișăm nu există în baza de date, vom arunca eroarea 404 folosind metoda `error(?string $message = null, int $httpCode = 404)`, care reprezintă eroarea HTTP 404: ```php public function renderShow(int $id): void @@ -384,7 +384,7 @@ Redirecționarea nu are loc cu o cerere AJAX sau POST, deoarece ar duce la pierd De asemenea, puteți invoca manual canonizarea folosind metoda `canonicalize()`, care, ca și metoda `link()`, primește ca argumente prezentatorul, acțiunile și parametrii. Aceasta creează o legătură și o compară cu URL-ul curent. Dacă este diferit, redirecționează către link-ul generat. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // redirecționează dacă $slug este diferit de $realSlug @@ -452,17 +452,6 @@ Restricții de acces Utilizarea `#[Requires]` .[#toc-access-restriction-using-re Aplicația `#[Requires]` oferă opțiuni avansate pentru restricționarea accesului la prezentatori și la metodele acestora. Acesta poate fi utilizat pentru a specifica metodele HTTP, pentru a solicita cereri AJAX, pentru a limita accesul la aceeași origine și pentru a restricționa accesul doar la redirecționare. Atributul poate fi aplicat atât claselor de prezentatori, cât și metodelor individuale, cum ar fi `action()`, `render()`, `handle()`, și `createComponent()`. -Iată un exemplu de utilizare pentru a restricționa accesul doar la metoda HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Puteți specifica aceste restricții: - pe metodele HTTP: `#[Requires(methods: ['GET', 'POST'])]` - care necesită o cerere AJAX: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ Puteți specifica aceste restricții: - acces numai prin redirecționare: `#[Requires(forward: true)]` - restricții privind anumite acțiuni: `#[Requires(actions: 'default')]` -Condițiile pot fi combinate prin enumerarea mai multor atribute sau prin unirea lor într-unul singur: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Pentru detalii, consultați [Cum se utilizează Requires |best-practices:attribute-requires]. Verificarea metodei HTTP .[#toc-http-method-check] diff --git a/application/ro/routing.texy b/application/ro/routing.texy index 4f80ca99be..d70bec707c 100644 --- a/application/ro/routing.texy +++ b/application/ro/routing.texy @@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +225,7 @@ $router->addRoute('/[/]', [ ]); ``` -Sau putem folosi această formă, observați rescrierea expresiei regulate de validare: +Pentru o specificație mai detaliată, se poate utiliza o formă și mai extinsă, în care, pe lângă valorile implicite, pot fi stabilite și alte proprietăți ale parametrilor, cum ar fi o expresie regulată de validare (a se vedea parametrul `id` ): ```php use Nette\Routing\Route; @@ -243,7 +243,7 @@ $router->addRoute('/[/]', [ ]); ``` -Aceste formate mai vorbărețe sunt utile pentru adăugarea altor metadate. +Este important de reținut faptul că, dacă parametrii definiți în matrice nu sunt incluși în masca de cale, valorile lor nu pot fi modificate, nici măcar folosind parametrii de interogare specificați după un semn de întrebare în URL. Filtre și traduceri .[#toc-filters-and-translations] @@ -477,10 +477,10 @@ $router->addRoute('index', /* ... */); Integrare .[#toc-integration] ============================= -Pentru a conecta routerul nostru la aplicație, trebuie să informăm containerul DI despre acesta. Cel mai simplu este să pregătim fabrica care va construi obiectul router și să spunem configurației containerului să o folosească. Să spunem că scriem o metodă în acest scop `App\Router\RouterFactory::createRouter()`: +Pentru a conecta routerul nostru la aplicație, trebuie să informăm containerul DI despre acesta. Cel mai simplu este să pregătim fabrica care va construi obiectul router și să spunem configurației containerului să o folosească. Să spunem că scriem o metodă în acest scop `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -499,7 +499,7 @@ Apoi scriem în [configurație |dependency-injection:services]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Orice dependență, cum ar fi o conexiune la o bază de date etc., este transmisă metodei factory ca parametru al acesteia folosind [autowiring |dependency-injection:autowiring]: @@ -663,7 +663,7 @@ Prin utilizare separată se înțelege utilizarea capacităților routerului în Deci, din nou, vom crea o metodă care va construi un router, de exemplu: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Sau vom crea obiecte direct: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/ro/templates.texy b/application/ro/templates.texy index abcead647e..043a5fb016 100644 --- a/application/ro/templates.texy +++ b/application/ro/templates.texy @@ -34,35 +34,81 @@ Iar acesta ar putea fi șablonul de acțiune: Acesta definește blocul `content`, care este inserat în locul lui `{include content}` în machetă și, de asemenea, redefinește blocul `title`, care suprascrie `{block title}` în machetă. Încercați să vă imaginați rezultatul. -Căutați șabloane .[#toc-search-for-templates] ---------------------------------------------- +Căutarea șablonului .[#toc-template-lookup] +------------------------------------------- + +În prezentări, nu trebuie să specificați ce șablon trebuie redat; cadrul va determina automat calea, ceea ce vă ușurează codificarea. + +Dacă utilizați o structură de directoare în care fiecare prezentator are propriul director, plasați pur și simplu șablonul în acest director sub numele acțiunii (de exemplu, view). De exemplu, pentru acțiunea `default`, utilizați șablonul `default.latte`: -Calea către șabloane este dedusă conform unei logici simple. Se încearcă să se vadă dacă unul dintre aceste fișiere șablon există în raport cu directorul în care se află clasa presenter, unde `` este numele prezentatorului curent și `` este numele acțiunii curente: +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -- `templates//.latte` -- `templates/..latte` +Dacă utilizați o structură în care prezentatorii sunt împreună într-un singur director, iar șabloanele într-un dosar `templates`, salvați-l fie într-un fișier `..latte` sau `/.latte`: -Dacă șablonul nu este găsit, se va încerca căutarea în directorul `templates` cu un nivel mai sus, adică la același nivel cu directorul cu clasa de prezentator. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- -Dacă șablonul nu este găsit nici acolo, răspunsul este o [eroare 404 |presenters#Error 404 etc.]. +Directorul `templates` poate fi, de asemenea, plasat un nivel mai sus, la același nivel cu directorul cu clasele de prezentatori. -De asemenea, puteți schimba vizualizarea folosind `$this->setView('otherView')`. Sau, în loc să căutați, specificați direct numele fișierului șablon folosind `$this->template->setFile('/path/to/template.latte')`. +În cazul în care șablonul nu este găsit, prezentatorul răspunde cu [eroarea 404 - page not found |presenters#Error 404 etc]. + +Puteți schimba vizualizarea folosind `$this->setView('anotherView')`. De asemenea, este posibilă specificarea directă a fișierului șablon cu `$this->template->setFile('/path/to/template.latte')`. .[note] -Puteți modifica căile în care sunt căutate șabloanele prin suprascrierea metodei [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], care returnează o matrice de posibile căi de acces la fișiere. +Fișierele în care sunt căutate șabloanele pot fi modificate prin suprascrierea metodei [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], care returnează o matrice de nume de fișiere posibile. + + +Căutarea șabloanelor de prezentare .[#toc-layout-template-lookup] +----------------------------------------------------------------- + +De asemenea, Nette caută automat fișierul de machetare. + +Dacă folosiți o structură de directoare în care fiecare prezentator are propriul director, plasați macheta fie în folderul cu prezentatorul, dacă este specifică doar acestuia, fie la un nivel superior, dacă este comună mai multor prezentatori: + +/--pre +app/ +└── UI/ + ├── @layout.latte ← common layout + └── Home/ + ├── @layout.latte ← only for Home presenter + ├── HomePresenter.php + └── default.latte +\-- + +Dacă utilizați o structură în care prezentatorii sunt grupați într-un singur director, iar șabloanele se află într-un dosar `templates`, macheta va fi așteptată în următoarele locuri: -Prezentarea este așteptată în următoarele fișiere: +/--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` aspect comun pentru mai mulți prezentatori +Dacă prezentatorul se află într-un [modul |modules], se va căuta și mai sus în arborele de directoare, în funcție de structura de anvelopare a modulului. -`` este numele prezentatorului curent și `` este numele structurii, care este în mod implicit `'layout'`. Numele poate fi schimbat cu `$this->setLayout('otherLayout')`, astfel încât să se încerce să se utilizeze fișierele `@otherLayout.latte`. +Numele prezentării poate fi schimbat folosind `$this->setLayout('layoutAdmin')` și apoi va fi așteptat în fișierul `@layoutAdmin.latte`. De asemenea, puteți specifica direct fișierul șablon de prezentare folosind `$this->setLayout('/path/to/template.latte')`. -De asemenea, puteți specifica direct numele de fișier al șablonului de machetă cu `$this->setLayout('/path/to/template.latte')`. Utilizarea `$this->setLayout(false)` va dezactiva căutarea de machete. +Utilizarea `$this->setLayout(false)` sau a etichetei `{layout none}` în interiorul șablonului dezactivează căutarea de layout. .[note] -Puteți modifica căile de căutare a șabloanelor prin suprascrierea metodei [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], care returnează o matrice de posibile căi de acces la fișiere. +Fișierele în care sunt căutate șabloanele de prezentare pot fi modificate prin suprascrierea metodei [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], care returnează o matrice de nume de fișiere posibile. Variabilele din șablon .[#toc-variables-in-the-template] @@ -104,7 +150,7 @@ Adnotarea `@property-read` este pentru IDE și analiza statică, va face ca auto Vă puteți permite și luxul de a șopti în șabloane, trebuie doar să instalați pluginul în PhpStorm și să specificați numele clasei la începutul șablonului, consultați articolul "Latte: cum se tastează sistemul":https://blog.nette.org/ro/latte-cum-se-utilizeaza-sistemul-de-tipuri: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void Latte versiunea 3 oferă o metodă mai avansată prin crearea unei [extensii |latte:creating-extension] pentru fiecare proiect web. Iată un exemplu aproximativ al unei astfel de clase: ```php -namespace App\Templating; +namespace App\UI\Accessory; final class LatteExtension extends Latte\Extension { @@ -214,7 +260,7 @@ O înregistrăm folosind [configuration#Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\UI\Accessory\LatteExtension ``` @@ -239,7 +285,7 @@ Alternativ, traducătorul poate fi setat cu ajutorul [configurației |configurat ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` Traducătorul poate fi apoi utilizat, de exemplu, ca filtru `|translate`, cu parametri suplimentari trecuți la metoda `translate()` (a se vedea `foo, bar`): diff --git a/application/ru/ajax.texy b/application/ru/ajax.texy index bed9143e1e..ffcb026607 100644 --- a/application/ru/ajax.texy +++ b/application/ru/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Сначала нужно [инициализировать |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] библиотеку: + +```js +naja.initialize(); +``` + Чтобы превратить обычную ссылку (сигнал) или отправку формы в AJAX-запрос, достаточно пометить соответствующую ссылку, форму или кнопку классом `ajax`: ```html diff --git a/application/ru/bootstrap.texy b/application/ru/bootstrap.texy index 6e041613e9..82e18f8d6b 100644 --- a/application/ru/bootstrap.texy +++ b/application/ru/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 ========= -В случае веб-приложений начальным файлом является `index.php`, который находится в общедоступном каталоге `www/`. Он позволяет классу `Bootstrap` инициализировать среду и возвращает `$configurator`, который создает контейнер DI. Затем он получает сервис `Application`, запускающий веб-приложение: +Начальным файлом для веб-приложений является `index.php`, расположенный в публичной директории `www/`. Он использует класс `Bootstrap` для инициализации среды и создания контейнера DI. Затем он получает из контейнера службу `Application`, которая запускает веб-приложение: ```php -// инициализируем среду + получаем объект Configurator -$configurator = App\Bootstrap::boot(); -// создаем DI-контейнер -$container = $configurator->createContainer(); -// DI-контейнер создайет объект Nette\Application\Application +$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 @@ Nette различает два основных режима, в которых Если вы хотите включить режим разработки в других случаях, например, для программистов, получающих доступ с определенного IP-адреса, вы можете использовать `setDebugMode()`: ```php -$configurator->setDebugMode('23.75.345.200'); // один или более IP-адресов +$this->configurator->setDebugMode('23.75.345.200'); // один или более IP-адресов ``` Мы определенно рекомендуем сочетать IP-адрес с файлом cookie. Мы будем хранить секретный токен в 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-контейнера, 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 Обычно мы хотим автоматически загружать классы с помощью [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/services.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()`, чтобы различать веб-проекты. Также можно добавить другие методы, например `bootTestEnvironment()` для инициализации окружения для модульных тестов, `bootConsoleApplication()` для скриптов, вызываемых из командной строки, и так далее. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Инициализация тестера Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container { - $configurator = self::boot(); - Tester\Environment::setup(); // Инициализация Nette Tester - return $configurator; + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); } ``` diff --git a/application/ru/components.texy b/application/ru/components.texy index 9c9fa01111..31c39481d0 100644 --- a/application/ru/components.texy +++ b/application/ru/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] ================================================== diff --git a/application/ru/configuration.texy b/application/ru/configuration.texy index 9cb80fc192..0121aa4071 100644 --- a/application/ru/configuration.texy +++ b/application/ru/configuration.texy @@ -95,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 ``` @@ -104,7 +107,7 @@ latte: ```neon latte: расширения: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/ru/how-it-works.texy b/application/ru/how-it-works.texy index 39b20c686c..6e3908f7e0 100644 --- a/application/ru/how-it-works.texy +++ b/application/ru/how-it-works.texy @@ -22,13 +22,13 @@ /--pre web-project/ ├── app/ ← каталог с приложением -│ ├── Presenters/ ← классы презентеров -│ │ ├── HomePresenter.php ← Класс презентера главной страницы -│ │ └── templates/ ← директория шаблонов -│ │ ├── @layout.latte ← шаблон общего макета -│ │ └── Home/ ← шаблоны презентера главной страницы -│ │ └── default.latte ← шаблон действия `default` -│ ├── Router/ ← конфигурация URL-адресов +│ ├── Core/ ← основные необходимые классы +│ │ └── RouterFactory.php ← настройка URL-адресов +│ ├── UI/ ← презентаторы, шаблоны и др. +│ │ ├── @layout.latte ← шаблон общего макета +│ │ └── Home/ ← Главная директория ведущих +│ │ ├── HomePresenter.php ← Класс презентера дома +│ │ └── default.latte ← шаблон для действий default │ └── Bootstrap.php ← загрузочный класс Bootstrap ├── bin/ ← скрипты командной строки ├── config/ ← файлы конфигурации @@ -91,7 +91,7 @@ Nette — это наставник, который направляет вас Приложение начинает работу с того, что просит так называемый маршрутизатор решить, какому из презентеров передать текущий запрос на обработку. Маршрутизатор решает, чья это ответственность. Он просматривает входной URL `https://example.com/product/123` и, основываясь на том, как он настроен, решает, что это задание, например, для **презентера** `Product`, который хочет `показать` продукт с `id: 123` как действие. Хорошей привычкой является написание пар презентер + действие, разделенных двоеточием: `Продукт:показать`. -Поэтому маршрутизатор преобразовал 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 создать презентера, потому что создание объектов — это его работа. @@ -121,12 +121,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter После этого презентер возвращает ответ. Это может быть HTML-страница, изображение, XML-документ, отправка файла с диска, JSON или перенаправление на другую страницу. Важно отметить, что если мы явно не указываем, как реагировать (что имеет место в случае с `ProductPresenter`), ответом будет отображение шаблона с HTML-страницей. Почему? Ну, потому что в 99% случаев мы хотим отобразить шаблон, поэтому презентер принимает такое поведение по умолчанию и хочет облегчить нашу работу. Это точка зрения Nette. -Нам даже не нужно указывать, какой шаблон нужно вывести, он сам выводит путь к нему в соответствии с простой логикой. В случае с презентером `Product` и действием `show`, он пытается проверить, существует ли один из этих файлов шаблонов относительно каталога, в котором находится класс `ProductPresenter`: +Нам даже не нужно указывать, какой шаблон рендерить, фреймворк сам определит путь. В случае с действием `show` он просто попытается загрузить шаблон `show.latte` в директории с классом `ProductPresenter`. Он также попытается найти макет в файле `@layout.latte` (подробнее о [поиске шаблонов |templates#Template Lookup]). -- `templates/Product/show.latte` -- `templates/Product.show.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/ru/modules.texy b/application/ru/modules.texy index e7a6b8b277..8159f0a30d 100644 --- a/application/ru/modules.texy +++ b/application/ru/modules.texy @@ -2,29 +2,31 @@ ****** .[perex] -В Nette модули представляют собой логические единицы, из которых состоит приложение. Они включают ведущие, шаблоны, возможно, компоненты и классы моделей. +Модули вносят ясность в приложения Nette, облегчая разделение на логические блоки. -Одного компонента для презентаторов и одного для шаблонов будет недостаточно для реальных проектов. Наличие десятков файлов в одной папке по меньшей мере неорганизованно. Как выйти из этого положения? Мы просто разделяем их на подкаталоги на диске и на пространства имен в коде. И это именно то, что делают модули Nette. - -Поэтому давайте забудем о единой папке для ведущих и шаблонов и вместо этого создадим модули, например, `Admin` и `Front`. +Подобно организации файлов в папки на жестком диске, в Nette мы можем разделить презентаторы, шаблоны и другие вспомогательные классы на модули. Как это работает на практике? Просто путем включения в структуру новых подкаталогов. Вот пример структуры с двумя модулями - Front и Admin: /--pre -app/ -├── Presenters/ -├── Modules/ ← директория с модулями -│ ├── Admin/ ← модуль Admin -│ │ ├── Presenters/ ← его презентеры -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← модуль Front -│ └── Presenters/ ← его презентеры -│ └── ... +app/ +├── UI/ +│ ├── Admin/ ← Admin module +│ │ ├── @layout.latte +│ │ ├── Dashboard/ +│ │ │ ├── DashboardPresenter.php +│ │ │ └── default.latte +│ │ └── ... +│ ├── Front/ ← Front module +│ │ ├── @layout.latte +│ │ ├── Home/ +│ │ │ ├── HomePresenter.php +│ │ │ └── default.latte +│ │ └── ... \-- -Эта структура каталогов будет отражена в пространствах имен классов, так, например, `DashboardPresenter` будет находиться в пространстве `App\Modules\Admin\Presenters`: +Эта структура каталогов отражается в пространствах имен классов, так, например, `DashboardPresenter` находится в пространстве имен `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 знает, что `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/ ← директория с модулями -│ ├── Blog/ ← модуль Blog -│ │ ├── Admin/ ← подмодуль Admin -│ │ │ ├── Presenters/ +app/ +├── UI/ +│ ├── Blog/ ← Blog module +│ │ ├── Admin/ ← Admin submodule +│ │ │ ├── Dashboard/ +│ │ │ └── ... +│ │ ├── Front/ ← Front submodule +│ │ │ ├── @layout.latte +│ │ │ ├── Home/ │ │ │ └── ... -│ │ └── Front/ ← подмодуль Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← модуль Forum +│ ├── Forum/ ← Forum module │ │ └── ... \-- -Таким образом, модуль `Blog` разбивается на подмодули `Admin` и `Front`. И опять же это будет отражено в пространствах имен, которые будут `App\Modules\Blog\Admin\Presenters` и т.д. Ведущий `Dashboard` внутри подмодуля называется `Blog:Admin:Dashboard`. +Модуль `Blog` делится на подмодули `Admin` и `Front`. Это также отражено в пространствах имен, которые отображаются как `App\UI\Blog\Admin` и аналогично. Для обозначения ведущего `Dashboard` в подмодуле `Admin` мы обозначаем его как `Blog:Admin:Dashboard`. -Ветвление может быть настолько глубоким, насколько вы захотите, поэтому вы можете создавать подмодули. +Вложенность может быть настолько глубокой, насколько это необходимо, что позволяет создавать подмодули. + +Например, если в администрировании есть много ведущих, связанных с управлением заказами, таких как `OrderDetail`, `OrderEdit`, `OrderDispatch`, и т. д., можно создать модуль `Order`, в котором будут организованы ведущие `Detail`, `Edit`, `Dispatch`, и другие. Создание ссылок .[#toc-creating-links] @@ -102,46 +118,66 @@ class DashboardPresenter extends Nette\Application\UI\Presenter Составление карты .[#toc-mapping] --------------------------------- -Определяет правила, по которым имя класса выводится из имени ведущего. Мы записываем их в [конфигурацию |configuration] под ключом `application › mapping`. +Mapping определяет правила получения имени класса из имени ведущего. Эти правила задаются в [конфигурации |configuration] под ключом `application › mapping`. + +Структуры каталогов, упомянутые ранее на этой странице, основаны на следующем отображении: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Начнем с примера, в котором не используются модули. Мы просто хотим, чтобы классы ведущего имели пространство имен `App\Presenters`. То есть мы хотим, чтобы ведущий, например, `Home` отображался на класс `App\Presenters\HomePresenter`. Этого можно достичь с помощью следующей конфигурации: +Как работает отображение? Для лучшего понимания давайте сначала представим себе приложение без модулей. Мы хотим, чтобы классы ведущих относились к пространству имен `App\UI`, так что ведущий `Home` отображается на класс `App\UI\HomePresenter`. Этого можно добиться с помощью следующей конфигурации: ```neon application: - mapping: App\Presenters\*Presenter + 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\HomePresenter`, а презентер `Admin:Dashboard` — `App\AdminModule\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/ru/presenters.texy b/application/ru/presenters.texy index 1e75735ac8..ac48e3a344 100644 --- a/application/ru/presenters.texy +++ b/application/ru/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 @@ -384,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 @@ -452,17 +452,6 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); Атрибут `#[Requires]` предоставляет расширенные возможности для ограничения доступа к ведущим и их методам. С его помощью можно указать HTTP-методы, потребовать AJAX-запросы, ограничить доступ к одному и тому же источнику и ограничить доступ только пересылкой. Атрибут может применяться как к классам ведущих, так и к отдельным методам, таким как `action()`, `render()`, `handle()`, и `createComponent()`. -Вот пример использования этого метода для ограничения доступа только к методу HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Вы можете указать эти ограничения: - на методы HTTP: `#[Requires(methods: ['GET', 'POST'])]` - требующих AJAX-запроса: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ class MyPresenter extends Nette\Application\UI\Presenter - доступ только через переадресацию: `#[Requires(forward: true)]` - ограничения на определенные действия: `#[Requires(actions: 'default')]` -Условия можно комбинировать, перечисляя несколько атрибутов или объединяя их в один: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Подробнее см. в разделе [Как использовать Requires атрибут |best-practices:attribute-requires]. Проверка метода HTTP .[#toc-http-method-check] diff --git a/application/ru/routing.texy b/application/ru/routing.texy index dd37c4fd4d..3363e2d1d5 100644 --- a/application/ru/routing.texy +++ b/application/ru/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', /* ... */); Интеграция .[#toc-integration] ============================== -Чтобы подключить наш маршрутизатор к приложению, мы должны сообщить о нем контейнеру DI. Самый простой способ - это подготовить фабрику, которая будет создавать объект маршрутизатора, и сообщить конфигурации контейнера, чтобы она его использовала. Допустим, мы напишем для этого метод `App\Router\RouterFactory::createRouter()`: +Чтобы подключить наш маршрутизатор к приложению, мы должны сообщить о нем контейнеру DI. Самый простой способ - это подготовить фабрику, которая будет создавать объект маршрутизатора, и сообщить конфигурации контейнера, чтобы она его использовала. Допустим, мы напишем для этого метод `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 ``` Любые зависимости, такие как подключение к базе данных и т.д., передаются методу фабрики в качестве параметров с помощью [autowiring |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/ru/templates.texy b/application/ru/templates.texy index 75f2c784d0..4be9f83e4d 100644 --- a/application/ru/templates.texy +++ b/application/ru/templates.texy @@ -34,35 +34,81 @@ Nette использует систему шаблонов [Latte |latte:]. Latt Он определяет блок `content`, который вставляется вместо `{include content}` в макете, а также переопределяет блок `title`, который перезаписывает `{block title}` в макете. Попытайтесь представить себе результат. -Поиск шаблонов .[#toc-search-for-templates] -------------------------------------------- +Поиск шаблонов .[#toc-template-lookup] +-------------------------------------- + +В презентаторах вам не нужно указывать, какой шаблон должен быть отображен; фреймворк автоматически определит путь, облегчая вам кодирование. + +Если вы используете структуру каталогов, в которой у каждого ведущего есть своя директория, просто поместите шаблон в эту директорию под именем действия (т. е. представления). Например, для действия `default` используйте шаблон `default.latte`: -Путь к шаблонам определяется ведущим с помощью простой логики. Он попытается проверить, есть ли один из этих файлов, расположенный относительно каталога класса ведущего, где `` это имя текущего ведущего, а `` это имя текущего события: +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -- `templates//.latte` -- `templates/..latte` +Если вы используете структуру, в которой ведущие находятся в одном каталоге, а шаблоны - в папке `templates`, сохраните их либо в файле `..latte` или `/.latte`: -Если шаблон не найден, он попытается выполнить поиск в каталоге `templates` на один уровень выше, т.е. на том же уровне, что и каталог с классом ведущего. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- -Если шаблон не найден и там, ответом будет [ошибка 404 |presenters#Error 404 etc.]. +Каталог `templates` можно также разместить на один уровень выше, на том же уровне, что и каталог с классами ведущих. -Вы также можете изменить вид с помощью `$this->setView('jineView')`. Или, вместо прямого поиска, укажите имя файла шаблона с помощью `$this->template->setFile('/path/to/template.latte')`. +Если шаблон не найден, ведущий отвечает [ошибкой 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('jinyLayout')`, поэтому будут опробованы файлы `@jinyLayout.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/ru/latte-kak-ispol-zovat-sistemu-tipov: ```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/sl/ajax.texy b/application/sl/ajax.texy index bfbc97906b..320e94b27c 100644 --- a/application/sl/ajax.texy +++ b/application/sl/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Najprej morate knjižnico [inicializirati |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization]: + +```js +naja.initialize(); +``` + Če želite, da običajna povezava (signal) ali oddaja obrazca postane zahteva AJAX, preprosto označite ustrezno povezavo, obrazec ali gumb z razredom `ajax`: ```html diff --git a/application/sl/bootstrap.texy b/application/sl/bootstrap.texy index 840ebe2ab6..33f36e50e8 100644 --- a/application/sl/bootstrap.texy +++ b/application/sl/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__); + // Konfigurator je odgovoren za nastavitev okolja aplikacije in storitev. + $this->configurator = new Configurator; + // Nastavite imenik za začasne datoteke, ki jih ustvari Nette (npr. sestavljene predloge). + $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 + { + // Program Nette je pameten in razvojni način se vklopi samodejno, + // lahko pa ga za določen naslov IP omogočite tako, da odkomentirate naslednjo vrstico: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Omogoči Tracy: najboljše orodje za razhroščevanje "švicarskega noža". + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: samodejno naloži vse razrede v danem imeniku + $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; + } + + private function setupContainer(): void + { + // Nalaganje konfiguracijskih datotek + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } ``` @@ -40,16 +66,15 @@ class Bootstrap index.php .[#toc-index-php] =========================== -Pri spletnih aplikacijah je začetna datoteka `index.php`, ki se nahaja v javnem imeniku `www/`. Omogoča, da razred `Bootstrap` inicializira okolje in vrne `$configurator`, ki ustvari vsebnik DI. Nato pridobi storitev `Application`, ki izvaja spletno aplikacijo: +Začetna datoteka za spletne aplikacije je `index.php`, ki se nahaja v javnem imeniku `www/`. Uporablja razred `Bootstrap` za inicializacijo okolja in ustvarjanje vsebnika DI. Nato iz vsebnika pridobi storitev `Application`, ki zažene spletno aplikacijo: ```php -// inicializacija okolja + pridobitev predmeta Configurator -$configurator = App\Bootstrap::boot(); -// ustvarite vsebnik DI -$container = $configurator->createContainer(); +$bootstrap = new App\Bootstrap; +// Inicializacija okolja + ustvarjanje vsebnika DI +$container = $bootstrap->bootWebApplication(); // vsebnik DI ustvari objekt Nette\Application\Application $application = $container->getByType(Nette\Application\Application::class); -// zaženite aplikacijo Nette +// Zagnati aplikacijo Nette in obdelati vhodno zahtevo $application->run(); ``` @@ -66,19 +91,19 @@ Izbira načina poteka s samodejnim zaznavanjem, zato običajno ni treba ničesar Če želite omogočiti razvojni način v drugih primerih, na primer za programerje, ki dostopajo z določenega naslova IP, lahko uporabite `setDebugMode()`: ```php -$configurator->setDebugMode('23.75.345.200'); // enega ali več naslovov IP. +$this->configurator->setDebugMode('23.75.345.200'); // enega ali več naslovov IP. ``` Vsekakor priporočamo kombinacijo naslova IP s piškotkom. V piškotek `nette-debug` bomo shranili tajni žeton, npr. `secret1234`, razvojni način pa bo aktiviran za programerje s to kombinacijo IP in piškotka. ```php -$configurator->setDebugMode('secret1234@23.75.345.200'); +$this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` Razvojni način lahko tudi popolnoma izklopimo, tudi za lokalni gostitelj: ```php -$configurator->setDebugMode(false); +$this->configurator->setDebugMode(false); ``` Vrednost `true` vklopi način za razvijalce, kar se na produkcijskem strežniku ne bi smelo zgoditi. @@ -90,7 +115,7 @@ Orodje za razhroščevanje Tracy .[#toc-debugging-tool-tracy] Za lažje razhroščevanje bomo vklopili odlično orodje [Tracy |tracy:]. V načinu za razvijalce vizualizira napake, v produkcijskem načinu pa napake beleži v določen imenik: ```php -$configurator->enableTracy($appDir . '/log'); +$this->configurator->enableTracy($this->rootDir . '/log'); ``` @@ -100,7 +125,7 @@ Začasne datoteke .[#toc-temporary-files] Nette uporablja predpomnilnik za vsebnik DI, RobotLoader, predloge itd. Zato je treba nastaviti pot do imenika, v katerem bo shranjen predpomnilnik: ```php -$configurator->setTempDirectory($appDir . '/temp'); +$this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` V operacijskem sistemu Linux ali macOS nastavite [dovoljenja za pisanje za |nette:troubleshooting#Setting directory permissions] imenike `log/` in `temp/`. @@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader] Običajno bomo želeli samodejno naložiti razrede z [RobotLoaderjem |robot-loader:], zato ga moramo zagnati in mu omogočiti, da naloži razrede iz imenika, kjer se nahaja `Bootstrap.php` (tj. `__DIR__`), in vseh njegovih podimenikov: ```php -$configurator->createRobotLoader() +$this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); ``` @@ -126,7 +151,7 @@ Druga možnost je, da uporabimo samo samodejno nalaganje [Composer |best-practic Configurator vam omogoča, da določite časovni pas za svojo aplikacijo. ```php -$configurator->setTimeZone('Europe/Prague'); +$this->configurator->setTimeZone('Europe/Prague'); ``` @@ -143,16 +168,17 @@ V razvojnem načinu se vsebnik samodejno posodobi vsakič, ko spremenite kodo al Konfiguracijske datoteke se naložijo z uporabo `addConfig()`: ```php -$configurator->addConfig($appDir . '/config/common.neon'); +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` Metodo `addConfig()` lahko za dodajanje več datotek pokličete večkrat. ```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/services.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 @@ Statični parametri .[#toc-static-parameters] Parametre, ki se uporabljajo v konfiguracijskih datotekah, je mogoče opredeliti [v razdelku `parameters` |dependency-injection:configuration#parameters] in jih tudi posredovati (ali prepisati) z metodo `addStaticParameters()` (ima vzdevek `addParameters()`). Pomembno je, da različne vrednosti parametrov povzročijo generiranje dodatnih vsebnikov DI, tj. dodatnih razredov. ```php -$configurator->addStaticParameters([ +$this->configurator->addStaticParameters([ 'projectId' => 23, ]); ``` @@ -183,7 +209,7 @@ Dinamični parametri .[#toc-dynamic-parameters] Kontejnerju lahko dodamo tudi dinamične parametre, katerih različne vrednosti za razliko od statičnih parametrov ne bodo povzročile generiranja novih DI kontejnerjev. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'remoteIp' => $_SERVER['REMOTE_ADDR'], ]); ``` @@ -191,7 +217,7 @@ $configurator->addDynamicParameters([ Spremenljivke okolja bi lahko preprosto dali na voljo z dinamičnimi parametri. Do njih lahko dostopamo prek spletne strani `%env.variable%` v konfiguracijskih datotekah. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'env' => getenv(), ]); ``` @@ -206,6 +232,7 @@ V konfiguracijskih datotekah lahko uporabite naslednje statične parametre: - `%wwwDir%` je absolutna pot do imenika, ki vsebuje vstopno datoteko `index.php` - `%tempDir%` je absolutna pot do imenika za začasne datoteke - `%vendorDir%` je absolutna pot do imenika, v katerega Composer namesti knjižnice +- `%rootDir%` je absolutna pot do korenskega imenika projekta - `%debugMode%` označuje, ali je aplikacija v načinu odpravljanja napak - `%consoleMode%` označuje, ali je bila zahteva poslana prek ukazne vrstice @@ -225,7 +252,7 @@ services: Ustvarite nov primerek in ga vstavite v bootstrap: ```php -$configurator->addServices([ +$this->configurator->addServices([ 'myservice' => new App\Model\MyCustomService('foobar'), ]); ``` @@ -234,13 +261,21 @@ $configurator->addServices([ Različna okolja .[#toc-different-environments] ============================================== -Razred `Bootstrap` lahko prilagodite svojim potrebam. Metodi `boot()` lahko dodate parametre za razlikovanje med spletnimi projekti ali dodate druge metode, kot so `bootForTests()`, ki inicializira okolje za teste enote, `bootForCli()` za skripte, ki se kličejo iz ukazne vrstice, in tako naprej. +Ne oklevajte, če želite razred `Bootstrap` prilagoditi svojim potrebam. Metodi `bootWebApplication()` lahko dodate parametre za razlikovanje med spletnimi projekti. Lahko pa dodate tudi druge metode, na primer `bootTestEnvironment()` za inicializacijo okolja za teste enote, `bootConsoleApplication()` za skripte, ki se kličejo iz ukazne vrstice, in tako naprej. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container { - $configurator = self::boot(); Tester\Environment::setup(); // Inicializacija Nette Testerja - 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/sl/components.texy b/application/sl/components.texy index dd8ce1e37d..344d660993 100644 --- a/application/sl/components.texy +++ b/application/sl/components.texy @@ -230,6 +230,28 @@ V predlogi so ta sporočila na voljo v spremenljivki `$flashes` kot objekti `std ``` +Preusmeritev po signalu .[#toc-redirection-after-a-signal] +========================================================== + +Po obdelavi signala komponente pogosto sledi preusmeritev. Ta situacija je podobna obrazcem - po oddaji obrazca prav tako preusmerimo, da preprečimo ponovno oddajo podatkov ob osvežitvi strani v brskalniku. + +```php +$this->redirect('this') // redirects to the current presenter and action +``` + +Ker je komponenta element za večkratno uporabo in običajno ne sme biti neposredno odvisna od določenih predstavnikov, metodi `redirect()` in `link()` samodejno interpretirata parameter kot signal komponente: + +```php +$this->redirect('click') // redirects to the 'click' signal of the same component +``` + +Če želite preusmeriti na drug predstavnik ali dejanje, lahko to storite prek predstavnika: + +```php +$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action +``` + + Trajni parametri .[#toc-persistent-parameters] ============================================== diff --git a/application/sl/configuration.texy b/application/sl/configuration.texy index b12fdcd385..179324658b 100644 --- a/application/sl/configuration.texy +++ b/application/sl/configuration.texy @@ -95,6 +95,9 @@ latte: # omogoča [preverjanje ustvarjene kode |latte:develop#Checking Generated Code] phpLinter: ... # (string) privzeto je null + # nastavi krajevni jezik + locale: cs_CZ # (string) privzeto je nič + # razred $this->template templateClass: App\MyTemplateClass # privzeto je Nette\Bridges\ApplicationLatte\DefaultTemplate ``` @@ -104,7 +107,7 @@ latte: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/sl/how-it-works.texy b/application/sl/how-it-works.texy index 0149fdc95f..53d08d145d 100644 --- a/application/sl/how-it-works.texy +++ b/application/sl/how-it-works.texy @@ -22,13 +22,13 @@ Struktura imenikov je videti nekako takole: /--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/ ← osnovni potrebni razredi +│ │ └── RouterFactory.php ← konfiguracija naslovov URL +│ ├── UI/ ← predstavitev, predloge in podobno. +│ │ ├── @layout.latte ← predloga skupne postavitve +│ │ └── Home/ ← Domači imenik predstavnikov +│ │ ├── HomePresenter.php ← Razred HomePresenter +│ │ └── default.latte ← predloga za akcijo default │ └── Bootstrap.php ← booting class Bootstrap ├── bin/ ← scripts for the command line ├── config/ ← configuration files @@ -91,7 +91,7 @@ Aplikacije, napisane v okolju Nette, so razdeljene na številne tako imenovane p Aplikacija se začne tako, da zahteva od tako imenovanega usmerjevalnika, da se odloči, kateremu od predstavnikov bo posredoval trenutno zahtevo v obdelavo. Usmerjevalnik se odloči, čigava je to odgovornost. Pregleda vhodni naslov URL `https://example.com/product/123`, ki želi `show` izdelek z `id: 123` kot dejanjem. Dobra navada je, da se pari predstavnik + akcija, ločeni z dvopičjem, zapišejo kot `Product:show`. -Usmerjevalnik je torej pretvoril naslov URL v par `Presenter:action` + parametri, v našem primeru `Product:show` + `id: 123`. Kako je videti usmerjevalnik, si lahko ogledate v datoteki `app/Router/RouterFactory.php`, podrobno pa ga bomo opisali v poglavju [Usmerjanje |Routing]. +Usmerjevalnik je torej pretvoril naslov URL v par `Presenter:action` + parametri, v našem primeru `Product:show` + `id: 123`. Kako je videti usmerjevalnik, si lahko ogledate v datoteki `app/Core/RouterFactory.php`, podrobno pa ga bomo opisali v poglavju [Usmerjanje |Routing]. Pojdimo naprej. Aplikacija že pozna ime predavatelja in lahko nadaljuje. Z ustvarjanjem predmeta `ProductPresenter`, ki je koda predstavnika `Product`. Natančneje, za ustvarjanje predstavnika zaprosi vsebnik DI, saj je izdelovanje objektov njegova naloga. @@ -121,12 +121,9 @@ Tako je bila klicana metoda `renderShow(123)`, katere koda je izmišljen primer, Nato predstavnik vrne odgovor. To je lahko stran HTML, slika, dokument XML, pošiljanje datoteke z diska, JSON ali preusmeritev na drugo stran. Pomembno je, da če izrecno ne navedemo, kako odgovoriti (kar je primer `ProductPresenter`), bo odgovor prikaz predloge s stranjo HTML. Zakaj? No, ker v 99 % primerov želimo izrisati predlogo, zato predstavnik to vedenje sprejme kot privzeto in nam želi olajšati delo. To je Nettejeva poanta. -Ni nam treba niti navesti, katero predlogo želimo narisati, on pot do nje izpelje po preprosti logiki. V primeru predstavnika `Product` in akcije `show`, poskuša preveriti, ali obstaja ena od teh datotek s predlogami glede na imenik, v katerem se nahaja razred `ProductPresenter`: +Sploh nam ni treba navesti, katero predlogo je treba prikazati; ogrodje bo pot določilo samo. V primeru akcije `show` preprosto poskuša naložiti predlogo `show.latte` v imeniku z razredom `ProductPresenter`. Prav tako poskuša najti postavitev v datoteki `@layout.latte` (več o [iskanju predlog |templates#Template Lookup]). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Prav tako poskuša poiskati postavitev v datoteki `@layout.latte` in nato upodobi predlogo. Zdaj je naloga predstavnika in celotne aplikacije končana. Če predloga ne obstaja, se vrne stran z napako 404. Več o predstavitvah si lahko preberete na strani [Predstavitve |Presenters]. +Nato se predloge izrišejo. S tem je naloga predstavnika in celotne aplikacije končana in delo je opravljeno. Če predloga ne bi obstajala, bi se vrnila stran z napako 404. Več o predstavnikih si lahko preberete na strani [Predstavniki |presenters]. [* request-flow.svg *] @@ -137,7 +134,7 @@ Da bi se prepričali, poskusite celoten postopek ponoviti z nekoliko drugačnim 3) usmerjevalnik dekodira naslov URL kot par `Home:default` 4) ustvari se objekt `HomePresenter` 5) kličemo metodo `renderDefault()` (če obstaja) -6) prikaže se predloga `templates/Home/default.latte` z razporeditvijo `templates/@layout.latte` +6) prikaže se predloga `default.latte` z razporeditvijo `@layout.latte` Morda ste zdaj naleteli na veliko novih konceptov, vendar verjamemo, da so smiselni. Ustvarjanje aplikacij v programu Nette je zelo enostavno. diff --git a/application/sl/modules.texy b/application/sl/modules.texy index c0c01ae28d..bda3fd5943 100644 --- a/application/sl/modules.texy +++ b/application/sl/modules.texy @@ -2,29 +2,31 @@ Moduli ****** .[perex] -V Nette moduli predstavljajo logične enote, ki sestavljajo aplikacijo. Vključujejo predstavnike, predloge, lahko tudi komponente in razrede modelov. +Moduli vnašajo jasnost v aplikacije Nette, saj omogočajo enostavno razdelitev na logične enote. -En imenik za predstavnike in en imenik za predloge za prave projekte ne bi bil dovolj. Če je v eni mapi na desetine datotek, je to vsaj neorganizirano. Kako to odpraviti? Preprosto jih razdelimo v podimenike na disku in v imenske prostore v kodi. In točno to naredijo moduli Nette. - -Pozabimo torej na eno mapo za predstavnike in predloge in namesto tega ustvarimo module, na primer `Admin` in `Front`. +Podobno kot pri urejanju datotek v mape na trdem disku, lahko v Nette predstavnike, predloge in druge pomožne razrede razdelimo v module. Kako to deluje v praksi? Preprosto z vključevanjem novih podimenikov v strukturo. Tukaj je primer strukture z dvema moduloma, Front in 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 +│ │ └── ... \-- -Ta struktura imenikov se bo odražala v imenskih prostorih razredov, tako da bo na primer `DashboardPresenter` v imenskem prostoru `App\Modules\Admin\Presenters`: +Ta imeniška struktura se odraža v imenskih prostorih razredov, tako da se na primer `DashboardPresenter` nahaja v imenskem prostoru `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 } ``` -Na predvajalnik `Dashboard` znotraj modula `Admin` se v aplikaciji sklicujemo z uporabo zapisa v dvopičju kot na `Admin:Dashboard`, na njegovo akcijo `default` pa kot na `Admin:Dashboard:default`. -In kako Nette pravilno ve, da `Admin:Dashboard` predstavlja razred `App\Modules\Admin\Presenters\DashboardPresenter`? To je določeno s [preslikavo |#mapping] v konfiguraciji. -Podana struktura torej ni trdno določena in jo lahko spreminjate glede na svoje potrebe. +V aplikaciji se na predstavnik `Dashboard` znotraj modula `Admin` sklicujemo z uporabo zapisa v dvopičju kot `Admin:Dashboard`. Za njegovo akcijo `default` se sklicujemo na `Admin:Dashboard:default`. -Moduli seveda lahko poleg predstavnikov in predlog vsebujejo tudi vse druge elemente, kot so komponente, razredi modelov itd. +Predstavljena struktura ni toga; v konfiguraciji [jo |#mapping] lahko v [celoti prilagodite svojim potrebam |#mapping]. .[tip] + +Moduli lahko poleg predstavnikov in predlog vključujejo tudi vse druge datoteke, kot so komponente in pomožni razredi. Če razmišljate, kam jih umestiti, razmislite o uporabi mape `Accessory`: + +/--pre +app/ +├── UI/ +│ ├── Admin/ +│ │ ├── Accessory/ +│ │ │ ├── FormFactory.php +│ │ │ └── AdminLayout.php +│ │ ├── Dashboard/ +│ │ └── ... +\-- Vgnezdeni moduli .[#toc-nested-modules] --------------------------------------- -Ni nujno, da moduli tvorijo le ravno strukturo, ustvarite lahko tudi podmodule, na primer: +Moduli so lahko vgrajeni na več ravneh, podobno kot struktura imenikov na disku: /--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 │ │ └── ... \-- -Tako je modul `Blog` razdeljen na podmodula `Admin` in `Front`. Tudi to se bo odražalo v imenskih prostorih, ki bodo `App\Modules\Blog\Admin\Presenters` itd. Predstavnik `Dashboard` znotraj podmodula se imenuje `Blog:Admin:Dashboard`. +Modul `Blog` je razdeljen na podmodula `Admin` in `Front`. To se odraža tudi v imenskih prostorih, ki so nato prikazani kot `App\UI\Blog\Admin` in podobno. Če se želimo sklicevati na predstavnik `Dashboard` znotraj podmodula `Admin`, ga imenujemo `Blog:Admin:Dashboard`. -Gnezdenje je lahko tako globoko, kot želite, zato lahko ustvarite podmodule. +Gnezdenje je lahko tako globoko, kot je potrebno, kar omogoča ustvarjanje podmodulov. + +Če imate na primer v administraciji veliko predstavnikov, povezanih z upravljanjem naročil, kot so `OrderDetail`, `OrderEdit`, `OrderDispatch` itd., lahko ustvarite modul `Order`, v katerem bodo organizirani predstavniki, kot so `Detail`, `Edit`, `Dispatch` in drugi. Ustvarjanje povezav .[#toc-creating-links] @@ -102,46 +118,66 @@ Glejte [poglavje o usmerjanju |routing#Modules]. Kartiranje .[#toc-mapping] -------------------------- -Določa pravila, po katerih se ime razreda izpelje iz imena predstavnika. Zapišemo jih v [konfiguracijo |configuration] pod ključ `application › mapping`. +Mapiranje določa pravila za izpeljavo imena razreda iz imena predstavnika. Ta pravila so določena v [konfiguraciji |configuration] pod ključem `application › mapping`. + +Strukture imenikov, omenjene prej na tej strani, temeljijo na naslednjem preslikavanju: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Začnimo z vzorcem, ki ne uporablja modulov. Želeli bomo le, da imajo razredi predstavnikov imenski prostor `App\Presenters`. To pomeni, da mora biti predstavnik, kot je `Home`, preslikan v razred `App\Presenters\HomePresenter`. To lahko dosežemo z naslednjo konfiguracijo: +Kako deluje kartiranje? Za boljše razumevanje si najprej predstavljajmo aplikacijo brez modulov. Želimo, da razredi predstavnikov spadajo v imenski prostor `App\UI`, tako da se predstavnik `Home` preslika v razred `App\UI\HomePresenter`. To lahko dosežemo s to konfiguracijo: ```neon application: - mapping: App\Presenters\*Presenter + mapping: App\UI\*Presenter ``` -V maski razreda se ime predvajalnika nadomesti z zvezdico, rezultat pa je ime razreda. Enostavno! +Ta preslikava deluje tako, da zvezdico v maski `App\UI\*Presenter` nadomestimo z imenom predstavnika `Home`, s čimer dobimo končno ime razreda `App\UI\HomePresenter`. Preprosto! + +Vendar, kot lahko vidite v primerih v tem in drugih poglavjih, umeščamo predstavitvene razrede v istoimenske podimenike, npr. predstavitveni razred `Home` je preslikan v razred `App\UI\Home\HomePresenter`. To dosežemo s podvojitvijo zvezdice (zahteva program Nette Application 3.2): + +```neon +application: + mapping: App\UI\**Presenter +``` -Če voditelje razdelimo na module, lahko za vsak modul pripravimo lastno preslikavo: +Sedaj se lotimo preslikave predstavnikov v module. Za vsak modul lahko opredelimo posebna preslikavanja: ```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 ``` -Sedaj je predstavnik `Front:Home` preslikan v razred `App\Modules\Front\Presenters\HomePresenter`, predstavnik `Admin:Dashboard` pa v razred `App\Modules\Admin\Presenters\DashboardPresenter`. +V skladu s to konfiguracijo se predstavnik `Front:Home` prikaže v razred `App\UI\Front\Home\HomePresenter`, predstavnik `Api:OAuth` pa v razred `App\Api\OAuthPresenter`. -Bolj praktično je ustvariti splošno (zvezdno) pravilo, ki bo nadomestilo prvi dve. Dodatna zvezdica bo dodana maski razreda samo za ta modul: +Ker imata modula `Front` in `Admin` podoben pristop k preslikavi in ker bo takih modulov verjetno več, je mogoče ustvariti splošno pravilo, ki ju nadomesti. V masko razreda se doda nova zvezdica za modul: ```neon application: mapping: - *: App\Modules\*\Presenters\*Presenter + *: App\UI\*\**Presenter Api: App\Api\*Presenter ``` -Kaj pa, če uporabljamo vgnezdene module in imamo predvajalnik `Admin:User:Edit`? V tem primeru se segment z zvezdico, ki predstavlja modul za vsako raven, preprosto ponovi in rezultat je razred `App\Modules\Admin\User\Presenters\EditPresenter`. +Za večnivojske vgnezdene module, kot je predstavnik `Admin:User:Edit`, se segment z zvezdico ponovi za vsako raven, tako da nastane razred `App\UI\Admin\User\Edit\EditPresenter`. Alternativni zapis je, da namesto niza uporabimo polje, sestavljeno iz treh segmentov. Ta zapis je enakovreden prejšnjemu: ```neon application: mapping: - *: [App\Modules, *, Presenters\*Presenter] + *: [App\UI, *, **Presenter] + Api: [App\Api, '', *Presenter] ``` -Privzeta vrednost je `*Module\*Presenter`. +Če imamo v konfiguraciji samo eno pravilo, splošno, lahko zapišemo na kratko: + +```neon +application: + mapping: App\UI\*\**Presenter +``` diff --git a/application/sl/presenters.texy b/application/sl/presenters.texy index 8e33a702e0..1f1e6b993b 100644 --- a/application/sl/presenters.texy +++ b/application/sl/presenters.texy @@ -60,7 +60,7 @@ Podobno kot pri metodi `render()`. Medtem ko `render()` je namenjena Pomembno je, da `action()` se pokliče pred `render()`, tako da lahko znotraj njega morebiti spremenimo naslednji potek življenjskega cikla, tj. spremenimo predlogo, ki se bo izrisala, in tudi metodo `render()` ki bo poklicana, z uporabo `setView('otherView')`. -Parametri iz zahteve se posredujejo metodi. Za parametre je mogoče in priporočljivo določiti tipe, npr. `actionShow(int $id, string $slug = null)` - če parameter `id` manjka ali če ni celo število, predstavnik vrne [napako 404 |#Error 404 etc.] in zaključi operacijo. +Parametri iz zahteve se posredujejo metodi. Za parametre je mogoče in priporočljivo določiti tipe, npr. `actionShow(int $id, ?string $slug = null)` - če parameter `id` manjka ali če ni celo število, predstavnik vrne [napako 404 |#Error 404 etc.] in zaključi operacijo. `handle(args...)` .{toc: handle()} @@ -205,7 +205,7 @@ V predlogi so ta sporočila na voljo v spremenljivki `$flashes` kot objekti `std Napaka 404 itd. .[#toc-error-404-etc] ===================================== -Kadar ne moremo izpolniti zahteve, ker na primer članek, ki ga želimo prikazati, ne obstaja v zbirki podatkov, bomo z metodo `error(string $message = null, int $httpCode = 404)`, ki predstavlja napako HTTP 404, vrgli napako 404: +Kadar ne moremo izpolniti zahteve, ker na primer članek, ki ga želimo prikazati, ne obstaja v zbirki podatkov, bomo z metodo `error(?string $message = null, int $httpCode = 404)`, ki predstavlja napako HTTP 404, vrgli napako 404: ```php public function renderShow(int $id): void @@ -384,7 +384,7 @@ Preusmeritev se ne izvede pri zahtevi AJAX ali POST, ker bi povzročila izgubo p Kanonizacijo lahko sprožite tudi ročno z metodo `canonicalize()`, ki tako kot metoda `link()` kot argumente prejme predstavnika, dejanja in parametre. Ustvari povezavo in jo primerja s trenutnim naslovom URL. Če se razlikuje, preusmeri na ustvarjeno povezavo. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // preusmeri, če se $slug razlikuje od $realSlug @@ -452,17 +452,6 @@ Omejitev dostopa z uporabo `#[Requires]` .[#toc-access-restriction-using-require . `#[Requires]` atribut zagotavlja napredne možnosti za omejevanje dostopa do predavateljev in njihovih metod. Z njim lahko določite metode HTTP, zahtevate zahteve AJAX, omejite dostop do istega izvora in omejite dostop samo na posredovanje. Atribut je mogoče uporabiti za razrede predstavnikov in posamezne metode, kot so `action()`, `render()`, `handle()`, in `createComponent()`. -Tukaj je primer uporabe za omejitev dostopa samo na metodo HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Določite lahko te omejitve: - za metode HTTP: `#[Requires(methods: ['GET', 'POST'])]` - ki zahteva zahtevo AJAX: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ Določite lahko te omejitve: - dostop samo prek posredovanja: `#[Requires(forward: true)]` - omejitve za določena dejanja: `#[Requires(actions: 'default')]` -Pogoje lahko združite tako, da navedete več atributov ali jih združite v enega: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Za podrobnosti glejte [Kako uporabljati Requires atribut |best-practices:attribute-requires]. Preverjanje metode HTTP .[#toc-http-method-check] diff --git a/application/sl/routing.texy b/application/sl/routing.texy index 0f717178bb..bea1f8c1b6 100644 --- a/application/sl/routing.texy +++ b/application/sl/routing.texy @@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +225,7 @@ $router->addRoute('/[/]', [ ]); ``` -Lahko pa uporabimo tudi to obliko, pri čemer opazimo prepisovanje regularnega izraza za preverjanje: +Za podrobnejšo specifikacijo je mogoče uporabiti še bolj razširjeno obliko, v kateri lahko poleg privzetih vrednosti nastavite tudi druge lastnosti parametrov, na primer regularni izraz za preverjanje (glejte parameter `id` ): ```php use Nette\Routing\Route; @@ -243,7 +243,7 @@ $router->addRoute('/[/]', [ ]); ``` -Te bolj zgovorne oblike so uporabne za dodajanje drugih metapodatkov. +Pomembno je opozoriti, da če parametri, opredeljeni v polju, niso vključeni v masko poti, njihovih vrednosti ni mogoče spremeniti, niti z uporabo parametrov poizvedbe, določenih za vprašalnim znakom v naslovu URL. Filtri in prevodi .[#toc-filters-and-translations] @@ -477,10 +477,10 @@ $router->addRoute('index', /* ... */); Integracija .[#toc-integration] =============================== -Da bi naš usmerjevalnik povezali z aplikacijo, moramo o tem obvestiti vsebnik DI. Najlažje je pripraviti tovarno, ki bo zgradila objekt usmerjevalnika, in povedati konfiguraciji vsebnika, naj jo uporabi. Recimo, da v ta namen napišemo metodo `App\Router\RouterFactory::createRouter()`: +Da bi naš usmerjevalnik povezali z aplikacijo, moramo o tem obvestiti vsebnik DI. Najlažje je pripraviti tovarno, ki bo zgradila objekt usmerjevalnika, in povedati konfiguraciji vsebnika, naj jo uporabi. Recimo, da v ta namen napišemo metodo `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -499,7 +499,7 @@ Nato zapišemo v [konfiguracijo |dependency-injection:services]: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Vse odvisnosti, kot je povezava s podatkovno bazo itd., se metodi tovarne posredujejo kot njeni parametri z uporabo [samodejnega povezovanja |dependency-injection:autowiring]: @@ -663,7 +663,7 @@ Z ločeno uporabo mislimo na uporabo zmožnosti usmerjevalnika v aplikaciji, ki Tako bomo ponovno ustvarili metodo, ki bo zgradila usmerjevalnik, na primer: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Ali pa bomo predmete ustvarili neposredno: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/sl/templates.texy b/application/sl/templates.texy index 66005783a9..06c0ddeb10 100644 --- a/application/sl/templates.texy +++ b/application/sl/templates.texy @@ -34,35 +34,81 @@ To pa je lahko predloga za dejanja: V njej je opredeljen blok `content`, ki se v postavitev vstavi namesto bloka `{include content}`, prav tako pa je na novo opredeljen blok `title`, ki v postavitvi prepiše blok `{block title}`. Poskusite si predstavljati rezultat. -Iskanje predlog .[#toc-search-for-templates] --------------------------------------------- +Iskanje predloge .[#toc-template-lookup] +---------------------------------------- -Pot do predlog se določi po preprosti logiki. Poskusi preveriti, ali obstaja ena od teh datotek s predlogami glede na imenik, v katerem se nahaja razred presenter, kjer `` je ime trenutnega predstavnika in `` je ime trenutnega dejanja: +V predstavitvenih programih vam ni treba določiti, katera predloga naj se prikaže; ogrodje samodejno določi pot, kar vam olajša kodiranje. -- `templates//.latte` -- `templates/..latte` +Če uporabljate imeniško strukturo, v kateri ima vsak predstavnik svoj imenik, preprosto postavite predlogo v ta imenik pod ime dejanja (npr. pogleda). Na primer, za dejanje `default` uporabite predlogo `default.latte`: -Če predloge ne najde, jo poskuša poiskati v imeniku `templates` eno raven višje, tj. na isti ravni kot imenik z razredom predstavnika. +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Če predloge ne najde niti tam, se kot odgovor prikaže [napaka 404 |presenters#Error 404 etc.]. +Če uporabljate strukturo, v kateri so predstavniki skupaj v enem imeniku, predloge pa v mapi `templates`, jo shranite bodisi v datoteko `..latte` ali . `/.latte`: -Pogled lahko spremenite tudi z uporabo `$this->setView('otherView')`. Lahko pa namesto iskanja neposredno določite ime datoteke s predlogo z uporabo `$this->template->setFile('/path/to/template.latte')`. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- + +Imenik `templates` lahko postavite tudi eno raven višje, na isto raven kot imenik z razredi predavateljev. + +Če predloge ni mogoče najti, se predstavitveni program odzove z [napako 404 - stran ni najdena |presenters#Error 404 etc]. + +Prikaz lahko spremenite z uporabo spletne strani `$this->setView('anotherView')`. Datoteko s predlogo lahko določite tudi neposredno z uporabo `$this->template->setFile('/path/to/template.latte')`. .[note] -Poti, po katerih se iščejo predloge, lahko spremenite tako, da nadgradite metodo [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()], ki vrne polje možnih poti do datotek. +Datoteke, v katerih se iščejo predloge, lahko spremenite tako, da nadgradite metodo [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()], ki vrne polje možnih imen datotek. + + +Iskanje predlog za postavitev .[#toc-layout-template-lookup] +------------------------------------------------------------ + +Nette samodejno poišče tudi datoteko z maketo. + +Če uporabljate imeniško strukturo, v kateri ima vsak predavatelj svoj imenik, postavite postavitev bodisi v mapo s predavateljem, če je namenjena samo njemu, bodisi za stopnjo višje, če je skupna več predavateljem: + +/--pre +app/ +└── UI/ + ├── @layout.latte ← common layout + └── Home/ + ├── @layout.latte ← only for Home presenter + ├── HomePresenter.php + └── default.latte +\-- + +Če uporabljate strukturo, v kateri so predstavniki združeni v enem imeniku, predloge pa so v mapi `templates`, bo postavitev pričakovana na naslednjih mestih: -Postavitev se pričakuje v naslednjih datotekah: +/--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` postavitev, ki je skupna več predstavnikom +Če je predstavnik v [modulu |modules], se bo poiskal tudi naprej po drevesu imenika v skladu z gnezdenjem modula. -`` je ime trenutnega predavatelja in `` je ime postavitve, ki je privzeto `'layout'`. Ime lahko spremenite s `$this->setLayout('otherLayout')`, tako da se bodo poskušale uporabiti datoteke `@otherLayout.latte`. +Ime postavitve lahko spremenite z uporabo spletne strani `$this->setLayout('layoutAdmin')`, nato pa jo boste pričakali v datoteki `@layoutAdmin.latte`. Datoteko s predlogo postavitve lahko določite tudi neposredno z uporabo `$this->setLayout('/path/to/template.latte')`. -Ime datoteke za predlogo postavitve lahko določite tudi neposredno z uporabo `$this->setLayout('/path/to/template.latte')`. Z uporabo spletne strani `$this->setLayout(false)` bo iskanje postavitve onemogočeno. +Uporaba `$this->setLayout(false)` ali oznake `{layout none}` znotraj predloge onemogoči iskanje postavitve. .[note] -Poti, po katerih se iščejo predloge, lahko spremenite tako, da nadgradite metodo [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], ki vrne polje možnih poti do datotek. +Datoteke, v katerih se iščejo predloge postavitve, lahko spremenite tako, da nadgradite metodo [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()], ki vrne polje možnih imen datotek. Spremenljivke v predlogi .[#toc-variables-in-the-template] @@ -104,7 +150,7 @@ Opomba `@property-read` je namenjena IDE in statični analizi, zaradi nje bo del Privoščite si lahko tudi razkošje šepetanja v predlogah, samo namestite vtičnik Latte v PhpStorm in na začetku predloge navedite ime razreda, glejte članek "Latte: kako vtipkati sistem":https://blog.nette.org/sl/latte-kako-uporabljati-sistem-tipov: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void Različica Latte 3 ponuja naprednejši način z ustvarjanjem [razširitve |latte:creating-extension] za vsak spletni projekt. Tukaj je približni primer takega razreda: ```php -namespace App\Templating; +namespace App\UI\Accessory; final class LatteExtension extends Latte\Extension { @@ -214,7 +260,7 @@ Registriramo ga z uporabo [konfiguracije|configuration#Latte]: ```neon latte: extensions: - - App\Templating\LatteExtension + - App\UI\Accessory\LatteExtension ``` @@ -239,7 +285,7 @@ Prevajalnik lahko nastavimo tudi s [konfiguracijo |configuration#Latte]: ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` Prevajalnik lahko nato uporabimo na primer kot filter `|translate`, pri čemer metodi `translate()` posredujemo dodatne parametre (glej `foo, bar`): diff --git a/application/tr/ajax.texy b/application/tr/ajax.texy index 05804fd81c..007283e44d 100644 --- a/application/tr/ajax.texy +++ b/application/tr/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Öncelikle kütüphaneyi [başlatmanız |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] gerekir: + +```js +naja.initialize(); +``` + Sıradan bir bağlantıyı (sinyal) veya form gönderimini AJAX isteği haline getirmek için ilgili bağlantıyı, formu veya düğmeyi `ajax` sınıfıyla işaretlemeniz yeterlidir: ```html diff --git a/application/tr/bootstrap.texy b/application/tr/bootstrap.texy index 98cf02a208..1513e7fe3a 100644 --- a/application/tr/bootstrap.texy +++ b/application/tr/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__); + // Yapılandırıcı, uygulama ortamını ve hizmetlerini ayarlamaktan sorumludur. + $this->configurator = new Configurator; + // Nette tarafından oluşturulan geçici dosyalar için dizini ayarlayın (örn. derlenmiş şablonlar) + $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 akıllıdır ve geliştirme modu otomatik olarak açılır, + // ya da aşağıdaki satırın yorumunu kaldırarak belirli bir IP adresi için etkinleştirebilirsiniz: + // $this->configurator->setDebugMode('secret@23.75.345.200'); + + // Tracy'yi etkinleştirir: nihai "İsviçre çakısı" hata ayıklama aracı. + $this->configurator->enableTracy($this->rootDir . '/log'); + + // RobotLoader: verilen dizindeki tüm sınıfları otomatik yükler + $this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); - $configurator->addConfig($appDir . '/config/common.neon'); - return $configurator; + } + + private function setupContainer(): void + { + // Yapılandırma dosyalarını yükle + $this->configurator->addConfig($this->rootDir . '/config/common.neon'); } } ``` @@ -40,16 +66,15 @@ class Bootstrap index.php .[#toc-index-php] =========================== -Web uygulamaları söz konusu olduğunda, başlangıç dosyası `index.php` olup `www/` genel dizininde bulunur. Ortamı başlatmak için `Bootstrap` sınıfına izin verir ve DI konteynerini oluşturan `$configurator` sınıfını döndürür. Daha sonra web uygulamasını çalıştıran `Application` hizmetini elde eder: +Web uygulamaları için başlangıç dosyası `index.php`, `www/` genel dizininde bulunur. Ortamı başlatmak ve bir DI konteyneri oluşturmak için `Bootstrap` sınıfını kullanır. Ardından, web uygulamasını başlatan kapsayıcıdan `Application` hizmetini alır: ```php -// ortamı başlat + Configurator nesnesini al -$configurator = App\Bootstrap::boot(); -// bir DI konteyneri oluşturun -$container = $configurator->createContainer(); -// DI container bir Nette\Application\Application nesnesi oluşturur +$bootstrap = new App\Bootstrap; +// Ortamı başlatma + bir DI konteyneri oluşturma +$container = $bootstrap->bootWebApplication(); +// DI konteyneri bir Nette\Application\Application nesnesi oluşturur $application = $container->getByType(Nette\Application\Application::class); -// Nette uygulamasını başlat +// Nette uygulamasını başlatın ve gelen isteği işleyin $application->run(); ``` @@ -66,19 +91,19 @@ Mod seçimi otomatik algılama ile yapılır, bu nedenle genellikle herhangi bir Geliştirme modunu diğer durumlarda, örneğin belirli bir IP adresinden erişen programcılar için etkinleştirmek istiyorsanız, `setDebugMode()` adresini kullanabilirsiniz: ```php -$configurator->setDebugMode('23.75.345.200'); // bir veya daha fazla IP adresi +$this->configurator->setDebugMode('23.75.345.200'); // bir veya daha fazla IP adresi ``` Bir IP adresini bir çerezle birleştirmenizi kesinlikle öneririz. `nette-debug` çerezine gizli bir belirteç depolayacağız, örneğin `secret1234` ve geliştirme modu, bu IP ve çerez kombinasyonuna sahip programcılar için etkinleştirilecektir. ```php -$configurator->setDebugMode('secret1234@23.75.345.200'); +$this->configurator->setDebugMode('secret1234@23.75.345.200'); ``` Ayrıca localhost için bile geliştirici modunu tamamen kapatabiliriz: ```php -$configurator->setDebugMode(false); +$this->configurator->setDebugMode(false); ``` `true` değerinin, bir üretim sunucusunda asla gerçekleşmemesi gereken geliştirici modunu zorlayarak açtığını unutmayın. @@ -90,7 +115,7 @@ Hata Ayıklama Aracı Tracy .[#toc-debugging-tool-tracy] Kolay hata ayıklama için harika araç [Tracy'yi |tracy:] açacağız. Geliştirici modunda hataları görselleştirir ve üretim modunda hataları belirtilen dizine kaydeder: ```php -$configurator->enableTracy($appDir . '/log'); +$this->configurator->enableTracy($this->rootDir . '/log'); ``` @@ -100,7 +125,7 @@ Geçici Dosyalar .[#toc-temporary-files] Nette, DI konteyneri, RobotLoader, şablonlar vb. için önbelleği kullanır. Bu nedenle, önbelleğin depolanacağı dizinin yolunu ayarlamak gerekir: ```php -$configurator->setTempDirectory($appDir . '/temp'); +$this->configurator->setTempDirectory($this->rootDir . '/temp'); ``` Linux veya macOS üzerinde, `log/` ve `temp/` dizinleri için [yazma izinlerini |nette:troubleshooting#Setting directory permissions] ayarlayın. @@ -112,7 +137,7 @@ RobotLoader .[#toc-robotloader] Genellikle, [RobotLoader'ı |robot-loader:] kullanarak sınıfları otomatik olarak yüklemek isteyeceğiz, bu yüzden onu başlatmalı ve `Bootstrap.php` 'un bulunduğu dizinden (yani `__DIR__`) ve tüm alt dizinlerinden sınıfları yüklemesine izin vermeliyiz: ```php -$configurator->createRobotLoader() +$this->configurator->createRobotLoader() ->addDirectory(__DIR__) ->register(); ``` @@ -126,7 +151,7 @@ Zaman Dilimi .[#toc-timezone] Yapılandırıcı, uygulamanız için bir saat dilimi belirlemenize olanak tanır. ```php -$configurator->setTimeZone('Europe/Prague'); +$this->configurator->setTimeZone('Europe/Prague'); ``` @@ -143,16 +168,17 @@ Geliştirme modunda, kodu veya yapılandırma dosyalarını her değiştirdiğin Yapılandırma dosyaları `addConfig()` kullanılarak yüklenir: ```php -$configurator->addConfig($appDir . '/config/common.neon'); +$this->configurator->addConfig($this->rootDir . '/config/common.neon'); ``` Birden fazla dosya eklemek için `addConfig()` yöntemi birden fazla kez çağrılabilir. ```php -$configurator->addConfig($appDir . '/config/common.neon'); -$configurator->addConfig($appDir . '/config/services.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 @@ Statik Parametreler .[#toc-static-parameters] Yapılandırma dosyalarında kullanılan parametreler [`parameters` bölümünde |dependency-injection:configuration#parameters] tanımlanabilir ve ayrıca `addStaticParameters()` yöntemi ( `addParameters()` takma adı vardır) tarafından geçirilebilir (veya üzerine yazılabilir). Farklı parametre değerlerinin ek DI konteynerlerinin, yani ek sınıfların oluşturulmasına neden olması önemlidir. ```php -$configurator->addStaticParameters([ +$this->configurator->addStaticParameters([ 'projectId' => 23, ]); ``` @@ -183,7 +209,7 @@ Dinamik Parametreler .[#toc-dynamic-parameters] Konteynere dinamik parametreler de ekleyebiliriz, statik parametrelerin aksine farklı değerleri yeni DI konteynerlerinin oluşturulmasına neden olmaz. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'remoteIp' => $_SERVER['REMOTE_ADDR'], ]); ``` @@ -191,7 +217,7 @@ $configurator->addDynamicParameters([ Ortam değişkenleri dinamik parametreler kullanılarak kolayca kullanılabilir hale getirilebilir. Bunlara yapılandırma dosyalarındaki `%env.variable%` adresinden erişebiliriz. ```php -$configurator->addDynamicParameters([ +$this->configurator->addDynamicParameters([ 'env' => getenv(), ]); ``` @@ -206,6 +232,7 @@ Yapılandırma dosyalarında aşağıdaki statik parametreleri kullanabilirsiniz - `%wwwDir%`, `index.php` giriş dosyasını içeren dizinin mutlak yoludur - `%tempDir%` geçici dosyalar için dizinin mutlak yoludur - `%vendorDir%` Composer'ın kütüphaneleri yüklediği dizinin mutlak yoludur +- `%rootDir%` projenin kök dizinine giden mutlak yoldur - `%debugMode%` uygulamanın hata ayıklama modunda olup olmadığını gösterir - `%consoleMode%` isteğin komut satırı üzerinden gelip gelmediğini gösterir @@ -225,7 +252,7 @@ services: Yeni bir örnek oluşturun ve bootstrap'e ekleyin: ```php -$configurator->addServices([ +$this->configurator->addServices([ 'myservice' => new App\Model\MyCustomService('foobar'), ]); ``` @@ -234,13 +261,21 @@ $configurator->addServices([ Farklı Ortamlar .[#toc-different-environments] ============================================== -`Bootstrap` sınıfını ihtiyaçlarınıza göre özelleştirmekten çekinmeyin. Web projelerini farklılaştırmak için `boot()` yöntemine parametreler ekleyebilir veya birim testleri için ortamı başlatan `bootForTests()`, komut satırından çağrılan komut dosyaları için `bootForCli()` gibi başka yöntemler ekleyebilirsiniz. + `Bootstrap` sınıfını ihtiyaçlarınıza göre özelleştirmekten çekinmeyin. Web projeleri arasında ayrım yapmak için `bootWebApplication()` yöntemine parametreler ekleyebilirsiniz. Alternatif olarak, birim testleri için ortamı başlatmak üzere `bootTestEnvironment()`, komut satırından çağrılan betikler için `bootConsoleApplication()` gibi başka yöntemler de ekleyebilirsiniz. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Nette Tester başlatma + $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/tr/components.texy b/application/tr/components.texy index 5e1c447262..cae382b8da 100644 --- a/application/tr/components.texy +++ b/application/tr/components.texy @@ -230,6 +230,28 @@ $this->redirect(/* ... */); // ve yeniden yönlendir ``` +Sinyalden Sonra Yeniden Yönlendirme .[#toc-redirection-after-a-signal] +====================================================================== + +Bir bileşen sinyali işlendikten sonra genellikle yeniden yönlendirme yapılır. Bu durum formlara benzer - bir form gönderildikten sonra, sayfa tarayıcıda yenilendiğinde verilerin yeniden gönderilmesini önlemek için de yönlendirme yaparız. + +```php +$this->redirect('this') // redirects to the current presenter and action +``` + +Bir bileşen yeniden kullanılabilir bir öğe olduğundan ve genellikle belirli sunuculara doğrudan bağımlı olmaması gerektiğinden, `redirect()` ve `link()` yöntemleri parametreyi otomatik olarak bir bileşen sinyali olarak yorumlar: + +```php +$this->redirect('click') // redirects to the 'click' signal of the same component +``` + +Farklı bir sunum yapan kişiye veya eyleme yönlendirmeniz gerekiyorsa, bunu sunum yapan kişi aracılığıyla yapabilirsiniz: + +```php +$this->getPresenter()->redirect('Product:show'); // redirects to a different presenter/action +``` + + Kalıcı Parametreler .[#toc-persistent-parameters] ================================================= diff --git a/application/tr/configuration.texy b/application/tr/configuration.texy index 2f1ed45c00..62d7a3f3f7 100644 --- a/application/tr/configuration.texy +++ b/application/tr/configuration.texy @@ -95,6 +95,9 @@ latte: # [oluşturulan kodun kontrol |latte:develop#Checking Generated Code]edilmesini sağlar phpLinter: ... # (string) varsayılan null + # yerel ayarı ayarlar + locale: cs_CZ # (string) varsayılan null + # $this->template sınıfı templateClass: App\MyTemplateClass # varsayılan olarak Nette\Bridges\ApplicationLatte\DefaultTemplate ``` @@ -104,7 +107,7 @@ Latte sürüm 3 kullanıyorsanız, kullanarak yeni [uzantı |latte:creating-exte ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/tr/how-it-works.texy b/application/tr/how-it-works.texy index 78f72c450c..50e09beecc 100644 --- a/application/tr/how-it-works.texy +++ b/application/tr/how-it-works.texy @@ -22,13 +22,13 @@ Dizin yapısı şuna benzer: /--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/ ← temel gerekli sınıflar +│ │ └── RouterFactory.php ← URL adreslerinin yapılandırılması +│ ├── UI/ ← presenters, templates & co. +│ │ ├── @layout.latte ← paylaşılan düzen şablonu +│ │ └── Home/ ← Ana Sayfa sunucu dizini +│ │ ├── HomePresenter.php ← Ev sunucusu sınıfı +│ │ └── default.latte ← eylem için şablon default │ └── Bootstrap.php ← booting class Bootstrap ├── bin/ ← scripts for the command line ├── config/ ← configuration files @@ -91,7 +91,7 @@ Nette'de yazılan uygulamalar, belirli bir web sitesi sayfasını temsil eden s Uygulama, yönlendirici olarak adlandırılan kişiden mevcut talebin işlenmek üzere hangi sunuculara iletileceğine karar vermesini isteyerek başlar. Yönlendirici bunun kimin sorumluluğunda olduğuna karar verir. `https://example.com/product/123` ile bir ürünü `id: 123` eylem olarak isteyen **sunucu** `Product` için bir iş olduğuna karar verir. Sunucu + eylem çiftlerini iki nokta üst üste ile ayırarak `Product:show` şeklinde yazmak iyi bir alışkanlıktır. -Böylece yönlendirici URL'yi bir `Presenter:action` + parametreler çiftine dönüştürdü, bizim durumumuzda `Product:show` + `id: 123`. Bir yönlendiricinin nasıl göründüğünü `app/Router/RouterFactory.php` dosyasında görebilirsiniz ve bunu [Yönlendirme |Routing] bölümünde ayrıntılı olarak açıklayacağız. +Böylece yönlendirici URL'yi bir `Presenter:action` + parametreler çiftine dönüştürdü, bizim durumumuzda `Product:show` + `id: 123`. Bir yönlendiricinin nasıl göründüğünü `app/Core/RouterFactory.php` dosyasında görebilirsiniz ve bunu [Yönlendirme |Routing] bölümünde ayrıntılı olarak açıklayacağız. Devam edelim. Uygulama zaten sunucunun adını biliyor ve devam edebilir. Sunum yapan kişinin kodu olan `ProductPresenter` nesnesini oluşturarak `Product`. Daha doğrusu, DI konteynerinden sunucuyu yaratmasını ister, çünkü nesneleri üretmek onun işidir. @@ -121,12 +121,9 @@ Böylece, kodu kurgusal bir örnek olan `renderShow(123)` yöntemi çağrıldı, Daha sonra, sunum yapan kişi yanıtı döndürür. Bu bir HTML sayfası, bir resim, bir XML belgesi, diskten bir dosya gönderme, JSON veya başka bir sayfaya yönlendirme olabilir. Daha da önemlisi, nasıl yanıt verileceğini açıkça belirtmezsek ( `ProductPresenter` adresinde olduğu gibi), yanıt şablonu bir HTML sayfası ile işlemek olacaktır. Neden mi? Çünkü vakaların %99'unda bir şablon çizmek isteriz, dolayısıyla sunum yapan kişi bu davranışı varsayılan olarak kabul eder ve işimizi kolaylaştırmak ister. Nette'in amacı da bu. -Hangi şablonun çizileceğini belirtmemize bile gerek yok, basit bir mantıkla o şablona giden yolu türetiyor. Sunucu `Product` ve eylem `show` durumunda, bu şablon dosyalarından birinin `ProductPresenter` sınıfının bulunduğu dizine göre var olup olmadığını görmeye çalışır: +Hangi şablonun işleneceğini belirtmemize bile gerek yoktur; framework yolu kendisi çıkaracaktır. `show` eylemi söz konusu olduğunda, basitçe `ProductPresenter` sınıfının bulunduğu dizindeki `show.latte` şablonunu yüklemeye çalışır. Ayrıca `@layout.latte` dosyasındaki düzeni bulmaya çalışır ( [şablon arama |templates#Template Lookup] hakkında daha fazla bilgi). -- `templates/Product/show.latte` -- `templates/Product.show.latte` - -Ayrıca `@layout.latte` dosyasında düzeni bulmaya çalışacak ve ardından şablonu oluşturacaktır. Artık sunucunun ve tüm uygulamanın görevi tamamlanmıştır. Şablon mevcut değilse, 404 hatalı bir sayfa döndürülecektir. Sunumcular hakkında daha fazla bilgiyi [Sunumcular |Presenters] sayfasında bulabilirsiniz. +Daha sonra şablonlar işlenir. Bu, sunucunun ve tüm uygulamanın görevini tamamlar ve iş biter. Eğer şablon mevcut değilse, 404 hata sayfası döndürülür. Sunucular hakkında daha fazla bilgi için [Sunucular |presenters] sayfasına bakabilirsiniz. [* request-flow.svg *] @@ -137,7 +134,7 @@ Sadece emin olmak için, tüm süreci biraz farklı bir URL ile özetlemeye çal 3) yönlendirici URL'yi bir çift olarak çözer `Home:default` 4) bir `HomePresenter` nesnesi oluşturulur 5) `renderDefault()` yöntemi çağrılır (eğer varsa) -6) `templates/@layout.latte` düzenine sahip bir `templates/Home/default.latte` şablonu oluşturulur +6) `@layout.latte` düzenine sahip bir `default.latte` şablonu oluşturulur Şu anda birçok yeni kavramla karşılaşmış olabilirsiniz, ancak bunların anlamlı olduğuna inanıyoruz. Nette'de uygulama oluşturmak çocuk oyuncağı. diff --git a/application/tr/modules.texy b/application/tr/modules.texy index 63da6e9e48..2fb05dde40 100644 --- a/application/tr/modules.texy +++ b/application/tr/modules.texy @@ -2,29 +2,31 @@ Modüller ******** .[perex] -Nette'de modüller bir uygulamayı oluşturan mantıksal birimleri temsil eder. Sunucuları, şablonları, muhtemelen bileşenleri ve model sınıflarını içerirler. +Modüller, mantıksal birimlere kolayca bölünmeyi kolaylaştırarak Nette uygulamalarına netlik kazandırır. -Sunucular için bir dizin ve şablonlar için bir dizin gerçek projeler için yeterli olmayacaktır. Bir klasörde düzinelerce dosya olması en azından düzensizdir. Bundan nasıl kurtulabiliriz? Basitçe onları diskteki alt dizinlere ve koddaki ad alanlarına böleriz. Nette modüllerinin yaptığı da tam olarak budur. - -Sunucular ve şablonlar için tek bir klasör oluşturmayı unutalım ve bunun yerine örneğin `Admin` ve `Front` gibi modüller oluşturalım. +Dosyaları sabit diskte klasörler halinde düzenlemeye benzer şekilde, Nette'de sunum yapan kişileri, şablonları ve diğer yardımcı sınıfları modüllere bölebiliriz. Bu pratikte nasıl çalışır? Basitçe yapıya yeni alt dizinler ekleyerek. İşte Front ve Admin olmak üzere iki modüllü bir yapı örneği: /--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 +│ │ └── ... \-- -Bu dizin yapısı sınıf ad alanları tarafından yansıtılacaktır, bu nedenle örneğin `DashboardPresenter` `App\Modules\Admin\Presenters` ad alanında olacaktır: +Bu dizin yapısı sınıfların isim alanlarına da yansıtılır; örneğin `DashboardPresenter`, `App\UI\Admin\Dashboard` isim alanında yer alır: ```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 } ``` -`Admin` modülü içindeki `Dashboard` sunumcusuna uygulama içinde iki nokta üst üste gösterimi kullanılarak `Admin:Dashboard` şeklinde ve `default` eylemine `Admin:Dashboard:default` şeklinde referans verilir. -Peki Nette proper `Admin:Dashboard` 'un `App\Modules\Admin\Presenters\DashboardPresenter` sınıfını temsil ettiğini nasıl biliyor? Bu, yapılandırmadaki [eşleme |#mapping] ile belirlenir. -Bu nedenle, verilen yapı sabit değildir ve ihtiyaçlarınıza göre değiştirebilirsiniz. +Uygulamada, `Admin` modülü içindeki `Dashboard` sunumcusuna iki nokta üst üste gösterimini kullanarak `Admin:Dashboard` olarak atıfta bulunuyoruz. `default` eylemi için `Admin:Dashboard:default` olarak adlandırıyoruz. -Modüller elbette sunum yapanların ve şablonların yanı sıra bileşenler, model sınıfları vb. gibi diğer tüm öğeleri de içerebilir. +Sunulan yapı katı değildir; yapılandırmada [ihtiyaçlarınıza göre tamamen özelleştirebilirsiniz |#mapping]. .[tip] + +Modüller, sunucular ve şablonların yanı sıra bileşenler ve yardımcı sınıflar gibi diğer tüm dosyaları da içerebilir. Bunları nereye yerleştireceğinizi düşünüyorsanız, bir `Accessory` klasörü kullanmayı düşünün: + +/--pre +app/ +├── UI/ +│ ├── Admin/ +│ │ ├── Accessory/ +│ │ │ ├── FormFactory.php +│ │ │ └── AdminLayout.php +│ │ ├── Dashboard/ +│ │ └── ... +\-- İç İçe Modüller .[#toc-nested-modules] -------------------------------------- -Modüller sadece düz bir yapı oluşturmak zorunda değildir, örneğin alt modüller de oluşturabilirsiniz: +Modüller, diskteki bir dizin yapısına benzer şekilde birden fazla iç içe geçme seviyesine sahip olabilir: /--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 │ │ └── ... \-- -Böylece, `Blog` modülü `Admin` ve `Front` alt modüllerine bölünmüştür. Yine, bu durum `App\Modules\Blog\Admin\Presenters` vb. isim alanlarına da yansıyacaktır. Alt modülün içindeki `Dashboard` sunucusu `Blog:Admin:Dashboard` olarak adlandırılır. + `Blog` modülü `Admin` ve `Front` alt modüllerine ayrılmıştır. Bu aynı zamanda `App\UI\Blog\Admin` ve benzer şekilde görünen isim alanlarına da yansıtılır. `Admin` alt modülü içindeki `Dashboard` sunucusuna atıfta bulunmak için, bunu `Blog:Admin:Dashboard` olarak adlandırıyoruz. -İç içe geçme istediğiniz kadar derin olabilir, böylece alt alt modüller oluşturulabilir. +Yerleştirme, alt alt modüllerin oluşturulmasına izin vererek gerektiği kadar derin olabilir. + +Örneğin, yönetimde `OrderDetail`, `OrderEdit`, `OrderDispatch`, vb. gibi sipariş yönetimiyle ilgili birçok sunucunuz varsa, `Detail`, `Edit`, `Dispatch` ve diğerleri gibi sunucuların düzenleneceği bir `Order` modülü oluşturabilirsiniz. Bağlantı Oluşturma .[#toc-creating-links] @@ -102,46 +118,66 @@ Yönlendirme .[#toc-routing] Haritalama .[#toc-mapping] -------------------------- -Sınıf adının sunum yapan kişinin adından türetildiği kuralları tanımlar. Bunları [yapılandırmada |configuration] `application › mapping` anahtarının altına yazıyoruz. +Eşleme, sınıf adının sunum yapan kişinin adından türetilmesine ilişkin kuralları tanımlar. Bu kurallar [yapılandırmada |configuration] `application › mapping` anahtarı altında belirtilir. + +Bu sayfada daha önce bahsedilen dizin yapıları aşağıdaki eşleştirmeye dayanmaktadır: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Modül kullanmayan bir örnekle başlayalım. Sadece sunum yapan sınıfların `App\Presenters` ad alanına sahip olmasını isteyeceğiz. Bu, `Home` gibi bir sunucunun `App\Presenters\HomePresenter` sınıfıyla eşleşmesi gerektiği anlamına gelir. Bu, aşağıdaki yapılandırma ile gerçekleştirilebilir: +Eşleme nasıl çalışır? Daha iyi anlamak için öncelikle modülsüz bir uygulama hayal edelim. Sunucu sınıflarının `App\UI` ad alanı altında olmasını istiyoruz, böylece `Home` sunucusu `App\UI\HomePresenter` sınıfıyla eşleşir. Bu, şu yapılandırma ile gerçekleştirilebilir: ```neon application: - mapping: App\Presenters\*Presenter + mapping: App\UI\*Presenter ``` -Sunum yapan kişinin adı sınıf maskesindeki yıldız işaretiyle değiştirilir ve sonuç sınıf adı olur. Kolay! +Bu eşleme, `App\UI\*Presenter` maskesindeki yıldız işaretini `Home` sunum yapan kişi adıyla değiştirerek çalışır ve sonuçta `App\UI\HomePresenter` nihai sınıf adı elde edilir. Çok basit! + +Ancak, bu ve diğer bölümlerdeki örneklerde görebileceğiniz gibi, sunum yapan sınıfları isimsiz alt dizinlere yerleştiriyoruz, örneğin, `Home` sunum yapan `App\UI\Home\HomePresenter` sınıfıyla eşleştirilmiştir. Bu, yıldız işaretinin iki katına çıkarılmasıyla elde edilir (Nette Application 3.2 gerektirir): + +```neon +application: + mapping: App\UI\**Presenter +``` -Sunum yapanları modüllere ayırırsak, her modül için kendi eşlememizi yapabiliriz: +Şimdi, sunum yapan kişileri modüllerle eşleştirmeye geçelim. Her modül için özel eşlemeler tanımlayabiliriz: ```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 ``` -Şimdi `Front:Home` sunucusu `App\Modules\Front\Presenters\HomePresenter` sınıfıyla ve `Admin:Dashboard` sunucusu `App\Modules\Admin\Presenters\DashboardPresenter` sınıfıyla eşleşir. +Bu yapılandırmaya göre, `Front:Home` sunucusu `App\UI\Front\Home\HomePresenter` sınıfıyla eşleşirken, `Api:OAuth` sunucusu `App\Api\OAuthPresenter` sınıfıyla eşleşir. -İlk ikisini değiştirmek için genel bir (yıldız) kural oluşturmak daha pratiktir. Ekstra yıldız işareti sadece modül için sınıf maskesine eklenecektir: + `Front` ve `Admin` modülleri benzer bir eşleme yaklaşımına sahip olduğundan ve bu türden daha fazla modül olması muhtemel olduğundan, bunların yerini alan genel bir kural oluşturmak mümkündür. Sınıf maskesine modül için yeni bir yıldız işareti eklenir: ```neon application: mapping: - *: App\Modules\*\Presenters\*Presenter + *: App\UI\*\**Presenter Api: App\Api\*Presenter ``` -Peki ya iç içe modüller kullanıyorsak ve bir sunumcumuz varsa `Admin:User:Edit`? Bu durumda, her seviye için modülü temsil eden yıldız işaretli bölüm basitçe tekrarlanır ve sonuç `App\Modules\Admin\User\Presenters\EditPresenter` sınıfı olur. +Sunum yapan kişi `Admin:User:Edit` gibi çok seviyeli iç içe modüller için yıldız işareti segmenti her seviye için tekrarlanır ve `App\UI\Admin\User\Edit\EditPresenter` sınıfı ortaya çıkar. -Alternatif bir gösterim, bir dize yerine üç segmentten oluşan bir dizi kullanmaktır. Bu gösterim bir öncekine eşdeğerdir: +Alternatif bir gösterim, dize yerine üç segmentten oluşan bir dizi kullanmaktır. Bu gösterim bir öncekine eşdeğerdir: ```neon application: mapping: - *: [App\Modules, *, Presenters\*Presenter] + *: [App\UI, *, **Presenter] + Api: [App\Api, '', *Presenter] ``` -Varsayılan değer `*Module\*Presenter`'dur. +Konfigürasyonda sadece bir kuralımız varsa, genel olanı, kısaca yazabiliriz: + +```neon +application: + mapping: App\UI\*\**Presenter +``` diff --git a/application/tr/presenters.texy b/application/tr/presenters.texy index 512b73e142..78a564da6d 100644 --- a/application/tr/presenters.texy +++ b/application/tr/presenters.texy @@ -60,7 +60,7 @@ Yönteme benzer şekilde `render()`. O sırada `render()` 'de daha s Bu önemlidir `action()` daha önce çağrılır `render()`Bu nedenle, içinde muhtemelen yaşam döngüsünün bir sonraki seyrini değiştirebiliriz, yani oluşturulacak şablonu ve ayrıca yöntemi değiştirebiliriz `render()` `setView('otherView')` kullanılarak çağrılacaktır. -İstekten gelen parametreler yönteme aktarılır. Parametreler için tür belirtmek mümkündür ve önerilir, örneğin `actionShow(int $id, string $slug = null)` - `id` parametresi eksikse veya tamsayı değilse, sunum yapan kişi [404 hatası |#Error 404 etc.] döndürür ve işlemi sonlandırır. +İstekten gelen parametreler yönteme aktarılır. Parametreler için tür belirtmek mümkündür ve önerilir, örneğin `actionShow(int $id, ?string $slug = null)` - `id` parametresi eksikse veya tamsayı değilse, sunum yapan kişi [404 hatası |#Error 404 etc.] döndürür ve işlemi sonlandırır. `handle(args...)` .{toc: handle()} @@ -205,7 +205,7 @@ $this->redirect(/* ... */); Hata 404 vb. .[#toc-error-404-etc] ================================== -Örneğin görüntülemek istediğimiz makale veritabanında bulunmadığı için isteği yerine getiremediğimizde, HTTP hatası 404'ü temsil eden `error(string $message = null, int $httpCode = 404)` yöntemini kullanarak 404 hatasını atacağız: +Örneğin görüntülemek istediğimiz makale veritabanında bulunmadığı için isteği yerine getiremediğimizde, HTTP hatası 404'ü temsil eden `error(?string $message = null, int $httpCode = 404)` yöntemini kullanarak 404 hatasını atacağız: ```php public function renderShow(int $id): void @@ -384,7 +384,7 @@ Yeniden yönlendirme bir AJAX veya POST isteği ile gerçekleşmez çünkü veri Ayrıca, `link()` yöntemi gibi sunum yapan kişiyi, eylemleri ve parametreleri bağımsız değişken olarak alan `canonicalize()` yöntemini kullanarak kanonlaştırmayı manuel olarak da çağırabilirsiniz. Bir bağlantı oluşturur ve bunu geçerli URL ile karşılaştırır. Farklıysa, oluşturulan bağlantıya yönlendirir. ```php -public function actionShow(int $id, string $slug = null): void +public function actionShow(int $id, ?string $slug = null): void { $realSlug = $this->facade->getSlugForId($id); // eğer $slug, $realSlug'dan farklıysa yönlendirir @@ -452,17 +452,6 @@ Erişim Kısıtlaması Kullanımı `#[Requires]` .[#toc-access-restriction-using Bu `#[Requires]` özniteliği, sunum yapanlara ve yöntemlerine erişimi kısıtlamak için gelişmiş seçenekler sağlar. HTTP yöntemlerini belirtmek, AJAX istekleri gerektirmek, erişimi aynı kaynakla sınırlamak ve erişimi yalnızca yönlendirme ile kısıtlamak için kullanılabilir. Öznitelik, sunum yapan sınıfların yanı sıra aşağıdaki gibi bireysel yöntemlere de uygulanabilir `action()`, `render()`, `handle()`ve `createComponent()`. -İşte sadece HTTP `POST` yöntemine erişimi kısıtlamak için bir kullanım örneği: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Bu kısıtlamaları belirtebilirsiniz: - HTTP yöntemleri üzerinde: `#[Requires(methods: ['GET', 'POST'])]` - AJAX isteği gerektiriyor: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ Bu kısıtlamaları belirtebilirsiniz: - yalnızca yönlendirme yoluyla erişim: `#[Requires(forward: true)]` - belirli eylemlere ilişkin kısıtlamalar: `#[Requires(actions: 'default')]` -Koşullar, birden fazla öznitelik listelenerek veya bir araya getirilerek birleştirilebilir: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +Ayrıntılar için, bkz [Nasıl kullanılır Requires öznitelik |best-practices:attribute-requires]. HTTP Yöntem Kontrolü .[#toc-http-method-check] diff --git a/application/tr/routing.texy b/application/tr/routing.texy index cbd1656234..3a0b50b5d3 100644 --- a/application/tr/routing.texy +++ b/application/tr/routing.texy @@ -216,7 +216,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ @@ -225,7 +225,7 @@ $router->addRoute('/[/]', [ ]); ``` -Ya da bu formu kullanabiliriz, doğrulama düzenli ifadesinin yeniden yazıldığına dikkat edin: +Daha ayrıntılı bir belirtim için, varsayılan değerlere ek olarak, doğrulama düzenli ifadesi gibi diğer parametre özelliklerinin de ayarlanabildiği daha da genişletilmiş bir form kullanılabilir (bkz. `id` parametresi): ```php use Nette\Routing\Route; @@ -243,7 +243,7 @@ $router->addRoute('/[/]', [ ]); ``` -Bu daha konuşkan formatlar diğer meta verileri eklemek için kullanışlıdır. +Dizide tanımlanan parametreler yol maskesine dahil edilmemişse, URL'de bir soru işaretinden sonra belirtilen sorgu parametreleri kullanılsa bile değerlerinin değiştirilemeyeceğine dikkat etmek önemlidir. Filtreler ve Çeviriler .[#toc-filters-and-translations] @@ -477,10 +477,10 @@ $router->addRoute('index', /* ... */); Entegrasyon .[#toc-integration] =============================== -Yönlendiricimizi uygulamaya bağlamak için DI konteynerine bunu söylemeliyiz. Bunun en kolay yolu router nesnesini oluşturacak fabrikayı hazırlamak ve container konfigürasyonuna bunu kullanmasını söylemektir. Diyelim ki bu amaçla bir metot yazdık `App\Router\RouterFactory::createRouter()`: +Yönlendiricimizi uygulamaya bağlamak için DI konteynerine bunu söylemeliyiz. Bunun en kolay yolu router nesnesini oluşturacak fabrikayı hazırlamak ve container konfigürasyonuna bunu kullanmasını söylemektir. Diyelim ki bu amaçla bir metot yazdık `App\Core\RouterFactory::createRouter()`: ```php -namespace App\Router; +namespace App\Core; use Nette\Application\Routers\RouteList; @@ -499,7 +499,7 @@ Daha sonra [yapılandırmaya |dependency-injection:services] yazıyoruz: ```neon services: - - App\Router\RouterFactory::createRouter + - App\Core\RouterFactory::createRouter ``` Veritabanı bağlantısı vb. gibi tüm bağımlılıklar, [otomatik |dependency-injection:autowiring] bağlantı kullanılarak fabrika yöntemine parametreleri olarak aktarılır: @@ -663,7 +663,7 @@ Ayrı kullanımla, yönlendiricinin yeteneklerinin Nette Uygulaması ve sunucula Bu yüzden yine örneğin bir yönlendirici oluşturacak bir yöntem oluşturacağız: ```php -namespace App\Router; +namespace App\Core; use Nette\Routing\RouteList; @@ -694,7 +694,7 @@ $httpRequest = $container->getByType(Nette\Http\IRequest::class); Ya da doğrudan nesneler oluşturacağız: ```php -$router = App\Router\RouterFactory::createRouter(); +$router = App\Core\RouterFactory::createRouter(); $httpRequest = (new Nette\Http\RequestFactory)->fromGlobals(); ``` diff --git a/application/tr/templates.texy b/application/tr/templates.texy index d817bc2d45..bd2fea6eee 100644 --- a/application/tr/templates.texy +++ b/application/tr/templates.texy @@ -34,35 +34,81 @@ Bu da eylem şablonu olabilir: Düzende `{include content}` yerine eklenen `content` bloğunu tanımlar ve ayrıca düzende `{block title}` 'un üzerine yazılan `title` bloğunu yeniden tanımlar. Sonucu hayal etmeye çalışın. -Şablon Arama .[#toc-search-for-templates] ------------------------------------------ +Şablon Arama .[#toc-template-lookup] +------------------------------------ + +Sunucularda, hangi şablonun işleneceğini belirtmenize gerek yoktur; çerçeve yolu otomatik olarak belirleyerek kodlamayı sizin için kolaylaştıracaktır. + +Her sunucunun kendi dizinine sahip olduğu bir dizin yapısı kullanıyorsanız, şablonu bu dizine eylemin adı (yani görünüm) altında yerleştirmeniz yeterlidir. Örneğin, `default` eylemi için `default.latte` şablonunu kullanın: -Şablonların yolu basit bir mantığa göre çıkarılır. Sunucu sınıfının bulunduğu dizine göre bu şablon dosyalarından birinin var olup olmadığına bakılır, burada `` geçerli sunum yapan kişinin adı ve `` geçerli eylemin adıdır: +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -- `templates//.latte` -- `templates/..latte` +Sunucuların tek bir dizinde ve şablonların `templates` klasöründe bir arada olduğu bir yapı kullanıyorsanız, bunu bir dosyaya kaydedin `..latte` veya `/.latte`: -Şablon bulunamazsa, `templates` dizininde bir seviye yukarıda, yani sunum yapan sınıfın bulunduğu dizinle aynı seviyede arama yapmaya çalışacaktır. +/--pre +app/ +└── Presenters/ + ├── HomePresenter.php + └── templates/ + ├── Home.default.latte ← 1st variant + └── Home/ + └── default.latte ← 2nd variant +\-- -Şablon orada da bulunamazsa, yanıt [404 hatası |presenters#Error 404 etc.] olur. + `templates` dizini, sunum yapan sınıfların bulunduğu dizinle aynı seviyede, bir seviye daha yükseğe de yerleştirilebilir. -Ayrıca `$this->setView('otherView')` adresini kullanarak görünümü değiştirebilirsiniz. Ya da arama yapmak yerine `$this->template->setFile('/path/to/template.latte')` adresini kullanarak şablon dosyasının adını doğrudan belirtin. +Şablon bulunamazsa, sunum yapan kişi [404 - sayfa bulunamadı hatası |presenters#Error 404 etc] ile yanıt verir. + +Görünümü `$this->setView('anotherView')` adresini kullanarak değiştirebilirsiniz. Şablon dosyasını `$this->template->setFile('/path/to/template.latte')` ile doğrudan belirtmek de mümkündür. .[note] -Olası dosya yollarından oluşan bir dizi döndüren [formatTemplateFiles |api:Nette\Application\UI\Presenter::formatTemplateFiles()] yöntemini geçersiz kılarak şablonların arandığı yolları değiştirebilirsiniz. +Şablonların arandığı dosyalar, olası dosya adlarından oluşan bir dizi döndüren [formatTemplateFiles() |api:Nette\Application\UI\Presenter::formatTemplateFiles()] yöntemi geçersiz kılınarak değiştirilebilir. + + +Düzen Şablonu Arama .[#toc-layout-template-lookup] +-------------------------------------------------- + +Nette ayrıca otomatik olarak yerleşim dosyasını arar. + +Her sunum yapan kişinin kendi dizinine sahip olduğu bir dizin yapısı kullanıyorsanız, düzeni ya sadece sunum yapan kişiye özelse sunum yapan kişinin bulunduğu klasöre ya da birden fazla sunum yapan kişi için ortaksa bir üst seviyeye yerleştirin: + +/--pre +app/ +└── UI/ + ├── @layout.latte ← common layout + └── Home/ + ├── @layout.latte ← only for Home presenter + ├── HomePresenter.php + └── default.latte +\-- + +Sunum yapanların tek bir dizinde gruplandığı ve şablonların `templates` klasöründe bulunduğu bir yapı kullanıyorsanız, düzen aşağıdaki yerlerde beklenecektir: -Düzen aşağıdaki dosyalarda beklenmektedir: +/--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` birden fazla sunumcu için ortak düzen +Sunucu bir [modüldeyse |modules], modülün iç içe geçmesine göre dizin ağacının daha yukarısında da arama yapacaktır. -`` geçerli sunum yapan kişinin adı ve `` varsayılan olarak `'layout'` olan düzenin adıdır. İsim `$this->setLayout('otherLayout')` ile değiştirilebilir, böylece `@otherLayout.latte` dosyaları denenecektir. +Düzenin adı `$this->setLayout('layoutAdmin')` kullanılarak değiştirilebilir ve ardından `@layoutAdmin.latte` dosyasında beklenir. Düzen şablon dosyasını `$this->setLayout('/path/to/template.latte')` adresini kullanarak doğrudan da belirtebilirsiniz. -Düzen şablonunun dosya adını `$this->setLayout('/path/to/template.latte')` adresini kullanarak doğrudan da belirtebilirsiniz. `$this->setLayout(false)` adresini kullanmak düzen aramayı devre dışı bırakacaktır. +Şablon içinde `$this->setLayout(false)` veya `{layout none}` etiketinin kullanılması düzen aramayı devre dışı bırakır. .[note] -Olası dosya yollarından oluşan bir dizi döndüren [formatLayoutTemplateFiles |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] yöntemini geçersiz kılarak şablonların arandığı yolları değiştirebilirsiniz. +Düzen şablonlarının arandığı dosyalar, olası dosya adlarından oluşan bir dizi döndüren [formatLayoutTemplateFiles() |api:Nette\Application\UI\Presenter::formatLayoutTemplateFiles()] yöntemi geçersiz kılınarak değiştirilebilir. Şablondaki Değişkenler .[#toc-variables-in-the-template] @@ -104,7 +150,7 @@ Sunucudaki `$this->template` nesnesi artık `ArticleTemplate` sınıfının bir Şablonlarda da fısıldama lüksünün tadını çıkarabilirsiniz, sadece PhpStorm'da Latte eklentisini kurun ve şablonun başında sınıf adını belirtin, "Latte: sistem nasıl yazılır":https://blog.nette.org/tr/latte-tip-sistemi-nasil-kullanilir makalesine bakın: ```latte -{templateType App\Presenters\ArticleTemplate} +{templateType App\UI\Article\ArticleTemplate} ... ``` @@ -176,7 +222,7 @@ public function beforeRender(): void Latte sürüm 3, her web projesi için bir [uzantı |latte:creating-extension] oluşturarak daha gelişmiş bir yol sunar. İşte böyle bir sınıfın kabaca bir örneği: ```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 @@ Alternatif olarak, çevirmen [yapılandırma |configuration#Latte] kullanılarak ```neon latte: extensions: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` Çevirmen daha sonra örneğin `|translate` filtresi olarak kullanılabilir ve ek parametreler `translate()` yöntemine aktarılabilir (bkz. `foo, bar`): diff --git a/application/uk/ajax.texy b/application/uk/ajax.texy index b6d0aac241..9a6e49c71c 100644 --- a/application/uk/ajax.texy +++ b/application/uk/ajax.texy @@ -77,6 +77,12 @@ npm install naja ``` +Спочатку потрібно [ініціалізувати |https://naja.js.org/#/guide/01-install-setup-naja?id=initialization] бібліотеку: + +```js +naja.initialize(); +``` + Щоб зробити звичайне посилання (сигнал) або відправку форми AJAX-запитом, просто позначте відповідне посилання, форму або кнопку класом `ajax`: ```html diff --git a/application/uk/bootstrap.texy b/application/uk/bootstrap.texy index 0ebf2ae13d..aef63e390f 100644 --- a/application/uk/bootstrap.texy +++ b/application/uk/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. Потім він отримує сервіс `Application`, який запускає веб-додаток: +Початковим файлом для веб-додатків є `index.php`, розташований у загальнодоступному каталозі `www/`. Він використовує клас `Bootstrap` для ініціалізації середовища і створення DI-контейнера. Потім він отримує сервіс `Application` з контейнера, який запускає веб-додаток: ```php -// ініціалізуємо середовище + отримуємо об'єкт Configurator -$configurator = App\Bootstrap::boot(); -// створюємо DI-контейнер -$container = $configurator->createContainer(); -// DI-контейнер створює об'єкт Nette\Application\Application +$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 @@ Nette розрізняє два основні режими, в яких вик Якщо ви хочете ввімкнути режим розробки в інших випадках, наприклад, для програмістів, які отримують доступ з певної IP-адреси, ви можете використовувати `setDebugMode()`: ```php -$configurator->setDebugMode('23.75.345.200'); // одна або більше IP-адрес +$this->configurator->setDebugMode('23.75.345.200'); // одна або більше IP-адрес ``` Ми безумовно рекомендуємо поєднувати IP-адресу з файлом cookie. Ми зберігатимемо секретний токен у 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-контейнера, 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/services.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()`, щоб розрізняти веб-проекти. Крім того, ви можете додати інші методи, такі як `bootTestEnvironment()` для ініціалізації середовища для модульних тестів, `bootConsoleApplication()` для скриптів, що викликаються з командного рядка, і так далі. ```php -public static function bootForTests(): Configurator +public function bootTestEnvironment(): Nette\DI\Container +{ + Tester\Environment::setup(); // Ініціалізація Nette Tester + $this->setupContainer(); + return $this->configurator->createContainer(); +} + +public function bootConsoleApplication(): Nette\DI\Container { - $configurator = self::boot(); - Tester\Environment::setup(); // Инициализация Nette Tester - return $configurator; + $this->configurator->setDebugMode(false); + $this->initializeEnvironment(); + $this->setupContainer(); + return $this->configurator->createContainer(); } ``` diff --git a/application/uk/components.texy b/application/uk/components.texy index 37b1d65983..b8b4c592e4 100644 --- a/application/uk/components.texy +++ b/application/uk/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] ================================================ diff --git a/application/uk/configuration.texy b/application/uk/configuration.texy index 3fe82774a3..9e34868a04 100644 --- a/application/uk/configuration.texy +++ b/application/uk/configuration.texy @@ -95,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 ``` @@ -104,7 +107,7 @@ latte: ```neon latte: расширения: - - Latte\Essential\TranslatorExtension + - Latte\Essential\TranslatorExtension(@Nette\Localization\Translator) ``` /--comment diff --git a/application/uk/how-it-works.texy b/application/uk/how-it-works.texy index be1f7ce5aa..12f465f683 100644 --- a/application/uk/how-it-works.texy +++ b/application/uk/how-it-works.texy @@ -22,13 +22,13 @@ /--pre web-project/ ├── app/ ← каталог с приложением -│ ├── Presenters/ ← классы презентеров -│ │ ├── HomePresenter.php ← Класс презентера главной страницы -│ │ └── templates/ ← директория шаблонов -│ │ ├── @layout.latte ← шаблон общего макета -│ │ └── Home/ ← шаблоны презентера главной страницы -│ │ └── default.latte ← шаблон действия `default` -│ ├── Router/ ← конфигурация URL-адресов +│ ├── Core/ ← основні необхідні класи +│ │ └── RouterFactory.php ← конфігурація URL-адрес +│ ├── UI/ ← презентатори, шаблони та інше +│ │ ├── @layout.latte ← шаблон спільного макета +│ │ └── Home/ ← домашній каталог доповідачів +│ │ ├── HomePresenter.php ← клас головного доповідача +│ │ └── default.latte ← шаблон дії default │ └── Bootstrap.php ← загрузочный класс Bootstrap ├── bin/ ← скрипты командной строки ├── config/ ← файлы конфигурации @@ -91,7 +91,7 @@ Nette - це наставник, який спрямовує вас до нап Додаток починає роботу з того, що просить так званий маршрутизатор вирішити, якому з презентерів передати поточний запит на обробку. Маршрутизатор вирішує, чия це відповідальність. Він переглядає вхідний URL `https://example.com/product/123`, який хоче `показать` продукт із `id: 123` як дію. Доброю звичкою є написання пар презентер + дія, розділених двокрапкою: `Продукт:показать`. -Тому маршрутизатор перетворив 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 створити презентера, тому що створення об'єктів - це його робота. @@ -121,12 +121,9 @@ class ProductPresenter extends Nette\Application\UI\Presenter Після цього презентер повертає відповідь. Це може бути HTML-сторінка, зображення, XML-документ, надсилання файлу з диска, JSON або перенаправлення на іншу сторінку. Важливо зазначити, що якщо ми явно не вказуємо, як реагувати (що має місце у випадку з `ProductPresenter`), відповіддю буде відображення шаблону з HTML-сторінкою. Чому? Ну, тому що в 99% випадків ми хочемо відобразити шаблон, тому презентер приймає таку поведінку за замовчуванням і хоче полегшити нашу роботу. Це точка зору Nette. -Нам навіть не потрібно вказувати, який шаблон потрібно вивести, він сам виводить шлях до нього відповідно до простої логіки. У випадку з презентером `Product` і дією `show`, він намагається перевірити, чи існує один із цих файлів шаблонів відносно каталогу, в якому знаходиться клас `ProductPresenter`: +Нам навіть не потрібно вказувати, який шаблон рендерити; фреймворк сам визначить шлях. У випадку дії `show` він просто намагається завантажити шаблон `show.latte` з каталогу з класом `ProductPresenter`. Він також намагається знайти макет у файлі `@layout.latte` (докладніше про [пошук шаблонів |templates#Template Lookup]). -- `templates/Product/show.latte` -- `templates/Product.show.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/uk/modules.texy b/application/uk/modules.texy index e660553440..a9b28a35c7 100644 --- a/application/uk/modules.texy +++ b/application/uk/modules.texy @@ -2,29 +2,31 @@ ****** .[perex] -У Nette модулі являють собою логічні одиниці, з яких складається додаток. Вони включають ведучі, шаблони, можливо, компоненти та класи моделей. +Модулі вносять ясність у додатки Nette, полегшуючи поділ на логічні блоки. -Одного компонента для презентаторів і одного для шаблонів буде недостатньо для реальних проектів. Наявність десятків файлів в одній папці щонайменше неорганізована. Як вийти з цього становища? Ми просто розділяємо їх на підкаталоги на диску і на простори імен у коді. І це саме те, що роблять модулі Nette. - -Тому давайте забудемо про єдину папку для ведучих і шаблонів і натомість створимо модулі, наприклад, `Admin` і `Front`. +Подібно до організації файлів у папки на жорсткому диску, в Nette ми можемо розділити презентатори, шаблони та інші допоміжні класи на модулі. Як це працює на практиці? Просто додаванням нових підкаталогів до структури. Ось приклад структури з двома модулями, Front і Admin: /--pre -app/ -├── Presenters/ -├── Modules/ ← директория с модулями -│ ├── Admin/ ← модуль Admin -│ │ ├── Presenters/ ← его презентеры -│ │ │ ├── DashboardPresenter.php -│ │ │ └── templates/ -│ └── Front/ ← модуль Front -│ └── Presenters/ ← его презентеры -│ └── ... +app/ +├── UI/ +│ ├── Admin/ ← Admin module +│ │ ├── @layout.latte +│ │ ├── Dashboard/ +│ │ │ ├── DashboardPresenter.php +│ │ │ └── default.latte +│ │ └── ... +│ ├── Front/ ← Front module +│ │ ├── @layout.latte +│ │ ├── Home/ +│ │ │ ├── HomePresenter.php +│ │ │ └── default.latte +│ │ └── ... \-- -Ця структура каталогів буде відображена в просторах імен класів, так, наприклад, `DashboardPresenter` буде знаходитися в просторі `App\Modules\Admin\Presenters`: +Ця структура каталогів відображається в просторах імен класів, тому, наприклад, `DashboardPresenter` знаходиться в просторі імен `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 знає, що `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/ ← директория с модулями -│ ├── Blog/ ← модуль Blog -│ │ ├── Admin/ ← подмодуль Admin -│ │ │ ├── Presenters/ +app/ +├── UI/ +│ ├── Blog/ ← Blog module +│ │ ├── Admin/ ← Admin submodule +│ │ │ ├── Dashboard/ +│ │ │ └── ... +│ │ ├── Front/ ← Front submodule +│ │ │ ├── @layout.latte +│ │ │ ├── Home/ │ │ │ └── ... -│ │ └── Front/ ← подмодуль Front -│ │ ├── Presenters/ -│ │ └── ... -│ ├── Forum/ ← модуль Forum +│ ├── Forum/ ← Forum module │ │ └── ... \-- -Таким чином, модуль `Blog` розбивається на підмодулі `Admin` і `Front`. І знову ж таки це буде відображено в просторах імен, які будуть `App\Modules\Blog\Admin\Presenters` тощо. Ведучий `Dashboard` всередині підмодуля називається `Blog:Admin:Dashboard`. +Модуль `Blog` поділяється на підмодулі `Admin` і `Front`. Це також відображається у просторах імен, які потім з'являються як `App\UI\Blog\Admin` і подібним чином. Щоб звернутися до доповідача `Dashboard` у підмодулі `Admin`, ми посилаємося на нього як `Blog:Admin:Dashboard`. -Розгалуження може бути настільки глибоким, наскільки ви захочете, тому ви можете створювати підмодулі. +Вкладеність може бути настільки глибокою, наскільки це необхідно, що дозволяє створювати підмодулі. + +Наприклад, якщо в адмініструванні ви маєте багато доповідачів, пов'язаних з управлінням замовленнями, таких як `OrderDetail`, `OrderEdit`, `OrderDispatch` і т.д., ви можете створити модуль `Order`, в якому будуть організовані доповідачі `Detail`, `Edit`, `Dispatch` та інші. Створення посилань .[#toc-creating-links] @@ -99,49 +115,69 @@ class DashboardPresenter extends Nette\Application\UI\Presenter Див. [розділ про маршрутизацію |routing#modules]. -Складання карти .[#toc-mapping] +Картографування .[#toc-mapping] ------------------------------- -Визначає правила, за якими ім'я класу виводиться з імені ведучого. Ми записуємо їх у [конфігурацію |configuration] під ключем `application › mapping`. +Відображення визначає правила отримання імені класу з імені доповідача. Ці правила задаються у [конфігурації |configuration] під ключем `application › mapping`. + +Структури каталогів, згадані раніше на цій сторінці, базуються на наступному відображенні: + +```neon +application: + mapping: App\UI\*\**Presenter +``` -Почнемо з прикладу, в якому не використовуються модулі. Ми просто хочемо, щоб класи ведучого мали простір імен `App\Presenters`. Тобто ми хочемо, щоб ведучий, наприклад, `Home` відображався на клас `App\Presenters\HomePresenter`. Цього можна досягти за допомогою такої конфігурації: +Як працює мапування? Для кращого розуміння, давайте спочатку уявимо додаток без модулів. Ми хочемо, щоб класи доповідача потрапляли до простору імен `App\UI`, щоб доповідач `Home` відображався у клас `App\UI\HomePresenter`. Цього можна досягти за допомогою такої конфігурації: ```neon application: - mapping: App\Presenters\*Presenter + 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\HomePresenter`, а презентер `Admin:Dashboard` ` - `App\AdminModule\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/uk/presenters.texy b/application/uk/presenters.texy index 9f85a0c5cb..47e3d7b0c0 100644 --- a/application/uk/presenters.texy +++ b/application/uk/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 @@ -384,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 @@ -452,17 +452,6 @@ $this->sendResponse(new Responses\CallbackResponse($callback)); Атрибут `#[Requires]` надає розширені можливості для обмеження доступу до доповідачів та їхніх методів. Його можна використовувати для визначення HTTP-методів, вимагати AJAX-запитів, обмежувати доступ до одного і того ж джерела та обмежувати доступ лише пересиланням. Атрибут можна застосовувати до класів презентера, а також до окремих методів, таких як `action()`, `render()`, `handle()`та `createComponent()`. -Ось приклад його використання для обмеження доступу лише до методу HTTP `POST`: - -```php -use Nette\Application\Attributes\Requires; - -#[Requires(methods: 'POST')] -class MyPresenter extends Nette\Application\UI\Presenter -{ -} -``` - Ви можете вказати такі обмеження: - на HTTP-методи: `#[Requires(methods: ['GET', 'POST'])]` - що вимагають AJAX-запиту: `#[Requires(ajax: true)]` @@ -470,22 +459,7 @@ class MyPresenter extends Nette\Application\UI\Presenter - доступ тільки через переадресацію: `#[Requires(forward: true)]` - обмеження на певні дії: `#[Requires(actions: 'default')]` -Умови можна комбінувати, перераховуючи кілька атрибутів або об'єднуючи їх в один: - -```php -#[Requires(methods: 'POST', ajax: true)] -public function actionDelete(int $id) -{ -} - -// or - -#[Requires(methods: 'POST')] -#[Requires(ajax: true)] -public function actionDelete(int $id) -{ -} -``` +За деталями дивіться [Як використовувати атрибут Requires атрибут |best-practices:attribute-requires]. Перевірка методу HTTP .[#toc-http-method-check] diff --git a/application/uk/routing.texy b/application/uk/routing.texy index c19046885b..6f1fc532a6 100644 --- a/application/uk/routing.texy +++ b/application/uk/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', /* ... */); Інтеграція .[#toc-integration] ============================== -Щоб підключити наш маршрутизатор до застосунку, ми повинні повідомити про нього контейнер DI. Найпростіший спосіб - це підготувати фабрику, яка буде створювати об'єкт маршрутизатора, і повідомити конфігурацію контейнера, щоб вона його використовувала. Припустимо, ми напишемо для цього метод `App\Router\RouterFactory::createRouter()`: +Щоб підключити наш маршрутизатор до застосунку, ми повинні повідомити про нього контейнер DI. Найпростіший спосіб - це підготувати фабрику, яка буде створювати об'єкт маршрутизатора, і повідомити конфігурацію контейнера, щоб вона його використовувала. Припустимо, ми напишемо для цього метод `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 ``` Будь-які залежності, такі як підключення до бази даних тощо, передаються методу фабрики як параметри за допомогою [autowiring |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/uk/templates.texy b/application/uk/templates.texy index 2dc5f8f053..c4ed7aee2b 100644 --- a/application/uk/templates.texy +++ b/application/uk/templates.texy @@ -34,35 +34,81 @@ Nette використовує систему шаблонів [Latte |latte:]. Він визначає блок `content`, який вставляється замість `{include content}` у макеті, а також перевизначає блок `title`, який перезаписує `{block title}` у макеті. Спробуйте уявити собі результат. -Пошук шаблонів .[#toc-search-for-templates] -------------------------------------------- +Пошук шаблонів .[#toc-template-lookup] +-------------------------------------- -Шлях до шаблонів визначається ведучим за допомогою простої логіки. Він спробує перевірити, чи є один із цих файлів, розташований відносно каталогу класу ведучого, де `` це ім'я поточного ведучого, а `` це ім'я поточної події: +У презентаторах вам не потрібно вказувати, який шаблон має бути відрендерений; фреймворк автоматично визначить шлях, полегшуючи вам кодування. -- `templates//.latte` -- `templates/..latte` +Якщо ви використовуєте структуру каталогів, де кожен презентер має власний каталог, просто розмістіть шаблон у цьому каталозі під назвою дії (тобто подання). Наприклад, для дії `default` використовуйте шаблон `default.latte`: -Якщо шаблон не буде знайдено, він спробує виконати пошук у каталозі `templates` на один рівень вище, тобто на тому ж рівні, що і каталог з класом presenter. +/--pre +app/ +└── UI/ + └── Home/ + ├── HomePresenter.php + └── default.latte +\-- -Якщо шаблон не буде знайдено і там, у відповідь буде видано [помилку 404 |presenters#Error 404 etc.]. +Якщо ви використовуєте структуру, де доповідачі знаходяться разом в одному каталозі, а шаблони - в папці `templates`, збережіть її або у файлі `..latte` або `/.latte`: -Ви також можете змінити вигляд за допомогою `$this->setView('jineView')`. Або, замість прямого пошуку, вкажіть ім'я файлу шаблону за допомогою `$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('jinyLayout')`, тому будуть випробувані файли `@jinyLayout.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/uk/latte-yak-koristuvatisya-sistemoyu-tipiv: ```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/best-practices/bg/@home.texy b/best-practices/bg/@home.texy index 2fc7fff5c7..814ac362f9 100644 --- a/best-practices/bg/@home.texy +++ b/best-practices/bg/@home.texy @@ -17,6 +17,8 @@ - [Как да се върнете към предишна страница |restore-request] - [Страница на резултатите от базата данни |Pagination] - [Динамични фрагменти |dynamic-snippets] +- [Как да използвате атрибута #Requires |attribute-requires] +- [Как да използвате правилно POST връзки |post-links]
diff --git a/best-practices/bg/attribute-requires.texy b/best-practices/bg/attribute-requires.texy new file mode 100644 index 0000000000..3918ac851d --- /dev/null +++ b/best-practices/bg/attribute-requires.texy @@ -0,0 +1,179 @@ +Как да използвате `#[Requires]` Атрибут +*************************************** + +.[perex] +Когато пишете уеб приложение, често се сблъсквате с необходимостта да ограничите достъпа до определени части на приложението. Може би искате някои заявки да могат да изпращат данни само чрез формуляр (като по този начин се използва методът POST) или да са достъпни само за AJAX повиквания. В Nette Framework 3.2 е въведен нов инструмент, който ви позволява да задавате такива ограничения по елегантен и ясен начин: инструментът `#[Requires]` атрибут. + +Атрибутът е специален маркер в PHP, който се добавя преди дефиницията на даден клас или метод. Тъй като по същество това е клас, трябва да включите клаузата use, за да работят следващите примери: + +```php +use Nette\Application\Attributes\Requires; +``` + +Можете да използвате `#[Requires]` атрибут в самия клас на презентатора и в тези методи: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Последните два метода също се отнасят за компоненти, така че можете да използвате атрибута и при тях. + +Ако условията, определени от атрибута, не са изпълнени, се задейства грешка HTTP 4xx. + + +HTTP методи .[#toc-http-methods] +-------------------------------- + +Можете да зададете кои HTTP методи (като GET, POST и т.н.) са разрешени за достъп. Например, ако искате да разрешите достъп само чрез изпращане на формуляр, задайте: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Защо трябва да използвате POST вместо GET за действия за промяна на състоянието и как да го направите? [Прочетете ръководството |post-links]. + +Можете да посочите метод или масив от методи. Специален случай е стойността `'*'`, за да се активират всички методи, което презентаторите не позволяват по подразбиране от [съображения за сигурност |application:presenters#http-method-check]. + + +Извиквания AJAX .[#toc-ajax-calls] +---------------------------------- + +Ако искате даден презентатор или метод да бъде достъпен само за AJAX заявки, използвайте: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Същият произход .[#toc-same-origin] +----------------------------------- + +За да повишите сигурността, можете да изискате заявката да бъде направена от същия домейн. Това предотвратява [уязвимостта към CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +За `handle()` методи, автоматично се изисква достъп от същия домейн. Затова, ако искате да разрешите достъп от всеки домейн, посочете: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Достъп чрез Forward .[#toc-access-via-forward] +---------------------------------------------- + +Понякога е полезно да се ограничи достъпът до даден презентатор, така че той да е достъпен само косвено, например чрез методите `forward()` или `switch()` от друг презентатор. По този начин се защитават презентаторите за грешки, за да се предотврати задействането им от URL адрес: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +В практиката често се налага да се маркират определени изгледи, до които може да се получи достъп само въз основа на логика в презентатора. Отново, за да не могат да бъдат отваряни директно: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Специфични действия .[#toc-specific-actions] +-------------------------------------------- + +Можете също така да ограничите достъпа до определен код, като например създаване на компонент, само за определени действия в презентатора: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +За едно действие не е необходимо да се пише масив: `#[Requires(actions: 'default')]` + + +Потребителски атрибути .[#toc-custom-attributes] +------------------------------------------------ + +Ако искате да използвате `#[Requires]` атрибут с едни и същи настройки, можете да създадете свой собствен атрибут, който ще наследи `#[Requires]` и да го настроите според нуждите си. + +Например, `#[SingleAction]` позволява достъп само чрез действието `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Или `#[RestMethods]` ще позволи достъп чрез всички HTTP методи, използвани за REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Заключение .[#toc-conclusion] +----------------------------- + +На `#[Requires]` ви дава голяма гъвкавост и контрол върху начина, по който се осъществява достъпът до вашите уеб страници. С помощта на прости, но мощни правила можете да повишите сигурността и правилното функциониране на вашето приложение. Както виждате, използването на атрибути в Nette може не само да опрости работата ви, но и да я осигури. + +{{sitename: Best Practices}} diff --git a/best-practices/bg/composer.texy b/best-practices/bg/composer.texy index 13bf7df4cc..2852264f64 100644 --- a/best-practices/bg/composer.texy +++ b/best-practices/bg/composer.texy @@ -189,7 +189,7 @@ Packagist.org - глобално хранилище .[#toc-packagist-org-global- Впоследствие трябва да стартирате командата `composer dumpautoload` при всяка промяна и да позволите на таблиците на автоматичния модул да се възстановят. Това е изключително неудобно и е много по-добре да оставите тази задача на [RobotLoader |robot-loader:], който върши същата работа автоматично във фонов режим и много по-бързо. -Вторият вариант е да следвате [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Казано по-просто, това е система, в която пространствата от имена и имената на класовете съответстват на структурата на директориите и имената на файловете, т.е. `App\Router\RouterFactory` се намира в `/path/to/App/Router/RouterFactory.php`. Примерна конфигурация: +Вторият вариант е да следвате [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Казано по-просто, това е система, в която пространствата от имена и имената на класовете съответстват на структурата на директориите и имената на файловете, т.е. `App\Core\RouterFactory` се намира в `/path/to/App/Core/RouterFactory.php`. Примерна конфигурация: ```js { diff --git a/best-practices/bg/dynamic-snippets.texy b/best-practices/bg/dynamic-snippets.texy index b927d707e4..7b2db16d21 100644 --- a/best-practices/bg/dynamic-snippets.texy +++ b/best-practices/bg/dynamic-snippets.texy @@ -35,7 +35,7 @@ public function handleUnlike(int $articleId): void Ajaxisation .[#toc-ajaxization] =============================== -Сега нека въведем AJAX в това просто приложение. Промяната на класацията на дадена статия не е достатъчно важна, за да изисква HTTP заявка с пренасочване, така че в идеалния случай това трябва да се прави с AJAX във фонов режим. Ще използваме [скрипта на манипулатора от добавките |https://componette.org/vojtech-dobes/nette.ajax.js/] с обичайната конвенция, че връзките AJAX имат CSS клас `ajax`. +Сега нека въведем AJAX в това просто приложение. Промяната на класацията на дадена статия не е достатъчно важна, за да изисква HTTP заявка с пренасочване, така че в идеалния случай това трябва да се прави с AJAX във фонов режим. Ще използваме [скрипта на манипулатора от добавките |application:ajax#toc-naja] с обичайната конвенция, че връзките AJAX имат CSS клас `ajax`. Как точно да го направим обаче? Nette предлага 2 начина: метода на динамичните фрагменти и метода на компонентите. И двете имат своите плюсове и минуси, затова ще ги покажем последователно. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ class LikeControl extends Nette\Application\UI\Control ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/bg/lets-create-contact-form.texy b/best-practices/bg/lets-create-contact-form.texy index b0f3d7ccf6..80a29d0e4e 100644 --- a/best-practices/bg/lets-create-contact-form.texy +++ b/best-practices/bg/lets-create-contact-form.texy @@ -39,7 +39,7 @@ class HomePresenter extends Presenter Но какво ще стане, ако потребителят не попълни някои полета? В такъв случай трябва да го уведомим, че това е задължително поле. Направихме това с метода `setRequired()`. Накрая добавихме и [събитие |nette:glossary#events] `onSuccess`, което се задейства, ако формулярът е изпратен успешно. В нашия случай то извиква метода `contactFormSucceeded`, който се грижи за обработката на изпратения формуляр. След малко ще добавим това към кода. -Нека компонентът `contantForm` бъде визуализиран в шаблона `templates/Home/default.latte`: +Нека компонентът `contantForm` бъде визуализиран в шаблона `Home/default.latte`: ```latte {block content} diff --git a/best-practices/bg/pagination.texy b/best-practices/bg/pagination.texy index 28256dd731..9d04ddc1b1 100644 --- a/best-practices/bg/pagination.texy +++ b/best-practices/bg/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository След това въвеждаме класа на модела в презентатора и в метода `render` правим справка за публикуваните статии, които предаваме на шаблона: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -В шаблона се грижим за извеждането на списъка със статии: +След това шаблонът `default.latte` ще се погрижи за изброяването на статиите: ```latte {block content} @@ -114,7 +113,7 @@ class ArticleRepository Също така разширяваме метода `render`, за да получим инстанцията Paginator, да я конфигурираме и да изберем желаните статии, които да се показват в шаблона. HomePresenter ще изглежда по следния начин: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Не е необходимо да създаваме Paginator в презентатора, вместо това ще използваме метода на обекта `Selection`, върнат от хранилището: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/bg/post-links.texy b/best-practices/bg/post-links.texy new file mode 100644 index 0000000000..f9299c9cbe --- /dev/null +++ b/best-practices/bg/post-links.texy @@ -0,0 +1,59 @@ +Как правилно да използвате POST връзки +************************************** + +В уеб приложенията, особено в административните интерфейси, трябва да е основно правило, че действия, променящи състоянието на сървъра, не трябва да се извършват чрез метода HTTP GET. Както подсказва името на метода, GET трябва да се използва само за извличане на данни, а не за тяхната промяна. +За действия като изтриване на записи е по-подходящо да се използва методът POST. Въпреки че идеалният вариант би бил да се използва методът DELETE, той не може да бъде извикан без JavaScript, поради което исторически се използва POST. + +Как да го направим на практика? Използвайте този прост трик. В началото на вашия шаблон създайте помощна форма с идентификатор `postForm`, която след това ще използвате за бутоните за изтриване: + +```latte .{file:@layout.latte} +
+``` + +С тази форма можете да използвате `
diff --git a/best-practices/cs/attribute-requires.texy b/best-practices/cs/attribute-requires.texy new file mode 100644 index 0000000000..fdde683a91 --- /dev/null +++ b/best-practices/cs/attribute-requires.texy @@ -0,0 +1,179 @@ +Jak používat atribut `#[Requires]` +********************************** + +.[perex] +Když píšete webovou aplikaci, často se setkáte s potřebou omezit přístup k určitým částem vaší aplikace. Možná chcete, aby některé požadavky mohly odesílat data pouze pomocí formuláře (tedy metodou POST), nebo aby byly přístupné pouze pro AJAXové volání. V Nette Frameworku 3.2 se objevil nový nástroj, který vám umožní taková omezení nastavit velmi elegantně a přehledně: atribut `#[Requires]`. + +Atribut je speciální značka v PHP, kterou přidáte před definici třídy nebo metody. Protože jde vlastně o třídu, aby vám následující příklady fungovaly, je nutné uvést klauzuli use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Atribut `#[Requires]` můžete použít u samotné třídy presenteru a také na těchto metodách: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Poslední dvě metody se týkají i komponent, tedy atribut můžete používat i u nich. + +Pokud nejsou splněny podmínky, které atribut uvádí, dojde k vyvolání HTTP chyby 4xx. + + +Metody HTTP +----------- + +Můžete specifikovat, které HTTP metody (jako GET, POST atd.) jsou pro přístup povolené. Například, pokud chcete povolit přístup pouze odesíláním formuláře, nastavíte: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Proč byste měli používat POST místo GET pro akce měnící stav a jak na to? [Přečtěte si návod |post-links]. + +Můžete uvést metodu nebo pole metod. Speciálním případem je hodnota `'*'`, která povolí všechny metody, což standardně presentery z [bezpečnostních důvodů nedovolují |application:presenters#toc-kontrola-http-metody]. + + +AJAXové volání +-------------- + +Pokud chcete, aby byl presenter nebo metoda dostupná pouze pro AJAXové požadavky, použijte: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Stejný původ +------------ + +Pro zvýšení bezpečnosti můžete vyžadovat, aby byl požadavek učiněn ze stejné domény. Tím zabráníte [zranitelnosti CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +U metod `handle()` je přístup ze stejné domény vyžadován automaticky. Takže pokud naopak chcete povolit přístup z jakékoliv domény, uveďte: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Přístup přes forward +-------------------- + +Někdy je užitečné omezit přístup k presenteru tak, aby byl dostupný pouze nepřímo, například použitím metody `forward()` nebo `switch()` z jiného presenteru. Takto se třeba chrání error-presentery, aby je nebylo možné vyvolat z URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +V praxi bývá často potřeba označit určité views, ke kterým se lze dostat až na základě logiky v presenteru. Tedy opět, aby je nebylo možné otevřít přímo: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = $this->facade->getProduct($id); + if (!$product) { + $this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Konkrétní akce +-------------- + +Můžete také omezit, že určitý kód, třeba vytvoření komponenty, bude dostupné pouze pro specifické akce v presenteru: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +V případě jedné akce není potřeba zapisovat pole: `#[Requires(actions: 'default')]` + + +Vlastní atributy +---------------- + +Pokud chcete použít atribut `#[Requires]` opakovaně s týmž nastavením, můžete si vytvořit vlastní atribut, který bude dědit `#[Requires]` a nastaví ho podle potřeb. + +Například `#[SingleAction]` umožní přístup pouze přes akci `default`: + +```php +#[\Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Nebo `#[RestMethods]` umožní přístup přes všechny HTTP metody používané pro REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Závěr +----- + +Atribut `#[Requires]` vám dává velkou flexibilitu a kontrolu nad tím, jak jsou vaše webové stránky přístupné. Pomocí jednoduchých, ale mocných pravidel můžete zvýšit bezpečnost a správné fungování vaší aplikace. Jak vidíte, použití atributů v Nette může vaši práci nejen usnadnit, ale i zabezpečit. + +{{sitename: Best Practices}} diff --git a/best-practices/cs/composer.texy b/best-practices/cs/composer.texy index 9ac10dc558..137b09c745 100644 --- a/best-practices/cs/composer.texy +++ b/best-practices/cs/composer.texy @@ -58,7 +58,7 @@ composer update Composer stáhne Nette Database do složky `vendor/`. Dále vytvoří soubor `composer.lock`, který obsahuje informace o tom, které verze knihoven přesně nainstaloval. -Composer vygeneruje soubor `vendor/autoload.php`, který můžeme jednoduše zainkludovat a začít používat knihovny bez jakékoli další práce: +Composer vygeneruje soubor `vendor/autoload.php`, který můžeme jednoduše inkludovat a začít používat knihovny bez jakékoli další práce: ```php require __DIR__ . '/vendor/autoload.php'; @@ -189,7 +189,7 @@ Nicméně je možné používat Composer i pro načítání dalších tříd i m Následně je potřeba při každé změně spustit příkaz `composer dumpautoload` a nechat autoloadovací tabulky přegenerovat. To je nesmírně nepohodlné a daleko lepší je tento úkol svěřit [RobotLoaderu|robot-loader:], který stejnou činnost provádí automaticky na pozadí a mnohem rychleji. -Druhou možností je dodržovat [PSR-4|https://www.php-fig.org/psr/psr-4/]. Zjednodušeně řečeno jde o systém, kdy jmenné prostory a názvy tříd odpovídají adresářové struktuře a názvům souborů, tedy např. `App\Router\RouterFactory` bude v souboru `/path/to/App/Router/RouterFactory.php`. Příklad konfigurace: +Druhou možností je dodržovat [PSR-4|https://www.php-fig.org/psr/psr-4/]. Zjednodušeně řečeno jde o systém, kdy jmenné prostory a názvy tříd odpovídají adresářové struktuře a názvům souborů, tedy např. `App\Core\RouterFactory` bude v souboru `/path/to/App/Core/RouterFactory.php`. Příklad konfigurace: ```js { diff --git a/best-practices/cs/dynamic-snippets.texy b/best-practices/cs/dynamic-snippets.texy index 600dc4b9bd..daf3b00928 100644 --- a/best-practices/cs/dynamic-snippets.texy +++ b/best-practices/cs/dynamic-snippets.texy @@ -35,7 +35,7 @@ public function handleUnlike(int $articleId): void Ajaxizace ========= -Pojďme nyní tuto jednoduchou aplikaci vybavit AJAXem. Změna hodnocení článku není natolik důležitá, aby muselo dojít k přesměrování, a proto by ideálně měla probíhat AJAXem na pozadí. Využijeme [obslužného skriptu z doplňků |https://componette.org/vojtech-dobes/nette.ajax.js/] s obvyklou konvencí, že AJAXové odkazy mají CSS třídu `ajax`. +Pojďme nyní tuto jednoduchou aplikaci vybavit AJAXem. Změna hodnocení článku není natolik důležitá, aby muselo dojít k přesměrování, a proto by ideálně měla probíhat AJAXem na pozadí. Využijeme [obslužného skriptu z doplňků |application:ajax#toc-naja] s obvyklou konvencí, že AJAXové odkazy mají CSS třídu `ajax`. Nicméně jak na to konkrétně? Nette nabízí 2 cesty: cestu tzv. dynamických snippetů a cestu komponent. Obě dvě mají svá pro a proti, a proto si je ukážeme jednu po druhé. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Samozřejmě se nám změní šablona view a do presenteru budeme muset doplnit ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/cs/inject-method-attribute.texy b/best-practices/cs/inject-method-attribute.texy index 9bd667a09f..b3e80f1266 100644 --- a/best-practices/cs/inject-method-attribute.texy +++ b/best-practices/cs/inject-method-attribute.texy @@ -61,7 +61,7 @@ class MyPresenter extends Nette\Application\UI\Presenter Výhodou tohoto způsobu předávání závislostí byla velice úsporná podoba zápisu. Nicméně s příchodem [constructor property promotion |https://blog.nette.org/cs/php-8-0-kompletni-prehled-novinek#toc-constructor-property-promotion] se jeví snazší použít konstruktor. -Naopak tento způsob trpí stejnými nedostatky, jako předávání závislosti do properites obecně: nemáme kontrolu nad změnami v proměnné a zároveň se proměnná stává součástí veřejného rozhraní třídy, což je nežádnoucí. +Naopak tento způsob trpí stejnými nedostatky, jako předávání závislosti do properties obecně: nemáme kontrolu nad změnami v proměnné a zároveň se proměnná stává součástí veřejného rozhraní třídy, což je nežádnoucí. {{sitename: Best Practices}} diff --git a/best-practices/cs/lets-create-contact-form.texy b/best-practices/cs/lets-create-contact-form.texy index 78f2f92886..aa24fb819f 100644 --- a/best-practices/cs/lets-create-contact-form.texy +++ b/best-practices/cs/lets-create-contact-form.texy @@ -39,7 +39,7 @@ Jak vidíte, vytvořili jsme dvě metody. První metoda `createComponentContactF Ale co když uživatel nevyplní nějaké pole? V takovém případě bychom mu měli dát vědět, že je to povinné pole. Toho jsme docílili metodou `setRequired()`. Nakonec jsme přidali také [událost |nette:glossary#Události] `onSuccess`, která se spustí, pokud je formulář úspěšně odeslán. V našem případě zavolá metodu `contactFormSucceeded`, která se postará o zpracování odeslaného formuláře. To do kódu doplníme za okamžik. -Komponentu `contantForm` necháme vykreslit v šabloně `templates/Home/default.latte`: +Komponentu `contantForm` necháme vykreslit v šabloně `Home/default.latte`: ```latte {block content} @@ -131,7 +131,7 @@ Zatím se odesílá prostý textový email obsahující pouze zprávu odeslanou ``` -Zbývá upravit `ContactFacade`, aby tuto šablonu používal. V konstruktoru si vyžádáme třídu `LatteFactory`, která umí vyrobit objekt `Latte\Engine`, tedy [vykreslovač Latte šablon |latte:develop#jak-vykreslit-sablonu]. Pomocí metody `renderToString()` šablonu vykreslíme do souboru, prvním parametrem je cesta k šabloně a druhým jsou proměnné. +Zbývá upravit `ContactFacade`, aby tuto šablonu používal. V konstruktoru si vyžádáme třídu `LatteFactory`, která umí vyrobit objekt `Latte\Engine`, tedy [vykreslovač Latte šablon |latte:develop#vykresleni-sablony]. Pomocí metody `renderToString()` šablonu vykreslíme do souboru, prvním parametrem je cesta k šabloně a druhým jsou proměnné. ```php namespace App\Model; diff --git a/best-practices/cs/pagination.texy b/best-practices/cs/pagination.texy index bf7d18a418..8a558bb0b7 100644 --- a/best-practices/cs/pagination.texy +++ b/best-practices/cs/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository V presenteru si pak injectujeme modelovou třídu a v render metodě si vyžádáme publikované články, které předáme do šablony: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -V šabloně se pak postaráme o výpis článků: +V šabloně `default.latte` se pak postaráme o výpis článků: ```latte {block content} @@ -114,7 +113,7 @@ Následně se pustíme do úprav presenteru. Do render metody budeme předávat Dále také render metodu rozšíříme o získání instance Paginatoru, jeho nastavení a výběru správných článků pro zobrazení v šabloně. HomePresenter bude po úpravách vypadat takto: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository V presenteru nemusíme vytvářet Paginator, použijeme místo něj metodu třídy `Selection`, kterou nám vrací repositář: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/cs/post-links.texy b/best-practices/cs/post-links.texy new file mode 100644 index 0000000000..e82bb4798d --- /dev/null +++ b/best-practices/cs/post-links.texy @@ -0,0 +1,59 @@ +Jak správně používat POST odkazy +******************************** + +Ve webových aplikacích, zejména v administrativních rozhraních, by mělo být základním pravidlem, že akce měnící stav serveru by neměly být prováděny prostřednictvím HTTP metody GET. Jak název metody napovídá, GET by měl sloužit pouze k získání dat, nikoli k jejich změně. +Pro akce jako třeba mazání záznamů je vhodnější použít metodu POST. I když ideální by byla metoda DELETE, ale tu nelze bez JavaScriptu vyvolat, proto se historicky používá POST. + +Jak na to v praxi? Využijte tento jednoduchý trik. Na začátku šablony si vytvoříte pomocný formulář s identifikátorem `postForm`, který následně použijete pro mazací tlačítka: + +```latte .{file:@layout.latte} +
+``` + +Díky tomuto formuláři můžete místo klasického odkazu `
` použít tlačítko `
diff --git a/best-practices/de/attribute-requires.texy b/best-practices/de/attribute-requires.texy new file mode 100644 index 0000000000..853a90b4f5 --- /dev/null +++ b/best-practices/de/attribute-requires.texy @@ -0,0 +1,179 @@ +Wie man das `#[Requires]` Attribut +********************************** + +.[perex] +Wenn Sie eine Webanwendung schreiben, stoßen Sie häufig auf die Notwendigkeit, den Zugriff auf bestimmte Teile Ihrer Anwendung zu beschränken. Vielleicht möchten Sie, dass einige Anfragen nur Daten über ein Formular senden können (also die POST-Methode verwenden) oder nur für AJAX-Aufrufe zugänglich sind. In Nette Framework 3.2 wurde ein neues Werkzeug eingeführt, mit dem Sie solche Einschränkungen elegant und klar festlegen können: das `#[Requires]` Attribut. + +Das Attribut ist eine spezielle Markierung in PHP, die Sie vor der Definition einer Klasse oder Methode hinzufügen. Da es sich im Wesentlichen um eine Klasse handelt, müssen Sie die Use-Klausel einfügen, damit die folgenden Beispiele funktionieren: + +```php +use Nette\Application\Attributes\Requires; +``` + +Sie können das `#[Requires]` Attribut mit der Presenter-Klasse selbst und mit diesen Methoden verwenden: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Die letzten beiden Methoden betreffen auch Komponenten, so dass Sie das Attribut auch für diese verwenden können. + +Wenn die durch das Attribut festgelegten Bedingungen nicht erfüllt sind, wird ein HTTP 4xx-Fehler ausgelöst. + + +HTTP-Methoden .[#toc-http-methods] +---------------------------------- + +Sie können angeben, welche HTTP-Methoden (wie GET, POST usw.) für den Zugriff zugelassen sind. Wenn Sie beispielsweise den Zugriff nur durch das Absenden eines Formulars erlauben wollen, legen Sie fest: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Warum sollten Sie POST anstelle von GET für zustandsändernde Aktionen verwenden, und wie geht das? [Lesen Sie den Leitfaden |post-links]. + +Sie können eine Methode oder eine Reihe von Methoden angeben. Ein Sonderfall ist der Wert `'*'`, um alle Methoden zu aktivieren, was Presenter aus [Sicherheitsgründen |application:presenters#http-method-check] standardmäßig nicht zulassen. + + +AJAX-Aufrufe .[#toc-ajax-calls] +------------------------------- + +Wenn Sie möchten, dass ein Presenter oder eine Methode nur für AJAX-Anfragen zugänglich ist, verwenden Sie: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Gleiche Herkunft .[#toc-same-origin] +------------------------------------ + +Um die Sicherheit zu erhöhen, können Sie verlangen, dass die Anfrage von der gleichen Domäne aus gestellt wird. Dies verhindert eine [Anfälligkeit für CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Für `handle()` Methoden ist der Zugriff aus derselben Domäne automatisch erforderlich. Wenn Sie also den Zugriff aus einer beliebigen Domäne zulassen wollen, geben Sie dies an: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Zugang über Forward .[#toc-access-via-forward] +---------------------------------------------- + +Manchmal ist es sinnvoll, den Zugriff auf einen Präsentator so einzuschränken, dass er nur indirekt verfügbar ist, z. B. über die Methoden `forward()` oder `switch()` eines anderen Präsentators. Auf diese Weise werden Fehlerpräsenter geschützt, um zu verhindern, dass sie von einer URL ausgelöst werden: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +In der Praxis ist es oft notwendig, bestimmte Ansichten zu markieren, auf die nur aufgrund der Logik im Präsentator zugegriffen werden kann. Auch hier gilt, dass sie nicht direkt geöffnet werden können: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Spezifische Aktionen .[#toc-specific-actions] +--------------------------------------------- + +Sie können auch einschränken, dass bestimmter Code, wie das Erstellen einer Komponente, nur für bestimmte Aktionen im Präsentator zugänglich ist: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Für eine einzelne Aktion muss kein Array geschrieben werden: `#[Requires(actions: 'default')]` + + +Benutzerdefinierte Attribute .[#toc-custom-attributes] +------------------------------------------------------ + +Wenn Sie das Attribut `#[Requires]` Attribut wiederholt mit denselben Einstellungen verwenden möchten, können Sie ein eigenes Attribut erstellen, das die `#[Requires]` erbt, und es nach Ihren Bedürfnissen einstellen. + +Zum Beispiel, `#[SingleAction]` erlaubt den Zugriff nur über die Aktion `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Oder `#[RestMethods]` ermöglicht den Zugriff über alle für die REST-API verwendeten HTTP-Methoden: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Schlussfolgerung .[#toc-conclusion] +----------------------------------- + +Das `#[Requires]` Attribut gibt Ihnen große Flexibilität und Kontrolle darüber, wie auf Ihre Webseiten zugegriffen wird. Mit einfachen, aber leistungsfähigen Regeln können Sie die Sicherheit und das ordnungsgemäße Funktionieren Ihrer Anwendung verbessern. Wie Sie sehen, kann die Verwendung von Attributen in Nette Ihre Arbeit nicht nur vereinfachen, sondern auch sichern. + +{{sitename: Best Practices}} diff --git a/best-practices/de/composer.texy b/best-practices/de/composer.texy index 8910b9e603..10aed8c202 100644 --- a/best-practices/de/composer.texy +++ b/best-practices/de/composer.texy @@ -189,7 +189,7 @@ Es ist jedoch auch möglich, Composer zu verwenden, um andere Klassen außerhalb Anschließend müssen Sie bei jeder Änderung den Befehl `composer dumpautoload` ausführen und die Autoloader-Tabellen neu generieren lassen. Dies ist äußerst lästig, und es ist weitaus besser, diese Aufgabe [RobotLoader |robot-loader:] anzuvertrauen, der dieselbe Tätigkeit automatisch im Hintergrund und viel schneller durchführt. -Die zweite Möglichkeit ist, [PSR-4 |https://www.php-fig.org/psr/psr-4/] zu folgen. Einfach gesagt handelt es sich um ein System, bei dem die Namensräume und Klassennamen der Verzeichnisstruktur und den Dateinamen entsprechen, d. h. `App\Router\RouterFactory` befindet sich in der Datei `/path/to/App/Router/RouterFactory.php`. Beispiel für eine Konfiguration: +Die zweite Möglichkeit ist, [PSR-4 |https://www.php-fig.org/psr/psr-4/] zu folgen. Einfach gesagt handelt es sich um ein System, bei dem die Namensräume und Klassennamen der Verzeichnisstruktur und den Dateinamen entsprechen, d. h. `App\Core\RouterFactory` befindet sich in der Datei `/path/to/App/Core/RouterFactory.php`. Beispiel für eine Konfiguration: ```js { diff --git a/best-practices/de/dynamic-snippets.texy b/best-practices/de/dynamic-snippets.texy index c75affd1e4..e4ac99a50a 100644 --- a/best-practices/de/dynamic-snippets.texy +++ b/best-practices/de/dynamic-snippets.texy @@ -35,7 +35,7 @@ Vorlage: Ajaxisierung .[#toc-ajaxization] ================================ -Bringen wir nun AJAX in diese einfache Anwendung. Das Ändern der Bewertung eines Artikels ist nicht wichtig genug, um eine HTTP-Anfrage mit Redirect zu erfordern, also sollte es idealerweise mit AJAX im Hintergrund geschehen. Wir werden das [Handler-Skript aus add-ons |https://componette.org/vojtech-dobes/nette.ajax.js/] verwenden, mit der üblichen Konvention, dass AJAX-Links die CSS-Klasse `ajax` haben. +Bringen wir nun AJAX in diese einfache Anwendung. Das Ändern der Bewertung eines Artikels ist nicht wichtig genug, um eine HTTP-Anfrage mit Redirect zu erfordern, also sollte es idealerweise mit AJAX im Hintergrund geschehen. Wir werden das [Handler-Skript aus add-ons |application:ajax#toc-naja] verwenden, mit der üblichen Konvention, dass AJAX-Links die CSS-Klasse `ajax` haben. Aber wie macht man das konkret? Nette bietet 2 Wege an: den dynamischen Snippet-Weg und den Komponenten-Weg. Beide haben ihre Vor- und Nachteile, daher werden wir sie nacheinander vorstellen. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Natürlich werden wir die Ansichtsvorlage ändern und dem Präsentator eine Fabr ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/de/lets-create-contact-form.texy b/best-practices/de/lets-create-contact-form.texy index 67b26710b2..886b452767 100644 --- a/best-practices/de/lets-create-contact-form.texy +++ b/best-practices/de/lets-create-contact-form.texy @@ -39,7 +39,7 @@ Wie Sie sehen können, haben wir zwei Methoden erstellt. Die erste Methode `crea Was aber, wenn der Benutzer einige Felder nicht ausfüllt? In diesem Fall sollten wir ihn darauf hinweisen, dass es sich um ein Pflichtfeld handelt. Wir haben dies mit der Methode `setRequired()` getan. Schließlich haben wir auch ein [Ereignis |nette:glossary#events] `onSuccess` hinzugefügt, das ausgelöst wird, wenn das Formular erfolgreich abgeschickt wurde. In unserem Fall ruft es die Methode `contactFormSucceeded` auf, die sich um die Verarbeitung des übermittelten Formulars kümmert. Das fügen wir dem Code gleich hinzu. -Die Komponente `contantForm` soll in der Vorlage `templates/Home/default.latte` gerendert werden: +Die Komponente `contantForm` soll in der Vorlage `Home/default.latte` gerendert werden: ```latte {block content} diff --git a/best-practices/de/pagination.texy b/best-practices/de/pagination.texy index ef5a0920ad..245680368c 100644 --- a/best-practices/de/pagination.texy +++ b/best-practices/de/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository In den Presenter injizieren wir dann die Modellklasse, und in der Rendering-Methode fragen wir nach den veröffentlichten Artikeln, die wir an die Vorlage übergeben: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -In der Vorlage kümmern wir uns um das Rendern einer Artikelliste: +Die Vorlage `default.latte` kümmert sich dann um die Auflistung der Artikel: ```latte {block content} @@ -114,7 +113,7 @@ Der nächste Schritt besteht darin, den Präsentator zu bearbeiten. Wir werden d Wir erweitern die Render-Methode auch, um die Paginator-Instanz zu erhalten, sie einzurichten und die richtigen Artikel für die Anzeige in der Vorlage auszuwählen. Der HomePresenter wird wie folgt aussehen: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Wir müssen keinen Paginator im Presenter erstellen, sondern verwenden die Methode des `Selection` -Objekts, das vom Repository zurückgegeben wird: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/de/post-links.texy b/best-practices/de/post-links.texy new file mode 100644 index 0000000000..13c38839bb --- /dev/null +++ b/best-practices/de/post-links.texy @@ -0,0 +1,59 @@ +Wie man POST-Links richtig verwendet +************************************ + +In Webanwendungen, insbesondere in Verwaltungsschnittstellen, sollte es eine Grundregel sein, dass Aktionen, die den Zustand des Servers verändern, nicht über die HTTP-GET-Methode durchgeführt werden sollten. Wie der Name der Methode schon sagt, sollte GET nur zum Abrufen von Daten verwendet werden, nicht zum Ändern. +Für Aktionen wie das Löschen von Datensätzen ist es angemessener, die POST-Methode zu verwenden. Ideal wäre die Verwendung der DELETE-Methode, die jedoch ohne JavaScript nicht aufgerufen werden kann, weshalb in der Regel POST verwendet wird. + +Wie macht man das in der Praxis? Verwenden Sie diesen einfachen Trick. Erstellen Sie am Anfang Ihrer Vorlage ein Hilfsformular mit dem Bezeichner `postForm`, das Sie dann für die Schaltflächen zum Löschen verwenden werden: + +```latte .{file:@layout.latte} +
+``` + +Mit diesem Formular können Sie ein `
diff --git a/best-practices/el/attribute-requires.texy b/best-practices/el/attribute-requires.texy new file mode 100644 index 0000000000..95d1417ac9 --- /dev/null +++ b/best-practices/el/attribute-requires.texy @@ -0,0 +1,179 @@ +Πώς να χρησιμοποιήσετε το `#[Requires]` Attribute +************************************************* + +.[perex] +Κατά τη συγγραφή μιας εφαρμογής ιστού, συναντάτε συχνά την ανάγκη να περιορίσετε την πρόσβαση σε ορισμένα τμήματα της εφαρμογής σας. Ίσως θέλετε κάποια αιτήματα να μπορούν να στέλνουν δεδομένα μόνο μέσω μιας φόρμας (χρησιμοποιώντας έτσι τη μέθοδο POST) ή να είναι προσβάσιμα μόνο σε κλήσεις AJAX. Στο Nette Framework 3.2, έχει εισαχθεί ένα νέο εργαλείο που σας επιτρέπει να ορίσετε τέτοιους περιορισμούς με κομψότητα και σαφήνεια: το `#[Requires]` χαρακτηριστικό. + +Το χαρακτηριστικό είναι ένας ειδικός δείκτης στην PHP, τον οποίο προσθέτετε πριν από τον ορισμό μιας κλάσης ή μεθόδου. Εφόσον πρόκειται ουσιαστικά για μια κλάση, πρέπει να συμπεριλάβετε τη ρήτρα use για να λειτουργήσουν τα παρακάτω παραδείγματα: + +```php +use Nette\Application\Attributes\Requires; +``` + +Μπορείτε να χρησιμοποιήσετε το `#[Requires]` με την ίδια την κλάση presenter και σε αυτές τις μεθόδους: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Οι δύο τελευταίες μέθοδοι αφορούν επίσης συστατικά, οπότε μπορείτε να χρησιμοποιήσετε το χαρακτηριστικό και με αυτά. + +Εάν δεν πληρούνται οι συνθήκες που καθορίζονται από το χαρακτηριστικό, ενεργοποιείται ένα σφάλμα HTTP 4xx. + + +Μέθοδοι HTTP .[#toc-http-methods] +--------------------------------- + +Μπορείτε να καθορίσετε ποιες μέθοδοι HTTP (όπως GET, POST κ.λπ.) επιτρέπονται για πρόσβαση. Για παράδειγμα, αν θέλετε να επιτρέψετε την πρόσβαση μόνο με την υποβολή μιας φόρμας, ορίστε: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Γιατί πρέπει να χρησιμοποιείτε POST αντί για GET για ενέργειες αλλαγής κατάστασης και πώς να το κάνετε; [Διαβάστε τον οδηγό |post-links]. + +Μπορείτε να καθορίσετε μια μέθοδο ή έναν πίνακα μεθόδων. Μια ειδική περίπτωση είναι η τιμή `'*'` για την ενεργοποίηση όλων των μεθόδων, την οποία οι παρουσιαστές δεν επιτρέπουν από προεπιλογή για [λόγους ασφαλείας |application:presenters#http-method-check]. + + +Κλήσεις AJAX .[#toc-ajax-calls] +------------------------------- + +Αν θέλετε ένας παρουσιαστής ή μια μέθοδος να είναι προσβάσιμη μόνο για αιτήσεις AJAX, χρησιμοποιήστε: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Ίδια προέλευση .[#toc-same-origin] +---------------------------------- + +Για να ενισχύσετε την ασφάλεια, μπορείτε να απαιτήσετε η αίτηση να γίνεται από τον ίδιο τομέα. Αυτό αποτρέπει την [ευπάθεια σε CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Για το `handle()` μεθόδους, απαιτείται αυτόματα πρόσβαση από τον ίδιο τομέα. Έτσι, αν θέλετε να επιτρέψετε την πρόσβαση από οποιονδήποτε τομέα, καθορίστε: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Πρόσβαση μέσω Forward .[#toc-access-via-forward] +------------------------------------------------ + +Μερικές φορές είναι χρήσιμο να περιορίσετε την πρόσβαση σε έναν παρουσιαστή, ώστε να είναι διαθέσιμος μόνο έμμεσα, για παράδειγμα, χρησιμοποιώντας τις μεθόδους `forward()` ή `switch()` από έναν άλλο παρουσιαστή. Με αυτόν τον τρόπο προστατεύονται οι παρουσιαστές σφαλμάτων, ώστε να μην μπορούν να ενεργοποιηθούν από μια διεύθυνση URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Στην πράξη, είναι συχνά απαραίτητο να επισημαίνονται ορισμένες προβολές στις οποίες μπορεί να γίνει πρόσβαση μόνο με βάση τη λογική του παρουσιαστή. Και πάλι, έτσι ώστε να μην μπορούν να ανοιχτούν απευθείας: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Ειδικές δράσεις .[#toc-specific-actions] +---------------------------------------- + +Μπορείτε επίσης να περιορίσετε ότι ορισμένος κώδικας, όπως η δημιουργία ενός στοιχείου, θα είναι προσβάσιμος μόνο για συγκεκριμένες ενέργειες στον παρουσιαστή: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Για μια μεμονωμένη ενέργεια, δεν χρειάζεται να γράψετε έναν πίνακα: `#[Requires(actions: 'default')]` + + +Προσαρμοσμένα χαρακτηριστικά .[#toc-custom-attributes] +------------------------------------------------------ + +Αν θέλετε να χρησιμοποιήσετε το `#[Requires]` χαρακτηριστικό επανειλημμένα με τις ίδιες ρυθμίσεις, μπορείτε να δημιουργήσετε το δικό σας χαρακτηριστικό που θα κληρονομεί `#[Requires]` και να το ορίσετε σύμφωνα με τις ανάγκες σας. + +Για παράδειγμα, `#[SingleAction]` επιτρέπει την πρόσβαση μόνο μέσω της ενέργειας `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Ή `#[RestMethods]` θα επιτρέψει την πρόσβαση μέσω όλων των μεθόδων HTTP που χρησιμοποιούνται για το REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Συμπέρασμα .[#toc-conclusion] +----------------------------- + +Το `#[Requires]` σας δίνει μεγάλη ευελιξία και έλεγχο στον τρόπο πρόσβασης στις ιστοσελίδες σας. Χρησιμοποιώντας απλούς, αλλά ισχυρούς κανόνες, μπορείτε να ενισχύσετε την ασφάλεια και την ορθή λειτουργία της εφαρμογής σας. Όπως μπορείτε να δείτε, η χρήση χαρακτηριστικών στη Nette μπορεί όχι μόνο να απλοποιήσει την εργασία σας αλλά και να την ασφαλίσει. + +{{sitename: Best Practices}} diff --git a/best-practices/el/composer.texy b/best-practices/el/composer.texy index 410b88c45a..75f9e3f0bc 100644 --- a/best-practices/el/composer.texy +++ b/best-practices/el/composer.texy @@ -189,7 +189,7 @@ Packagist.org - Παγκόσμιο αποθετήριο .[#toc-packagist-org-glo Στη συνέχεια, είναι απαραίτητο να εκτελείτε την εντολή `composer dumpautoload` με κάθε αλλαγή και να αφήνετε τους πίνακες αυτόματης φόρτωσης να αναγεννώνται. Αυτό είναι εξαιρετικά άβολο και είναι πολύ καλύτερο να αναθέσετε αυτή την εργασία στο [RobotLoader |robot-loader:], το οποίο εκτελεί την ίδια δραστηριότητα αυτόματα στο παρασκήνιο και πολύ πιο γρήγορα. -Η δεύτερη επιλογή είναι να ακολουθήσετε το [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Με απλά λόγια, πρόκειται για ένα σύστημα όπου τα namespaces και τα ονόματα των κλάσεων αντιστοιχούν στη δομή των καταλόγων και των ονομάτων των αρχείων, δηλαδή το `App\Router\RouterFactory` βρίσκεται στο αρχείο `/path/to/App/Router/RouterFactory.php`. Παράδειγμα διαμόρφωσης: +Η δεύτερη επιλογή είναι να ακολουθήσετε το [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Με απλά λόγια, πρόκειται για ένα σύστημα όπου τα namespaces και τα ονόματα των κλάσεων αντιστοιχούν στη δομή των καταλόγων και των ονομάτων των αρχείων, δηλαδή το `App\Core\RouterFactory` βρίσκεται στο αρχείο `/path/to/App/Core/RouterFactory.php`. Παράδειγμα διαμόρφωσης: ```js { diff --git a/best-practices/el/dynamic-snippets.texy b/best-practices/el/dynamic-snippets.texy index 59031461f0..c75bfa12dd 100644 --- a/best-practices/el/dynamic-snippets.texy +++ b/best-practices/el/dynamic-snippets.texy @@ -35,7 +35,7 @@ Template: Ajaxization .[#toc-ajaxization] =============================== -Ας φέρουμε τώρα το AJAX σε αυτή την απλή εφαρμογή. Η αλλαγή της βαθμολογίας ενός άρθρου δεν είναι αρκετά σημαντική ώστε να απαιτεί ένα αίτημα HTTP με ανακατεύθυνση, οπότε ιδανικά θα πρέπει να γίνεται με AJAX στο παρασκήνιο. Θα χρησιμοποιήσουμε το [σενάριο χειρισμού από τα πρόσθετα |https://componette.org/vojtech-dobes/nette.ajax.js/] με τη συνήθη σύμβαση ότι οι σύνδεσμοι AJAX έχουν την κλάση CSS `ajax`. +Ας φέρουμε τώρα το AJAX σε αυτή την απλή εφαρμογή. Η αλλαγή της βαθμολογίας ενός άρθρου δεν είναι αρκετά σημαντική ώστε να απαιτεί ένα αίτημα HTTP με ανακατεύθυνση, οπότε ιδανικά θα πρέπει να γίνεται με AJAX στο παρασκήνιο. Θα χρησιμοποιήσουμε το [σενάριο χειρισμού από τα πρόσθετα |application:ajax#toc-naja] με τη συνήθη σύμβαση ότι οι σύνδεσμοι AJAX έχουν την κλάση CSS `ajax`. Ωστόσο, πώς να το κάνουμε συγκεκριμένα; Η Nette προσφέρει 2 τρόπους: τον τρόπο με τα δυναμικά αποσπάσματα και τον τρόπο με τα συστατικά. Και οι δύο έχουν τα πλεονεκτήματα και τα μειονεκτήματά τους, γι' αυτό θα τους παρουσιάσουμε έναν προς έναν. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ class LikeControl extends Nette\Application\UI\Control ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/el/lets-create-contact-form.texy b/best-practices/el/lets-create-contact-form.texy index aeded4979a..8cf6ab9a9f 100644 --- a/best-practices/el/lets-create-contact-form.texy +++ b/best-practices/el/lets-create-contact-form.texy @@ -39,7 +39,7 @@ class HomePresenter extends Presenter Τι γίνεται όμως αν ο χρήστης δεν συμπληρώσει κάποια πεδία; Σε αυτή την περίπτωση, θα πρέπει να τον ενημερώσουμε ότι πρόκειται για υποχρεωτικό πεδίο. Αυτό το κάναμε με τη μέθοδο `setRequired()`. Τέλος, προσθέσαμε και ένα [συμβάν |nette:glossary#events] `onSuccess`, το οποίο ενεργοποιείται αν η φόρμα υποβληθεί με επιτυχία. Στην περίπτωσή μας, καλεί τη μέθοδο `contactFormSucceeded`, η οποία αναλαμβάνει την επεξεργασία της υποβληθείσας φόρμας. Θα το προσθέσουμε αυτό στον κώδικα σε λίγο. -Αφήστε το στοιχείο `contantForm` να αποδοθεί στο πρότυπο `templates/Home/default.latte`: +Αφήστε το στοιχείο `contantForm` να αποδοθεί στο πρότυπο `Home/default.latte`: ```latte {block content} diff --git a/best-practices/el/pagination.texy b/best-practices/el/pagination.texy index 06fca7da8a..599f3ff25a 100644 --- a/best-practices/el/pagination.texy +++ b/best-practices/el/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository Στον Presenter στη συνέχεια εισάγουμε την κλάση model και στη μέθοδο render θα ζητήσουμε τα δημοσιευμένα άρθρα που θα περάσουμε στο template: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Στο πρότυπο, θα φροντίσουμε για την απόδοση μιας λίστας άρθρων: +Το πρότυπο `default.latte` θα αναλάβει στη συνέχεια την καταχώριση των άρθρων: ```latte {block content} @@ -114,7 +113,7 @@ class ArticleRepository Επεκτείνουμε επίσης τη μέθοδο render για να λάβουμε την περίπτωση Paginator, να τη ρυθμίσουμε και να επιλέξουμε τα σωστά άρθρα που θα εμφανίζονται στο πρότυπο. Το HomePresenter θα μοιάζει με αυτό: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Δεν χρειάζεται να δημιουργήσουμε Paginator στο Presenter, αντίθετα θα χρησιμοποιήσουμε τη μέθοδο του αντικειμένου `Selection` που επιστρέφεται από το αποθετήριο: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/el/post-links.texy b/best-practices/el/post-links.texy new file mode 100644 index 0000000000..634091cf38 --- /dev/null +++ b/best-practices/el/post-links.texy @@ -0,0 +1,59 @@ +Πώς να χρησιμοποιείτε σωστά τους συνδέσμους POST +************************************************ + +Στις εφαρμογές ιστού, ειδικά στις διαχειριστικές διεπαφές, θα πρέπει να αποτελεί βασικό κανόνα ότι οι ενέργειες που αλλάζουν την κατάσταση του διακομιστή δεν θα πρέπει να εκτελούνται μέσω της μεθόδου HTTP GET. Όπως υποδηλώνει το όνομα της μεθόδου, η GET θα πρέπει να χρησιμοποιείται μόνο για την ανάκτηση δεδομένων και όχι για την αλλαγή τους. +Για ενέργειες όπως η διαγραφή εγγραφών, είναι καταλληλότερο να χρησιμοποιείται η μέθοδος POST. Αν και το ιδανικό θα ήταν να χρησιμοποιείται η μέθοδος DELETE, αυτή δεν μπορεί να κληθεί χωρίς JavaScript, γι' αυτό και χρησιμοποιείται ιστορικά η μέθοδος POST. + +Πώς να το κάνετε στην πράξη; Χρησιμοποιήστε αυτό το απλό τέχνασμα. Στην αρχή του προτύπου σας, δημιουργήστε μια βοηθητική φόρμα με το αναγνωριστικό `postForm`, την οποία στη συνέχεια θα χρησιμοποιήσετε για τα κουμπιά διαγραφής: + +```latte .{file:@layout.latte} +
+``` + +Με αυτή τη φόρμα, μπορείτε να χρησιμοποιήσετε ένα `
diff --git a/best-practices/en/attribute-requires.texy b/best-practices/en/attribute-requires.texy new file mode 100644 index 0000000000..514355ad79 --- /dev/null +++ b/best-practices/en/attribute-requires.texy @@ -0,0 +1,179 @@ +How to Use the `#[Requires]` Attribute +************************************** + +.[perex] +When writing a web application, you often encounter the need to restrict access to certain parts of your application. Perhaps you want some requests to only be able to send data via a form (thus using the POST method) or to be accessible only to AJAX calls. In Nette Framework 3.2, a new tool has been introduced that allows you to set such restrictions elegantly and clearly: the `#[Requires]` attribute. + +The attribute is a special marker in PHP, which you add before the definition of a class or method. Since it is essentially a class, you need to include the use clause to make the following examples work: + +```php +use Nette\Application\Attributes\Requires; +``` + +You can use the `#[Requires]` attribute with the presenter class itself and on these methods: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +The last two methods also concern components, so you can use the attribute with them as well. + +If the conditions specified by the attribute are not met, an HTTP 4xx error is triggered. + + +HTTP Methods +------------ + +You can specify which HTTP methods (such as GET, POST, etc.) are allowed for access. For example, if you want to allow access only by submitting a form, set: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Why should you use POST instead of GET for state changing actions and how to do it? [Read the guide |post-links]. + +You can specify a method or an array of methods. A special case is the value `'*'` to enable all methods, which presenters do not allow by default for [security reasons |application:presenters#http-method-check]. + + +AJAX Calls +---------- + +If you want a presenter or method to be accessible only for AJAX requests, use: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Same Origin +----------- + +To enhance security, you can require that the request be made from the same domain. This prevents [vulnerability to CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +For `handle()` methods, access from the same domain is automatically required. So, if you want to allow access from any domain, specify: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Access via Forward +------------------ + +Sometimes it is useful to restrict access to a presenter so that it is only available indirectly, for example, using the `forward()` or `switch()` methods from another presenter. This is how error-presenters are protected to prevent them from being triggered from a URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +In practice, it is often necessary to mark certain views that can only be accessed based on logic in the presenter. Again, so that they cannot be opened directly: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Specific Actions +---------------- + +You can also restrict that certain code, like creating a component, will be accessible only for specific actions in the presenter: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +For a single action, there's no need to write an array: `#[Requires(actions: 'default')]` + + +Custom Attributes +----------------- + +If you want to use the `#[Requires]` attribute repeatedly with the same settings, you can create your own attribute that will inherit `#[Requires]` and set it according to your needs. + +For example, `#[SingleAction]` allows access only through the `default` action: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Or `#[RestMethods]` will allow access via all HTTP methods used for the REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Conclusion +---------- + +The `#[Requires]` attribute gives you great flexibility and control over how your web pages are accessed. Using simple, yet powerful rules, you can enhance the security and proper functioning of your application. As you can see, using attributes in Nette can not only simplify your work but also secure it. + +{{sitename: Best Practices}} diff --git a/best-practices/en/composer.texy b/best-practices/en/composer.texy index bc65bb1c55..d8753b98bd 100644 --- a/best-practices/en/composer.texy +++ b/best-practices/en/composer.texy @@ -189,7 +189,7 @@ However, it is also possible to use Composer to load other classes outside the f Subsequently, it is necessary to run the command `composer dumpautoload` with each change and let the autoloading tables regenerate. This is extremely inconvenient, and it is far better to entrust this task to [RobotLoader|robot-loader:], which performs the same activity automatically in the background and much faster. -The second option is to follow [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Simply saying, it is a system where the namespaces and class names correspond to the directory structure and file names, ie `App\Router\RouterFactory` is located in the file `/path/to/App/Router/RouterFactory.php`. Configuration example: +The second option is to follow [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Simply saying, it is a system where the namespaces and class names correspond to the directory structure and file names, ie `App\Core\RouterFactory` is located in the file `/path/to/App/Core/RouterFactory.php`. Configuration example: ```js { diff --git a/best-practices/en/dynamic-snippets.texy b/best-practices/en/dynamic-snippets.texy index a0e8e09cae..00a63361f5 100644 --- a/best-practices/en/dynamic-snippets.texy +++ b/best-practices/en/dynamic-snippets.texy @@ -35,7 +35,7 @@ Template: Ajaxization =========== -Let's now bring AJAX to this simple application. Changing the rating of an article is not important enough to require a HTTP request with redirect, so ideally it should be done with AJAX in the background. We'll use the [handler script from add-ons |https://componette.org/vojtech-dobes/nette.ajax.js/] with the usual convention that AJAX links have the CSS class `ajax`. +Let's now bring AJAX to this simple application. Changing the rating of an article is not important enough to require a HTTP request with redirect, so ideally it should be done with AJAX in the background. We'll use the [handler script from add-ons |application:ajax#toc-naja] with the usual convention that AJAX links have the CSS class `ajax`. However, how to do it specifically? Nette offers 2 ways: the dynamic snippet way and the component way. Both of them have their pros and cons, so we will show them one by one. @@ -72,7 +72,7 @@ public function handleLike(int $articleId): void $this->ratingService->saveLike($articleId, $this->user->id); if ($this->isAjax()) { $this->redrawControl('articlesContainer'); - // $this->redrawControl('article-' . $articleId); -- není potřeba + // $this->redrawControl('article-' . $articleId); -- not needed } else { $this->redirect('this'); } @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Of course we will change the view template and we will have to add a factory to ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/en/lets-create-contact-form.texy b/best-practices/en/lets-create-contact-form.texy index ad2472ed15..f00cad1b75 100644 --- a/best-practices/en/lets-create-contact-form.texy +++ b/best-practices/en/lets-create-contact-form.texy @@ -39,7 +39,7 @@ As you can see, we have created two methods. The first method `createComponentCo But what if the user doesn't fill in some fields? In that case, we should let him know that it is a required field. We did this with the `setRequired()` method. Finally, we also added an [event |nette:glossary#events] `onSuccess`, which is triggered if the form is submitted successfully. In our case, it calls the `contactFormSucceeded` method , which takes care of processing the submitted form. We'll add that to the code in a moment. -Let the `contantForm` component be rendered in the `templates/Home/default.latte` template: +Let the `contantForm` component be rendered in the `Home/default.latte` template: ```latte {block content} diff --git a/best-practices/en/pagination.texy b/best-practices/en/pagination.texy index 113ce80193..8fa2b98ace 100644 --- a/best-practices/en/pagination.texy +++ b/best-practices/en/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository In the Presenter we then inject the model class and in the render method we will ask for the published articles that we pass to the template: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -In the template, we will take care of rendering an articles list: +The `default.latte` template will then take care of listing the articles: ```latte {block content} @@ -114,7 +113,7 @@ The next step is to edit the presenter. We will forward the number of the curren We also expand the render method to get the Paginator instance, setting it up, and selecting the correct articles to display in the template. HomePresenter will look like this: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository We do not have to create Paginator in the Presenter, instead we will use the method of `Selection` object returned by the repository: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/en/post-links.texy b/best-practices/en/post-links.texy new file mode 100644 index 0000000000..45af7ea028 --- /dev/null +++ b/best-practices/en/post-links.texy @@ -0,0 +1,59 @@ +How to Properly Use POST Links +****************************** + +In web applications, especially in administrative interfaces, it should be a basic rule that actions changing the state of the server should not be performed via the HTTP GET method. As the method name suggests, GET should be used only to retrieve data, not to change it. +For actions such as deleting records, it is more appropriate to use the POST method. Although the ideal would be to use the DELETE method, this cannot be invoked without JavaScript, hence POST is historically used. + +How to do it in practice? Use this simple trick. At the beginning of your template, create a helper form with the identifier `postForm`, which you will then use for the delete buttons: + +```latte .{file:@layout.latte} +
+``` + +With this form, you can use a `
diff --git a/best-practices/es/attribute-requires.texy b/best-practices/es/attribute-requires.texy new file mode 100644 index 0000000000..0c51ef716b --- /dev/null +++ b/best-practices/es/attribute-requires.texy @@ -0,0 +1,179 @@ +Cómo utilizar el atributo `#[Requires]` Atributo +************************************************ + +.[perex] +Cuando escribes una aplicación web, a menudo te encuentras con la necesidad de restringir el acceso a ciertas partes de tu aplicación. Tal vez desee que algunas peticiones sólo puedan enviar datos a través de un formulario (utilizando así el método POST) o que sólo sean accesibles para llamadas AJAX. En Nette Framework 3.2, se ha introducido una nueva herramienta que permite establecer estas restricciones de forma elegante y clara: el atributo `#[Requires]` atributo. + +El atributo es un marcador especial en PHP, que se añade antes de la definición de una clase o método. Dado que se trata esencialmente de una clase, es necesario incluir la cláusula use para que los siguientes ejemplos funcionen: + +```php +use Nette\Application\Attributes\Requires; +``` + +Puede utilizar el atributo `#[Requires]` con la propia clase presentadora y en estos métodos: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Los dos últimos métodos también afectan a los componentes, por lo que también puede utilizar el atributo con ellos. + +Si no se cumplen las condiciones especificadas por el atributo, se produce un error HTTP 4xx. + + +Métodos HTTP .[#toc-http-methods] +--------------------------------- + +Puede especificar qué métodos HTTP (como GET, POST, etc.) están permitidos para el acceso. Por ejemplo, si desea permitir el acceso sólo mediante el envío de un formulario, establezca: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +¿Por qué debería utilizar POST en lugar de GET para las acciones de cambio de estado y cómo hacerlo? [Lea la guía |post-links]. + +Puede especificar un método o un conjunto de métodos. Un caso especial es el valor `'*'` para habilitar todos los métodos, que los presentadores no permiten por defecto por [motivos de seguridad |application:presenters#http-method-check]. + + +Llamadas AJAX .[#toc-ajax-calls] +-------------------------------- + +Si desea que un presentador o método sea accesible sólo para peticiones AJAX, utilice: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Mismo origen .[#toc-same-origin] +-------------------------------- + +Para mejorar la seguridad, puede exigir que la solicitud se realice desde el mismo dominio. Esto evita la [vulnerabilidad a CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Para los métodos `handle()` se requiere automáticamente el acceso desde el mismo dominio. Por lo tanto, si desea permitir el acceso desde cualquier dominio, especifique: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Acceso a través de Forward .[#toc-access-via-forward] +----------------------------------------------------- + +A veces es útil restringir el acceso a un presentador para que sólo esté disponible indirectamente, por ejemplo, utilizando los métodos `forward()` o `switch()` desde otro presentador. Así es como se protegen los presentadores de errores para evitar que se activen desde una URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +En la práctica, a menudo es necesario marcar determinadas vistas a las que sólo se puede acceder basándose en la lógica del presentador. De nuevo, para que no puedan abrirse directamente: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Acciones específicas .[#toc-specific-actions] +--------------------------------------------- + +También puede restringir que cierto código, como la creación de un componente, sea accesible sólo para acciones específicas en el presentador: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Para una sola acción, no es necesario escribir una matriz: `#[Requires(actions: 'default')]` + + +Atributos personalizados .[#toc-custom-attributes] +-------------------------------------------------- + +Si desea utilizar el atributo `#[Requires]` atributo repetidamente con la misma configuración, puede crear su propio atributo que heredará `#[Requires]` y configurarlo según tus necesidades. + +Por ejemplo `#[SingleAction]` sólo permite el acceso a través de la acción `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +O `#[RestMethods]` permitirá el acceso a través de todos los métodos HTTP utilizados para la API REST: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Conclusión .[#toc-conclusion] +----------------------------- + +El atributo `#[Requires]` te ofrece una gran flexibilidad y control sobre cómo se accede a tus páginas web. Usando reglas simples, pero potentes, puedes mejorar la seguridad y el buen funcionamiento de tu aplicación. Como puede ver, el uso de atributos en Nette no sólo puede simplificar su trabajo, sino también asegurarlo. + +{{sitename: Best Practices}} diff --git a/best-practices/es/composer.texy b/best-practices/es/composer.texy index e9fd4a1bf3..86cf99808a 100644 --- a/best-practices/es/composer.texy +++ b/best-practices/es/composer.texy @@ -189,7 +189,7 @@ Sin embargo, también es posible utilizar Composer para cargar otras clases fuer Posteriormente, es necesario ejecutar el comando `composer dumpautoload` con cada cambio y dejar que se regeneren las tablas de autocarga. Esto es extremadamente incómodo, y es mucho mejor confiar esta tarea a [RobotLoader |robot-loader:], que realiza la misma actividad automáticamente en segundo plano y mucho más rápido. -La segunda opción es seguir [PSR-4 |https://www.php-fig.org/psr/psr-4/]. En pocas palabras, se trata de un sistema en el que los espacios de nombres y los nombres de las clases se corresponden con la estructura de directorios y los nombres de los archivos, es decir, `App\Router\RouterFactory` se encuentra en el archivo `/path/to/App/Router/RouterFactory.php`. Ejemplo de configuración: +La segunda opción es seguir [PSR-4 |https://www.php-fig.org/psr/psr-4/]. En pocas palabras, se trata de un sistema en el que los espacios de nombres y los nombres de las clases se corresponden con la estructura de directorios y los nombres de los archivos, es decir, `App\Core\RouterFactory` se encuentra en el archivo `/path/to/App/Core/RouterFactory.php`. Ejemplo de configuración: ```js { diff --git a/best-practices/es/dynamic-snippets.texy b/best-practices/es/dynamic-snippets.texy index 2133b4a573..d59e1eaefe 100644 --- a/best-practices/es/dynamic-snippets.texy +++ b/best-practices/es/dynamic-snippets.texy @@ -35,7 +35,7 @@ Plantilla: Ajaxización .[#toc-ajaxization] =============================== -Llevemos ahora AJAX a esta sencilla aplicación. Cambiar la calificación de un artículo no es lo suficientemente importante como para requerir una petición HTTP con redirección, así que lo ideal sería hacerlo con AJAX en segundo plano. Usaremos el [script handler de add-ons |https://componette.org/vojtech-dobes/nette.ajax.js/] con la convención habitual de que los enlaces AJAX tienen la clase CSS `ajax`. +Llevemos ahora AJAX a esta sencilla aplicación. Cambiar la calificación de un artículo no es lo suficientemente importante como para requerir una petición HTTP con redirección, así que lo ideal sería hacerlo con AJAX en segundo plano. Usaremos el [script handler de add-ons |application:ajax#toc-naja] con la convención habitual de que los enlaces AJAX tienen la clase CSS `ajax`. Sin embargo, ¿cómo hacerlo específicamente? Nette ofrece 2 maneras: la del fragmento dinámico y la del componente. Ambas tienen sus pros y sus contras, así que las mostraremos una a una. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Por supuesto cambiaremos la plantilla de la vista y tendremos que añadir una f ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/es/lets-create-contact-form.texy b/best-practices/es/lets-create-contact-form.texy index 2c7e155167..142740037a 100644 --- a/best-practices/es/lets-create-contact-form.texy +++ b/best-practices/es/lets-create-contact-form.texy @@ -39,7 +39,7 @@ Como puedes ver, hemos creado dos métodos. El primer método `createComponentCo Pero, ¿qué pasa si el usuario no rellena algunos campos? En ese caso, debemos hacerle saber que se trata de un campo obligatorio. Hicimos esto con el método `setRequired()`. Por último, también añadimos un [evento |nette:glossary#events] `onSuccess`, que se activa si el formulario se envía correctamente. En nuestro caso, llama al método `contactFormSucceeded`, que se encarga de procesar el formulario enviado. Lo añadiremos al código en un momento. -Dejemos que el componente `contantForm` sea renderizado en la plantilla `templates/Home/default.latte`: +Dejemos que el componente `contantForm` sea renderizado en la plantilla `Home/default.latte`: ```latte {block content} diff --git a/best-practices/es/pagination.texy b/best-practices/es/pagination.texy index 44d6fddd5f..287b5d17a3 100644 --- a/best-practices/es/pagination.texy +++ b/best-practices/es/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository En el Presentador inyectamos entonces la clase modelo y en el método render pediremos los artículos publicados que pasamos al modelo: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -En la plantilla nos encargaremos de renderizar una lista de artículos: +La plantilla `default.latte` se encargará de listar los artículos: ```latte {block content} @@ -114,7 +113,7 @@ El siguiente paso es editar el presentador. Enviaremos el número de la página También expandimos el método render para obtener la instancia Paginator, configurándola, y seleccionando los artículos correctos para mostrar en la plantilla. HomePresenter tendrá este aspecto: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository No tenemos que crear Paginator en el Presentador, en su lugar utilizaremos el método del objeto `Selection` devuelto por el repositorio: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/es/post-links.texy b/best-practices/es/post-links.texy new file mode 100644 index 0000000000..4b13b68b11 --- /dev/null +++ b/best-practices/es/post-links.texy @@ -0,0 +1,59 @@ +Cómo utilizar correctamente los enlaces POST +******************************************** + +En aplicaciones web, especialmente en interfaces administrativas, debería ser una regla básica que las acciones que cambian el estado del servidor no deberían realizarse a través del método HTTP GET. Como el nombre del método sugiere, GET debería usarse sólo para recuperar datos, no para cambiarlos. +Para acciones como borrar registros, es más apropiado utilizar el método POST. Aunque lo ideal sería utilizar el método DELETE, éste no puede invocarse sin JavaScript, de ahí que históricamente se utilice POST. + +¿Cómo hacerlo en la práctica? Utilice este sencillo truco. Al principio de su plantilla, cree un formulario de ayuda con el identificador `postForm`, que luego utilizará para los botones de eliminación: + +```latte .{file:@layout.latte} +
+``` + +Con este formulario, puedes utilizar un `
diff --git a/best-practices/fr/attribute-requires.texy b/best-practices/fr/attribute-requires.texy new file mode 100644 index 0000000000..03e5be9f2e --- /dev/null +++ b/best-practices/fr/attribute-requires.texy @@ -0,0 +1,179 @@ +Comment utiliser l'attribut `#[Requires]` Attribut +************************************************** + +.[perex] +Lorsque vous écrivez une application web, vous rencontrez souvent le besoin de restreindre l'accès à certaines parties de votre application. Vous souhaitez peut-être que certaines requêtes ne puissent envoyer des données que par l'intermédiaire d'un formulaire (en utilisant donc la méthode POST) ou qu'elles ne soient accessibles qu'aux appels AJAX. Dans le Nette Framework 3.2, un nouvel outil a été introduit qui vous permet de définir de telles restrictions de manière élégante et claire : l'attribut `#[Requires]` attribut. + +L'attribut est un marqueur spécial en PHP, que vous ajoutez avant la définition d'une classe ou d'une méthode. Comme il s'agit essentiellement d'une classe, vous devez inclure la clause use pour que les exemples suivants fonctionnent : + +```php +use Nette\Application\Attributes\Requires; +``` + +Vous pouvez utiliser l'attribut `#[Requires]` avec la classe du présentateur elle-même et avec ces méthodes : + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Les deux dernières méthodes concernent également les composants, de sorte que vous pouvez également utiliser l'attribut avec eux. + +Si les conditions spécifiées par l'attribut ne sont pas remplies, une erreur HTTP 4xx est déclenchée. + + +Méthodes HTTP .[#toc-http-methods] +---------------------------------- + +Vous pouvez spécifier les méthodes HTTP (telles que GET, POST, etc.) autorisées pour l'accès. Par exemple, si vous souhaitez n'autoriser l'accès qu'en soumettant un formulaire, définissez : + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Pourquoi utiliser POST au lieu de GET pour les actions de changement d'état et comment le faire ? [Lire le guide |post-links]. + +Vous pouvez spécifier une méthode ou un tableau de méthodes. Un cas particulier est la valeur `'*'` qui permet d'activer toutes les méthodes, ce que les présentateurs n'autorisent pas par défaut pour des [raisons de sécurité |application:presenters#http-method-check]. + + +Appels AJAX .[#toc-ajax-calls] +------------------------------ + +Si vous souhaitez qu'un présentateur ou une méthode ne soit accessible que pour les requêtes AJAX, utilisez : + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Même origine .[#toc-same-origin] +-------------------------------- + +Pour renforcer la sécurité, vous pouvez exiger que la demande soit faite à partir du même domaine. Cela permet d'éviter la [vulnérabilité au CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Pour les méthodes `handle()` l'accès à partir du même domaine est automatiquement requis. Par conséquent, si vous souhaitez autoriser l'accès à partir de n'importe quel domaine, indiquez : + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Accès via Forward .[#toc-access-via-forward] +-------------------------------------------- + +Il est parfois utile de restreindre l'accès à un présentateur pour qu'il ne soit accessible qu'indirectement, par exemple en utilisant les méthodes `forward()` ou `switch()` d'un autre présentateur. C'est ainsi que les présentateurs d'erreurs sont protégés pour éviter qu'ils ne soient déclenchés à partir d'une URL : + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Dans la pratique, il est souvent nécessaire de marquer certaines vues auxquelles on ne peut accéder qu'en fonction de la logique du présentateur. Là encore, elles ne peuvent pas être ouvertes directement : + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Actions spécifiques .[#toc-specific-actions] +-------------------------------------------- + +Vous pouvez également limiter l'accès à certains codes, comme la création d'un composant, à des actions spécifiques dans le présentateur : + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Pour une action unique, il n'est pas nécessaire d'écrire un tableau : `#[Requires(actions: 'default')]` + + +Attributs personnalisés .[#toc-custom-attributes] +------------------------------------------------- + +Si vous souhaitez utiliser l'attribut `#[Requires]` à plusieurs reprises avec les mêmes paramètres, vous pouvez créer votre propre attribut qui héritera de l'attribut `#[Requires]` et le paramétrer en fonction de vos besoins. + +Par exemple, l'attribut `#[SingleAction]` n'autorise l'accès que par l'action `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +ou `#[RestMethods]` permet l'accès via toutes les méthodes HTTP utilisées pour l'API REST : + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Conclusion .[#toc-conclusion] +----------------------------- + +L'attribut `#[Requires]` vous offre une grande flexibilité et un contrôle sur la manière dont vos pages web sont accessibles. En utilisant des règles simples mais puissantes, vous pouvez améliorer la sécurité et le bon fonctionnement de votre application. Comme vous pouvez le constater, l'utilisation des attributs dans Nette peut non seulement simplifier votre travail, mais aussi le sécuriser. + +{{sitename: Best Practices}} diff --git a/best-practices/fr/composer.texy b/best-practices/fr/composer.texy index d934b6499d..bb6de406f9 100644 --- a/best-practices/fr/composer.texy +++ b/best-practices/fr/composer.texy @@ -189,7 +189,7 @@ Toutefois, il est également possible d'utiliser Composer pour charger d'autres Par la suite, il est nécessaire d'exécuter la commande `composer dumpautoload` à chaque modification et de laisser les tables d'autoloadage se régénérer. Ceci est extrêmement gênant, et il est de loin préférable de confier cette tâche à [RobotLoader |robot-loader:], qui effectue la même activité automatiquement en arrière-plan et beaucoup plus rapidement. -La deuxième option consiste à suivre le [système PSR-4 |https://www.php-fig.org/psr/psr-4/]. Pour faire simple, il s'agit d'un système où les espaces de noms et les noms de classes correspondent à la structure des répertoires et aux noms de fichiers, c'est-à-dire que `App\Router\RouterFactory` est situé dans le fichier `/path/to/App/Router/RouterFactory.php`. Exemple de configuration : +La deuxième option consiste à suivre le [système PSR-4 |https://www.php-fig.org/psr/psr-4/]. Pour faire simple, il s'agit d'un système où les espaces de noms et les noms de classes correspondent à la structure des répertoires et aux noms de fichiers, c'est-à-dire que `App\Core\RouterFactory` est situé dans le fichier `/path/to/App/Core/RouterFactory.php`. Exemple de configuration : ```js { diff --git a/best-practices/fr/dynamic-snippets.texy b/best-practices/fr/dynamic-snippets.texy index 5bc4743f67..053c6e6607 100644 --- a/best-practices/fr/dynamic-snippets.texy +++ b/best-practices/fr/dynamic-snippets.texy @@ -35,7 +35,7 @@ Template : Ajaxisation .[#toc-ajaxization] =============================== -Introduisons maintenant AJAX dans cette application simple. La modification de l'évaluation d'un article n'est pas assez importante pour nécessiter une requête HTTP avec redirection, donc l'idéal serait de le faire avec AJAX en arrière-plan. Nous utiliserons le [script de gestion des modules complémentaires |https://componette.org/vojtech-dobes/nette.ajax.js/] avec la convention habituelle selon laquelle les liens AJAX ont la classe CSS `ajax`. +Introduisons maintenant AJAX dans cette application simple. La modification de l'évaluation d'un article n'est pas assez importante pour nécessiter une requête HTTP avec redirection, donc l'idéal serait de le faire avec AJAX en arrière-plan. Nous utiliserons le [script de gestion des modules complémentaires |application:ajax#toc-naja] avec la convention habituelle selon laquelle les liens AJAX ont la classe CSS `ajax`. Cependant, comment le faire spécifiquement ? Nette propose deux méthodes : le snippet dynamique et le composant. Ces deux méthodes ont leurs avantages et leurs inconvénients, nous allons donc les présenter une par une. @@ -151,7 +151,7 @@ Bien sûr, nous allons modifier le modèle de vue et nous devrons ajouter une fa ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/fr/lets-create-contact-form.texy b/best-practices/fr/lets-create-contact-form.texy index 50afc50327..0b3b5b4409 100644 --- a/best-practices/fr/lets-create-contact-form.texy +++ b/best-practices/fr/lets-create-contact-form.texy @@ -39,7 +39,7 @@ Comme vous pouvez le voir, nous avons créé deux méthodes. La première métho Mais que se passe-t-il si l'utilisateur ne remplit pas certains champs ? Dans ce cas, nous devons l'informer qu'il s'agit d'un champ obligatoire. C'est ce que nous avons fait avec la méthode `setRequired()`. Enfin, nous avons également ajouté un [événement |nette:glossary#events] `onSuccess`, qui est déclenché si le formulaire est soumis avec succès. Dans notre cas, il appelle la méthode `contactFormSucceeded`, qui se charge de traiter le formulaire soumis. Nous l'ajouterons au code dans un instant. -Laissez le composant `contantForm` être rendu dans le modèle `templates/Home/default.latte`: +Laissez le composant `contantForm` être rendu dans le modèle `Home/default.latte`: ```latte {block content} diff --git a/best-practices/fr/pagination.texy b/best-practices/fr/pagination.texy index cbd07b5ec6..cafbff84cc 100644 --- a/best-practices/fr/pagination.texy +++ b/best-practices/fr/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository Dans le Presenter nous injectons ensuite la classe model et dans la méthode render nous allons demander les articles publiés que nous passons au template : ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Dans le modèle, nous allons nous occuper de rendre une liste d'articles : +Le modèle `default.latte` se chargera ensuite de répertorier les articles : ```latte {block content} @@ -114,7 +113,7 @@ L'étape suivante consiste à modifier le présentateur. Nous allons transmettre Nous étendons également la méthode de rendu pour obtenir l'instance de Paginator, la configurer et sélectionner les bons articles à afficher dans le modèle. Le HomePresenter ressemblera à ceci : ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Nous n'avons pas besoin de créer le Paginator dans le Presenter, à la place nous utiliserons la méthode de l'objet `Selection` retourné par le référentiel : ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/fr/post-links.texy b/best-practices/fr/post-links.texy new file mode 100644 index 0000000000..70211882bc --- /dev/null +++ b/best-practices/fr/post-links.texy @@ -0,0 +1,59 @@ +Comment utiliser correctement les liens POST +******************************************** + +Dans les applications web, en particulier dans les interfaces administratives, la règle de base devrait être que les actions modifiant l'état du serveur ne doivent pas être effectuées via la méthode HTTP GET. Comme le nom de la méthode le suggère, GET ne doit être utilisé que pour récupérer des données, et non pour les modifier. +Pour des actions telles que la suppression d'enregistrements, il est plus approprié d'utiliser la méthode POST. L'idéal serait d'utiliser la méthode DELETE, mais celle-ci ne peut être invoquée sans JavaScript, d'où l'utilisation historique de POST. + +Comment faire en pratique ? Utilisez cette astuce simple. Au début de votre modèle, créez un formulaire d'aide avec l'identifiant `postForm`, que vous utiliserez ensuite pour les boutons de suppression : + +```latte .{file:@layout.latte} +
+``` + +Avec ce formulaire, vous pouvez utiliser un `
diff --git a/best-practices/hu/attribute-requires.texy b/best-practices/hu/attribute-requires.texy new file mode 100644 index 0000000000..22cea565bb --- /dev/null +++ b/best-practices/hu/attribute-requires.texy @@ -0,0 +1,179 @@ +Hogyan használjuk a `#[Requires]` Attribútum +******************************************** + +.[perex] +Webalkalmazások írása során gyakran találkozunk azzal az igénnyel, hogy korlátozni kell az alkalmazás bizonyos részeihez való hozzáférést. Talán azt szeretné, hogy egyes kérések csak űrlapon keresztül küldhessenek adatokat (tehát a POST módszerrel), vagy csak AJAX-hívások számára legyenek elérhetők. A Nette Framework 3.2-ben egy új eszköz került bevezetésre, amely lehetővé teszi az ilyen korlátozások elegáns és egyértelmű beállítását: a `#[Requires]` attribútum. + +Az attribútum egy speciális jelölő a PHP-ban, amelyet egy osztály vagy metódus definíciója előtt adunk meg. Mivel lényegében egy osztályról van szó, a következő példák működéséhez a use záradékot is be kell illesztenie: + +```php +use Nette\Application\Attributes\Requires; +``` + +Használhatja a `#[Requires]` attribútumot magával a prezentáló osztállyal és ezekkel a metódusokkal: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Az utolsó két módszer szintén komponensekre vonatkozik, így ezekkel is használhatja az attribútumot. + +Ha az attribútum által meghatározott feltételek nem teljesülnek, a rendszer egy HTTP 4xx hibaüzenetet küld. + + +HTTP-módszerek .[#toc-http-methods] +----------------------------------- + +Megadhatja, hogy mely HTTP-módszerek (például GET, POST stb.) engedélyezettek a hozzáféréshez. Ha például csak egy űrlap elküldésével akarja engedélyezni a hozzáférést, állítsa be a következőket: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Miért érdemes POST-ot használni GET helyett állapotváltoztató műveletekhez, és hogyan kell ezt megtenni? [Olvassa el az útmutatót |post-links]. + +Megadhat egy módszert vagy módszerek tömbjét. Speciális eset a `'*'` érték, amely minden módszert engedélyez, amit a prezenterek [biztonsági okokból |application:presenters#http-method-check] alapértelmezés szerint nem engedélyeznek. + + +AJAX hívások .[#toc-ajax-calls] +------------------------------- + +Ha azt szeretné, hogy egy bemutató vagy metódus csak AJAX-kérések számára legyen elérhető, használja a következőt: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Ugyanaz az eredet .[#toc-same-origin] +------------------------------------- + +A biztonság növelése érdekében megkövetelheti, hogy a kérés ugyanarról a tartományról érkezzen. Ez megakadályozza [a CSRF sebezhetőséget |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +A oldalon. `handle()` módszerek esetében automatikusan ugyanabból a tartományból való hozzáférés szükséges. Ha tehát bármilyen tartományból engedélyezni szeretné a hozzáférést, adja meg: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Hozzáférés a Forwardon keresztül .[#toc-access-via-forward] +----------------------------------------------------------- + +Néha célszerű úgy korlátozni a hozzáférést egy prezentálóhoz, hogy az csak közvetve legyen elérhető, például egy másik prezentáló `forward()` vagy `switch()` módszereivel. A hiba-bemutatókat így védik, hogy megakadályozzák, hogy egy URL-ből indítsák őket: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +A gyakorlatban gyakran szükség van bizonyos nézetek megjelölésére, amelyek csak a prezenter logikája alapján érhetők el. Megint csak azért, hogy ne lehessen őket közvetlenül megnyitni: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Konkrét intézkedések .[#toc-specific-actions] +--------------------------------------------- + +Azt is korlátozhatja, hogy bizonyos kódok, például egy komponens létrehozása, csak bizonyos műveletek esetén legyenek elérhetők a prezenterben: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Egyetlen művelethez nem szükséges tömböt írni: `#[Requires(actions: 'default')]` + + +Egyéni attribútumok .[#toc-custom-attributes] +--------------------------------------------- + +Ha a `#[Requires]` attribútumot ismételten ugyanazokkal a beállításokkal használni, létrehozhat saját attribútumot, amely örökli a `#[Requires]` és az igényeinek megfelelően állíthatja be. + +Például, `#[SingleAction]` csak a `default` műveleten keresztül engedélyezi a hozzáférést: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Vagy `#[RestMethods]` lehetővé teszi a hozzáférést a REST API-hoz használt összes HTTP-módszeren keresztül: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Következtetés .[#toc-conclusion] +-------------------------------- + +A `#[Requires]` attribútum nagyfokú rugalmasságot és ellenőrzést biztosít a weboldalak elérésének módját illetően. Egyszerű, de hatékony szabályok használatával fokozhatja az alkalmazás biztonságát és megfelelő működését. Amint láthatja, az attribútumok használata a Nette-ben nemcsak egyszerűsítheti, hanem biztosíthatja is a munkáját. + +{{sitename: Best Practices}} diff --git a/best-practices/hu/composer.texy b/best-practices/hu/composer.texy index fee65b20c5..d3c767b633 100644 --- a/best-practices/hu/composer.texy +++ b/best-practices/hu/composer.texy @@ -189,7 +189,7 @@ Lehetőség van azonban arra is, hogy a Composer segítségével a `vendor` mapp Ezt követően minden egyes változtatásnál el kell indítani a `composer dumpautoload` parancsot, és hagyni kell, hogy az autoloading táblák újratermelődjenek. Ez rendkívül kényelmetlen, és sokkal jobb, ha ezt a feladatot a [RobotLoaderre |robot-loader:] bízzuk, amely a háttérben automatikusan és sokkal gyorsabban végzi el ugyanezt a tevékenységet. -A második lehetőség a [PSR-4 |https://www.php-fig.org/psr/psr-4/] követése. Egyszerűen fogalmazva, ez egy olyan rendszer, ahol a névterek és az osztálynevek megfelelnek a könyvtárszerkezetnek és a fájlneveknek, azaz a `App\Router\RouterFactory` a `/path/to/App/Router/RouterFactory.php` fájlban található. Konfigurációs példa: +A második lehetőség a [PSR-4 |https://www.php-fig.org/psr/psr-4/] követése. Egyszerűen fogalmazva, ez egy olyan rendszer, ahol a névterek és az osztálynevek megfelelnek a könyvtárszerkezetnek és a fájlneveknek, azaz a `App\Core\RouterFactory` a `/path/to/App/Core/RouterFactory.php` fájlban található. Konfigurációs példa: ```js { diff --git a/best-practices/hu/dynamic-snippets.texy b/best-practices/hu/dynamic-snippets.texy index 508065649e..9234d5b6ce 100644 --- a/best-practices/hu/dynamic-snippets.texy +++ b/best-practices/hu/dynamic-snippets.texy @@ -35,7 +35,7 @@ Template: Ajaxization .[#toc-ajaxization] =============================== -Vigyük most az AJAX-ot ebbe az egyszerű alkalmazásba. Egy cikk értékelésének megváltoztatása nem elég fontos ahhoz, hogy HTTP-kérést igényeljen átirányítással, így ideális esetben ezt AJAX-szel kell elvégezni a háttérben. Használni fogjuk a [kezelőszkriptet a kiegészítésekből |https://componette.org/vojtech-dobes/nette.ajax.js/] a szokásos konvencióval, hogy az AJAX linkek CSS osztálya a `ajax`. +Vigyük most az AJAX-ot ebbe az egyszerű alkalmazásba. Egy cikk értékelésének megváltoztatása nem elég fontos ahhoz, hogy HTTP-kérést igényeljen átirányítással, így ideális esetben ezt AJAX-szel kell elvégezni a háttérben. Használni fogjuk a [kezelőszkriptet a kiegészítésekből |application:ajax#toc-naja] a szokásos konvencióval, hogy az AJAX linkek CSS osztálya a `ajax`. Azonban hogyan kell ezt konkrétan megtenni? A Nette 2 módszert kínál: a dinamikus snippet módot és a komponens módot. Mindkettőnek megvannak az előnyei és hátrányai, ezért egyenként mutatjuk be őket. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Természetesen meg fogjuk változtatni a nézetsablont, és hozzá kell adnunk e ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/hu/lets-create-contact-form.texy b/best-practices/hu/lets-create-contact-form.texy index 0f6e9b14d8..f13d2dc808 100644 --- a/best-practices/hu/lets-create-contact-form.texy +++ b/best-practices/hu/lets-create-contact-form.texy @@ -39,7 +39,7 @@ Mint látható, két metódust hoztunk létre. Az első metódus `createComponen De mi van akkor, ha a felhasználó nem tölt ki néhány mezőt? Ebben az esetben tudatnunk kell vele, hogy az adott mező kötelezően kitöltendő. Ezt a `setRequired()` metódussal tettük meg. Végül hozzáadtunk egy `onSuccess`[eseményt |nette:glossary#events] is, amely akkor lép működésbe, ha az űrlapot sikeresen elküldtük. A mi esetünkben meghívja a `contactFormSucceeded` metódust , amely a beküldött űrlap feldolgozásáról gondoskodik. Ezt is hozzáadjuk a kódhoz egy pillanat múlva. -Legyen a `contantForm` komponens megjelenítve a `templates/Home/default.latte` sablonban: +Legyen a `contantForm` komponens megjelenítve a `Home/default.latte` sablonban: ```latte {block content} diff --git a/best-practices/hu/pagination.texy b/best-practices/hu/pagination.texy index e1805afd07..f9611c6e3f 100644 --- a/best-practices/hu/pagination.texy +++ b/best-practices/hu/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository A Presenterben ezután befecskendezzük a modell osztályt, és a render metódusban lekérdezzük a publikált cikkeket, amelyeket átadunk a sablonhoz: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -A sablonban gondoskodunk egy cikklista rendereléséről: +A `default.latte` sablon ezután gondoskodik a cikkek felsorolásáról: ```latte {block content} @@ -114,7 +113,7 @@ A következő lépés a bemutató szerkesztése. Az aktuálisan megjelenített o A render metódust kibővítjük a Paginator példány megszerzésével, beállításával és a sablonban megjelenítendő megfelelő cikkek kiválasztásával is. A HomePresenter így fog kinézni: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Nem kell Paginátort létrehoznunk a Presenterben, helyette az adattár által visszaadott `Selection` objektum metódusát fogjuk használni: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/hu/post-links.texy b/best-practices/hu/post-links.texy new file mode 100644 index 0000000000..7fa8a2a16e --- /dev/null +++ b/best-practices/hu/post-links.texy @@ -0,0 +1,59 @@ +Hogyan kell helyesen használni a POST linkeket +********************************************** + +A webes alkalmazásokban, különösen az adminisztrációs felületeken alapvető szabály kell, hogy legyen, hogy a kiszolgáló állapotát megváltoztató műveleteket nem szabad a HTTP GET módszerrel végrehajtani. Ahogy a módszer neve is sugallja, a GET csak adatok lekérdezésére használható, azok megváltoztatására nem. +Az olyan műveletekhez, mint például a rekordok törlése, célszerűbb a POST módszert használni. Bár az ideális a DELETE módszer használata lenne, ez nem hívható elő JavaScript nélkül, ezért a POST módszert használják. + +Hogyan kell ezt a gyakorlatban csinálni? Használja ezt az egyszerű trükköt. A sablon elején hozzon létre egy segédűrlapot a `postForm` azonosítóval, amelyet aztán a törlés gombokhoz fog használni: + +```latte .{file:@layout.latte} +
+``` + +Ezzel az űrlappal használhat egy `
diff --git a/best-practices/it/attribute-requires.texy b/best-practices/it/attribute-requires.texy new file mode 100644 index 0000000000..f6a54bf381 --- /dev/null +++ b/best-practices/it/attribute-requires.texy @@ -0,0 +1,179 @@ +Come utilizzare l'attributo `#[Requires]` Attributo +*************************************************** + +.[perex] +Quando si scrive un'applicazione web, spesso si incontra la necessità di limitare l'accesso a certe parti dell'applicazione. Forse si desidera che alcune richieste possano essere inviate solo tramite un modulo (utilizzando quindi il metodo POST) o che siano accessibili solo alle chiamate AJAX. In Nette Framework 3.2 è stato introdotto un nuovo strumento che consente di impostare tali restrizioni in modo elegante e chiaro: l'attributo `#[Requires]` attributo. + +L'attributo è un marcatore speciale in PHP, che si aggiunge prima della definizione di una classe o di un metodo. Poiché si tratta essenzialmente di una classe, è necessario includere la clausola use per far funzionare gli esempi seguenti: + +```php +use Nette\Application\Attributes\Requires; +``` + +È possibile utilizzare l'attributo `#[Requires]` con la classe del presentatore e con questi metodi: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Anche gli ultimi due metodi riguardano i componenti, quindi è possibile utilizzare l'attributo anche con essi. + +Se le condizioni specificate dall'attributo non sono soddisfatte, viene generato un errore HTTP 4xx. + + +Metodi HTTP .[#toc-http-methods] +-------------------------------- + +È possibile specificare quali metodi HTTP (come GET, POST, ecc.) sono consentiti per l'accesso. Ad esempio, se si desidera consentire l'accesso solo tramite l'invio di un modulo, impostare: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Perché usare POST invece di GET per le azioni di modifica dello stato e come farlo? [Leggete la guida |post-links]. + +È possibile specificare un metodo o una serie di metodi. Un caso particolare è il valore `'*'` per abilitare tutti i metodi, che i presentatori non consentono di default per [motivi di sicurezza |application:presenters#http-method-check]. + + +Chiamate AJAX .[#toc-ajax-calls] +-------------------------------- + +Se si desidera che un presentatore o un metodo sia accessibile solo per le richieste AJAX, utilizzare: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Stessa origine .[#toc-same-origin] +---------------------------------- + +Per migliorare la sicurezza, è possibile richiedere che la richiesta venga effettuata dallo stesso dominio. In questo modo si evita la [vulnerabilità al CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Per i metodi `handle()` l'accesso dallo stesso dominio è automaticamente richiesto. Pertanto, se si desidera consentire l'accesso da qualsiasi dominio, specificare: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Accesso tramite Forward .[#toc-access-via-forward] +-------------------------------------------------- + +A volte è utile limitare l'accesso a un presentatore in modo che sia disponibile solo indirettamente, ad esempio utilizzando i metodi `forward()` o `switch()` di un altro presentatore. In questo modo si proteggono i presentatori di errori, per evitare che vengano attivati da un URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +In pratica, spesso è necessario contrassegnare alcune viste a cui si può accedere solo in base alla logica del presentatore. Anche in questo caso, in modo che non possano essere aperte direttamente: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Azioni specifiche .[#toc-specific-actions] +------------------------------------------ + +È anche possibile limitare l'accesso a determinati codici, come la creazione di un componente, solo per azioni specifiche nel presentatore: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Per una singola azione, non è necessario scrivere un array: `#[Requires(actions: 'default')]` + + +Attributi personalizzati .[#toc-custom-attributes] +-------------------------------------------------- + +Se si desidera utilizzare l'attributo `#[Requires]` con le stesse impostazioni, è possibile creare un attributo personalizzato che erediterà `#[Requires]` e impostarlo secondo le proprie esigenze. + +Ad esempio, `#[SingleAction]` consente l'accesso solo attraverso l'azione `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Oppure `#[RestMethods]` consentirà l'accesso tramite tutti i metodi HTTP utilizzati per l'API REST: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Conclusione .[#toc-conclusion] +------------------------------ + +L'attributo `#[Requires]` offre grande flessibilità e controllo sulle modalità di accesso alle pagine web. Utilizzando regole semplici ma potenti, è possibile migliorare la sicurezza e il corretto funzionamento dell'applicazione. Come si può vedere, l'uso degli attributi in Nette non solo semplifica il lavoro, ma lo rende anche sicuro. + +{{sitename: Best Practices}} diff --git a/best-practices/it/composer.texy b/best-practices/it/composer.texy index 93f33e4133..6ca7aff82e 100644 --- a/best-practices/it/composer.texy +++ b/best-practices/it/composer.texy @@ -189,7 +189,7 @@ Tuttavia, è anche possibile usare Composer per caricare altre classi al di fuor Successivamente, è necessario eseguire il comando `composer dumpautoload` a ogni modifica e lasciare che le tabelle di autocaricamento si rigenerino. Questo è estremamente scomodo ed è molto meglio affidare questo compito a [RobotLoader |robot-loader:], che svolge la stessa attività automaticamente in background e molto più velocemente. -La seconda opzione consiste nel seguire [PSR-4 |https://www.php-fig.org/psr/psr-4/]. In parole povere, si tratta di un sistema in cui gli spazi dei nomi e i nomi delle classi corrispondono alla struttura delle directory e ai nomi dei file, cioè `App\Router\RouterFactory` si trova nel file `/path/to/App/Router/RouterFactory.php`. Esempio di configurazione: +La seconda opzione consiste nel seguire [PSR-4 |https://www.php-fig.org/psr/psr-4/]. In parole povere, si tratta di un sistema in cui gli spazi dei nomi e i nomi delle classi corrispondono alla struttura delle directory e ai nomi dei file, cioè `App\Core\RouterFactory` si trova nel file `/path/to/App/Core/RouterFactory.php`. Esempio di configurazione: ```js { diff --git a/best-practices/it/dynamic-snippets.texy b/best-practices/it/dynamic-snippets.texy index 870ac5ebdc..c195b97853 100644 --- a/best-practices/it/dynamic-snippets.texy +++ b/best-practices/it/dynamic-snippets.texy @@ -35,7 +35,7 @@ Template: Ajaxization .[#toc-ajaxization] =============================== -Ora aggiungiamo AJAX a questa semplice applicazione. La modifica della valutazione di un articolo non è abbastanza importante da richiedere una richiesta HTTP con redirect, quindi idealmente dovrebbe essere fatta con AJAX in background. Utilizzeremo lo [script handler di add-on |https://componette.org/vojtech-dobes/nette.ajax.js/] con la solita convenzione che i link AJAX abbiano la classe CSS `ajax`. +Ora aggiungiamo AJAX a questa semplice applicazione. La modifica della valutazione di un articolo non è abbastanza importante da richiedere una richiesta HTTP con redirect, quindi idealmente dovrebbe essere fatta con AJAX in background. Utilizzeremo lo [script handler di add-on |application:ajax#toc-naja] con la solita convenzione che i link AJAX abbiano la classe CSS `ajax`. Tuttavia, come farlo in modo specifico? Nette offre due modi: quello degli snippet dinamici e quello dei componenti. Entrambi hanno pro e contro, quindi li mostreremo uno per uno. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Naturalmente cambieremo il modello della vista e dovremo aggiungere un factory a ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/it/lets-create-contact-form.texy b/best-practices/it/lets-create-contact-form.texy index 463ed21c39..468790dce9 100644 --- a/best-practices/it/lets-create-contact-form.texy +++ b/best-practices/it/lets-create-contact-form.texy @@ -39,7 +39,7 @@ Come si può vedere, abbiamo creato due metodi. Il primo metodo `createComponent Ma cosa succede se l'utente non compila alcuni campi? In questo caso, dovremmo fargli sapere che si tratta di un campo obbligatorio. Lo abbiamo fatto con il metodo `setRequired()`. Infine, abbiamo aggiunto anche un [evento |nette:glossary#events] `onSuccess`, che si attiva se il form viene inviato con successo. Nel nostro caso, richiama il metodo `contactFormSucceeded`, che si occupa di elaborare il modulo inviato. Lo aggiungeremo al codice tra poco. -Il componente `contantForm` deve essere reso nel template `templates/Home/default.latte`: +Il componente `contantForm` deve essere reso nel template `Home/default.latte`: ```latte {block content} diff --git a/best-practices/it/pagination.texy b/best-practices/it/pagination.texy index 0d6ca96483..adf166b262 100644 --- a/best-practices/it/pagination.texy +++ b/best-practices/it/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository Nel Presenter iniettiamo poi la classe Model e nel metodo render chiediamo gli articoli pubblicati che passiamo al template: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Nel modello, ci occuperemo di rendere un elenco di articoli: +Il modello `default.latte` si occuperà di elencare gli articoli: ```latte {block content} @@ -114,7 +113,7 @@ Il passo successivo è modificare il presentatore. Inoltreremo il numero della p Espandiamo inoltre il metodo render per ottenere l'istanza di Paginator, impostandola e selezionando gli articoli corretti da visualizzare nel template. HomePresenter avrà questo aspetto: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Non è necessario creare il Paginator nel Presenter, ma si utilizzerà il metodo dell'oggetto `Selection` restituito dal repository: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/it/post-links.texy b/best-practices/it/post-links.texy new file mode 100644 index 0000000000..8fbca0e2c4 --- /dev/null +++ b/best-practices/it/post-links.texy @@ -0,0 +1,59 @@ +Come utilizzare correttamente i link POST +***************************************** + +Nelle applicazioni web, soprattutto nelle interfacce amministrative, dovrebbe essere una regola di base che le azioni che modificano lo stato del server non dovrebbero essere eseguite tramite il metodo HTTP GET. Come suggerisce il nome del metodo, GET deve essere usato solo per recuperare dati, non per modificarli. +Per azioni come la cancellazione di record, è più appropriato usare il metodo POST. Anche se l'ideale sarebbe usare il metodo DELETE, questo non può essere invocato senza JavaScript, quindi storicamente si usa POST. + +Come fare nella pratica? Utilizzando questo semplice trucco. All'inizio del modello, creare un modulo di aiuto con l'identificatore `postForm`, che verrà poi utilizzato per i pulsanti di cancellazione: + +```latte .{file:@layout.latte} +
+``` + +Con questo modulo, è possibile utilizzare un elemento `
diff --git a/best-practices/pl/attribute-requires.texy b/best-practices/pl/attribute-requires.texy new file mode 100644 index 0000000000..2c79b3fc87 --- /dev/null +++ b/best-practices/pl/attribute-requires.texy @@ -0,0 +1,179 @@ +Jak używać atrybutu `#[Requires]` Atrybut +***************************************** + +.[perex] +Podczas pisania aplikacji internetowej często pojawia się potrzeba ograniczenia dostępu do niektórych jej części. Być może chcesz, aby niektóre żądania mogły wysyłać dane tylko za pośrednictwem formularza (a więc przy użyciu metody POST) lub aby były dostępne tylko dla wywołań AJAX. W Nette Framework 3.2 wprowadzono nowe narzędzie, które pozwala ustawić takie ograniczenia w elegancki i przejrzysty sposób: atrybut `#[Requires]` atrybut. + +Atrybut jest specjalnym znacznikiem w PHP, który dodaje się przed definicją klasy lub metody. Ponieważ jest to zasadniczo klasa, musisz dołączyć klauzulę use, aby poniższe przykłady działały: + +```php +use Nette\Application\Attributes\Requires; +``` + +Atrybutu `#[Requires]` z samą klasą prezentera i tymi metodami: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Dwie ostatnie metody również dotyczą komponentów, więc można użyć atrybutu również z nimi. + +Jeśli warunki określone przez atrybut nie są spełnione, wywoływany jest błąd HTTP 4xx. + + +Metody HTTP .[#toc-http-methods] +-------------------------------- + +Można określić, które metody HTTP (takie jak GET, POST itp.) są dozwolone dla dostępu. Na przykład, jeśli chcesz zezwolić na dostęp tylko poprzez przesłanie formularza, ustaw: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Dlaczego należy używać POST zamiast GET do akcji zmieniających stan i jak to zrobić? [Przeczytaj przewodnik |post-links]. + +Można określić metodę lub tablicę metod. Szczególnym przypadkiem jest wartość `'*'`, która włącza wszystkie metody, na które prezentery nie pozwalają domyślnie ze [względów |application:presenters#http-method-check] bezpieczeństwa. + + +Wywołania AJAX .[#toc-ajax-calls] +--------------------------------- + +Jeśli chcesz, aby prezenter lub metoda były dostępne tylko dla żądań AJAX, użyj: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +To samo pochodzenie .[#toc-same-origin] +--------------------------------------- + +Aby zwiększyć bezpieczeństwo, można wymagać, aby żądanie zostało wykonane z tej samej domeny. Zapobiega to [podatności na CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Dla `handle()` automatycznie wymagany jest dostęp z tej samej domeny. Jeśli więc chcesz zezwolić na dostęp z dowolnej domeny, określ: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Dostęp przez Forward .[#toc-access-via-forward] +----------------------------------------------- + +Czasami przydatne jest ograniczenie dostępu do prezentera, tak aby był on dostępny tylko pośrednio, na przykład przy użyciu metod `forward()` lub `switch()` z innego prezentera. W ten sposób chronione są prezentery błędów, aby uniemożliwić ich wywołanie z adresu URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +W praktyce często konieczne jest oznaczenie pewnych widoków, do których można uzyskać dostęp tylko w oparciu o logikę w prezenterze. Ponownie, aby nie można było ich otworzyć bezpośrednio: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Konkretne działania .[#toc-specific-actions] +-------------------------------------------- + +Można również ograniczyć dostęp do określonego kodu, takiego jak tworzenie komponentu, tylko dla określonych akcji w prezenterze: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Dla pojedynczej akcji nie ma potrzeby pisania tablicy: `#[Requires(actions: 'default')]` + + +Atrybuty niestandardowe .[#toc-custom-attributes] +------------------------------------------------- + +Jeśli chcesz używać atrybutu `#[Requires]` z tymi samymi ustawieniami, można utworzyć własny atrybut, który będzie dziedziczył `#[Requires]` i ustawić go zgodnie z własnymi potrzebami. + +Na przykład, `#[SingleAction]` zezwala na dostęp tylko poprzez akcję `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +lub `#[RestMethods]` umożliwi dostęp za pośrednictwem wszystkich metod HTTP używanych w interfejsie API REST: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Wnioski .[#toc-conclusion] +-------------------------- + +Atrybut `#[Requires]` zapewnia dużą elastyczność i kontrolę nad sposobem dostępu do stron internetowych. Korzystając z prostych, ale potężnych reguł, można zwiększyć bezpieczeństwo i prawidłowe funkcjonowanie aplikacji. Jak widać, korzystanie z atrybutów w Nette może nie tylko uprościć pracę, ale także ją zabezpieczyć. + +{{sitename: Best Practices}} diff --git a/best-practices/pl/composer.texy b/best-practices/pl/composer.texy index 125441e90d..77950657da 100644 --- a/best-practices/pl/composer.texy +++ b/best-practices/pl/composer.texy @@ -189,7 +189,7 @@ Jednakże możliwe jest również użycie Composera do załadowania innych klas Następnie musisz uruchomić polecenie `composer dumpautoload` za każdym razem, gdy dokonujesz zmiany i masz ponownie wygenerowane tabele autoloader. Jest to niezwykle uciążliwe i zdecydowanie lepiej powierzyć to zadanie [RobotLoaderowi |robot-loader:], który wykonuje tę samą pracę automatycznie w tle i znacznie szybciej. -Inną możliwością jest zastosowanie się do [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Mówiąc najprościej, jest to system, w którym przestrzenie nazw i nazwy klas odpowiadają strukturom katalogów i nazwom plików, więc na przykład `App\Router\RouterFactory` będzie w pliku `/path/to/App/Router/RouterFactory.php`. Przykładowa konfiguracja: +Inną możliwością jest zastosowanie się do [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Mówiąc najprościej, jest to system, w którym przestrzenie nazw i nazwy klas odpowiadają strukturom katalogów i nazwom plików, więc na przykład `App\Core\RouterFactory` będzie w pliku `/path/to/App/Core/RouterFactory.php`. Przykładowa konfiguracja: ```js { diff --git a/best-practices/pl/dynamic-snippets.texy b/best-practices/pl/dynamic-snippets.texy index c1ad93724d..74c1c17c1f 100644 --- a/best-practices/pl/dynamic-snippets.texy +++ b/best-practices/pl/dynamic-snippets.texy @@ -35,7 +35,7 @@ Szablon: Ajaxizacja .[#toc-ajaxization] ============================== -Wyposażmy teraz tę prostą aplikację w AJAX. Zmiana oceny artykułu nie jest na tyle ważna, aby wymagała przekierowania, więc idealnie powinna być wykonana z AJAX w tle. Wykorzystamy [skrypt handler z dodatków ze |https://componette.org/vojtech-dobes/nette.ajax.js/] zwykłą konwencją, że linki AJAX mają klasę CSS `ajax`. +Wyposażmy teraz tę prostą aplikację w AJAX. Zmiana oceny artykułu nie jest na tyle ważna, aby wymagała przekierowania, więc idealnie powinna być wykonana z AJAX w tle. Wykorzystamy [skrypt handler z dodatków ze |application:ajax#toc-naja] zwykłą konwencją, że linki AJAX mają klasę CSS `ajax`. Jednak jak to konkretnie zrobić? Nette oferuje 2 ścieżki: tzw. ścieżkę dynamicznych snippetów oraz ścieżkę komponentów. Oba mają swoje plusy i minusy, więc pokażemy je po kolei. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Oczywiście zmienimy szablon widoku i będziemy musieli dodać fabrykę do preze ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/pl/lets-create-contact-form.texy b/best-practices/pl/lets-create-contact-form.texy index 25c109b3c9..40f832e01b 100644 --- a/best-practices/pl/lets-create-contact-form.texy +++ b/best-practices/pl/lets-create-contact-form.texy @@ -39,7 +39,7 @@ Jak widać, stworzyliśmy dwie metody. Pierwsza metoda `createComponentContactFo Ale co jeśli użytkownik nie wypełni niektórych pól? W takim przypadku powinniśmy dać mu znać, że jest to pole wymagane. Zrobiliśmy to za pomocą metody `setRequired()`. Na koniec dodaliśmy również [zdarzenie |nette:glossary#events] `onSuccess`, które jest wywoływane, jeśli formularz zostanie przesłany pomyślnie. W naszym przypadku wywołuje ono metodę `contactFormSucceeded`, która zajmuje się przetwarzaniem przesłanego formularza. Za chwilę dodamy to do kodu. -Niech komponent `contantForm` będzie renderowany w szablonie `templates/Home/default.latte`: +Niech komponent `contantForm` będzie renderowany w szablonie `Home/default.latte`: ```latte {block content} diff --git a/best-practices/pl/pagination.texy b/best-practices/pl/pagination.texy index d6da41b76b..13e39bab4a 100644 --- a/best-practices/pl/pagination.texy +++ b/best-practices/pl/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository Następnie wstrzykujemy klasę modelu w prezenterze i w metodzie render żądamy opublikowanych artykułów do przekazania do szablonu: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -W szablonie zajmujemy się następnie wyszczególnieniem artykułów: +Szablon `default.latte` zajmie się następnie listą artykułów: ```latte {block content} @@ -114,7 +113,7 @@ Następnie zabierzemy się do pracy nad modyfikacją prezentera. Do metody rende Następnie rozszerzymy również metodę render, aby uzyskać instancję Paginatora, skonfigurować ją i wybrać odpowiednie artykuły do wyświetlenia w szablonie. HomePresenter po modyfikacjach będzie wyglądał tak: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Nie musimy tworzyć Paginatora w prezenterze, zamiast tego używamy metody klasy `Selection` zwracanej przez repozytorium: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/pl/post-links.texy b/best-practices/pl/post-links.texy new file mode 100644 index 0000000000..9db658ec70 --- /dev/null +++ b/best-practices/pl/post-links.texy @@ -0,0 +1,59 @@ +Jak prawidłowo używać linków POST +********************************* + +W aplikacjach internetowych, zwłaszcza w interfejsach administracyjnych, podstawową zasadą powinno być to, że działania zmieniające stan serwera nie powinny być wykonywane za pomocą metody HTTP GET. Jak sugeruje nazwa metody, GET powinien być używany tylko do pobierania danych, a nie do ich zmiany. +W przypadku działań takich jak usuwanie rekordów, bardziej odpowiednie jest użycie metody POST. Chociaż idealnym rozwiązaniem byłoby użycie metody DELETE, nie można jej wywołać bez JavaScript, dlatego POST jest historycznie używany. + +Jak to zrobić w praktyce? Użyj tej prostej sztuczki. Na początku szablonu utwórz formularz pomocniczy o identyfikatorze `postForm`, który następnie użyjesz dla przycisków usuwania: + +```latte .{file:@layout.latte} +
+``` + +W tym formularzu możesz użyć `
diff --git a/best-practices/pt/attribute-requires.texy b/best-practices/pt/attribute-requires.texy new file mode 100644 index 0000000000..af9f1f4ec0 --- /dev/null +++ b/best-practices/pt/attribute-requires.texy @@ -0,0 +1,179 @@ +Como usar o `#[Requires]` Atributo +********************************** + +.[perex] +Ao escrever um aplicativo da Web, você frequentemente se depara com a necessidade de restringir o acesso a determinadas partes do aplicativo. Talvez você queira que algumas solicitações só possam enviar dados por meio de um formulário (usando, portanto, o método POST) ou que sejam acessíveis somente a chamadas AJAX. No Nette Framework 3.2, foi introduzida uma nova ferramenta que permite que você defina essas restrições de forma elegante e clara: o atributo `#[Requires]` atributo. + +O atributo é um marcador especial no PHP, que você adiciona antes da definição de uma classe ou método. Como ele é essencialmente uma classe, você precisa incluir a cláusula use para que os exemplos a seguir funcionem: + +```php +use Nette\Application\Attributes\Requires; +``` + +Você pode usar o atributo `#[Requires]` com a própria classe do apresentador e nesses métodos: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Os dois últimos métodos também se referem a componentes, portanto, você também pode usar o atributo com eles. + +Se as condições especificadas pelo atributo não forem atendidas, será acionado um erro HTTP 4xx. + + +Métodos HTTP .[#toc-http-methods] +--------------------------------- + +Você pode especificar quais métodos HTTP (como GET, POST, etc.) são permitidos para acesso. Por exemplo, se você quiser permitir o acesso somente por meio do envio de um formulário, defina: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Por que você deve usar POST em vez de GET para ações de alteração de estado e como fazer isso? [Leia o guia |post-links]. + +Você pode especificar um método ou uma matriz de métodos. Um caso especial é o valor `'*'` para ativar todos os métodos, que os apresentadores não permitem por padrão por [motivos de segurança |application:presenters#http-method-check]. + + +Chamadas AJAX .[#toc-ajax-calls] +-------------------------------- + +Se você quiser que um apresentador ou método seja acessível somente para solicitações AJAX, use: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Mesma origem .[#toc-same-origin] +-------------------------------- + +Para aumentar a segurança, você pode exigir que a solicitação seja feita a partir do mesmo domínio. Isso evita a [vulnerabilidade ao CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Para os métodos `handle()` o acesso do mesmo domínio é automaticamente necessário. Portanto, se você quiser permitir o acesso de qualquer domínio, especifique: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Acesso via Forward .[#toc-access-via-forward] +--------------------------------------------- + +Às vezes, é útil restringir o acesso a um apresentador para que ele esteja disponível apenas indiretamente, por exemplo, usando os métodos `forward()` ou `switch()` de outro apresentador. É assim que os apresentadores de erros são protegidos para evitar que sejam acionados a partir de um URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Na prática, muitas vezes é necessário marcar determinadas exibições que só podem ser acessadas com base na lógica do apresentador. Novamente, para que elas não possam ser abertas diretamente: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Ações específicas .[#toc-specific-actions] +------------------------------------------ + +Você também pode restringir o acesso a determinados códigos, como a criação de um componente, apenas para ações específicas no apresentador: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Para uma única ação, não há necessidade de escrever uma matriz: `#[Requires(actions: 'default')]` + + +Atributos personalizados .[#toc-custom-attributes] +-------------------------------------------------- + +Se você quiser usar o atributo `#[Requires]` repetidamente com as mesmas configurações, você pode criar seu próprio atributo que herdará o atributo `#[Requires]` e defini-lo de acordo com suas necessidades. + +Por exemplo, `#[SingleAction]` permite o acesso somente por meio da ação `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Ou `#[RestMethods]` permitirá o acesso por meio de todos os métodos HTTP usados para a API REST: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Conclusão .[#toc-conclusion] +---------------------------- + +O atributo `#[Requires]` oferece grande flexibilidade e controle sobre como as páginas da Web são acessadas. Usando regras simples, porém poderosas, você pode aumentar a segurança e o funcionamento adequado do seu aplicativo. Como você pode ver, o uso de atributos no Nette pode não apenas simplificar seu trabalho, mas também torná-lo seguro. + +{{sitename: Best Practices}} diff --git a/best-practices/pt/composer.texy b/best-practices/pt/composer.texy index a22608c832..bc48d771b3 100644 --- a/best-practices/pt/composer.texy +++ b/best-practices/pt/composer.texy @@ -189,7 +189,7 @@ Entretanto, também é possível utilizar o Composer para carregar outras classe Em seguida, é necessário executar o comando `composer dumpautoload` a cada mudança e deixar as mesas de auto-carga se regenerar. Isto é extremamente inconveniente, e é muito melhor confiar esta tarefa ao [RobotLoader |robot-loader:], que executa a mesma atividade automaticamente em segundo plano e muito mais rápido. -A segunda opção é seguir o [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Simplesmente dizendo, é um sistema onde os namespaces e nomes de classes correspondem à estrutura do diretório e nomes de arquivos, ou seja, `App\Router\RouterFactory` está localizado no arquivo `/path/to/App/Router/RouterFactory.php`. Exemplo de configuração: +A segunda opção é seguir o [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Simplesmente dizendo, é um sistema onde os namespaces e nomes de classes correspondem à estrutura do diretório e nomes de arquivos, ou seja, `App\Core\RouterFactory` está localizado no arquivo `/path/to/App/Core/RouterFactory.php`. Exemplo de configuração: ```js { diff --git a/best-practices/pt/dynamic-snippets.texy b/best-practices/pt/dynamic-snippets.texy index 660329a182..65434b41c0 100644 --- a/best-practices/pt/dynamic-snippets.texy +++ b/best-practices/pt/dynamic-snippets.texy @@ -35,7 +35,7 @@ Modelo: Ajaxização .[#toc-ajaxization] ============================== -Vamos agora trazer o AJAX para esta simples aplicação. Mudar a classificação de um artigo não é suficientemente importante para exigir um pedido HTTP com redirecionamento, então o ideal é que isso seja feito com AJAX em segundo plano. Usaremos o [script do handler dos add-ons |https://componette.org/vojtech-dobes/nette.ajax.js/] com a convenção usual de que os links AJAX têm a classe CSS `ajax`. +Vamos agora trazer o AJAX para esta simples aplicação. Mudar a classificação de um artigo não é suficientemente importante para exigir um pedido HTTP com redirecionamento, então o ideal é que isso seja feito com AJAX em segundo plano. Usaremos o [script do handler dos add-ons |application:ajax#toc-naja] com a convenção usual de que os links AJAX têm a classe CSS `ajax`. No entanto, como fazer isso especificamente? A Nette oferece 2 maneiras: a maneira dinâmica do snippet e a maneira dos componentes. Ambas têm seus prós e contras, por isso vamos mostrar-lhes uma a uma. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Modelo de componente: ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/pt/lets-create-contact-form.texy b/best-practices/pt/lets-create-contact-form.texy index 73db878db9..6fd619dd72 100644 --- a/best-practices/pt/lets-create-contact-form.texy +++ b/best-practices/pt/lets-create-contact-form.texy @@ -39,7 +39,7 @@ Como você pode ver, nós criamos dois métodos. O primeiro método `createCompo Mas e se o usuário não preencher alguns campos? Nesse caso, devemos avisá-lo que se trata de um campo obrigatório. Fizemos isso com o método `setRequired()`. Finalmente, adicionamos também um [evento |nette:glossary#events] `onSuccess`, que é acionado se o formulário for submetido com sucesso. Em nosso caso, ele chama o método `contactFormSucceeded`, que se encarrega de processar o formulário submetido. Acrescentaremos isso ao código em um momento. -Deixe o componente `contantForm` ser apresentado no modelo `templates/Home/default.latte`: +Deixe o componente `contantForm` ser apresentado no modelo `Home/default.latte`: ```latte {block content} diff --git a/best-practices/pt/pagination.texy b/best-practices/pt/pagination.texy index 7fae6d5b5e..149dfc1b72 100644 --- a/best-practices/pt/pagination.texy +++ b/best-practices/pt/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository No Apresentador injetamos então a classe do modelo e no método de renderização pediremos os artigos publicados que passamos para o modelo: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -No modelo, nós nos encarregaremos de elaborar uma lista de artigos: +O modelo `default.latte` se encarregará de listar os artigos: ```latte {block content} @@ -114,7 +113,7 @@ O próximo passo é editar o apresentador. Nós encaminharemos o número da pág Também expandimos o método de renderização para obter a instância Paginator, configurando-a e selecionando os artigos corretos a serem exibidos no modelo. Home PagePresenter terá este aspecto: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Não temos que criar o Paginador no Apresentador, em vez disso usaremos o método do objeto `Selection` devolvido pelo repositório: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/pt/post-links.texy b/best-practices/pt/post-links.texy new file mode 100644 index 0000000000..453fc217e1 --- /dev/null +++ b/best-practices/pt/post-links.texy @@ -0,0 +1,59 @@ +Como usar corretamente os links POST +************************************ + +Em aplicativos da Web, especialmente em interfaces administrativas, deve ser uma regra básica que as ações que alteram o estado do servidor não sejam executadas por meio do método HTTP GET. Como o nome do método sugere, o GET deve ser usado somente para recuperar dados, não para alterá-los. +Para ações como a exclusão de registros, é mais apropriado usar o método POST. Embora o ideal fosse usar o método DELETE, ele não pode ser invocado sem JavaScript, por isso o POST é historicamente usado. + +Como fazer isso na prática? Use este truque simples. No início do seu modelo, crie um formulário auxiliar com o identificador `postForm`, que você usará para os botões de exclusão: + +```latte .{file:@layout.latte} +
+``` + +Com esse formulário, você pode usar um `
diff --git a/best-practices/ro/attribute-requires.texy b/best-practices/ro/attribute-requires.texy new file mode 100644 index 0000000000..7d541aaa50 --- /dev/null +++ b/best-practices/ro/attribute-requires.texy @@ -0,0 +1,179 @@ +Cum se utilizează `#[Requires]` Atributul +***************************************** + +.[perex] +Atunci când scrieți o aplicație web, vă confruntați adesea cu necesitatea de a restricționa accesul la anumite părți ale aplicației. Poate doriți ca unele cereri să poată trimite date doar prin intermediul unui formular (utilizând astfel metoda POST) sau să fie accesibile doar apelurilor AJAX. În Nette Framework 3.2, a fost introdus un nou instrument care vă permite să stabiliți astfel de restricții în mod elegant și clar: instrumentul `#[Requires]` atribut. + +Atributul este un marker special în PHP, pe care îl adăugați înainte de definiția unei clase sau metode. Deoarece este în esență o clasă, trebuie să includeți clauza use pentru ca următoarele exemple să funcționeze: + +```php +use Nette\Application\Attributes\Requires; +``` + +Puteți utiliza funcția `#[Requires]` cu clasa presenter în sine și cu aceste metode: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Ultimele două metode se referă, de asemenea, la componente, astfel încât puteți utiliza atributul și cu acestea. + +În cazul în care condițiile specificate de atribut nu sunt îndeplinite, se declanșează o eroare HTTP 4xx. + + +Metode HTTP .[#toc-http-methods] +-------------------------------- + +Puteți specifica ce metode HTTP (cum ar fi GET, POST etc.) sunt permise pentru acces. De exemplu, dacă doriți să permiteți accesul numai prin trimiterea unui formular, setați: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +De ce ar trebui să folosiți POST în loc de GET pentru acțiunile de schimbare a stării și cum să faceți acest lucru? [Citiți ghidul |post-links]. + +Puteți specifica o metodă sau o serie de metode. Un caz special este valoarea `'*'` pentru a activa toate metodele, pe care prezentatorii nu o permit în mod implicit din [motive de securitate |application:presenters#http-method-check]. + + +Apeluri AJAX .[#toc-ajax-calls] +------------------------------- + +Dacă doriți ca un prezentator sau o metodă să fie accesibilă numai pentru cererile AJAX, utilizați: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Aceeași origine .[#toc-same-origin] +----------------------------------- + +Pentru a spori securitatea, puteți solicita ca solicitarea să fie făcută din același domeniu. Acest lucru previne [vulnerabilitatea la CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Pentru `handle()` este necesar în mod automat accesul din același domeniu. Prin urmare, dacă doriți să permiteți accesul din orice domeniu, specificați: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Acces prin Forward .[#toc-access-via-forward] +--------------------------------------------- + +Uneori este util să se restricționeze accesul la un prezentator astfel încât acesta să fie disponibil doar indirect, de exemplu, prin utilizarea metodelor `forward()` sau `switch()` de la un alt prezentator. Acesta este modul în care sunt protejați prezentatorii de erori pentru a împiedica declanșarea lor de la un URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +În practică, este adesea necesar să se marcheze anumite vizualizări care pot fi accesate numai pe baza logicii din prezentator. Din nou, pentru ca acestea să nu poată fi deschise direct: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Acțiuni specifice .[#toc-specific-actions] +------------------------------------------ + +Puteți, de asemenea, să restricționați accesul la anumite coduri, cum ar fi crearea unei componente, numai pentru anumite acțiuni din prezentator: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Pentru o singură acțiune, nu este nevoie să scrieți o matrice: `#[Requires(actions: 'default')]` + + +Atribute personalizate .[#toc-custom-attributes] +------------------------------------------------ + +Dacă doriți să utilizați `#[Requires]` în mod repetat cu aceleași setări, puteți crea propriul atribut care va moșteni atributul `#[Requires]` și să îl setați în funcție de nevoile dumneavoastră. + +De exemplu, `#[SingleAction]` permite accesul numai prin intermediul acțiunii `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Sau `#[RestMethods]` va permite accesul prin toate metodele HTTP utilizate pentru API REST: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Concluzie .[#toc-conclusion] +---------------------------- + +The `#[Requires]` vă oferă o mare flexibilitate și control asupra modului în care sunt accesate paginile dvs. web. Utilizând reguli simple, dar puternice, puteți spori securitatea și buna funcționare a aplicației dumneavoastră. După cum puteți vedea, utilizarea atributelor în Nette nu numai că vă poate simplifica munca, dar o poate și securiza. + +{{sitename: Best Practices}} diff --git a/best-practices/ro/composer.texy b/best-practices/ro/composer.texy index a2c5f12e81..0905a7c202 100644 --- a/best-practices/ro/composer.texy +++ b/best-practices/ro/composer.texy @@ -189,7 +189,7 @@ Cu toate acestea, este de asemenea posibil să utilizați Composer pentru a înc Ulterior, este necesar să executați comanda `composer dumpautoload` cu fiecare modificare și să lăsați tabelele de autoloading să se regenereze. Acest lucru este extrem de incomod și este mult mai bine să încredințați această sarcină lui [RobotLoader |robot-loader:], care efectuează aceeași activitate în mod automat în fundal și mult mai rapid. -A doua opțiune este să urmați [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Spunând simplu, este un sistem în care spațiile de nume și numele claselor corespund structurii directoarelor și numelor de fișiere, adică `App\Router\RouterFactory` se află în fișierul `/path/to/App/Router/RouterFactory.php`. Exemplu de configurare: +A doua opțiune este să urmați [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Spunând simplu, este un sistem în care spațiile de nume și numele claselor corespund structurii directoarelor și numelor de fișiere, adică `App\Core\RouterFactory` se află în fișierul `/path/to/App/Core/RouterFactory.php`. Exemplu de configurare: ```js { diff --git a/best-practices/ro/dynamic-snippets.texy b/best-practices/ro/dynamic-snippets.texy index c5201295d5..3635169fd9 100644 --- a/best-practices/ro/dynamic-snippets.texy +++ b/best-practices/ro/dynamic-snippets.texy @@ -35,7 +35,7 @@ public function handleUnlike(int $articleId): void Ajaxizare .[#toc-ajaxization] ============================= -Să aducem acum AJAX în această aplicație simplă. Modificarea ratingului unui articol nu este suficient de importantă pentru a necesita o cerere HTTP cu redirecționare, așa că, în mod ideal, ar trebui să se facă cu AJAX în fundal. Vom folosi [scriptul handler din add-ons |https://componette.org/vojtech-dobes/nette.ajax.js/] cu convenția obișnuită ca legăturile AJAX să aibă clasa CSS `ajax`. +Să aducem acum AJAX în această aplicație simplă. Modificarea ratingului unui articol nu este suficient de importantă pentru a necesita o cerere HTTP cu redirecționare, așa că, în mod ideal, ar trebui să se facă cu AJAX în fundal. Vom folosi [scriptul handler din add-ons |application:ajax#toc-naja] cu convenția obișnuită ca legăturile AJAX să aibă clasa CSS `ajax`. Totuși, cum să o facem în mod specific? Nette oferă 2 modalități: modalitatea cu fragmente dinamice și modalitatea cu componente. Ambele au avantajele și dezavantajele lor, așa că le vom prezenta pe rând. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Bineînțeles că vom schimba șablonul de vizualizare și va trebui să adăug ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/ro/lets-create-contact-form.texy b/best-practices/ro/lets-create-contact-form.texy index eecffd9791..42435507e4 100644 --- a/best-practices/ro/lets-create-contact-form.texy +++ b/best-practices/ro/lets-create-contact-form.texy @@ -39,7 +39,7 @@ După cum puteți vedea, am creat două metode. Prima metodă `createComponentCo Dar ce se întâmplă dacă utilizatorul nu completează unele câmpuri? În acest caz, ar trebui să-l anunțăm că este un câmp obligatoriu. Am făcut acest lucru cu metoda `setRequired()`. În cele din urmă, am adăugat și un [eveniment |nette:glossary#events] `onSuccess`, care este declanșat dacă formularul este trimis cu succes. În cazul nostru, acesta apelează metoda `contactFormSucceeded`, care se ocupă de procesarea formularului trimis. Vom adăuga acest lucru în cod imediat. -Lăsați componenta `contantForm` să fie redată în șablonul `templates/Home/default.latte`: +Lăsați componenta `contantForm` să fie redată în șablonul `Home/default.latte`: ```latte {block content} diff --git a/best-practices/ro/pagination.texy b/best-practices/ro/pagination.texy index 40d9de47aa..ee9ac16acc 100644 --- a/best-practices/ro/pagination.texy +++ b/best-practices/ro/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository În Presenter vom injecta apoi clasa model, iar în metoda render vom cere articolele publicate pe care le vom trece în șablon: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -În șablon, ne vom ocupa de redarea unei liste de articole: +Șablonul `default.latte` se va ocupa apoi de listarea articolelor: ```latte {block content} @@ -114,7 +113,7 @@ Următorul pas este să modificăm prezentatorul. Vom transmite numărul paginii De asemenea, extindem metoda de randare pentru a obține instanța Paginator, configurând-o și selectând articolele corecte pentru a fi afișate în șablon. HomePresenter va arăta astfel: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Nu trebuie să creăm Paginator în Presenter, în schimb vom folosi metoda obiectului `Selection` returnat de depozit: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/ro/post-links.texy b/best-practices/ro/post-links.texy new file mode 100644 index 0000000000..d81e2aa8bb --- /dev/null +++ b/best-practices/ro/post-links.texy @@ -0,0 +1,59 @@ +Cum să folosiți corect legăturile POST +************************************** + +În aplicațiile web, în special în interfețele administrative, ar trebui să fie o regulă de bază ca acțiunile care modifică starea serverului să nu fie efectuate prin metoda HTTP GET. După cum sugerează și numele metodei, GET ar trebui să fie utilizată numai pentru a prelua date, nu pentru a le modifica. +Pentru acțiuni precum ștergerea înregistrărilor, este mai indicat să se utilizeze metoda POST. Deși ideal ar fi să se folosească metoda DELETE, aceasta nu poate fi invocată fără JavaScript, de aceea se folosește în mod obișnuit metoda POST. + +Cum se procedează în practică? Folosiți acest truc simplu. La începutul șablonului dumneavoastră, creați un formular ajutător cu identificatorul `postForm`, pe care îl veți folosi apoi pentru butoanele de ștergere: + +```latte .{file:@layout.latte} +
+``` + +Cu acest formular, puteți utiliza un `
diff --git a/best-practices/ru/attribute-requires.texy b/best-practices/ru/attribute-requires.texy new file mode 100644 index 0000000000..f91273fa06 --- /dev/null +++ b/best-practices/ru/attribute-requires.texy @@ -0,0 +1,179 @@ +Как использовать `#[Requires]` Атрибут +************************************** + +.[perex] +При написании веб-приложений вы часто сталкиваетесь с необходимостью ограничить доступ к определенным частям вашего приложения. Возможно, вы хотите, чтобы некоторые запросы могли отправлять данные только через форму (таким образом, используя метод POST) или были доступны только для вызовов AJAX. В Nette Framework 3.2 появился новый инструмент, позволяющий элегантно и четко задать такие ограничения: атрибут `#[Requires]` атрибут. + +Атрибут - это специальный маркер в PHP, который добавляется перед определением класса или метода. Так как по сути это класс, вам необходимо включить условие use, чтобы следующие примеры работали: + +```php +use Nette\Application\Attributes\Requires; +``` + +Вы можете использовать атрибут `#[Requires]` атрибут в самом классе ведущего и в этих методах: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Последние два метода также относятся к компонентам, поэтому вы можете использовать атрибут и с ними. + +Если условия, указанные в атрибуте, не выполняются, возникает ошибка HTTP 4xx. + + +Методы HTTP .[#toc-http-methods] +-------------------------------- + +Вы можете указать, какие методы HTTP (такие как GET, POST и т. д.) разрешены для доступа. Например, если вы хотите разрешить доступ только при отправке формы, установите: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Почему для действий, изменяющих состояние, следует использовать POST, а не GET, и как это сделать? [Читайте руководство |post-links]. + +Вы можете указать метод или массив методов. Особым случаем является значение `'*'` для включения всех методов, что по умолчанию не разрешается презентаторами из [соображений безопасности |application:presenters#http-method-check]. + + +Вызовы AJAX .[#toc-ajax-calls] +------------------------------ + +Если вы хотите, чтобы ведущий или метод был доступен только для AJAX-запросов, используйте: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +То же происхождение .[#toc-same-origin] +--------------------------------------- + +Для повышения безопасности можно потребовать, чтобы запрос выполнялся из одного и того же домена. Это предотвратит [уязвимость к CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Для `handle()` методов автоматически требуется доступ из того же домена. Поэтому, если вы хотите разрешить доступ из любого домена, укажите: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Доступ через Forward .[#toc-access-via-forward] +----------------------------------------------- + +Иногда полезно ограничить доступ к презентатору так, чтобы он был доступен только косвенно, например, с помощью методов `forward()` или `switch()` из другого презентатора. Так защищаются презентаторы ошибок, чтобы их нельзя было вызвать с URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +На практике часто возникает необходимость пометить определенные представления, доступ к которым возможен только на основе логики в презентере. Опять же, чтобы их нельзя было открыть напрямую: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Конкретные действия .[#toc-specific-actions] +-------------------------------------------- + +Вы также можете ограничить доступ к определенному коду, например к созданию компонента, только для определенных действий в презентере: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Для одного действия нет необходимости писать массив: `#[Requires(actions: 'default')]` + + +Пользовательские атрибуты .[#toc-custom-attributes] +--------------------------------------------------- + +Если вы хотите использовать атрибут `#[Requires]` атрибут многократно с одними и теми же настройками, вы можете создать собственный атрибут, который будет наследоваться `#[Requires]` и настроить его в соответствии с вашими потребностями. + +Например, `#[SingleAction]` разрешает доступ только через действие `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Или `#[RestMethods]` позволит получить доступ через все методы HTTP, используемые для REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Заключение .[#toc-conclusion] +----------------------------- + +Атрибут `#[Requires]` предоставляет вам большую гибкость и контроль над тем, как осуществляется доступ к вашим веб-страницам. Используя простые, но мощные правила, вы можете повысить безопасность и правильное функционирование вашего приложения. Как видите, использование атрибутов в Nette может не только упростить вашу работу, но и обезопасить ее. + +{{sitename: Best Practices}} diff --git a/best-practices/ru/composer.texy b/best-practices/ru/composer.texy index 276a92c29e..6e77697023 100644 --- a/best-practices/ru/composer.texy +++ b/best-practices/ru/composer.texy @@ -189,7 +189,7 @@ Packagist.org — глобальный репозиторий .[#toc-packagist-o Впоследствии необходимо выполнять команду `composer dumpautoload` при каждом изменении и позволять таблицам автозагрузки регенерироваться. Это крайне неудобно, и гораздо лучше доверить эту задачу [RobotLoader|robot-loader:], который выполняет ту же самую работу автоматически в фоновом режиме и гораздо быстрее. -Второй вариант — следовать [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Проще говоря, это система, в которой пространства имен и имена классов соответствуют структуре каталогов и именам файлов, т. е. `App\Router\RouterFactory` находится в файле `/path/to/App/Router/RouterFactory.php`. Пример конфигурации: +Второй вариант — следовать [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Проще говоря, это система, в которой пространства имен и имена классов соответствуют структуре каталогов и именам файлов, т. е. `App\Core\RouterFactory` находится в файле `/path/to/App/Core/RouterFactory.php`. Пример конфигурации: ```js { diff --git a/best-practices/ru/dynamic-snippets.texy b/best-practices/ru/dynamic-snippets.texy index 744b905458..2d90f071b4 100644 --- a/best-practices/ru/dynamic-snippets.texy +++ b/best-practices/ru/dynamic-snippets.texy @@ -35,7 +35,7 @@ Template: Аяксизация .[#toc-ajaxization] ============================== -Теперь давайте привнесем AJAX в это простое приложение. Изменение рейтинга статьи не настолько важно, чтобы требовать HTTP-запрос с перенаправлением, поэтому в идеале это должно быть сделано с помощью AJAX в фоновом режиме. Мы будем использовать [скрипт обработчика из дополнений |https://componette.org/vojtech-dobes/nette.ajax.js/] с обычным соглашением, что AJAX ссылки имеют CSS класс `ajax`. +Теперь давайте привнесем AJAX в это простое приложение. Изменение рейтинга статьи не настолько важно, чтобы требовать HTTP-запрос с перенаправлением, поэтому в идеале это должно быть сделано с помощью AJAX в фоновом режиме. Мы будем использовать [скрипт обработчика из дополнений |application:ajax#toc-naja] с обычным соглашением, что AJAX ссылки имеют CSS класс `ajax`. Однако как это сделать конкретно? Nette предлагает 2 способа: способ динамических фрагментов и способ компонентов. У обоих есть свои плюсы и минусы, поэтому мы покажем их по очереди. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ class LikeControl extends Nette\Application\UI\Control ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/ru/lets-create-contact-form.texy b/best-practices/ru/lets-create-contact-form.texy index f81335f4ef..bed8cf51df 100644 --- a/best-practices/ru/lets-create-contact-form.texy +++ b/best-practices/ru/lets-create-contact-form.texy @@ -39,7 +39,7 @@ class HomePresenter extends Presenter Но что, если пользователь не заполнит некоторые поля? В этом случае мы должны сообщить ему, что это обязательное поле. Мы сделали это с помощью метода `setRequired()`. Наконец, мы также добавили [событие |nette:glossary#events] `onSuccess`, которое срабатывает в случае успешной отправки формы. В нашем случае оно вызывает метод `contactFormSucceeded`, который обрабатывает отправленную форму. Мы добавим это в код через некоторое время. -Пусть компонент `contantForm` будет отображен в шаблоне `templates/Home/default.latte`: +Пусть компонент `contantForm` будет отображен в шаблоне `Home/default.latte`: ```latte {block content} diff --git a/best-practices/ru/pagination.texy b/best-practices/ru/pagination.texy index 6d384a2ebf..87693d2c25 100644 --- a/best-practices/ru/pagination.texy +++ b/best-practices/ru/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository Затем в презентере мы вводим класс модели и в методе `render` запрашиваем опубликованные статьи, которые передаем в шаблон: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -В шаблоне мы позаботимся о выводе списка статей: +Шаблон `default.latte` позаботится о перечислении статей: ```latte {block content} @@ -114,7 +113,7 @@ class ArticleRepository Мы также расширяем метод `render` для получения экземпляра Paginator, его настройки и выбора нужных статей для отображения в шаблоне. HomePresenter будет выглядеть следующим образом: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Нам не нужно создавать Paginator в презентере, вместо этого мы будем использовать метод объекта `Selection`, возвращаемый репозиторием: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/ru/post-links.texy b/best-practices/ru/post-links.texy new file mode 100644 index 0000000000..ac941e667a --- /dev/null +++ b/best-practices/ru/post-links.texy @@ -0,0 +1,59 @@ +Как правильно использовать POST-ссылки +************************************** + +В веб-приложениях, особенно в административных интерфейсах, должно быть основным правилом, что действия, изменяющие состояние сервера, не должны выполняться с помощью метода HTTP GET. Как следует из названия метода, GET должен использоваться только для получения данных, а не для их изменения. +Для таких действий, как удаление записей, целесообразнее использовать метод POST. Хотя идеальным вариантом было бы использование метода DELETE, его невозможно вызвать без JavaScript, поэтому исторически используется POST. + +Как сделать это на практике? Используйте этот простой прием. В начале вашего шаблона создайте вспомогательную форму с идентификатором `postForm`, которую вы затем будете использовать для кнопок удаления: + +```latte .{file:@layout.latte} +
+``` + +В этой форме вы можете использовать `
diff --git a/best-practices/sl/attribute-requires.texy b/best-practices/sl/attribute-requires.texy new file mode 100644 index 0000000000..9c1494eb09 --- /dev/null +++ b/best-practices/sl/attribute-requires.texy @@ -0,0 +1,179 @@ +Kako uporabljati `#[Requires]` Atribut +************************************** + +.[perex] +Pri pisanju spletne aplikacije se pogosto srečate s potrebo po omejitvi dostopa do določenih delov aplikacije. Morda želite, da lahko nekatere zahteve pošljejo podatke samo prek obrazca (torej z uporabo metode POST) ali da so dostopne samo za klice AJAX. V okolju Nette Framework 3.2 je bilo uvedeno novo orodje, ki vam omogoča elegantno in jasno določanje takšnih omejitev: orodje `#[Requires]` atribut. + +Atribut je posebna oznaka v jeziku PHP, ki jo dodate pred definicijo razreda ali metode. Ker gre v bistvu za razred, morate za delovanje naslednjih primerov vključiti klavzulo use: + +```php +use Nette\Application\Attributes\Requires; +``` + +Uporabite lahko `#[Requires]` atribut v samem razredu presenter in v teh metodah: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Tudi zadnji dve metodi se nanašata na komponente, zato lahko atribut uporabite tudi z njima. + +Če pogoji, določeni z atributom, niso izpolnjeni, se sproži napaka HTTP 4xx. + + +Metode HTTP .[#toc-http-methods] +-------------------------------- + +Določite lahko, katere metode HTTP (kot so GET, POST itd.) so dovoljene za dostop. Če želite na primer dovoliti dostop samo z oddajo obrazca, nastavite: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Zakaj za spreminjanje stanja uporabiti POST namesto GET in kako to storiti? [Preberite vodnik |post-links]. + +Določite lahko metodo ali niz metod. Poseben primer je vrednost `'*'` za omogočanje vseh metod, ki jih predstavniki zaradi [varnostnih razlogov |application:presenters#http-method-check] privzeto ne omogočajo. + + +Klici AJAX .[#toc-ajax-calls] +----------------------------- + +Če želite, da je predstavnik ali metoda dostopna samo za zahteve AJAX, uporabite: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Enako poreklo .[#toc-same-origin] +--------------------------------- + +Če želite povečati varnost, lahko zahtevate, da je zahteva poslana iz iste domene. S tem preprečite [ranljivost CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Za `handle()` je samodejno potreben dostop iz iste domene. Če torej želite dovoliti dostop iz katere koli domene, določite: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Dostop prek spletne strani Forward .[#toc-access-via-forward] +------------------------------------------------------------- + +Včasih je koristno omejiti dostop do predstavnika, tako da je na voljo le posredno, na primer z uporabo metod `forward()` ali `switch()` iz drugega predstavnika. Tako so predstavniki napak zaščiteni, da jih ni mogoče sprožiti z naslova URL: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +V praksi je pogosto treba označiti določene poglede, do katerih je mogoče dostopati le na podlagi logike v predstavitvenem programu. Spet tako, da jih ni mogoče odpreti neposredno: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Posebni ukrepi .[#toc-specific-actions] +--------------------------------------- + +Prav tako lahko omejite, da bo določena koda, na primer ustvarjanje komponente, dostopna samo za določena dejanja v predstavitvenem programu: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Za posamezno dejanje ni treba pisati polja: `#[Requires(actions: 'default')]` + + +Atributi po meri .[#toc-custom-attributes] +------------------------------------------ + +Če želite uporabiti `#[Requires]` atribut večkrat uporabiti z enakimi nastavitvami, lahko ustvarite svoj atribut, ki bo podedoval `#[Requires]` in ga nastavite v skladu s svojimi potrebami. + +Na primer, `#[SingleAction]` omogoča dostop samo prek dejanja `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Ali `#[RestMethods]` omogoči dostop prek vseh metod HTTP, ki se uporabljajo za API REST: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Zaključek .[#toc-conclusion] +---------------------------- + +Na spletni strani `#[Requires]` vam omogoča veliko prilagodljivost in nadzor nad načinom dostopa do spletnih strani. Z uporabo preprostih, a zmogljivih pravil lahko izboljšate varnost in pravilno delovanje svoje aplikacije. Kot lahko vidite, lahko z uporabo atributov v Nette ne le poenostavite svoje delo, temveč ga tudi zavarujete. + +{{sitename: Best Practices}} diff --git a/best-practices/sl/composer.texy b/best-practices/sl/composer.texy index 97a8e4ad75..6a3fa3ffdc 100644 --- a/best-practices/sl/composer.texy +++ b/best-practices/sl/composer.texy @@ -189,7 +189,7 @@ Vendar je mogoče Composer uporabiti tudi za nalaganje drugih razredov zunaj map Nato je treba ob vsaki spremembi zagnati ukaz `composer dumpautoload` in pustiti, da se tabele za samodejno nalaganje regenerirajo. To je izredno neprijetno in veliko bolje je to nalogo zaupati programu [RobotLoader |robot-loader:], ki isto dejavnost opravi samodejno v ozadju in veliko hitreje. -Druga možnost je, da sledite [priporočilu PSR-4 |https://www.php-fig.org/psr/psr-4/]. Preprosto povedano, gre za sistem, v katerem imenska območja in imena razredov ustrezajo imeniški strukturi in imenom datotek, tj. `App\Router\RouterFactory` se nahaja v datoteki `/path/to/App/Router/RouterFactory.php`. Primer konfiguracije: +Druga možnost je, da sledite [priporočilu PSR-4 |https://www.php-fig.org/psr/psr-4/]. Preprosto povedano, gre za sistem, v katerem imenska območja in imena razredov ustrezajo imeniški strukturi in imenom datotek, tj. `App\Core\RouterFactory` se nahaja v datoteki `/path/to/App/Core/RouterFactory.php`. Primer konfiguracije: ```js { diff --git a/best-practices/sl/dynamic-snippets.texy b/best-practices/sl/dynamic-snippets.texy index dcd25eb0c7..0554bab175 100644 --- a/best-practices/sl/dynamic-snippets.texy +++ b/best-practices/sl/dynamic-snippets.texy @@ -35,7 +35,7 @@ Predloga: Ajaksizacija .[#toc-ajaxization] ================================ -V to preprosto aplikacijo zdaj vnesimo AJAX. Spreminjanje ocene članka ni dovolj pomembno, da bi zahtevalo zahtevo HTTP s preusmeritvijo, zato bi bilo idealno, če bi to opravili z AJAXom v ozadju. Uporabili bomo [skript za obdelavo iz dodatkov |https://componette.org/vojtech-dobes/nette.ajax.js/] z običajno konvencijo, da imajo povezave AJAX razred CSS `ajax`. +V to preprosto aplikacijo zdaj vnesimo AJAX. Spreminjanje ocene članka ni dovolj pomembno, da bi zahtevalo zahtevo HTTP s preusmeritvijo, zato bi bilo idealno, če bi to opravili z AJAXom v ozadju. Uporabili bomo [skript za obdelavo iz dodatkov |application:ajax#toc-naja] z običajno konvencijo, da imajo povezave AJAX razred CSS `ajax`. Vendar pa, kako to storiti konkretno? Nette ponuja dva načina: način z dinamičnimi utrinki in način s komponentami. Oba načina imata svoje prednosti in slabosti, zato ju bomo prikazali enega za drugim. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Seveda bomo spremenili predlogo pogleda in morali bomo dodati tovarno za predsta ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/sl/lets-create-contact-form.texy b/best-practices/sl/lets-create-contact-form.texy index feeae86ee8..0fe2c371a9 100644 --- a/best-practices/sl/lets-create-contact-form.texy +++ b/best-practices/sl/lets-create-contact-form.texy @@ -39,7 +39,7 @@ Kot lahko vidite, smo ustvarili dve metodi. Prva metoda `createComponentContactF Kaj pa, če uporabnik ne izpolni nekaterih polj? V tem primeru mu moramo sporočiti, da gre za zahtevano polje. To smo storili z metodo `setRequired()`. Na koncu smo dodali še [dogodek |nette:glossary#events] `onSuccess`, ki se sproži, če je obrazec uspešno oddan. V našem primeru pokliče metodo `contactFormSucceeded`, ki poskrbi za obdelavo oddanega obrazca. To bomo v kodo dodali v naslednjem trenutku. -Naj bo komponenta `contantForm` prikazana v predlogi `templates/Home/default.latte`: +Naj bo komponenta `contantForm` prikazana v predlogi `Home/default.latte`: ```latte {block content} diff --git a/best-practices/sl/pagination.texy b/best-practices/sl/pagination.texy index fff9e4bf1f..f0136cebbc 100644 --- a/best-practices/sl/pagination.texy +++ b/best-practices/sl/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository V Presenter nato injiciramo razred model in v metodi render bomo zahtevali objavljene članke, ki jih posredujemo predlogi: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -V predlogi bomo poskrbeli za upodobitev seznama člankov: +Predloga `default.latte` bo nato poskrbela za seznam člankov: ```latte {block content} @@ -114,7 +113,7 @@ Naslednji korak je urejanje predstavnika. Številko trenutno prikazane strani bo Metodo upodabljanja razširimo tudi na pridobitev primerka Paginatorja, njegovo nastavitev in izbiro pravilnih člankov za prikaz v predlogi. HomePresenter bo videti takole: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Namesto tega bomo uporabili metodo predmeta `Selection`, ki ga je vrnil repozitorij: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/sl/post-links.texy b/best-practices/sl/post-links.texy new file mode 100644 index 0000000000..8c588b5864 --- /dev/null +++ b/best-practices/sl/post-links.texy @@ -0,0 +1,59 @@ +Kako pravilno uporabljati povezave POST +*************************************** + +V spletnih aplikacijah, zlasti v upravnih vmesnikih, bi moralo veljati osnovno pravilo, da se dejanja, ki spreminjajo stanje strežnika, ne smejo izvajati z metodo HTTP GET. Kot pove že ime metode, naj se GET uporablja samo za pridobivanje podatkov in ne za njihovo spreminjanje. +Za dejanja, kot je brisanje zapisov, je primerneje uporabiti metodo POST. Čeprav bi bilo idealno uporabiti metodo DELETE, je brez JavaScripta ni mogoče izvesti, zato se v preteklosti uporablja metoda POST. + +Kako to storiti v praksi? Uporabite ta preprost trik. Na začetku predloge ustvarite pomožni obrazec z identifikatorjem `postForm`, ki ga boste nato uporabili za gumbe za brisanje: + +```latte .{file:@layout.latte} +
+``` + +S tem obrazcem lahko uporabite `
diff --git a/best-practices/tr/attribute-requires.texy b/best-practices/tr/attribute-requires.texy new file mode 100644 index 0000000000..41ce8c21e6 --- /dev/null +++ b/best-practices/tr/attribute-requires.texy @@ -0,0 +1,179 @@ +Nasıl Kullanılır `#[Requires]` Öznitelik +**************************************** + +.[perex] +Bir web uygulaması yazarken, uygulamanızın belirli bölümlerine erişimi kısıtlama ihtiyacıyla sık sık karşılaşırsınız. Belki de bazı isteklerin yalnızca bir form aracılığıyla veri gönderebilmesini (dolayısıyla POST yöntemini kullanarak) veya yalnızca AJAX çağrıları tarafından erişilebilir olmasını istiyorsunuzdur. Nette Framework 3.2'de, bu tür kısıtlamaları zarif ve açık bir şekilde ayarlamanıza olanak tanıyan yeni bir araç sunulmuştur: `#[Requires]` nitelik. + +Nitelik, PHP'de bir sınıf veya yöntemin tanımından önce eklediğiniz özel bir işarettir. Esasen bir sınıf olduğu için, aşağıdaki örneklerin çalışması için use cümlesini eklemeniz gerekir: + +```php +use Nette\Application\Attributes\Requires; +``` + +Kullanabilirsiniz `#[Requires]` özniteliğinin sunum yapan sınıfın kendisiyle ve bu yöntemlerle olan ilişkisi: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Son iki yöntem de bileşenlerle ilgilidir, bu nedenle özniteliği onlarla da kullanabilirsiniz. + +Öznitelik tarafından belirtilen koşullar karşılanmazsa, bir HTTP 4xx hatası tetiklenir. + + +HTTP Yöntemleri .[#toc-http-methods] +------------------------------------ + +Erişim için hangi HTTP yöntemlerine (GET, POST, vb.) izin verileceğini belirtebilirsiniz. Örneğin, yalnızca bir form göndererek erişime izin vermek istiyorsanız, şunu ayarlayın: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Durum değiştirme eylemleri için neden GET yerine POST kullanmalısınız ve bunu nasıl yapmalısınız? [Kılavuzu okuyun |post-links]. + +Bir yöntem veya bir dizi yöntem belirtebilirsiniz. Özel bir durum, sunum yapanların [güvenlik nedeniyle |application:presenters#http-method-check] varsayılan olarak izin vermediği tüm yöntemleri etkinleştirmek için `'*'` değeridir. + + +AJAX Çağrıları .[#toc-ajax-calls] +--------------------------------- + +Bir sunucunun veya yöntemin yalnızca AJAX istekleri için erişilebilir olmasını istiyorsanız, şunu kullanın: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Aynı Köken .[#toc-same-origin] +------------------------------ + +Güvenliği artırmak için, isteğin aynı etki alanından yapılmasını zorunlu tutabilirsiniz. Bu, [CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]'ye karşı güvenlik açığını önler: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +İçin `handle()` yöntemlerinde, aynı etki alanından erişim otomatik olarak gereklidir. Bu nedenle, herhangi bir etki alanından erişime izin vermek istiyorsanız, şunu belirtin: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Forward üzerinden erişim .[#toc-access-via-forward] +--------------------------------------------------- + +Bazen bir sunucuya erişimi kısıtlamak yararlı olabilir, böylece yalnızca dolaylı olarak, örneğin başka bir sunucudan `forward()` veya `switch()` yöntemleri kullanılarak kullanılabilir. Hata sunucuları, bir URL'den tetiklenmelerini önlemek için bu şekilde korunur: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Pratikte, yalnızca sunum yapan kişideki mantığa dayalı olarak erişilebilen belirli görünümleri işaretlemek genellikle gereklidir. Yine, böylece doğrudan açılamazlar: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Spesifik Eylemler .[#toc-specific-actions] +------------------------------------------ + +Ayrıca, bir bileşen oluşturmak gibi belirli kodların yalnızca sunum aracındaki belirli eylemler için erişilebilir olmasını da kısıtlayabilirsiniz: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Tek bir eylem için bir dizi yazmaya gerek yoktur: `#[Requires(actions: 'default')]` + + +Özel Nitelikler .[#toc-custom-attributes] +----------------------------------------- + +Eğer kullanmak istiyorsanız `#[Requires]` özniteliğini aynı ayarlarla tekrar tekrar kullanmak istemiyorsanız, kendi özniteliğinizi oluşturabilirsiniz. `#[Requires]` ve ihtiyaçlarınıza göre ayarlayın. + +Örneğin, `#[SingleAction]` yalnızca `default` eylemi aracılığıyla erişime izin verir: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Ya da `#[RestMethods]` REST API için kullanılan tüm HTTP yöntemleri aracılığıyla erişime izin verecektir: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Sonuç .[#toc-conclusion] +------------------------ + +Bu `#[Requires]` özelliği, web sayfalarınıza nasıl erişileceği konusunda size büyük esneklik ve kontrol sağlar. Basit ama güçlü kurallar kullanarak uygulamanızın güvenliğini ve düzgün çalışmasını artırabilirsiniz. Gördüğünüz gibi, Nette öznitelikleri kullanmak sadece işinizi basitleştirmekle kalmaz, aynı zamanda güvenli hale de getirir. + +{{sitename: Best Practices}} diff --git a/best-practices/tr/composer.texy b/best-practices/tr/composer.texy index c16a3f0864..f16faee810 100644 --- a/best-practices/tr/composer.texy +++ b/best-practices/tr/composer.texy @@ -189,7 +189,7 @@ Ancak, Composer'ı `vendor` klasörü dışındaki diğer sınıfları yüklemek Daha sonra, her değişiklikte `composer dumpautoload` komutunu çalıştırmak ve otomatik yükleme tablolarının yeniden oluşturulmasına izin vermek gerekir. Bu son derece zahmetlidir ve bu görevi, aynı etkinliği arka planda otomatik olarak ve çok daha hızlı gerçekleştiren [RobotLoader'a |robot-loader:] emanet etmek çok daha iyidir. -İkinci seçenek [PSR-4'ü |https://www.php-fig.org/psr/psr-4/] takip etmektir. Basitçe söylemek gerekirse, ad alanlarının ve sınıf adlarının dizin yapısına ve dosya adlarına karşılık geldiği bir sistemdir, yani `App\Router\RouterFactory`, `/path/to/App/Router/RouterFactory.php` dosyasında bulunur. Yapılandırma örneği: +İkinci seçenek [PSR-4'ü |https://www.php-fig.org/psr/psr-4/] takip etmektir. Basitçe söylemek gerekirse, ad alanlarının ve sınıf adlarının dizin yapısına ve dosya adlarına karşılık geldiği bir sistemdir, yani `App\Core\RouterFactory`, `/path/to/App/Core/RouterFactory.php` dosyasında bulunur. Yapılandırma örneği: ```js { diff --git a/best-practices/tr/dynamic-snippets.texy b/best-practices/tr/dynamic-snippets.texy index bae43f06d3..30980e895e 100644 --- a/best-practices/tr/dynamic-snippets.texy +++ b/best-practices/tr/dynamic-snippets.texy @@ -35,7 +35,7 @@ public function handleUnlike(int $articleId): void Ajaxlaştırma .[#toc-ajaxization] ================================ -Şimdi bu basit uygulamaya AJAX'ı getirelim. Bir makalenin derecelendirmesini değiştirmek, yönlendirmeli bir HTTP isteği gerektirecek kadar önemli değildir, bu nedenle ideal olarak arka planda AJAX ile yapılmalıdır. AJAX bağlantılarının `ajax` CSS sınıfına sahip olduğu olağan kuralıyla [eklentilerdeki işleyici |https://componette.org/vojtech-dobes/nette.ajax.js/] komut dosyasını kullanacağız. +Şimdi bu basit uygulamaya AJAX'ı getirelim. Bir makalenin derecelendirmesini değiştirmek, yönlendirmeli bir HTTP isteği gerektirecek kadar önemli değildir, bu nedenle ideal olarak arka planda AJAX ile yapılmalıdır. AJAX bağlantılarının `ajax` CSS sınıfına sahip olduğu olağan kuralıyla [eklentilerdeki işleyici |application:ajax#toc-naja] komut dosyasını kullanacağız. Ancak, bunu özellikle nasıl yapmalı? Nette 2 yol sunuyor: dinamik snippet yolu ve bileşen yolu. Her ikisinin de artıları ve eksileri var, bu yüzden bunları tek tek göstereceğiz. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ Elbette görünüm şablonunu değiştireceğiz ve sunucuya bir fabrika eklememi ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/tr/lets-create-contact-form.texy b/best-practices/tr/lets-create-contact-form.texy index 52d307b8bf..306be16c72 100644 --- a/best-practices/tr/lets-create-contact-form.texy +++ b/best-practices/tr/lets-create-contact-form.texy @@ -39,7 +39,7 @@ Gördüğünüz gibi iki metot oluşturduk. İlk yöntem `createComponentContact Peki ya kullanıcı bazı alanları doldurmazsa? Bu durumda, ona bunun gerekli bir alan olduğunu bildirmeliyiz. Bunu `setRequired()` metodu ile yaptık. Son olarak, form başarıyla gönderildiğinde tetiklenen bir `onSuccess`[olayı |nette:glossary#events] da ekledik. Bizim durumumuzda, gönderilen formun işlenmesiyle ilgilenen `contactFormSucceeded` yöntemini çağırır. Bunu birazdan koda ekleyeceğiz. - `contantForm` bileşeninin `templates/Home/default.latte` şablonunda oluşturulmasına izin verin: + `contantForm` bileşeninin `Home/default.latte` şablonunda oluşturulmasına izin verin: ```latte {block content} diff --git a/best-practices/tr/pagination.texy b/best-practices/tr/pagination.texy index 0f0d378bfd..e6513d47d1 100644 --- a/best-practices/tr/pagination.texy +++ b/best-practices/tr/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository Presenter'da daha sonra model sınıfını enjekte edeceğiz ve render yönteminde şablona aktardığımız yayınlanmış makaleleri isteyeceğiz: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -Şablonda, bir makale listesi oluşturmaya özen göstereceğiz: +`default.latte` şablonu daha sonra makaleleri listelemekle ilgilenecektir: ```latte {block content} @@ -114,7 +113,7 @@ Bir sonraki adım sunucuyu düzenlemektir. Şu anda görüntülenen sayfanın nu Ayrıca Paginator örneğini almak, ayarlamak ve şablonda görüntülenecek doğru makaleleri seçmek için render yöntemini genişletiyoruz. HomePresenter şu şekilde görünecektir: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Presenter'da Paginator oluşturmak zorunda değiliz, bunun yerine repository tarafından döndürülen `Selection` nesnesinin yöntemini kullanacağız: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/tr/post-links.texy b/best-practices/tr/post-links.texy new file mode 100644 index 0000000000..94bef4ceec --- /dev/null +++ b/best-practices/tr/post-links.texy @@ -0,0 +1,59 @@ +POST Bağlantıları Nasıl Doğru Kullanılır? +***************************************** + +Web uygulamalarında, özellikle de yönetim arayüzlerinde, sunucunun durumunu değiştiren eylemlerin HTTP GET yöntemi ile gerçekleştirilmemesi temel bir kural olmalıdır. Metot adından da anlaşılacağı üzere GET sadece veri almak için kullanılmalıdır, değiştirmek için değil. +Kayıt silme gibi eylemler için POST yönteminin kullanılması daha uygundur. İdeal olan DELETE yöntemini kullanmak olsa da, bu JavaScript olmadan çağrılamaz, bu nedenle POST tarihsel olarak kullanılır. + +Pratikte nasıl yapılır? Bu basit numarayı kullanın. Şablonunuzun başında, daha sonra silme düğmeleri için kullanacağınız `postForm` tanımlayıcısına sahip bir yardımcı form oluşturun: + +```latte .{file:@layout.latte} +
+``` + +Bu form ile, bir `
diff --git a/best-practices/uk/attribute-requires.texy b/best-practices/uk/attribute-requires.texy new file mode 100644 index 0000000000..543ae066c2 --- /dev/null +++ b/best-practices/uk/attribute-requires.texy @@ -0,0 +1,179 @@ +Як використовувати атрибут `#[Requires]` Атрибут +************************************************ + +.[perex] +При написанні веб-додатків часто виникає потреба обмежити доступ до певних частин вашого додатку. Можливо, ви хочете, щоб деякі запити могли надсилати дані лише через форму (використовуючи метод POST) або щоб вони були доступні лише для AJAX-викликів. У Nette Framework 3.2 з'явився новий інструмент, який дозволяє елегантно і зрозуміло встановлювати такі обмеження: атрибут `#[Requires]` атрибут + +Атрибут - це спеціальний маркер в PHP, який ви додаєте перед визначенням класу або методу. Оскільки це, по суті, клас, вам потрібно включити речення use, щоб наведені нижче приклади працювали: + +```php +use Nette\Application\Attributes\Requires; +``` + +Ви можете використовувати атрибут `#[Requires]` з самим класом доповідача та з цими методами: + +- `action()` +- `render()` +- `handle()` +- `createComponent()` + +Останні два методи також стосуються компонентів, тому ви можете використовувати атрибут і з ними. + +Якщо умови, визначені атрибутом, не виконуються, генерується помилка HTTP 4xx. + + +Методи HTTP .[#toc-http-methods] +-------------------------------- + +Ви можете вказати, які HTTP-методи (наприклад, GET, POST тощо) дозволені для доступу. Наприклад, якщо ви хочете дозволити доступ тільки за допомогою відправки форми, встановіть: + +```php +class AdminPresenter extends Nette\Application\UI\Presenter +{ + #[Requires(methods: 'POST')] + public function actionDelete(int $id): void + { + } +} +``` + +Чому для зміни стану слід використовувати POST, а не GET, і як це зробити? [Читайте гайд |post-links]. + +Ви можете вказати метод або масив методів. Особливим випадком є значення `'*'`, щоб увімкнути всі методи, що не дозволяється за замовчуванням з [міркувань безпеки |application:presenters#http-method-check]. + + +AJAX-дзвінки .[#toc-ajax-calls] +------------------------------- + +Якщо ви хочете, щоб доповідач або метод був доступний лише для AJAX-запитів, використовуйте цей параметр: + +```php +#[Requires(ajax: true)] +class AjaxPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Одного походження .[#toc-same-origin] +------------------------------------- + +Щоб підвищити безпеку, ви можете вимагати, щоб запит був зроблений з того ж домену. Це запобігає [вразливості до CSRF |nette:vulnerability-protection#cross-site-request-forgery-csrf]: + +```php +#[Requires(sameOrigin: true)] +class SecurePresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Для методів `handle()` автоматично вимагається доступ з того ж домену. Тому, якщо ви хочете дозволити доступ з будь-якого домену, вкажіть це: + +```php +#[Requires(sameOrigin: false)] +public function handleList(): void +{ +} +``` + + +Доступ через Форвард .[#toc-access-via-forward] +----------------------------------------------- + +Іноді корисно обмежити доступ до презентера так, щоб він був доступний лише опосередковано, наприклад, за допомогою методів `forward()` або `switch()` з іншого презентера. Так захищаються презентери, що спричиняють помилки, щоб запобігти їхньому запуску з URL-адреси: + +```php +#[Requires(forward: true)] +class ForwardedPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +На практиці часто виникає потреба позначити певні подання, доступ до яких можна отримати лише на основі логіки в презентері. Знову ж таки, щоб їх не можна було відкрити безпосередньо: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + + public function actionDefault(int $id): void + { + $product = this->facade->getProduct($id); + if (!product) { + this->setView('notfound'); + } + } + + #[Requires(forward: true)] + public function renderNotFound(): void + { + } +} +``` + + +Конкретні дії .[#toc-specific-actions] +-------------------------------------- + +Ви також можете обмежити доступ до певного коду, наприклад, створення компонента, лише для певних дій у презентері: + +```php +class EditDeletePresenter extends Nette\Application\UI\Presenter +{ + #[Requires(actions: ['add', 'edit'])] + public function createComponentPostForm() + { + } +} +``` + +Для однієї дії не потрібно писати масив: `#[Requires(actions: 'default')]` + + +Користувацькі атрибути .[#toc-custom-attributes] +------------------------------------------------ + +Якщо ви хочете використовувати атрибут `#[Requires]` з тими самими налаштуваннями, ви можете створити власний атрибут, який успадкує `#[Requires]` і налаштувати його відповідно до ваших потреб. + +Наприклад `#[SingleAction]` дозволяє доступ тільки через дію `default`: + +```php +#[Attribute] +class SingleAction extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(actions: 'default'); + } +} + +#[SingleAction] +class SingleActionPresenter extends Nette\Application\UI\Presenter +{ +} +``` + +Або `#[RestMethods]` дозволить доступ за допомогою всіх HTTP-методів, що використовуються для REST API: + +```php +#[\Attribute] +class RestMethods extends Nette\Application\Attributes\Requires +{ + public function __construct() + { + parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']); + } +} + +#[RestMethods] +class ApiPresenter extends Nette\Application\UI\Presenter +{ +} +``` + + +Висновок .[#toc-conclusion] +--------------------------- + +Атрибут `#[Requires]` надає вам велику гнучкість і контроль над тим, як здійснюється доступ до ваших веб-сторінок. Використовуючи прості, але потужні правила, ви можете підвищити безпеку та належне функціонування вашого додатку. Як бачите, використання атрибутів у Nette може не тільки спростити вашу роботу, але й убезпечити її. + +{{sitename: Best Practices}} diff --git a/best-practices/uk/composer.texy b/best-practices/uk/composer.texy index edca818c9a..e6777c8700 100644 --- a/best-practices/uk/composer.texy +++ b/best-practices/uk/composer.texy @@ -189,7 +189,7 @@ Packagist.org - глобальний репозиторій .[#toc-packagist-org Згодом необхідно виконувати команду `composer dumpautoload` при кожній зміні та дозволяти таблицям автозавантаження регенеруватися. Це вкрай незручно, і набагато краще довірити цю задачу [RobotLoader |robot-loader:], який виконує ту ж саму роботу автоматично у фоновому режимі і набагато швидше. -Другий варіант - слідувати [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Простіше кажучи, це система, в якій простори імен та імена класів відповідають структурі каталогів та іменам файлів, тобто `App\Router\RouterFactory` знаходиться у файлі `/path/to/App/Router/RouterFactory.php`. Приклад конфігурації: +Другий варіант - слідувати [PSR-4 |https://www.php-fig.org/psr/psr-4/]. Простіше кажучи, це система, в якій простори імен та імена класів відповідають структурі каталогів та іменам файлів, тобто `App\Core\RouterFactory` знаходиться у файлі `/path/to/App/Core/RouterFactory.php`. Приклад конфігурації: ```js { diff --git a/best-practices/uk/dynamic-snippets.texy b/best-practices/uk/dynamic-snippets.texy index 40867a88cf..33872a3b0e 100644 --- a/best-practices/uk/dynamic-snippets.texy +++ b/best-practices/uk/dynamic-snippets.texy @@ -35,7 +35,7 @@ Template: Аяксизація .[#toc-ajaxization] ============================== -Тепер давайте привнесемо AJAX у цей простий додаток. Зміна рейтингу статті не настільки важлива, щоб вимагати HTTP-запит із переспрямуванням, тому в ідеалі це має бути зроблено за допомогою AJAX у фоновому режимі. Ми будемо використовувати [скрипт обробника з додат |https://componette.org/vojtech-dobes/nette.ajax.js/] ків зі звичайною угодою, що AJAX-посилання мають CSS клас `ajax`. +Тепер давайте привнесемо AJAX у цей простий додаток. Зміна рейтингу статті не настільки важлива, щоб вимагати HTTP-запит із переспрямуванням, тому в ідеалі це має бути зроблено за допомогою AJAX у фоновому режимі. Ми будемо використовувати [скрипт обробника з додат |application:ajax#toc-naja] ків зі звичайною угодою, що AJAX-посилання мають CSS клас `ajax`. Однак як це зробити конкретно? Nette пропонує 2 способи: спосіб динамічних фрагментів і спосіб компонентів. Обидва мають свої плюси та мінуси, тому ми покажемо їх по черзі. @@ -92,7 +92,7 @@ public function handleLike(int $articleId): void if ($this->isAjax()) { // ... $this->template->articles = [ - $this->connection->table('articles')->get($articleId), + $this->db->table('articles')->get($articleId), ]; } else { // ... @@ -101,7 +101,7 @@ public function handleLike(int $articleId): void public function renderDefault(): void { if (!isset($this->template->articles)) { - $this->template->articles = $this->connection->table('articles'); + $this->template->articles = $this->db->table('articles'); } } ``` @@ -151,7 +151,7 @@ class LikeControl extends Nette\Application\UI\Control ```php protected function createComponentLikeControl() { - $articles = $this->connection->table('articles'); + $articles = $this->db->table('articles'); return new Nette\Application\UI\Multiplier(function (int $articleId) use ($articles) { return new LikeControl($articles[$articleId]); }); diff --git a/best-practices/uk/lets-create-contact-form.texy b/best-practices/uk/lets-create-contact-form.texy index 724f0c58d7..e4cc290b82 100644 --- a/best-practices/uk/lets-create-contact-form.texy +++ b/best-practices/uk/lets-create-contact-form.texy @@ -39,7 +39,7 @@ class HomePresenter extends Presenter Але що, якщо користувач не заповнить деякі поля? У такому випадку ми повинні повідомити йому, що це поле є обов'язковим для заповнення. Ми зробили це за допомогою методу `setRequired()`. Нарешті, ми також додали [подію |nette:glossary#events] `onSuccess`, яка спрацьовує в разі успішного відправлення форми. У нашому випадку вона викликає метод `contactFormSucceeded`, який відповідає за обробку надісланої форми. Ми додамо його до коду за мить. -Нехай компонент `contantForm` рендериться в шаблоні `templates/Home/default.latte`: +Нехай компонент `contantForm` рендериться в шаблоні `Home/default.latte`: ```latte {block content} diff --git a/best-practices/uk/pagination.texy b/best-practices/uk/pagination.texy index 56ddd52028..c74c942c2e 100644 --- a/best-practices/uk/pagination.texy +++ b/best-practices/uk/pagination.texy @@ -11,7 +11,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -34,7 +33,7 @@ class ArticleRepository Потім у презентері ми вводимо клас моделі і в методі `render` запитуємо опубліковані статті, які передаємо в шаблон: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -53,7 +52,7 @@ class HomePresenter extends Nette\Application\UI\Presenter } ``` -У шаблоні ми подбаємо про виведення списку статей: +Після цього шаблон `default.latte` подбає про список статей: ```latte {block content} @@ -114,7 +113,7 @@ class ArticleRepository Ми також розширюємо метод `render` для отримання екземпляра Paginator, його налаштування та вибору потрібних статей для відображення в шаблоні. HomePresenter матиме такий вигляд: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; @@ -190,7 +189,6 @@ namespace App\Model; use Nette; - class ArticleRepository { public function __construct( @@ -210,7 +208,7 @@ class ArticleRepository Нам не потрібно створювати Paginator у презентері, натомість ми використовуватимемо метод об'єкта `Selection`, який повертає сховище: ```php -namespace App\Presenters; +namespace App\UI\Home; use Nette; use App\Model\ArticleRepository; diff --git a/best-practices/uk/post-links.texy b/best-practices/uk/post-links.texy new file mode 100644 index 0000000000..b0e7213fc5 --- /dev/null +++ b/best-practices/uk/post-links.texy @@ -0,0 +1,59 @@ +Як правильно використовувати POST-посилання +******************************************* + +У веб-додатках, особливо в адміністративних інтерфейсах, основним правилом має бути те, що дії, які змінюють стан сервера, не повинні виконуватися за допомогою методу HTTP GET. Як випливає з назви методу, GET слід використовувати тільки для отримання даних, а не для їх зміни. +Для таких дій, як видалення записів, доцільніше використовувати метод POST. Хоча ідеальним варіантом було б використання методу DELETE, але його неможливо викликати без JavaScript, тому історично використовується POST. + +Як це зробити на практиці? Використовуйте цей простий трюк. На початку вашого шаблону створіть допоміжну форму з ідентифікатором `postForm`, яку ви потім будете використовувати для кнопок видалення: + +```latte .{file:@layout.latte} +
+``` + +У цій формі ви можете використовувати `