diff --git a/application/bg/@left-menu.texy b/application/bg/@left-menu.texy index e19ec32175..24596b3137 100644 --- a/application/bg/@left-menu.texy +++ b/application/bg/@left-menu.texy @@ -15,5 +15,8 @@ Допълнително четене ******************* +- [Защо да използвате Nette? |www:10-reasons-why-nette] +- [Инсталация |nette:installation] +- [Създайте първото си приложение! |quickstart:] - [Най-добри практики |best-practices:] - [Отстраняване на неизправности |nette:troubleshooting] diff --git a/application/bg/ajax.texy b/application/bg/ajax.texy index 5ac48a22b8..1bcf75bc98 100644 --- a/application/bg/ajax.texy +++ b/application/bg/ajax.texy @@ -10,9 +10,13 @@ AJAX и фрагменти -Заявката AJAX може да бъде открита чрез метода на услугата, който [капсулира HTTP заявката |http:request] `$httpRequest->isAjax()` (определя се въз основа на HTTP заглавието `X-Requested-With`). Съществува и съкратен метод в програмата за представяне: `$this->isAjax()`. -Заявката AJAX не се различава от обикновената заявка - водещият се извиква с определено представяне и параметри. От водещия зависи и как ще реагира: той може да използва процедурите си, за да върне фрагмент от HTML код, XML документ, JSON обект или част от Javascript код. +Заявка AJAX .[#toc-ajax-request] +================================ + +Заявката AJAX не се различава от класическата заявка - водещият се извиква с определен изглед и параметри. От водещия също зависи как да отговори на нея: той може да използва своя собствена процедура, която връща фрагмент от HTML код (HTML snippet), XML документ, JSON обект или JavaScript код. + +От страна на сървъра AJAX заявката може да бъде открита с помощта на метода на услугата, [капсулиращ HTTP заявката |http:request] `$httpRequest->isAjax()` (открива се въз основа на HTTP заглавието `X-Requested-With`). Вътре в презентатора е наличен пряк път под формата на метода `$this->isAjax()`. Съществува предварително обработен обект `payload`, предназначен за изпращане на данни към браузъра във формат JSON. @@ -60,6 +64,21 @@ npm install naja ``` +За да създадете AJAX заявка от обикновена връзка (сигнал) или подаване на формуляр, просто маркирайте съответната връзка, формуляр или бутон с класа `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Извадки .[#toc-snippety] ======================== @@ -149,7 +168,7 @@ $this->isControlInvalid('footer'); // -> true В горния пример трябва да се уверите, че само един елемент ще бъде добавен към масива `$list`, когато бъде направена заявката AJAX, така че цикълът `foreach` ще изведе само един динамичен фрагмент. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * Этот метод возвращает данные для списка. diff --git a/application/bg/bootstrap.texy b/application/bg/bootstrap.texy index 17833a42e7..8d0b3dc5c4 100644 --- a/application/bg/bootstrap.texy +++ b/application/bg/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -Конфигурационните файлове могат да използват нормалния запис `%projectId%` за достъп до параметъра с име `projectId`. По подразбиране конфигураторът попълва следните параметри: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` и `consoleMode`. +В конфигурационните файлове можем да запишем обичайната нотация `%projectId%`, за да получим достъп до параметъра с име `projectId`. Динамични параметри .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Параметри по подразбиране .[#toc-default-parameters] +---------------------------------------------------- + +Можете да използвате следните статични параметри в конфигурационните файлове: + +- `%appDir%` е абсолютният път до директорията на файла `Bootstrap.php` +- `%wwwDir%` е абсолютният път до директорията, съдържаща входния файл `index.php` +- `%tempDir%` е абсолютният път до директорията за временни файлове +- `%vendorDir%` е абсолютният път до директорията, в която Composer инсталира библиотеки +- `%debugMode%` показва дали приложението е в режим на отстраняване на грешки +- `%consoleMode%` показва дали заявката е постъпила от командния ред + + Внесени услуги .[#toc-imported-services] ---------------------------------------- diff --git a/application/bg/components.texy b/application/bg/components.texy index e2ed9dfed6..27b0129100 100644 --- a/application/bg/components.texy +++ b/application/bg/components.texy @@ -233,31 +233,34 @@ $this->redirect(/* ... */); // пренасочване Постоянни параметри .[#toc-persistent-parameters] ================================================= -Често е необходимо да се запази даден параметър в компонент за целия период на експлоатация на компонента. Това може да бъде например номер на страница в странирането. Този параметър трябва да бъде отбелязан като постоянен с помощта на анотацията `@persistent`. +Постоянните параметри се използват за поддържане на състоянието на компонентите между различните заявки. Тяхната стойност остава същата дори след щракване върху връзката. За разлика от данните за сесията, те се прехвърлят в URL адреса. И се прехвърлят автоматично, включително връзките, създадени в други компоненти на същата страница. + +Например, имате компонент за страниране на съдържание. На една страница може да има няколко такива компонента. И искате всички компоненти да останат на текущата си страница, когато щракнете върху връзката. Затова правим номера на страницата (`page`) постоянен параметър. + +Създаването на постоянен параметър е изключително лесно в Nette. Просто създайте публично свойство и го маркирайте с атрибута: (преди се използваше `/** @persistent */` ) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // този ред е важен + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // трябва да бъдат публични } ``` -Този параметър ще се предава автоматично във всяка връзка като параметър `GET`, докато потребителят напусне страницата с този компонент. +Препоръчваме ви да включите типа данни (например `int`) към свойството, като можете да включите и стойност по подразбиране. Стойностите на параметрите могат да бъдат [валидирани |#Validation of Persistent Parameters]. -.[caution] -Никога не се доверявайте сляпо на постоянни параметри, тъй като те могат лесно да бъдат подправени (чрез презаписване на URL адреса). Проверете например дали номерът на страницата е в правилния интервал. +Можете да променяте стойността на постоянен параметър, когато създавате връзка: -В PHP 8 можете също така да използвате атрибути, за да маркирате постоянни параметри: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Или може да бъде *ресетнат*, т.е. да бъде премахнат от URL адреса. След това той ще приеме стойността си по подразбиране: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ interface PollControlFactory 1) може да се показва в шаблон 2) той знае каква част от себе си да покаже по време на [заявката AJAX |ajax#invalidation] (фрагменти) -3) може да съхранява състоянието си в URL (параметри за устойчивост). +3) има възможност да съхранява състоянието си в URL (постоянни параметри). 4) има възможност да реагира на действията (сигналите) на потребителя. 5) създава йерархична структура (където коренът е главният). @@ -403,6 +406,33 @@ Nette\ComponentModel\Component { IComponent } [* lifecycle-component.svg *] *** * Жизнен цикъл на компонента* .<> +Утвърждаване на постоянни параметри .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------------- + +Стойностите на [постоянните параметри, |#persistent parameters] получени от URL адреси, се записват в свойствата чрез метода `loadState()`. Той също така проверява дали типът данни, зададен за свойството, съвпада, в противен случай ще отговори с грешка 404 и страницата няма да бъде показана. + +Никога не се доверявайте сляпо на постоянните параметри, защото те лесно могат да бъдат пренаписани от потребителя в URL адреса. Например, така проверяваме дали номерът на страницата `$this->page` е по-голям от 0. Добър начин да направите това е да презапишете метода `loadState()`, споменат по-горе: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // тук се задава $this->page + // следва проверката на потребителската стойност: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Противоположният процес, т.е. събирането на стойности от постоянни пропъртита, се обработва от метода `saveState()`. + + Сигнали в дълбочина .[#toc-signals-in-depth] -------------------------------------------- diff --git a/application/bg/creating-links.texy b/application/bg/creating-links.texy index 6097a3719c..659326e6fe 100644 --- a/application/bg/creating-links.texy +++ b/application/bg/creating-links.texy @@ -52,7 +52,7 @@ Атрибутът `n:href` е много полезен за HTML тагове. ``. Ако искаме да покажем връзката на друго място, например в текста, използваме `{link}`: ```latte -URL: {link Homepage:default} +URL: {link Home:default} ``` @@ -88,7 +88,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Следователно основната форма е `Presenter:action`: ```latte -главная страница +главная страница ``` Ако се позоваваме на действието на текущия водещ, можем да пропуснем името му: @@ -100,7 +100,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Ако действието е `default`, можем да го пропуснем, но двоеточието трябва да остане: ```latte -главная страница +главная страница ``` Връзките могат да сочат и към други [модули |modules]. Тук връзките се разграничават на относителни към подмодули или абсолютни. Принципът е подобен на дисковите пътища, само че с двоеточия вместо с наклонени черти. Да предположим, че водещият е част от модул `Front`, тогава записваме: @@ -119,7 +119,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Можем да направим връзка към определена част от HTML страницата чрез така наречения фрагмент след символа хеш `#`: ```latte -ссылка на Homepage:default и фрагмент #main +ссылка на Home:default и фрагмент #main ``` @@ -128,7 +128,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Връзките, генерирани от `link()` или `n:href`, винаги са абсолютни пътища (т.е. започват с `/`), но не и абсолютни URL адреси с протокол и домейн, като `https://domain`. -За да създадете абсолютен URL адрес, добавете две наклонени черти в началото (например `n:href="//Homepage:"`). Или можете да превключите презентатора да генерира само абсолютни връзки, като зададете `$this->absoluteUrls = true`. +За да създадете абсолютен URL адрес, добавете две наклонени черти в началото (например `n:href="//Home:"`). Или можете да превключите презентатора да генерира само абсолютни връзки, като зададете `$this->absoluteUrls = true`. Връзка към текущата страница .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Ако искаме да направим препратка към презентаторите в шаблона на компонента, използваме тага `{plink}`: ```latte -главная страница +главная страница ``` или в кода ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/bg/how-it-works.texy b/application/bg/how-it-works.texy index b329738fcc..6f267f3b26 100644 --- a/application/bg/how-it-works.texy +++ b/application/bg/how-it-works.texy @@ -23,10 +23,10 @@ web-project/ ├── app/ ← каталог с приложением │ ├── Presenters/ ← классы презентеров -│ │ ├── HomepagePresenter.php ← Класс презентера главной страницы +│ │ ├── HomePresenter.php ← Класс презентера главной страницы │ │ └── templates/ ← директория шаблонов │ │ ├── @layout.latte ← шаблон общего макета -│ │ └── Homepage/ ← шаблоны презентера главной страницы +│ │ └── Home/ ← шаблоны презентера главной страницы │ │ └── default.latte ← шаблон действия `default` │ ├── Router/ ← конфигурация URL-адресов │ └── Bootstrap.php ← загрузочный класс Bootstrap @@ -134,10 +134,10 @@ class ProductPresenter extends Nette\Application\UI\Presenter 1) URL адресът ще `https://example.com` 2) изтегляме приложението, създаваме контейнер и стартираме `Application::run()` -3) маршрутизаторът декодира URL адреса като двойка `Homepage:default` -4) обектът е създаден `HomepagePresenter` +3) маршрутизаторът декодира URL адреса като двойка `Home:default` +4) обектът е създаден `HomePresenter` 5) извиква се методът `renderDefault()` (ако съществува) -6) шаблонът `templates/Homepage/default.latte` с оформлението `templates/@layout.latte` се визуализира +6) шаблонът `templates/Home/default.latte` с оформлението `templates/@layout.latte` се визуализира Може би сега ще се сблъскате с много нови концепции, но ние смятаме, че те имат смисъл. Създаването на приложения в Nette е лесно. diff --git a/application/bg/modules.texy b/application/bg/modules.texy index d62f08c7d5..622fd48b2e 100644 --- a/application/bg/modules.texy +++ b/application/bg/modules.texy @@ -104,7 +104,7 @@ class DashboardPresenter extends Nette\Application\UI\Presenter Определя правилата, по които името на класа се извежда от главното име. Записваме ги в [конфигурацията |configuration] под ключа `application › mapping`. -Нека започнем с пример, при който не се използват модули. Искаме само главните класове да имат пространството от имена `App\Presenters`. Това означава, че искаме главното име, например `Homepage`, да се съпостави с класа `App\Presenters\HomepagePresenter`. Това може да се постигне със следната конфигурация: +Нека започнем с пример, при който не се използват модули. Искаме само главните класове да имат пространството от имена `App\Presenters`. Това означава, че искаме главното име, например `Home`, да се съпостави с класа `App\Presenters\HomePresenter`. Това може да се постигне със следната конфигурация: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Сега водещият `Front:Homepage` е определен от класа `App\Modules\Front\HomepagePresenter`, а презентер `Admin:Dashboard` - `App\AdminModule\DashboardPresenter`. +Сега водещият `Front:Home` е определен от класа `App\Modules\Front\HomePresenter`, а презентер `Admin:Dashboard` - `App\AdminModule\DashboardPresenter`. Би било по-удобно да се създаде общо правило (звездичка), което да замени първите две правила, и да се добави допълнителна звездичка само за модула: diff --git a/application/bg/presenters.texy b/application/bg/presenters.texy index 6eddfd227a..9c09769cda 100644 --- a/application/bg/presenters.texy +++ b/application/bg/presenters.texy @@ -158,7 +158,7 @@ $url = $this->link('Product:show', [$id, 'lang' => 'en']); $this->forward('Product:show'); ``` -Пример за временно пренасочване с HTTP код 302 или 303: +Пример за т.нар. временно пренасочване с HTTP код 302 (или 303, ако текущият метод на заявка е POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ $this->redirect('Product:show', $id); $this->redirectPermanent('Product:show', $id); ``` -Можете да пренасочите към друг URL адрес извън приложението, като използвате метода `redirectUrl()`: +Можете да пренасочите към друг URL адрес извън приложението, като използвате метода `redirectUrl()`. Като втори параметър може да се посочи HTTP кодът, като по подразбиране той е 302 (или 303, ако текущият метод на заявка е POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Постоянни параметри .[#toc-persistent-parameters] ================================================= -Постоянните параметри се **предават автоматично** във връзките. Това означава, че не е необходимо да ги посочваме изрично във всеки `link()` или `n:href` в шаблона, но те все пак ще бъдат предадени. +Постоянните параметри се използват за поддържане на състоянието между различните заявки. Стойността им остава същата дори след щракване върху връзката. За разлика от данните за сесията, те се предават в URL адреса. Това става напълно автоматично, така че не е необходимо да ги посочвате изрично в `link()` или `n:href`. -Ако приложението ви има няколко езикови версии, текущият език е параметър, който винаги трябва да бъде част от URL адреса. И би било изключително досадно да го споменаваме във всяка връзка. При Nette това не е необходимо. Затова просто отбелязваме параметъра `lang` като постоянен: +Пример за използване? Имате многоезично приложение. Действителният език е параметър, който трябва да бъде част от URL адреса по всяко време. Но би било изключително досадно да го включвате във всяка връзка. Затова го правите постоянен параметър с име `lang` и той ще се пренася сам. Страхотно! + +Създаването на постоянен параметър е изключително лесно в Nette. Просто създайте публично свойство и го маркирайте с атрибута: (преди се използваше `/** @persistent */` ) ```php +use Nette\Application\Attributes\Persistent; // този ред е важен + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // трябва да бъдат публични } ``` -Ако текущата стойност на параметъра `lang` е `'en'`, тогава URL адресът, създаден с `link()` или `n:href` в шаблона, ще съдържа `lang=en`. Перфектно! - -Можем обаче да добавим и параметъра `lang` и по този начин да променим стойността му: +Ако `$this->lang` има стойност като `'en'`, то връзките, създадени с помощта на `link()` или `n:href`, ще съдържат и параметъра `lang=en`. И когато върху връзката се щракне, тя отново ще бъде `$this->lang = 'en'`. -```latte -подробности на английском -``` - -Или пък можем да го изтрием, като му зададем стойност null: - -```latte -нажмите здесь -``` +За свойствата препоръчваме да включите типа данни (например `string`), а също така можете да включите стойност по подразбиране. Стойностите на параметрите могат да бъдат [валидирани |#Validation of Persistent Parameters]. -Една постоянна променлива трябва да бъде декларирана като публична. Можем да зададем и стойност по подразбиране. Ако параметърът има същата стойност като тази по подразбиране, той няма да бъде включен в URL адреса. +Постоянните параметри се предават между всички действия на даден презентатор по подразбиране. За да ги предадете между няколко водещи, трябва да ги дефинирате или: -Постоянството отразява йерархията на класовете на презентаторите, така че параметър, дефиниран в конкретен презентатор или черта, се предава автоматично на всеки презентатор, който наследява от него или използва същата черта. - -В PHP 8 можете също така да използвате атрибути, за да маркирате постоянни параметри: +- в общ предшественик, от който презентаторите наследяват +- в чертата, която презентаторите използват: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Можете да променяте стойността на постоянен параметър, когато създавате връзка: + +```latte +detail in Czech +``` + +Или може да бъде *ресетнат*, т.е. да бъде премахнат от URL адреса. След това той ще приеме стойността си по подразбиране: + +```latte +click ``` @@ -302,7 +310,32 @@ class ProductPresenter extends Nette\Application\UI\Presenter Изисквания и параметри .[#toc-requirement-and-parameters] --------------------------------------------------------- -Заявката, която се обработва от водещия, е обект [api:Nette\Application\Request] и се връща от метода на водещия `getRequest()`. Той включва масив от параметри, всеки от които принадлежи или на някой компонент, или директно на водещия (който всъщност също е компонент, макар и специален). По този начин Nette преразпределя параметрите и ги прехвърля между отделните компоненти (и водещия) чрез извикване на метода `loadState(array $params)`, който е описан по-подробно в главата [Компоненти |components]. Параметрите могат да се извличат чрез метода `getParameters(): array`, а поотделно - чрез `getParameter($name)`. Стойностите на параметрите са низове или масиви от низове, предимно необработени данни, получени директно от URL. +Заявката, която се обработва от водещия, е обектът [api:Nette\Application\Request] и се връща от метода на водещия `getRequest()`. Тя включва масив от параметри и всеки от тях принадлежи или на някой от компонентите, или директно на водещия (който всъщност също е компонент, макар и специален). Затова Nette преразпределя параметрите и преминава между отделните компоненти (и водещия), като извиква метода `loadState(array $params)`. Параметрите могат да бъдат получени чрез метода `getParameters(): array`, а поотделно чрез `getParameter($name)`. Стойностите на параметрите са низове или масиви от низове, те по същество са необработени данни, получени директно от URL адреса. + + +Утвърждаване на постоянни параметри .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------------- + +Стойностите на [постоянните параметри, |#persistent parameters] получени от URL адреси, се записват в свойствата чрез метода `loadState()`. Той също така проверява дали типът данни, посочен в свойството, съвпада, в противен случай ще отговори с грешка 404 и страницата няма да бъде показана. + +Никога не се доверявайте сляпо на постоянните параметри, тъй като те лесно могат да бъдат пренаписани от потребителя в URL адреса. Например, така проверяваме дали `$this->lang` е сред поддържаните езици. Добър начин да направите това е да пренастроите метода `loadState()`, споменат по-горе: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // тук се задава $this->lang + // следва проверката на потребителската стойност: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Запазване и възстановяване на заявка .[#toc-save-and-restore-the-request] diff --git a/application/bg/routing.texy b/application/bg/routing.texy index 183446edbb..96e134bc79 100644 --- a/application/bg/routing.texy +++ b/application/bg/routing.texy @@ -93,12 +93,12 @@ $router->addRoute('chronicle/', 'History:show'); Разбира се, името на водещия и действието също могат да бъдат параметри. Например: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Този маршрут приема например URL адреси под формата съответно на `/article/edit` и `/catalog/list` и ги превръща в презентатори и действия съответно `Article:edit` и `Catalog:list`. -Той също така дава на `presenter` и `action` стойности по подразбиране за `Homepage` и `default` и следователно те не са задължителни. Затова маршрутът също така взема URL адреса `/article` и го превежда като `Article:default`. Или обратното, връзка към `Product:default` генерира пътя `/product`, а връзка към стандартния `Homepage:default` генерира пътя `/`. +Той също така дава на `presenter` и `action` стойности по подразбиране за `Home` и `default` и следователно те не са задължителни. Затова маршрутът също така взема URL адреса `/article` и го превежда като `Article:default`. Или обратното, връзка към `Product:default` генерира пътя `/product`, а връзка към стандартния `Home:default` генерира пътя `/`. Маската може да описва не само относителен път, базиран на корена на сайта, но и абсолютен път, ако започва с наклонена черта, или дори цял абсолютен URL адрес, ако започва с две наклонени черти: @@ -160,7 +160,7 @@ $router->addRoute('//[.]example.com//', /* ... */); ```php $router->addRoute( '[[-]/][page-]', - 'Homepage:default', + 'Home:default', ); //получени URL адреси: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Незадължителни параметри (т.е. параметри, които имат стойност по подразбиране) без квадратни скоби се държат така, сякаш са обвити по този начин: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // е равно на: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -За да промените начина, по който се генерира най-дясната наклонена черта, т.е. вместо `/homepage/` да получите `/homepage`, конфигурирайте маршрута по този начин: +За да промените начина, по който се генерира най-дясната наклонена черта, т.е. вместо `/home/` да получите `/home`, конфигурирайте маршрута по този начин: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ $router->addRoute('/[/]', [ Добра практика е изходният код да бъде написан на английски език, но какво да правите, ако URL адресът на уебсайта ви трябва да бъде преведен на друг език? ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` ще генерира английски URL адреси като `/product/123` или `/cart`. Ако искаме презентаторите и действията в URL адреса да бъдат преведени на немски език (напр. `/produkt/123` или `/einkaufswagen`), можем да използваме речник за превод. За да го добавим, вече се нуждаем от "по-ясна" версия на втория параметър: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // строка в URL => ведущий 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ $router->addRoute('//', [ use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Параметърът на конструктора `SimpleRouter` е презентаторът и действието по подразбиране, т.е. действието, което ще се изпълни, ако отворим например `http://example.com/` без никакви допълнителни параметри. ```php -// използвайте презентатора 'Homepage' и действието 'default' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// използвайте презентатора 'Home' и действието 'default' +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Препоръчваме да дефинирате SimpleRouter директно в [конфигурацията |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ class MyRouter implements Nette\Routing\Router ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/bg/templates.texy b/application/bg/templates.texy index d32c30c5ba..850d3e2ecd 100644 --- a/application/bg/templates.texy +++ b/application/bg/templates.texy @@ -42,7 +42,9 @@ Nette използва системата за шаблони [Latte |latte:]. L - `templates//.latte` - `templates/..latte` -Ако шаблонът не е намерен, отговорът ще бъде [грешка 404 |presenters#error-404-etc]. +Ако шаблонът не бъде намерен, ще се опита да търси в директорията `templates` едно ниво по-нагоре, т.е. на същото ниво като директорията с класа на водещия. + +Ако шаблонът не бъде намерен и там, отговорът ще бъде [грешка 404 |presenters#Error 404 etc.]. Можете също така да промените изгледа с помощта на `$this->setView('jineView')`. Или вместо да търсите директно, посочете името на файла с шаблона, като използвате `$this->template->setFile('/path/to/template.latte')`. @@ -148,7 +150,7 @@ public function renderDefault(): void Атрибутът `n:href` е много удобен за HTML таговете. ``. Ако искаме да посочим връзка на друго място, например в текста, използваме `{link}`: ```latte -Adresa je: {link Homepage:default} +Adresa je: {link Home:default} ``` Вижте [Създаване на URL връзки |creating-links] за повече информация. diff --git a/application/cs/@left-menu.texy b/application/cs/@left-menu.texy index 9c512de998..f7378fff34 100644 --- a/application/cs/@left-menu.texy +++ b/application/cs/@left-menu.texy @@ -15,5 +15,8 @@ Aplikace v Nette Další četba *********** +- [Proč používat Nette? |www:10-reasons-why-nette] +- [Instalace |nette:installation] +- [Píšeme první aplikaci! |quickstart:] - [Návody a postupy |best-practices:] - [Řešení problémů |nette:troubleshooting] diff --git a/application/cs/ajax.texy b/application/cs/ajax.texy index 90b0c2d742..e4d30f762a 100644 --- a/application/cs/ajax.texy +++ b/application/cs/ajax.texy @@ -10,10 +10,14 @@ Moderní webové aplikace dnes běží napůl na serveru, napůl v prohlížeči -AJAXový požadavek lze detekovat metodou služby [zapouzdřující HTTP požadavek |http:request] `$httpRequest->isAjax()` (detekuje podle HTTP hlavičky `X-Requested-With`). Uvnitř presenteru je k dispozici "zkratka" v podobě metody `$this->isAjax()`. + +AJAXový požadavek +================= AJAXový požadavek se nijak neliší od klasického požadavku - je zavolán presenter s určitým view a parametry. Je také věcí presenteru, jak bude na něj reagovat: může použít vlastní rutinu, která vrátí nějaký fragment HTML kódu (HTML snippet), XML dokument, JSON objekt nebo kód v JavaScriptu. +Na straně serveru lze AJAXový požadavek detekovat metodou služby [zapouzdřující HTTP požadavek |http:request] `$httpRequest->isAjax()` (detekuje podle HTTP hlavičky `X-Requested-With`). Uvnitř presenteru je k dispozici "zkratka" v podobě metody `$this->isAjax()`. + Pro odesílání dat prohlížeči ve formátu JSON lze využít předpřipravený objekt `payload`: ```php @@ -60,6 +64,21 @@ npm install naja ``` +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 +Go + +
+ +
+ +nebo +
+ +
+``` + Snippety ======== @@ -149,7 +168,7 @@ Dynamické snippety nelze invalidovat přímo (invalidace `item-1` neudělá vů V příkladu výše zkrátka musíte zajistit, aby při ajaxovém požadavku byla v proměnné `$list` pouze jedna položka a tedy aby ten cyklus `foreach` naplnil pouze jeden dynamický snippet: ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * Tato metoda vrací data pro seznam. diff --git a/application/cs/bootstrap.texy b/application/cs/bootstrap.texy index fdb5decb4d..55fbbfc0f5 100644 --- a/application/cs/bootstrap.texy +++ b/application/cs/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -Na parametr `projectId` se lze v konfiguraci odkázat obvyklým zápisem `%projectId%`. Třída Configurator automaticky přidává parametry `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` a `consoleMode`. +Na parametr `projectId` se lze v konfiguraci odkázat obvyklým zápisem `%projectId%`. Dynamické parametry @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Výchozí parametry +----------------- + +V konfiguračních souborech můžete využít tyto statické parametry: + +- `%appDir%` je absolutní cesta k adresáři se souborem `Bootstrap.php` +- `%wwwDir%` je absolutní cesta k adresáři se vstupním souborem `index.php` +- `%tempDir%` je absolutní cesta k adresáři pro dočasné soubory +- `%vendorDir%` je absolutní cesta k adresáři, kam Composer instaluje knihovny +- `%debugMode%` udává, zda je aplikace v debugovacím režimu +- `%consoleMode%` udává, zda request přišel přes příkazovou řádku + + Importované služby ------------------ diff --git a/application/cs/components.texy b/application/cs/components.texy index 977f44f950..d73de7ae83 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. @@ -233,31 +233,34 @@ $this->redirect(/* ... */); // a přesměrujeme Persistentní parametry ====================== -Často se stává, že je v komponentách potřeba držet nějaký parametr pro uživatele po celou dobu, kdy se s komponentou pracuje. Může to být například číslo stránky ve stránkování. Takový parametr označíme jako persistentní pomocí anotace `@persistent`. +Persistentní parametry slouží k udržování stavu v komponentách mezi různými požadavky. Jejich hodnota zůstává stejná i po kliknutí na odkaz. Na rozdíl od dat v session se přenášejí v URL. A to zcela automaticky, včetně odkazů vytvořených v jiných komponentách na téže stránce. + +Máte např. komponentu pro stránkování obsahu. Takových komponent může být na stránce několik. A přejeme si, aby po kliknutí na odkaz zůstaly všechny komponenty na své aktuální stránce. Proto z čísla stránky (`page`) uděláme persistentní parametr. + +Vytvoření persistentního parametru je v Nette nesmírně jednoduché. Stačí vytvořit veřejnou property a označit ji atributem: (dříve se používalo `/** @persistent */`) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // tento řádek je důležitý + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // musí být public } ``` -Tento parametr bude automaticky přenášen v každém odkazu jako GET parametr, a to až do chvíle, kdy uživatel stránku s touto komponentou opustí. +U property doporučujeme uvádět i datový typ (např. `int`) a můžete uvést i výchozí hodnotu. Hodnoty parametrů lze [validovat |#Validace persistentních parametrů]. -.[caution] -Nikdy slepě nevěřte persistentním parametrům, protože mohou být snadno podvrženy (přepsáním v URL adrese stránky). Ověřte si například, zda je číslo stránky v platném rozsahu. +Při vytváření odkazu lze persistentnímu parametru změnit hodnotu: -V PHP 8 můžete pro označení persistentních parametrů použít také atributy: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Nebo jej lze *vyresetovat*, tj. odstranit z URL. Pak bude nabývat svou výchozí hodnotu: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ Komponenty v Nette Application představují znovupoužitelné součásti webov 1) je vykreslitelná v šabloně 2) ví, kterou svou část má vykreslit při [AJAXovém požadavku |ajax#invalidace] (snippety) -3) má schopnost ukládat svůj stav do URL (persistetní parametry) +3) má schopnost ukládat svůj stav do URL (persistentní parametry) 4) má schopnost reagovat na uživatelské akce (signály) 5) vytváří hierarchickou strukturu (kde kořenem je presenter) @@ -403,6 +406,33 @@ Nette\ComponentModel\Component { IComponent } [* lifecycle-component.svg *] *** *Životní cyklus componenty* .<> +Validace persistentních parametrů +--------------------------------- + +Hodnoty [persistentních parametrů |#Persistentní parametry] přijatých z URL zapisuje do properties metoda `loadState()`. Ta také kontroluje, zda odpovídá datový typ uvedený u property, jinak odpoví chybou 404 a stránka se nezobrazí. + +Nikdy slepě nevěřte persistentním parametrům, protože mohou být snadno uživatelem přepsány v URL. Takto například ověříme, zda je číslo stránky `$this->page` větší než 0. Vhodnou cestou je přepsat zmíněnou metodu `loadState()`: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // zde se nastaví $this->page + // následuje vlastní kontrola hodnoty: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Opačný proces, tedy sesbírání hodnot z persistentních properites, má na starosti metoda `saveState()`. + + Signály do hloubky ------------------ @@ -414,7 +444,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. @@ -437,17 +467,6 @@ Tím je signál provedený předčasně a už se nebude znovu volat. /** @var callable[]&(callable(Component $sender): void)[]; Occurs when component is attached to presenter */ public $onAnchor; - /** - * Loads state informations. - */ - public function loadState(array $params): void - - /** - * Saves state informations for next request. - */ - public function saveState(array &$params): void - - /** * Returns destination as Link object. * @param string $destination in format "[homepage] [[[module:]presenter:]action | signal! | this] [#fragment]" diff --git a/application/cs/creating-links.texy b/application/cs/creating-links.texy index b2f3fcf88d..a21c986098 100644 --- a/application/cs/creating-links.texy +++ b/application/cs/creating-links.texy @@ -52,7 +52,7 @@ V odkazech se také automaticky předávají tzv. [persistentní parametry|prese Atribut `n:href` je velmi šikovný pro HTML značky ``. Chceme-li odkaz vypsat jinde, například v textu, použijeme `{link}`: ```latte -Adresa je: {link Homepage:default} +Adresa je: {link Home:default} ``` @@ -88,7 +88,7 @@ Formát podporují všechny značky Latte a všechny metody presenteru, které s Základním tvarem je tedy `Presenter:action`: ```latte -úvodní stránka +úvodní stránka ``` Pokud odkazujeme na akci aktuálního presenteru, můžeme jeho název vynechat: @@ -100,7 +100,7 @@ Pokud odkazujeme na akci aktuálního presenteru, můžeme jeho název vynechat: Pokud je cílem akce `default`, můžeme ji vynechat, ale dvojtečka musí zůstat: ```latte -úvodní stránka +úvodní stránka ``` Odkazy mohou také směřovat do jiných [modulů |modules]. Zde se odkazy rozlišují na relativní do zanořeného submodulu, nebo absolutní. Princip je analogický k cestám na disku, jen místo lomítek jsou dvojtečky. Předpokládejme, že aktuální presenter je součástí modulu `Front`, potom zapíšeme: @@ -119,7 +119,7 @@ Speciálním případem je odkaz [na sebe sama|#Odkaz na aktuální stránku], k Odkazovat můžeme na určitou část stránky přes tzv. fragment za znakem mřížky `#`: ```latte -odkaz na Homepage:default a fragment #main +odkaz na Home:default a fragment #main ``` @@ -128,7 +128,7 @@ Absolutní cesty Odkazy generované pomocí `link()` nebo `n:href` jsou vždy absolutní cesty (tj. začínají znakem `/`), ale nikoliv absolutní URL s protokolem a doménou jako `https://domain`. -Pro vygenerování absolutní URL přidejte na začátek dvě lomítka (např. `n:href="//Homepage:"`). Nebo lze přepnout presenter, aby generoval jen absolutní odkazy nastavením `$this->absoluteUrls = true`. +Pro vygenerování absolutní URL přidejte na začátek dvě lomítka (např. `n:href="//Home:"`). Nebo lze přepnout presenter, aby generoval jen absolutní odkazy nastavením `$this->absoluteUrls = true`. Odkaz na aktuální stránku @@ -165,7 +165,7 @@ Parametry jsou stejné jako u metody `link()`, navíc je však možné místo ko V kombinaci s `n:href` v jednom elementu se dá použít zkrácená podoba: ```latte -... +... ``` Zástupný znak `*` lze použít pouze místo akce, nikoliv presenteru. @@ -213,13 +213,13 @@ Protože [komponenty|components] jsou samostatné znovupoužitelné celky, kter Pokud bychom chtěli v šabloně komponenty odkazovat na presentery, použijeme k tomu značku `{plink}`: ```latte -úvod +úvod ``` nebo v kódu ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/cs/how-it-works.texy b/application/cs/how-it-works.texy index e696819c47..b0025a12a0 100644 --- a/application/cs/how-it-works.texy +++ b/application/cs/how-it-works.texy @@ -23,10 +23,10 @@ Adresářová struktura vypadá nějak takto: web-project/ ├── app/ ← adresář s aplikací │ ├── Presenters/ ← presentery a šablony -│ │ ├── HomepagePresenter.php ← třída presenteru Homepage +│ │ ├── HomePresenter.php ← třída presenteru Home │ │ └── templates/ ← adresář se šablonami │ │ ├── @layout.latte ← šablona layoutu -│ │ └── Homepage/ ← šablony presenteru Homepage +│ │ └── Home/ ← šablony presenteru Home │ │ └── default.latte ← šablona akce 'default' │ ├── Router/ ← konfigurace URL adres │ └── Bootstrap.php ← zaváděcí třída Bootstrap @@ -134,10 +134,10 @@ Pro jistotu, zkusme si zrekapitulovat celý proces s trošku jinou URL: 1) URL bude `https://example.com` 2) bootujeme aplikaci, vytvoří se kontejner a spustí `Application::run()` -3) router URL dekóduje jako dvojici `Homepage:default` -4) vytvoří se objekt třídy `HomepagePresenter` +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/Homepage/default.latte` s layoutem např. `templates/@layout.latte` +6) vykreslí se šablona např. `templates/Home/default.latte` s layoutem např. `templates/@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 d1158b7d2a..2cb3e42719 100644 --- a/application/cs/modules.texy +++ b/application/cs/modules.texy @@ -104,7 +104,7 @@ 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`. -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 `Homepage` mapoval na třídu `App\Presenters\HomepagePresenter`. Toho lze docílit následující konfigurací: +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í: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Nyní se presenter `Front:Homepage` mapuje na třídu `App\Modules\Front\Presenters\HomepagePresenter` a presenter `Admin:Dashboard` na třídu `App\Modules\Admin\Presenters\DashboardPresenter`. +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`. 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: diff --git a/application/cs/presenters.texy b/application/cs/presenters.texy index 4ee86fce4e..c6621449e7 100644 --- a/application/cs/presenters.texy +++ b/application/cs/presenters.texy @@ -158,7 +158,7 @@ Metoda `forward()` přejde na nový presenter okamžitě bez HTTP přesměrován $this->forward('Product:show'); ``` -Příklad tzv. dočasného přesměrování s HTTP kódem 302 nebo 303: +Příklad tzv. dočasného přesměrování s HTTP kódem 302 (nebo 303, je-li metoda aktuálního požadavku POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ Permanentní přesměrování s HTTP kódem 301 docílíte takto: $this->redirectPermanent('Product:show', $id); ``` -Na jinou URL mimo aplikaci lze přesměrovat metodou `redirectUrl()`: +Na jinou URL mimo aplikaci lze přesměrovat metodou `redirectUrl()`. Jako druhý parametr lze uvést HTTP kód, výchozí je 302 (nebo 303, je-li metoda aktuálního požadavku POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Persistentní parametry ====================== -Persistentní parametry se v odkazech **přenášejí automaticky**. To znamená, že je nemusíme explicitně uvádět v každém volání `link()` nebo `n:href` v šabloně, ale přesto se přenesou. +Persistentní parametry slouží k udržování stavu mezi různými požadavky. Jejich hodnota zůstává stejná i po kliknutí na odkaz. Na rozdíl od dat v session se přenášejí v URL. A to zcela automaticky, není tedy potřeba je explicitně uvádět v `link()` nebo `n:href`. -Pokud má vaše aplikace více jazykových mutací, tak aktuální jazyk je parameter, který musí být neustále součástí URL. A bylo by neskutečně únavné ho v každém odkazu uvádět. To není s Nette potřeba. Prostě si parametr `lang` označíme jako persistentní tímto způsobem: +Příklad použití? Máte multijazyčnou aplikaci. Aktuální jazyk je parameter, který musí být neustále součástí URL. Ale bylo by neskutečně únavné ho v každém odkazu uvádět. Tak z něj uděláte persistentní parametr `lang` a bude se přenášet sám. Paráda! + +Vytvoření persistentního parametru je v Nette nesmírně jednoduché. Stačí vytvořit veřejnou property a označit ji atributem: (dříve se používalo `/** @persistent */`) ```php +use Nette\Application\Attributes\Persistent; // tento řádek je důležitý + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // musí být public } ``` -Pokud aktuální hodnota parametru `lang` bude `'en'`, tak URL vytvořené pomocí `link()` nebo `n:href` v šabloně bude obsahovat `lang=en`. Paráda! - -Při vytváření odkazu nicméně lze persistentní parametr uvést a tím změnit jeho hodnotu: - -```latte -detail v češtině -``` +Pokud bude `$this->lang` mít hodnotu například `'en'`, tak i odkazy vytvořené pomocí `link()` nebo `n:href` budou obsahovat parameter `lang=en`. A po kliknutí na odkaz bude opět `$this->lang = 'en'`. -Nebo jej lze naopak odstranit tím, že ho vynulujeme: +U property doporučujeme uvádět i datový typ (např. `string`) a můžete uvést i výchozí hodnotu. Hodnoty parametrů lze [validovat |#Validace persistentních parametrů]. -```latte -klikni -``` +Persistentní parametry se standardně přenášejí mezi všemi akcemi daného presenteru. Aby se přenášely i mezi více presentery, je potřeba je definovat buď: -Persistentní proměnná musí být deklarovaná jako public. Můžeme uvést i výchozí hodnotu. Bude-li mít parametr hodnotu stejnou jako výchozí, nebude v URL obsažen. - -Persistence zohledňuje hierarchii tříd presenterů, tedy parametr definovaný v určitém presenteru nebo traitě je poté automaticky přenášen do každého presenteru z něj dědícího nebo užívajícího stejnou traitu. - -V PHP 8 můžete pro označení persistentních parametrů použít také atributy: +- ve společném předkovi, od kterého presentery dědí +- v traitě, kterou presentery použijí: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Při vytváření odkazu lze persistentnímu parametru změnit hodnotu: + +```latte +detail v češtině +``` + +Nebo jej lze *vyresetovat*, tj. odstranit z URL. Pak bude nabývat svou výchozí hodnotu: + +```latte +klikni ``` @@ -302,7 +310,32 @@ S tím, co jsme si dosud v této kapitole ukázali, si nejspíš úplně vystač Požadavek a parametry --------------------- -Požadavek, který vyřizuje presenter, je objekt [api:Nette\Application\Request] a vrací ho metoda presenteru `getRequest()`. Jeho součástí je pole parametrů a každý z nich patří buď některé z komponent, nebo přímo presenteru (což je vlastně také komponenta, byť speciální). Nette tedy parametry přerozdělí a předá mezi jednotlivé komponenty (a presenter) zavoláním metody `loadState(array $params)`, což dále popisujeme v kapitole [Komponenty|components]. Získat parametry lze metodu `getParameters(): array`, jednotlivě pomocí `getParameter($name)`. Hodnoty parametrů jsou řetězce či pole řetězců, jde v podstatě o surové data získané přímo z URL. +Požadavek, který vyřizuje presenter, je objekt [api:Nette\Application\Request] a vrací ho metoda presenteru `getRequest()`. Jeho součástí je pole parametrů a každý z nich patří buď některé z komponent, nebo přímo presenteru (což je vlastně také komponenta, byť speciální). Nette tedy parametry přerozdělí a předá mezi jednotlivé komponenty (a presenter) zavoláním metody `loadState(array $params)`. Získat parametry lze metodu `getParameters(): array`, jednotlivě pomocí `getParameter($name)`. Hodnoty parametrů jsou řetězce či pole řetězců, jde v podstatě o surové data získané přímo z URL. + + +Validace persistentních parametrů +--------------------------------- + +Hodnoty [persistentních parametrů |#Persistentní parametry] přijatých z URL zapisuje do properties metoda `loadState()`. Ta také kontroluje, zda odpovídá datový typ uvedený u property, jinak odpoví chybou 404 a stránka se nezobrazí. + +Nikdy slepě nevěřte persistentním parametrům, protože mohou být snadno uživatelem přepsány v URL. Takto například ověříme, zda je jazyk `$this->lang` mezi podporovanými. Vhodnou cestou je přepsat zmíněnou metodu `loadState()`: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // zde se nastaví $this->lang + // následuje vlastní kontrola hodnoty: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Uložení a obnovení požadavku diff --git a/application/cs/routing.texy b/application/cs/routing.texy index aee02d66fe..816436fac4 100644 --- a/application/cs/routing.texy +++ b/application/cs/routing.texy @@ -93,12 +93,12 @@ Routa bude nyní akceptovat i URL `https://example.com/chronicle/`, které opět Parametrem může být samozřejmě i jméno presenteru a akce. Třeba takto: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Uvedená routa akceptuje např. URL ve tvaru `/article/edit` nebo také `/catalog/list` a chápe je jako presentery a akce `Article:edit` a `Catalog:list`. -Zaroveň dává parametrům `presenter` a `action` výchozí hodnoty `Homepage` a `default` a jsou tedy také volitelné. Takže routa akceptuje i URL ve tvaru `/article` a chápe ji jako `Article:default`. Nebo obráceně, odkaz na `Product:default` vygeneruje cestu `/product`, odkaz na výchozí `Homepage:default` cestu `/`. +Zaroveň dává parametrům `presenter` a `action` výchozí hodnoty `Home` a `default` a jsou tedy také volitelné. Takže routa akceptuje i URL ve tvaru `/article` a chápe ji jako `Article:default`. Nebo obráceně, odkaz na `Product:default` vygeneruje cestu `/product`, odkaz na výchozí `Home:default` cestu `/`. Maska může popisovat nejen relativní cestu od kořenového adresáře webu, ale také absolutní cestu, pokud začíná lomítkem, nebo dokonce celé absolutní URL, začíná-li dvěma lomítky: @@ -160,7 +160,7 @@ Sekvence je možné libovolně zanořovat a kombinovat: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // Akceptuje cesty: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Volitelné parametry (tj. parametry mající výchozí hodnotu) bez hranatých závorek se chovají v podstatě tak, jako by byly uzávorkovány následujícím způsobem: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // odpovídá tomuto: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Pokud bychom chtěli ovlivnit chování koncového lomítka, aby se např. místo `/homepage/` generovalo jen `/homepage`, lze toho docílit takto: +Pokud bychom chtěli ovlivnit chování koncového lomítka, aby se např. místo `/home/` generovalo jen `/home`, lze toho docílit takto: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ Druhý parametr routy, který často zapisujeme ve formátu `Presenter:action`, ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filtry a překlady Zdrojové kódy aplikace píšeme v angličtině, ale pokud má mít web české URL, pak jednoduché routování typu: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` bude generovat anglické URL, jako třeba `/product/123` nebo `/cart`. Pokud chceme mít presentery a akce v URL reprezentované českými slovy (např. `/produkt/123` nebo `/kosik`), můžeme využít překladového slovníku. Pro jeho zápis už potřebujeme "upovídanější" variantu druhého parametru: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // řetězec v URL => presenter 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Vedle filtrů určených pro konkrétní parametry můžeme definovat též obec use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Parametrem konstruktoru SimpleRouteru je výchozí presenter & akce, na který se má směřovat, pokud otevřeme stránku bez parametrů, např. `http://example.com/`. ```php -// výchozím presenterem bude 'Homepage' a akce 'default' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// výchozím presenterem bude 'Home' a akce 'default' +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Doporučujeme SimpleRouter přímo definovat v [konfiguraci |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ Při zpracování požadavku musíme vrátit minimálně presenter a akci. Náze ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/cs/templates.texy b/application/cs/templates.texy index 075572e94e..5d0de8dc1a 100644 --- a/application/cs/templates.texy +++ b/application/cs/templates.texy @@ -42,7 +42,9 @@ Cestu k šablonám odvodí presenter podle jednoduché logiky. Zkusí, zda exist - `templates//.latte` - `templates/..latte` -Pokud šablonu nenajde, je odpovědí [chyba 404|presenters#Chyba 404 a spol.]. +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. + +Pokud ani tam šablonu nenajde, je odpovědí [chyba 404|presenters#Chyba 404 a spol.]. 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')`. @@ -148,7 +150,7 @@ V šabloně se vytvářejí odkazy na další presentery & akce tímto způsobem Atribut `n:href` je velmi šikovný pro HTML značky ``. Chceme-li odkaz vypsat jinde, například v textu, použijeme `{link}`: ```latte -Adresa je: {link Homepage:default} +Adresa je: {link Home:default} ``` Více informací najdete v kapitole [Vytváření odkazů URL|creating-links]. diff --git a/application/de/@left-menu.texy b/application/de/@left-menu.texy index 8fac50f03b..e3d59146db 100644 --- a/application/de/@left-menu.texy +++ b/application/de/@left-menu.texy @@ -15,5 +15,8 @@ Nette Bewerbung Weitere Lektüre *************** +- [Warum Nette verwenden? |www:10-reasons-why-nette] +- [Die Installation |nette:installation] +- [Erstellen Sie Ihre erste Anwendung! |quickstart:] - [Bewährte Praktiken |best-practices:] - [Fehlersuche |nette:troubleshooting] diff --git a/application/de/ajax.texy b/application/de/ajax.texy index a51e2b4011..63922c5732 100644 --- a/application/de/ajax.texy +++ b/application/de/ajax.texy @@ -10,9 +10,13 @@ Moderne Webanwendungen laufen heute zur Hälfte auf einem Server und zur Hälfte -Eine AJAX-Anfrage kann mit einer Methode eines Dienstes, [der eine HTTP-Anfrage kapselt |http:request], erkannt werden `$httpRequest->isAjax()` (erkennt anhand des `X-Requested-With` HTTP-Headers). Es gibt auch eine Kurzform der Methode in Presenter: `$this->isAjax()`. -Eine AJAX-Anfrage unterscheidet sich nicht von einer normalen Anfrage - ein Presenter wird mit einer bestimmten Ansicht und Parametern aufgerufen. Auch hier ist es dem Präsentator überlassen, wie er reagiert: Er kann seine Routinen nutzen, um entweder ein Fragment von HTML-Code (ein Snippet), ein XML-Dokument, ein JSON-Objekt oder ein Stück Javascript-Code zurückzugeben. +AJAX-Anfrage .[#toc-ajax-request] +================================= + +Eine AJAX-Anfrage unterscheidet sich nicht von einer klassischen Anfrage - der Presenter wird mit einer bestimmten Ansicht und Parametern aufgerufen. Es liegt auch am Präsentator, wie er darauf reagiert: Er kann seine eigene Routine verwenden, die ein HTML-Codefragment (HTML-Snippet), ein XML-Dokument, ein JSON-Objekt oder JavaScript-Code zurückgibt. + +Auf der Serverseite kann eine AJAX-Anfrage mit Hilfe der Servicemethode erkannt werden [, die die HTTP-Anfrage kapselt |http:request] `$httpRequest->isAjax()` (erkennt auf der Grundlage des HTTP-Headers `X-Requested-With`). Innerhalb des Presenters ist eine Abkürzung in Form der Methode `$this->isAjax()` verfügbar. Es gibt ein vorverarbeitetes Objekt namens `payload`, das für das Senden von Daten in JSON an den Browser bestimmt ist. @@ -60,6 +64,21 @@ npm install naja ``` +Um eine AJAX-Anfrage aus einem regulären Link (Signal) oder einer Formularübermittlung zu erzeugen, markieren Sie einfach den entsprechenden Link, das Formular oder die Schaltfläche mit der Klasse `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Schnipsel .[#toc-snippets] ========================== @@ -149,7 +168,7 @@ Ein dynamisches Snippet kann nicht direkt neu gezeichnet werden (das erneute Zei Im obigen Beispiel müssen Sie sicherstellen, dass bei einer AJAX-Anfrage nur ein Element zum Array `$list` hinzugefügt wird, so dass die Schleife `foreach` nur ein dynamisches Snippet ausgibt. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/de/bootstrap.texy b/application/de/bootstrap.texy index 217e1b2407..590e4e76ef 100644 --- a/application/de/bootstrap.texy +++ b/application/de/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -In Konfigurationsdateien können wir die übliche Notation `%projectId%` verwenden, um auf den Parameter mit dem Namen `projectId` zuzugreifen. Standardmäßig füllt der Configurator die folgenden Parameter aus: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` und `consoleMode`. +In Konfigurationsdateien können wir die übliche Notation `%projectId%` verwenden, um auf den Parameter `projectId` zuzugreifen. Dynamische Parameter .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Standard-Parameter .[#toc-default-parameters] +--------------------------------------------- + +Sie können die folgenden statischen Parameter in den Konfigurationsdateien verwenden: + +- `%appDir%` ist der absolute Pfad zu dem Verzeichnis, in dem sich die Datei `Bootstrap.php` befindet +- `%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 +- `%debugMode%` gibt an, ob sich die Anwendung im Debug-Modus befindet +- `%consoleMode%` zeigt an, ob die Anfrage über die Befehlszeile kam + + Importierte Dienste .[#toc-imported-services] --------------------------------------------- diff --git a/application/de/components.texy b/application/de/components.texy index a9da3e2047..4429cd3cd7 100644 --- a/application/de/components.texy +++ b/application/de/components.texy @@ -233,31 +233,34 @@ In der Vorlage stehen diese Meldungen in der Variablen `$flashes` als Objekte `s Dauerhafte Parameter .[#toc-persistent-parameters] ================================================== -Oft ist es erforderlich, einige Parameter in einer Komponente für die gesamte Zeit der Arbeit mit der Komponente zu behalten. Dies kann z.B. die Seitennummer bei der Paginierung sein. Dieser Parameter sollte mit dem Vermerk `@persistent` als persistent gekennzeichnet werden. +Persistente Parameter werden verwendet, um den Zustand von Komponenten zwischen verschiedenen Anfragen zu erhalten. Ihr Wert bleibt gleich, auch wenn ein Link angeklickt wird. Im Gegensatz zu Sitzungsdaten werden sie in der URL übertragen. Und sie werden automatisch übertragen, einschließlich Links, die in anderen Komponenten auf derselben Seite erstellt wurden. + +Sie haben zum Beispiel eine Komponente zum Paging von Inhalten. Es kann mehrere solcher Komponenten auf einer Seite geben. Und Sie möchten, dass alle Komponenten auf ihrer aktuellen Seite bleiben, wenn Sie auf den Link klicken. Deshalb machen wir die Seitennummer (`page`) zu einem dauerhaften Parameter. + +Das Erstellen eines dauerhaften Parameters ist in Nette extrem einfach. Erstellen Sie einfach eine öffentliche Eigenschaft und versehen Sie sie mit dem Attribut: (früher wurde `/** @persistent */` verwendet) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // diese Zeile ist wichtig + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // muss öffentlich sein } ``` -Dieser Parameter wird automatisch in jedem Link als `GET` Parameter übergeben, bis der Benutzer die Seite mit dieser Komponente verlässt. +Es wird empfohlen, den Datentyp (z. B. `int`) mit der Eigenschaft zu verknüpfen, und Sie können auch einen Standardwert angeben. Parameterwerte können [validiert |#Validation of Persistent Parameters] werden. -.[caution] -Vertrauen Sie persistenten Parametern niemals blind, da sie leicht gefälscht werden können (durch Überschreiben der URL). Überprüfen Sie zum Beispiel, ob die Seitenzahl innerhalb des richtigen Intervalls liegt. +Sie können den Wert eines persistenten Parameters beim Erstellen eines Links ändern: -In PHP 8 können Sie auch Attribute verwenden, um persistente Parameter zu kennzeichnen: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Oder er kann *zurückgesetzt* werden, d.h. aus der URL entfernt werden. Er nimmt dann seinen Standardwert an: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ Komponenten in einer Nette-Anwendung sind die wiederverwendbaren Teile einer Web 1) sie ist in einer Vorlage renderbar 2) sie weiß, welcher Teil von ihr bei einer [AJAX-Anfrage |ajax#invalidation] gerendert werden soll (Snippets) -3) sie hat die Möglichkeit, ihren Zustand in einer URL zu speichern (Persistenzparameter) +3) er kann seinen Zustand in einer URL speichern (persistente Parameter) 4) es hat die Fähigkeit, auf Benutzeraktionen zu reagieren (Signale) 5) er erstellt eine hierarchische Struktur (wobei die Wurzel der Präsentator ist) @@ -403,6 +406,33 @@ Lebenszyklus der Komponente .[#toc-life-cycle-of-component] [* lifecycle-component.svg *] *** *Lebenszyklus einer Komponente* .<> +Validierung von persistenten Parametern .[#toc-validation-of-persistent-parameters] +----------------------------------------------------------------------------------- + +Die Werte von [persistenten Parametern |#persistent parameters], die von URLs empfangen werden, werden von der Methode `loadState()` in Eigenschaften geschrieben. Sie prüft auch, ob der für die Eigenschaft angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt. + +Verlassen Sie sich niemals blind auf persistente Parameter, da sie leicht vom Benutzer in der URL überschrieben werden können. So prüfen wir zum Beispiel, ob die Seitenzahl `$this->page` größer als 0 ist. Eine gute Möglichkeit, dies zu tun, ist, die oben erwähnte Methode `loadState()` zu überschreiben: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // hier wird die $this->page gesetzt + // auf die Prüfung der Benutzerwerte: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Der umgekehrte Prozess, d. h. das Sammeln von Werten aus dauerhaften Propertys, wird von der Methode `saveState()` übernommen. + + Signale in der Tiefe .[#toc-signals-in-depth] --------------------------------------------- diff --git a/application/de/creating-links.texy b/application/de/creating-links.texy index ae9a95e4d6..1ec3fbd1e9 100644 --- a/application/de/creating-links.texy +++ b/application/de/creating-links.texy @@ -52,7 +52,7 @@ Die so genannten [persistenten Parameter |presenters#persistent parameters] werd Das Attribut `n:href` ist sehr praktisch für HTML-Tags ``. Wenn wir den Link an anderer Stelle, zum Beispiel im Text, ausgeben wollen, verwenden wir `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ Das Format wird von allen Latte-Tags und allen Presenter-Methoden unterstützt, Die Grundform ist also `Presenter:action`: ```latte -homepage +home ``` Wenn wir auf die Aktion des aktuellen Moderators verweisen, können wir seinen Namen weglassen: ```latte -homepage +home ``` Wenn die Aktion `default` lautet, können wir sie weglassen, aber der Doppelpunkt muss bleiben: ```latte -homepage +home ``` Links können auch auf andere [Module |modules] verweisen. Hier unterscheidet man zwischen relativen und absoluten Links zu den Untermodulen. Das Prinzip ist analog zu Plattenpfaden, nur dass anstelle von Schrägstrichen Doppelpunkte stehen. Nehmen wir an, dass der eigentliche Präsentator Teil des Moduls `Front` ist, dann schreiben wir: @@ -119,7 +119,7 @@ Ein Sonderfall ist die [Verknüpfung mit sich selbst |#Links to Current Page]. H Wir können auf einen bestimmten Teil der HTML-Seite über ein so genanntes Fragment nach dem Rautezeichen `#` verlinken: ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ Absolute Pfade .[#toc-absolute-paths] Die von `link()` oder `n:href` erzeugten Links sind immer absolute Pfade (d. h. sie beginnen mit `/`), nicht aber absolute URLs mit Protokoll und Domäne wie `https://domain`. -Um eine absolute URL zu erzeugen, fügen Sie zwei Schrägstriche am Anfang hinzu (z. B. `n:href="//Homepage:"`). Sie können den Präsentator auch so einstellen, dass er nur absolute Links erzeugt, indem Sie `$this->absoluteUrls = true` einstellen. +Um eine absolute URL zu erzeugen, fügen Sie zwei Schrägstriche am Anfang hinzu (z. B. `n:href="//Home:"`). Sie können den Präsentator auch so einstellen, dass er nur absolute Links erzeugt, indem Sie `$this->absoluteUrls = true` einstellen. Link zur aktuellen Seite .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ Da [Komponenten |components] separate, wiederverwendbare Einheiten sind, die kei Wenn wir auf Präsentatoren in der Komponentenvorlage verlinken wollen, verwenden wir das Tag `{plink}`: ```latte -homepage +home ``` oder im Code ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/de/how-it-works.texy b/application/de/how-it-works.texy index c99da75b2b..ae27a40ede 100644 --- a/application/de/how-it-works.texy +++ b/application/de/how-it-works.texy @@ -23,10 +23,10 @@ Die Verzeichnisstruktur sieht in etwa so aus: web-project/ ├── app/ ← Verzeichnis mit Anwendung │ ├── Presenters/ ← Presenter-Klassen -│ │ ├── HomepagePresenter.php ← Homepage presenterklasse +│ │ ├── HomePresenter.php ← Home presenterklasse │ │ └── templates/ ← Vorlagenverzeichnis │ │ ├── @layout.latte ← Vorlage für gemeinsames Layout -│ │ └── Homepage/ ← Vorlagen für Homepage-presenter +│ │ └── Home/ ← Vorlagen für Home-presenter │ │ └── default.latte ← Vorlage für Aktion `default` │ ├── Router/ ← Konfiguration von URL-Adressen │ └── Bootstrap.php ← bootende Klasse Bootstrap @@ -134,10 +134,10 @@ Um sicherzugehen, versuchen wir, den gesamten Prozess mit einer etwas anderen UR 1) Die URL lautet dann `https://example.com` 2) wir booten die Anwendung, erstellen einen Container und starten `Application::run()` -3) der Router dekodiert die URL als ein Paar `Homepage:default` -4) ein `HomepagePresenter` Objekt wird erstellt +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/Homepage/default.latte` mit einem Layout `templates/@layout.latte` wird gerendert +6) eine Vorlage `templates/Home/default.latte` mit einem Layout `templates/@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 6481f57995..fcdf4331c4 100644 --- a/application/de/modules.texy +++ b/application/de/modules.texy @@ -104,7 +104,7 @@ Abbildung .[#toc-mapping] Legt die Regeln fest, nach denen der Klassenname aus dem Namen des Präsentators abgeleitet wird. Wir schreiben sie in die [Konfiguration |configuration] unter dem Schlüssel `application › mapping`. -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 `Homepage` auf die Klasse `App\Presenters\HomepagePresenter` abgebildet werden soll. Dies kann durch die folgende Konfiguration erreicht werden: +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: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Der Referent `Front:Homepage` wird der Klasse `App\Modules\Front\Presenters\HomepagePresenter` zugeordnet und der Referent `Admin:Dashboard` der Klasse `App\Modules\Admin\Presenters\DashboardPresenter`. +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`. 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: diff --git a/application/de/presenters.texy b/application/de/presenters.texy index 10ea87fbee..3e5c98a43d 100644 --- a/application/de/presenters.texy +++ b/application/de/presenters.texy @@ -158,7 +158,7 @@ Die `forward()` schaltet ohne HTTP-Umleitung sofort auf den neuen Präsentator u $this->forward('Product:show'); ``` -Beispiel einer temporären Umleitung mit HTTP-Code 302 oder 303: +Beispiel für eine so genannte temporäre Umleitung mit HTTP-Code 302 (oder 303, wenn die aktuelle Anfragemethode POST ist): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ Um eine dauerhafte Umleitung mit HTTP-Code 301 zu erreichen, verwenden Sie: $this->redirectPermanent('Product:show', $id); ``` -Sie können mit der Methode `redirectUrl()` zu einer anderen URL außerhalb der Anwendung umleiten: +Sie können mit der Methode `redirectUrl()` zu einer anderen URL außerhalb der Anwendung umleiten. Der HTTP-Code kann als zweiter Parameter angegeben werden, wobei der Standardwert 302 ist (oder 303, wenn die aktuelle Anforderungsmethode POST ist): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Dauerhafte Parameter .[#toc-persistent-parameters] ================================================== -Persistente Parameter werden in Links **automatisch** übertragen. Das bedeutet, dass wir sie nicht explizit in jedem `link()` oder `n:href` in der Vorlage angeben müssen, aber sie werden trotzdem übertragen. +Persistente Parameter werden verwendet, um den Zustand zwischen verschiedenen Anfragen zu erhalten. Ihr Wert bleibt gleich, auch wenn ein Link angeklickt wird. Im Gegensatz zu Sitzungsdaten werden sie in der URL übergeben. Dies geschieht völlig automatisch, so dass es nicht notwendig ist, sie in `link()` oder `n:href` explizit anzugeben. -Wenn Ihre Anwendung mehrere Sprachversionen hat, dann ist die aktuelle Sprache ein Parameter, der immer Teil der URL sein muss. Und es wäre unglaublich mühsam, ihn in jedem Link zu erwähnen. Mit Nette ist das nicht nötig. Wir kennzeichnen den Parameter `lang` auf diese Weise einfach als persistent: +Beispiel für die Verwendung? Sie haben eine mehrsprachige Anwendung. Die aktuelle Sprache ist ein Parameter, der immer Teil der URL sein muss. Es wäre aber unglaublich mühsam, ihn in jeden Link aufzunehmen. Also machen Sie ihn zu einem dauerhaften Parameter mit dem Namen `lang` und er wird sich selbst tragen. Toll! + +Das Erstellen eines dauerhaften Parameters ist in Nette extrem einfach. Erstellen Sie einfach eine öffentliche Eigenschaft und kennzeichnen Sie sie mit dem Attribut: (früher wurde `/** @persistent */` verwendet) ```php +use Nette\Application\Attributes\Persistent; // diese Zeile ist wichtig + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // muss öffentlich sein } ``` -Wenn der aktuelle Wert des Parameters `lang` ist `'en'`, dann wird die URL, die mit `link()` oder `n:href` in der Vorlage erstellt wird, `lang=en` enthalten. Prima! - -Wir können jedoch auch den Parameter `lang` hinzufügen und damit seinen Wert ändern: +Wenn `$this->lang` einen Wert wie `'en'` hat, dann werden Links, die mit `link()` oder `n:href` erstellt werden, auch den Parameter `lang=en` enthalten. Und wenn der Link angeklickt wird, wird er wieder `$this->lang = 'en'` sein. -```latte -detail in English -``` - -Umgekehrt kann er entfernt werden, indem er auf null gesetzt wird: - -```latte -click here -``` +Für Eigenschaften wird empfohlen, den Datentyp anzugeben (z. B. `string`), und Sie können auch einen Standardwert angeben. Parameterwerte können [validiert |#Validation of Persistent Parameters] werden. -Die persistente Variable muss als öffentlich deklariert werden. Wir können auch einen Standardwert angeben. Wenn der Parameter den gleichen Wert wie der Standardwert hat, wird er nicht in die URL aufgenommen. +Persistente Parameter werden standardmäßig zwischen allen Aktionen eines bestimmten Präsentators weitergegeben. Um sie zwischen mehreren Präsentatoren zu übergeben, müssen Sie sie entweder definieren: -Die Persistenz spiegelt die Hierarchie der Presenter-Klassen wider, d. h. ein in einem bestimmten Presenter oder Trait definierter Parameter wird automatisch auf jeden Presenter übertragen, der von diesem erbt oder denselben Trait verwendet. - -In PHP 8 können Sie auch Attribute verwenden, um persistente Parameter zu kennzeichnen: +- in einem gemeinsamen Vorfahren, von dem die Präsentatoren erben +- in der Eigenschaft, die die Präsentatoren verwenden: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Sie können den Wert eines dauerhaften Parameters ändern, wenn Sie einen Link erstellen: + +```latte +detail in Czech +``` + +Oder er kann *zurückgesetzt* werden, d.h. aus der URL entfernt werden. Er nimmt dann seinen Standardwert an: + +```latte +click ``` @@ -302,7 +310,32 @@ Was wir bisher in diesem Kapitel gezeigt haben, wird wahrscheinlich ausreichen. Anforderung und Parameter .[#toc-requirement-and-parameters] ------------------------------------------------------------ -Die vom Präsentator bearbeitete Anforderung ist das Objekt [api:Nette\Application\Request] und wird von der Methode `getRequest()` des Präsentators zurückgegeben. Es enthält ein Array von Parametern, und jeder von ihnen gehört entweder zu einer der Komponenten oder direkt zum Präsentator (der eigentlich auch eine Komponente ist, wenn auch eine spezielle). Nette verteilt also die Parameter um und übergibt sie zwischen den einzelnen Komponenten (und dem Presenter), indem es die Methode `loadState(array $params)` aufruft, die im Kapitel [Komponenten |Components] näher beschrieben wird. Die Parameter können über die Methode `getParameters(): array`, individuell über `getParameter($name)` bezogen werden. Bei den Parameterwerten handelt es sich um Strings oder Arrays von Strings, d. h. um Rohdaten, die direkt aus einer URL bezogen werden. +Die vom Präsentator bearbeitete Anfrage ist das Objekt [api:Nette\Application\Request] und wird von der Methode `getRequest()` des Präsentators zurückgegeben. Sie enthält ein Array von Parametern, und jeder von ihnen gehört entweder zu einer der Komponenten oder direkt zum Präsentator (der eigentlich auch eine Komponente ist, wenn auch eine spezielle). Nette verteilt also die Parameter um und übergibt sie zwischen den einzelnen Komponenten (und dem Präsentator) durch Aufruf der Methode `loadState(array $params)`. Die Parameter können mit der Methode `getParameters(): array`, einzeln mit `getParameter($name)` abgerufen werden. Bei den Parameterwerten handelt es sich um Strings oder Arrays von Strings, also im Grunde um Rohdaten, die direkt aus einer URL bezogen werden. + + +Validierung von persistenten Parametern .[#toc-validation-of-persistent-parameters] +----------------------------------------------------------------------------------- + +Die Werte von [persistenten Parametern |#persistent parameters], die von URLs empfangen werden, werden von der Methode `loadState()` in Eigenschaften geschrieben. Sie prüft auch, ob der in der Eigenschaft angegebene Datentyp übereinstimmt, andernfalls antwortet sie mit einem 404-Fehler und die Seite wird nicht angezeigt. + +Verlassen Sie sich niemals blind auf persistente Parameter, da sie leicht vom Benutzer in der URL überschrieben werden können. So überprüfen wir zum Beispiel, ob `$this->lang` zu den unterstützten Sprachen gehört. Eine gute Möglichkeit, dies zu tun, besteht darin, die oben erwähnte Methode `loadState()` zu überschreiben: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // hier wird die $this->lang gesetzt + // nach der Prüfung der Benutzerwerte: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Speichern und Wiederherstellen der Anfrage .[#toc-save-and-restore-the-request] diff --git a/application/de/routing.texy b/application/de/routing.texy index 438c8ff3c0..1cd5fcc40d 100644 --- a/application/de/routing.texy +++ b/application/de/routing.texy @@ -93,12 +93,12 @@ Die Route wird nun die URL `https://any-domain.com/chronicle/` mit dem Parameter Natürlich kann auch der Name des Präsentators und der Aktion ein Parameter sein. Zum Beispiel: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Diese Route nimmt z.B. eine URL in der Form `/article/edit` bzw. `/catalog/list` entgegen und übersetzt sie in Präsentatoren und Aktionen `Article:edit` bzw. `Catalog:list`. -Außerdem werden den Parametern `presenter` und `action` die Standardwerte`Homepage` und `default` zugewiesen, so dass sie optional sind. So akzeptiert die Route auch eine URL `/article` und übersetzt sie als `Article:default`. Oder umgekehrt, ein Link auf `Product:default` erzeugt einen Pfad `/product`, ein Link auf den Standardwert `Homepage:default` erzeugt einen Pfad `/`. +Außerdem werden den Parametern `presenter` und `action` die Standardwerte`Home` und `default` zugewiesen, so dass sie optional sind. So akzeptiert die Route auch eine URL `/article` und übersetzt sie als `Article:default`. Oder umgekehrt, ein Link auf `Product:default` erzeugt einen Pfad `/product`, ein Link auf den Standardwert `Home:default` erzeugt einen Pfad `/`. Die Maske kann nicht nur den relativen Pfad auf der Grundlage des Stammverzeichnisses der Website beschreiben, sondern auch den absoluten Pfad, wenn er mit einem Schrägstrich beginnt, oder sogar die gesamte absolute URL, wenn sie mit zwei Schrägstrichen beginnt: @@ -160,7 +160,7 @@ Sequenzen können frei verschachtelt und kombiniert werden: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // Akzeptierte URLs: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Optionale Parameter (d.h. Parameter mit Standardwerten) ohne eckige Klammern verhalten sich so, als wären sie so umbrochen: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // gleichbedeutend mit: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Um zu ändern, wie der Schrägstrich ganz rechts erzeugt wird, d. h. statt `/homepage/` ein `/homepage` zu erhalten, passen Sie die Route folgendermaßen an: +Um zu ändern, wie der Schrägstrich ganz rechts erzeugt wird, d. h. statt `/home/` ein `/home` zu erhalten, passen Sie die Route folgendermaßen an: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ Der zweite Parameter der Route, den wir oft im Format `Presenter:action` schreib ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filter und Übersetzungen .[#toc-filters-and-translations] Es ist eine gute Praxis, den Quellcode auf Englisch zu schreiben, aber was ist, wenn die URL Ihrer Website in eine andere Sprache übersetzt werden soll? Einfache Routen wie z.B.: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` erzeugen englische URLs, wie `/product/123` oder `/cart`. Wenn wir Präsentatoren und Aktionen in der URL ins Deutsche übersetzt haben wollen (z. B. `/produkt/123` oder `/einkaufswagen`), können wir ein Übersetzungswörterbuch verwenden. Um es hinzuzufügen, benötigen wir bereits eine "gesprächigere" Variante des zweiten Parameters: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // String in URL => Präsentator 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Neben Filtern für bestimmte Parameter können Sie auch allgemeine Filter defini use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Der Parameter des `SimpleRouter` -Konstruktors ist ein Standard-Präsentator und eine Aktion, d.h. eine Aktion, die ausgeführt wird, wenn wir z.B. `http://example.com/` ohne zusätzliche Parameter öffnen. ```php -// Standardmäßig wird der Präsentator 'Homepage' und die Aktion 'default' verwendet -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// Standardmäßig wird der Präsentator 'Home' und die Aktion 'default' verwendet +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Wir empfehlen, SimpleRouter direkt in der [Konfiguration |dependency-injection:services] zu definieren: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ Bei der Verarbeitung der Anfrage müssen wir zumindest den Präsentator und die ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/de/templates.texy b/application/de/templates.texy index 65639b369c..a21bd7266f 100644 --- a/application/de/templates.texy +++ b/application/de/templates.texy @@ -42,7 +42,9 @@ Der Pfad zu den Vorlagen wird nach einer einfachen Logik hergeleitet. Es wird ve - `templates//.latte` - `templates/..latte` -Wenn die Vorlage nicht gefunden wird, wird der [Fehler 404 |presenters#Error 404 etc.] ausgegeben. +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. + +Wenn die Vorlage auch dort nicht gefunden wird, ist die Antwort ein [404-Fehler |presenters#Error 404 etc.]. 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. @@ -148,7 +150,7 @@ In der Vorlage erstellen wir Links zu anderen Präsentatoren und Aktionen wie fo Das Attribut `n:href` ist sehr praktisch für HTML-Tags ``. Wenn wir den Link an anderer Stelle, zum Beispiel im Text, ausgeben wollen, verwenden wir `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` Weitere Informationen finden Sie unter [Links erstellen |Creating Links]. diff --git a/application/el/@left-menu.texy b/application/el/@left-menu.texy index 79024816ce..b704f8e06d 100644 --- a/application/el/@left-menu.texy +++ b/application/el/@left-menu.texy @@ -15,5 +15,8 @@ Περαιτέρω ανάγνωση ****************** +- [Γιατί να χρησιμοποιήσετε τη Nette; |www:10-reasons-why-nette] +- [Εγκατάσταση |nette:installation] +- [Δημιουργήστε την πρώτη σας εφαρμογή! |quickstart:] - [Βέλτιστες πρακτικές |best-practices:] - [Αντιμετώπιση προβλημάτων |nette:troubleshooting] diff --git a/application/el/ajax.texy b/application/el/ajax.texy index aa08c15d07..8ca0741a48 100644 --- a/application/el/ajax.texy +++ b/application/el/ajax.texy @@ -10,9 +10,13 @@ AJAX & Snippets -Μια αίτηση AJAX μπορεί να ανιχνευθεί με τη χρήση μιας μεθόδου μιας υπηρεσίας που [ενθυλακώνει μια αίτηση HTTP |http:request] `$httpRequest->isAjax()` (ανιχνεύει με βάση την επικεφαλίδα `X-Requested-With` HTTP). Υπάρχει επίσης μια σύντομη μέθοδος στο presenter: `$this->isAjax()`. -Μια αίτηση AJAX δεν διαφέρει από μια κανονική αίτηση - καλείται ένας παρουσιαστής με μια συγκεκριμένη προβολή και παραμέτρους. Εξαρτάται, επίσης, από τον παρουσιαστή πώς θα αντιδράσει: μπορεί να χρησιμοποιήσει τις ρουτίνες του για να επιστρέψει είτε ένα τμήμα κώδικα HTML (ένα απόσπασμα), ένα έγγραφο XML, ένα αντικείμενο JSON ή ένα κομμάτι κώδικα Javascript. +Αίτηση AJAX .[#toc-ajax-request] +================================ + +Ένα αίτημα AJAX δεν διαφέρει από ένα κλασικό αίτημα - ο παρουσιαστής καλείται με μια συγκεκριμένη προβολή και παραμέτρους. Εξαρτάται επίσης από τον παρουσιαστή πώς θα απαντήσει σε αυτό: μπορεί να χρησιμοποιήσει τη δική του ρουτίνα, η οποία επιστρέφει ένα τμήμα κώδικα HTML (απόσπασμα HTML), ένα έγγραφο XML, ένα αντικείμενο JSON ή κώδικα JavaScript. + +Από την πλευρά του διακομιστή, ένα αίτημα AJAX μπορεί να ανιχνευθεί χρησιμοποιώντας τη μέθοδο service που [ενθυλακώνει το αίτημα HTTP |http:request] `$httpRequest->isAjax()` (ανιχνεύει με βάση την επικεφαλίδα HTTP `X-Requested-With`). Στο εσωτερικό του παρουσιαστή, είναι διαθέσιμη μια συντόμευση με τη μορφή της μεθόδου `$this->isAjax()`. Υπάρχει ένα προεπεξεργασμένο αντικείμενο που ονομάζεται `payload` και είναι αφιερωμένο στην αποστολή δεδομένων στο πρόγραμμα περιήγησης σε JSON. @@ -60,6 +64,21 @@ npm install naja ``` +Για να δημιουργήσετε μια αίτηση AJAX από έναν κανονικό σύνδεσμο (σήμα) ή μια υποβολή φόρμας, απλά επισημάνετε τον σχετικό σύνδεσμο, τη φόρμα ή το κουμπί με την κλάση `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Snippets .[#toc-snippets] ========================= @@ -149,7 +168,7 @@ $this->isControlInvalid('footer'); // -> true Στο παραπάνω παράδειγμα πρέπει να βεβαιωθείτε ότι για μια αίτηση AJAX θα προστεθεί μόνο ένα στοιχείο στον πίνακα `$list`, επομένως ο βρόχος `foreach` θα εκτυπώσει μόνο ένα δυναμικό απόσπασμα. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/el/bootstrap.texy b/application/el/bootstrap.texy index 830a951dd6..71f6028048 100644 --- a/application/el/bootstrap.texy +++ b/application/el/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -Στα αρχεία ρυθμίσεων, μπορούμε να γράψουμε τον συνήθη συμβολισμό `%projectId%` για να αποκτήσουμε πρόσβαση στην παράμετρο με το όνομα `projectId`. Από προεπιλογή, ο διαμορφωτής συμπληρώνει τις ακόλουθες παραμέτρους: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` και `consoleMode`. +Στα αρχεία ρυθμίσεων, μπορούμε να γράψουμε τον συνήθη συμβολισμό `%projectId%` για να αποκτήσουμε πρόσβαση στην παράμετρο με το όνομα `projectId`. Δυναμικές παράμετροι .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Προεπιλεγμένες παράμετροι .[#toc-default-parameters] +---------------------------------------------------- + +Μπορείτε να χρησιμοποιήσετε τις ακόλουθες στατικές παραμέτρους στα αρχεία διαμόρφωσης: + +- `%appDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο του αρχείου `Bootstrap.php` +- `%wwwDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο που περιέχει το αρχείο καταχώρησης `index.php` +- `%tempDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο για τα προσωρινά αρχεία +- `%vendorDir%` είναι η απόλυτη διαδρομή προς τον κατάλογο όπου ο Composer εγκαθιστά τις βιβλιοθήκες +- Το `%debugMode%` δηλώνει αν η εφαρμογή βρίσκεται σε κατάσταση αποσφαλμάτωσης. +- Το `%consoleMode%` δηλώνει αν η αίτηση υποβλήθηκε μέσω της γραμμής εντολών. + + Εισαγόμενες υπηρεσίες .[#toc-imported-services] ----------------------------------------------- diff --git a/application/el/components.texy b/application/el/components.texy index e9658ada5f..7f4088b591 100644 --- a/application/el/components.texy +++ b/application/el/components.texy @@ -233,31 +233,34 @@ $this->redirect(/* ... */); // και ανακατεύθυνση Μόνιμες παράμετροι .[#toc-persistent-parameters] ================================================ -Συχνά χρειάζεται να διατηρείται κάποια παράμετρος σε ένα στοιχείο για όλη τη διάρκεια της εργασίας με το στοιχείο. Μπορεί να είναι για παράδειγμα ο αριθμός της σελίδας στην σελιδοποίηση. Αυτή η παράμετρος θα πρέπει να χαρακτηρίζεται ως μόνιμη χρησιμοποιώντας το σχόλιο `@persistent`. +Οι μόνιμες παράμετροι χρησιμοποιούνται για τη διατήρηση της κατάστασης των στοιχείων μεταξύ διαφορετικών αιτήσεων. Η τιμή τους παραμένει η ίδια ακόμη και μετά το κλικ σε έναν σύνδεσμο. Σε αντίθεση με τα δεδομένα συνόδου, μεταφέρονται στη διεύθυνση URL. Και μεταφέρονται αυτόματα, συμπεριλαμβανομένων των συνδέσμων που δημιουργούνται σε άλλα στοιχεία στην ίδια σελίδα. + +Για παράδειγμα, έχετε ένα στοιχείο σελιδοποίησης περιεχομένου. Μπορεί να υπάρχουν πολλά τέτοια στοιχεία σε μια σελίδα. Και θέλετε όλα τα συστατικά να παραμένουν στην τρέχουσα σελίδα τους όταν κάνετε κλικ στο σύνδεσμο. Επομένως, κάνουμε τον αριθμό σελίδας (`page`) μια μόνιμη παράμετρο. + +Η δημιουργία μιας μόνιμης παραμέτρου είναι εξαιρετικά εύκολη στη Nette. Απλά δημιουργήστε μια δημόσια ιδιότητα και επισημάνετέ την με το χαρακτηριστικό: (προηγουμένως χρησιμοποιούνταν το `/** @persistent */` ) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // αυτή η γραμμή είναι σημαντική + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // πρέπει να είναι δημόσια } ``` -Αυτή η παράμετρος θα περνάει αυτόματα σε κάθε σύνδεσμο ως παράμετρος `GET` έως ότου ο χρήστης εγκαταλείψει τη σελίδα με αυτό το στοιχείο. +Συνιστούμε να συμπεριλάβετε τον τύπο δεδομένων (π.χ. `int`) μαζί με την ιδιότητα και μπορείτε επίσης να συμπεριλάβετε μια προεπιλεγμένη τιμή. Οι τιμές των παραμέτρων μπορούν να [επικυρωθούν |#Validation of Persistent Parameters]. -.[caution] -Ποτέ μην εμπιστεύεστε τυφλά τις μόνιμες παραμέτρους, επειδή μπορούν να παραποιηθούν εύκολα (με αντικατάσταση της διεύθυνσης URL). Επαληθεύστε, για παράδειγμα, αν ο αριθμός της σελίδας είναι εντός του σωστού διαστήματος. +Μπορείτε να αλλάξετε την τιμή μιας μόνιμης παραμέτρου κατά τη δημιουργία ενός συνδέσμου: -Στην PHP 8, μπορείτε επίσης να χρησιμοποιήσετε χαρακτηριστικά για να επισημάνετε μόνιμες παραμέτρους: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Ή μπορεί να *επαναρυθμιστεί*, δηλαδή να αφαιρεθεί από τη διεύθυνση URL. Τότε θα πάρει την προεπιλεγμένη τιμή της: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ interface PollControlFactory 1) είναι δυνατό να αποδοθεί σε ένα πρότυπο 2) γνωρίζει ποιο μέρος του εαυτού του να αποδώσει κατά τη διάρκεια μιας [αίτησης AJAX |ajax#invalidation] (αποσπάσματα) -3) έχει τη δυνατότητα να αποθηκεύει την κατάστασή του σε μια διεύθυνση URL (παράμετροι επιμονής) +3) έχει τη δυνατότητα να αποθηκεύει την κατάστασή του σε μια διεύθυνση URL (μόνιμες παράμετροι) 4) έχει τη δυνατότητα να ανταποκρίνεται σε ενέργειες του χρήστη (σήματα) 5) δημιουργεί μια ιεραρχική δομή (όπου η ρίζα είναι ο παρουσιαστής) @@ -403,6 +406,33 @@ Nette\ComponentModel\Component { IComponent } [* lifecycle-component.svg *] *** *Κύκλος ζωής του συστατικού* .<> +Επικύρωση μόνιμων παραμέτρων .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------ + +Οι τιμές των [μόνιμων παραμέτρων |#persistent parameters] που λαμβάνονται από τις διευθύνσεις URL εγγράφονται στις ιδιότητες με τη μέθοδο `loadState()`. Ελέγχει επίσης αν ο τύπος δεδομένων που έχει καθοριστεί για την ιδιότητα ταιριάζει, διαφορετικά θα απαντήσει με σφάλμα 404 και η σελίδα δεν θα εμφανιστεί. + +Ποτέ μην εμπιστεύεστε τυφλά τις μόνιμες παραμέτρους, επειδή μπορούν εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση URL. Για παράδειγμα, με αυτόν τον τρόπο ελέγχουμε αν ο αριθμός σελίδας `$this->page` είναι μεγαλύτερος από 0. Ένας καλός τρόπος για να το κάνετε αυτό είναι να παρακάμψετε τη μέθοδο `loadState()` που αναφέρθηκε παραπάνω: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // εδώ ορίζεται η σελίδα $this->page + // ακολουθεί τον έλεγχο της τιμής του χρήστη: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Η αντίθετη διαδικασία, δηλαδή η συλλογή τιμών από persistent properites, αντιμετωπίζεται από τη μέθοδο `saveState()`. + + Σήματα σε βάθος .[#toc-signals-in-depth] ---------------------------------------- diff --git a/application/el/creating-links.texy b/application/el/creating-links.texy index 5e914b2863..e36d982225 100644 --- a/application/el/creating-links.texy +++ b/application/el/creating-links.texy @@ -52,7 +52,7 @@ Το χαρακτηριστικό `n:href` είναι πολύ χρήσιμο για τις ετικέτες HTML ``. Αν θέλουμε να εκτυπώσουμε τον σύνδεσμο αλλού, για παράδειγμα στο κείμενο, χρησιμοποιούμε το `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Η βασική μορφή είναι επομένως `Presenter:action`: ```latte -homepage +home ``` Αν συνδεθούμε με τη δράση του τρέχοντος παρουσιαστή, μπορούμε να παραλείψουμε το όνομά του: ```latte -homepage +home ``` Αν η ενέργεια είναι `default`, μπορούμε να την παραλείψουμε, αλλά η άνω και κάτω τελεία πρέπει να παραμείνει: ```latte -homepage +home ``` Οι σύνδεσμοι μπορούν επίσης να παραπέμπουν σε άλλες [ενότητες |modules]. Εδώ, οι σύνδεσμοι διακρίνονται σε σχετικούς με τις υποενότητες ή απόλυτους. Η αρχή είναι ανάλογη με τις διαδρομές δίσκου, μόνο που αντί για κάθετους υπάρχουν άνω και κάτω τελεία. Ας υποθέσουμε ότι ο πραγματικός παρουσιαστής είναι μέρος της ενότητας `Front`, τότε θα γράψουμε: @@ -119,7 +119,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Μπορούμε να συνδεθούμε σε ένα συγκεκριμένο τμήμα της σελίδας HTML μέσω ενός λεγόμενου αποσπάσματος μετά το σύμβολο κατακερματισμού `#`: ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Οι σύνδεσμοι που δημιουργούνται από το `link()` ή το `n:href` είναι πάντα απόλυτες διαδρομές (δηλ. ξεκινούν με `/`), αλλά όχι απόλυτες διευθύνσεις URL με πρωτόκολλο και τομέα όπως `https://domain`. -Για να δημιουργήσετε μια απόλυτη διεύθυνση URL, προσθέστε δύο κάθετους στην αρχή (π.χ. `n:href="//Homepage:"`). Ή μπορείτε να αλλάξετε τον παρουσιαστή ώστε να παράγει μόνο απόλυτους συνδέσμους, ρυθμίζοντας το `$this->absoluteUrls = true`. +Για να δημιουργήσετε μια απόλυτη διεύθυνση URL, προσθέστε δύο κάθετους στην αρχή (π.χ. `n:href="//Home:"`). Ή μπορείτε να αλλάξετε τον παρουσιαστή ώστε να παράγει μόνο απόλυτους συνδέσμους, ρυθμίζοντας το `$this->absoluteUrls = true`. Σύνδεσμος προς την τρέχουσα σελίδα .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Αν θέλουμε να συνδέσουμε με παρουσιαστές στο πρότυπο συστατικού, χρησιμοποιούμε την ετικέτα `{plink}`: ```latte -homepage +home ``` ή στον κώδικα ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/el/how-it-works.texy b/application/el/how-it-works.texy index 138f0006ee..b5b1f766dd 100644 --- a/application/el/how-it-works.texy +++ b/application/el/how-it-works.texy @@ -23,10 +23,10 @@ web-project/ ├── app/ ← directory with application │ ├── Presenters/ ← presenter classes -│ │ ├── HomepagePresenter.php ← Homepage presenter class +│ │ ├── HomePresenter.php ← Home presenter class │ │ └── templates/ ← templates directory │ │ ├── @layout.latte ← template of shared layout -│ │ └── Homepage/ ← templates for Homepage presenter +│ │ └── Home/ ← templates for Home presenter │ │ └── default.latte ← template for action `default` │ ├── Router/ ← configuration of URL addresses │ └── Bootstrap.php ← booting class Bootstrap @@ -134,10 +134,10 @@ class ProductPresenter extends Nette\Application\UI\Presenter 1) η διεύθυνση URL θα είναι `https://example.com` 2) εκκινούμε την εφαρμογή, δημιουργούμε έναν περιέκτη και εκτελούμε `Application::run()` -3) ο δρομολογητής αποκωδικοποιεί τη διεύθυνση URL ως ζεύγος `Homepage:default` -4) δημιουργείται ένα αντικείμενο `HomepagePresenter` +3) ο δρομολογητής αποκωδικοποιεί τη διεύθυνση URL ως ζεύγος `Home:default` +4) δημιουργείται ένα αντικείμενο `HomePresenter` 5) καλείται η μέθοδος `renderDefault()` (αν υπάρχει) -6) αποδίδεται ένα πρότυπο `templates/Homepage/default.latte` με διάταξη `templates/@layout.latte` +6) αποδίδεται ένα πρότυπο `templates/Home/default.latte` με διάταξη `templates/@layout.latte` Μπορεί να έχετε συναντήσει πολλές νέες έννοιες τώρα, αλλά πιστεύουμε ότι βγάζουν νόημα. Η δημιουργία εφαρμογών στη Nette είναι πανεύκολη. diff --git a/application/el/modules.texy b/application/el/modules.texy index 56dc5ee920..b2bb1dffd0 100644 --- a/application/el/modules.texy +++ b/application/el/modules.texy @@ -104,7 +104,7 @@ class DashboardPresenter extends Nette\Application\UI\Presenter Καθορίζει τους κανόνες με τους οποίους το όνομα της κλάσης προκύπτει από το όνομα του παρουσιαστή. Τους γράφουμε στη [διαμόρφωση |configuration] κάτω από το κλειδί `application › mapping`. -Ας ξεκινήσουμε με ένα δείγμα που δεν χρησιμοποιεί ενότητες. Θα θέλουμε απλώς οι κλάσεις presenter να έχουν το namespace `App\Presenters`. Αυτό σημαίνει ότι ένας παρουσιαστής όπως το `Homepage` θα πρέπει να αντιστοιχίζεται στην κλάση `App\Presenters\HomepagePresenter`. Αυτό μπορεί να επιτευχθεί με την ακόλουθη διαμόρφωση: +Ας ξεκινήσουμε με ένα δείγμα που δεν χρησιμοποιεί ενότητες. Θα θέλουμε απλώς οι κλάσεις presenter να έχουν το namespace `App\Presenters`. Αυτό σημαίνει ότι ένας παρουσιαστής όπως το `Home` θα πρέπει να αντιστοιχίζεται στην κλάση `App\Presenters\HomePresenter`. Αυτό μπορεί να επιτευχθεί με την ακόλουθη διαμόρφωση: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Τώρα ο παρουσιαστής `Front:Homepage` αντιστοιχίζεται στην κλάση `App\Modules\Front\Presenters\HomepagePresenter` και ο παρουσιαστής `Admin:Dashboard` στην κλάση `App\Modules\Admin\Presenters\DashboardPresenter`. +Τώρα ο παρουσιαστής `Front:Home` αντιστοιχίζεται στην κλάση `App\Modules\Front\Presenters\HomePresenter` και ο παρουσιαστής `Admin:Dashboard` στην κλάση `App\Modules\Admin\Presenters\DashboardPresenter`. Είναι πιο πρακτικό να δημιουργήσετε έναν γενικό κανόνα (αστέρι) για να αντικαταστήσετε τους δύο πρώτους. Ο επιπλέον αστερίσκος θα προστεθεί στη μάσκα κλάσης μόνο για την ενότητα: diff --git a/application/el/presenters.texy b/application/el/presenters.texy index 2406abbc74..98c5b4ea42 100644 --- a/application/el/presenters.texy +++ b/application/el/presenters.texy @@ -158,7 +158,7 @@ $url = $this->link('Product:show', [$id, 'lang' => 'en']); $this->forward('Product:show'); ``` -με κωδικό HTTP 302 ή 303: +Παράδειγμα μιας λεγόμενης προσωρινής ανακατεύθυνσης με κωδικό HTTP 302 (ή 303, εάν η τρέχουσα μέθοδος αίτησης είναι POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ $this->redirect('Product:show', $id); $this->redirectPermanent('Product:show', $id); ``` -Μπορείτε να ανακατευθύνετε σε μια άλλη διεύθυνση URL εκτός της εφαρμογής με τη μέθοδο `redirectUrl()`: +Μπορείτε να ανακατευθύνετε σε μια άλλη διεύθυνση URL εκτός της εφαρμογής χρησιμοποιώντας τη μέθοδο `redirectUrl()`. Ο κωδικός HTTP μπορεί να καθοριστεί ως δεύτερη παράμετρος, με προεπιλεγμένη τιμή 302 (ή 303, αν η τρέχουσα μέθοδος αίτησης είναι POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Εμμένουσες παράμετροι .[#toc-persistent-parameters] =================================================== -Οι μόνιμες παράμετροι **μεταφέρονται αυτόματα** στους συνδέσμους. Αυτό σημαίνει ότι δεν χρειάζεται να τις προσδιορίσουμε ρητά σε κάθε `link()` ή `n:href` του προτύπου, αλλά και πάλι θα μεταφερθούν. +Οι μόνιμες παράμετροι χρησιμοποιούνται για τη διατήρηση της κατάστασης μεταξύ διαφορετικών αιτήσεων. Η τιμή τους παραμένει η ίδια ακόμη και μετά το κλικ σε έναν σύνδεσμο. Σε αντίθεση με τα δεδομένα συνόδου, μεταβιβάζονται στη διεύθυνση URL. Αυτό γίνεται εντελώς αυτόματα, οπότε δεν χρειάζεται να τις δηλώσετε ρητά στο `link()` ή στο `n:href`. -Εάν η εφαρμογή σας έχει πολλαπλές γλωσσικές εκδόσεις, τότε η τρέχουσα γλώσσα είναι μια παράμετρος που πρέπει πάντα να αποτελεί μέρος της διεύθυνσης URL. Και θα ήταν απίστευτα κουραστικό να την αναφέρετε σε κάθε σύνδεσμο. Αυτό δεν είναι απαραίτητο με τη Nette. Με αυτόν τον τρόπο απλά χαρακτηρίζουμε την παράμετρο `lang` ως μόνιμη: +Παράδειγμα χρήσης; Έχετε μια πολύγλωσση εφαρμογή. Η πραγματική γλώσσα είναι μια παράμετρος που πρέπει να αποτελεί μέρος του URL ανά πάσα στιγμή. Αλλά θα ήταν απίστευτα κουραστικό να τη συμπεριλάβετε σε κάθε σύνδεσμο. Οπότε την κάνετε μια μόνιμη παράμετρο με το όνομα `lang` και θα μεταφέρεται μόνη της. Ωραία! + +Η δημιουργία μιας μόνιμης παραμέτρου είναι εξαιρετικά εύκολη στη Nette. Απλά δημιουργήστε μια δημόσια ιδιότητα και επισημάνετέ την με το χαρακτηριστικό: (προηγουμένως χρησιμοποιούνταν το `/** @persistent */` ) ```php +use Nette\Application\Attributes\Persistent; // αυτή η γραμμή είναι σημαντική + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // πρέπει να είναι δημόσια } ``` -Εάν η τρέχουσα τιμή της παραμέτρου `lang` είναι `'en'`, τότε η διεύθυνση URL που δημιουργείται με `link()` ή `n:href` στο πρότυπο θα περιέχει `lang=en`. Υπέροχα! - -Ωστόσο, μπορούμε επίσης να προσθέσουμε την παράμετρο `lang` και μέσω αυτής να αλλάξουμε την τιμή της: +Εάν το `$this->lang` έχει μια τιμή όπως `'en'`, τότε οι σύνδεσμοι που δημιουργούνται με χρήση των `link()` ή `n:href` θα περιέχουν επίσης την παράμετρο `lang=en`. Και όταν ο σύνδεσμος πατηθεί, θα είναι και πάλι `$this->lang = 'en'`. -```latte -detail in English -``` - -Ή, αντίστροφα, μπορεί να αφαιρεθεί θέτοντας την τιμή null: - -```latte -click here -``` +Για τις ιδιότητες, συνιστούμε να περιλαμβάνετε τον τύπο δεδομένων (π.χ. `string`) και μπορείτε επίσης να συμπεριλάβετε μια προεπιλεγμένη τιμή. Οι τιμές των παραμέτρων μπορούν να [επικυρωθούν |#Validation of Persistent Parameters]. -Η μόνιμη μεταβλητή πρέπει να δηλωθεί ως δημόσια. Μπορούμε επίσης να καθορίσουμε μια προεπιλεγμένη τιμή. Εάν η παράμετρος έχει την ίδια τιμή με την προεπιλεγμένη, δεν θα συμπεριληφθεί στη διεύθυνση URL. +Οι μόνιμες παράμετροι μεταβιβάζονται μεταξύ όλων των ενεργειών ενός συγκεκριμένου παρουσιαστή από προεπιλογή. Για να τις περάσετε μεταξύ πολλαπλών παρουσιαστών, πρέπει να τις ορίσετε είτε: -Η εμμονή αντικατοπτρίζει την ιεραρχία των κλάσεων παρουσιαστή, έτσι η παράμετρος που ορίζεται σε έναν συγκεκριμένο παρουσιαστή ή χαρακτηριστικό μεταφέρεται στη συνέχεια αυτόματα σε κάθε παρουσιαστή που κληρονομεί από αυτόν ή χρησιμοποιεί το ίδιο χαρακτηριστικό. - -Στην PHP 8, μπορείτε επίσης να χρησιμοποιήσετε χαρακτηριστικά για να επισημάνετε μόνιμες παραμέτρους: +- σε έναν κοινό πρόγονο από τον οποίο κληρονομούν οι παρουσιαστές +- στην ιδιότητα που χρησιμοποιούν οι παρουσιαστές: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Μπορείτε να αλλάξετε την τιμή μιας μόνιμης παραμέτρου κατά τη δημιουργία ενός συνδέσμου: + +```latte +detail in Czech +``` + +Ή μπορεί να *επαναρυθμιστεί*, δηλαδή να αφαιρεθεί από τη διεύθυνση URL. Τότε θα πάρει την προεπιλεγμένη τιμή της: + +```latte +click ``` @@ -302,7 +310,32 @@ class ProductPresenter extends Nette\Application\UI\Presenter Απαίτηση και παράμετροι .[#toc-requirement-and-parameters] ---------------------------------------------------------- -Το αίτημα που χειρίζεται ο παρουσιαστής είναι το αντικείμενο [api:Nette\Application\Request] και επιστρέφεται από τη μέθοδο του παρουσιαστή `getRequest()`. Περιλαμβάνει έναν πίνακα παραμέτρων και κάθε μία από αυτές ανήκει είτε σε κάποιο από τα συστατικά είτε απευθείας στον παρουσιαστή (ο οποίος στην πραγματικότητα είναι επίσης ένα συστατικό, αν και ειδικό). Έτσι, η Nette ανακατανέμει τις παραμέτρους και περνάει μεταξύ των επιμέρους συστατικών (και του παρουσιαστή) καλώντας τη μέθοδο `loadState(array $params)`, η οποία περιγράφεται περαιτέρω στο κεφάλαιο [Components |Components]. Οι παράμετροι μπορούν να ληφθούν με τη μέθοδο `getParameters(): array`, μεμονωμένα με τη χρήση του `getParameter($name)`. Οι τιμές των παραμέτρων είναι συμβολοσειρές ή πίνακες συμβολοσειρών, είναι ουσιαστικά ακατέργαστα δεδομένα που λαμβάνονται απευθείας από μια διεύθυνση URL. +Το αίτημα που χειρίζεται ο παρουσιαστής είναι το αντικείμενο [api:Nette\Application\Request] και επιστρέφεται από τη μέθοδο του παρουσιαστή `getRequest()`. Περιλαμβάνει έναν πίνακα παραμέτρων και κάθε μία από αυτές ανήκει είτε σε κάποιο από τα συστατικά είτε απευθείας στον παρουσιαστή (ο οποίος στην πραγματικότητα είναι επίσης ένα συστατικό, αν και ειδικό). Έτσι, η Nette ανακατανέμει τις παραμέτρους και περνάει μεταξύ των επιμέρους συστατικών (και του παρουσιαστή) καλώντας τη μέθοδο `loadState(array $params)`. Οι παράμετροι μπορούν να ληφθούν με τη μέθοδο `getParameters(): array`, μεμονωμένα με τη χρήση του `getParameter($name)`. Οι τιμές των παραμέτρων είναι συμβολοσειρές ή πίνακες συμβολοσειρών, είναι ουσιαστικά ακατέργαστα δεδομένα που λαμβάνονται απευθείας από μια διεύθυνση URL. + + +Επικύρωση μόνιμων παραμέτρων .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------ + +Οι τιμές των [μόνιμων παραμέτρων |#persistent parameters] που λαμβάνονται από τις διευθύνσεις URL εγγράφονται στις ιδιότητες με τη μέθοδο `loadState()`. Ελέγχει επίσης αν ο τύπος δεδομένων που καθορίζεται στην ιδιότητα ταιριάζει, διαφορετικά θα απαντήσει με σφάλμα 404 και η σελίδα δεν θα εμφανιστεί. + +Ποτέ μην εμπιστεύεστε τυφλά τις μόνιμες παραμέτρους, καθώς μπορούν εύκολα να αντικατασταθούν από τον χρήστη στη διεύθυνση URL. Για παράδειγμα, με αυτόν τον τρόπο ελέγχουμε αν το `$this->lang` είναι μεταξύ των υποστηριζόμενων γλωσσών. Ένας καλός τρόπος για να το κάνετε αυτό είναι να παρακάμψετε τη μέθοδο `loadState()` που αναφέρθηκε παραπάνω: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // εδώ ορίζεται το $this->lang + // ακολουθεί τον έλεγχο της τιμής του χρήστη: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Αποθήκευση και επαναφορά της αίτησης .[#toc-save-and-restore-the-request] diff --git a/application/el/routing.texy b/application/el/routing.texy index 55da217819..19b2ceb7bf 100644 --- a/application/el/routing.texy +++ b/application/el/routing.texy @@ -93,12 +93,12 @@ $router->addRoute('chronicle/', 'History:show'); Φυσικά, το όνομα του παρουσιαστή και της ενέργειας μπορεί επίσης να είναι παράμετρος. Για παράδειγμα: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Αυτή η διαδρομή δέχεται, για παράδειγμα, μια διεύθυνση URL της μορφής `/article/edit` ή `/catalog/list` και τις μεταφράζει σε παρουσιαστές και ενέργειες `Article:edit` ή `Catalog:list`. -Δίνει επίσης στις παραμέτρους `presenter` και `action` προεπιλεγμένες τιμές`Homepage` και `default` και επομένως είναι προαιρετικές. Έτσι, η διαδρομή δέχεται επίσης ένα URL `/article` και το μεταφράζει ως `Article:default`. Ή αντίστροφα, ένας σύνδεσμος προς το `Product:default` δημιουργεί μια διαδρομή `/product`, ένας σύνδεσμος προς την προεπιλεγμένη `Homepage:default` δημιουργεί μια διαδρομή `/`. +Δίνει επίσης στις παραμέτρους `presenter` και `action` προεπιλεγμένες τιμές`Home` και `default` και επομένως είναι προαιρετικές. Έτσι, η διαδρομή δέχεται επίσης ένα URL `/article` και το μεταφράζει ως `Article:default`. Ή αντίστροφα, ένας σύνδεσμος προς το `Product:default` δημιουργεί μια διαδρομή `/product`, ένας σύνδεσμος προς την προεπιλεγμένη `Home:default` δημιουργεί μια διαδρομή `/`. Η μάσκα μπορεί να περιγράφει όχι μόνο τη σχετική διαδρομή με βάση τη ρίζα του ιστότοπου, αλλά και την απόλυτη διαδρομή όταν αρχίζει με μια κάθετο, ή ακόμη και ολόκληρη την απόλυτη διεύθυνση URL όταν αρχίζει με δύο κάθετους: @@ -160,7 +160,7 @@ $router->addRoute('//[.]example.com//', /* ... */); ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // Αποδεκτές διευθύνσεις URL: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Οι προαιρετικές παράμετροι (δηλαδή οι παράμετροι που έχουν προεπιλεγμένη τιμή) χωρίς τετράγωνες αγκύλες συμπεριφέρονται σαν να είναι τυλιγμένες έτσι: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // ισούται με: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Για να αλλάξετε τον τρόπο με τον οποίο παράγεται η δεξιότερη κάθετος, δηλαδή αντί για `/homepage/` να πάρετε ένα `/homepage`, προσαρμόστε τη διαδρομή με αυτόν τον τρόπο: +Για να αλλάξετε τον τρόπο με τον οποίο παράγεται η δεξιότερη κάθετος, δηλαδή αντί για `/home/` να πάρετε ένα `/home`, προσαρμόστε τη διαδρομή με αυτόν τον τρόπο: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ $router->addRoute('/[/]', [ Είναι μια καλή πρακτική να γράφετε τον πηγαίο κώδικα στα αγγλικά, αλλά τι γίνεται αν θέλετε ο ιστότοπός σας να έχει μεταφρασμένο URL σε διαφορετική γλώσσα; Απλές διαδρομές, όπως: "Η γλώσσα που θα χρησιμοποιηθεί για να μεταφραστεί σε άλλη γλώσσα": ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` θα δημιουργήσουν αγγλικές διευθύνσεις URL, όπως `/product/123` ή `/cart`. Αν θέλουμε να έχουμε παρουσιαστές και ενέργειες στη διεύθυνση URL μεταφρασμένες στα γερμανικά (π.χ. `/produkt/123` ή `/einkaufswagen`), μπορούμε να χρησιμοποιήσουμε ένα μεταφραστικό λεξικό. Για να το προσθέσουμε, χρειαζόμαστε ήδη μια "πιο ομιλητική" παραλλαγή της δεύτερης παραμέτρου: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // συμβολοσειρά στη διεύθυνση URL => παρουσιαστής 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ $router->addRoute('//', [ use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Η παράμετρος του κατασκευαστή `SimpleRouter` είναι ένας προεπιλεγμένος παρουσιαστής & ενέργεια, δηλαδή ενέργεια που θα εκτελεστεί αν ανοίξουμε π.χ. το `http://example.com/` χωρίς πρόσθετες παραμέτρους. ```php -// προεπιλογή παρουσιαστή 'Homepage' και δράσης 'default' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// προεπιλογή παρουσιαστή 'Home' και δράσης 'default' +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Συνιστούμε τον ορισμό του SimpleRouter απευθείας στη [διαμόρφωση |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ class MyRouter implements Nette\Routing\Router ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/el/templates.texy b/application/el/templates.texy index 364d15388a..4b83599f45 100644 --- a/application/el/templates.texy +++ b/application/el/templates.texy @@ -42,7 +42,9 @@ - `templates//.latte` - `templates/..latte` -Αν δεν βρει το πρότυπο, η απάντηση είναι [σφάλμα 404 |presenters#Error 404 etc.]. +Αν το πρότυπο δεν βρεθεί, θα προσπαθήσει να ψάξει στον κατάλογο `templates` ένα επίπεδο πιο πάνω, δηλαδή στο ίδιο επίπεδο με τον κατάλογο με την κλάση παρουσιαστή. + +Εάν το πρότυπο δεν βρεθεί ούτε εκεί, η απάντηση είναι ένα [σφάλμα 404 |presenters#Error 404 etc.]. Μπορείτε επίσης να αλλάξετε την προβολή χρησιμοποιώντας το `$this->setView('otherView')`. Ή, αντί για αναζήτηση, καθορίστε απευθείας το όνομα του αρχείου προτύπου χρησιμοποιώντας τη διεύθυνση `$this->template->setFile('/path/to/template.latte')`. @@ -148,7 +150,7 @@ Default Variables .[#toc-default-variables] Το χαρακτηριστικό `n:href` είναι πολύ βολικό για τις ετικέτες HTML ``. Αν θέλουμε να εκτυπώσουμε τον σύνδεσμο αλλού, για παράδειγμα στο κείμενο, χρησιμοποιούμε το `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` Για περισσότερες πληροφορίες, ανατρέξτε στην ενότητα [Δημιουργία συνδέσμων |Creating Links]. diff --git a/application/en/@left-menu.texy b/application/en/@left-menu.texy index 3506cff2c9..bc4b360cee 100644 --- a/application/en/@left-menu.texy +++ b/application/en/@left-menu.texy @@ -15,5 +15,8 @@ Nette Application Further Reading *************** +- [Why Use Nette?|www:10-reasons-why-nette] +- [Installation |nette:installation] +- [Create Your First Application! |quickstart:] - [Best practices |best-practices:] - [Troubleshooting |nette:troubleshooting] diff --git a/application/en/ajax.texy b/application/en/ajax.texy index 5e26e4ced2..f43ff28c2f 100644 --- a/application/en/ajax.texy +++ b/application/en/ajax.texy @@ -10,9 +10,13 @@ Modern web applications nowadays run half on a server and half in a browser. AJA -An AJAX request can be detected using a method of a service [encapsulating a HTTP request |http:request] `$httpRequest->isAjax()` (detects based on the `X-Requested-With` HTTP header). There is also a shorthand method in presenter: `$this->isAjax()`. -An AJAX request is no different from a normal one – a presenter is called with a certain view and parameters. It is, too, up to the presenter how will it react: it can use its routines to either return a fragment of HTML code (a snippet), an XML document, a JSON object or a piece of Javascript code. +AJAX Request +============ + +An AJAX request does not differ from a classic request - the presenter is called with a specific view and parameters. It is also up to the presenter how to respond to it: it can use its own routine, which returns an HTML code fragment (HTML snippet), an XML document, a JSON object, or JavaScript code. + +On the server side, an AJAX request can be detected using the service method [encapsulating the HTTP request |http:request] `$httpRequest->isAjax()` (detects based on the HTTP header `X-Requested-With`). Inside the presenter, a shortcut is available in the form of the method `$this->isAjax()`. There is a pre-processed object called `payload` dedicated to sending data to the browser in JSON. @@ -60,6 +64,21 @@ npm install naja ``` +To create an AJAX request from a regular link (signal) or form submittion, simply mark the relevant link, form, or button with the class `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Snippets ======== @@ -149,7 +168,7 @@ You can't redraw a dynamic snippet directly (redrawing of `item-1` has no effect In the example above you have to make sure that for an AJAX request only one item will be added to the `$list` array, therefore the `foreach` loop will print just one dynamic snippet. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/en/bootstrap.texy b/application/en/bootstrap.texy index c7927f9733..c763e715a3 100644 --- a/application/en/bootstrap.texy +++ b/application/en/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -In configuration files, we can write usual notation `%projectId%` to access the parameter named `projectId`. By default, the Configurator populates the following parameters: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` and `consoleMode`. +In configuration files, we can write usual notation `%projectId%` to access the parameter named `projectId`. Dynamic Parameters @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Default Parameters +------------------ + +You can use the following static parameters in the configuration files: + +- `%appDir%` is the absolute path to the directory of `Bootstrap.php` file +- `%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 +- `%debugMode%` indicates whether the application is in debug mode +- `%consoleMode%` indicates whether the request came through the command line + + Imported Services ----------------- diff --git a/application/en/components.texy b/application/en/components.texy index 218958b13f..bdf8052c3a 100644 --- a/application/en/components.texy +++ b/application/en/components.texy @@ -233,31 +233,34 @@ In the template, these messages are available in the variable `$flashes` as obje Persistent Parameters ===================== -It is often needed to keep some parameter in a component for the whole time of working with the component. It can be for example the number of the page in pagination. This parameter should be marked as persistent using the annotation `@persistent`. +Persistent parameters are used to maintain state in components between different requests. Their value remains the same even after a link is clicked. Unlike session data, they are transferred in the URL. And they are transferred automatically, including links created in other components on the same page. + +For example, you have a content paging component. There can be several such components on a page. And you want all components to stay on their current page when you click on the link. Therefore, we make the page number (`page`) a persistent parameter. + +Creating a persistent parameter is extremely easy in Nette. Just create a public property and tag it with the attribute: (previously `/** @persistent */` was used) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // this line is important + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // must be public } ``` -This parameter will be automatically passed in every link as a `GET` parameter until the user leaves the page with this component. +We recommend that you include the data type (e.g. `int`) with the property, and you can also include a default value. Parameter values can be [validated |#Validation of Persistent Parameters]. -.[caution] -Never trust persistent parameters blindly because they can be faked easily (by overwriting the URL). Verify, for example, if the page number is within the correct interval. +You can change the value of a persistent parameter when creating a link: -In PHP 8, you can also use attributes to mark persistent parameters: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Or it can be *reset*, i.e. removed from the URL. It will then take its default value: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ Components in a Nette Application are the reusable parts of a web application th 1) it is renderable in a template 2) it knows which part of itself to render during an [AJAX request |ajax#invalidation] (snippets) -3) it has the ability to store its state in a URL (persistence parameters) +3) it has the ability to store its state in a URL (persistent parameters) 4) has the ability to respond to user actions (signals) 5) creates a hierarchical structure (where the root is the presenter) @@ -403,6 +406,33 @@ Life Cycle of Component [* lifecycle-component.svg *] *** *Life cycle of component* .<> +Validation of Persistent Parameters +----------------------------------- + +The values of [#persistent parameters] received from URLs are written to properties by the `loadState()` method. It also checks if the data type specified for the property matches, otherwise it will respond with a 404 error and the page will not be displayed. + +Never blindly trust persistent parameters because they can easily be overwritten by the user in the URL. For example, this is how we check if the page number `$this->page` is greater than 0. A good way to do this is to override the `loadState()` method mentioned above: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // here is set the $this->page + // follows the user value check: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +The opposite process, that is, collecting values from persistent properites, is handled by the `saveState()` method. + + Signals in Depth ---------------- diff --git a/application/en/creating-links.texy b/application/en/creating-links.texy index 3415287796..0fb783c609 100644 --- a/application/en/creating-links.texy +++ b/application/en/creating-links.texy @@ -52,7 +52,7 @@ The so-called [persistent parameters|presenters#persistent parameters] are also Attribute `n:href` is very handy for HTML tags ``. If we want to print the link elsewhere, for example in the text, we use `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ The format is supported by all Latte tags and all presenter methods that work wi The basic form is therefore `Presenter:action`: ```latte -homepage +home ``` If we link to the action of the current presenter, we can omit its name: ```latte -homepage +home ``` If the action is `default`, we can omit it, but the colon must remain: ```latte -homepage +home ``` Links may also point to other [modules]. Here, the links are distinguished into relative to the submodules, or absolute. The principle is analogous to disk paths, only instead of slashes there are colons. Let's assume that the actual presenter is part of module `Front`, then we will write: @@ -119,7 +119,7 @@ A special case is [linking to itself|#Links to Current Page]. Here we'll write ` We can link to a certain part of the HTML page via a so-called fragment after the `#` hash symbol: ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ Absolute Paths Links generated by `link()` or `n:href` are always absolute paths (i.e., they start with `/`), but not absolute URLs with a protocol and domain like `https://domain`. -To generate an absolute URL, add two slashes to the beginning (e.g., `n:href="//Homepage:"`). Or you can switch the presenter to generate only absolute links by setting `$this->absoluteUrls = true`. +To generate an absolute URL, add two slashes to the beginning (e.g., `n:href="//Home:"`). Or you can switch the presenter to generate only absolute links by setting `$this->absoluteUrls = true`. Link to Current Page @@ -213,13 +213,13 @@ Because [components] are separate reusable units that should have no relations t If we want to link to presenters in the component template, we use the tag `{plink}`: ```latte -homepage +home ``` or in the code ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/en/how-it-works.texy b/application/en/how-it-works.texy index 2feed87b13..569d48065f 100644 --- a/application/en/how-it-works.texy +++ b/application/en/how-it-works.texy @@ -23,10 +23,10 @@ The directory structure looks something like this: web-project/ ├── app/ ← directory with application │ ├── Presenters/ ← presenter classes -│ │ ├── HomepagePresenter.php ← Homepage presenter class +│ │ ├── HomePresenter.php ← Home presenter class │ │ └── templates/ ← templates directory │ │ ├── @layout.latte ← template of shared layout -│ │ └── Homepage/ ← templates for Homepage presenter +│ │ └── Home/ ← templates for Home presenter │ │ └── default.latte ← template for action `default` │ ├── Router/ ← configuration of URL addresses │ └── Bootstrap.php ← booting class Bootstrap @@ -134,10 +134,10 @@ Just to be sure, let's try to recap the whole process with a slightly different 1) the URL will be `https://example.com` 2) we boot the application, create a container and run `Application::run()` -3) the router decodes the URL as a pair `Homepage:default` -4) an `HomepagePresenter` object is created +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/Homepage/default.latte` with a layout `templates/@layout.latte` is rendered +6) a template `templates/Home/default.latte` with a layout `templates/@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 3cddb2754f..6312143fdd 100644 --- a/application/en/modules.texy +++ b/application/en/modules.texy @@ -104,7 +104,7 @@ 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. -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 `Homepage` should map to the `App\Presenters\HomepagePresenter` class. This can be achieved by the following configuration: +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: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Now presenter `Front:Homepage` maps to class `App\Modules\Front\Presenters\HomepagePresenter` and presenter `Admin:Dashboard` to class `App\Modules\Admin\Presenters\DashboardPresenter`. +Now presenter `Front:Home` maps to class `App\Modules\Front\Presenters\HomePresenter` and presenter `Admin:Dashboard` to class `App\Modules\Admin\Presenters\DashboardPresenter`. 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: diff --git a/application/en/presenters.texy b/application/en/presenters.texy index cc82288e56..680b937cf9 100644 --- a/application/en/presenters.texy +++ b/application/en/presenters.texy @@ -158,7 +158,7 @@ The `forward()` switches to the new presenter immediately without HTTP redirecti $this->forward('Product:show'); ``` -Example of temporary redirection with HTTP code 302 or 303: +Example of a so-called temporary redirection with HTTP code 302 (or 303, if the current request method is POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ To achieve permanent redirection with HTTP code 301 use: $this->redirectPermanent('Product:show', $id); ``` -You can redirect to another URL outside the application with the `redirectUrl()` method: +You can redirect to another URL outside the application using the `redirectUrl()` method. The HTTP code can be specified as the second parameter, with the default being 302 (or 303, if the current request method is POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Persistent Parameters ===================== -Persistent parameters are **transferred automatically** in links. This means that we do not have to explicitly specify them in every `link()` or `n:href` in the template, but they will still be transferred. +Persistent parameters are used to maintain state between different requests. Their value remains the same even after a link is clicked. Unlike session data, they are passed in the URL. This is completely automatic, so there is no need to explicitly state them in `link()` or `n:href`. -If your application has multiple language versions, then the current language is a parameter that must always be part of the URL. And it would be incredibly tiring to mention it in every link. That's not necessary with Nette. We simply mark the `lang` parameter as persistent in this way: +Example of use? You have a multilingual application. The actual language is a parameter that needs to be part of the URL at all times. But it would be incredibly tedious to include it in every link. So you make it a persistent parameter named `lang` and it will carry itself. Cool! + +Creating a persistent parameter is extremely easy in Nette. Just create a public property and tag it with the attribute: (previously `/** @persistent */` was used) ```php +use Nette\Application\Attributes\Persistent; // this line is important + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // must be public } ``` -If the current value of the parameter `lang` is `'en'`, then the URL created with `link()` or `n:href` in the template will contain `lang=en`. Great! - -However, we can also add parameter `lang` and by that change its value: +If `$this->lang` has a value such as `'en'`, then links created using `link()` or `n:href` will also contain the `lang=en` parameter. And when the link is clicked, it will again be `$this->lang = 'en'`. -```latte -detail in English -``` - -Or, conversely, it can be removed by setting to null: - -```latte -click here -``` +For properties, we recommend that you include the data type (e.g. `string`) and you can also include a default value. Parameter values can be [validated |#Validation of Persistent Parameters]. -The persistent variable must be declared as public. We can also specify a default value. If the parameter has the same value as the default, it will not be included in the URL. +Persistent parameters are passed between all actions of a given presenter by default. To pass them between multiple presenters, you need to define them either: -Persistence reflects the hierarchy of presenter classes, thus parameter defined in a certain presenter or trait is then automatically transferred to each presenter inheriting from it or using the same trait. - -In PHP 8, you can also use attributes to mark persistent parameters: +- in a common ancestor from which the presenters inherit +- in the trait that the presenters use: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +You can change the value of a persistent parameter when creating a link: + +```latte +detail in Czech +``` + +Or it can be *reset*, i.e. removed from the URL. It will then take its default value: + +```latte +click ``` @@ -302,7 +310,32 @@ What we have shown so far in this chapter will probably suffice. The following l Requirement and Parameters -------------------------- -The request handled by the presenter is the [api:Nette\Application\Request] object and is returned by the presenter's method `getRequest()`. It includes an array of parameters and each of them belongs either to some of the components or directly to the presenter (which is actually also a component, albeit a special one). So Nette redistributes the parameters and passes between the individual components (and the presenter) by calling the method `loadState(array $params)`, which is further described in the chapter [Components]. The parameters can be obtained by the method `getParameters(): array`, individually using `getParameter($name)`. Parameter values ​​are strings or arrays of strings, they are basically raw data obtained directly from a URL. +The request handled by the presenter is the [api:Nette\Application\Request] object and is returned by the presenter's method `getRequest()`. It includes an array of parameters and each of them belongs either to some of the components or directly to the presenter (which is actually also a component, albeit a special one). So Nette redistributes the parameters and passes between the individual components (and the presenter) by calling the method `loadState(array $params)`. The parameters can be obtained by the method `getParameters(): array`, individually using `getParameter($name)`. Parameter values ​​are strings or arrays of strings, they are basically raw data obtained directly from a URL. + + +Validation of Persistent Parameters +----------------------------------- + +The values of [#persistent parameters] received from URLs are written to properties by the `loadState()` method. It also checks if the data type specified in the property matches, otherwise it will respond with a 404 error and the page will not be displayed. + +Never blindly trust persistent parameters, as they can easily be overwritten by the user in the URL. For example, this is how we check if `$this->lang` is among the supported languages. A good way to do this is to override the `loadState()` method mentioned above: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // here is set the $this->lang + // follows the user value check: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Save and Restore the Request diff --git a/application/en/routing.texy b/application/en/routing.texy index 55df69e79f..bb30283d14 100644 --- a/application/en/routing.texy +++ b/application/en/routing.texy @@ -93,12 +93,12 @@ The route will now accept the URL `https://any-domain.com/chronicle/`, which wil Of course, the name of the presenter and the action can also be a parameter. For example: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` This route accepts, for example, a URL in the form `/article/edit` resp. `/catalog/list` and translates them to presenters and actions `Article:edit` resp. `Catalog:list`. -It also gives to parameters `presenter` and `action` default values ​​`Homepage` and `default` and therefore they are optional. So the route also accepts a URL `/article` and translates it as `Article:default`. Or vice versa, a link to `Product:default` generates a path `/product`, a link to the default `Homepage:default` generates a path `/`. +It also gives to parameters `presenter` and `action` default values ​​`Home` and `default` and therefore they are optional. So the route also accepts a URL `/article` and translates it as `Article:default`. Or vice versa, a link to `Product:default` generates a path `/product`, a link to the default `Home:default` generates a path `/`. The mask can describe not only the relative path based on the site root, but also the absolute path when it begins with a slash, or even the entire absolute URL when it begins with two slashes: @@ -160,7 +160,7 @@ Sequences may be freely nested and combined: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // Accepted URLs: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Optional parameters (ie. parameters having default value) without square brackets do behave as if wrapped like this: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // equals to: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -To change how the rightmost slash is generated, i.e. instead of `/homepage/` get a `/homepage`, adjust the route this way: +To change how the rightmost slash is generated, i.e. instead of `/home/` get a `/home`, adjust the route this way: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ The second parameter of the route, which we often write in the format `Presenter ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filters and Translations It's a good practice to write source code in English, but what if you need your website to have translated URL to different language? Simple routes such as: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` will generate English URLs, such as `/product/123` or `/cart`. If we want to have presenters and actions in the URL translated to Deutsch (e.g. `/produkt/123` or `/einkaufswagen`), we can use a translation dictionary. To add it, we already need a "more talkative" variant of the second parameter: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // string in URL => presenter 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Besides filters for specific parameters, you can also define general filters tha use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 The parameter of the `SimpleRouter` constructor is a default presenter & action, ie. action to be executed if we open e.g. `http://example.com/` without additional parameters. ```php -// defaults to presenter 'Homepage' and action 'default' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// defaults to presenter 'Home' and action 'default' +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` We recommend defining SimpleRouter directly in [configuration |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ When processing the request, we must return at least the presenter and the actio ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/en/templates.texy b/application/en/templates.texy index 89a8e2cf3c..5107b4493a 100644 --- a/application/en/templates.texy +++ b/application/en/templates.texy @@ -42,7 +42,9 @@ The path to the templates is deduced according to simple logic. It tries to see - `templates//.latte` - `templates/..latte` -If it does not find the template, the response is [error 404 |presenters#Error 404 etc.]. +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. + +If the template is not found there either, the response is a [404 error|presenters#Error 404 etc.]. 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')`. @@ -148,7 +150,7 @@ In template we create links to other presenters & actions as follows: Attribute `n:href` is very handy for HTML tags ``. If we want to print the link elsewhere, for example in the text, we use `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` For more information, see [Creating Links]. diff --git a/application/es/@left-menu.texy b/application/es/@left-menu.texy index aea80f2e18..c615204682 100644 --- a/application/es/@left-menu.texy +++ b/application/es/@left-menu.texy @@ -15,5 +15,8 @@ Aplicación Nette Más información *************** +- [¿Por qué utilizar Nette? |www:10-reasons-why-nette] +- [Instalación |nette:installation] +- [Cree su primera aplicación |quickstart:] - [Buenas prácticas |best-practices:] - [Solución de problemas |nette:troubleshooting] diff --git a/application/es/ajax.texy b/application/es/ajax.texy index 17e7543771..0ba8886dd4 100644 --- a/application/es/ajax.texy +++ b/application/es/ajax.texy @@ -10,9 +10,13 @@ Hoy en día, las aplicaciones web modernas se ejecutan mitad en un servidor y mi -Una solicitud AJAX puede detectarse utilizando un método de un servicio que [encapsula una solicitud HTTP |http:request] `$httpRequest->isAjax()` (detecta basándose en la cabecera HTTP `X-Requested-With` ). También existe un método abreviado en presentador: `$this->isAjax()`. -Una petición AJAX no difiere de una normal: se llama a un presentador con una vista y unos parámetros determinados. También depende del presentador cómo reaccionará: puede utilizar sus rutinas para devolver un fragmento de código HTML (un snippet), un documento XML, un objeto JSON o un fragmento de código Javascript. +Solicitud AJAX .[#toc-ajax-request] +=================================== + +Una petición AJAX no difiere de una petición clásica: se llama al presentador con una vista y unos parámetros específicos. También depende del presentador cómo responder a ella: puede utilizar su propia rutina, que devuelve un fragmento de código HTML (HTML snippet), un documento XML, un objeto JSON o código JavaScript. + +En el lado del servidor, una petición AJAX puede detectarse utilizando el método de servicio [que encapsula la petición HTTP |http:request] `$httpRequest->isAjax()` (detecta basándose en la cabecera HTTP `X-Requested-With`). Dentro del presentador, se dispone de un acceso directo en forma del método `$this->isAjax()`. Existe un objeto preprocesado llamado `payload` dedicado a enviar datos al navegador en JSON. @@ -60,6 +64,21 @@ npm install naja ``` +Para crear una solicitud AJAX a partir de un enlace normal (señal) o el envío de un formulario, basta con marcar el enlace, formulario o botón correspondiente con la clase `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Fragmentos .[#toc-snippets] =========================== @@ -149,7 +168,7 @@ No puedes redibujar un fragmento dinámico directamente (redibujar `item-1` no t En el ejemplo anterior tiene que asegurarse de que para una petición AJAX sólo se añadirá un elemento a la matriz `$list`, por lo que el bucle `foreach` sólo imprimirá un fragmento dinámico. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/es/bootstrap.texy b/application/es/bootstrap.texy index ef1140b871..3b67d5c4b9 100644 --- a/application/es/bootstrap.texy +++ b/application/es/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -En los archivos de configuración, podemos escribir la notación habitual `%projectId%` para acceder al parámetro denominado `projectId`. Por defecto, el Configurador rellena los siguientes parámetros: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` y `consoleMode`. +En los archivos de configuración, podemos escribir la notación habitual `%projectId%` para acceder al parámetro denominado `projectId`. Parámetros dinámicos .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Parámetros por defecto .[#toc-default-parameters] +------------------------------------------------- + +Puede utilizar los siguientes parámetros estáticos en los archivos de configuración: + +- `%appDir%` es la ruta absoluta al directorio del archivo `Bootstrap.php` +- `%wwwDir%` es la ruta absoluta al directorio que contiene el archivo de entrada `index.php` +- `%tempDir%` es la ruta absoluta al directorio para los archivos temporales +- `%vendorDir%` es la ruta absoluta al directorio donde Composer instala las bibliotecas +- `%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 + + Servicios importados .[#toc-imported-services] ---------------------------------------------- diff --git a/application/es/components.texy b/application/es/components.texy index aa08c97455..d3dad11756 100644 --- a/application/es/components.texy +++ b/application/es/components.texy @@ -233,31 +233,34 @@ En la plantilla, estos mensajes están disponibles en la variable `$flashes` com Parámetros persistentes .[#toc-persistent-parameters] ===================================================== -A menudo es necesario mantener algún parámetro en un componente durante todo el tiempo de trabajo con el componente. Puede ser, por ejemplo, el número de la página en la paginación. Este parámetro debe marcarse como persistente utilizando la anotación `@persistent`. +Los parámetros persistentes se utilizan para mantener el estado de los componentes entre diferentes peticiones. Su valor sigue siendo el mismo incluso después de hacer clic en un enlace. A diferencia de los datos de sesión, se transfieren en la URL. Y se transfieren automáticamente, incluidos los enlaces creados en otros componentes de la misma página. + +Por ejemplo, tiene un componente de paginación de contenido. Puede haber varios componentes de este tipo en una página. Y quiere que todos los componentes permanezcan en su página actual cuando haga clic en el enlace. Por lo tanto, hacemos que el número de página (`page`) sea un parámetro persistente. + +Crear un parámetro persistente es extremadamente fácil en Nette. Basta con crear una propiedad pública y etiquetarla con el atributo: (antes se utilizaba `/** @persistent */` ) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // esta línea es importante + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // debe ser público } ``` -Este parámetro se pasará automáticamente en cada enlace como parámetro `GET` hasta que el usuario abandone la página con este componente. +Te recomendamos que incluyas el tipo de dato (por ejemplo `int`) con la propiedad, y también puedes incluir un valor por defecto. Los valores de los parámetros se pueden [validar |#Validation of Persistent Parameters]. -.[caution] -Nunca confíe ciegamente en los parámetros persistentes porque pueden falsificarse fácilmente (sobrescribiendo la URL). Verifique, por ejemplo, si el número de página está dentro del intervalo correcto. +Puede cambiar el valor de un parámetro persistente al crear un enlace: -En PHP 8, también puedes usar atributos para marcar parámetros persistentes: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +O puede ser *reset*, es decir, eliminado de la URL. Entonces tomará su valor por defecto: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ Los componentes en una aplicación Nette son las partes reutilizables de una apl 1) es renderizable en una plantilla 2) sabe qué parte de sí mismo renderizar durante una [petición AJAX |ajax#invalidation] (fragmentos) -3) tiene la capacidad de almacenar su estado en una URL (parámetros de persistencia) +3) tiene la capacidad de almacenar su estado en una URL (parámetros persistentes) 4) tiene la capacidad de responder a las acciones del usuario (señales) 5) crea una estructura jerárquica (donde la raíz es el presentador) @@ -403,6 +406,33 @@ Ciclo de vida del componente .[#toc-life-cycle-of-component] [* lifecycle-component.svg *] *** *Ciclo de vida del componente* .<> +Validación de parámetros persistentes .[#toc-validation-of-persistent-parameters] +--------------------------------------------------------------------------------- + +Los valores de los parámetros [persistentes |#persistent parameters] recibidos de las URLs son escritos en las propiedades por el método `loadState()`. También comprueba si el tipo de datos especificado para la propiedad coincide, de lo contrario responderá con un error 404 y la página no se mostrará. + +Nunca confíes ciegamente en los parámetros persistentes porque pueden ser fácilmente sobrescritos por el usuario en la URL. Por ejemplo, así es como comprobamos si el número de página `$this->page` es mayor que 0. Una buena forma de hacerlo es sobrescribir el método `loadState()` mencionado anteriormente: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // aquí se establece el $this->page + // sigue la comprobación del valor del usuario: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +El proceso opuesto, es decir, recolectar valores de propiedades persistentes, es manejado por el método `saveState()`. + + Señales en profundidad .[#toc-signals-in-depth] ----------------------------------------------- diff --git a/application/es/creating-links.texy b/application/es/creating-links.texy index 1b8304bb6f..cd4645f3a5 100644 --- a/application/es/creating-links.texy +++ b/application/es/creating-links.texy @@ -52,7 +52,7 @@ Los llamados [parámetros persistentes |presenters#persistent parameters] tambi El atributo `n:href` es muy útil para las etiquetas HTML ``. Si queremos imprimir el enlace en otro lugar, por ejemplo en el texto, utilizamos `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ El formato es soportado por todas las etiquetas Latte y todos los métodos de pr Por lo tanto, la forma básica es `Presenter:action`: ```latte -homepage +home ``` Si enlazamos con la acción del presentador actual, podemos omitir su nombre: ```latte -homepage +home ``` Si la acción es `default`, podemos omitirlo, pero los dos puntos deben permanecer: ```latte -homepage +home ``` Los enlaces también pueden apuntar a otros [módulos |modules]. Aquí, los enlaces se distinguen en relativos a los submódulos, o absolutos. El principio es análogo a las rutas de disco, sólo que en lugar de barras inclinadas hay dos puntos. Supongamos que el presentador real forma parte del módulo `Front`, entonces escribiremos: @@ -119,7 +119,7 @@ Un caso especial es el [enlace a sí mismo |#Links to Current Page]. Aquí escri Podemos enlazar a una parte determinada de la página HTML mediante un fragmento llamado `#` después del símbolo de almohadilla `#`: ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ Rutas absolutas .[#toc-absolute-paths] Los enlaces generados por `link()` o `n:href` son siempre rutas absolutas (es decir, empiezan por `/`), pero no URLs absolutas con protocolo y dominio como `https://domain`. -Para generar una URL absoluta, añada dos barras al principio (por ejemplo, `n:href="//Homepage:"`). También puede hacer que el presentador genere sólo enlaces absolutos configurando `$this->absoluteUrls = true`. +Para generar una URL absoluta, añada dos barras al principio (por ejemplo, `n:href="//Home:"`). También puede hacer que el presentador genere sólo enlaces absolutos configurando `$this->absoluteUrls = true`. Enlace a la página actual .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ Dado que los [componentes |components] son unidades reutilizables independientes Si queremos enlazar con presentadores en la plantilla de componentes, utilizaremos la etiqueta `{plink}`: ```latte -homepage +home ``` o en el código ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/es/how-it-works.texy b/application/es/how-it-works.texy index f414f77972..41276637aa 100644 --- a/application/es/how-it-works.texy +++ b/application/es/how-it-works.texy @@ -23,10 +23,10 @@ La estructura de directorios se parece a esto web-project/ ├── app/ ← directorio con la aplicación │ ├── Presenters/ ← clases para presentadores -│ │ ├── HomepagePresenter.php ← Homepage de inicio de la clase de presentador +│ │ ├── HomePresenter.php ← Home de inicio de la clase de presentador │ │ └── templates/ ← directorio de plantillas │ │ ├── @layout.latte ← plantilla de diseño compartida -│ │ └── Homepage/ ← plantillas para Homepage presentador de inicio +│ │ └── Home/ ← plantillas para Home presentador de inicio │ │ └── default.latte ← plantilla para la acción `default` │ ├── Router/ ← configuración de direcciones URL │ └── Bootstrap.php ← clase de arranque Bootstrap @@ -134,10 +134,10 @@ Sólo para estar seguros, intentemos recapitular todo el proceso con una URL lig 1) la URL será `https://example.com` 2) arrancamos la aplicación, creamos un contenedor y ejecutamos `Application::run()` -3) el router decodifica la URL como un par `Homepage:default` -4) se crea un objeto `HomepagePresenter` +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/Homepage/default.latte` con un diseño `templates/@layout.latte` +6) se renderiza una plantilla `templates/Home/default.latte` con un diseño `templates/@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 3b04ffbe05..e183346e5f 100644 --- a/application/es/modules.texy +++ b/application/es/modules.texy @@ -104,7 +104,7 @@ Mapeo .[#toc-mapping] Define las reglas por las que el nombre de la clase se deriva del nombre del presentador. Las escribimos en [configuración |configuration] bajo la clave `application › mapping`. -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 `Homepage` debe mapearse a la clase `App\Presenters\HomepagePresenter`. Esto se puede lograr con la siguiente configuración: +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: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Ahora el presentador `Front:Homepage` se asigna a la clase `App\Modules\Front\Presenters\HomepagePresenter` y el presentador `Admin:Dashboard` a la clase `App\Modules\Admin\Presenters\DashboardPresenter`. +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`. 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: diff --git a/application/es/presenters.texy b/application/es/presenters.texy index 614b163f84..0bbbb69867 100644 --- a/application/es/presenters.texy +++ b/application/es/presenters.texy @@ -158,7 +158,7 @@ El `forward()` cambia al nuevo presentador inmediatamente sin redirección HTTP: $this->forward('Product:show'); ``` -Ejemplo de redirección temporal con código HTTP 302 o 303: +Ejemplo de una redirección temporal con código HTTP 302 (o 303, si el método de solicitud actual es POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ Para conseguir una redirección permanente con código HTTP 301 utilice: $this->redirectPermanent('Product:show', $id); ``` -Puede redirigir a otra URL fuera de la aplicación con el método `redirectUrl()`: +Puede redirigir a otra URL fuera de la aplicación utilizando el método `redirectUrl()`. El código HTTP puede especificarse como segundo parámetro, siendo el predeterminado 302 (o 303, si el método de solicitud actual es POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Parámetros persistentes .[#toc-persistent-parameters] ===================================================== -Los parámetros persistentes se **transfieren automáticamente** en los enlaces. Esto significa que no tenemos que especificarlos explícitamente en cada `link()` o `n:href` de la plantilla, pero aún así serán transferidos. +Los parámetros persistentes se utilizan para mantener el estado entre diferentes peticiones. Su valor sigue siendo el mismo incluso después de hacer clic en un enlace. A diferencia de los datos de sesión, se pasan en la URL. Esto es completamente automático, por lo que no es necesario indicarlos explícitamente en `link()` o `n:href`. -Si tu aplicación tiene varias versiones de idioma, entonces el idioma actual es un parámetro que siempre debe formar parte de la URL. Y sería increíblemente cansado mencionarlo en cada enlace. Eso no es necesario con Nette. Simplemente marcamos el parámetro `lang` como persistente de esta forma: +¿Ejemplo de uso? Tiene una aplicación multilingüe. El idioma real es un parámetro que debe formar parte de la URL en todo momento. Pero sería increíblemente tedioso incluirlo en cada enlace. Así que lo conviertes en un parámetro persistente llamado `lang` y se guardará solo. Genial. + +Crear un parámetro persistente es extremadamente fácil en Nette. Basta con crear una propiedad pública y etiquetarla con el atributo: (antes se utilizaba `/** @persistent */` ) ```php +use Nette\Application\Attributes\Persistent; // esta línea es importante + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // debe ser público } ``` -Si el valor actual del parámetro `lang` es `'en'`, entonces la URL creada con `link()` o `n:href` en la plantilla contendrá `lang=en`. ¡Genial! - -Sin embargo, también podemos añadir el parámetro `lang` y así cambiar su valor: +Si `$this->lang` tiene un valor como `'en'`, entonces los enlaces creados usando `link()` o `n:href` también contendrán el parámetro `lang=en`. Y cuando se haga clic en el enlace, volverá a ser `$this->lang = 'en'`. -```latte -detail in English -``` - -O, por el contrario, se puede eliminar mediante el establecimiento de null: - -```latte -click here -``` +Para las propiedades, se recomienda incluir el tipo de datos (por ejemplo, `string`) y también se puede incluir un valor por defecto. Los valores de los parámetros se pueden [validar |#Validation of Persistent Parameters]. -La variable persistente debe declararse como pública. También podemos especificar un valor por defecto. Si el parámetro tiene el mismo valor que el predeterminado, no se incluirá en la URL. +Los parámetros persistentes se pasan entre todas las acciones de un presentador determinado por defecto. Para pasarlos entre varios presentadores, es necesario definirlos: -La persistencia refleja la jerarquía de las clases de presentador, por lo que el parámetro definido en un determinado presentador o rasgo se transfiere automáticamente a cada presentador que herede de él o que utilice el mismo rasgo. - -En PHP 8, también puede usar atributos para marcar parámetros persistentes: +- en un ancestro común del que hereden los presentadores +- en el rasgo que utilizan los presentadores: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Puede cambiar el valor de un parámetro persistente al crear un enlace: + +```latte +detail in Czech +``` + +O puede ser *reset*, es decir, eliminado de la URL. Entonces tomará su valor por defecto: + +```latte +click ``` @@ -302,7 +310,32 @@ Lo que hemos mostrado hasta ahora en este capítulo probablemente será suficien Requisitos y parámetros .[#toc-requirement-and-parameters] ---------------------------------------------------------- -La solicitud gestionada por el presentador es el objeto [api:Nette\Application\Request] y es devuelta por el método del presentador `getRequest()`. Incluye una matriz de parámetros y cada uno de ellos pertenece a alguno de los componentes o directamente al presentador (que en realidad también es un componente, aunque especial). Así pues, Nette redistribuye los parámetros y los pasa entre los componentes individuales (y el presentador) llamando al método `loadState(array $params)`, que se describe con más detalle en el capítulo [Componentes |Components]. Los parámetros pueden obtenerse mediante el método `getParameters(): array`, de forma individual utilizando `getParameter($name)`. Los valores de los parámetros son cadenas o matrices de cadenas, son básicamente datos en bruto obtenidos directamente de una URL. +La solicitud gestionada por el presentador es el objeto [api:Nette\Application\Request] y es devuelta por el método del presentador `getRequest()`. Incluye una matriz de parámetros y cada uno de ellos pertenece a alguno de los componentes o directamente al presentador (que en realidad también es un componente, aunque especial). Así pues, Nette redistribuye los parámetros y los pasa entre los distintos componentes (y el presentador) llamando al método `loadState(array $params)`. Los parámetros pueden obtenerse mediante el método `getParameters(): array`, individualmente mediante `getParameter($name)`. Los valores de los parámetros son cadenas o matrices de cadenas, son básicamente datos en bruto obtenidos directamente de una URL. + + +Validación de parámetros persistentes .[#toc-validation-of-persistent-parameters] +--------------------------------------------------------------------------------- + +Los valores de los parámetros [persistentes |#persistent parameters] recibidos de las URLs son escritos en propiedades por el método `loadState()`. También comprueba si el tipo de datos especificado en la propiedad coincide, de lo contrario responderá con un error 404 y no se mostrará la página. + +Nunca confíes ciegamente en los parámetros persistentes, ya que pueden ser fácilmente sobrescritos por el usuario en la URL. Por ejemplo, así es como comprobamos si `$this->lang` está entre los idiomas soportados. Una buena forma de hacerlo es sobrescribir el método `loadState()` mencionado anteriormente: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // aquí se establece el $this->lang + // sigue la comprobación del valor del usuario: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Guardar y restaurar la petición .[#toc-save-and-restore-the-request] diff --git a/application/es/routing.texy b/application/es/routing.texy index 058cfd31b8..2e886aa907 100644 --- a/application/es/routing.texy +++ b/application/es/routing.texy @@ -93,12 +93,12 @@ La ruta aceptará ahora la URL `https://any-domain.com/chronicle/` con el parám Por supuesto, el nombre del presentador y la acción también pueden ser un parámetro. Por ejemplo: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Esta ruta acepta, por ejemplo, una URL de la forma `/article/edit` resp. `/catalog/list` y las traduce a presentadores y acciones `Article:edit` resp. `Catalog:list`. -También da a los parámetros `presenter` y `action` valores por defecto`Homepage` y `default` y, por tanto, son opcionales. Así, la ruta también acepta una URL `/article` y la traduce como `Article:default`. O viceversa, un enlace a `Product:default` genera una ruta `/product`, un enlace al valor por defecto `Homepage:default` genera una ruta `/`. +También da a los parámetros `presenter` y `action` valores por defecto`Home` y `default` y, por tanto, son opcionales. Así, la ruta también acepta una URL `/article` y la traduce como `Article:default`. O viceversa, un enlace a `Product:default` genera una ruta `/product`, un enlace al valor por defecto `Home:default` genera una ruta `/`. La máscara puede describir no sólo la ruta relativa basada en la raíz del sitio, sino también la ruta absoluta cuando comienza con una barra, o incluso toda la URL absoluta cuando comienza con dos barras: @@ -160,7 +160,7 @@ Las secuencias pueden anidarse y combinarse libremente: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // URL aceptadas: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Los parámetros opcionales (es decir, los parámetros que tienen un valor por defecto) sin corchetes se comportan como si estuvieran envueltos de esta manera: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // igual a: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Para cambiar cómo se genera la barra más a la derecha, es decir, en lugar de `/homepage/` obtener un `/homepage`, ajustar la ruta de esta manera: +Para cambiar cómo se genera la barra más a la derecha, es decir, en lugar de `/home/` obtener un `/home`, ajustar la ruta de esta manera: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ El segundo parámetro de la ruta, que a menudo escribimos en el formato `Present ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filtros y traducciones .[#toc-filters-and-translations] Es una buena práctica escribir el código fuente en inglés, pero ¿qué pasa si necesitas que tu sitio web tenga la URL traducida a otro idioma? Rutas simples como: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` generarán URL en inglés, como `/product/123` o `/cart`. Si queremos que los presentadores y las acciones de la URL se traduzcan al alemán (por ejemplo, `/produkt/123` o `/einkaufswagen`), podemos utilizar un diccionario de traducción. Para añadirlo, ya necesitamos una variante "más locuaz" del segundo parámetro: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // cadena en URL => presentador 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Además de filtros para parámetros específicos, también puede definir filtros use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 El parámetro del constructor `SimpleRouter` es un presentador y una acción por defecto, es decir, la acción que se ejecutará si abrimos, por ejemplo, `http://example.com/` sin parámetros adicionales. ```php -// por defecto el presentador es 'Homepage' y la acción es 'default -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// por defecto el presentador es 'Home' y la acción es 'default +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Recomendamos definir SimpleRouter directamente en la [configuración |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ Al procesar la solicitud, debemos devolver al menos el presentador y la acción. ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/es/templates.texy b/application/es/templates.texy index 1448f09a72..761f7a648e 100644 --- a/application/es/templates.texy +++ b/application/es/templates.texy @@ -42,7 +42,9 @@ La ruta a las plantillas se deduce según una lógica simple. Se intenta ver si - `templates//.latte` - `templates/..latte` -Si no encuentra la plantilla, la respuesta es [error 404 |presenters#Error 404 etc.]. +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. + +Si la plantilla tampoco se encuentra allí, la respuesta es un [error 404 |presenters#Error 404 etc.]. 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')`. @@ -148,7 +150,7 @@ En la plantilla creamos enlaces a otros presentadores y acciones de la siguiente Atributo `n:href` es muy útil para etiquetas HTML ``. Si queremos imprimir el enlace en otro lugar, por ejemplo en el texto, utilizamos `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` Para más información, véase [Creación de enlaces |Creating Links]. diff --git a/application/fr/@left-menu.texy b/application/fr/@left-menu.texy index 93d1d53128..38078e6bf0 100644 --- a/application/fr/@left-menu.texy +++ b/application/fr/@left-menu.texy @@ -15,5 +15,8 @@ Application Nette Autres lectures *************** +- [Pourquoi utiliser Nette ? |www:10-reasons-why-nette] +- L'[installation |nette:installation] +- [Créez votre première application ! |quickstart:] - [Meilleures pratiques |best-practices:] - [Dépannage |nette:troubleshooting] diff --git a/application/fr/ajax.texy b/application/fr/ajax.texy index 406327ab56..55c07c6dc6 100644 --- a/application/fr/ajax.texy +++ b/application/fr/ajax.texy @@ -10,9 +10,13 @@ Les applications web modernes fonctionnent aujourd'hui pour moitié sur un serve -Une requête AJAX peut être détectée à l'aide d'une méthode d'un service [encapsulant une requête HTTP |http:request] `$httpRequest->isAjax()` (détecte sur la base de l'en-tête HTTP `X-Requested-With` ). Il existe également une méthode abrégée dans Presenter : `$this->isAjax()`. -Une demande AJAX n'est pas différente d'une demande normale - un présentateur est appelé avec une certaine vue et des paramètres. La réaction du présentateur est également libre : il peut utiliser ses routines pour renvoyer un fragment de code HTML (un snippet), un document XML, un objet JSON ou un morceau de code Javascript. +Demande AJAX .[#toc-ajax-request] +================================= + +Une requête AJAX ne diffère pas d'une requête classique : le diffuseur est appelé avec une vue et des paramètres spécifiques. C'est également au présentateur de décider comment y répondre : il peut utiliser sa propre routine, qui renvoie un fragment de code HTML (extrait HTML), un document XML, un objet JSON ou du code JavaScript. + +Côté serveur, une requête AJAX peut être détectée à l'aide de la méthode de service [encapsulant la requête HTTP |http:request] `$httpRequest->isAjax()` (détection basée sur l'en-tête HTTP `X-Requested-With`). Dans le présentateur, un raccourci est disponible sous la forme de la méthode `$this->isAjax()`. Il existe un objet prétraité appelé `payload` dédié à l'envoi de données au navigateur en JSON. @@ -60,6 +64,21 @@ npm install naja ``` +Pour créer une requête AJAX à partir d'un lien normal (signal) ou d'une soumission de formulaire, il suffit de marquer le lien, le formulaire ou le bouton concerné avec la classe `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Extraits de texte .[#toc-snippets] ================================== @@ -149,7 +168,7 @@ Vous ne pouvez pas redessiner un extrait dynamique directement (redessiner `item Dans l'exemple ci-dessus, vous devez vous assurer que, pour une requête AJAX, un seul élément sera ajouté au tableau `$list`. Par conséquent, la boucle `foreach` n'imprimera qu'un seul extrait dynamique. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/fr/bootstrap.texy b/application/fr/bootstrap.texy index fbb3566d18..8bea659e34 100644 --- a/application/fr/bootstrap.texy +++ b/application/fr/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -Dans les fichiers de configuration, nous pouvons écrire la notation usuelle `%projectId%` pour accéder au paramètre nommé `projectId`. Par défaut, le configurateur renseigne les paramètres suivants : `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` et `consoleMode`. +Dans les fichiers de configuration, nous pouvons écrire la notation habituelle `%projectId%` pour accéder au paramètre nommé `projectId`. Paramètres dynamiques .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Paramètres par défaut .[#toc-default-parameters] +------------------------------------------------ + +Vous pouvez utiliser les paramètres statiques suivants dans les fichiers de configuration : + +- `%appDir%` est le chemin absolu vers le répertoire du fichier `Bootstrap.php` +- `%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 +- `%debugMode%` indique si l'application est en mode débogage +- `%consoleMode%` indique si la demande provient de la ligne de commande + + Services importés .[#toc-imported-services] ------------------------------------------- diff --git a/application/fr/components.texy b/application/fr/components.texy index b86da2b448..9c269642a6 100644 --- a/application/fr/components.texy +++ b/application/fr/components.texy @@ -233,31 +233,34 @@ Dans le modèle, ces messages sont disponibles dans la variable `$flashes` sous Paramètres persistants .[#toc-persistent-parameters] ==================================================== -Il est souvent nécessaire de conserver un paramètre dans un composant pendant toute la durée de l'utilisation de ce dernier. Il peut s'agir par exemple du numéro de la page dans la pagination. Ce paramètre doit être marqué comme persistant à l'aide de l'annotation `@persistent`. +Les paramètres persistants sont utilisés pour maintenir l'état des composants entre les différentes requêtes. Leur valeur reste inchangée même après avoir cliqué sur un lien. Contrairement aux données de session, ils sont transférés dans l'URL. Ils sont également transférés automatiquement, y compris les liens créés dans d'autres composants de la même page. + +Par exemple, vous avez un composant de pagination de contenu. Il peut y avoir plusieurs composants de ce type sur une page. Vous souhaitez que tous les composants restent sur leur page actuelle lorsque vous cliquez sur le lien. C'est pourquoi nous faisons du numéro de page (`page`) un paramètre persistant. + +La création d'un paramètre persistant est extrêmement simple dans Nette. Il suffit de créer une propriété publique et de la baliser avec l'attribut : (auparavant `/** @persistent */` était utilisé) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // cette ligne est importante + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // doit être publique } ``` -Ce paramètre sera automatiquement transmis dans chaque lien en tant que paramètre `GET` jusqu'à ce que l'utilisateur quitte la page avec ce composant. +Nous vous recommandons d'inclure le type de données (par exemple `int`) avec la propriété, et vous pouvez également inclure une valeur par défaut. Les valeurs des paramètres peuvent être [validées |#Validation of Persistent Parameters]. -.[caution] -Ne faites jamais aveuglément confiance aux paramètres persistants car ils peuvent être facilement falsifiés (en écrasant l'URL). Vérifiez, par exemple, si le numéro de page se situe dans l'intervalle correct. +Vous pouvez modifier la valeur d'un paramètre persistant lors de la création d'un lien : -En PHP 8, vous pouvez également utiliser des attributs pour marquer les paramètres persistants : +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Ou il peut être *réinitialisé*, c'est-à-dire supprimé de l'URL. Il prendra alors sa valeur par défaut : -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ Les composants d'une application Nette sont les parties réutilisables d'une app 1) il peut être rendu dans un modèle 2) Il sait quelle partie de lui-même doit être rendue lors d'une [requête AJAX |ajax#invalidation] (snippets). -3) il a la capacité de stocker son état dans une URL (paramètres de persistance) +3) il a la capacité de stocker son état dans une URL (paramètres persistants) 4) il a la capacité de répondre aux actions de l'utilisateur (signaux) 5) il crée une structure hiérarchique (dont la racine est le présentateur). @@ -403,6 +406,33 @@ Cycle de vie du composant .[#toc-life-cycle-of-component] [* lifecycle-component.svg *] *** *Cycle de vie du composant* .<> +Validation des paramètres persistants .[#toc-validation-of-persistent-parameters] +--------------------------------------------------------------------------------- + +Les valeurs des [paramètres persistants |#persistent parameters] reçus des URL sont écrites dans les propriétés par la méthode `loadState()`. Elle vérifie également si le type de données spécifié pour la propriété correspond, sinon elle répondra par une erreur 404 et la page ne sera pas affichée. + +Il ne faut jamais faire aveuglément confiance aux paramètres persistants, car ils peuvent facilement être remplacés par l'utilisateur dans l'URL. Par exemple, voici comment nous vérifions si le numéro de page `$this->page` est supérieur à 0. Une bonne façon de le faire est de surcharger la méthode `loadState()` mentionnée ci-dessus : + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // ici est définie la page $this->page + // suit la vérification de la valeur de l'utilisateur: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Le processus inverse, c'est-à-dire la collecte de valeurs à partir de propriétés persistantes, est géré par la méthode `saveState()`. + + Signaux en profondeur .[#toc-signals-in-depth] ---------------------------------------------- diff --git a/application/fr/creating-links.texy b/application/fr/creating-links.texy index 9194ce7f9d..c6786ac362 100644 --- a/application/fr/creating-links.texy +++ b/application/fr/creating-links.texy @@ -52,7 +52,7 @@ Les [paramètres |presenters#persistent parameters] dits [persistants |presenter L'attribut `n:href` est très pratique pour les balises HTML ``. Si nous voulons imprimer le lien ailleurs, par exemple dans le texte, nous utilisons `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ Le format est pris en charge par toutes les balises Latte et toutes les méthode La forme de base est donc `Presenter:action`: ```latte -homepage +home ``` Si nous établissons un lien avec l'action du présentateur actuel, nous pouvons omettre son nom : ```latte -homepage +home ``` Si l'action est `default`, nous pouvons l'omettre, mais les deux points doivent rester : ```latte -homepage +home ``` Les liens peuvent également pointer vers d'autres [modules]. Ici, on distingue les liens relatifs aux sous-modules et les liens absolus. Le principe est analogue à celui des chemins d'accès aux disques, mais les deux-points remplacent les barres obliques. Supposons que le présentateur actuel fasse partie du module `Front`, nous écrirons alors : @@ -119,7 +119,7 @@ Un cas particulier est la [liaison à lui-même |#Links to Current Page]. Ici, n Nous pouvons créer un lien vers une certaine partie de la page HTML par le biais de ce que l'on appelle un fragment après le symbole dièse `#` : ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ Chemins absolus .[#toc-absolute-paths] Les liens générés par `link()` ou `n:href` sont toujours des chemins absolus (c'est-à-dire qu'ils commencent par `/`), mais pas des URL absolus avec un protocole et un domaine comme `https://domain`. -Pour générer une URL absolue, ajoutez deux barres obliques au début (par exemple, `n:href="//Homepage:"`). Vous pouvez aussi faire en sorte que le diffuseur ne génère que des liens absolus en définissant `$this->absoluteUrls = true`. +Pour générer une URL absolue, ajoutez deux barres obliques au début (par exemple, `n:href="//Home:"`). Vous pouvez aussi faire en sorte que le diffuseur ne génère que des liens absolus en définissant `$this->absoluteUrls = true`. Lien vers la page actuelle .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ Comme les [composants |components] sont des unités distinctes réutilisables qu Si nous voulons créer un lien vers les présentateurs dans le modèle de composant, nous utilisons la balise `{plink}`: ```latte -homepage +home ``` ou dans le code ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/fr/how-it-works.texy b/application/fr/how-it-works.texy index fa3e6e3d48..065eb5aa33 100644 --- a/application/fr/how-it-works.texy +++ b/application/fr/how-it-works.texy @@ -23,10 +23,10 @@ La structure des répertoires ressemble à ceci : web-project/ ├── app/ ← répertoire avec application │ ├── Presenters/ ← classes d'presenter -│ │ ├── HomepagePresenter.php ← Homepage classe des présentateurs +│ │ ├── HomePresenter.php ← Home classe des présentateurs │ │ └── templates/ ← répertoire des modèles │ │ ├── @layout.latte ← modèle de disposition partagée -│ │ └── Homepage/ ← Modèles pour le présentateur de la page d'accueil +│ │ └── 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 │ └── Bootstrap.php ← classe de démarrage Bootstrap @@ -134,10 +134,10 @@ Juste pour être sûr, essayons de récapituler l'ensemble du processus avec une 1) l'URL sera `https://example.com` 2) nous démarrons l'application, nous créons un conteneur et nous l'exécutons `Application::run()` -3) le routeur décode l'URL comme une paire `Homepage:default` -4) un objet `HomepagePresenter` est créé +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/Homepage/default.latte` avec une mise en page `templates/@layout.latte` est rendu +6) un modèle `templates/Home/default.latte` avec une mise en page `templates/@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 4b774c8fb7..ade58f61b9 100644 --- a/application/fr/modules.texy +++ b/application/fr/modules.texy @@ -104,7 +104,7 @@ 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`. -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 `Homepage` doit correspondre à la classe `App\Presenters\HomepagePresenter`. Ceci peut être réalisé par la configuration suivante : +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 : ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Maintenant, le présentateur `Front:Homepage` correspond à la classe `App\Modules\Front\Presenters\HomepagePresenter` et le présentateur `Admin:Dashboard` à la classe `App\Modules\Admin\Presenters\DashboardPresenter`. +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`. 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 : diff --git a/application/fr/presenters.texy b/application/fr/presenters.texy index f60d64c798..59db98b157 100644 --- a/application/fr/presenters.texy +++ b/application/fr/presenters.texy @@ -158,7 +158,7 @@ La méthode `forward()` permet de passer immédiatement au nouveau présentateur $this->forward('Product:show'); ``` -Exemple de redirection temporaire avec le code HTTP 302 ou 303 : +Exemple de redirection temporaire avec le code HTTP 302 (ou 303, si la méthode de requête actuelle est POST) : ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ Pour obtenir une redirection permanente avec le code HTTP 301, utilisez : $this->redirectPermanent('Product:show', $id); ``` -Vous pouvez rediriger vers une autre URL en dehors de l'application avec la méthode `redirectUrl()`: +Vous pouvez rediriger vers une autre URL en dehors de l'application en utilisant la méthode `redirectUrl()`. Le code HTTP peut être spécifié comme deuxième paramètre, la valeur par défaut étant 302 (ou 303, si la méthode de requête actuelle est POST) : ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Paramètres persistants .[#toc-persistent-parameters] ==================================================== -Les paramètres persistants sont **transférés automatiquement** dans les liens. Cela signifie que nous ne devons pas les spécifier explicitement dans chaque `link()` ou `n:href` du modèle, mais ils seront tout de même transférés. +Les paramètres persistants sont utilisés pour maintenir l'état entre les différentes requêtes. Leur valeur reste inchangée même après avoir cliqué sur un lien. Contrairement aux données de session, ils sont transmis dans l'URL. Cette opération est entièrement automatique, il n'est donc pas nécessaire de les indiquer explicitement dans `link()` ou `n:href`. -Si votre application comporte plusieurs versions linguistiques, la langue actuelle est un paramètre qui doit toujours faire partie de l'URL. Et il serait incroyablement fatigant de le mentionner dans chaque lien. Ce n'est pas nécessaire avec Nette. Nous marquons simplement le paramètre `lang` comme persistant de cette manière : +Exemple d'utilisation ? Vous avez une application multilingue. La langue réelle est un paramètre qui doit toujours faire partie de l'URL. Mais il serait incroyablement fastidieux de l'inclure dans chaque lien. Vous en faites donc un paramètre persistant, nommé `lang`, qui se chargera de lui-même. C'est super ! + +La création d'un paramètre persistant est extrêmement simple dans Nette. Il suffit de créer une propriété publique et de la baliser avec l'attribut : (auparavant `/** @persistent */` était utilisé) ```php +use Nette\Application\Attributes\Persistent; // cette ligne est importante + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // doit être publique } ``` -Si la valeur actuelle du paramètre `lang` est `'en'`, alors l'URL créée avec `link()` ou `n:href` dans le modèle contiendra `lang=en`. Excellent ! - -Cependant, nous pouvons également ajouter le paramètre `lang` et modifier ainsi sa valeur : +Si `$this->lang` a une valeur telle que `'en'`, les liens créés à l'aide de `link()` ou `n:href` contiendront également le paramètre `lang=en`. Et lorsque le lien sera cliqué, il s'agira à nouveau de `$this->lang = 'en'`. -```latte -detail in English -``` - -Ou, à l'inverse, on peut le supprimer en le mettant à null : - -```latte -click here -``` +Pour les propriétés, nous vous recommandons d'indiquer le type de données (par exemple `string`) et vous pouvez également inclure une valeur par défaut. Les valeurs des paramètres peuvent être [validées |#Validation of Persistent Parameters]. -La variable persistante doit être déclarée comme publique. On peut également spécifier une valeur par défaut. Si le paramètre a la même valeur que la valeur par défaut, il ne sera pas inclus dans l'URL. +Les paramètres persistants sont transmis par défaut entre toutes les actions d'un présentateur donné. Pour les transmettre entre plusieurs présentateurs, vous devez les définir : -La persistance reflète la hiérarchie des classes de présentateurs, ainsi le paramètre défini dans un certain présentateur ou trait est ensuite automatiquement transféré à chaque présentateur qui en hérite ou qui utilise le même trait. - -En PHP 8, vous pouvez également utiliser des attributs pour marquer les paramètres persistants : +- dans un ancêtre commun dont les présentateurs héritent +- dans le trait que les présentateurs utilisent : ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Vous pouvez modifier la valeur d'un paramètre persistant lors de la création d'un lien : + +```latte +detail in Czech +``` + +Ou il peut être *réinitialisé*, c'est-à-dire supprimé de l'URL. Il prendra alors sa valeur par défaut : + +```latte +click ``` @@ -302,7 +310,32 @@ Ce que nous avons montré jusqu'à présent dans ce chapitre suffira probablemen Exigences et paramètres .[#toc-requirement-and-parameters] ---------------------------------------------------------- -La demande traitée par le diffuseur est l'objet [api:Nette\Application\Request] et est renvoyée par la méthode du diffuseur `getRequest()`. Elle comprend un tableau de paramètres et chacun d'entre eux appartient soit à certains des composants, soit directement au présentateur (qui est en fait aussi un composant, bien qu'il soit spécial). Nette redistribue donc les paramètres et les passe entre les différents composants (et le présentateur) en appelant la méthode `loadState(array $params)`, qui est décrite plus en détail dans le chapitre [Composants |Components]. Les paramètres peuvent être obtenus par la méthode `getParameters(): array`, individuellement en utilisant `getParameter($name)`. Les valeurs des paramètres sont des chaînes de caractères ou des tableaux de chaînes de caractères, ce sont essentiellement des données brutes obtenues directement d'une URL. +La requête traitée par le présentateur est l'objet [api:Nette\Application\Request] et est renvoyée par la méthode du présentateur `getRequest()`. Elle comprend un tableau de paramètres et chacun d'entre eux appartient soit à l'un des composants, soit directement au présentateur (qui est également un composant, bien qu'il soit spécial). Nette redistribue donc les paramètres et les transmet entre les différents composants (et le présentateur) en appelant la méthode `loadState(array $params)`. Les paramètres peuvent être obtenus par la méthode `getParameters(): array`, individuellement en utilisant `getParameter($name)`. Les valeurs des paramètres sont des chaînes ou des tableaux de chaînes, il s'agit essentiellement de données brutes obtenues directement à partir d'une URL. + + +Validation des paramètres persistants .[#toc-validation-of-persistent-parameters] +--------------------------------------------------------------------------------- + +Les valeurs des [paramètres persistants |#persistent parameters] reçus des URL sont écrites dans les propriétés par la méthode `loadState()`. Elle vérifie également si le type de données spécifié dans la propriété correspond, sinon elle répondra par une erreur 404 et la page ne sera pas affichée. + +Ne faites jamais aveuglément confiance aux paramètres persistants, car ils peuvent facilement être remplacés par l'utilisateur dans l'URL. Par exemple, voici comment nous vérifions si `$this->lang` fait partie des langues prises en charge. Une bonne façon de le faire est de surcharger la méthode `loadState()` mentionnée ci-dessus : + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // ici est défini le $this->lang + // suit la vérification de la valeur de l'utilisateur: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Sauvegarde et restauration de la demande .[#toc-save-and-restore-the-request] diff --git a/application/fr/routing.texy b/application/fr/routing.texy index f387a22ac2..a2ec2aff40 100644 --- a/application/fr/routing.texy +++ b/application/fr/routing.texy @@ -93,12 +93,12 @@ La route acceptera maintenant l'URL `https://any-domain.com/chronicle/` avec le Bien entendu, le nom du présentateur et de l'action peut également être un paramètre. Par exemple : ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Cette route accepte, par exemple, une URL sous la forme `/article/edit` resp. `/catalog/list` et les traduit en présentateurs et actions `Article:edit` resp. `Catalog:list`. -Elle donne également aux paramètres `presenter` et `action` des valeurs par défaut`Homepage` et `default` et ils sont donc facultatifs. Ainsi, la route accepte également une URL `/article` et la traduit en `Article:default`. Ou vice versa, un lien vers `Product:default` génère un chemin `/product`, un lien vers le défaut `Homepage:default` génère un chemin `/`. +Elle donne également aux paramètres `presenter` et `action` des valeurs par défaut`Home` et `default` et ils sont donc facultatifs. Ainsi, la route accepte également une URL `/article` et la traduit en `Article:default`. Ou vice versa, un lien vers `Product:default` génère un chemin `/product`, un lien vers le défaut `Home:default` génère un chemin `/`. Le masque peut décrire non seulement le chemin relatif basé sur la racine du site, mais aussi le chemin absolu lorsqu'il commence par une barre oblique, ou même l'URL absolue entière lorsqu'elle commence par deux barres obliques : @@ -160,7 +160,7 @@ Les séquences peuvent être librement imbriquées et combinées : ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // URLs acceptées: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Les paramètres optionnels (c'est-à-dire les paramètres ayant une valeur par défaut) sans crochets se comportent comme s'ils étaient enveloppés de cette façon : ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // équivaut à: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Pour modifier la façon dont la barre oblique la plus à droite est générée, c'est-à-dire qu'au lieu de `/homepage/`, vous obtenez `/homepage`, ajustez la route de cette façon : +Pour modifier la façon dont la barre oblique la plus à droite est générée, c'est-à-dire qu'au lieu de `/home/`, vous obtenez `/home`, ajustez la route de cette façon : ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ Le deuxième paramètre de la route, que nous écrivons souvent sous le format ` ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filtres et traductions .[#toc-filters-and-translations] C'est une bonne pratique d'écrire le code source en anglais, mais que faire si vous avez besoin que l'URL de votre site Web soit traduite dans une autre langue ? Des routes simples telles que : ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` généreront des URL en anglais, comme `/product/123` ou `/cart`. Si nous voulons que les présentateurs et les actions dans l'URL soient traduits en allemand (par exemple `/produkt/123` ou `/einkaufswagen`), nous pouvons utiliser un dictionnaire de traduction. Pour l'ajouter, nous avons déjà besoin d'une variante "plus bavarde" du deuxième paramètre : @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // string dans l'URL => presenter 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Outre les filtres pour des paramètres spécifiques, vous pouvez également déf use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Le paramètre du constructeur `SimpleRouter` est un présentateur et une action par défaut, c'est-à-dire une action à exécuter si nous ouvrons par exemple `http://example.com/` sans paramètres supplémentaires. ```php -// par défaut, le présentateur est 'Homepage' et l'action 'default'. -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// par défaut, le présentateur est 'Home' et l'action 'default'. +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Nous recommandons de définir SimpleRouter directement dans la [configuration |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ Lors du traitement de la demande, nous devons retourner au moins le diffuseur et ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/fr/templates.texy b/application/fr/templates.texy index 5ee5236ba2..8ecd733495 100644 --- a/application/fr/templates.texy +++ b/application/fr/templates.texy @@ -42,7 +42,9 @@ Le chemin vers les modèles est déduit selon une logique simple. Il essaie de v - `templates//.latte` - `templates/..latte` -S'il ne trouve pas le modèle, la réponse est une [erreur 404 |presenters#Error 404 etc.]. +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. + +Si le modèle n'y est pas trouvé non plus, la réponse est une [erreur 404 |presenters#Error 404 etc.]. 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')`. @@ -148,7 +150,7 @@ Dans le modèle, nous créons des liens vers d'autres présentateurs et actions L'attribut `n:href` est très pratique pour les balises HTML ``. Si nous voulons imprimer le lien ailleurs, par exemple dans le texte, nous utilisons `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` Pour plus d'informations, voir [Création de liens |Creating Links]. diff --git a/application/hu/@left-menu.texy b/application/hu/@left-menu.texy index cfe693b111..ab68654c37 100644 --- a/application/hu/@left-menu.texy +++ b/application/hu/@left-menu.texy @@ -15,5 +15,8 @@ Nette alkalmazás További olvasnivaló ******************* +- [Miért használja a Nette-et? |www:10-reasons-why-nette] +- [Telepítés |nette:installation] +- [Készítse el első alkalmazását! |quickstart:] - [Legjobb gyakorlatok |best-practices:] - [Hibaelhárítás |nette:troubleshooting] diff --git a/application/hu/ajax.texy b/application/hu/ajax.texy index 4d50da3143..fb0a6e08b8 100644 --- a/application/hu/ajax.texy +++ b/application/hu/ajax.texy @@ -10,9 +10,13 @@ A modern webes alkalmazások manapság félig a szerveren, félig a böngészőb -Egy AJAX-kérés a `$httpRequest->isAjax()` [HTTP-kérést kapszulázó |http:request] szolgáltatás módszerével detektálható (a `X-Requested-With` HTTP-fejléc alapján detektál). A prezenterben is van egy rövidített módszer: `$this->isAjax()`. -Az AJAX-kérés nem különbözik a normál kéréstől - a prezentert egy bizonyos nézettel és paraméterekkel hívják meg. Az is a prezenteren múlik, hogy miként reagál: a rutinjaival vagy egy HTML kódrészletet (snippet), egy XML-dokumentumot, egy JSON objektumot vagy egy Javascript kódrészletet adhat vissza. +AJAX kérés .[#toc-ajax-request] +=============================== + +Az AJAX-kérés nem különbözik a klasszikus kéréstől - a bemutatót egy adott nézettel és paraméterekkel hívják meg. Az is a prezenteren múlik, hogyan válaszol rá: használhat saját rutint, amely egy HTML kódrészletet (HTML snippet), egy XML-dokumentumot, egy JSON-objektumot vagy JavaScript-kódot ad vissza. + +Kiszolgálói oldalon az AJAX-kérés a [HTTP-kérést kapszulázó |http:request] szolgáltatási módszerrel detektálható `$httpRequest->isAjax()` (a HTTP fejléc alapján detektál `X-Requested-With`). A prezenteren belül a `$this->isAjax()` metódus formájában egy rövidítés áll rendelkezésre. Létezik egy `payload` nevű előfeldolgozott objektum, amely arra szolgál, hogy az adatokat JSON-ban küldje el a böngészőnek. @@ -60,6 +64,21 @@ npm install naja ``` +Ahhoz, hogy AJAX-kérést hozzon létre egy hagyományos linkből (jel) vagy űrlapküldésből, egyszerűen jelölje meg az adott linket, űrlapot vagy gombot a `ajax` osztállyal: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Snippets .[#toc-snippets] ========================= @@ -149,7 +168,7 @@ Egy dinamikus snippet közvetlenül nem rajzolható újra (a `item-1` újrarajzo A fenti példában gondoskodnia kell arról, hogy egy AJAX-kérés esetén csak egy elem kerüljön a `$list` tömbhöz, ezért a `foreach` ciklus csak egy dinamikus snippetet fog kiírni. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/hu/bootstrap.texy b/application/hu/bootstrap.texy index 941bad5c80..5d0a614439 100644 --- a/application/hu/bootstrap.texy +++ b/application/hu/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -A konfigurációs fájlokban a `%projectId%` szokásos jelölést írhatjuk a `projectId` nevű paraméter eléréséhez. Alapértelmezés szerint a konfigurátor a következő paramétereket tölti fel: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` és `consoleMode`. +A konfigurációs fájlokban a `%projectId%` szokásos jelölést írhatjuk a `projectId` nevű paraméter eléréséhez. Dinamikus paraméterek .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Alapértelmezett paraméterek .[#toc-default-parameters] +------------------------------------------------------ + +A konfigurációs fájlokban a következő statikus paramétereket használhatja: + +- `%appDir%` a `Bootstrap.php` fájl könyvtárának abszolút elérési útja. +- `%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. +- `%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. + + Importált szolgáltatások .[#toc-imported-services] -------------------------------------------------- diff --git a/application/hu/components.texy b/application/hu/components.texy index 6640b18b34..839db3273e 100644 --- a/application/hu/components.texy +++ b/application/hu/components.texy @@ -233,31 +233,34 @@ A sablonban ezek az üzenetek a `$flashes` változóban állnak rendelkezésre, Állandó paraméterek .[#toc-persistent-parameters] ================================================= -Gyakran van szükség arra, hogy egy komponensben valamilyen paramétert a komponenssel való munka teljes ideje alatt megőrizzünk. Ez lehet például az oldalszám a lapozásban. Ezt a paramétert a `@persistent` megjegyzéssel állandónak kell jelölni. +A tartós paraméterek a komponensek állapotának különböző kérések közötti fenntartására szolgálnak. Értékük a linkre való kattintás után is ugyanaz marad. A munkamenetadatokkal ellentétben ezek az URL-ben kerülnek átvitelre. És automatikusan továbbításra kerülnek, beleértve az ugyanazon az oldalon lévő más komponensekben létrehozott linkeket is. + +Például van egy tartalom lapozó komponens. Egy oldalon több ilyen komponens is lehet. És azt szeretné, hogy minden komponens az aktuális oldalon maradjon, amikor a linkre kattint. Ezért az oldalszámot (`page`) állandó paraméterré tesszük. + +A perzisztens paraméter létrehozása rendkívül egyszerű a Nette-ben. Csak hozzon létre egy nyilvános tulajdonságot, és címkézze meg az attribútummal: (korábban a `/** @persistent */` volt használatos). ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // ez a sor fontos + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // nyilvánosnak kell lennie } ``` -Ez a paraméter automatikusan átadásra kerül minden linkben a `GET` paraméterként, amíg a felhasználó el nem hagyja az oldalt ezzel a komponenssel. +Javasoljuk, hogy a tulajdonsághoz adja meg az adattípust (pl. `int`), és alapértelmezett értéket is megadhat. A paraméterek értékei [érvényesíthetők |#Validation of Persistent Parameters]. -.[caution] -Soha ne bízzon vakon a tartós paraméterekben, mert könnyen meghamisíthatók (az URL felülírásával). Ellenőrizze például, hogy az oldalszám a megfelelő intervallumon belül van-e. +A tartós paraméterek értékét a hivatkozás létrehozásakor módosíthatja: -A PHP 8-ban attribútumokat is használhat a perzisztens paraméterek jelölésére: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Vagy *visszaállítható*, azaz eltávolítható az URL-ből. Ekkor az alapértelmezett értéket veszi fel: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ A komponensek egy Nette alkalmazásban a webalkalmazás újrafelhasználható r 1) egy sablonban megjeleníthető. 2) tudja, hogy egy [AJAX-kérés |ajax#invalidation] során melyik részét kell renderelni (snippet) -3) képes az állapotát egy URL-ben tárolni (perzisztencia paraméterek) +3) képes az állapotát egy URL-ben tárolni (állandó paraméterek). 4) képes reagálni a felhasználói műveletekre (jelek) 5) hierarchikus struktúrát hoz létre (ahol a gyökér a bemutató) @@ -403,6 +406,33 @@ A komponens életciklusa .[#toc-life-cycle-of-component] [* lifecycle-component.svg *] *** *A komponens életciklusa* .<> +A tartós paraméterek validálása .[#toc-validation-of-persistent-parameters] +--------------------------------------------------------------------------- + +Az URL-ekből kapott [állandó paraméterek |#persistent parameters] értékeit a `loadState()` módszer írja a tulajdonságokba. A módszer azt is ellenőrzi, hogy a tulajdonsághoz megadott adattípus megfelel-e, ellenkező esetben 404-es hibával válaszol, és az oldal nem jelenik meg. + +Soha ne bízzunk vakon a perzisztens paraméterekben, mert azokat a felhasználó könnyen felülírhatja az URL-ben. Például így ellenőrizzük, hogy a `$this->page` oldalszám nagyobb-e 0-nál. Erre jó megoldás a fent említett `loadState()` metódus felülírása: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // itt van beállítva a $this->page + // követi a felhasználói értékek ellenőrzését: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Az ellenkező folyamatot, vagyis az értékek begyűjtését a perzisztens tulajdonságokból a `saveState()` metódus kezeli. + + Mélységi jelek .[#toc-signals-in-depth] --------------------------------------- diff --git a/application/hu/creating-links.texy b/application/hu/creating-links.texy index 801a922bbe..8f7c11af58 100644 --- a/application/hu/creating-links.texy +++ b/application/hu/creating-links.texy @@ -52,7 +52,7 @@ Az úgynevezett [tartós paraméterek |presenters#persistent parameters] is auto A `n:href` attribútum nagyon hasznos a HTML címkékhez. ``. Ha a linket máshol, például a szövegben akarjuk kiírni, akkor a `{link}` attribútumot használjuk: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ A formátumot az összes Latte tag és az összes olyan presenter módszer támo Az alapforma tehát `Presenter:action`: ```latte -homepage +home ``` Ha az aktuális bemutató akciójára hivatkozunk, elhagyhatjuk a nevét: ```latte -homepage +home ``` Ha a művelet a `default`, elhagyhatjuk, de a kettőspontnak meg kell maradnia: ```latte -homepage +home ``` A linkek más [modulokra |modules] is mutathatnak. Itt a linkeket megkülönböztetjük az almodulokhoz viszonyított, illetve abszolút linkekre. Az elv analóg a lemezes elérési utakkal, csak a kötőjelek helyett kettőspontok vannak. Tegyük fel, hogy a tényleges bemutató a `Front` modul része, akkor azt írjuk: @@ -119,7 +119,7 @@ Speciális eset a [saját magára való hivatkozás |#Links to Current Page]. It A HTML oldal egy bizonyos részére a `#` hash szimbólum után egy úgynevezett fragmentummal tudunk hivatkozni: ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ Abszolút elérési utak .[#toc-absolute-paths] A `link()` vagy `n:href` által generált linkek mindig abszolút elérési utak (azaz `/`-vel kezdődnek), de nem abszolút URL-ek protokollal és domainnel, mint például `https://domain`. -Abszolút URL létrehozásához adjon hozzá két kötőjelet az elejéhez (pl. `n:href="//Homepage:"`). Vagy a `$this->absoluteUrls = true` beállítással úgy is beállíthatja a prezentert, hogy csak abszolút linkeket generáljon. +Abszolút URL létrehozásához adjon hozzá két kötőjelet az elejéhez (pl. `n:href="//Home:"`). Vagy a `$this->absoluteUrls = true` beállítással úgy is beállíthatja a prezentert, hogy csak abszolút linkeket generáljon. Link az aktuális oldalra .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ Mivel a [komponensek |components] különálló, újrafelhasználható egységek Ha a komponenssablonban előadókra akarunk hivatkozni, akkor a `{plink}` címkét használjuk: ```latte -homepage +home ``` vagy a kódban ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/hu/how-it-works.texy b/application/hu/how-it-works.texy index d96f2efe4b..59f923ab51 100644 --- a/application/hu/how-it-works.texy +++ b/application/hu/how-it-works.texy @@ -23,10 +23,10 @@ A könyvtárszerkezet valahogy így néz ki: web-project/ ├── app/ ← directory with application │ ├── Presenters/ ← presenter classes -│ │ ├── HomepagePresenter.php ← Homepage presenter class +│ │ ├── HomePresenter.php ← Home presenter class │ │ └── templates/ ← templates directory │ │ ├── @layout.latte ← template of shared layout -│ │ └── Homepage/ ← templates for Homepage presenter +│ │ └── Home/ ← templates for Home presenter │ │ └── default.latte ← template for action `default` │ ├── Router/ ← configuration of URL addresses │ └── Bootstrap.php ← booting class Bootstrap @@ -134,10 +134,10 @@ A biztonság kedvéért próbáljuk meg az egész folyamatot egy kicsit más URL 1) az URL a következő lesz `https://example.com` 2) elindítjuk az alkalmazást, létrehozunk egy konténert és futtatjuk `Application::run()` -3) a router dekódolja az URL-t, mint egy párat `Homepage:default` -4) létrejön egy `HomepagePresenter` objektum +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/Homepage/default.latte` sablon `templates/@layout.latte` elrendezéssel megjelenik. +6) egy `templates/Home/default.latte` sablon `templates/@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 bb5b84f6c6..5dde2d1a36 100644 --- a/application/hu/modules.texy +++ b/application/hu/modules.texy @@ -104,7 +104,7 @@ Feltérképezés .[#toc-mapping] 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. -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 `Homepage`, a `App\Presenters\HomepagePresenter` osztályhoz kell kapcsolódnia. Ezt a következő konfigurációval érhetjük el: +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: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Most a `Front:Homepage` bemutatót a `App\Modules\Front\Presenters\HomepagePresenter` osztályra, a `Admin:Dashboard` bemutatót pedig a `App\Modules\Admin\Presenters\DashboardPresenter` osztályra képezzük le. +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. 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: diff --git a/application/hu/presenters.texy b/application/hu/presenters.texy index 28291eea60..d2c2238c1a 100644 --- a/application/hu/presenters.texy +++ b/application/hu/presenters.texy @@ -158,7 +158,7 @@ A `forward()` azonnal átvált az új bemutatóra HTTP átirányítás nélkül: $this->forward('Product:show'); ``` -Példa ideiglenes átirányításra 302 vagy 303 HTTP-kóddal: +Példa egy úgynevezett ideiglenes átirányításra 302-es HTTP-kóddal (vagy 303-as kóddal, ha az aktuális kérési mód POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ A 301-es kódú HTTP-kóddal történő állandó átirányítás eléréséhez $this->redirectPermanent('Product:show', $id); ``` -A `redirectUrl()` módszerrel átirányíthat egy másik, az alkalmazáson kívüli URL-címre: +A `redirectUrl()` módszerrel átirányíthat egy másik, az alkalmazáson kívüli URL-címre. A HTTP-kódot a második paraméterként lehet megadni, az alapértelmezett érték 302 (vagy 303, ha az aktuális kérési mód POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Tartós paraméterek .[#toc-persistent-parameters] ================================================ -A tartós paraméterek **automatikusan** kerülnek átvitelre a hivatkozásokban. Ez azt jelenti, hogy nem kell őket explicit módon megadni minden egyes `link()` vagy `n:href` sablonban, de ettől még átvitelre kerülnek. +A perzisztens paraméterek a különböző kérések közötti állapot fenntartására szolgálnak. Értékük a linkre való kattintás után is ugyanaz marad. A munkamenetadatokkal ellentétben ezek az URL-ben kerülnek átadásra. Ez teljesen automatikus, így nincs szükség a `link()` vagy a `n:href` oldalon történő kifejezett megadásukra. -Ha az alkalmazásunknak több nyelvi változata van, akkor az aktuális nyelv olyan paraméter, amelynek mindig az URL része kell, hogy legyen. És hihetetlenül fárasztó lenne minden linkben megemlíteni. A Nette esetében erre nincs szükség. Egyszerűen csak a `lang` paramétert jelöljük így állandónak: +Felhasználási példa? Van egy többnyelvű alkalmazása. Az aktuális nyelv egy olyan paraméter, amelynek mindig az URL része kell, hogy legyen. De hihetetlenül fárasztó lenne minden linkben feltüntetni. Ezért egy állandó paramétert hozol létre, amelynek a neve `lang`, és ez önmagát hordozza. Király! + +A tartós paraméter létrehozása rendkívül egyszerű a Nette-ben. Csak hozzon létre egy nyilvános tulajdonságot, és jelölje meg az attribútummal: (korábban a `/** @persistent */` volt használatos). ```php +use Nette\Application\Attributes\Persistent; // ez a sor fontos + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // nyilvánosnak kell lennie } ``` -Ha a `lang` paraméter aktuális értéke `'en'`, akkor a sablonban a `link()` vagy `n:href` címmel létrehozott URL a `lang=en` címet fogja tartalmazni. Remek! - -Azonban a `lang` paramétert is hozzáadhatjuk, és ezzel megváltoztathatjuk az értékét: +Ha a `$this->lang` értéke például `'en'`, akkor a `link()` vagy `n:href` használatával létrehozott linkek a `lang=en` paramétert is tartalmazni fogják. És amikor a linkre kattintunk, akkor ismét a `$this->lang = 'en'` lesz. -```latte -detail in English -``` - -Vagy fordítva, eltávolíthatjuk a null értékre állításával: - -```latte -click here -``` +A tulajdonságok esetében javasoljuk, hogy adja meg az adattípust (pl. `string`), és egy alapértelmezett értéket is megadhat. A paraméterek értékei [érvényesíthetők |#Validation of Persistent Parameters]. -A tartós változót nyilvánosnak kell deklarálni. Megadhatunk egy alapértelmezett értéket is. Ha a paraméter értéke megegyezik az alapértelmezett értékkel, akkor az URL-ben nem fog szerepelni. +A perzisztens paraméterek alapértelmezés szerint egy adott bemutató összes művelete között átadásra kerülnek. Több prezenter között történő átadásukhoz vagy meg kell határozni őket: -A perzisztencia tükrözi a prezenter osztályok hierarchiáját, így egy bizonyos prezenterben vagy tulajdonságban definiált paraméter automatikusan átkerül minden olyan prezenterbe, amelyik tőle öröklődik vagy ugyanazt a tulajdonságot használja. - -A PHP 8-ban attribútumokat is használhat a perzisztens paraméterek jelölésére: +- egy közös ősben, amelytől az előadók öröklik a paramétereket. +- abban a tulajdonságban, amelyet az előadók használnak: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Megváltoztathatja egy állandó paraméter értékét a hivatkozás létrehozásakor: + +```latte +detail in Czech +``` + +Vagy *visszaállítható*, azaz eltávolítható az URL-ből. Ekkor az alapértelmezett értéket veszi fel: + +```latte +click ``` @@ -302,7 +310,32 @@ Amit eddig ebben a fejezetben bemutattunk, valószínűleg elegendő lesz. A kö Követelmények és paraméterek .[#toc-requirement-and-parameters] --------------------------------------------------------------- -A bemutató által kezelt kérés a [api:Nette\Application\Request] objektum, amelyet a bemutató `getRequest()` metódusa ad vissza. Ez paraméterek tömbjét tartalmazza, és mindegyik paraméter vagy valamelyik komponenshez, vagy közvetlenül a prezentálóhoz tartozik (amely valójában szintén egy komponens, bár egy speciális komponens). A Nette tehát a `loadState(array $params)` metódus meghívásával osztja újra a paramétereket, és az egyes komponensek (és a prezenter) között átadja a metódust, amelyet a [komponensek |Components] fejezetben ismertetünk részletesebben. A paramétereket a `getParameters(): array` metódus segítségével lehet beszerezni, külön-külön a `getParameter($name)` segítségével. A paraméterértékek karakterláncok vagy karakterláncok tömbjei, ezek alapvetően közvetlenül az URL-ből nyert nyers adatok. +A bemutató által kezelt kérés a [api:Nette\Application\Request] objektum, amelyet a bemutató `getRequest()` metódusa ad vissza. Ez paraméterek tömbjét tartalmazza, és mindegyik paraméter vagy valamelyik komponenshez, vagy közvetlenül a prezentálóhoz tartozik (amely valójában szintén egy komponens, bár egy speciális komponens). A Nette tehát a `loadState(array $params)` metódus meghívásával újraosztja a paramétereket és átadja az egyes komponensek (és a prezenter) között. A paramétereket a `getParameters(): array` metódussal lehet megszerezni, egyenként a `getParameter($name)` segítségével. A paraméterek értékei karakterláncok vagy karakterláncok tömbjei, alapvetően közvetlenül az URL-ből nyert nyers adatok. + + +A tartós paraméterek validálása .[#toc-validation-of-persistent-parameters] +--------------------------------------------------------------------------- + +Az URL-ekből kapott [állandó paraméterek |#persistent parameters] értékeit a `loadState()` módszer írja a tulajdonságokba. A módszer azt is ellenőrzi, hogy a tulajdonságban megadott adattípus megfelel-e, ellenkező esetben 404-es hibával válaszol, és az oldal nem jelenik meg. + +Soha ne bízzunk vakon a perzisztens paraméterekben, mivel azokat a felhasználó könnyen felülírhatja az URL-ben. Például így ellenőrizzük, hogy a `$this->lang` szerepel-e a támogatott nyelvek között. Jó megoldás erre a fent említett `loadState()` metódus felülírása: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // itt van beállítva a $this->lang + // követi a felhasználói értékek ellenőrzését: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` A kérelem mentése és visszaállítása .[#toc-save-and-restore-the-request] diff --git a/application/hu/routing.texy b/application/hu/routing.texy index 204915c672..ca3e0ff108 100644 --- a/application/hu/routing.texy +++ b/application/hu/routing.texy @@ -93,12 +93,12 @@ Az útvonal mostantól elfogadja a `https://any-domain.com/chronicle/` címet fo Természetesen a bemutató és az akció neve is lehet paraméter. Például: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Ez az útvonal elfogad például egy URL-t a `/article/edit` illetve `/catalog/list` formában, és lefordítja azokat a `Article:edit` illetve `Catalog:list` előadókra és műveletekre. -A `presenter` és a `action` paramétereknek alapértelmezett értékeket ad:`Homepage` és `default`, ezért ezek opcionálisak. Az útvonal tehát elfogad egy `/article` URL-t is, és lefordítja `Article:default`-ként. Vagy fordítva, a `Product:default` hivatkozás a `/product` útvonalat generálja, az alapértelmezett `Homepage:default` hivatkozás a `/` útvonalat generálja. +A `presenter` és a `action` paramétereknek alapértelmezett értékeket ad:`Home` és `default`, ezért ezek opcionálisak. Az útvonal tehát elfogad egy `/article` URL-t is, és lefordítja `Article:default`-ként. Vagy fordítva, a `Product:default` hivatkozás a `/product` útvonalat generálja, az alapértelmezett `Home:default` hivatkozás a `/` útvonalat generálja. A maszk nem csak a webhely gyökere alapján a relatív elérési utat írhatja le, hanem az abszolút elérési utat is, ha az kötőjellel kezdődik, vagy akár a teljes abszolút URL-t, ha két kötőjellel kezdődik: @@ -160,7 +160,7 @@ A szekvenciák szabadon egymásba ágyazhatók és kombinálhatók: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // Elfogadott URL-címek: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); A szögletes zárójel nélküli opcionális paraméterek (azaz az alapértelmezett értékkel rendelkező paraméterek) úgy viselkednek, mintha így lennének becsomagolva: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // egyenlő: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Ha meg akarja változtatni a jobb oldali slash generálásának módját, azaz a `/homepage/` helyett egy `/homepage` kapjon, állítsa be az útvonalat így: +Ha meg akarja változtatni a jobb oldali slash generálásának módját, azaz a `/home/` helyett egy `/home` kapjon, állítsa be az útvonalat így: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ Az útvonal második paramétere, amelyet gyakran a `Presenter:action` formátum ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Szűrők és fordítások .[#toc-filters-and-translations] Jó gyakorlat, hogy a forráskódot angolul írjuk, de mi van akkor, ha a weboldalunknak le kell fordítani az URL-t más nyelvre? Egyszerű útvonalak, mint például: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` angol nyelvű URL-eket generálnak, például `/product/123` vagy `/cart`. Ha azt szeretnénk, hogy az URL-ben szereplő bemutatókat és műveleteket németre fordítsák le (pl. `/produkt/123` vagy `/einkaufswagen`), használhatunk egy fordítószótárat. Ennek hozzáadásához már a második paraméter "beszédesebb" változatára van szükségünk: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // string az URL-ben => prezenter 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ A specifikus paraméterekhez tartozó szűrők mellett általános szűrőket is use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 A `SimpleRouter` konstruktor paramétere egy alapértelmezett bemutató és művelet, azaz a végrehajtandó művelet, ha további paraméterek nélkül megnyitjuk pl. a `http://example.com/` címet. ```php -// alapértelmezett bemutató 'Homepage' és művelet 'default' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// alapértelmezett bemutató 'Home' és művelet 'default' +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Javasoljuk, hogy a SimpleRouter-t közvetlenül a [konfigurációban |dependency-injection:services] definiáljuk: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ A kérés feldolgozásakor legalább a prezentert és az actiont kell visszaadnu ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/hu/templates.texy b/application/hu/templates.texy index adb3369071..71b205b7d1 100644 --- a/application/hu/templates.texy +++ b/application/hu/templates.texy @@ -42,7 +42,9 @@ A sablonok elérési útvonalát egyszerű logika szerint vezetjük le. Megprób - `templates//.latte` - `templates/..latte` -Ha nem találja a sablont, a válasz [404-es hiba |presenters#Error 404 etc.]. +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. + +Ha a sablon ott sem található, a válasz [404-es hiba |presenters#Error 404 etc.]. 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. @@ -148,7 +150,7 @@ A sablonban az alábbiak szerint hozunk létre linkeket más előadókra és mű A `n:href` attribútum nagyon hasznos a HTML címkékhez. ``. Ha a linket máshol, például a szövegben akarjuk kiírni, akkor a `{link}`-t használjuk: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` További információért lásd: [Linkek létrehozása |Creating Links]. diff --git a/application/it/@left-menu.texy b/application/it/@left-menu.texy index e7d6c10da4..7fea73f2da 100644 --- a/application/it/@left-menu.texy +++ b/application/it/@left-menu.texy @@ -15,5 +15,8 @@ Applicazione Nette Ulteriori letture ***************** +- [Perché utilizzare Nette? |www:10-reasons-why-nette] +- [Installazione |nette:installation] +- [Create la vostra prima applicazione! |quickstart:] - [Migliori pratiche |best-practices:] - [Risoluzione dei problemi |nette:troubleshooting] diff --git a/application/it/ajax.texy b/application/it/ajax.texy index aee9d22e66..61d7bcd9ca 100644 --- a/application/it/ajax.texy +++ b/application/it/ajax.texy @@ -10,9 +10,13 @@ Le moderne applicazioni web oggi vengono eseguite per metà su un server e per m -Una richiesta AJAX può essere rilevata utilizzando un metodo di un servizio [che incapsula una richiesta HTTP |http:request] `$httpRequest->isAjax()` (rileva in base all'intestazione HTTP `X-Requested-With` ). Esiste anche un metodo abbreviato in presenter: `$this->isAjax()`. -Una richiesta AJAX non è diversa da una normale richiesta: un presenter viene chiamato con una determinata vista e con dei parametri. Anche il presentatore può decidere come reagire: può usare le sue routine per restituire un frammento di codice HTML (uno snippet), un documento XML, un oggetto JSON o un pezzo di codice Javascript. +Richiesta AJAX .[#toc-ajax-request] +=================================== + +Una richiesta AJAX non differisce da una richiesta classica: il presentatore viene chiamato con una vista e dei parametri specifici. Il presentatore può anche decidere come rispondere: può usare la propria routine, che restituisce un frammento di codice HTML (snippet HTML), un documento XML, un oggetto JSON o codice JavaScript. + +Sul lato server, una richiesta AJAX può essere rilevata utilizzando il metodo di servizio [che incapsula la richiesta HTTP |http:request] `$httpRequest->isAjax()` (rileva in base all'intestazione HTTP `X-Requested-With`). All'interno del presentatore, è disponibile una scorciatoia sotto forma del metodo `$this->isAjax()`. Esiste un oggetto pre-elaborato chiamato `payload`, dedicato all'invio di dati al browser in JSON. @@ -60,6 +64,21 @@ npm install naja ``` +Per creare una richiesta AJAX da un normale link (segnale) o dall'invio di un modulo, è sufficiente contrassegnare il relativo link, modulo o pulsante con la classe `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Snippet .[#toc-snippets] ======================== @@ -149,7 +168,7 @@ Non è possibile ridisegnare direttamente uno snippet dinamico (il ridisegno di Nell'esempio precedente, bisogna assicurarsi che per una richiesta AJAX venga aggiunto un solo elemento all'array `$list`, quindi il ciclo `foreach` stamperà un solo frammento dinamico. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/it/bootstrap.texy b/application/it/bootstrap.texy index c2032bcbb8..4efbe28af1 100644 --- a/application/it/bootstrap.texy +++ b/application/it/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -Nei file di configurazione, si può scrivere la solita notazione `%projectId%` per accedere al parametro chiamato `projectId`. Per impostazione predefinita, il Configuratore popola i seguenti parametri: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` e `consoleMode`. +Nei file di configurazione, possiamo scrivere la solita notazione `%projectId%` per accedere al parametro chiamato `projectId`. Parametri dinamici .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Parametri predefiniti .[#toc-default-parameters] +------------------------------------------------ + +È possibile utilizzare i seguenti parametri statici nei file di configurazione: + +- `%appDir%` è il percorso assoluto della directory del file `Bootstrap.php` +- `%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 +- `%debugMode%` indica se l'applicazione è in modalità debug +- `%consoleMode%` indica se la richiesta è arrivata attraverso la riga di comando + + Servizi importati .[#toc-imported-services] ------------------------------------------- diff --git a/application/it/components.texy b/application/it/components.texy index 23eeae4957..38f8c6513d 100644 --- a/application/it/components.texy +++ b/application/it/components.texy @@ -233,31 +233,34 @@ Nel modello, questi messaggi sono disponibili nella variabile `$flashes` come og Parametri persistenti .[#toc-persistent-parameters] =================================================== -Spesso è necessario mantenere alcuni parametri in un componente per tutto il tempo in cui si lavora con il componente. Può essere, ad esempio, il numero della pagina nella paginazione. Questo parametro deve essere contrassegnato come persistente utilizzando l'annotazione `@persistent`. +I parametri persistenti sono utilizzati per mantenere lo stato dei componenti tra diverse richieste. Il loro valore rimane invariato anche dopo che si è fatto clic su un collegamento. A differenza dei dati di sessione, vengono trasferiti nell'URL. Vengono trasferiti automaticamente, compresi i collegamenti creati in altri componenti della stessa pagina. + +Ad esempio, si dispone di un componente di paginazione dei contenuti. In una pagina possono esserci diversi componenti di questo tipo. Si vuole che tutti i componenti rimangano nella pagina corrente quando si fa clic sul collegamento. Pertanto, il numero di pagina (`page`) è un parametro persistente. + +Creare un parametro persistente è estremamente facile in Nette. È sufficiente creare una proprietà pubblica e contrassegnarla con l'attributo: (in precedenza si usava `/** @persistent */` ) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // questa linea è importante + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // deve essere pubblico } ``` -Questo parametro sarà passato automaticamente in ogni collegamento come parametro `GET`, finché l'utente non lascerà la pagina con questo componente. +Si consiglia di includere nella proprietà il tipo di dati (ad esempio, `int`) e si può anche includere un valore predefinito. I valori dei parametri possono essere [convalidati |#Validation of Persistent Parameters]. -.[caution] -Non fidarsi mai ciecamente dei parametri persistenti, perché possono essere falsificati facilmente (sovrascrivendo l'URL). Verificare, ad esempio, se il numero di pagina rientra nell'intervallo corretto. +È possibile modificare il valore di un parametro persistente durante la creazione di un collegamento: -In PHP 8, è possibile utilizzare anche gli attributi per contrassegnare i parametri persistenti: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Oppure può essere *ripristinato*, cioè rimosso dall'URL. In questo caso, assumerà il valore predefinito: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ I componenti di un'applicazione Nette sono le parti riutilizzabili di un'applica 1) è renderizzabile in un modello 2) sa quale parte di sé rendere durante una [richiesta AJAX |ajax#invalidation] (snippet) -3) ha la capacità di memorizzare il suo stato in un URL (parametri di persistenza) +3) ha la capacità di memorizzare il proprio stato in un URL (parametri persistenti) 4) ha la capacità di rispondere alle azioni dell'utente (segnali) 5) crea una struttura gerarchica (dove la radice è il presenter) @@ -403,6 +406,33 @@ Ciclo di vita di un componente .[#toc-life-cycle-of-component] [* lifecycle-component.svg *] *** *Ciclo di vita del componente* .<> +Convalida dei parametri persistenti .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------------- + +I valori dei [parametri persistenti |#persistent parameters] ricevuti dagli URL vengono scritti nelle proprietà dal metodo `loadState()`. Il metodo controlla anche se il tipo di dati specificato per la proprietà corrisponde, altrimenti risponde con un errore 404 e la pagina non viene visualizzata. + +Non fidarsi mai ciecamente dei parametri persistenti, perché possono essere facilmente sovrascritti dall'utente nell'URL. Per esempio, ecco come verificare se il numero di pagina `$this->page` è maggiore di 0. Un buon modo per farlo è sovrascrivere il metodo `loadState()` citato in precedenza: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // qui viene impostato $this->pagina + // segue il controllo del valore dell'utente: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Il processo opposto, cioè la raccolta di valori da proprietà persistenti, è gestito dal metodo `saveState()`. + + Segnali in profondità .[#toc-signals-in-depth] ---------------------------------------------- diff --git a/application/it/creating-links.texy b/application/it/creating-links.texy index a88d9796ea..7b8cafdf00 100644 --- a/application/it/creating-links.texy +++ b/application/it/creating-links.texy @@ -52,7 +52,7 @@ Anche i cosiddetti [parametri persistenti |presenters#persistent parameters] ven L'attributo `n:href` è molto utile per i tag HTML ``. Se vogliamo stampare il link altrove, per esempio nel testo, usiamo `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ Il formato è supportato da tutti i tag Latte e da tutti i metodi del presentato La forma base è quindi `Presenter:action`: ```latte -homepage +home ``` Se ci si collega all'azione del presentatore corrente, si può omettere il suo nome: ```latte -homepage +home ``` Se l'azione è `default`, possiamo ometterlo, ma i due punti devono rimanere: ```latte -homepage +home ``` I collegamenti possono anche puntare ad altri [moduli |modules]. In questo caso, i collegamenti si distinguono in relativi ai sottomoduli o assoluti. Il principio è analogo a quello dei percorsi su disco, solo che al posto degli slash ci sono i punti. Supponiamo che il presentatore attuale faccia parte del modulo `Front`, quindi scriveremo: @@ -119,7 +119,7 @@ Un caso particolare è il [collegamento a se stesso |#Links to Current Page]. Qu Possiamo collegarci a una determinata parte della pagina HTML tramite un cosiddetto frammento dopo il simbolo hash `#`: ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ Percorsi assoluti .[#toc-absolute-paths] I link generati da `link()` o `n:href` sono sempre percorsi assoluti (cioè iniziano con `/`), ma non URL assoluti con protocollo e dominio come `https://domain`. -Per generare un URL assoluto, aggiungere due barre all'inizio (ad esempio, `n:href="//Homepage:"`). Oppure si può impostare il presentatore in modo che generi solo collegamenti assoluti, impostando `$this->absoluteUrls = true`. +Per generare un URL assoluto, aggiungere due barre all'inizio (ad esempio, `n:href="//Home:"`). Oppure si può impostare il presentatore in modo che generi solo collegamenti assoluti, impostando `$this->absoluteUrls = true`. Collegamento alla pagina corrente .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ Poiché i [componenti |components] sono unità riutilizzabili separate che non d Se vogliamo collegarci ai presentatori nel modello del componente, usiamo il tag `{plink}`: ```latte -homepage +home ``` o nel codice ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/it/how-it-works.texy b/application/it/how-it-works.texy index a1016563ab..aa331da3c3 100644 --- a/application/it/how-it-works.texy +++ b/application/it/how-it-works.texy @@ -23,10 +23,10 @@ La struttura delle directory è simile a questa: web-project/ ├── app/ ← directory with application │ ├── Presenters/ ← presenter classes -│ │ ├── HomepagePresenter.php ← Homepage presenter class +│ │ ├── HomePresenter.php ← Home presenter class │ │ └── templates/ ← templates directory │ │ ├── @layout.latte ← template of shared layout -│ │ └── Homepage/ ← templates for Homepage presenter +│ │ └── Home/ ← templates for Home presenter │ │ └── default.latte ← template for action `default` │ ├── Router/ ← configuration of URL addresses │ └── Bootstrap.php ← booting class Bootstrap @@ -134,10 +134,10 @@ Per sicurezza, proviamo a riepilogare l'intero processo con un URL leggermente d 1) l'URL sarà `https://example.com` 2) si avvia l'applicazione, si crea un contenitore e si esegue `Application::run()` -3) il router decodifica l'URL come una coppia di oggetti `Homepage:default` -4) viene creato un oggetto `HomepagePresenter` +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/Homepage/default.latte` con un layout `templates/@layout.latte` +6) viene reso un modello `templates/Home/default.latte` con un layout `templates/@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 4686c49192..55ee0c0696 100644 --- a/application/it/modules.texy +++ b/application/it/modules.texy @@ -104,7 +104,7 @@ 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`. -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 `Homepage` deve mappare alla classe `App\Presenters\HomepagePresenter`. Questo si può ottenere con la seguente configurazione: +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: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Ora il presentatore `Front:Homepage` mappa alla classe `App\Modules\Front\Presenters\HomepagePresenter` e il presentatore `Admin:Dashboard` alla classe `App\Modules\Admin\Presenters\DashboardPresenter`. +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`. È 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: diff --git a/application/it/presenters.texy b/application/it/presenters.texy index b9fc76bec1..9cae206b58 100644 --- a/application/it/presenters.texy +++ b/application/it/presenters.texy @@ -158,7 +158,7 @@ Il metodo `forward()` passa immediatamente al nuovo presentatore senza reindiriz $this->forward('Product:show'); ``` -Esempio di reindirizzamento temporaneo con codice HTTP 302 o 303: +Esempio di un cosiddetto reindirizzamento temporaneo con codice HTTP 302 (o 303, se il metodo di richiesta corrente è POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ Per ottenere un reindirizzamento permanente con codice HTTP 301 utilizzare: $this->redirectPermanent('Product:show', $id); ``` -È possibile reindirizzare a un altro URL esterno all'applicazione con il metodo `redirectUrl()`: +È possibile reindirizzare a un altro URL al di fuori dell'applicazione utilizzando il metodo `redirectUrl()`. Il codice HTTP può essere specificato come secondo parametro; il valore predefinito è 302 (o 303, se il metodo di richiesta corrente è POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Parametri persistenti .[#toc-persistent-parameters] =================================================== -I parametri persistenti sono **trasferiti automaticamente** nei collegamenti. Ciò significa che non è necessario specificarli esplicitamente in ogni `link()` o `n:href` template, ma saranno comunque trasferiti. +I parametri persistenti sono usati per mantenere lo stato tra diverse richieste. Il loro valore rimane invariato anche dopo aver cliccato su un link. A differenza dei dati di sessione, vengono passati nell'URL. Questo è completamente automatico, quindi non è necessario dichiararli esplicitamente in `link()` o `n:href`. -Se l'applicazione ha più versioni linguistiche, la lingua corrente è un parametro che deve sempre far parte dell'URL. E sarebbe incredibilmente faticoso menzionarlo in ogni link. Con Nette non è necessario. In questo modo, il parametro `lang` viene semplicemente contrassegnato come persistente: +Un esempio di utilizzo? Avete un'applicazione multilingue. La lingua attuale è un parametro che deve sempre far parte dell'URL. Ma sarebbe incredibilmente noioso includerlo in ogni link. Perciò lo si rende un parametro persistente, chiamato `lang`, che si autoalimenta. Forte! + +Creare un parametro persistente è estremamente facile in Nette. Basta creare una proprietà pubblica e contrassegnarla con l'attributo: (in precedenza si usava `/** @persistent */` ) ```php +use Nette\Application\Attributes\Persistent; // questa linea è importante + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // deve essere pubblico } ``` -Se il valore attuale del parametro `lang` è `'en'`, allora l'URL creato con `link()` o `n:href` nel modello conterrà `lang=en`. Ottimo! - -Tuttavia, possiamo anche aggiungere il parametro `lang` e modificarne il valore: +Se `$this->lang` ha un valore come `'en'`, i link creati con `link()` o `n:href` conterranno anche il parametro `lang=en`. E quando il link viene cliccato, sarà di nuovo `$this->lang = 'en'`. -```latte -detail in English -``` - -Oppure, al contrario, può essere rimosso impostandolo a null: - -```latte -click here -``` +Per le proprietà, si consiglia di includere il tipo di dati (ad esempio, `string`) e si può anche includere un valore predefinito. I valori dei parametri possono essere [convalidati |#Validation of Persistent Parameters]. -La variabile persistente deve essere dichiarata come pubblica. Si può anche specificare un valore predefinito. Se il parametro ha lo stesso valore di quello predefinito, non verrà incluso nell'URL. +I parametri persistenti vengono passati per impostazione predefinita tra tutte le azioni di un determinato presentatore. Per passarli tra più presentatori, è necessario definirli: -La persistenza riflette la gerarchia delle classi di presenter, per cui il parametro definito in un certo presenter o tratto viene automaticamente trasferito a ogni presenter che ne eredita o che utilizza lo stesso tratto. - -In PHP 8, è possibile utilizzare anche gli attributi per contrassegnare i parametri persistenti: +- in un antenato comune dal quale i presentatori ereditano +- nel tratto che i presentatori utilizzano: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +È possibile modificare il valore di un parametro persistente durante la creazione di un collegamento: + +```latte +detail in Czech +``` + +Oppure può essere *ripristinato*, cioè rimosso dall'URL. In questo caso, assumerà il valore predefinito: + +```latte +click ``` @@ -302,7 +310,32 @@ Quanto mostrato finora in questo capitolo sarà probabilmente sufficiente. Le ri Requisiti e parametri .[#toc-requirement-and-parameters] -------------------------------------------------------- -La richiesta gestita dal presentatore è l'oggetto [api:Nette\Application\Request] e viene restituita dal metodo del presentatore `getRequest()`. Include un array di parametri e ognuno di essi appartiene o a qualche componente o direttamente al presentatore (che in realtà è anch'esso un componente, anche se speciale). Quindi Nette ridistribuisce i parametri e i passaggi tra i singoli componenti (e il presentatore) richiamando il metodo `loadState(array $params)`, che viene descritto ulteriormente nel capitolo [Componenti |Components]. I parametri possono essere ottenuti con il metodo `getParameters(): array`, singolarmente con `getParameter($name)`. I valori dei parametri sono stringhe o array di stringhe, in pratica dati grezzi ottenuti direttamente da un URL. +La richiesta gestita dal presentatore è l'oggetto [api:Nette\Application\Request] e viene restituita dal metodo del presentatore `getRequest()`. Include un array di parametri e ognuno di essi appartiene o a qualche componente o direttamente al presentatore (che in realtà è anch'esso un componente, anche se speciale). Quindi Nette ridistribuisce i parametri e passa tra i singoli componenti (e il presentatore) chiamando il metodo `loadState(array $params)`. I parametri possono essere ottenuti con il metodo `getParameters(): array`, singolarmente con `getParameter($name)`. I valori dei parametri sono stringhe o array di stringhe, in pratica dati grezzi ottenuti direttamente da un URL. + + +Convalida dei parametri persistenti .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------------- + +I valori dei [parametri persistenti |#persistent parameters] ricevuti dagli URL vengono scritti nelle proprietà dal metodo `loadState()`. Il metodo controlla anche se il tipo di dati specificato nella proprietà corrisponde, altrimenti risponde con un errore 404 e la pagina non viene visualizzata. + +Non fidarsi mai ciecamente dei parametri persistenti, perché possono essere facilmente sovrascritti dall'utente nell'URL. Ad esempio, è così che si controlla se `$this->lang` è tra le lingue supportate. Un buon modo per farlo è sovrascrivere il metodo `loadState()` citato in precedenza: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // qui viene impostato $this->lang + // segue il controllo del valore dell'utente: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Salvare e ripristinare la richiesta .[#toc-save-and-restore-the-request] diff --git a/application/it/routing.texy b/application/it/routing.texy index 1ddb486642..b530d0b83b 100644 --- a/application/it/routing.texy +++ b/application/it/routing.texy @@ -93,12 +93,12 @@ La rotta accetterà ora l'URL `https://any-domain.com/chronicle/` con il paramet Naturalmente, anche il nome del presentatore e l'azione possono essere un parametro. Ad esempio: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Questo percorso accetta, ad esempio, un URL nella forma `/article/edit` e `/catalog/list` e li traduce in presentatori e azioni `Article:edit` e `Catalog:list`. -Inoltre, assegna ai parametri `presenter` e `action` i valori predefiniti`Homepage` e `default`, che sono quindi facoltativi. Quindi il percorso accetta anche un URL `/article` e lo traduce in `Article:default`. O viceversa, un link a `Product:default` genera un percorso `/product`, un link al default `Homepage:default` genera un percorso `/`. +Inoltre, assegna ai parametri `presenter` e `action` i valori predefiniti`Home` e `default`, che sono quindi facoltativi. Quindi il percorso accetta anche un URL `/article` e lo traduce in `Article:default`. O viceversa, un link a `Product:default` genera un percorso `/product`, un link al default `Home:default` genera un percorso `/`. La maschera può descrivere non solo il percorso relativo basato sulla radice del sito, ma anche il percorso assoluto quando inizia con una barra, o addirittura l'intero URL assoluto quando inizia con due barre: @@ -160,7 +160,7 @@ Le sequenze possono essere liberamente annidate e combinate: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // URL accettati: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); I parametri opzionali (cioè quelli che hanno un valore predefinito) senza parentesi quadre si comportano come se fossero avvolti in questo modo: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // equivale a: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Per cambiare il modo in cui viene generato lo slash più a destra, cioè invece di `/homepage/` ottenere un `/homepage`, regolare il percorso in questo modo: +Per cambiare il modo in cui viene generato lo slash più a destra, cioè invece di `/home/` ottenere un `/home`, regolare il percorso in questo modo: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ Il secondo parametro della rotta, che spesso scriviamo nel formato `Presenter:ac ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filtri e traduzioni .[#toc-filters-and-translations] È buona norma scrivere il codice sorgente in inglese, ma cosa succede se è necessario che il sito web abbia un URL tradotto in una lingua diversa? Percorsi semplici come: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` genereranno URL in inglese, come `/product/123` o `/cart`. Se vogliamo che i presenter e le azioni nell'URL siano tradotti in tedesco (per esempio `/produkt/123` o `/einkaufswagen`), possiamo usare un dizionario di traduzione. Per aggiungerlo, abbiamo già bisogno di una variante "più loquace" del secondo parametro: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // stringa nell'URL => presenter 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Oltre ai filtri per parametri specifici, è possibile definire anche filtri gene use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Il parametro del costruttore `SimpleRouter` è un presentatore e un'azione predefiniti, cioè l'azione da eseguire se si apre, ad esempio, `http://example.com/` senza ulteriori parametri. ```php -// default al presenter 'Homepage' e all'azione 'default' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// default al presenter 'Home' e all'azione 'default' +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Si consiglia di definire SimpleRouter direttamente nella [configurazione |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ Quando si elabora la richiesta, è necessario restituire almeno il presentatore ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/it/templates.texy b/application/it/templates.texy index 9c6cf13691..bb704a88f2 100644 --- a/application/it/templates.texy +++ b/application/it/templates.texy @@ -42,7 +42,9 @@ Il percorso dei modelli viene dedotto secondo una semplice logica. Si cerca di v - `templates//.latte` - `templates/..latte` -Se non trova il modello, la risposta è un [errore 404 |presenters#Error 404 etc.]. +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. + +Se il modello non viene trovato nemmeno lì, la risposta è un [errore 404 |presenters#Error 404 etc.]. 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')`. @@ -148,7 +150,7 @@ Nel modello si creano collegamenti ad altri presentatori e azioni come segue: L'attributo `n:href` è molto utile per i tag HTML ``. Se vogliamo stampare il link altrove, ad esempio nel testo, usiamo `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` Per ulteriori informazioni, vedere [Creazione di collegamenti |Creating Links]. diff --git a/application/pl/@left-menu.texy b/application/pl/@left-menu.texy index c5153e7498..b150490a16 100644 --- a/application/pl/@left-menu.texy +++ b/application/pl/@left-menu.texy @@ -15,5 +15,8 @@ Aplikacje w Nette Dalsze czytanie *************** +- [Dlaczego warto używać Nette? |www:10-reasons-why-nette] +- [Instalacja |nette:installation] +- [Stwórz swoją pierwszą aplikację! |quickstart:] - [Samouczki i procedury |best-practices:] - [Rozwiązywanie problemów |nette:troubleshooting] diff --git a/application/pl/ajax.texy b/application/pl/ajax.texy index bdeae39bd0..4b42eac748 100644 --- a/application/pl/ajax.texy +++ b/application/pl/ajax.texy @@ -10,9 +10,13 @@ Nowoczesne aplikacje internetowe działają dziś w połowie na serwerze, w poł -Żądanie AJAX może być wykryte przez metodę serwisową [enkapsulującą żądanie HTTP |http:request] `$httpRequest->isAjax()` (wykryte przez nagłówek HTTP `X-Requested-With`). Wewnątrz prezentera jest zapewniony "skrót" w postaci metody `$this->isAjax()`. -Żądanie AJAX nie różni się od tradycyjnego żądania - prezenter jest wywoływany z określonym widokiem i parametrami. To również zależy od prezentera, jak odpowiada: może użyć niestandardowej procedury, która zwraca fragment HTML, dokument XML, obiekt JSON lub kod JavaScript. +Żądanie AJAX .[#toc-ajax-request] +================================= + +Żądanie AJAX nie różni się od klasycznego żądania - prezenter jest wywoływany z określonym widokiem i parametrami. Również od prezentera zależy, jak na nie odpowie: może użyć własnej procedury, która zwraca fragment kodu HTML (HTML snippet), dokument XML, obiekt JSON lub kod JavaScript. + +Po stronie serwera żądanie AJAX może zostać wykryte za pomocą metody serwisowej [obudowującej żądanie HTTP |http:request] `$httpRequest->isAjax()` (wykrywa na podstawie nagłówka HTTP `X-Requested-With`). Wewnątrz prezentera dostępny jest skrót w postaci metody `$this->isAjax()`. Aby wysłać dane do przeglądarki w formacie JSON, możesz użyć gotowego obiektu `payload`: @@ -60,6 +64,21 @@ npm install naja ``` +Aby utworzyć żądanie AJAX ze zwykłego linku (sygnału) lub submitu formularza, wystarczy oznaczyć odpowiedni link, formularz lub przycisk klasą `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Snippets ======== @@ -149,7 +168,7 @@ Nie można bezpośrednio unieważnić dynamicznych snippetów (unieważnienie `i W powyższym przykładzie musisz po prostu upewnić się, że gdy wykonasz żądanie ajaxowe, w zmiennej `$list` znajduje się tylko jeden wpis, a zatem, że pętla `foreach` wypełnia tylko jeden dynamiczny snippet: ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * Tato metoda vrací data pro seznam. diff --git a/application/pl/bootstrap.texy b/application/pl/bootstrap.texy index 1a1ef76bbc..3fb6475d75 100644 --- a/application/pl/bootstrap.texy +++ b/application/pl/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -Do parametru `projectId` można się odwołać w konfiguracji za pomocą zwykłej notacji `%projectId%`. Klasa Configurator automatycznie dodaje parametry `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` oraz `consoleMode`. +W plikach konfiguracyjnych możemy zapisać zwykłą notację `%projectId%` aby uzyskać dostęp do parametru o nazwie `projectId`. Parametry dynamiczne .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Parametry domyślne .[#toc-default-parameters] +--------------------------------------------- + +W plikach konfiguracyjnych można używać następujących parametrów statycznych: + +- `%appDir%` jest bezwzględną ścieżką do katalogu zawierającego plik `Bootstrap.php` +- `%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 +- `%debugMode%` wskazuje, czy aplikacja jest w trybie debugowania +- `%consoleMode%` wskazuje, czy żądanie przyszło z linii poleceń + + Usługi importowane .[#toc-imported-services] -------------------------------------------- diff --git a/application/pl/components.texy b/application/pl/components.texy index c26a8ca608..94d619c41e 100644 --- a/application/pl/components.texy +++ b/application/pl/components.texy @@ -233,31 +233,34 @@ Do szablonu wiadomości te są dostępne w zmiennej `$flashes` jako obiekty `std Trwałe parametry .[#toc-persistent-parameters] ============================================== -Często zdarza się, że komponenty muszą trzymać jakiś parametr dla użytkownika przez cały czas pracy z komponentem. Na przykład może to być numer strony w paginacji. Oznaczamy taki parametr jako trwały używając adnotacji `@persistent`. +Trwałe parametry są używane do utrzymania stanu w komponentach pomiędzy różnymi żądaniami. Ich wartość pozostaje taka sama nawet po kliknięciu linku. W przeciwieństwie do danych sesji, są one przekazywane w adresie URL. I są przekazywane automatycznie, łącznie z linkami utworzonymi w innych komponentach na tej samej stronie. + +Na przykład, masz komponent stronicowania treści. Na stronie może być kilka takich komponentów. I chcesz, aby wszystkie komponenty pozostały na bieżącej stronie, gdy użytkownik kliknie na link. Dlatego czynimy numer strony (`page`) trwałym parametrem. + +Tworzenie trwałych parametrów jest w Nette niezwykle proste. Wystarczy stworzyć właściwość publiczną i oznaczyć ją atrybutem: (poprzednio użyto `/** @persistent */` ) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // ta linia jest ważna + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // musi być publiczny } ``` -Ten parametr będzie automatycznie przekazywany w każdym linku jako parametr GET, dopóki użytkownik nie opuści strony z tym komponentem. +Zalecamy dołączenie typu danych (np. `int`) do właściwości, możesz także dołączyć wartość domyślną. Wartości parametrów mogą być [walidowane |#Validation of Persistent Parameters]. -.[caution] -Nigdy nie ufaj ślepo trwałym parametrom, ponieważ mogą one zostać łatwo sfałszowane (poprzez nadpisanie ich w adresie URL strony). Na przykład sprawdź, czy numer strony znajduje się w prawidłowym zakresie. +Możesz zmienić wartość trwałego parametru podczas tworzenia linku: -W PHP 8 możesz również użyć atrybutów do wskazania trwałych parametrów: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Można też go *resetować*, czyli usunąć z adresu URL. Przyjmie on wtedy swoją domyślną wartość: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ Komponenty w aplikacji Nette to części aplikacji internetowej wielokrotnego u 1) Jest renderowalny w szablonie 2) wie, która część siebie ma być renderowana podczas [żądania AJAX |ajax#Invalidation] (snippety) -3) ma możliwość przechowywania swojego stanu w URL (parametry persystencji) +3) ma możliwość przechowywania swojego stanu w URL (trwałe parametry) 4) posiada zdolność do reagowania na działania (sygnały) użytkownika 5) tworzy strukturę hierarchiczną (gdzie korzeniem jest prezenter) @@ -403,6 +406,33 @@ Cykl życia komponentów .[#toc-zivotni-cyklus-componenty] [* lifecycle-component.svg *] *** *Cykl życia składników* .<> +Walidacja stałych parametrów .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------ + +Wartości [trwałych parametrów |#persistent parameters] otrzymanych z adresów URL są zapisywane do właściwości przez metodę `loadState()`. Sprawdza ona również, czy typ danych określony dla właściwości pasuje, w przeciwnym razie odpowie błędem 404 i strona nie zostanie wyświetlona. + +Nigdy ślepo nie ufaj trwałym parametrom, ponieważ mogą one zostać łatwo nadpisane przez użytkownika w adresie URL. Na przykład, w ten sposób sprawdzamy, czy numer strony `$this->page` jest większy niż 0. Dobrym sposobem na to jest nadpisanie metody `loadState()` wspomnianej powyżej: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // tutaj jest ustawione $this->page + // następuje sprawdzenie wartości użytkownika: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Procesem przeciwnym, czyli pobieraniem wartości z persistent properites, zajmuje się metoda `saveState()`. + + Sygnały w głąb .[#toc-signaly-do-hloubky] ----------------------------------------- diff --git a/application/pl/creating-links.texy b/application/pl/creating-links.texy index e87b158f61..5c9126dba0 100644 --- a/application/pl/creating-links.texy +++ b/application/pl/creating-links.texy @@ -52,7 +52,7 @@ Tak zwane [trwałe parametry |presenters#Persistent-Parameters] są również au Atrybut `n:href` jest bardzo przydatny dla znaczników HTML ``. Jeśli chcemy wymienić link w innym miejscu, na przykład w tekście, używamy `{link}`: ```latte -Adresa je: {link Homepage:default} +Adresa je: {link Home:default} ``` @@ -88,7 +88,7 @@ Format jest obsługiwany przez wszystkie znaczniki Latte oraz wszystkie metody p Podstawową formą jest więc `Presenter:action`: ```latte -úvodní stránka +úvodní stránka ``` Jeśli odnosimy się do działania bieżącego prezentera, możemy pominąć nazwę prezentera: @@ -100,7 +100,7 @@ Jeśli odnosimy się do działania bieżącego prezentera, możemy pominąć naz Jeśli celem działania jest `default`, możemy go pominąć, ale dwukropek musi pozostać: ```latte -úvodní stránka +úvodní stránka ``` Linki mogą również wskazywać na inne [moduły |modules]. Tutaj linki są rozróżniane jako względne do zagnieżdżonego podmodułu lub bezwzględne. Zasada działania jest analogiczna do ścieżek dyskowych, ale z dwukropkami zamiast ukośników. Załóżmy, że aktualny prezenter jest częścią modułu `Front`, wtedy piszemy: @@ -119,7 +119,7 @@ Szczególnym przypadkiem jest [autoreferencja |#Link-to-Current-Page], w której Możemy linkować do określonej części strony poprzez fragment po znaku siatki `#`: ```latte -odkaz na Homepage:default a fragment #main +odkaz na Home:default a fragment #main ``` @@ -128,7 +128,7 @@ Możemy linkować do określonej części strony poprzez fragment po znaku siatk Linki generowane przez `link()` lub `n:href` są zawsze ścieżkami bezwzględnymi (tj. zaczynają się od `/`), ale nie bezwzględnymi adresami URL z protokołem i domeną jak `https://domain`. -Aby wygenerować bezwzględny adres URL, dodaj dwa ukośniki na początku (np. `n:href="//Homepage:"`). Możesz też przełączyć prezenter, aby generował tylko bezwzględne linki, ustawiając `$this->absoluteUrls = true`. +Aby wygenerować bezwzględny adres URL, dodaj dwa ukośniki na początku (np. `n:href="//Home:"`). Możesz też przełączyć prezenter, aby generował tylko bezwzględne linki, ustawiając `$this->absoluteUrls = true`. Link do bieżącej strony .[#toc-link-to-current-page] @@ -165,7 +165,7 @@ Parametry są takie same jak w przypadku metody `link()`, ale dodatkowo można z Forma krótka może być stosowana w połączeniu z `n:href` w jednym elemencie: ```latte -... +... ``` Symbol wieloznaczny `*` może być użyty tylko w miejsce akcji, nie prezentera. @@ -213,13 +213,13 @@ Ponieważ [komponenty |components] są samodzielnymi, wielokrotnego użytku jedn Gdybyśmy chcieli odwołać się do prezenterów w szablonie komponentu, użylibyśmy do tego celu tagu `{plink}`: ```latte -úvod +úvod ``` lub w kodzie ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/pl/how-it-works.texy b/application/pl/how-it-works.texy index 003a54759f..84c129dd41 100644 --- a/application/pl/how-it-works.texy +++ b/application/pl/how-it-works.texy @@ -23,10 +23,10 @@ Struktura katalogów wygląda mniej więcej tak: web-project/ ├── app/ ← adresář s aplikací │ ├── Presenters/ ← presentery a šablony -│ │ ├── HomepagePresenter.php ← třída presenteru Homepage +│ │ ├── HomePresenter.php ← třída presenteru Home │ │ └── templates/ ← adresář se šablonami │ │ ├── @layout.latte ← šablona layoutu -│ │ └── Homepage/ ← šablony presenteru Homepage +│ │ └── Home/ ← šablony presenteru Home │ │ └── default.latte ← šablona akce 'default' │ ├── Router/ ← konfigurace URL adres │ └── Bootstrap.php ← zaváděcí třída Bootstrap @@ -134,10 +134,10 @@ Aby być bezpiecznym, spróbujmy podsumować cały proces z nieco innym adresem 1) Adres URL będzie następujący `https://example.com` 2) uruchamiamy aplikację, tworzymy kontener i uruchamiamy `Application::run()` -3) router dekoduje adres URL jako parę `Homepage:default` -4) tworzony jest obiekt klasy `HomepagePresenter` +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/Homepage/default.latte` z układem np. `templates/@layout.latte` +6) wyrenderować szablon np. `templates/Home/default.latte` z układem np. `templates/@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 632936966b..a2a4480132 100644 --- a/application/pl/modules.texy +++ b/application/pl/modules.texy @@ -104,7 +104,7 @@ 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`. -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, `Homepage` mapował do klasy `App\Presenters\HomepagePresenter`. Można to osiągnąć dzięki następującej konfiguracji: +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: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Teraz prezenter `Front:Homepage` mapuje do klasy `App\Modules\Front\Presenters\HomepagePresenter`, a prezenter `Admin:Dashboard` mapuje do klasy `App\Modules\Admin\Presenters\DashboardPresenter`. +Teraz prezenter `Front:Home` mapuje do klasy `App\Modules\Front\Presenters\HomePresenter`, a prezenter `Admin:Dashboard` mapuje do klasy `App\Modules\Admin\Presenters\DashboardPresenter`. 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: diff --git a/application/pl/presenters.texy b/application/pl/presenters.texy index 2be1ee6735..8abef24187 100644 --- a/application/pl/presenters.texy +++ b/application/pl/presenters.texy @@ -158,7 +158,7 @@ Metoda `forward()` przechodzi natychmiast do nowego prezentera bez przekierowani $this->forward('Product:show'); ``` -Przykład tymczasowego przekierowania z kodem HTTP 302 lub 303: +Przykład tzw. tymczasowego przekierowania z kodem HTTP 302 (lub 303, jeśli aktualną metodą żądania jest POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ Aby uzyskać trwałe przekierowanie z kodem HTTP 301, wykonaj następujące czyn $this->redirectPermanent('Product:show', $id); ``` -Możesz przekierować do innego adresu URL poza aplikacją za pomocą metody `redirectUrl()`: +Możesz przekierować na inny adres URL poza aplikacją, używając metody `redirectUrl()`. Kod HTTP może być określony jako drugi parametr, przy czym domyślnie jest to 302 (lub 303, jeśli bieżącą metodą żądania jest POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Trwałe parametry .[#toc-persistent-parameters] ============================================== -Parametry trwałe są **przenoszone automatycznie** w linkach. Oznacza to, że nie musimy ich jawnie podawać w każdym wywołaniu `link()` lub `n:href` w szablonie, ale nadal będą one przenoszone. +Trwałe parametry są używane do utrzymania stanu pomiędzy różnymi żądaniami. Ich wartość pozostaje taka sama nawet po kliknięciu linku. W przeciwieństwie do danych sesji, są one przekazywane w adresie URL. Dzieje się to całkowicie automatycznie, więc nie ma potrzeby wyraźnego ich podawania `link()` lub `n:href`. -Jeśli twoja aplikacja ma wiele języków, bieżący język jest parametrem, który musi być częścią adresu URL przez cały czas. I byłoby niesamowicie żmudne, aby uwzględnić go w każdym linku. Z Nette nie ma takiej potrzeby. Wystarczy oznaczyć parametr `lang` jako trwały w ten sposób: +Przykład użycia? Masz wielojęzyczną aplikację. Rzeczywisty język jest parametrem, który musi być częścią adresu URL przez cały czas. Ale byłoby to niewiarygodnie żmudne, aby zawrzeć go w każdym linku. Więc robisz z niego trwały parametr o nazwie `lang` i będzie się on sam przenosił. Fajnie! + +Tworzenie trwałych parametrów jest niezwykle proste w Nette. Wystarczy stworzyć właściwość publiczną i oznaczyć ją atrybutem: (poprzednio używano `/** @persistent */` ) ```php +use Nette\Application\Attributes\Persistent; // ta linia jest ważna + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // musi być publiczny } ``` -Jeśli aktualną wartością parametru `lang` jest `'en'`, adres URL utworzony za pomocą `link()` lub `n:href` w szablonie będzie zawierał `lang=en`. Super! - -Jednakże, podczas tworzenia linku, parametr persistent może zostać określony, aby zmienić jego wartość: +Jeśli `$this->lang` ma wartość taką jak `'en'`, to linki utworzone przy użyciu `link()` lub `n:href` będą zawierały również parametr `lang=en`. A gdy link zostanie kliknięty, ponownie będzie to `$this->lang = 'en'`. -```latte -detail v češtině -``` - -Możesz też usunąć go poprzez zresetowanie: - -```latte -klikni -``` +Dla właściwości zalecamy dołączenie typu danych (np. `string`) i można również dołączyć wartość domyślną. Wartości parametrów mogą być [walidowane |#Validation of Persistent Parameters]. -Zmienna trwała musi być zadeklarowana jako publiczna. Można również określić wartość domyślną. Jeśli parametr ma taką samą wartość jak domyślny, nie zostanie uwzględniony w adresie URL. +Trwałe parametry są domyślnie przekazywane pomiędzy wszystkimi akcjami danego prezentera. Aby przekazać je pomiędzy wieloma prezenterami, musisz je zdefiniować: -Persistence bierze pod uwagę hierarchię klas prezenterów, więc parametr zdefiniowany w prezenterze lub cechach jest automatycznie przekazywany do każdego prezentera dziedziczącego po nim lub używającego tej samej cechy. - -W PHP 8 możesz również użyć atrybutów do wskazania trwałych parametrów: +- we wspólnym przodku, po którym dziedziczą prezentery +- w cechach, które są używane przez prezenterów: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Możesz zmienić wartość trwałego parametru podczas tworzenia łącza: + +```latte +detail in Czech +``` + +Można też go *resetować*, czyli usunąć z adresu URL. Przyjmie on wtedy swoją domyślną wartość: + +```latte +click ``` @@ -302,7 +310,32 @@ Z tym, co do tej pory omówiliśmy w tym rozdziale, jesteś prawdopodobnie całk Wymagania i parametry .[#toc-requirement-and-parameters] -------------------------------------------------------- -Żądanie obsługiwane przez prezentera ma postać obiektu [api:Nette\Application\Request] i jest zwracane przez metodę prezentera `getRequest()`. Zawiera tablicę parametrów, a każdy z nich należy albo do któregoś z komponentów, albo bezpośrednio do prezentera (który w rzeczywistości też jest komponentem, choć specjalnym). Nette redystrybuuje więc parametry i przekazuje je między poszczególnymi komponentami (i prezenterem), wywołując metodę `loadState(array $params)`, która jest szerzej opisana w rozdziale [Komponenty |Components]. Parametry można uzyskać przy pomocy metody `getParameters(): array`, indywidualnie przy pomocy `getParameter($name)`. Wartości parametrów są ciągami lub tablicami ciągów, są to w zasadzie surowe dane uzyskane bezpośrednio z adresu URL. +Żądanie obsługiwane przez prezentera ma postać obiektu [api:Nette\Application\Request] i jest zwracane przez metodę prezentera `getRequest()`. Zawiera ona tablicę parametrów, a każdy z nich należy albo do któregoś z komponentów, albo bezpośrednio do prezentera (który w rzeczywistości też jest komponentem, choć specjalnym). Nette dokonuje więc redystrybucji parametrów i przechodzi między poszczególnymi komponentami (i prezenterem), wywołując metodę `loadState(array $params)`. Parametry można uzyskać za pomocą metody `getParameters(): array`, indywidualnie za pomocą `getParameter($name)`. Wartości parametrów są ciągami lub tablicami ciągów, są to w zasadzie surowe dane uzyskane bezpośrednio z adresu URL. + + +Walidacja trwałych parametrów .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------- + +Wartości [trwałych parametrów |#persistent parameters] otrzymanych z adresów URL są zapisywane do właściwości przez metodę `loadState()`. Sprawdza ona również, czy typ danych określony we właściwości pasuje, w przeciwnym razie odpowie błędem 404 i strona nie zostanie wyświetlona. + +Nigdy ślepo nie ufaj trwałym parametrom, ponieważ mogą one zostać łatwo nadpisane przez użytkownika w adresie URL. Na przykład w ten sposób sprawdzamy, czy `$this->lang` jest wśród obsługiwanych języków. Dobrym sposobem na to jest nadpisanie metody `loadState()` wspomnianej powyżej: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // tutaj jest ustawiony $this->lang + // następuje sprawdzenie wartości użytkownika: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Zapisywanie i przywracanie wniosku .[#toc-save-and-restore-the-request] diff --git a/application/pl/routing.texy b/application/pl/routing.texy index d0054da557..1cc006af6e 100644 --- a/application/pl/routing.texy +++ b/application/pl/routing.texy @@ -93,12 +93,12 @@ Trasa będzie teraz akceptować również adres URL `https://example.com/chronic Oczywiście parametrem może być również prezenter i nazwa wydarzenia. Na przykład: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Podana trasa przyjmuje np. adresy URL o postaci `/article/edit` lub również `/catalog/list` i rozumie je jako prezentery i wydarzenia `Article:edit` i `Catalog:list`. -Jednocześnie nadaje parametrom `presenter` i `action` wartości domyślne `Homepage` i `default`, a zatem są one również opcjonalne. Tak więc router akceptuje również adresy URL w postaci `/article` i traktuje je jako `Article:default`. Lub odwrotnie, link do `Product:default` wygeneruje ścieżkę `/product`, link do domyślnego `Homepage:default` wygeneruje ścieżkę `/`. +Jednocześnie nadaje parametrom `presenter` i `action` wartości domyślne `Home` i `default`, a zatem są one również opcjonalne. Tak więc router akceptuje również adresy URL w postaci `/article` i traktuje je jako `Article:default`. Lub odwrotnie, link do `Product:default` wygeneruje ścieżkę `/product`, link do domyślnego `Home:default` wygeneruje ścieżkę `/`. Maska może opisywać nie tylko ścieżkę względną z korzenia strony, ale także ścieżkę bezwzględną, jeśli zaczyna się od ukośnika, a nawet pełny bezwzględny adres URL, jeśli zaczyna się od dwóch ukośników: @@ -160,7 +160,7 @@ Sekwencje mogą być dowolnie osadzane i łączone: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // Akceptuje cesty: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Parametry opcjonalne (tzn. parametry posiadające wartość domyślną) bez nawiasów kwadratowych zachowują się zasadniczo tak, jakby były objęte nawiasami, jak poniżej: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // odpovídá tomuto: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Jeśli chcemy wpłynąć na zachowanie ukośnika spiczastego tak, aby np. zamiast `/homepage/` generowany był tylko `/homepage`, można to zrobić w następujący sposób: +Jeśli chcemy wpłynąć na zachowanie ukośnika spiczastego tak, aby np. zamiast `/home/` generowany był tylko `/home`, można to zrobić w następujący sposób: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ Drugi parametr routy, który często zapisywany jest w formacie `Presenter:actio ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filtry i tłumaczenia .[#toc-filters-and-translations] Kod źródłowy aplikacji piszemy w języku angielskim, ale jeśli strona ma mieć czeski adres URL, to prosty typ routingu: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` wygeneruje angielski adres URL, taki jak `/product/123` lub `/cart`. Jeśli chcemy, aby prezentery i akcje w adresie URL były reprezentowane przez czeskie słowa (np. `/produkt/123` lub `/kosik`), możemy użyć słownika tłumaczeń. Aby go napisać, potrzebujemy bardziej "verbose" wersji drugiego parametru: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // řetězec v URL => presenter 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Oprócz filtrów specyficznych dla parametrów, możemy również zdefiniować f use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Parametrem konstruktora SimpleRouter jest domyślny prezenter & akcja, do której zostaniemy skierowani, jeśli otworzymy stronę bez parametrów, np. `http://example.com/`. ```php -// výchozím presenterem bude 'Homepage' a akce 'default' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// výchozím presenterem bude 'Home' a akce 'default' +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Zalecane jest zdefiniowanie SimpleRoutera bezpośrednio w [konfiguracji |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ Podczas przetwarzania żądania musimy zwrócić co najmniej prezentera i akcję ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/pl/templates.texy b/application/pl/templates.texy index 4c18b34672..a7dd044914 100644 --- a/application/pl/templates.texy +++ b/application/pl/templates.texy @@ -42,7 +42,9 @@ Wyszukiwanie szablonów .[#toc-search-for-templates] - `templates//.latte` - `templates/..latte` -Jeśli szablon nie zostanie znaleziony, odpowiedzią jest [błąd 404 |presenters#Error-404-etc]. +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. + +Jeśli tam również nie zostanie znaleziony szablon, odpowiedzią będzie [błąd 404 |presenters#Error 404 etc.]. 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')`. @@ -148,7 +150,7 @@ Szablon tworzy w ten sposób linki do innych prezenterów & wydarzeń: Atrybut `n:href` jest bardzo przydatny dla znaczników HTML ``. Jeśli chcemy wymienić link w innym miejscu, na przykład w tekście, używamy `{link}`: ```latte -Adresa je: {link Homepage:default} +Adresa je: {link Home:default} ``` Aby uzyskać więcej informacji, zobacz [Tworzenie linków URL |creating-links]. diff --git a/application/pt/@left-menu.texy b/application/pt/@left-menu.texy index aef9759b41..b44fee7d04 100644 --- a/application/pt/@left-menu.texy +++ b/application/pt/@left-menu.texy @@ -15,5 +15,8 @@ Aplicação Nette Leitura adicional ***************** +- [Por que usar Nette? |www:10-reasons-why-nette] +- [Instalação |nette:installation] +- [Crie sua primeira aplicação! |quickstart:] - [As melhores práticas |best-practices:] - [Solução de problemas |nette:troubleshooting] diff --git a/application/pt/ajax.texy b/application/pt/ajax.texy index cf4a0f988e..2ef450e4d6 100644 --- a/application/pt/ajax.texy +++ b/application/pt/ajax.texy @@ -10,9 +10,13 @@ As aplicações web modernas atualmente rodam metade em um servidor e metade em -Uma solicitação AJAX pode ser detectada usando um método de um serviço [que encapsula uma solicitação HTTP |http:request] `$httpRequest->isAjax()` (detecta com base no cabeçalho HTTP `X-Requested-With` ). Há também um método abreviado no apresentador: `$this->isAjax()`. -Um pedido AJAX não é diferente de um pedido normal - um apresentador é chamado com uma certa visão e parâmetros. Também depende do apresentador como ele irá reagir: ele pode usar suas rotinas para retornar um fragmento de código HTML (um snippet), um documento XML, um objeto JSON ou um pedaço de código Javascript. +Solicitação AJAX .[#toc-ajax-request] +===================================== + +Uma solicitação AJAX não difere de uma solicitação clássica - o apresentador é chamado com uma visão e parâmetros específicos. Cabe também ao apresentador como responder a ela: ele pode usar sua própria rotina, que retorna um fragmento de código HTML (HTML snippet), um documento XML, um objeto JSON ou código JavaScript. + +No lado do servidor, uma solicitação AJAX pode ser detectada usando o método de serviço [que encapsula a solicitação HTTP |http:request] `$httpRequest->isAjax()` (detecta com base no cabeçalho HTTP `X-Requested-With`). Dentro do apresentador, um atalho está disponível na forma do método `$this->isAjax()`. Há um objeto pré-processado chamado `payload` dedicado ao envio de dados para o navegador no JSON. @@ -60,6 +64,21 @@ npm install naja ``` +Para criar uma solicitação AJAX a partir de um link regular (sinal) ou envio de formulário, basta marcar o link, formulário ou botão relevante com a classe `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Snippets .[#toc-snippets] ========================= @@ -149,7 +168,7 @@ Você não pode redesenhar um trecho dinâmico diretamente (o redesenho de `item No exemplo acima você tem que ter certeza de que para um pedido AJAX apenas um item será adicionado à matriz `$list`, portanto o laço `foreach` imprimirá apenas um trecho dinâmico. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/pt/bootstrap.texy b/application/pt/bootstrap.texy index 2fe2d46f10..1ebef20eff 100644 --- a/application/pt/bootstrap.texy +++ b/application/pt/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -Nos arquivos de configuração, podemos escrever a notação usual `%projectId%` para acessar o parâmetro `projectId`. Por padrão, o Configurador preenche os seguintes parâmetros: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` e `consoleMode`. +Nos arquivos de configuração, podemos escrever a notação usual `%projectId%` para acessar o parâmetro `projectId`. Parâmetros dinâmicos .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Parâmetros padrão .[#toc-default-parameters] +-------------------------------------------- + +Você pode usar os seguintes parâmetros estáticos nos arquivos de configuração: + +- `%appDir%` é o caminho absoluto para o diretório do arquivo `Bootstrap.php` +- `%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 +- `%debugMode%` indica se a aplicação está em modo de depuração +- `%consoleMode%` indica se o pedido veio através da linha de comando + + Serviços Importados .[#toc-imported-services] --------------------------------------------- diff --git a/application/pt/components.texy b/application/pt/components.texy index 8f43d26d5e..55d25cc8ce 100644 --- a/application/pt/components.texy +++ b/application/pt/components.texy @@ -233,31 +233,34 @@ No modelo, estas mensagens estão disponíveis na variável `$flashes` como obje Parâmetros Persistentes .[#toc-persistent-parameters] ===================================================== -Muitas vezes é necessário manter algum parâmetro em um componente durante todo o tempo de trabalho com o componente. Pode ser, por exemplo, o número da página na paginação. Este parâmetro deve ser marcado como persistente usando a anotação `@persistent`. +Parâmetros persistentes são usados para manter o estado em componentes entre diferentes solicitações. Seu valor permanece o mesmo, mesmo depois que um link é clicado. Ao contrário dos dados da sessão, eles são transferidos na URL. E eles são transferidos automaticamente, incluindo links criados em outros componentes na mesma página. + +Por exemplo, você tem um componente de paginação de conteúdo. Pode haver vários desses componentes em uma página. E você quer que todos os componentes permaneçam em sua página atual quando você clicar no link. Portanto, tornamos o número da página (`page`) um parâmetro persistente. + +Criar um parâmetro persistente é extremamente fácil em Nette. Basta criar uma propriedade pública e etiquetá-la com o atributo: (anteriormente foi utilizado `/** @persistent */` ) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // esta linha é importante + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // deve ser público } ``` -Este parâmetro será passado automaticamente em cada link como um parâmetro `GET` até que o usuário deixe a página com este componente. +Recomendamos que você inclua o tipo de dados (por exemplo `int`) com o imóvel, e você também pode incluir um valor padrão. Os valores dos parâmetros podem ser [validados |#Validation of Persistent Parameters]. -.[caution] -Nunca confie cegamente nos parâmetros persistentes, pois eles podem ser falsificados facilmente (sobrescrevendo a URL). Verificar, por exemplo, se o número da página está dentro do intervalo correto. +Você pode alterar o valor de um parâmetro persistente ao criar um link: -No PHP 8, você também pode usar atributos para marcar parâmetros persistentes: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Ou pode ser *reset*, ou seja, removido do URL. Então, ele tomará seu valor padrão: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ Os componentes de uma aplicação Nette são as partes reutilizáveis de uma apl 1) é renderizável em um modelo 2) ela sabe qual parte de si mesma deve prestar durante um [pedido AJAX |ajax#invalidation] (trechos) -3) tem a capacidade de armazenar seu estado em uma URL (parâmetros de persistência) +3) tem a capacidade de armazenar seu estado em uma URL (parâmetros persistentes) 4) tem a capacidade de responder às ações (sinais) do usuário 5) cria uma estrutura hierárquica (onde a raiz é o apresentador) @@ -403,6 +406,33 @@ Ciclo de vida do componente .[#toc-life-cycle-of-component] [* lifecycle-component.svg *] *** Ciclo de vida do componente* .<> +Validação de Parâmetros Persistentes .[#toc-validation-of-persistent-parameters] +-------------------------------------------------------------------------------- + +Os valores de [parâmetros persistentes |#persistent parameters] recebidos de URLs são escritos nas propriedades pelo método `loadState()`. Ele também verifica se o tipo de dados especificado para a propriedade corresponde, caso contrário ele responderá com um erro 404 e a página não será exibida. + +Nunca confie cegamente em parâmetros persistentes porque eles podem ser facilmente sobrescritos pelo usuário no URL. Por exemplo, é assim que verificamos se o número da página `$this->page` é maior que 0. Uma boa maneira de fazer isso é sobrescrever o método `loadState()` mencionado acima: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // aqui está definido o $this->page + // segue a verificação do valor do usuário: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +O processo oposto, ou seja, a coleta de valores de properites persistentes, é tratado pelo método `saveState()`. + + Sinais em profundidade .[#toc-signals-in-depth] ----------------------------------------------- diff --git a/application/pt/creating-links.texy b/application/pt/creating-links.texy index df174aca3e..b58d739ab1 100644 --- a/application/pt/creating-links.texy +++ b/application/pt/creating-links.texy @@ -52,7 +52,7 @@ Os chamados [parâmetros persistentes |presenters#persistent parameters] também Atributo `n:href` é muito útil para tags HTML ``. Se quisermos imprimir o link em outro lugar, por exemplo, no texto, usamos `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ O formato é suportado por todas as etiquetas Latte e todos os métodos de apres A forma básica é, portanto, `Presenter:action`: ```latte -homepage +home ``` Se nos ligarmos à ação do atual apresentador, podemos omitir seu nome: ```latte -homepage +home ``` Se a ação é `default`, podemos omiti-la, mas o cólon deve permanecer: ```latte -homepage +home ``` Os links também podem apontar para outros [módulos |modules]. Aqui, os links são diferenciados em relativos aos submódulos, ou absolutos. O princípio é análogo aos caminhos do disco, somente em vez de cortes existem colons. Vamos supor que o apresentador real faça parte do módulo `Front`, então escreveremos: @@ -119,7 +119,7 @@ Um caso especial está [ligado a si mesmo |#Links to Current Page]. Aqui escreve Podemos criar um link para uma determinada parte da página HTML através do chamado fragmento após o símbolo `#` hash: ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ Caminhos Absolutos .[#toc-absolute-paths] Links gerados por `link()` ou `n:href` são sempre caminhos absolutos (ou seja, começam com `/`), mas não URLs absolutas com um protocolo e domínio como `https://domain`. -Para gerar uma URL absoluta, acrescente duas barras ao início (por exemplo, `n:href="//Homepage:"`). Ou você pode mudar o apresentador para gerar apenas links absolutos, definindo `$this->absoluteUrls = true`. +Para gerar uma URL absoluta, acrescente duas barras ao início (por exemplo, `n:href="//Home:"`). Ou você pode mudar o apresentador para gerar apenas links absolutos, definindo `$this->absoluteUrls = true`. Link para a página atual .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ Como [os componentes |components] são unidades reutilizáveis separadas que nã Se quisermos fazer um link para apresentadores no modelo de componente, usamos a tag `{plink}`: ```latte -homepage +home ``` ou no código ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/pt/how-it-works.texy b/application/pt/how-it-works.texy index 596ea9373b..a4e6915e34 100644 --- a/application/pt/how-it-works.texy +++ b/application/pt/how-it-works.texy @@ -23,10 +23,10 @@ A estrutura do diretório é algo parecido com isto: web-project/ ├── app/ ← directory with application │ ├── Presenters/ ← presenter classes -│ │ ├── HomepagePresenter.php ← Homepage presenter class +│ │ ├── HomePresenter.php ← Home presenter class │ │ └── templates/ ← templates directory │ │ ├── @layout.latte ← template of shared layout -│ │ └── Homepage/ ← templates for Homepage presenter +│ │ └── Home/ ← templates for Home presenter │ │ └── default.latte ← template for action `default` │ ├── Router/ ← configuration of URL addresses │ └── Bootstrap.php ← booting class Bootstrap @@ -134,10 +134,10 @@ Só para ter certeza, vamos tentar recapitular todo o processo com uma URL ligei 1) a URL será `https://example.com` 2) iniciamos a aplicação, criamos um container e executamos `Application::run()` -3) o roteador decodifica a URL como um par `Homepage:default` -4) um objeto `HomepagePresenter` é criado +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/Homepage/default.latte` com um layout `templates/@layout.latte` é apresentado +6) um modelo `templates/Home/default.latte` com um layout `templates/@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 fbabdb0113..35ee3786d3 100644 --- a/application/pt/modules.texy +++ b/application/pt/modules.texy @@ -104,7 +104,7 @@ 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`. -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 `Homepage` deve mapear para a classe `App\Presenters\HomepagePresenter`. Isto pode ser conseguido através da seguinte configuração: +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: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Agora o apresentador `Front:Homepage` mapeia para a classe `App\Modules\Front\Presenters\HomepagePresenter` e o apresentador `Admin:Dashboard` para a classe `App\Modules\Admin\Presenters\DashboardPresenter`. +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`. É 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: diff --git a/application/pt/presenters.texy b/application/pt/presenters.texy index 24c949200b..d8fb8a9e30 100644 --- a/application/pt/presenters.texy +++ b/application/pt/presenters.texy @@ -158,7 +158,7 @@ O `forward()` muda imediatamente para o novo apresentador sem redirecionamento H $this->forward('Product:show'); ``` -Exemplo de redirecionamento temporário com o código HTTP 302 ou 303: +Exemplo de um chamado redirecionamento temporário com o código HTTP 302 (ou 303, se o método de solicitação atual for POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ Para obter um redirecionamento permanente com o uso do código HTTP 301: $this->redirectPermanent('Product:show', $id); ``` -Você pode redirecionar para outra URL fora da aplicação com o método `redirectUrl()`: +Você pode redirecionar para outra URL fora da aplicação usando o método `redirectUrl()`. O código HTTP pode ser especificado como o segundo parâmetro, sendo o padrão 302 (ou 303, se o método de solicitação atual for POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Parâmetros Persistentes .[#toc-persistent-parameters] ===================================================== -Os parâmetros persistentes são **transferidos automaticamente** em links. Isto significa que não temos que especificá-los explicitamente em cada `link()` ou `n:href` no modelo, mas eles ainda serão transferidos. +Parâmetros persistentes são usados para manter o estado entre diferentes solicitações. Seu valor permanece o mesmo mesmo, mesmo depois que um link é clicado. Ao contrário dos dados da sessão, eles são passados na URL. Isto é completamente automático, portanto não há necessidade de declará-los explicitamente em `link()` ou `n:href`. -Se sua aplicação tem versões em vários idiomas, então o idioma atual é um parâmetro que deve sempre fazer parte da URL. E seria incrivelmente cansativo mencioná-lo em cada link. Isso não é necessário com Nette. Basta marcar o parâmetro `lang` como persistente desta forma: +Exemplo de uso? Você tem uma aplicação multilíngüe. O idioma real é um parâmetro que precisa fazer parte da URL o tempo todo. Mas seria incrivelmente entediante incluí-lo em cada link. Portanto, você o torna um parâmetro persistente chamado `lang` e ele se carregará sozinho. Legal! + +Criar um parâmetro persistente é extremamente fácil em Nette. Basta criar uma propriedade pública e etiquetá-la com o atributo: (anteriormente foi utilizado `/** @persistent */` ) ```php +use Nette\Application\Attributes\Persistent; // esta linha é importante + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // deve ser público } ``` -Se o valor atual do parâmetro `lang` for `'en'`, então a URL criada com `link()` ou `n:href` no modelo conterá `lang=en`. Ótimo! - -Entretanto, também podemos acrescentar o parâmetro `lang` e com isso alterar seu valor: +Se `$this->lang` tem um valor como `'en'`, então os links criados usando `link()` ou `n:href` também conterão o parâmetro `lang=en`. E quando o link for clicado, ele será novamente `$this->lang = 'en'`. -```latte -detail in English -``` - -Ou, inversamente, pode ser removido por ajuste a zero: - -```latte -click here -``` +Para propriedades, recomendamos que você inclua o tipo de dados (por exemplo, `string`) e você também pode incluir um valor padrão. Os valores dos parâmetros podem ser [validados |#Validation of Persistent Parameters]. -A variável persistente deve ser declarada como pública. Também podemos especificar um valor padrão. Se o parâmetro tiver o mesmo valor padrão, ele não será incluído na URL. +Parâmetros persistentes são passados por padrão entre todas as ações de um determinado apresentador. Para passá-los entre vários apresentadores, você precisa defini-los também: -A persistência reflete a hierarquia das classes de apresentadores, assim o parâmetro definido em um determinado apresentador ou traço é então transferido automaticamente para cada apresentador que herda dele ou usa o mesmo traço. - -No PHP 8, você também pode usar atributos para marcar parâmetros persistentes: +- em um ancestral comum do qual os apresentadores herdam +- no traço que os apresentadores usam: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Você pode alterar o valor de um parâmetro persistente ao criar um link: + +```latte +detail in Czech +``` + +Ou pode ser *reset*, ou seja, removido da URL. Então, ele tomará seu valor padrão: + +```latte +click ``` @@ -302,7 +310,32 @@ O que mostramos até agora neste capítulo provavelmente será suficiente. As se Requisitos e parâmetros .[#toc-requirement-and-parameters] ---------------------------------------------------------- -O pedido tratado pelo apresentador é o objeto [api:Nette\Application\Request] e é devolvido pelo método do apresentador `getRequest()`. Ele inclui um conjunto de parâmetros e cada um deles pertence a alguns dos componentes ou diretamente ao apresentador (que na verdade é também um componente, embora especial). Portanto, Nette redistribui os parâmetros e passa entre os componentes individuais (e o apresentador) chamando o método `loadState(array $params)`, que é descrito mais detalhadamente no capítulo [Componentes |Components]. Os parâmetros podem ser obtidos pelo método `getParameters(): array`, individualmente, usando `getParameter($name)`. Os valores dos parâmetros são strings ou matrizes de strings, são basicamente dados brutos obtidos diretamente de uma URL. +O pedido tratado pelo apresentador é o objeto [api:Nette\Application\Request] e é devolvido pelo método do apresentador `getRequest()`. Ele inclui um conjunto de parâmetros e cada um deles pertence a alguns dos componentes ou diretamente ao apresentador (que na verdade é também um componente, embora especial). Assim, Nette redistribui os parâmetros e passa entre os componentes individuais (e o apresentador), chamando o método `loadState(array $params)`. Os parâmetros podem ser obtidos pelo método `getParameters(): array`, individualmente usando `getParameter($name)`. Os valores dos parâmetros são strings ou matrizes de strings, são basicamente dados brutos obtidos diretamente de uma URL. + + +Validação de Parâmetros Persistentes .[#toc-validation-of-persistent-parameters] +-------------------------------------------------------------------------------- + +Os valores de [parâmetros persistentes |#persistent parameters] recebidos de URLs são escritos nas propriedades pelo método `loadState()`. Ele também verifica se o tipo de dados especificado na propriedade corresponde, caso contrário, responderá com um erro 404 e a página não será exibida. + +Nunca confie cegamente em parâmetros persistentes, pois eles podem ser facilmente sobrescritos pelo usuário no URL. Por exemplo, é assim que verificamos se `$this->lang` está entre os idiomas suportados. Uma boa maneira de fazer isso é sobrescrever o método `loadState()` mencionado acima: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // aqui está definido o $this->lang + // segue a verificação do valor do usuário: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Salvar e Restaurar o Pedido .[#toc-save-and-restore-the-request] diff --git a/application/pt/routing.texy b/application/pt/routing.texy index 23679faf82..d9ad4462b0 100644 --- a/application/pt/routing.texy +++ b/application/pt/routing.texy @@ -93,12 +93,12 @@ A rota agora aceitará a URL `https://any-domain.com/chronicle/` com o parâmetr Naturalmente, o nome do apresentador e a ação também podem ser um parâmetro. Por exemplo, o nome do apresentador e a ação também podem ser um parâmetro: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Esta rota aceita, por exemplo, uma URL no formulário `/article/edit` resp. `/catalog/list` e as traduz para os apresentadores e ações `Article:edit` resp. `Catalog:list`. -Também dá aos parâmetros `presenter` e `action` valores padrão`Homepage` e `default` e, portanto, são opcionais. Portanto, a rota também aceita uma URL `/article` e a traduz como `Article:default`. Ou vice versa, um link para `Product:default` gera um caminho `/product`, um link para o padrão `Homepage:default` gera um caminho `/`. +Também dá aos parâmetros `presenter` e `action` valores padrão`Home` e `default` e, portanto, são opcionais. Portanto, a rota também aceita uma URL `/article` e a traduz como `Article:default`. Ou vice versa, um link para `Product:default` gera um caminho `/product`, um link para o padrão `Home:default` gera um caminho `/`. A máscara pode descrever não apenas o caminho relativo baseado na raiz do site, mas também o caminho absoluto quando ele começa com uma barra, ou mesmo todo o URL absoluto quando começa com duas barras: @@ -160,7 +160,7 @@ As seqüências podem ser livremente aninhadas e combinadas: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // URLs aceitas: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Os parâmetros opcionais (ou seja, parâmetros com valor padrão) sem parênteses rectos comportam-se como se fossem embrulhados desta forma: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // é igual a: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Para mudar a forma como a barra mais à direita é gerada, ou seja, ao invés de `/homepage/`, obtenha um `/homepage`, ajuste a rota desta forma: +Para mudar a forma como a barra mais à direita é gerada, ou seja, ao invés de `/home/`, obtenha um `/home`, ajuste a rota desta forma: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ O segundo parâmetro da rota, que freqüentemente escrevemos no formato `Present ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filtros e Traduções .[#toc-filters-and-translations] É uma boa prática escrever o código fonte em inglês, mas e se você precisar que seu website tenha o URL traduzido para outro idioma? Rotas simples, como por exemplo: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` gerará URLs em inglês, tais como `/product/123` ou `/cart`. Se quisermos ter apresentadores e ações na URL traduzidas para o Deutsch (por exemplo `/produkt/123` ou `/einkaufswagen`), podemos usar um dicionário de tradução. Para adicioná-lo, já precisamos de uma variante "mais faladora" do segundo parâmetro: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // string na URL => apresentador 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Além dos filtros para parâmetros específicos, você também pode definir filt use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 O parâmetro do construtor `SimpleRouter` é um apresentador e ação padrão, ou seja, ação a ser executada se abrirmos, por exemplo, `http://example.com/` sem parâmetros adicionais. ```php -// default para o apresentador 'Homepage' e ação 'default -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// default para o apresentador 'Home' e ação 'default +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Recomendamos definir o SimpleRouter diretamente na [configuração |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ Ao processar o pedido, devemos retornar pelo menos o apresentador e a ação. O ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/pt/templates.texy b/application/pt/templates.texy index bd63ac37d7..059e35178e 100644 --- a/application/pt/templates.texy +++ b/application/pt/templates.texy @@ -42,7 +42,9 @@ O caminho para os modelos é deduzido de acordo com uma lógica simples. Ele ten - `templates//.latte` - `templates/..latte` -Se não encontrar o modelo, a resposta é [erro 404 |presenters#Error 404 etc.]. +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. + +Se o modelo também não for encontrado lá, a resposta é um [erro 404 |presenters#Error 404 etc.]. 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')`. @@ -148,7 +150,7 @@ No modelo, criamos links para outros apresentadores e ações da seguinte forma: O atributo `n:href` é muito útil para tags HTML ``. Se quisermos imprimir o link em outro lugar, por exemplo, no texto, usamos `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` Para mais informações, consulte [Criação de links |Creating Links]. diff --git a/application/ro/@left-menu.texy b/application/ro/@left-menu.texy index 844756dc21..97c4913b85 100644 --- a/application/ro/@left-menu.texy +++ b/application/ro/@left-menu.texy @@ -15,5 +15,8 @@ Aplicație Nette Lecturi suplimentare ******************** +- [De ce să folosiți Nette? |www:10-reasons-why-nette] +- [Instalare |nette:installation] +- [Creați prima dumneavoastră aplicație! |quickstart:] - [Cele mai bune practici |best-practices:] - [Rezolvarea problemelor |nette:troubleshooting] diff --git a/application/ro/ajax.texy b/application/ro/ajax.texy index 3c1de634a7..145ab87fdd 100644 --- a/application/ro/ajax.texy +++ b/application/ro/ajax.texy @@ -10,9 +10,13 @@ AJAX & Snippets -O cerere AJAX poate fi detectată prin intermediul unei metode a unui serviciu care [încapsulează o cerere HTTP |http:request] `$httpRequest->isAjax()` (detectează pe baza antetului HTTP `X-Requested-With` ). Există, de asemenea, o metodă prescurtată în presenter: `$this->isAjax()`. -O solicitare AJAX nu diferă cu nimic de una normală - un prezentator este apelat cu o anumită vizualizare și parametri. Depinde, de asemenea, de prezentator cum va reacționa: acesta își poate folosi rutinele pentru a returna fie un fragment de cod HTML (un snippet), fie un document XML, un obiect JSON sau o bucată de cod Javascript. +Cerere AJAX .[#toc-ajax-request] +================================ + +O cerere AJAX nu diferă de o cerere clasică - prezentatorul este apelat cu o vizualizare și parametri specifici. De asemenea, este la latitudinea prezentatorului cum să răspundă la aceasta: poate utiliza propria rutină, care returnează un fragment de cod HTML (fragment HTML), un document XML, un obiect JSON sau cod JavaScript. + +Pe partea serverului, o cerere AJAX poate fi detectată cu ajutorul metodei de serviciu care [încapsulează cererea HTTP |http:request] `$httpRequest->isAjax()` (detectează pe baza antetului HTTP `X-Requested-With`). În interiorul prezentatorului, este disponibilă o scurtătură sub forma metodei `$this->isAjax()`. Există un obiect preprocesat numit `payload` dedicat trimiterii de date către browser în JSON. @@ -60,6 +64,21 @@ npm install naja ``` +Pentru a crea o solicitare AJAX dintr-un link obișnuit (semnal) sau un formular de trimitere, trebuie doar să marcați link-ul, formularul sau butonul respectiv cu clasa `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Snippets .[#toc-snippets] ========================= @@ -149,7 +168,7 @@ Nu puteți redesena direct un fragment dinamic (redesenarea lui `item-1` nu are În exemplul de mai sus, trebuie să vă asigurați că, pentru o cerere AJAX, doar un singur element va fi adăugat la matricea `$list`, prin urmare, bucla `foreach` va imprima doar un singur fragment dinamic. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/ro/bootstrap.texy b/application/ro/bootstrap.texy index 585b6eed26..def23eed0e 100644 --- a/application/ro/bootstrap.texy +++ b/application/ro/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -În fișierele de configurare, putem scrie notația obișnuită `%projectId%` pentru a accesa parametrul numit `projectId`. În mod implicit, configuratorul completează următorii parametri: `appDir`, `wwwDir`, `tempDir`, , `vendorDir`, `debugMode` și `consoleMode`. +În fișierele de configurare, putem scrie notația obișnuită `%projectId%` pentru a accesa parametrul numit `projectId`. Parametrii dinamici .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Parametrii impliciți .[#toc-default-parameters] +----------------------------------------------- + +Puteți utiliza următorii parametri statici în fișierele de configurare: + +- `%appDir%` este calea absolută către directorul fișierului `Bootstrap.php` +- `%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 +- `%debugMode%` indică dacă aplicația se află în modul de depanare +- `%consoleMode%` indică dacă cererea a venit prin linia de comandă + + Servicii importate .[#toc-imported-services] -------------------------------------------- diff --git a/application/ro/components.texy b/application/ro/components.texy index bb58b75354..67cf732f5a 100644 --- a/application/ro/components.texy +++ b/application/ro/components.texy @@ -233,31 +233,34 @@ $this->redirect(/* ... */); // și redirecționarea Parametrii persistenți .[#toc-persistent-parameters] ==================================================== -Adesea este necesar să se păstreze un anumit parametru într-o componentă pe toată durata lucrului cu aceasta. Acesta poate fi, de exemplu, numărul paginii în paginare. Acest parametru trebuie marcat ca persistent cu ajutorul adnotării `@persistent`. +Parametrii persistenți sunt utilizați pentru a menține starea componentelor între diferite cereri. Valoarea lor rămâne aceeași chiar și după ce se face clic pe un link. Spre deosebire de datele de sesiune, aceștia sunt transferați în URL. Și sunt transferați automat, inclusiv legăturile create în alte componente din aceeași pagină. + +De exemplu, aveți o componentă de paginare a conținutului. Pot exista mai multe astfel de componente pe o pagină. Și doriți ca toate componentele să rămână pe pagina lor curentă atunci când faceți clic pe link. Prin urmare, facem din numărul paginii (`page`) un parametru persistent. + +Crearea unui parametru persistent este extrem de ușoară în Nette. Trebuie doar să creați o proprietate publică și să o marcați cu atributul: (anterior se folosea `/** @persistent */` ) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // această linie este importantă + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // trebuie să fie publice } ``` -Acest parametru va fi transmis automat în fiecare link ca parametru `GET` până când utilizatorul părăsește pagina cu această componentă. +Vă recomandăm să includeți tipul de date (de exemplu, `int`) cu proprietatea și puteți include și o valoare implicită. Valorile parametrilor pot fi [validate |#Validation of Persistent Parameters]. -.[caution] -Nu vă încredeți niciodată orbește în parametrii persistenți, deoarece aceștia pot fi falsificați cu ușurință (prin suprascrierea URL-ului). Verificați, de exemplu, dacă numărul paginii se află în intervalul corect. +Puteți modifica valoarea unui parametru persistent atunci când creați o legătură: -În PHP 8, puteți utiliza, de asemenea, atribute pentru a marca parametrii persistenți: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Sau poate fi *restat*, adică eliminat din URL. În acest caz, va lua valoarea implicită: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ Componentele într-o aplicație Nette sunt părțile reutilizabile ale unei apli 1) este redabilă într-un șablon 2) știe ce parte din ea însăși trebuie să redea în timpul unei [cereri AJAX |ajax#invalidation] (fragmente) -3) are capacitatea de a-și stoca starea într-un URL (parametri de persistență) +3) are capacitatea de a stoca starea sa într-un URL (parametri persistenți) 4) are capacitatea de a răspunde la acțiunile utilizatorului (semnale) 5) creează o structură ierarhică (în care rădăcina este prezentatorul) @@ -403,6 +406,33 @@ Ciclul de viață al componentei .[#toc-life-cycle-of-component] [* lifecycle-component.svg *] *** *Ciclul de viață al componentei* .<> +Validarea parametrilor persistenți .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------------ + +Valorile [parametrilor persistenți |#persistent parameters] primite de la URL-uri sunt scrise în proprietăți prin metoda `loadState()`. Aceasta verifică, de asemenea, dacă tipul de date specificat pentru proprietate se potrivește, în caz contrar se va răspunde cu o eroare 404 și pagina nu va fi afișată. + +Nu vă încredeți niciodată orbește în parametrii persistenți, deoarece aceștia pot fi ușor suprascriși de către utilizator în URL. De exemplu, acesta este modul în care verificăm dacă numărul paginii `$this->page` este mai mare decât 0. O modalitate bună de a face acest lucru este de a suprascrie metoda `loadState()` menționată mai sus: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // aici este setat $this->page + // urmează verificarea valorii utilizatorului: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Procesul opus, și anume colectarea valorilor din proprietățile persistente, este gestionat de metoda `saveState()`. + + Semnale în profunzime .[#toc-signals-in-depth] ---------------------------------------------- diff --git a/application/ro/creating-links.texy b/application/ro/creating-links.texy index 1b4d1fd1d4..659cc88a08 100644 --- a/application/ro/creating-links.texy +++ b/application/ro/creating-links.texy @@ -52,7 +52,7 @@ Așa-numiții [parametri persistenți |presenters#persistent parameters] sunt, d Atributul `n:href` este foarte util pentru etichetele HTML ``. Dacă dorim să imprimăm link-ul în altă parte, de exemplu în text, folosim `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ Formatul este acceptat de toate etichetele Latte și de toate metodele presenter Forma de bază este, prin urmare, `Presenter:action`: ```latte -homepage +home ``` Dacă facem legătura cu acțiunea prezentatorului curent, putem omite numele acestuia: ```latte -homepage +home ``` Dacă acțiunea este `default`, putem omite numele, dar trebuie să păstrăm două puncte: ```latte -homepage +home ``` Legăturile pot indica și alte [module |modules]. Aici, legăturile se disting în relative la submodule sau absolute. Principiul este analog cu cel al căilor de acces pe disc, doar că în loc de bară oblică se folosesc două puncte. Să presupunem că actualul prezentator face parte din modulul `Front`, atunci vom scrie: @@ -119,7 +119,7 @@ Un caz special este [legătura către sine |#Links to Current Page]. Aici vom sc Putem crea o legătură către o anumită parte a paginii HTML prin intermediul unui așa-numit fragment după simbolul hash `#`: ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ Căi de acces absolute .[#toc-absolute-paths] Legăturile generate de `link()` sau `n:href` sunt întotdeauna căi de acces absolute (adică încep cu `/`), dar nu și URL-uri absolute cu un protocol și domeniu, cum ar fi `https://domain`. -Pentru a genera o adresă URL absolută, adăugați două bariere la început (de exemplu, `n:href="//Homepage:"`). Sau puteți comuta prezentatorul pentru a genera numai link-uri absolute, setând `$this->absoluteUrls = true`. +Pentru a genera o adresă URL absolută, adăugați două bariere la început (de exemplu, `n:href="//Home:"`). Sau puteți comuta prezentatorul pentru a genera numai link-uri absolute, setând `$this->absoluteUrls = true`. Legătură către pagina curentă .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ Deoarece [componentele |components] sunt unități separate reutilizabile care n Dacă dorim să facem legătura cu prezentatorii din șablonul componentei, folosim eticheta `{plink}`: ```latte -homepage +home ``` sau în cod ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/ro/how-it-works.texy b/application/ro/how-it-works.texy index acecb91475..eeb65bb9f7 100644 --- a/application/ro/how-it-works.texy +++ b/application/ro/how-it-works.texy @@ -23,10 +23,10 @@ Structura directoarelor arată cam așa: web-project/ ├── app/ ← directory with application │ ├── Presenters/ ← presenter classes -│ │ ├── HomepagePresenter.php ← Homepage presenter class +│ │ ├── HomePresenter.php ← Home presenter class │ │ └── templates/ ← templates directory │ │ ├── @layout.latte ← template of shared layout -│ │ └── Homepage/ ← templates for Homepage presenter +│ │ └── Home/ ← templates for Home presenter │ │ └── default.latte ← template for action `default` │ ├── Router/ ← configuration of URL addresses │ └── Bootstrap.php ← booting class Bootstrap @@ -134,10 +134,10 @@ Doar pentru a fi siguri, să încercăm să recapitulăm întregul proces cu un 1) URL-ul va fi `https://example.com` 2) vom porni aplicația, vom crea un container și vom rula `Application::run()` -3) routerul decodifică URL-ul ca o pereche `Homepage:default` -4) este creat un obiect `HomepagePresenter` +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/Homepage/default.latte` cu un layout `templates/@layout.latte` +6) este redat un șablon `templates/Home/default.latte` cu un layout `templates/@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 d9a6513d73..bc8e11b0d2 100644 --- a/application/ro/modules.texy +++ b/application/ro/modules.texy @@ -104,7 +104,7 @@ 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`. -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 `Homepage` ar trebui să se mapeze la clasa `App\Presenters\HomepagePresenter`. Acest lucru poate fi realizat prin următoarea configurație: +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: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Acum, prezentatorul `Front:Homepage` se referă la clasa `App\Modules\Front\Presenters\HomepagePresenter` și prezentatorul `Admin:Dashboard` la clasa `App\Modules\Admin\Presenters\DashboardPresenter`. +Acum, prezentatorul `Front:Home` se referă la clasa `App\Modules\Front\Presenters\HomePresenter` și prezentatorul `Admin:Dashboard` la clasa `App\Modules\Admin\Presenters\DashboardPresenter`. 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: diff --git a/application/ro/presenters.texy b/application/ro/presenters.texy index f11d183908..98dfebc458 100644 --- a/application/ro/presenters.texy +++ b/application/ro/presenters.texy @@ -158,7 +158,7 @@ Metoda `forward()` trece imediat la noul prezentator, fără redirecționare HTT $this->forward('Product:show'); ``` -Exemplu de redirecționare temporară cu codul HTTP 302 sau 303: +Exemplu de așa-numită redirecționare temporară cu codul HTTP 302 (sau 303, dacă metoda de solicitare curentă este POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ Pentru a obține o redirecționare permanentă cu codul HTTP 301, utilizați: $this->redirectPermanent('Product:show', $id); ``` -Puteți redirecționa către un alt URL din afara aplicației cu metoda `redirectUrl()`: +Puteți redirecționa către o altă adresă URL din afara aplicației utilizând metoda `redirectUrl()`. Codul HTTP poate fi specificat ca al doilea parametru, valoarea implicită fiind 302 (sau 303, dacă metoda de solicitare curentă este POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Parametrii persistenți .[#toc-persistent-parameters] ==================================================== -Parametrii persistenți sunt **transferați automat** în legături. Acest lucru înseamnă că nu trebuie să îi specificăm în mod explicit în fiecare `link()` sau `n:href` din șablon, dar ei vor fi totuși transferați. +Parametrii persistenți sunt utilizați pentru a menține starea între diferite cereri. Valoarea lor rămâne aceeași chiar și după ce se face clic pe un link. Spre deosebire de datele de sesiune, aceștia sunt trecuți în URL. Acest lucru este complet automat, astfel încât nu este nevoie să îi menționăm în mod explicit în `link()` sau `n:href`. -Dacă aplicația dvs. are mai multe versiuni lingvistice, atunci limba curentă este un parametru care trebuie să facă întotdeauna parte din URL. Și ar fi incredibil de obositor să îl menționați în fiecare link. Acest lucru nu este necesar cu Nette. Pur și simplu marcăm parametrul `lang` ca fiind persistent în acest mod: +Exemplu de utilizare? Aveți o aplicație multilingvă. Limba actuală este un parametru care trebuie să facă parte în permanență din URL. Dar ar fi incredibil de anevoios să îl includeți în fiecare link. Așa că îl transformați într-un parametru persistent numit `lang` și se va păstra singur. E foarte bine! + +Crearea unui parametru persistent este extrem de ușoară în Nette. Trebuie doar să creați o proprietate publică și să o marcați cu atributul: (anterior se folosea `/** @persistent */` ) ```php +use Nette\Application\Attributes\Persistent; // această linie este importantă + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // trebuie să fie publice } ``` -Dacă valoarea curentă a parametrului `lang` este `'en'`, atunci URL-ul creat cu `link()` sau `n:href` în șablon va conține `lang=en`. Minunat! - -Cu toate acestea, putem adăuga și parametrul `lang` și, prin aceasta, putem schimba valoarea acestuia: +Dacă `$this->lang` are o valoare, cum ar fi `'en'`, atunci legăturile create folosind `link()` sau `n:href` vor conține și parametrul `lang=en`. Iar atunci când se face clic pe link, acesta va fi din nou `$this->lang = 'en'`. -```latte -detail in English -``` - -Sau, dimpotrivă, poate fi eliminat prin setarea la null: - -```latte -click here -``` +Pentru proprietăți, vă recomandăm să includeți tipul de date (de exemplu, `string`) și puteți include și o valoare implicită. Valorile parametrilor pot fi [validate |#Validation of Persistent Parameters]. -Variabila persistentă trebuie să fie declarată ca fiind publică. De asemenea, putem specifica o valoare implicită. Dacă parametrul are aceeași valoare ca cea implicită, acesta nu va fi inclus în URL. +Parametrii persistenți sunt trecuți în mod implicit între toate acțiunile unui anumit prezentator. Pentru a-i transmite între mai mulți prezentatori, trebuie să îi definiți fie: -Persistența reflectă ierarhia claselor de prezentatori, astfel încât parametrul definit într-un anumit prezentator sau trăsătură este apoi transferat automat la fiecare prezentator care moștenește din acesta sau care utilizează aceeași trăsătură. - -În PHP 8, puteți utiliza, de asemenea, atribute pentru a marca parametrii persistenți: +- într-un strămoș comun din care moștenesc prezentatorii +- în trăsătura pe care o utilizează prezentatorii: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Puteți modifica valoarea unui parametru persistent atunci când creați o legătură: + +```latte +detail in Czech +``` + +Sau poate fi *restat*, adică eliminat din URL. În acest caz, va lua valoarea implicită: + +```latte +click ``` @@ -302,7 +310,32 @@ Ceea ce am arătat până acum în acest capitol va fi probabil suficient. Rând Cerință și parametri .[#toc-requirement-and-parameters] ------------------------------------------------------- -Cererea gestionată de prezentator este obiectul [api:Nette\Application\Request] și este returnată de metoda prezentatorului `getRequest()`. Acesta include o matrice de parametri și fiecare dintre ei aparține fie unora dintre componente, fie direct prezentatorului (care este de fapt tot o componentă, deși una specială). Așadar, Nette redistribui parametrii și trece între componentele individuale (și prezentator) prin apelarea metodei `loadState(array $params)`, care este descrisă în continuare în capitolul [Componente |Components]. Parametrii pot fi obținuți prin metoda `getParameters(): array`, folosind individual `getParameter($name)`. Valorile parametrilor sunt șiruri de caractere sau array-uri de șiruri de caractere, acestea fiind practic date brute obținute direct dintr-un URL. +Cererea gestionată de prezentator este obiectul [api:Nette\Application\Request] și este returnată de metoda prezentatorului `getRequest()`. Acesta include o matrice de parametri și fiecare dintre ei aparține fie unora dintre componente, fie direct prezentatorului (care este de fapt tot o componentă, deși una specială). Astfel, Nette redistribuie parametrii și pasează între componentele individuale (și prezentator) prin apelarea metodei `loadState(array $params)`. Parametrii pot fi obținuți prin metoda `getParameters(): array`, folosind individual `getParameter($name)`. Valorile parametrilor sunt șiruri de caractere sau array-uri de șiruri de caractere, acestea fiind practic date brute obținute direct de la un URL. + + +Validarea parametrilor persistenți .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------------ + +Valorile [parametrilor persistenți |#persistent parameters] primite de la URL-uri sunt scrise în proprietăți prin metoda `loadState()`. Aceasta verifică, de asemenea, dacă tipul de date specificat în proprietate se potrivește, în caz contrar se va răspunde cu o eroare 404 și pagina nu va fi afișată. + +Nu vă încredeți niciodată orbește în parametrii persistenți, deoarece aceștia pot fi ușor suprascriși de către utilizator în URL. De exemplu, iată cum verificăm dacă `$this->lang` se află printre limbile acceptate. O modalitate bună de a face acest lucru este să suprascrieți metoda `loadState()` menționată mai sus: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // aici este setat $this->lang + // urmează verificarea valorii utilizatorului: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Salvarea și restaurarea cererii .[#toc-save-and-restore-the-request] diff --git a/application/ro/routing.texy b/application/ro/routing.texy index 9a99de408f..0879cd0109 100644 --- a/application/ro/routing.texy +++ b/application/ro/routing.texy @@ -93,12 +93,12 @@ Ruta va accepta acum URL-ul `https://any-domain.com/chronicle/` cu parametrul `y Bineînțeles, numele prezentatorului și al acțiunii pot fi, de asemenea, un parametru. De exemplu: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Această rută acceptă, de exemplu, un URL de forma `/article/edit` resp. `/catalog/list` și le traduce în prezentatorii și acțiunile `Article:edit` resp. `Catalog:list`. -De asemenea, oferă parametrilor `presenter` și `action` valori implicite`Homepage` și `default` și, prin urmare, aceștia sunt opționali. Astfel, ruta acceptă și un URL `/article` și îl traduce ca `Article:default`. Sau invers, un link către `Product:default` generează o cale `/product`, un link către `Homepage:default` implicit generează o cale `/`. +De asemenea, oferă parametrilor `presenter` și `action` valori implicite`Home` și `default` și, prin urmare, aceștia sunt opționali. Astfel, ruta acceptă și un URL `/article` și îl traduce ca `Article:default`. Sau invers, un link către `Product:default` generează o cale `/product`, un link către `Home:default` implicit generează o cale `/`. Masca poate descrie nu numai calea relativă bazată pe rădăcina site-ului, ci și calea absolută atunci când începe cu o bară oblică, sau chiar întregul URL absolut atunci când începe cu două bare oblice: @@ -160,7 +160,7 @@ Secvențele pot fi liber imbricate și combinate: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // URL-uri acceptate: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Parametrii opționali (adică parametrii cu valoare implicită) fără paranteze pătrate se comportă ca și cum ar fi înfășurați astfel: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // este egal cu: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Pentru a schimba modul în care este generată cea mai din dreapta bară oblică, adică în loc de `/homepage/` obțineți un `/homepage`, ajustați traseul în acest fel: +Pentru a schimba modul în care este generată cea mai din dreapta bară oblică, adică în loc de `/home/` obțineți un `/home`, ajustați traseul în acest fel: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ Al doilea parametru al traseului, pe care îl scriem adesea în formatul `Presen ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filtre și traduceri .[#toc-filters-and-translations] Este o bună practică să scrieți codul sursă în limba engleză, dar ce se întâmplă dacă aveți nevoie ca site-ul dvs. să aibă URL-ul tradus în altă limbă? Rute simple, cum ar fi: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` va genera URL-uri în limba engleză, cum ar fi `/product/123` sau `/cart`. Dacă dorim ca prezentatorii și acțiunile din URL să fie traduse în limba germană (de exemplu, `/produkt/123` sau `/einkaufswagen`), putem utiliza un dicționar de traducere. Pentru a-l adăuga, avem deja nevoie de o variantă "mai vorbăreață" a celui de-al doilea parametru: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // șir de caractere în URL => prezentator 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Filtre generale .[#toc-general-filters] use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Parametrul constructorului `SimpleRouter` este un prezentator și o acțiune implicită, adică acțiunea care va fi executată dacă deschidem, de exemplu, `http://example.com/` fără parametri suplimentari. ```php -// implicit la prezentatorul "Homepage" și acțiunea "default -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// implicit la prezentatorul "Home" și acțiunea "default +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Vă recomandăm să definiți SimpleRouter direct în [configurare |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ Atunci când procesăm cererea, trebuie să returnăm cel puțin prezentatorul ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/ro/templates.texy b/application/ro/templates.texy index c4be035160..abcead647e 100644 --- a/application/ro/templates.texy +++ b/application/ro/templates.texy @@ -42,7 +42,9 @@ Calea către șabloane este dedusă conform unei logici simple. Se încearcă s - `templates//.latte` - `templates/..latte` -În cazul în care nu găsește șablonul, răspunsul este [eroarea 404 |presenters#Error 404 etc.]. +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. + +Dacă șablonul nu este găsit nici acolo, răspunsul este o [eroare 404 |presenters#Error 404 etc.]. 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')`. @@ -148,7 +150,7 @@ Crearea legăturilor .[#toc-creating-links] Atributul `n:href` este foarte util pentru etichetele HTML ``. Dacă dorim să imprimăm link-ul în altă parte, de exemplu în text, folosim `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` Pentru mai multe informații, consultați [Crearea de linkuri |Creating Links]. diff --git a/application/ru/@left-menu.texy b/application/ru/@left-menu.texy index d00f4e17e2..a0ba69fe6b 100644 --- a/application/ru/@left-menu.texy +++ b/application/ru/@left-menu.texy @@ -15,5 +15,8 @@ Дальнейшее чтение ***************** +- [Почему стоит использовать Nette? |www:10-reasons-why-nette] +- [Установка |nette:installation] +- [Создайте свое первое приложение! |quickstart:] - [Лучшие практики |best-practices:] - [Устранение неполадок |nette:troubleshooting] diff --git a/application/ru/ajax.texy b/application/ru/ajax.texy index 90acd53800..6009fc864c 100644 --- a/application/ru/ajax.texy +++ b/application/ru/ajax.texy @@ -10,9 +10,13 @@ AJAX и сниппеты -AJAX-запрос может быть обнаружен с помощью метода сервиса [инкапсуляция HTTP-запроса |http:request] `$httpRequest->isAjax()` (определяет на основе HTTP-заголовка `X-Requested-With`). Существует также сокращенный метод в презентере: `$this->isAjax()`. -AJAX-запрос ничем не отличается от обычного — вызывается презентер с определенным представлением и параметрами. От презентера также зависит, как он отреагирует: он может использовать свои процедуры для возврата фрагмента HTML-кода (сниппета), XML-документа, объекта JSON или фрагмента кода Javascript. +Запрос AJAX .[#toc-ajax-request] +================================ + +AJAX-запрос не отличается от классического запроса - к ведущему обращаются с определенным представлением и параметрами. Ведущий также решает, как ответить на него: он может использовать свою собственную процедуру, которая возвращает фрагмент HTML-кода (HTML snippet), XML-документ, JSON-объект или JavaScript-код. + +На стороне сервера AJAX-запрос может быть обнаружен с помощью сервисного метода, [инкапсулирующего HTTP-запрос |http:request] `$httpRequest->isAjax()` (определяет на основе HTTP-заголовка `X-Requested-With`). Внутри презентатора доступен ярлык в виде метода `$this->isAjax()`. Существует предварительно обработанный объект `payload`, предназначенный для отправки данных в браузер в формате JSON. @@ -60,6 +64,21 @@ npm install naja ``` +Чтобы создать AJAX-запрос из обычной ссылки (сигнала) или отправки формы, просто пометьте соответствующую ссылку, форму или кнопку классом `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Сниппеты ======== @@ -149,7 +168,7 @@ $this->isControlInvalid('footer'); // -> true В приведенном примере необходимо убедиться, что при AJAX-запросе в массив `$list` будет добавлен только один элемент, поэтому цикл `foreach` будет выводить только один динамический фрагмент. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * Этот метод возвращает данные для списка. diff --git a/application/ru/bootstrap.texy b/application/ru/bootstrap.texy index fb4b949974..071d2c9fc5 100644 --- a/application/ru/bootstrap.texy +++ b/application/ru/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -В конфигурационных файлах можно использовать обычную нотацию `%projectId%` для доступа к параметру с именем `projectId`. По умолчанию конфигуратор заполняет следующие параметры: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` и `consoleMode`. +В конфигурационных файлах мы можем написать обычную нотацию `%projectId%` для доступа к параметру с именем `projectId`. Динамические параметры .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Параметры по умолчанию .[#toc-default-parameters] +------------------------------------------------- + +Вы можете использовать следующие статические параметры в конфигурационных файлах: + +- `%appDir%` - абсолютный путь к директории, содержащей файл `Bootstrap.php`. +- `%wwwDir%` - абсолютный путь к директории, содержащей входной файл `index.php` +- `%tempDir%` - абсолютный путь к директории для временных файлов. +- `%vendorDir%` - абсолютный путь к директории, в которую Composer устанавливает библиотеки. +- `%debugMode%` указывает, находится ли приложение в режиме отладки +- `%consoleMode%` указывает, поступил ли запрос через командную строку + + Импортированные сервисы .[#toc-imported-services] ------------------------------------------------- diff --git a/application/ru/components.texy b/application/ru/components.texy index 54f81b8b2f..b4e0f8a4d4 100644 --- a/application/ru/components.texy +++ b/application/ru/components.texy @@ -233,31 +233,34 @@ $this->redirect(/* ... */); // делаем редирект Постоянные параметры .[#toc-persistent-parameters] ================================================== -Часто бывает необходимо сохранить какой-либо параметр в компоненте на всё время работы с ним. Это может быть, например, номер страницы в пагинации. Этот параметр должен быть помечен как постоянный с помощью аннотации `@persistent`. +Постоянные параметры используются для сохранения состояния компонентов между различными запросами. Их значение остается неизменным даже после нажатия на ссылку. В отличие от данных сессии, они передаются в URL. И они передаются автоматически, включая ссылки, созданные в других компонентах на той же странице. + +Например, у вас есть компонент пейджинга контента. На странице может быть несколько таких компонентов. И вы хотите, чтобы при нажатии на ссылку все компоненты оставались на текущей странице. Поэтому мы делаем номер страницы (`page`) постоянным параметром. + +Создать постоянный параметр в Nette очень просто. Просто создайте публичное свойство и пометьте его атрибутом: (ранее использовалось `/** @persistent */` ). ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // эта строка важна + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // должны быть публичными } ``` -Этот параметр будет автоматически передаваться в каждой ссылке как параметр `GET` до тех пор, пока пользователь не покинет страницу с этим компонентом. +Мы рекомендуем указывать тип данных (например, `int`) вместе со свойством, а также можно указать значение по умолчанию. Значения параметров могут быть [проверены |#Validation of Persistent Parameters]. -.[caution] -Никогда не доверяйте постоянным параметрам вслепую, поскольку их можно легко подделать (путем перезаписи URL). Проверьте, например, находится ли номер страницы в правильном интервале. +Значение постоянного параметра можно изменить при создании ссылки: -В PHP 8 вы также можете использовать атрибуты для маркировки постоянных параметров: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Или его можно *сбросить*, т.е. удалить из URL. Тогда он примет значение по умолчанию: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ interface PollControlFactory 1) он может быть отображен в шаблоне 2) он знает, какую часть себя отображать во время [AJAX-запроса |ajax#invalidation] (сниппеты) -3) он может хранить свое состояние в URL (параметры персистентности) +3) он имеет возможность хранить свое состояние в URL (постоянные параметры) 4) имеет возможность реагировать на действия пользователя (сигналы) 5) создает иерархическую структуру (где корнем является ведущий) @@ -406,6 +409,29 @@ Nette\ComponentModel\Component { IComponent } Валидация постоянных параметров .[#toc-validation-of-persistent-parameters] --------------------------------------------------------------------------- +Значения [постоянных параметров |#persistent parameters], полученных из URL, записываются в свойства методом `loadState()`. Он также проверяет, соответствует ли тип данных, указанный для свойства, в противном случае выдается ошибка 404 и страница не отображается. + +Никогда не доверяйте слепо постоянным параметрам, так как они могут быть легко перезаписаны пользователем в URL. Например, так мы проверяем, больше ли номер страницы `$this->page`, чем 0. Хороший способ сделать это - переопределить метод `loadState()`, упомянутый выше: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // здесь устанавливается $this->page + // после проверки значения пользователем: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Противоположный процесс, то есть сбор значений из постоянных свойств, обрабатывается методом `saveState()`. + Сигналы в глубину .[#toc-signals-in-depth] ------------------------------------------ diff --git a/application/ru/creating-links.texy b/application/ru/creating-links.texy index c7e3fc466b..3a4dbb4805 100644 --- a/application/ru/creating-links.texy +++ b/application/ru/creating-links.texy @@ -52,7 +52,7 @@ Атрибут `n:href` очень удобен для HTML-тегов ``. Если мы хотим вывести ссылку в другом месте, например, в тексте, мы используем `{link}`: ```latte -URL: {link Homepage:default} +URL: {link Home:default} ``` @@ -88,7 +88,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Поэтому основной формой является `Presenter:action`: ```latte -главная страница +главная страница ``` Если мы ссылаемся на действие текущего презентера, мы можем опустить его имя: @@ -100,7 +100,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Если действие является `default`, мы можем опустить его, но двоеточие должно остаться: ```latte -главная страница +главная страница ``` Ссылки могут также указывать на другие [модули |modules]. Здесь ссылки различаются на относительные по отношению к подмодулям или абсолютные. Принцип аналогичен дисковым путям, только вместо косых черт стоят двоеточия. Предположим, что настоящий презентер является частью модуля `Front`, тогда мы напишем: @@ -119,7 +119,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Мы можем ссылаться на определенную часть HTML-страницы через так называемый фрагмент после хэш-символа `#`: ```latte -ссылка на Homepage:default и фрагмент #main +ссылка на Home:default и фрагмент #main ``` @@ -128,7 +128,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Ссылки, генерируемые `link()` или `n:href`, всегда являются абсолютными путями (т.е. начинаются с `/`), но не абсолютными URL с протоколом и доменом, как `https://domain`. -Чтобы создать абсолютный URL, добавьте две косые черты в начало (например, `n:href="//Homepage:"`). Или вы можете переключить презентатор на генерацию только абсолютных ссылок, установив `$this->absoluteUrls = true`. +Чтобы создать абсолютный URL, добавьте две косые черты в начало (например, `n:href="//Home:"`). Или вы можете переключить презентатор на генерацию только абсолютных ссылок, установив `$this->absoluteUrls = true`. Ссылка на текущую страницу .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Если мы хотим сделать ссылку на презентеры в шаблоне компонента, мы используем тег `{plink}`: ```latte -главная страница +главная страница ``` or in the code ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/ru/how-it-works.texy b/application/ru/how-it-works.texy index 6b2a22a899..b5b91aa685 100644 --- a/application/ru/how-it-works.texy +++ b/application/ru/how-it-works.texy @@ -23,10 +23,10 @@ web-project/ ├── app/ ← каталог с приложением │ ├── Presenters/ ← классы презентеров -│ │ ├── HomepagePresenter.php ← Класс презентера главной страницы +│ │ ├── HomePresenter.php ← Класс презентера главной страницы │ │ └── templates/ ← директория шаблонов │ │ ├── @layout.latte ← шаблон общего макета -│ │ └── Homepage/ ← шаблоны презентера главной страницы +│ │ └── Home/ ← шаблоны презентера главной страницы │ │ └── default.latte ← шаблон действия `default` │ ├── Router/ ← конфигурация URL-адресов │ └── Bootstrap.php ← загрузочный класс Bootstrap @@ -134,10 +134,10 @@ class ProductPresenter extends Nette\Application\UI\Presenter 1) URL будет `https://example.com` 2) мы загружаем приложение, создаем контейнер и запускаем `Application::run()` -3) маршрутизатор декодирует URL как пару `Homepage:default` -4) создается объект `HomepagePresenter` +3) маршрутизатор декодирует URL как пару `Home:default` +4) создается объект `HomePresenter` 5) вызывается метод `renderDefault()` (если существует) -6) шаблон `templates/Homepage/default.latte` с макетом `templates/@layout.latte` отрисован +6) шаблон `templates/Home/default.latte` с макетом `templates/@layout.latte` отрисован Возможно, сейчас вы столкнулись с множеством новых понятий, но мы считаем, что они имеют смысл. Создавать приложения в Nette — проще простого. diff --git a/application/ru/modules.texy b/application/ru/modules.texy index 3a970717cf..b8d3e626c2 100644 --- a/application/ru/modules.texy +++ b/application/ru/modules.texy @@ -104,7 +104,7 @@ class DashboardPresenter extends Nette\Application\UI\Presenter Определяет правила, по которым имя класса выводится из имени ведущего. Мы записываем их в [конфигурацию |configuration] под ключом `application › mapping`. -Начнем с примера, в котором не используются модули. Мы просто хотим, чтобы классы ведущего имели пространство имен `App\Presenters`. То есть мы хотим, чтобы ведущий, например, `Homepage` отображался на класс `App\Presenters\HomepagePresenter`. Этого можно достичь с помощью следующей конфигурации: +Начнем с примера, в котором не используются модули. Мы просто хотим, чтобы классы ведущего имели пространство имен `App\Presenters`. То есть мы хотим, чтобы ведущий, например, `Home` отображался на класс `App\Presenters\HomePresenter`. Этого можно достичь с помощью следующей конфигурации: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Теперь презентер `Front:Homepage` определяется классом `App\Modules\Front\HomepagePresenter`, а презентер `Admin:Dashboard` — `App\AdminModule\DashboardPresenter`. +Теперь презентер `Front:Home` определяется классом `App\Modules\Front\HomePresenter`, а презентер `Admin:Dashboard` — `App\AdminModule\DashboardPresenter`. Удобнее будет создать общее правило (звездочка), которое заменит первые два правила и добавит дополнительную звездочку только для модуля: diff --git a/application/ru/presenters.texy b/application/ru/presenters.texy index d07034b3e9..3363ced5d1 100644 --- a/application/ru/presenters.texy +++ b/application/ru/presenters.texy @@ -158,7 +158,7 @@ $url = $this->link('Product:show', [$id, 'lang' => 'en']); $this->forward('Product:show'); ``` -Пример временного перенаправления с HTTP-кодом 302 или 303: +Пример так называемого временного перенаправления с HTTP-кодом 302 (или 303, если текущий метод запроса - POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ $this->redirect('Product:show', $id); $this->redirectPermanent('Product:show', $id); ``` -Вы можете перенаправить на другой URL вне приложения с помощью метода `redirectUrl()`: +Вы можете перенаправить на другой URL вне приложения, используя метод `redirectUrl()`. HTTP-код может быть указан в качестве второго параметра, по умолчанию это 302 (или 303, если текущий метод запроса - POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Постоянные параметры .[#toc-persistent-parameters] ================================================== -Постоянные параметры **передаются автоматически** в ссылках. Это означает, что нам не нужно явно указывать их в каждом `link()` или `n:href` в шаблоне, но они все равно будут переданы. +Постоянные параметры используются для сохранения состояния между различными запросами. Их значение остается неизменным даже после нажатия на ссылку. В отличие от данных сессии, они передаются в URL. Это происходит полностью автоматически, поэтому нет необходимости явно указывать их в `link()` или `n:href`. -Если ваше приложение имеет несколько языковых версий, то текущий язык является параметром, который всегда должен быть частью URL. И было бы невероятно утомительно упоминать об этом в каждой ссылке. С Nette в этом нет необходимости. Таким образом мы просто помечаем параметр `lang` как постоянный: +Пример использования? У вас есть многоязычное приложение. Фактический язык - это параметр, который всегда должен быть частью URL. Но было бы невероятно утомительно указывать его в каждой ссылке. Поэтому вы сделаете его постоянным параметром с именем `lang`, и он сам себя перенесет. Круто! + +Создать постоянный параметр в Nette очень просто. Просто создайте публичное свойство и пометьте его атрибутом: (ранее использовалось `/** @persistent */` ). ```php +use Nette\Application\Attributes\Persistent; // эта строка важна + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // должны быть публичными } ``` -Если текущее значение параметра `lang` равно `'en'`, то URL, созданный с помощью `link()` или `n:href` в шаблоне, будет содержать `lang=en`. Отлично! - -Однако мы также можем добавить параметр `lang` и тем самым изменить его значение: +Если `$this->lang` имеет значение, например, `'en'`, то ссылки, созданные с помощью `link()` или `n:href`, также будут содержать параметр `lang=en`. И когда ссылка будет щелкнута, она снова станет `$this->lang = 'en'`. -```latte -подробности на английском -``` - -Или, наоборот, его можно удалить, установив в null: - -```latte -нажмите здесь -``` +Для свойств рекомендуется указывать тип данных (например, `string`), а также можно указать значение по умолчанию. Значения параметров могут быть [проверены |#Validation of Persistent Parameters]. -Постоянная переменная должна быть объявлена как public. Мы также можем указать значение по умолчанию. Если параметр имеет то же значение, что и значение по умолчанию, он не будет включен в URL. +Постоянные параметры по умолчанию передаются между всеми действиями данного ведущего. Чтобы передать их между несколькими ведущими, необходимо определить их либо: -Постоянство отражает иерархию классов презентеров, поэтому параметр, определенный в конкретном презентере или трейте, автоматически передается каждому презентеру, наследующему от него или использующему тот же самый трейт. - -В PHP 8 вы также можете использовать атрибуты для маркировки постоянных параметров: +- в общем предке, от которого наследуют ведущие +- в трейте, который используют ведущие: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Вы можете изменить значение постоянного параметра при создании ссылки: + +```latte +detail in Czech +``` + +Или его можно *сбросить*, т.е. удалить из URL. Тогда он примет значение по умолчанию: + +```latte +click ``` @@ -302,7 +310,32 @@ class ProductPresenter extends Nette\Application\UI\Presenter Требования и параметры .[#toc-requirement-and-parameters] --------------------------------------------------------- -Запрос, обрабатываемый презентером, является объектом [api:Nette\Application\Request] и возвращается методом презентера `getRequest()`. Он включает в себя массив параметров, каждый из которых принадлежит либо какому-то из компонентов, либо непосредственно презентеру (который на самом деле тоже является компонентом, хотя и специальным). Таким образом, Nette перераспределяет параметры и передается между отдельными компонентами (и презентером) путем вызова метода `loadState(array $params)`, который более подробно описан в главе [Компоненты |components]. Параметры можно получить с помощью метода `getParameters(): array`, индивидуально используя `getParameter($name)`. Значения параметров — это строки или массивы строк, в основном это необработанные данные, полученные непосредственно из URL. +Запрос, обрабатываемый ведущим, представляет собой объект [api:Nette\Application\Request] и возвращается методом ведущего `getRequest()`. Он включает в себя массив параметров, и каждый из них принадлежит либо какому-то из компонентов, либо непосредственно ведущему (который на самом деле тоже является компонентом, хотя и специальным). Поэтому Nette перераспределяет параметры и передает их между отдельными компонентами (и ведущим), вызывая метод `loadState(array $params)`. Параметры могут быть получены методом `getParameters(): array`, индивидуально с помощью `getParameter($name)`. Значения параметров представляют собой строки или массивы строк, в основном это необработанные данные, полученные непосредственно из URL. + + +Валидация постоянных параметров .[#toc-validation-of-persistent-parameters] +--------------------------------------------------------------------------- + +Значения [постоянных параметров |#persistent parameters], полученных из URL, записываются в свойства методом `loadState()`. Он также проверяет, соответствует ли тип данных, указанный в свойстве, в противном случае выдается ошибка 404 и страница не отображается. + +Никогда не доверяйте слепо постоянным параметрам, так как они могут быть легко перезаписаны пользователем в URL. Например, так мы проверяем, входит ли `$this->lang` в число поддерживаемых языков. Хороший способ сделать это - переопределить метод `loadState()`, упомянутый выше: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // здесь устанавливается $this->lang + // после проверки значения пользователя: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Сохранение и восстановление запроса .[#toc-save-and-restore-the-request] diff --git a/application/ru/routing.texy b/application/ru/routing.texy index 3e41696e64..5bff8b8035 100644 --- a/application/ru/routing.texy +++ b/application/ru/routing.texy @@ -93,12 +93,12 @@ $router->addRoute('chronicle/', 'History:show'); Конечно, имя презентера и действие также могут быть параметрами. Например: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Этот маршрут принимает, например, URL в форме `/article/edit` и `/catalog/list` соответственно, и переводит их в презентеры и действия `Article:edit` и `Catalog:list` соответственно. -Он также придает параметрам `presenter` и `action` значения по умолчанию `Homepage` и `default` и поэтому они являются необязательными. Поэтому маршрут также принимает URL `/article` и переводит его как `Article:default`. Или наоборот, ссылка на `Product:default` генерирует путь `/product`, ссылка на стандартную `Homepage:default` генерирует путь `/`. +Он также придает параметрам `presenter` и `action` значения по умолчанию `Home` и `default` и поэтому они являются необязательными. Поэтому маршрут также принимает URL `/article` и переводит его как `Article:default`. Или наоборот, ссылка на `Product:default` генерирует путь `/product`, ссылка на стандартную `Home:default` генерирует путь `/`. Маска может описывать не только относительный путь, основанный на корне сайта, но и абсолютный путь, если он начинается со слэша, или даже весь абсолютный URL, если он начинается с двух слэшей: @@ -160,7 +160,7 @@ $router->addRoute('//[.]example.com//', /* ... */); ```php $router->addRoute( '[[-]/][page-]', - 'Homepage:default', + 'Home:default', ); // Принимаемые URL-адреса: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Необязательные параметры (т. е. параметры, имеющие значение по умолчанию) без квадратных скобок ведут себя так, как если бы они были обернуты таким образом: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // equals to: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Чтобы изменить способ генерации самой правой косой черты, т. е. вместо `/homepage/` получить `/homepage`, настройте маршрут таким образом: +Чтобы изменить способ генерации самой правой косой черты, т. е. вместо `/home/` получить `/home`, настройте маршрут таким образом: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ $router->addRoute('/[/]', [ Хорошей практикой является написание исходного кода на английском языке, но что если вам нужно, чтобы URL вашего сайта был переведен на другой язык? ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` будет генерировать английские URL, такие как `/product/123` или `/cart`. Если мы хотим, чтобы презентеры и действия в URL были переведены на немецкий язык (например, `/produkt/123` или `/einkaufswagen`), мы можем использовать словарь переводов. Чтобы добавить его, нам уже нужен «более понятный» вариант второго параметра: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // строка в URL => ведущий 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ $router->addRoute('//', [ use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Параметром конструктора `SimpleRouter` является презентер и действие по умолчанию, т. е. действие, которое будет выполнено, если мы откроем, например, `http://example.com/` без дополнительных параметров. ```php -// используем презентер 'Homepage' и действие 'default' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// используем презентер 'Home' и действие 'default' +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Мы рекомендуем определять SimpleRouter непосредственно в [конфигурации |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ class MyRouter implements Nette\Routing\Router ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/ru/templates.texy b/application/ru/templates.texy index 046b77987c..75f2c784d0 100644 --- a/application/ru/templates.texy +++ b/application/ru/templates.texy @@ -42,7 +42,9 @@ Nette использует систему шаблонов [Latte |latte:]. Latt - `templates//.latte` - `templates/..latte` -Если шаблон не найден, ответом будет [ошибка 404 |presenters#error-404-etc]. +Если шаблон не найден, он попытается выполнить поиск в каталоге `templates` на один уровень выше, т.е. на том же уровне, что и каталог с классом ведущего. + +Если шаблон не найден и там, ответом будет [ошибка 404 |presenters#Error 404 etc.]. Вы также можете изменить вид с помощью `$this->setView('jineView')`. Или, вместо прямого поиска, укажите имя файла шаблона с помощью `$this->template->setFile('/path/to/template.latte')`. @@ -148,7 +150,7 @@ public function renderDefault(): void Атрибут `n:href` очень удобен для HTML-тегов. ``. Если мы хотим указать ссылку в другом месте, например, в тексте, мы используем `{link}`: ```latte -Adresa je: {link Homepage:default} +Adresa je: {link Home:default} ``` Дополнительные сведения см. в разделе [Создание ссылок URL |creating-links]. diff --git a/application/sl/@left-menu.texy b/application/sl/@left-menu.texy index de4606eaba..4e23237948 100644 --- a/application/sl/@left-menu.texy +++ b/application/sl/@left-menu.texy @@ -15,5 +15,8 @@ Nette aplikacija Nadaljnje branje **************** +- [Zakaj uporabljati Nette |www:10-reasons-why-nette]? +- [Namestitev |nette:installation] +- [Ustvarite svojo prvo aplikacijo |quickstart:]! - [Najboljše prakse |best-practices:] - [Odpravljanje težav |nette:troubleshooting] diff --git a/application/sl/ajax.texy b/application/sl/ajax.texy index da623b80c7..1d982a2057 100644 --- a/application/sl/ajax.texy +++ b/application/sl/ajax.texy @@ -10,9 +10,13 @@ Sodobne spletne aplikacije danes tečejo pol na strežniku in pol v brskalniku. -Zahtevo AJAX je mogoče zaznati z metodo storitve, ki [enkapsulira zahtevo HTTP |http:request] `$httpRequest->isAjax()` (zaznava na podlagi glave `X-Requested-With` HTTP). Obstaja tudi skrajšana metoda v programu presenter: `$this->isAjax()`. -Zahteva AJAX se v ničemer ne razlikuje od običajne zahteve - presenter se pokliče z določenim pogledom in parametri. Tudi od predstavnika je odvisno, kako se bo odzval: s svojimi rutinami lahko vrne fragment kode HTML (snippet), dokument XML, objekt JSON ali del kode Javascript. +Zahteva AJAX .[#toc-ajax-request] +================================= + +Zahteva AJAX se ne razlikuje od klasične zahteve - predvajalnik se pokliče z določenim pogledom in parametri. Prav tako je od predstavnika odvisno, kako se bo nanjo odzval: uporabi lahko svojo rutino, ki vrne fragment kode HTML (fragment HTML), dokument XML, objekt JSON ali kodo JavaScript. + +Na strani strežnika je mogoče zahtevo AJAX zaznati s storitveno metodo, ki [enkapsulira zahtevo HTTP |http:request] `$httpRequest->isAjax()` (zazna na podlagi glave HTTP `X-Requested-With`). Znotraj predstavnika je na voljo bližnjica v obliki metode `$this->isAjax()`. Na voljo je vnaprej obdelan objekt, imenovan `payload`, namenjen pošiljanju podatkov brskalniku v obliki JSON. @@ -60,6 +64,21 @@ npm install naja ``` +Če želite ustvariti zahtevo AJAX iz običajne povezave (signala) ali oddaje obrazca, preprosto označite ustrezno povezavo, obrazec ali gumb z razredom `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + . .[#toc-snippets] ================== @@ -149,7 +168,7 @@ Dinamičnega utrinka ne morete narisati neposredno (ponovno narisanje `item-1` n V zgornjem primeru morate poskrbeti, da bo za zahtevo AJAX v polje `$list` dodan samo en element, zato bo zanka `foreach` izpisala samo en dinamični odlomek. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/sl/bootstrap.texy b/application/sl/bootstrap.texy index ba8eb0b587..bc60e3cfe8 100644 --- a/application/sl/bootstrap.texy +++ b/application/sl/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -V konfiguracijskih datotekah lahko zapišemo običajni zapis `%projectId%` za dostop do parametra z imenom `projectId`. Privzeto konfigurator izpolni naslednje parametre: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` in `consoleMode`. +V konfiguracijskih datotekah lahko zapišemo običajni zapis `%projectId%` za dostop do parametra z imenom `projectId`. Dinamični parametri .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Privzete parametre .[#toc-default-parameters] +--------------------------------------------- + +V konfiguracijskih datotekah lahko uporabite naslednje statične parametre: + +- `%appDir%` je absolutna pot do imenika datoteke `Bootstrap.php`. +- `%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 +- `%debugMode%` označuje, ali je aplikacija v načinu odpravljanja napak +- `%consoleMode%` označuje, ali je bila zahteva poslana prek ukazne vrstice + + Uvožene storitve .[#toc-imported-services] ------------------------------------------ diff --git a/application/sl/components.texy b/application/sl/components.texy index 1e7612766a..91bb598e94 100644 --- a/application/sl/components.texy +++ b/application/sl/components.texy @@ -233,31 +233,34 @@ V predlogi so ta sporočila na voljo v spremenljivki `$flashes` kot objekti `std Trajni parametri .[#toc-persistent-parameters] ============================================== -Pogosto je treba v komponenti ves čas dela s komponento hraniti nek parameter. To je lahko na primer številka strani pri paginaciji. Ta parameter je treba označiti kot stalen z uporabo opombe `@persistent`. +Trajni parametri se uporabljajo za ohranjanje stanja v komponentah med različnimi zahtevami. Njihova vrednost ostane enaka tudi po kliku na povezavo. Za razliko od podatkov seje se prenesejo v naslovu URL. In se prenesejo samodejno, vključno s povezavami, ustvarjenimi v drugih komponentah na isti strani. + +Na primer, imate komponento za listanje vsebine. Na strani je lahko več takšnih komponent. Želite, da vse komponente ostanejo na trenutni strani, ko kliknete povezavo. Zato je številka strani (`page`) trajni parameter. + +Ustvarjanje trajnega parametra je v programu Nette zelo enostavno. Ustvarite le javno lastnost in jo označite z atributom: (prej je bila uporabljena `/** @persistent */` ) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // ta vrstica je pomembna + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // morajo biti javni. } ``` -Ta parameter bo samodejno posredovan v vsaki povezavi kot parameter `GET`, dokler uporabnik ne zapusti strani s to komponento. +Priporočamo, da pri lastnosti navedete podatkovno vrsto (npr. `int`), vključite pa lahko tudi privzeto vrednost. Vrednosti parametrov je mogoče [potrditi |#Validation of Persistent Parameters]. -.[caution] -Trajnim parametrom nikoli ne zaupajte slepo, saj jih je mogoče zlahka ponarediti (s prepisovanjem URL-ja). Preverite na primer, ali je številka strani v pravilnem intervalu. +Pri ustvarjanju povezave lahko spremenite vrednost trajnega parametra: -V PHP 8 lahko za označevanje trajnih parametrov uporabite tudi atribute: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Lahko ga tudi *resetirate*, tj. odstranite iz URL-ja. Nato bo prevzel privzeto vrednost: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ Komponente v aplikaciji Nette so ponovno uporabni deli spletne aplikacije, ki ji 1) mogoče jo je izrisati v predlogi 2) ve, kateri del sebe naj prikaže med [zahtevo AJAX |ajax#invalidation] (odlomki) -3) ima možnost shranjevanja svojega stanja v naslovu URL (parametri persistence) +3) ima možnost shranjevanja svojega stanja v naslovu URL (trajni parametri). 4) ima sposobnost odzivanja na dejanja uporabnika (signali) 5) ustvarja hierarhično strukturo (kjer je korenski element predstavnik) @@ -403,6 +406,33 @@ Nette\ComponentModel\Component { IComponent } [* lifecycle-component.svg *] *** *Življenjski cikel komponente* .<> +Potrjevanje trajnih parametrov .[#toc-validation-of-persistent-parameters] +-------------------------------------------------------------------------- + +Vrednosti [trajnih parametrov |#persistent parameters], prejetih z naslovov URL, se zapišejo v lastnosti z metodo `loadState()`. Preveri tudi, ali se podatkovna vrsta, določena za lastnost, ujema, sicer se odzove z napako 404 in stran se ne prikaže. + +Nikoli ne zaupajte slepo trajnim parametrom, saj jih lahko uporabnik zlahka prepiše v naslovu URL. Tako na primer preverimo, ali je številka strani `$this->page` večja od 0. Dober način za to je, da prekrijemo zgoraj omenjeno metodo `loadState()`: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // tukaj je nastavljen $this->page + // sledi preverjanju uporabniške vrednosti: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Nasprotni postopek, to je zbiranje vrednosti iz trajnih lastnosti, je obdelan z metodo `saveState()`. + + Signali v globino .[#toc-signals-in-depth] ------------------------------------------ diff --git a/application/sl/creating-links.texy b/application/sl/creating-links.texy index 5db6afbcb0..4943feeb98 100644 --- a/application/sl/creating-links.texy +++ b/application/sl/creating-links.texy @@ -52,7 +52,7 @@ V povezavah se samodejno posredujejo tudi tako imenovani [trajni parametri |pres Atribut `n:href` je zelo priročen za oznake HTML ``. Če želimo povezavo izpisati drugje, na primer v besedilu, uporabimo `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ To obliko podpirajo vse oznake Latte in vse metode presenterja, ki delajo s pove Osnovna oblika je torej `Presenter:action`: ```latte -homepage +home ``` Če se povežemo z dejanjem trenutnega predstavnika, lahko izpustimo njegovo ime: ```latte -homepage +home ``` Če je dejanje `default`, ga lahko izpustimo, vendar mora dvopičje ostati: ```latte -homepage +home ``` Povezave lahko kažejo tudi na druge [module |modules]. Tu se povezave razlikujejo na relativne na podmodule ali absolutne. Načelo je podobno kot pri diskovnih poteh, le da so namesto poševnic dvopičja. Predpostavimo, da je dejanski predstavnik del modula `Front`, potem bomo zapisali: @@ -119,7 +119,7 @@ Poseben primer je [povezovanje na samega sebe |#Links to Current Page]. V tem pr Na določen del strani HTML se lahko povežemo s tako imenovanim fragmentom za simbolom `#` hash: ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ Absolutne poti .[#toc-absolute-paths] Povezave, ki jih generirata `link()` ali `n:href`, so vedno absolutne poti (tj. začnejo se z `/`), ne pa tudi absolutni naslovi URL s protokolom in domeno, kot `https://domain`. -Če želite ustvariti absolutni naslov URL, na začetek dodajte dve poševnici (npr. `n:href="//Homepage:"`). Lahko pa tudi preklopite predstavnik, da ustvarja samo absolutne povezave, tako da nastavite `$this->absoluteUrls = true`. +Če želite ustvariti absolutni naslov URL, na začetek dodajte dve poševnici (npr. `n:href="//Home:"`). Lahko pa tudi preklopite predstavnik, da ustvarja samo absolutne povezave, tako da nastavite `$this->absoluteUrls = true`. Povezava na trenutno stran .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ Ker so [komponente |components] ločene enote za večkratno uporabo, ki naj ne b Če se želimo v predlogi komponente povezati s predstavniki, uporabimo oznako `{plink}`: ```latte -homepage +home ``` ali v kodi ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/sl/how-it-works.texy b/application/sl/how-it-works.texy index 75913cfb5f..ed6745881b 100644 --- a/application/sl/how-it-works.texy +++ b/application/sl/how-it-works.texy @@ -23,10 +23,10 @@ Struktura imenikov je videti nekako takole: web-project/ ├── app/ ← directory with application │ ├── Presenters/ ← presenter classes -│ │ ├── HomepagePresenter.php ← Homepage presenter class +│ │ ├── HomePresenter.php ← Home presenter class │ │ └── templates/ ← templates directory │ │ ├── @layout.latte ← template of shared layout -│ │ └── Homepage/ ← templates for Homepage presenter +│ │ └── Home/ ← templates for Home presenter │ │ └── default.latte ← template for action `default` │ ├── Router/ ← configuration of URL addresses │ └── Bootstrap.php ← booting class Bootstrap @@ -134,10 +134,10 @@ Da bi se prepričali, poskusite celoten postopek ponoviti z nekoliko drugačnim 1) naslov URL bo `https://example.com` 2) zaženemo aplikacijo, ustvarimo vsebnik in zaženemo `Application::run()` -3) usmerjevalnik dekodira naslov URL kot par `Homepage:default` -4) ustvari se objekt `HomepagePresenter` +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/Homepage/default.latte` z razporeditvijo `templates/@layout.latte` +6) prikaže se predloga `templates/Home/default.latte` z razporeditvijo `templates/@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 39f7ce0d31..d37ea342b1 100644 --- a/application/sl/modules.texy +++ b/application/sl/modules.texy @@ -104,7 +104,7 @@ 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`. -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 `Homepage`, preslikan v razred `App\Presenters\HomepagePresenter`. To lahko dosežemo z naslednjo konfiguracijo: +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: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Sedaj je predstavnik `Front:Homepage` preslikan v razred `App\Modules\Front\Presenters\HomepagePresenter`, predstavnik `Admin:Dashboard` pa v razred `App\Modules\Admin\Presenters\DashboardPresenter`. +Sedaj je predstavnik `Front:Home` preslikan v razred `App\Modules\Front\Presenters\HomePresenter`, predstavnik `Admin:Dashboard` pa v razred `App\Modules\Admin\Presenters\DashboardPresenter`. Bolj praktično je ustvariti splošno (zvezdno) pravilo, ki bo nadomestilo prvi dve. Dodatna zvezdica bo dodana maski razreda samo za ta modul: diff --git a/application/sl/presenters.texy b/application/sl/presenters.texy index 2b4472874a..1745ff06db 100644 --- a/application/sl/presenters.texy +++ b/application/sl/presenters.texy @@ -158,7 +158,7 @@ Metoda `forward()` takoj preklopi na novi predstavnik brez preusmeritve HTTP: $this->forward('Product:show'); ``` -Primer začasne preusmeritve s kodo HTTP 302 ali 303: +Primer tako imenovane začasne preusmeritve s kodo HTTP 302 (ali 303, če je trenutni način zahteve POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ Za trajno preusmeritev s kodo HTTP 301 uporabite: $this->redirectPermanent('Product:show', $id); ``` -Z metodo `redirectUrl()` lahko preusmerite na drug URL zunaj aplikacije: +Z metodo `redirectUrl()` lahko preusmerite na drug naslov URL zunaj aplikacije. Kodo HTTP lahko določite kot drugi parameter, pri čemer je privzeta vrednost 302 (ali 303, če je trenutna metoda zahteve POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Trajni parametri .[#toc-persistent-parameters] ============================================== -Trajni parametri se v povezavah **prenesejo samodejno**. To pomeni, da nam jih ni treba izrecno navesti v vsakem `link()` ali `n:href` v predlogi, vendar se bodo vseeno prenesli. +Trajni parametri se uporabljajo za ohranjanje stanja med različnimi zahtevami. Njihova vrednost ostane enaka tudi po kliku na povezavo. Za razliko od podatkov seje se posredujejo v naslovu URL. To poteka povsem samodejno, zato jih ni treba izrecno navesti v `link()` ali `n:href`. -Če ima vaša aplikacija več jezikovnih različic, je trenutni jezik parameter, ki mora biti vedno del naslova URL. In bilo bi neverjetno naporno, če bi ga morali navajati v vsaki povezavi. Z Nette to ni potrebno. Parameter `lang` na ta način preprosto označimo kot stalen: +Primer uporabe? Imate večjezično aplikacijo. Dejanski jezik je parameter, ki mora biti vedno del naslova URL. Vendar bi bilo izjemno zamudno, če bi ga vključili v vsako povezavo. Zato ga naredite za trajni parameter z imenom `lang`, ki se bo prenašal sam. Super! + +Ustvarjanje trajnega parametra je v Nette izjemno enostavno. Preprosto ustvarite javno lastnost in jo označite z atributom: (prej se je uporabljal `/** @persistent */` ). ```php +use Nette\Application\Attributes\Persistent; // ta vrstica je pomembna + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // morajo biti javni. } ``` -Če je trenutna vrednost parametra `lang` `'en'` , potem bo URL, ustvarjen s `link()` ali `n:href` v predlogi, vseboval `lang=en`. Odlično! - -Vendar pa lahko dodamo tudi parameter `lang` in s tem spremenimo njegovo vrednost: +Če ima `$this->lang` vrednost, kot je `'en'`, bodo povezave, ustvarjene z uporabo `link()` ali `n:href`, vsebovale tudi parameter `lang=en`. In ko boste povezavo kliknili, bo ta spet imela vrednost `$this->lang = 'en'`. -```latte -detail in English -``` - -Lahko pa ga tudi odstranimo z nastavitvijo vrednosti null: - -```latte -click here -``` +Za lastnosti priporočamo, da vključite podatkovno vrsto (npr. `string`), vključite pa lahko tudi privzeto vrednost. Vrednosti parametrov je mogoče [potrditi |#Validation of Persistent Parameters]. -Trajna spremenljivka mora biti deklarirana kot javna. Določimo lahko tudi privzeto vrednost. Če ima parameter enako vrednost kot privzeta vrednost, ne bo vključen v naslov URL. +Trajni parametri se privzeto posredujejo med vsemi dejanji določenega predstavnika. Če jih želite posredovati med več predstavniki, jih morate opredeliti: -Vztrajnost odraža hierarhijo razredov predstavnikov, zato se parameter, opredeljen v določenem predstavniku ali lastnosti, nato samodejno prenese na vsak predstavnik, ki ga podeduje ali uporablja isto lastnost. - -V PHP 8 lahko za označevanje trajnih parametrov uporabite tudi atribute: +- v skupnem predniku, od katerega predstavniki dedujejo +- v lastnosti, ki jo uporabljajo predstavniki: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Pri ustvarjanju povezave lahko spremenite vrednost trajnega parametra: + +```latte +detail in Czech +``` + +Lahko ga tudi *resetirate*, tj. odstranite iz URL-ja. Nato bo prevzel privzeto vrednost: + +```latte +click ``` @@ -302,7 +310,32 @@ To, kar smo doslej prikazali v tem poglavju, bo verjetno zadostovalo. Naslednje Zahteve in parametri .[#toc-requirement-and-parameters] ------------------------------------------------------- -Zahteva, ki jo obravnava predstavnik, je objekt [api:Nette\Application\Request] in jo vrne predstavnikova metoda `getRequest()`. Vključuje niz parametrov, vsak od njih pa pripada bodisi kateri od komponent bodisi neposredno predstavniku (ki je pravzaprav tudi komponenta, čeprav posebna). Nette torej prerazporedi parametre in prehaja med posameznimi komponentami (in predstavnikom) tako, da pokliče metodo `loadState(array $params)`, ki je podrobneje opisana v poglavju [Komponente |Components]. Parametre lahko pridobimo z metodo `getParameters(): array`, posamično pa z uporabo `getParameter($name)`. Vrednosti parametrov so nizi ali polja nizov, v bistvu gre za surove podatke, pridobljene neposredno iz naslova URL. +Zahteva, ki jo obravnava predstavnik, je objekt [api:Nette\Application\Request] in jo vrne predstavnikova metoda `getRequest()`. Vključuje polje parametrov, vsak od njih pa pripada bodisi kateri od komponent bodisi neposredno predstavniku (ki je pravzaprav tudi komponenta, čeprav posebna). Nette torej parametre prerazporedi in posreduje med posameznimi komponentami (in predstavnikom) tako, da pokliče metodo `loadState(array $params)`. Parametre lahko pridobimo z metodo `getParameters(): array`, posamično pa z metodo `getParameter($name)`. Vrednosti parametrov so nizi ali polja nizov, v bistvu so surovi podatki, pridobljeni neposredno iz naslova URL. + + +Potrjevanje trajnih parametrov .[#toc-validation-of-persistent-parameters] +-------------------------------------------------------------------------- + +Vrednosti [trajnih parametrov |#persistent parameters], prejetih z naslovov URL, se zapišejo v lastnosti z metodo `loadState()`. Preveri tudi, ali se podatkovna vrsta, navedena v lastnosti, ujema, sicer se odzove z napako 404 in stran se ne prikaže. + +Nikoli ne zaupajte slepo trajnim parametrom, saj jih lahko uporabnik zlahka prepiše v naslovu URL. Tako na primer preverimo, ali je `$this->lang` med podprtimi jeziki. Dober način za to je, da prekrijete zgoraj omenjeno metodo `loadState()`: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // tukaj je nastavljen $this->lang + // sledi preverjanju uporabniške vrednosti: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Shranjevanje in obnavljanje zahtevka .[#toc-save-and-restore-the-request] diff --git a/application/sl/routing.texy b/application/sl/routing.texy index ec238884e4..046c205488 100644 --- a/application/sl/routing.texy +++ b/application/sl/routing.texy @@ -93,12 +93,12 @@ Pot bo zdaj sprejela naslov URL `https://any-domain.com/chronicle/` s parametrom Seveda sta lahko parametra tudi ime predstavnika in akcija. Na primer: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Ta pot sprejme na primer naslov URL v obliki `/article/edit` oz. `/catalog/list` in ga prevede v predstavnike in dejanja `Article:edit` oz. `Catalog:list`. -Parametroma `presenter` in `action` daje tudi privzete vrednosti`Homepage` in `default`, zato sta neobvezna. Tako pot sprejme tudi naslov URL `/article` in ga prevede kot `Article:default`. Ali obratno, povezava na `Product:default` ustvari pot `/product`, povezava na privzeto `Homepage:default` pa ustvari pot `/`. +Parametroma `presenter` in `action` daje tudi privzete vrednosti`Home` in `default`, zato sta neobvezna. Tako pot sprejme tudi naslov URL `/article` in ga prevede kot `Article:default`. Ali obratno, povezava na `Product:default` ustvari pot `/product`, povezava na privzeto `Home:default` pa ustvari pot `/`. Maska lahko opiše ne le relativno pot na podlagi korena spletnega mesta, temveč tudi absolutno pot, kadar se začne s poševnico, ali celo celoten absolutni naslov URL, kadar se začne z dvema poševnicama: @@ -160,7 +160,7 @@ Sekvence se lahko poljubno gnezdijo in kombinirajo: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // Sprejeti naslovi URL: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Izbirni parametri (tj. parametri s privzeto vrednostjo) brez oglatih oklepajev se obnašajo, kot da bi bili zaviti na ta način: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // je enako: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Če želite spremeniti način generiranja skrajne desne poševnice, tj. namesto `/homepage/` dobite `/homepage`, prilagodite pot na ta način: +Če želite spremeniti način generiranja skrajne desne poševnice, tj. namesto `/home/` dobite `/home`, prilagodite pot na ta način: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ Drugi parameter poti, ki ga pogosto zapišemo v obliki `Presenter:action`, je ok ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filtri in prevodi .[#toc-filters-and-translations] Dobra praksa je, da izvorno kodo pišete v angleščini, a kaj, če potrebujete, da je URL vašega spletnega mesta preveden v drug jezik? Preproste poti, kot so npr: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` bodo ustvarile angleške naslove URL, kot sta `/product/123` ali `/cart`. Če želimo, da so predstavniki in dejanja v naslovu URL prevedeni v nemščino (npr. `/produkt/123` ali `/einkaufswagen`), lahko uporabimo prevajalski slovar. Za njegovo dodajanje že potrebujemo "bolj zgovorno" različico drugega parametra: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // niz v naslovu URL => presenter 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Poleg filtrov za določene parametre lahko določite tudi splošne filtre, ki pr use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Parameter konstruktorja `SimpleRouter` je privzeti predstavnik in dejanje, tj. dejanje, ki se izvede, če odpremo npr. `http://example.com/` brez dodatnih parametrov. ```php -// privzeto za predstavitelja 'Homepage' in akcijo 'default' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// privzeto za predstavitelja 'Home' in akcijo 'default' +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Priporočamo, da se SimpleRouter opredeli neposredno v [konfiguraciji |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ Pri obdelavi zahteve moramo vrniti vsaj predstavnika in akcijo. Ime predstavnika ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/sl/templates.texy b/application/sl/templates.texy index aacc758b1c..66005783a9 100644 --- a/application/sl/templates.texy +++ b/application/sl/templates.texy @@ -42,7 +42,9 @@ Pot do predlog se določi po preprosti logiki. Poskusi preveriti, ali obstaja en - `templates//.latte` - `templates/..latte` -Če predloge ne najde, je odgovor [napaka 404 |presenters#Error 404 etc.]. +Če predloge ne najde, jo poskuša poiskati v imeniku `templates` eno raven višje, tj. na isti ravni kot imenik z razredom predstavnika. + +Če predloge ne najde niti tam, se kot odgovor prikaže [napaka 404 |presenters#Error 404 etc.]. 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')`. @@ -148,7 +150,7 @@ V predlogi ustvarimo povezave do drugih predstavnikov in akcij na naslednji nač Atribut `n:href` je zelo priročen za oznake HTML ``. Če želimo povezavo natisniti drugje, na primer v besedilu, uporabimo `{link}`: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` Za več informacij glejte [Ustvarjanje povezav |Creating Links]. diff --git a/application/tr/@left-menu.texy b/application/tr/@left-menu.texy index 96ce183d38..b64011d843 100644 --- a/application/tr/@left-menu.texy +++ b/application/tr/@left-menu.texy @@ -15,5 +15,8 @@ Nette Uygulama Daha Fazla Okuma **************** +- [Neden Nette Kullanılmalı? |www:10-reasons-why-nette] +- [Kurulum |nette:installation] +- [İlk Başvurunuzu Oluşturun! |quickstart:] - [En iyi uygulamalar |best-practices:] - [Sorun Giderme |nette:troubleshooting] diff --git a/application/tr/ajax.texy b/application/tr/ajax.texy index 9a1ff115fc..2502de3b69 100644 --- a/application/tr/ajax.texy +++ b/application/tr/ajax.texy @@ -10,9 +10,13 @@ Günümüzde modern web uygulamalarının yarısı sunucuda, yarısı da tarayı -Bir AJAX isteği, `$httpRequest->isAjax()` [HTTP isteğini kapsülleyen |http:request] bir hizmet yöntemi kullanılarak algılanabilir ( `X-Requested-With` HTTP başlığına göre algılar). Ayrıca presenter'da steno bir yöntem de vardır: `$this->isAjax()`. -Bir AJAX isteği normal bir istekten farklı değildir - bir sunucu belirli bir görünüm ve parametrelerle çağrılır. Nasıl tepki vereceği de sunucuya bağlıdır: rutinlerini bir HTML kodu parçası (snippet), bir XML belgesi, bir JSON nesnesi veya bir Javascript kodu parçası döndürmek için kullanabilir. +AJAX İsteği .[#toc-ajax-request] +================================ + +Bir AJAX isteği klasik bir istekten farklı değildir - sunum yapan kişi belirli bir görünüm ve parametrelerle çağrılır. Buna nasıl yanıt verileceği de sunucuya bağlıdır: bir HTML kod parçası (HTML snippet), bir XML belgesi, bir JSON nesnesi veya JavaScript kodu döndüren kendi rutinini kullanabilir. + +Sunucu tarafında, bir AJAX isteği `$httpRequest->isAjax()` [HTTP isteğini kapsülleyen |http:request] hizmet yöntemi kullanılarak algılanabilir ( `X-Requested-With` HTTP başlığına dayalı olarak algılar). Sunucunun içinde, `$this->isAjax()` yöntemi şeklinde bir kısayol mevcuttur. JSON'da tarayıcıya veri göndermeye adanmış `payload` adında önceden işlenmiş bir nesne vardır. @@ -60,6 +64,21 @@ npm install naja ``` +Normal bir bağlantıdan (sinyal) veya form gönderiminden bir AJAX isteği oluşturmak için ilgili bağlantıyı, formu veya düğmeyi `ajax` sınıfıyla işaretlemeniz yeterlidir: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Parçacıklar .[#toc-snippets] ============================ @@ -149,7 +168,7 @@ Dinamik bir snippet'i doğrudan yeniden çizemezsiniz ( `item-1` adresinin yenid Yukarıdaki örnekte, bir AJAX isteği için `$list` dizisine yalnızca bir öğe ekleneceğinden emin olmanız gerekir, bu nedenle `foreach` döngüsü yalnızca bir dinamik parçacık yazdıracaktır. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * This method returns data for the list. diff --git a/application/tr/bootstrap.texy b/application/tr/bootstrap.texy index c2829b9f89..403f4ee3f7 100644 --- a/application/tr/bootstrap.texy +++ b/application/tr/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -Yapılandırma dosyalarında, `projectId` adlı parametreye erişmek için `%projectId%` normal gösterimini yazabiliriz. Yapılandırıcı varsayılan olarak aşağıdaki parametreleri doldurur: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` ve `consoleMode`. +Yapılandırma dosyalarında, `projectId` adlı parametreye erişmek için `%projectId%` normal notasyonunu yazabiliriz. Dinamik Parametreler .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Varsayılan Parametreler .[#toc-default-parameters] +-------------------------------------------------- + +Yapılandırma dosyalarında aşağıdaki statik parametreleri kullanabilirsiniz: + +- `%appDir%`, `Bootstrap.php` dosyasının dizinine giden mutlak yoldur +- `%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 +- `%debugMode%` uygulamanın hata ayıklama modunda olup olmadığını gösterir +- `%consoleMode%` isteğin komut satırı üzerinden gelip gelmediğini gösterir + + İthal Hizmetler .[#toc-imported-services] ----------------------------------------- diff --git a/application/tr/components.texy b/application/tr/components.texy index 0f851adb89..030924af19 100644 --- a/application/tr/components.texy +++ b/application/tr/components.texy @@ -233,31 +233,34 @@ $this->redirect(/* ... */); // ve yeniden yönlendir Kalıcı Parametreler .[#toc-persistent-parameters] ================================================= -Genellikle bir bileşenle çalıştığınız süre boyunca bazı parametreleri bileşende tutmanız gerekir. Örneğin sayfalamadaki sayfanın numarası olabilir. Bu parametre `@persistent` ek açıklaması kullanılarak kalıcı olarak işaretlenmelidir. +Kalıcı parametreler, farklı istekler arasında bileşenlerdeki durumu korumak için kullanılır. Bir bağlantı tıklandıktan sonra bile değerleri aynı kalır. Oturum verilerinin aksine, URL içinde aktarılırlar. Ve aynı sayfadaki diğer bileşenlerde oluşturulan bağlantılar da dahil olmak üzere otomatik olarak aktarılırlar. + +Örneğin, bir içerik sayfalama bileşeniniz var. Bir sayfada bu tür birkaç bileşen olabilir. Ve bağlantıya tıkladığınızda tüm bileşenlerin geçerli sayfalarında kalmasını istiyorsunuz. Bu nedenle, sayfa numarasını (`page`) kalıcı bir parametre haline getiriyoruz. + +Kalıcı bir parametre oluşturmak Nette son derece kolaydır. Sadece bir public özellik oluşturun ve şu nitelikle etiketleyin: (daha önce `/** @persistent */` kullanılıyordu) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // bu satır önemlidir + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // halka açık olmalı } ``` -Bu parametre, kullanıcı bu bileşenle sayfadan ayrılana kadar her bağlantıda `GET` parametresi olarak otomatik olarak geçirilecektir. +Veri türünü (örn. `int`) özellikle birlikte eklemenizi öneririz ve ayrıca varsayılan bir değer de ekleyebilirsiniz. Parametre değerleri [doğrulanabilir |#Validation of Persistent Parameters]. -.[caution] -Kalıcı parametrelere asla körü körüne güvenmeyin çünkü kolayca taklit edilebilirler (URL'nin üzerine yazarak). Örneğin, sayfa numarasının doğru aralıkta olup olmadığını doğrulayın. +Bir bağlantı oluştururken kalıcı bir parametrenin değerini değiştirebilirsiniz: -PHP 8'de, kalıcı değiştirgeleri işaretlemek için öznitelikleri de kullanabilirsiniz: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Ya da *reset* edilebilir, yani URL'den kaldırılabilir. Daha sonra varsayılan değerini alacaktır: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ Bir Nette Uygulamasındaki bileşenler, bu bölümün konusu olan sayfalara göm 1) bir şablon içinde oluşturulabilir 2) [AJAX isteği |ajax#invalidation] sırasında hangi bölümünün işleneceğini bilir (snippet'ler) -3) durumunu bir URL'de saklama yeteneğine sahiptir (kalıcılık parametreleri) +3) durumunu bir URL'de saklama yeteneğine sahiptir (kalıcı parametreler) 4) kullanıcı eylemlerine (sinyallere) yanıt verme yeteneğine sahiptir 5) hiyerarşik bir yapı oluşturur (kökün sunum yapan kişi olduğu) @@ -403,6 +406,33 @@ Bileşenin Yaşam Döngüsü .[#toc-life-cycle-of-component] [* lifecycle-component.svg *] *** *Bileşenin yaşam döngüsü* .<> +Kalıcı Parametrelerin Doğrulanması .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------------ + +URL'lerden alınan [kalıcı parametrelerin |#persistent parameters] değerleri `loadState()` metodu tarafından özelliklere yazılır. Ayrıca, özellik için belirtilen veri türünün eşleşip eşleşmediğini kontrol eder, aksi takdirde 404 hatasıyla yanıt verir ve sayfa görüntülenmez. + +Kalıcı parametrelere asla körü körüne güvenmeyin çünkü bunlar URL'de kullanıcı tarafından kolayca üzerine yazılabilir. Örneğin, `$this->page` sayfa numarasının 0'dan büyük olup olmadığını bu şekilde kontrol ederiz. Bunu yapmanın iyi bir yolu, yukarıda bahsedilen `loadState()` yöntemini geçersiz kılmaktır: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // burada $this->page ayarlanır + // kullanıcı değeri kontrolünü takip eder: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Tersi işlem, yani kalıcı özelliklerden değerlerin toplanması, `saveState()` yöntemi tarafından gerçekleştirilir. + + Derinlemesine Sinyaller .[#toc-signals-in-depth] ------------------------------------------------ diff --git a/application/tr/creating-links.texy b/application/tr/creating-links.texy index 0cad61b67b..1b8325a446 100644 --- a/application/tr/creating-links.texy +++ b/application/tr/creating-links.texy @@ -52,7 +52,7 @@ Parametreler bir dizide saklanıyorsa, `...` operatörü (veya Latte 2.x'te `(ex Öznitelik `n:href` HTML etiketleri için çok kullanışlıdır ``. Bağlantıyı başka bir yere, örneğin metnin içine yazdırmak istiyorsak `{link}` adresini kullanırız: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` @@ -88,19 +88,19 @@ Bu format tüm Latte etiketleri ve bağlantılarla çalışan tüm sunum yöntem Bu nedenle temel form `Presenter:action` şeklindedir: ```latte -homepage +home ``` Geçerli sunucunun eylemine bağlantı verirsek, adını atlayabiliriz: ```latte -homepage +home ``` Eylem `default` ise, bunu atlayabiliriz, ancak iki nokta üst üste kalmalıdır: ```latte -homepage +home ``` Bağlantılar diğer [modüllere |modules] de işaret edebilir. Burada, bağlantılar alt modüllere göreli veya mutlak olarak ayırt edilir. Prensip disk yollarına benzer, sadece eğik çizgiler yerine iki nokta üst üste vardır. Gerçek sunucunun `Front` modülünün bir parçası olduğunu varsayalım, o zaman yazacağız: @@ -119,7 +119,7 @@ Bağlantılar diğer [modüllere |modules] de işaret edebilir. Burada, bağlant HTML sayfasının belirli bir bölümüne `#` hash sembolünden sonra gelen fragment adı verilen bir parça aracılığıyla bağlantı verebiliriz: ```latte -link to Homepage:default and fragment #main +link to Home:default and fragment #main ``` @@ -128,7 +128,7 @@ Mutlak Yollar .[#toc-absolute-paths] `link()` veya `n:href` tarafından oluşturulan bağlantılar her zaman mutlak yollardır (yani `/` ile başlarlar), ancak `https://domain` gibi bir protokol ve etki alanı içeren mutlak URL'ler değildir. -Mutlak bir URL oluşturmak için başına iki eğik çizgi ekleyin (örneğin, `n:href="//Homepage:"`). Ya da `$this->absoluteUrls = true` adresini ayarlayarak sunucuyu yalnızca mutlak bağlantılar oluşturacak şekilde değiştirebilirsiniz. +Mutlak bir URL oluşturmak için başına iki eğik çizgi ekleyin (örneğin, `n:href="//Home:"`). Ya da `$this->absoluteUrls = true` adresini ayarlayarak sunucuyu yalnızca mutlak bağlantılar oluşturacak şekilde değiştirebilirsiniz. Güncel Sayfaya Bağlantı .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ Bileşendeki Bağlantılar .[#toc-links-in-component] Bileşen şablonunda sunum yapanlara bağlantı vermek istiyorsak `{plink}` etiketini kullanırız: ```latte -homepage +home ``` veya kodda ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/tr/how-it-works.texy b/application/tr/how-it-works.texy index c3449f2c31..35bbb16a50 100644 --- a/application/tr/how-it-works.texy +++ b/application/tr/how-it-works.texy @@ -23,10 +23,10 @@ Dizin yapısı şuna benzer: web-project/ ├── app/ ← directory with application │ ├── Presenters/ ← presenter classes -│ │ ├── HomepagePresenter.php ← Homepage presenter class +│ │ ├── HomePresenter.php ← Home presenter class │ │ └── templates/ ← templates directory │ │ ├── @layout.latte ← template of shared layout -│ │ └── Homepage/ ← templates for Homepage presenter +│ │ └── Home/ ← templates for Home presenter │ │ └── default.latte ← template for action `default` │ ├── Router/ ← configuration of URL addresses │ └── Bootstrap.php ← booting class Bootstrap @@ -134,10 +134,10 @@ Sadece emin olmak için, tüm süreci biraz farklı bir URL ile özetlemeye çal 1) URL şu şekilde olacaktır `https://example.com` 2) uygulamayı önyüklüyoruz, bir konteyner oluşturuyoruz ve `Application::run()` -3) yönlendirici URL'yi bir çift olarak çözer `Homepage:default` -4) bir `HomepagePresenter` nesnesi oluşturulur +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/Homepage/default.latte` şablonu oluşturulur +6) `templates/@layout.latte` düzenine sahip bir `templates/Home/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 2915afa552..e447d581bd 100644 --- a/application/tr/modules.texy +++ b/application/tr/modules.texy @@ -104,7 +104,7 @@ 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. -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, `Homepage` gibi bir sunucunun `App\Presenters\HomepagePresenter` sınıfıyla eşleşmesi gerektiği anlamına gelir. Bu, aşağıdaki yapılandırma ile gerçekleştirilebilir: +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: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Şimdi `Front:Homepage` sunucusu `App\Modules\Front\Presenters\HomepagePresenter` sınıfıyla ve `Admin:Dashboard` sunucusu `App\Modules\Admin\Presenters\DashboardPresenter` sınıfıyla eşleşir. +Ş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. İ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: diff --git a/application/tr/presenters.texy b/application/tr/presenters.texy index 04d2626cf2..74e9bd24c8 100644 --- a/application/tr/presenters.texy +++ b/application/tr/presenters.texy @@ -158,7 +158,7 @@ Yeniden Yönlendirme .[#toc-redirection] $this->forward('Product:show'); ``` -HTTP kodu 302 veya 303 ile geçici yeniden yönlendirme örneği: +HTTP kodu 302 (veya geçerli istek yöntemi POST ise 303) ile sözde geçici yeniden yönlendirme örneği: ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ HTTP kodu 301 ile kalıcı yeniden yönlendirme elde etmek için kullanın: $this->redirectPermanent('Product:show', $id); ``` -`redirectUrl()` yöntemi ile uygulama dışında başka bir URL'ye yönlendirme yapabilirsiniz: +`redirectUrl()` yöntemini kullanarak uygulama dışında başka bir URL'ye yönlendirme yapabilirsiniz. HTTP kodu ikinci parametre olarak belirtilebilir; varsayılan değer 302'dir (veya geçerli istek yöntemi POST ise 303'tür): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Kalıcı Parametreler .[#toc-persistent-parameters] ================================================= -Kalıcı parametreler bağlantılarda **otomatik olarak** aktarılır. Bu, şablondaki her `link()` veya `n:href` adresinde bunları açıkça belirtmek zorunda olmadığımız, ancak yine de aktarılacakları anlamına gelir. +Kalıcı parametreler, farklı istekler arasında durumu korumak için kullanılır. Bir bağlantı tıklandıktan sonra bile değerleri aynı kalır. Oturum verilerinin aksine, URL'de aktarılırlar. Bu tamamen otomatiktir, bu nedenle bunları `link()` veya `n:href` adresinde açıkça belirtmeye gerek yoktur. -Uygulamanızın birden fazla dil sürümü varsa, geçerli dil her zaman URL'nin bir parçası olması gereken bir parametredir. Ve her bağlantıda bundan bahsetmek inanılmaz derecede yorucu olacaktır. Nette ile buna gerek yok. Sadece `lang` parametresini bu şekilde kalıcı olarak işaretliyoruz: +Kullanım örneği? Çok dilli bir uygulamanız var. Gerçek dil, her zaman URL'nin bir parçası olması gereken bir parametredir. Ancak bunu her bağlantıya dahil etmek inanılmaz derecede sıkıcı olacaktır. Bu yüzden onu `lang` adında kalıcı bir parametre yaparsınız ve kendi kendini taşır. Harika! + +Kalıcı bir parametre oluşturmak Nette son derece kolaydır. Sadece bir public özellik oluşturun ve şu nitelikle etiketleyin: (daha önce `/** @persistent */` kullanılıyordu) ```php +use Nette\Application\Attributes\Persistent; // bu satır önemlidir + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // halka açık olmalı } ``` -`lang` parametresinin geçerli değeri `'en'` ise, şablonda `link()` veya `n:href` ile oluşturulan URL `lang=en` içerecektir. Harika! - -Ancak, `lang` parametresini de ekleyebilir ve bununla değerini değiştirebiliriz: + `$this->lang`, `'en'` gibi bir değere sahipse, `link()` veya `n:href` kullanılarak oluşturulan bağlantılar da `lang=en` parametresini içerecektir. Ve bağlantı tıklandığında, yine `$this->lang = 'en'` olacaktır. -```latte -detail in English -``` - -Ya da tersine, null olarak ayarlanarak kaldırılabilir: - -```latte -click here -``` +Özellikler için veri türünü eklemenizi öneririz (örn. `string`) ve ayrıca varsayılan bir değer de ekleyebilirsiniz. Parametre değerleri [doğrulanabilir |#Validation of Persistent Parameters]. -Kalıcı değişken public olarak bildirilmelidir. Ayrıca varsayılan bir değer de belirtebiliriz. Parametre varsayılan değerle aynı değere sahipse, URL'ye dahil edilmeyecektir. +Kalıcı parametreler varsayılan olarak belirli bir sunum yapan kişinin tüm eylemleri arasında aktarılır. Bunları birden fazla sunum yapan kişi arasında geçirmek için tanımlamanız gerekir: -Kalıcılık, sunum yapan sınıfların hiyerarşisini yansıtır, böylece belirli bir sunum yapan sınıfta veya özellikte tanımlanan parametre, ondan miras alan veya aynı özelliği kullanan her sunum yapan sınıfa otomatik olarak aktarılır. - -PHP 8'de, kalıcı değiştirgeleri işaretlemek için öznitelikleri de kullanabilirsiniz: +- Sunum yapanların miras aldığı ortak bir atada +- sunum yapanların kullandığı özellikte: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Bir bağlantı oluştururken kalıcı bir parametrenin değerini değiştirebilirsiniz: + +```latte +detail in Czech +``` + +Ya da *reset* edilebilir, yani URL'den kaldırılabilir. Daha sonra varsayılan değerini alacaktır: + +```latte +click ``` @@ -302,7 +310,32 @@ Bu bölümde şimdiye kadar gösterdiklerimiz muhtemelen yeterli olacaktır. Aş Gereksinim ve Parametreler .[#toc-requirement-and-parameters] ------------------------------------------------------------- -Sunum yapan kişi tarafından ele alınan istek [api:Nette\Application\Request] nesnesidir ve sunum yapan kişinin `getRequest()` yöntemi tarafından döndürülür. Bir dizi parametre içerir ve bunların her biri ya bazı bileşenlere ya da doğrudan sunum yapan kişiye aittir (aslında bu da özel bir bileşen olsa da bir bileşendir). Bu nedenle Nette parametreleri yeniden dağıtır ve [Bileşenler |Components] bölümünde daha ayrıntılı olarak açıklanan `loadState(array $params)` yöntemini çağırarak tek tek bileşenler (ve sunum yapan kişi) arasında geçiş yapar. Parametreler `getParameters(): array` yöntemi ile, `getParameter($name)` kullanılarak ayrı ayrı elde edilebilir. Parametre değerleri dizeler veya dizelerin dizileridir, temel olarak doğrudan bir URL'den elde edilen ham verilerdir. +Sunum yapan kişi tarafından ele alınan istek [api:Nette\Application\Request] nesnesidir ve sunum yapan kişinin `getRequest()` yöntemi tarafından döndürülür. Bir dizi parametre içerir ve bunların her biri ya bazı bileşenlere ya da doğrudan sunum yapan kişiye aittir (aslında bu da özel bir bileşen olsa da bir bileşendir). Dolayısıyla Nette parametreleri yeniden dağıtır ve `loadState(array $params)` yöntemini çağırarak tek tek bileşenler (ve sunum yapan kişi) arasında geçiş yapar. Parametreler `getParameters(): array` yöntemiyle, ayrı ayrı `getParameter($name)` kullanılarak elde edilebilir. Parametre değerleri dizeler veya dizelerin dizileridir, temelde doğrudan bir URL'den elde edilen ham verilerdir. + + +Kalıcı Parametrelerin Doğrulanması .[#toc-validation-of-persistent-parameters] +------------------------------------------------------------------------------ + +URL'lerden alınan [kalıcı parametrelerin |#persistent parameters] değerleri `loadState()` metodu tarafından özelliklere yazılır. Ayrıca özellikte belirtilen veri türünün eşleşip eşleşmediğini kontrol eder, aksi takdirde 404 hatası ile yanıt verir ve sayfa görüntülenmez. + +Kalıcı parametrelere asla körü körüne güvenmeyin, çünkü bunlar URL'de kullanıcı tarafından kolayca üzerine yazılabilir. Örneğin, `$this->lang` adresinin desteklenen diller arasında olup olmadığını bu şekilde kontrol ediyoruz. Bunu yapmanın iyi bir yolu, yukarıda bahsedilen `loadState()` yöntemini geçersiz kılmaktır: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // burada $this->lang ayarlanır + // kullanıcı değeri kontrolünü takip eder: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Talebi Kaydetme ve Geri Yükleme .[#toc-save-and-restore-the-request] diff --git a/application/tr/routing.texy b/application/tr/routing.texy index 5a77af89c1..b1812614ab 100644 --- a/application/tr/routing.texy +++ b/application/tr/routing.texy @@ -93,12 +93,12 @@ Rota şimdi `https://any-domain.com/chronicle/` parametresiyle birlikte `History Elbette, sunum yapan kişinin adı ve eylem de bir parametre olabilir. Örneğin: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Bu rota, örneğin `/article/edit` veya `/catalog/list` şeklinde bir URL'yi kabul eder ve bunları `Article:edit` veya `Catalog:list` sunucularına ve eylemlerine çevirir. -Ayrıca `presenter` ve `action` parametrelerine`Homepage` ve `default` varsayılan değerlerini verir ve bu nedenle bunlar isteğe bağlıdır. Böylece rota `/article` URL'sini de kabul eder ve bunu `Article:default` olarak çevirir. Ya da tam tersi, `Product:default` adresine bir bağlantı `/product` yolunu oluşturur, varsayılan `Homepage:default` adresine bir bağlantı `/` yolunu oluşturur. +Ayrıca `presenter` ve `action` parametrelerine`Home` ve `default` varsayılan değerlerini verir ve bu nedenle bunlar isteğe bağlıdır. Böylece rota `/article` URL'sini de kabul eder ve bunu `Article:default` olarak çevirir. Ya da tam tersi, `Product:default` adresine bir bağlantı `/product` yolunu oluşturur, varsayılan `Home:default` adresine bir bağlantı `/` yolunu oluşturur. Maske yalnızca site köküne dayalı göreli yolu değil, aynı zamanda eğik çizgi ile başladığında mutlak yolu, hatta iki eğik çizgi ile başladığında tüm mutlak URL'yi de tanımlayabilir: @@ -160,7 +160,7 @@ Diziler serbestçe iç içe geçirilebilir ve birleştirilebilir: ```php $router->addRoute( '[[-]/][/page-]', - 'Homepage:default', + 'Home:default', ); // Kabul edilen URL'ler: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Köşeli parantezler olmadan isteğe bağlı parametreler (yani varsayılan değere sahip parametreler) bu şekilde sarılmış gibi davranır: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // eşittir: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -En sağdaki eğik çizginin nasıl oluşturulduğunu değiştirmek için, yani `/homepage/` yerine bir `/homepage` almak için, rotayı bu şekilde ayarlayın: +En sağdaki eğik çizginin nasıl oluşturulduğunu değiştirmek için, yani `/home/` yerine bir `/home` almak için, rotayı bu şekilde ayarlayın: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ Genellikle `Presenter:action` biçiminde yazdığımız rotanın ikinci parametr ```php $router->addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ Filtreler ve Çeviriler .[#toc-filters-and-translations] Kaynak kodunu İngilizce yazmak iyi bir uygulamadır, ancak web sitenizin URL'sinin farklı bir dile çevrilmesi gerekiyorsa ne olacak? Gibi basit rotalar: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` `/product/123` veya `/cart` gibi İngilizce URL'ler oluşturacaktır. URL'deki sunucuların ve eylemlerin Almanca'ya çevrilmesini istiyorsak (örneğin `/produkt/123` veya `/einkaufswagen`), bir çeviri sözlüğü kullanabiliriz. Bunu eklemek için, ikinci parametrenin "daha konuşkan" bir varyantına zaten ihtiyacımız var: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // URL'deki dize => presenter 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ Belirli parametreler için filtrelerin yanı sıra, herhangi bir şekilde deği use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -504,14 +504,14 @@ http://example.com/?presenter=Product&action=detail&id=123 ```php // varsayılan olarak sunucu 'Anasayfa' ve eylem 'varsayılan' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` SimpleRouter'ı doğrudan [yapılandırmada |dependency-injection:services] tanımlamanızı öneririz: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ class MyRouter implements Nette\Routing\Router ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/tr/templates.texy b/application/tr/templates.texy index 6512641528..d817bc2d45 100644 --- a/application/tr/templates.texy +++ b/application/tr/templates.texy @@ -42,7 +42,9 @@ Düzende `{include content}` yerine eklenen `content` bloğunu tanımlar ve ayr - `templates//.latte` - `templates/..latte` -Şablonu bulamazsa, yanıt [404 hatası |presenters#Error 404 etc.] olur. +Ş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. + +Şablon orada da bulunamazsa, yanıt [404 hatası |presenters#Error 404 etc.] olur. 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. @@ -148,7 +150,7 @@ Bağlantı Oluşturma .[#toc-creating-links] Öznitelik `n:href` HTML etiketleri için çok kullanışlıdır ``. Bağlantıyı başka bir yere, örneğin metnin içine yazdırmak istiyorsak `{link}` adresini kullanırız: ```latte -URL is: {link Homepage:default} +URL is: {link Home:default} ``` Daha fazla bilgi için [Bağlantı Oluşturma |Creating Links] bölümüne bakın. diff --git a/application/uk/@left-menu.texy b/application/uk/@left-menu.texy index dfefb7aa58..1f6999ed7d 100644 --- a/application/uk/@left-menu.texy +++ b/application/uk/@left-menu.texy @@ -15,5 +15,8 @@ Подальше читання **************** +- [Чому варто використовувати Nette? |www:10-reasons-why-nette] +- [Встановлення |nette:installation] +- [Створіть свій перший додаток! |quickstart:] - [Кращі практики |best-practices:] - [Усунення неполадок |nette:troubleshooting] diff --git a/application/uk/ajax.texy b/application/uk/ajax.texy index a05a0e96e6..657629160c 100644 --- a/application/uk/ajax.texy +++ b/application/uk/ajax.texy @@ -10,9 +10,13 @@ AJAX і сніпети -AJAX-запит може бути виявлений за допомогою методу сервісу [інкапсуляція HTTP-запиту |http:request] `$httpRequest->isAjax()` (визначає на основі HTTP-заголовка `X-Requested-With`). Існує також скорочений метод у презентері: `$this->isAjax()`. -AJAX-запит нічим не відрізняється від звичайного - викликається презентер з певним поданням і параметрами. Від презентера також залежить, як він відреагує: він може використати свої процедури для повернення фрагмента HTML-коду (сніпету), XML-документа, об'єкта JSON або фрагмента коду Javascript. +AJAX запит .[#toc-ajax-request] +=============================== + +AJAX-запит нічим не відрізняється від класичного запиту - викликається доповідач з певним представленням і параметрами. Як на нього відповісти, також залежить від доповідача: він може використовувати власну процедуру, яка повертає фрагмент HTML-коду (HTML-сніппет), XML-документ, JSON-об'єкт або JavaScript-код. + +На стороні сервера AJAX-запит можна виявити за допомогою сервісного методу, що [інкапсулює HTTP-запит |http:request] `$httpRequest->isAjax()` (виявляє на основі HTTP-заголовка `X-Requested-With`). Усередині доповідача доступний ярлик у вигляді методу `$this->isAjax()`. Існує попередньо оброблений об'єкт `payload`, призначений для надсилання даних у браузер у форматі JSON. @@ -60,6 +64,21 @@ npm install naja ``` +Щоб створити AJAX-запит зі звичайного посилання (сигналу) або форми, просто позначте відповідне посилання, форму або кнопку класом `ajax`: + +```html +Go + +
+ +
+ +or +
+ +
+``` + Сніпети .[#toc-snippety] ======================== @@ -149,7 +168,7 @@ $this->isControlInvalid('footer'); // -> true У наведеному прикладі необхідно переконатися, що під час AJAX-запиту до масиву `$list` буде додано тільки один елемент, тому цикл `foreach` виводитиме тільки один динамічний фрагмент. ```php -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { /** * Этот метод возвращает данные для списка. diff --git a/application/uk/bootstrap.texy b/application/uk/bootstrap.texy index f4872269e9..f71b35252b 100644 --- a/application/uk/bootstrap.texy +++ b/application/uk/bootstrap.texy @@ -174,7 +174,7 @@ $configurator->addStaticParameters([ ]); ``` -У конфігураційних файлах можна використовувати звичайну нотацію `%projectId%` для доступу до параметра з ім'ям `projectId`. За замовчуванням конфігуратор заповнює такі параметри: `appDir`, `wwwDir`, `tempDir`, `vendorDir`, `debugMode` і `consoleMode`. +У конфігураційних файлах ми можемо записати звичайну нотацію `%projectId%` для доступу до параметра з ім'ям `projectId`. Динамічні параметри .[#toc-dynamic-parameters] @@ -197,6 +197,19 @@ $configurator->addDynamicParameters([ ``` +Параметри за замовчуванням .[#toc-default-parameters] +----------------------------------------------------- + +Ви можете використовувати наступні статичні параметри у файлах конфігурації: + +- `%appDir%` - абсолютний шлях до каталогу з файлом `Bootstrap.php` +- `%wwwDir%` - абсолютний шлях до каталогу, в якому знаходиться файл запису `index.php` +- `%tempDir%` - абсолютний шлях до каталогу для тимчасових файлів +- `%vendorDir%` - абсолютний шлях до каталогу, куди Composer встановлює бібліотеки +- `%debugMode%` вказує на те, чи перебуває програма у режимі налагодження +- `%consoleMode%` вказує на те, що запит надійшов через командний рядок + + Імпортовані сервіси .[#toc-imported-services] --------------------------------------------- diff --git a/application/uk/components.texy b/application/uk/components.texy index 8b5c0f89f9..097773c6ba 100644 --- a/application/uk/components.texy +++ b/application/uk/components.texy @@ -233,31 +233,34 @@ $this->redirect(/* ... */); // робимо редирект Постійні параметри .[#toc-persistent-parameters] ================================================ -Часто буває необхідно зберегти якийсь параметр у компоненті на весь час роботи з ним. Це може бути, наприклад, номер сторінки в пагінації. Цей параметр має бути позначений як постійний за допомогою анотації `@persistent`. +Постійні параметри використовуються для збереження стану компонентів між різними запитами. Їх значення залишається незмінним навіть після переходу за посиланням. На відміну від сесійних даних, вони передаються в URL-адресі. І вони передаються автоматично, включаючи посилання, створені в інших компонентах на тій же сторінці. + +Наприклад, у вас є компонент підкачки контенту. Таких компонентів на сторінці може бути декілька. І ви хочете, щоб при переході за посиланням всі компоненти залишалися на своїй поточній сторінці. Тому ми робимо номер сторінки (`page`) постійним параметром. + +Створити постійний параметр в Nette надзвичайно просто. Просто створіть загальнодоступну властивість і позначте її атрибутом: (раніше використовувалося `/** @persistent */` ) ```php -class PollControl extends Control +use Nette\Application\Attributes\Persistent; // цей рядок важливий + +class PaginatingControl extends Control { - /** @persistent */ - public int $page = 1; + #[Persistent] + public int $page = 1; // повинні бути публічними } ``` -Цей параметр буде автоматично передаватися в кожному посиланні як параметр `GET` доти, доки користувач не покине сторінку з цим компонентом. +Ми рекомендуємо вам вказати тип даних (наприклад, `int`) разом з властивістю, а також ви можете вказати значення за замовчуванням. Значення параметрів можуть бути [перевірені |#Validation of Persistent Parameters]. -.[caution] -Ніколи не довіряйте постійним параметрам наосліп, оскільки їх можна легко підробити (шляхом перезапису URL). Перевірте, наприклад, чи знаходиться номер сторінки в правильному інтервалі. +Ви можете змінити значення постійного параметра під час створення посилання: -У PHP 8 ви також можете використовувати атрибути для маркування постійних параметрів: +```latte +next +``` -```php -use Nette\Application\Attributes\Persistent; +Або ж його можна *скинути*, тобто видалити з URL-адреси. Тоді він прийме значення за замовчуванням: -class PollControl extends Control -{ - #[Persistent] - public int $page = 1; -} +```latte +reset ``` @@ -378,7 +381,7 @@ interface PollControlFactory 1) він може бути відображений у шаблоні 2) він знає, яку частину себе відображати під час [AJAX-запиту |ajax#invalidation] (сніпети) -3) він може зберігати свій стан в URL (параметри персистентності) +3) має можливість зберігати свій стан в URL (постійні параметри) 4) має можливість реагувати на дії користувача (сигнали) 5) створює ієрархічну структуру (де коренем є ведучий) @@ -406,6 +409,29 @@ Nette\ComponentModel\Component { IComponent } Перевірка постійних параметрів .[#toc-validation-of-persistent-parameters] -------------------------------------------------------------------------- +Значення [постійних параметрів |#persistent parameters], отримані з URL-адрес, записуються у властивості методом `loadState()`. Також перевіряється, чи збігається тип даних, вказаний для властивості, інакше буде видано помилку 404, і сторінка не буде відображена. + +Ніколи не довіряйте сліпо постійним параметрам, оскільки вони можуть бути легко перезаписані користувачем в URL. Наприклад, так ми перевіряємо, чи номер сторінки `$this->page` більший за 0. Хорошим способом зробити це є перевизначення методу `loadState()`, згаданого вище: + +```php +class PaginatingControl extends Control +{ + #[Persistent] + public int $page = 1; + + public function loadState(array $params): void + { + parent::loadState($params); // тут задається $this->page + // слідує перевірка користувацького значення: + if ($this->page < 1) { + $this->error(); + } + } +} +``` + +Протилежний процес, тобто збір значень з постійних властивостей, обробляється методом `saveState()`. + Сигнали в глибину .[#toc-signals-in-depth] ------------------------------------------ diff --git a/application/uk/creating-links.texy b/application/uk/creating-links.texy index 1ff9cc58b6..34ec0457fb 100644 --- a/application/uk/creating-links.texy +++ b/application/uk/creating-links.texy @@ -52,7 +52,7 @@ Атрибут `n:href` дуже зручний для HTML-тегів ``. Якщо ми хочемо вивести посилання в іншому місці, наприклад, у тексті, ми використовуємо `{link}`: ```latte -URL: {link Homepage:default} +URL: {link Home:default} ``` @@ -88,7 +88,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Тому основною формою є `Presenter:action`: ```latte -главная страница +главная страница ``` Якщо ми посилаємося на дію поточного презентера, ми можемо опустити його ім'я: @@ -100,7 +100,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Якщо дія є `default`, ми можемо опустити її, але двокрапка має залишитися: ```latte -главная страница +главная страница ``` Посилання можуть також вказувати на інші [модулі |modules]. Тут посилання розрізняються на відносні по відношенню до підмодулів або абсолютні. Принцип аналогічний дисковим шляхам, тільки замість косих рисок стоять двокрапки. Припустимо, що справжній презентер є частиною модуля `Front`, тоді ми напишемо: @@ -119,7 +119,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Ми можемо посилатися на певну частину HTML-сторінки через так званий фрагмент після хеш-символу `#`: ```latte -ссылка на Homepage:default и фрагмент #main +ссылка на Home:default и фрагмент #main ``` @@ -128,7 +128,7 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Посилання, що генеруються `link()` або `n:href`, завжди є абсолютними шляхами (тобто починаються з `/`), але не абсолютними URL з протоколом і доменом, як `https://domain`. -Щоб створити абсолютний URL, додайте дві косі риски на початок (наприклад, `n:href="//Homepage:"`). Або ви можете перемкнути презентатор на генерацію тільки абсолютних посилань, встановивши `$this->absoluteUrls = true`. +Щоб створити абсолютний URL, додайте дві косі риски на початок (наприклад, `n:href="//Home:"`). Або ви можете перемкнути презентатор на генерацію тільки абсолютних посилань, встановивши `$this->absoluteUrls = true`. Посилання на поточну сторінку .[#toc-link-to-current-page] @@ -213,13 +213,13 @@ $url = $this->link('Product:show', [$product->id, 'lang' => 'cs']); Якщо ми хочемо зробити посилання на презентери в шаблоні компонента, ми використовуємо тег `{plink}`: ```latte -главная страница +главная страница ``` or in the code ```php -$this->getPresenter()->link('Homepage:default') +$this->getPresenter()->link('Home:default') ``` diff --git a/application/uk/how-it-works.texy b/application/uk/how-it-works.texy index 548d29a4c2..903ddc003d 100644 --- a/application/uk/how-it-works.texy +++ b/application/uk/how-it-works.texy @@ -23,10 +23,10 @@ web-project/ ├── app/ ← каталог с приложением │ ├── Presenters/ ← классы презентеров -│ │ ├── HomepagePresenter.php ← Класс презентера главной страницы +│ │ ├── HomePresenter.php ← Класс презентера главной страницы │ │ └── templates/ ← директория шаблонов │ │ ├── @layout.latte ← шаблон общего макета -│ │ └── Homepage/ ← шаблоны презентера главной страницы +│ │ └── Home/ ← шаблоны презентера главной страницы │ │ └── default.latte ← шаблон действия `default` │ ├── Router/ ← конфигурация URL-адресов │ └── Bootstrap.php ← загрузочный класс Bootstrap @@ -134,10 +134,10 @@ class ProductPresenter extends Nette\Application\UI\Presenter 1) URL буде `https://example.com` 2) ми завантажуємо додаток, створюємо контейнер і запускаємо `Application::run()` -3) маршрутизатор декодує URL як пару `Homepage:default` -4) створюється об'єкт `HomepagePresenter` +3) маршрутизатор декодує URL як пару `Home:default` +4) створюється об'єкт `HomePresenter` 5) викликається метод `renderDefault()` (якщо існує) -6) шаблон `templates/Homepage/default.latte` з макетом `templates/@layout.latte` відмальований +6) шаблон `templates/Home/default.latte` з макетом `templates/@layout.latte` відмальований Можливо, зараз ви зіткнулися з безліччю нових понять, але ми вважаємо, що вони мають сенс. Створювати додатки в Nette - простіше простого. diff --git a/application/uk/modules.texy b/application/uk/modules.texy index 028dd4666b..5503142028 100644 --- a/application/uk/modules.texy +++ b/application/uk/modules.texy @@ -104,7 +104,7 @@ class DashboardPresenter extends Nette\Application\UI\Presenter Визначає правила, за якими ім'я класу виводиться з імені ведучого. Ми записуємо їх у [конфігурацію |configuration] під ключем `application › mapping`. -Почнемо з прикладу, в якому не використовуються модулі. Ми просто хочемо, щоб класи ведучого мали простір імен `App\Presenters`. Тобто ми хочемо, щоб ведучий, наприклад, `Homepage` відображався на клас `App\Presenters\HomepagePresenter`. Цього можна досягти за допомогою такої конфігурації: +Почнемо з прикладу, в якому не використовуються модулі. Ми просто хочемо, щоб класи ведучого мали простір імен `App\Presenters`. Тобто ми хочемо, щоб ведучий, наприклад, `Home` відображався на клас `App\Presenters\HomePresenter`. Цього можна досягти за допомогою такої конфігурації: ```neon application: @@ -124,7 +124,7 @@ application: Api: App\Api\*Presenter ``` -Тепер презентер `Front:Homepage` визначається класом `App\Modules\Front\HomepagePresenter`, а презентер `Admin:Dashboard` ` - `App\AdminModule\DashboardPresenter`. +Тепер презентер `Front:Home` визначається класом `App\Modules\Front\HomePresenter`, а презентер `Admin:Dashboard` ` - `App\AdminModule\DashboardPresenter`. Зручніше буде створити загальне правило (зірочка), яке замінить перші два правила і додасть додаткову зірочку тільки для модуля: diff --git a/application/uk/presenters.texy b/application/uk/presenters.texy index 454b1150c5..4ff391e760 100644 --- a/application/uk/presenters.texy +++ b/application/uk/presenters.texy @@ -158,7 +158,7 @@ $url = $this->link('Product:show', [$id, 'lang' => 'en']); $this->forward('Product:show'); ``` -Приклад тимчасового перенаправлення з HTTP-кодом 302 або 303: +Приклад так званого тимчасового перенаправлення з HTTP-кодом 302 (або 303, якщо поточний метод запиту - POST): ```php $this->redirect('Product:show', $id); @@ -170,7 +170,7 @@ $this->redirect('Product:show', $id); $this->redirectPermanent('Product:show', $id); ``` -Ви можете перенаправити на інший URL поза додатком за допомогою методу `redirectUrl()`: +Ви можете перенаправити на іншу URL-адресу за межами програми за допомогою методу `redirectUrl()`. Другим параметром можна вказати HTTP-код, за замовчуванням 302 (або 303, якщо поточний метод запиту - POST): ```php $this->redirectUrl('https://nette.org'); @@ -239,46 +239,54 @@ public function actionData(): void Постійні параметри .[#toc-persistent-parameters] ================================================ -Постійні параметри **передаються автоматично** у посиланнях. Це означає, що нам не потрібно явно вказувати їх у кожному `link()` або `n:href` у шаблоні, але вони все одно будуть передані. +Постійні параметри використовуються для збереження стану між різними запитами. Їх значення залишається незмінним навіть після переходу за посиланням. На відміну від сесійних даних, вони передаються в URL-адресі. Це відбувається повністю автоматично, тому немає необхідності явно вказувати їх в `link()` або `n:href`. -Якщо ваш додаток має кілька мовних версій, то поточна мова є параметром, який завжди повинен бути частиною URL. І було б неймовірно утомливо згадувати про це в кожному посиланні. З Nette в цьому немає необхідності. Таким чином ми просто позначаємо параметр `lang` як постійний: +Приклад використання? У вас є багатомовний додаток. Фактична мова - це параметр, який завжди повинен бути частиною URL-адреси. Але було б неймовірно нудно включати його в кожне посилання. Тому ви робите його постійним параметром з іменем `lang`, і він сам себе переноситиме. Круто! + +Створити постійний параметр у Nette надзвичайно просто. Просто створіть загальнодоступну властивість і позначте її атрибутом: (раніше використовували `/** @persistent */` ) ```php +use Nette\Application\Attributes\Persistent; // цей рядок важливий + class ProductPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ - public string $lang; + #[Persistent] + public string $lang; // повинні бути публічними } ``` -Якщо поточне значення параметра `lang` дорівнює `'en'`, то URL, створений за допомогою `link()` або `n:href` у шаблоні, міститиме `lang=en`. Чудово! - -Однак ми також можемо додати параметр `lang` і тим самим змінити його значення: +Якщо `$this->lang` має значення `'en'`, то посилання, створені за допомогою `link()` або `n:href`, також будуть містити параметр `lang=en`. І коли посилання буде натиснуто, воно знову стане `$this->lang = 'en'`. -```latte -подробности на английском -``` - -Або, навпаки, його можна видалити, встановивши в null: - -```latte -нажмите здесь -``` +Для властивостей рекомендується вказувати тип даних (наприклад, `string`), а також можна вказати значення за замовчуванням. Значення параметрів можна [перевіряти |#Validation of Persistent Parameters]. -Постійна змінна має бути оголошена як public. Ми також можемо вказати значення за замовчуванням. Якщо параметр має те саме значення, що й значення за замовчуванням, його не буде включено в URL. +Постійні параметри за замовчуванням передаються між усіма діями даного доповідача. Щоб передати їх між кількома доповідачами, вам також потрібно їх визначити: -Сталість відображає ієрархію класів презентерів, тому параметр, визначений у конкретному презентері або трейте, автоматично передається кожному презентеру, який успадковує від нього або використовує той самий трейт. - -У PHP 8 ви також можете використовувати атрибути для маркування постійних параметрів: +- у спільному предку, від якого успадковуються ведучі +- в ознаці, яку ведучі використовують: ```php -use Nette\Application\Attributes\Persistent; - -class ProductPresenter extends Nette\Application\UI\Presenter +trait LangAware { #[Persistent] public string $lang; } + +class ProductPresenter extends Nette\Application\UI\Presenter +{ + use LangAware; +} +``` + +Ви можете змінити значення постійного параметра при створенні посилання: + +```latte +detail in Czech +``` + +Або ж його можна *скинути*, тобто видалити з URL-адреси. Тоді він прийме значення за замовчуванням: + +```latte +click ``` @@ -302,7 +310,32 @@ class ProductPresenter extends Nette\Application\UI\Presenter Вимоги та параметри .[#toc-requirement-and-parameters] ------------------------------------------------------ -Запит, що обробляється презентером, є об'єктом [api:Nette\Application\Request] і повертається методом презентера `getRequest()`. Він містить масив параметрів, кожен з яких належить або якомусь із компонентів, або безпосередньо презентеру (який насправді теж є компонентом, хоча й спеціальним). Таким чином, Nette перерозподіляє параметри і передається між окремими компонентами (і презентером) шляхом виклику методу `loadState(array $params)`, який детальніше описано в розділі [Компоненти |components]. Параметри можна отримати за допомогою методу `getParameters(): array`, індивідуально використовуючи `getParameter($name)`. Значення параметрів - це рядки або масиви рядків, в основному це необроблені дані, отримані безпосередньо з URL. +Запит, який обробляє доповідач, є об'єктом [api:Nette\Application\Request] і повертається методом доповідача `getRequest()`. Він містить масив параметрів, кожен з яких належить або якомусь з компонентів, або безпосередньо доповідачу (який, власне, теж є компонентом, хоча й особливим). Тож Nette перерозподіляє параметри та передачі між окремими компонентами (та доповідачем) за допомогою виклику методу `loadState(array $params)`. Параметри можна отримати за допомогою методу `getParameters(): array`, окремо за допомогою `getParameter($name)`. Значення параметрів - це рядки або масиви рядків, в основному це необроблені дані, отримані безпосередньо з URL-адреси. + + +Перевірка постійних параметрів .[#toc-validation-of-persistent-parameters] +-------------------------------------------------------------------------- + +Значення [постійних параметрів |#persistent parameters], отриманих з URL-адрес, записуються у властивості методом `loadState()`. Також перевіряється, чи збігається тип даних, вказаний у властивості, інакше буде видано помилку 404, і сторінка не буде відображена. + +Ніколи не довіряйте сліпо постійним параметрам, оскільки вони можуть бути легко перезаписані користувачем в URL. Наприклад, так ми перевіряємо, чи є `$this->lang` серед підтримуваних мов. Хороший спосіб зробити це - перевизначити метод `loadState()`, згаданий вище: + +```php +class ProductPresenter extends Nette\Application\UI\Presenter +{ + #[Persistent] + public string $lang; + + public function loadState(array $params): void + { + parent::loadState($params); // тут задається значення $this->lang + // слідує за перевіркою користувацького значення: + if (!in_array($this->lang, ['en', 'cs'])) { + $this->error(); + } + } +} +``` Збереження та відновлення запиту .[#toc-save-and-restore-the-request] diff --git a/application/uk/routing.texy b/application/uk/routing.texy index 0833ee1c7e..6295ad4828 100644 --- a/application/uk/routing.texy +++ b/application/uk/routing.texy @@ -93,12 +93,12 @@ $router->addRoute('chronicle/', 'History:show'); Звісно, ім'я презентера та дія також можуть бути параметрами. Наприклад: ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` Цей маршрут приймає, наприклад, URL у формі `/article/edit` і `/catalog/list` відповідно, і переводить їх у презентери та дії `Article:edit` і `Catalog:list` відповідно. -Він також надає параметрам `presenter` і `action` значення за замовчуванням `Homepage` і `default` і тому вони є необов'язковими. Тому маршрут також приймає URL `/article` і переводить його як `Article:default`. Або навпаки, посилання на `Product:default` генерує шлях `/product`, посилання на стандартну `Homepage:default` генерує шлях `/`. +Він також надає параметрам `presenter` і `action` значення за замовчуванням `Home` і `default` і тому вони є необов'язковими. Тому маршрут також приймає URL `/article` і переводить його як `Article:default`. Або навпаки, посилання на `Product:default` генерує шлях `/product`, посилання на стандартну `Home:default` генерує шлях `/`. Маска може описувати не тільки відносний шлях, що базується на корені сайту, а й абсолютний шлях, якщо він починається зі слеша, або навіть увесь абсолютний URL, якщо він починається з двох слешів: @@ -160,7 +160,7 @@ $router->addRoute('//[.]example.com//', /* ... */); ```php $router->addRoute( '[[-]/][page-]', - 'Homepage:default', + 'Home:default', ); // URL-адреси, що приймаються: @@ -183,16 +183,16 @@ $router->addRoute('[!.html]', /* ... */); Необов'язкові параметри (тобто параметри, що мають значення за замовчуванням) без квадратних дужок поводяться так, як якщо б вони були обгорнуті таким чином: ```php -$router->addRoute('//', /* ... */); +$router->addRoute('//', /* ... */); // дорівнює: -$router->addRoute('[/[/[]]]', /* ... */); +$router->addRoute('[/[/[]]]', /* ... */); ``` -Щоб змінити спосіб генерації самої правої косої риски, тобто замість `/homepage/` отримати `/homepage`, налаштуйте маршрут таким чином: +Щоб змінити спосіб генерації самої правої косої риски, тобто замість `/home/` отримати `/home`, налаштуйте маршрут таким чином: ```php -$router->addRoute('[[/[/]]]', /* ... */); +$router->addRoute('[[/[/]]]', /* ... */); ``` @@ -220,7 +220,7 @@ $router->addRoute('//www.%sld%.%tld%/%basePath%//addRoute('/[/]', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', ]); ``` @@ -232,7 +232,7 @@ use Nette\Routing\Route; $router->addRoute('/[/]', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', ], 'action' => [ Route::Value => 'default', @@ -252,7 +252,7 @@ $router->addRoute('/[/]', [ Доброю практикою є написання вихідного коду англійською мовою, але що якщо вам потрібно, щоб URL вашого сайту було перекладено іншою мовою? ```php -$router->addRoute('/', 'Homepage:default'); +$router->addRoute('/', 'Home:default'); ``` буде генерувати англійські URL, такі як `/product/123` або `/cart`. Якщо ми хочемо, щоб презентери та дії в URL були перекладені німецькою мовою (наприклад, `/produkt/123` або `/einkaufswagen`), ми можемо використовувати словник перекладів. Щоб додати його, нам уже потрібен "більш зрозумілий" варіант другого параметра: @@ -262,7 +262,7 @@ use Nette\Routing\Route; $router->addRoute('/', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterTable => [ // строка в URL => ведущий 'produkt' => 'Product', @@ -290,7 +290,7 @@ use Nette\Routing\Route; $router->addRoute('//', [ 'presenter' => [ - Route::Value => 'Homepage', + Route::Value => 'Home', Route::FilterIn => function (string $s): string { /* ... */ }, Route::FilterOut => function (string $s): string { /* ... */ }, ], @@ -313,7 +313,7 @@ $router->addRoute('//', [ use Nette\Routing\Route; $router->addRoute('/', [ - 'presenter' => 'Homepage', + 'presenter' => 'Home', 'action' => 'default', null => [ Route::FilterIn => function (array $params): array { /* ... */ }, @@ -503,15 +503,15 @@ http://example.com/?presenter=Product&action=detail&id=123 Параметром конструктора `SimpleRouter` є презентер і дія за замовчуванням, тобто дія, яку буде виконано, якщо ми відкриємо, наприклад, `http://example.com/` без додаткових параметрів. ```php -// використовуємо презентер 'Homepage' і дію 'default' -$router = new Nette\Application\Routers\SimpleRouter('Homepage:default'); +// використовуємо презентер 'Home' і дію 'default' +$router = new Nette\Application\Routers\SimpleRouter('Home:default'); ``` Ми рекомендуємо визначати SimpleRouter безпосередньо в [конфігурації |dependency-injection:services]: ```neon services: - - Nette\Application\Routers\SimpleRouter('Homepage:default') + - Nette\Application\Routers\SimpleRouter('Home:default') ``` @@ -611,7 +611,7 @@ class MyRouter implements Nette\Routing\Router ```php [ - 'presenter' => 'Front:Homepage', + 'presenter' => 'Front:Home', 'action' => 'default', ] ``` diff --git a/application/uk/templates.texy b/application/uk/templates.texy index b24a5fdb6a..2dc5f8f053 100644 --- a/application/uk/templates.texy +++ b/application/uk/templates.texy @@ -42,7 +42,9 @@ Nette використовує систему шаблонів [Latte |latte:]. - `templates//.latte` - `templates/..latte` -Якщо шаблон не знайдено, відповіддю буде [помилка 404 |presenters#error-404-etc]. +Якщо шаблон не буде знайдено, він спробує виконати пошук у каталозі `templates` на один рівень вище, тобто на тому ж рівні, що і каталог з класом presenter. + +Якщо шаблон не буде знайдено і там, у відповідь буде видано [помилку 404 |presenters#Error 404 etc.]. Ви також можете змінити вигляд за допомогою `$this->setView('jineView')`. Або, замість прямого пошуку, вкажіть ім'я файлу шаблону за допомогою `$this->template->setFile('/path/to/template.latte')`. @@ -148,7 +150,7 @@ public function renderDefault(): void Атрибут `n:href` дуже зручний для HTML-тегів. ``. Якщо ми хочемо вказати посилання в іншому місці, наприклад, у тексті, ми використовуємо `{link}`: ```latte -Adresa je: {link Homepage:default} +Adresa je: {link Home:default} ``` Додаткові відомості див. у розділі [Створення посилань URL |creating-links]. diff --git a/assistant/cs/@home.texy b/assistant/cs/@home.texy new file mode 100644 index 0000000000..75b9540392 --- /dev/null +++ b/assistant/cs/@home.texy @@ -0,0 +1,4 @@ +Assistant +********* + +Chystaný nástroj pro scaffold, rychlé zkrácení doby vývoje automatickým generováním potřebných souborů a konfigurace aplikace. diff --git a/assistant/en/@home.texy b/assistant/en/@home.texy new file mode 100644 index 0000000000..98c325dd39 --- /dev/null +++ b/assistant/en/@home.texy @@ -0,0 +1,4 @@ +Assistant +********* + +Upcoming tool for scaffold, quickly reduce development time by automatically generating the necessary files and configuring the application. diff --git a/pla/meta.json b/assistant/meta.json similarity index 100% rename from pla/meta.json rename to assistant/meta.json diff --git a/best-practices/bg/@home.texy b/best-practices/bg/@home.texy index 57e7205ddf..c3d00d8610 100644 --- a/best-practices/bg/@home.texy +++ b/best-practices/bg/@home.texy @@ -26,6 +26,7 @@ --------- - [Повторна употреба на формуляри |form-reuse] - [Формуляр за създаване и редактиране на запис |creating-editing-form] +- [Да създадем формуляр за контакт |lets-create-contact-form] - [Зависими полета за избор |https://blog.nette.org/bg/zavisimi-selektirasi-poleta-elegantno-v-nette-i-cist-js] diff --git a/best-practices/bg/composer.texy b/best-practices/bg/composer.texy index 1d9cd82c26..b8a26ebb20 100644 --- a/best-practices/bg/composer.texy +++ b/best-practices/bg/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Актуализиране до най-новата версия .[#toc-update-to-the-latest-version] -======================================================================= +Актуализиране на пакетите до най-новите версии .[#toc-update-packages-to-the-latest-versions] +============================================================================================= За да обновите всички използвани пакети до най-новата версия в съответствие с ограниченията на версиите, определени в `composer.json`, използвайте командата `composer update`. Например зависимостта `"nette/database": "^3.0"` ще инсталира най-новата версия 3.x.x, но не и версия 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project Вместо `name-of-the-project` посочете името на директорията на проекта и изпълнете командата. Composer ще изтегли хранилището `nette/web-project` от GitHub, което вече съдържа файла `composer.json`, и веднага след това ще инсталира самата рамка Nette. Остава само да [проверите разрешенията за запис на |nette:troubleshooting#Setting-Directory-Permissions] директориите `temp/` и `log/`, и сте готови да започнете работа. +Ако знаете на каква версия на PHP ще бъде хостван проектът, не забравяйте да [я настро |#PHP Version]ите. + Версия на PHP .[#toc-php-version] ================================= -Composer винаги инсталира версии на пакети, които са съвместими с версията на PHP, която използвате в момента. Разбира се, това може да не е същата версия на PHP, която е инсталирана на вашия хост. Затова е добре да добавите информация за версията на PHP на хоста към `composer.json`, така че да бъдат инсталирани само съвместимите версии на пакетите: +Composer винаги инсталира версиите на пакетите, които са съвместими с версията на PHP, която използвате в момента (или по-скоро с версията на PHP, използвана в командния ред при стартиране на Composer). Вероятно това не е същата версия, която използва вашият уеб хост. Ето защо е много важно да добавите информация за версията на PHP на вашия хостинг във файла `composer.json`. След това ще бъдат инсталирани само версии на пакети, съвместими с хоста. + +Например, за да настроите проекта да работи с PHP 8.2.3, използвайте командата: + +```shell +composer config platform.php 8.2.3 +``` + +По този начин версията се записва във файла `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP версия сервера + "php": "8.2.3" } } } ``` +Номерът на версията на PHP обаче е посочен и на друго място във файла, в раздела `require`. Докато първият номер определя версията, за която ще бъдат инсталирани пакетите, вторият номер казва за каква версия е написано самото приложение. +(Разбира се, няма смисъл тези версии да се различават, така че двойното вписване е излишно.) Тази версия се задава с командата: + +```shell +composer require php 8.2.3 --no-update +``` + +Или директно във файла `composer.json`: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Фалшиви доклади .[#toc-false-reports] +===================================== + +При надграждане на пакети или промяна на номера на версиите се случват конфликти. Един пакет има изисквания, които са в конфликт с друг и т.н. Понякога обаче Composer отпечатва фалшиви съобщения. Той съобщава за конфликт, който в действителност не съществува. В този случай помага да изтриете файла `composer.lock` и да опитате отново. + +Ако съобщението за грешка продължава, то е мислено сериозно и трябва да прочетете от него какво и как да промените. + Packagist.org - глобално хранилище .[#toc-packagist-org-global-repository] ========================================================================== diff --git a/best-practices/bg/dynamic-snippets.texy b/best-practices/bg/dynamic-snippets.texy index 60c15669d8..b927d707e4 100644 --- a/best-practices/bg/dynamic-snippets.texy +++ b/best-practices/bg/dynamic-snippets.texy @@ -51,7 +51,7 @@ Ajaxisation .[#toc-ajaxization]

{$article->title}

{$article->content}
- {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked}
Мне нравится {else} diff --git a/best-practices/bg/editors-and-tools.texy b/best-practices/bg/editors-and-tools.texy index d39f58b782..86ee4a91f8 100644 --- a/best-practices/bg/editors-and-tools.texy +++ b/best-practices/bg/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan е инструмент, който открива логически г Инсталирайте го чрез Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: И след това го оставете да анализира класовете в папката `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/bg/form-reuse.texy b/best-practices/bg/form-reuse.texy index 8d7b9c98e2..9470a63875 100644 --- a/best-practices/bg/form-reuse.texy +++ b/best-practices/bg/form-reuse.texy @@ -2,62 +2,217 @@ ************************************************** .[perex] -Как да използваме един и същ формуляр на няколко места и да не дублираме кода? Това е много лесно да се направи в Nette и можете да избирате от няколко начина. +В Nette имате няколко възможности за повторно използване на една и съща форма на няколко места, без да дублирате код. В тази статия ще разгледаме различните решения, включително и тези, които трябва да избягвате. Фабрика за формуляри .[#toc-form-factory] ========================================= -Нека създадем клас, който може да създава формуляр. Такъв клас се нарича фабрика. Когато искаме да използваме формуляра (например в презентатор), задаваме заявка към [фабриката като зависимост |dependency-injection:passing-dependencies]. +Един от основните подходи за използване на един и същ компонент на няколко места е да се създаде метод или клас, който генерира компонента, и след това да се извика този метод на различни места в приложението. Такъв метод или клас се нарича *фабрика*. Моля, не бъркайте с шаблона за проектиране *фабричен метод*, който описва специфичен начин за използване на фабрики и не е свързан с тази тема. -Фабричната част е кодът, който предава данните за по-нататъшна обработка, когато формулярът е успешно изпратен. Обикновено към слоя на модела. Освен това проверява дали всичко е било успешно и [предава |forms:validation#Processing-errors] всички грешки [обратно към |forms:validation#Processing-errors] формата. Моделът в следващия пример е представен от класа `Facade`: +Като пример, нека създадем фабрика, която ще изгради форма за редактиране: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // тук се добавят допълнителни полета за формуляри + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Сега можете да използвате тази фабрика на различни места в приложението си, например в презентатори или компоненти. Това става, като [я заявяваме като зависимост |dependency-injection:passing-dependencies]. Затова първо ще запишем класа в конфигурационния файл: + +```neon +services: + - FormFactory +``` + +И след това ще го използваме в презентатора: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // обработка на изпратени данни + }; + return $form; + } +} +``` + +Можете да разширите фабриката за формуляри с допълнителни методи, за да създадете други видове формуляри, подходящи за вашето приложение. И, разбира се, можете да добавите метод, който създава основна форма без елементи, която другите методи ще използват: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // тук се добавят допълнителни полета за формуляри + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // добавляем елементи към формата +Методът `createForm()` все още не прави нищо полезно, но това бързо ще се промени. - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Зависимости на фабриката .[#toc-factory-dependencies] +===================================================== + +С течение на времето ще стане ясно, че е необходимо формулярите да бъдат многоезични. Това означава, че трябва да настроим [преводач за |forms:rendering#Translating] всички форми. За да направим това, модифицираме класа `FormFactory`, за да приеме обекта `Translator` като зависимост в конструктора и да го предаде на формата: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Тъй като методът `createForm()` се извиква и от други методи, които създават конкретни форми, трябва да зададем преводача само в този метод. И сме готови. Не е необходимо да променяме какъвто и да е код на презентатора или компонента, което е чудесно. + + +Още фабрични класове .[#toc-more-factory-classes] +================================================= + +Като алтернатива можете да създадете няколко класа за всяка форма, която искате да използвате в приложението си. +Този подход може да увеличи четимостта на кода и да улесни управлението на формулярите. Оставете оригиналния `FormFactory` за създаване само на чиста форма с основна конфигурация (например с поддръжка на превод) и създайте нова фабрика `EditFormFactory` за формата за редактиране. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // обработка на формата - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ използване на състава +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // тук се добавят допълнителни полета на формуляра + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Разбира се, фабриката може да бъде параметрична, т.е. може да приема параметри, които да влияят на външния вид на създаваната форма. +Много е важно обвързването между класовете `FormFactory` и `EditFormFactory` да се реализира чрез композиция, а не чрез наследяване на обекти: -Сега ще демонстрираме предаването на фабриката на водещия. Първо го записваме в конфигурационен файл: - -```neon -services: - - EditFormFactory +```php +// ⛔ НЕ! НАСЛЕДСТВОТО НЕ ПРИНАДЛЕЖИ ТУК +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // тук се добавят допълнителни полета на формуляра + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -И след това го изискваме в презентатора. Последната стъпка при обработката на изпратения формуляр е пренасочването към следващата страница: +Използването на наследяване в този случай би било напълно непродуктивно. Много бързо ще се сблъскате с проблеми. Например, ако искате да добавите параметри към метода `create()`; PHP ще отчете грешка, че сигнатурата му е различна от тази на родителя. +Или при предаване на зависимост на класа `EditFormFactory` чрез конструктора. Това би довело до това, което наричаме " [ад на конструкторите" |dependency-injection:passing-dependencies#Constructor hell]. + +Като цяло е по-добре да се предпочита композицията пред наследяването. + + +Обработка на формуляри .[#toc-form-handling] +============================================ + +Обработчикът на формуляри, който се извиква след успешно изпращане, може също да бъде част от фабричен клас. Той ще работи, като предава изпратените данни на модела за обработка. Той ще предаде всички грешки [обратно към |forms:validation#Processing Errors] формата. Моделът в следващия пример е представен от класа `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // тук се добавят допълнителни полета за формуляри + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // обработка на подадените данни + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Нека водещият сам се справи с пренасочването. Той ще добави друг обработващ към събитието `onSuccess`, който ще извърши пренасочването. Това ще позволи формулярът да се използва в различни презентатори, като всеки от тях може да пренасочва към различно място. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Тъй като пренасочването се обработва от обработчика на презентатора, компонентът може да се използва на няколко места и да се пренасочва към различно място на всяко място. +Това решение се възползва от свойството на формите, че когато се извика `addError()` на форма или неин елемент, не се извиква следващият обработващ `onSuccess`. + +Наследяване от класа Form .[#toc-inheriting-from-the-form-class] +================================================================ -Компонент с форма .[#toc-component-with-form] -============================================= +Вградената форма не трябва да бъде дете на форма. С други думи, не използвайте това решение: -Друг начин е да създадете нов [компонент |application:components], който съдържа формуляр. Това ни дава възможност да визуализираме формуляра по определен начин, ако компонентът включва шаблон. -Или можем да използваме сигнали, за да комуникираме с AJAX и да зареждаме информация във формуляра, например за автоматично попълване и т.н. +```php +// ⛔ НЕ! НАСЛЕДСТВОТО НЕ ПРИНАДЛЕЖИ ТУК +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // тук се добавят допълнителни полета на формуляра + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Вместо да изграждате формата в конструктора, използвайте фабриката. + +Важно е да осъзнаете, че класът `Form` е преди всичко инструмент за сглобяване на формуляр, т.е. конструктор на формуляри. А сглобената форма може да се счита за негов продукт. Продуктът обаче не е специфичен случай на конструктора; между тях няма *има* връзка, която е в основата на наследяването. + + +Компонент на формата .[#toc-form-component] +=========================================== + +Съвсем различен подход е да създадете [компонент |application:components], който включва формуляр. Това дава нови възможности, например да визуализирате формуляра по определен начин, тъй като компонентът включва шаблон. +Или пък могат да се използват сигнали за AJAX комуникация и зареждане на информация във формата, например за подсказване и т.н. ```php @@ -105,34 +284,32 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // добавляем елементи към формата - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // тук се добавят допълнителни полета за формуляри + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // обработка на формата - $this->facade->process($values); + // обработка на подадените данни + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); return; } - // вызов события - $this->onSave($this, $values); + // извикване на събитие + $this->onSave($this, $data); } } ``` -След това ще създадем фабрика, която ще произвежда този компонент. Просто [напишете неговия интерфейс |application:components#Components with Dependencies]: - +Нека да създадем фабрика, която ще произвежда този компонент. Достатъчно е да [напишем нейния интерфейс |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,14 +318,14 @@ interface EditControlFactory } ``` -И го добавете в конфигурационния файл: +И да го добавим към конфигурационния файл: ```neon services: - EditControlFactory ``` -Сега можем да заявим фабриката и да я използваме в презентатора: +И сега можем да поискаме фабриката и да я използваме в презентатора: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -164,7 +341,7 @@ class MyPresenter extends Nette\Application\UI\Presenter $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // или перенаправить на результат редактирования, например: + // или пренасочете към резултата от редактирането, например: // $this->redirect('detail', ['id' => $data->id]); }; @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Най-добри практики}} diff --git a/best-practices/bg/inject-method-attribute.texy b/best-practices/bg/inject-method-attribute.texy index e58745cbff..fa3e46916a 100644 --- a/best-practices/bg/inject-method-attribute.texy +++ b/best-practices/bg/inject-method-attribute.texy @@ -2,13 +2,20 @@ ******************************** .[perex] -С помощта на конкретни примери ще разгледаме възможностите за предаване на зависимости на презентатори и ще обясним методите и атрибутите/анотациите на `inject`. +В тази статия ще се съсредоточим върху различните начини за предаване на зависимости на презентатори в рамката Nette. Ще сравним предпочитания метод, който е конструкторът, с други възможности, като например методи и атрибути на `inject`. + +И за презентаторите предаването на зависимости чрез [конструктора |dependency-injection:passing-dependencies#Constructor Injection] е предпочитаният начин. +Въпреки това, ако създадете общ предшественик, от който другите презентатори наследяват (например BasePresenter), и този предшественик също има зависимости, възниква проблем, който наричаме [конструкторски ад |dependency-injection:passing-dependencies#Constructor hell]. +Той може да бъде заобиколен с помощта на алтернативни методи, които включват инжектиране на методи и атрибути (анотации). `inject*()` Методи .[#toc-inject-methods] ========================================= -В presenter, както и във всеки друг код, предпочитаният начин за предаване на зависимости е чрез използване на [конструктор |dependency-injection:passing-dependencies#Constructor Injection]. Въпреки това, ако presenter наследява от общ предшественик (например `BasePresenter`), по-добре е да се използват методите на `inject*()` в този предшественик. Това е специален случай на setter, при който методът започва с префикс `inject`. Това е така, защото запазваме конструктора свободен за потомците: +Това е форма на предаване на зависимости с помощта на [задаващи елементи |dependency-injection:passing-dependencies#Setter Injection]. Имената на тези задаващи елементи започват с префикса inject. +Nette DI автоматично извиква тези методи веднага след създаването на инстанцията на презентатора и им предава всички необходими зависимости. Следователно те трябва да бъдат декларирани като публични. + +`inject*()` Методите могат да се разглеждат като вид разширение на конструктора в множество методи. Благодарение на това `BasePresenter` може да приема зависимости чрез друг метод и да остави конструктора свободен за своите наследници: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Основната разлика от setter е, че Nette DI автоматично извиква методите с тези имена в preenters веднага след създаването на инстанцията, като им предава всички необходими зависимости. Презентаторът може да съдържа няколко метода `inject*()`, като всеки метод може да има произволен брой параметри. - -Изпълнението чрез конструктор не се препоръчва за общи предци, тъй като по време на унаследяването трябва да получите зависимостите на всички родителски предци и да ги предадете на `parent::__construct()`: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // това е усложнение - $this->bar = $bar; - } -} -``` - -Методите `inject*()` са полезни и когато главният файл [се състои от функции |presenter-traits] и всяка от тях изисква своя собствена зависимост. +Презентаторът може да съдържа произволен брой методи `inject*()` и всеки от тях може да има произволен брой параметри. Това е чудесно и за случаите, когато презентаторът е [съставен от черти |presenter-traits] и всяка от тях изисква своя собствена зависимост. -Може да се използва и анотацията `@inject`, но е важно да се помни, че капсулирането е нарушено. +`Inject` Атрибути .[#toc-inject-attributes] +=========================================== -Анотации `Inject` .[#toc-inject-annotations] -============================================ - -В този случай свойството е анотирано като `@inject` в коментара към документацията. Типът може да бъде анотиран и в коментара на документацията, ако използвате PHP под версия 7.4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Това е форма на [инжектиране в свойствата |dependency-injection:passing-dependencies#Property Injection]. Достатъчно е да посочите кои свойства трябва да бъдат инжектирани и Nette DI автоматично предава зависимостите веднага след създаването на инстанцията на презентатора. За да ги вмъкнете, е необходимо да ги декларирате като публични. -От версия PHP 8.0 свойството може да бъде маркирано с атрибута `Inject`: +Свойствата се маркират с атрибут: (преди се използваше анотацията `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // този ред е важен class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Отново, Nette DI автоматично ще предава зависимости на свойствата, анотирани по този начин в презентатора, веднага щом инстанцията бъде създадена. +Предимството на този метод за предаване на зависимостите е, че той е много икономичен за записване. С въвеждането на [промоцията на свойствата на конструктора |https://blog.nette.org/bg/php-8-0-p-len-pregled-na-novostite#toc-constructor-property-promotion] обаче използването на конструктора изглежда по-лесно. -Този метод има същите недостатъци като предаването на зависимости към публично свойство. Използва се в презентаторите, защото не усложнява кода и изисква минимално въвеждане. +От друга страна, този метод страда от същите недостатъци като предаването на зависимости в свойства като цяло: нямаме контрол върху промените в променливата, а в същото време променливата става част от публичния интерфейс на класа, което е нежелателно. {{sitename: Най-добри практики}} diff --git a/best-practices/bg/lets-create-contact-form.texy b/best-practices/bg/lets-create-contact-form.texy new file mode 100644 index 0000000000..b0f3d7ccf6 --- /dev/null +++ b/best-practices/bg/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Да създадем формуляр за контакт +******************************* + +.[perex] +Нека разгледаме как да създадем формуляр за контакт в Nette, включително изпращането му на имейл. Така че нека го направим! + +Първо трябва да създадем нов проект. Както е обяснено в страницата " [Започване" |nette:installation]. А след това можем да започнем създаването на формуляра. + +Най-лесният начин е да създадете [формуляра директно в Presenter |forms:in-presenter]. Можем да използваме предварително създадената страница `HomePresenter`. Ще добавим компонента `contactForm`, представляващ формата. Ще направим това, като напишем фабричния метод на `createComponentContactForm()` в кода, който ще създава компонента: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Както можете да видите, създадохме два метода. Първият метод `createComponentContactForm()` създава нова форма. Тя има полета за име, имейл и съобщение, които добавяме с помощта на методите `addText()`, `addEmail()` и `addTextArea()`. Добавихме и бутон за изпращане на формуляра. +Но какво ще стане, ако потребителят не попълни някои полета? В такъв случай трябва да го уведомим, че това е задължително поле. Направихме това с метода `setRequired()`. +Накрая добавихме и [събитие |nette:glossary#events] `onSuccess`, което се задейства, ако формулярът е изпратен успешно. В нашия случай то извиква метода `contactFormSucceeded`, който се грижи за обработката на изпратения формуляр. След малко ще добавим това към кода. + +Нека компонентът `contantForm` бъде визуализиран в шаблона `templates/Home/default.latte`: + +```latte +{block content} +

Contant Form

+{control contactForm} +``` + +За да изпратим самия имейл, създаваме нов клас, наречен `ContactFacade`, и го поставяме във файла `app/Model/ContactFacade.php`: + +```php +addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +Методът `sendMessage()` ще създаде и изпрати имейла. За целта той използва така наречения mailer, който предава като зависимост чрез конструктора. Прочетете повече за [изпращането на имейли |mail:]. + +Сега ще се върнем към презентатора и ще завършим метода `contactFormSucceeded()`. Той извиква метода `sendMessage()` на класа `ContactFacade` и му предава данните от формуляра. А как ще получим обекта `ContactFacade`? Той ще ни бъде предаден от конструктора: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +След като имейлът бъде изпратен, показваме на потребителя т.нар. [флаш съобщение |application:components#flash-messages], с което потвърждаваме, че съобщението е изпратено, и след това пренасочваме към следващата страница, така че формулярът да не може да бъде изпратен отново с помощта на *refresh* в браузъра. + + +Е, ако всичко е наред, би трябвало да можете да изпратите имейл от формуляра си за контакт. Поздравления! + + +HTML шаблон за електронна поща .[#toc-html-email-template] +---------------------------------------------------------- + +Засега се изпраща имейл с обикновен текст, съдържащ само съобщението, изпратено от формуляра. Но можем да използваме HTML в имейла и да го направим по-привлекателен. Ще създадем шаблон за него в Latte, който ще запазим в `app/Model/contactEmail.latte`: + +```latte + + Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Остава да модифицираме `ContactFacade`, за да използваме този шаблон. В конструктора заявяваме класа `LatteFactory`, който може да създаде обекта `Latte\Engine`, [рендер на шаблона Latte |latte:develop#how-to-render-a-template]. Използваме метода `renderToString()`, за да визуализираме шаблона във файл, като първият параметър е пътят до шаблона, а вторият - променливите. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +След това предаваме генерирания HTML имейл на метода `setHtmlBody()` вместо на оригиналния `setBody()`. Също така не е необходимо да посочваме темата на имейла в `setSubject()`, защото библиотеката я взема от елемента `` в шаблона. + + +Конфигуриране на .[#toc-configuring] +------------------------------------ + +В кода на класа `ContactFacade` нашият имейл адрес на администратора `admin@example.com` все още е твърдо кодиран. По-добре би било да го преместите в конфигурационния файл. Как да го направим? + +Първо, модифицираме класа `ContactFacade` и заменяме низът за имейл с променлива, предадена от конструктора: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +Втората стъпка е да въведем стойността на тази променлива в конфигурацията. Във файла `app/config/services.neon` добавяме: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +И това е всичко. Ако в раздела `services` има много елементи и ви се струва, че имейлът се губи сред тях, можем да го направим променлива. Ще променим записа на: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +И ще дефинираме тази променлива във файла `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +И готово! + + +{{sitename: Най-добри практики}} diff --git a/best-practices/bg/pagination.texy b/best-practices/bg/pagination.texy index 0ce839379c..be7883dffc 100644 --- a/best-practices/bg/pagination.texy +++ b/best-practices/bg/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Следващата стъпка е да редактирате водещия. Ще предадем номера на текущо показваната страница на метода `render`. В случай че този номер не е част от URL адреса, трябва да зададем стойност по подразбиране за първата страница. -Също така разширяваме метода `render`, за да получим инстанцията Paginator, да я конфигурираме и да изберем желаните статии, които да се показват в шаблона. HomepagePresenter ще изглежда по следния начин: +Също така разширяваме метода `render`, за да получим инстанцията Paginator, да я конфигурираме и да изберем желаните статии, които да се показват в шаблона. HomePresenter ще изглежда по следния начин: ```php namespace App\Presenters; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/bg/restore-request.texy b/best-practices/bg/restore-request.texy index 3735c08f41..d65a20ca9e 100644 --- a/best-practices/bg/restore-request.texy +++ b/best-practices/bg/restore-request.texy @@ -31,9 +31,11 @@ class AdminPresenter extends Nette\Application\UI\Presenter ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/cs/@home.texy b/best-practices/cs/@home.texy index b51c897898..5466d6d7fc 100644 --- a/best-practices/cs/@home.texy +++ b/best-practices/cs/@home.texy @@ -26,6 +26,7 @@ Formuláře --------- - [Znovupoužití formulářů |form-reuse] - [Formulář pro vytvoření i editaci záznamu |creating-editing-form] +- [Vytváříme kontaktní formulář |lets-create-contact-form] - [Závislé selectboxy |https://blog.nette.org/cs/zavisle-selectboxy-elegantne-v-nette-a-cistem-javascriptu] </div> diff --git a/best-practices/cs/composer.texy b/best-practices/cs/composer.texy index 12ba37407d..2f0c503870 100644 --- a/best-practices/cs/composer.texy +++ b/best-practices/cs/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Aktualizace na nejnovější verze -=============================== +Aktualizace balíčků na nejnovější verze +======================================= Aktualizaci použiváných knihoven na nejnovější verze podle podmínek definovaných v `composer.json` má na starosti příkaz `composer update`. Např. u závislosti `"nette/database": "^3.0"` nainstaluje nejnovější verzi 3.x.x, ale nikoliv už verzi 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project nazev-projektu Jako `nazev-projektu` vložte název adresáře pro svůj projekt a potvrďte. Composer stáhne repozitář `nette/web-project` z GitHubu, který už obsahuje soubor `composer.json`, a hned potom Nette Framework. Mělo by již stačit pouze [nastavit oprávnění |nette:troubleshooting#nastaveni-prav-adresaru] na zápis do složek `temp/` a `log/` a projekt by měl ožít. +Pokud víte, na jaké verzi bude PHP projekt hostován, nezapomeňte [ji nastavit |#Verze PHP]. + Verze PHP ========= -Composer vždy instaluje ty verze balíčků, které jsou kompatibilní s verzí PHP, kterou právě používáte. Což ovšem nemusí být stejná verze, jako používá váš hosting. Proto je užitečné si do souboru `composer.json` přidat informaci o verzi PHP na hostingu a poté se budou instalovat pouze verze balíčků s hostingem kompatibilní: +Composer vždy instaluje ty verze balíčků, které jsou kompatibilní s verzí PHP, kterou právě používáte (lépe řečeno s verzí PHP používanou v příkazové řádce při spouštění Composeru). Což ale nejspíš není stejná verze, jakou používá váš hosting. Proto je velmi důležité si do souboru `composer.json` přidat informaci o verzi PHP na hostingu. Poté se budou instalovat pouze verze balíčků s hostingem kompatibilní. + +To, že projekt poběží například na PHP 8.2.3, nastavíme příkazem: + +```shell +composer config platform.php 8.2.3 +``` + +Takto se verze zapíše do souboru `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # verze PHP na hostingu + "php": "8.2.3" } } } ``` +Nicméně číslo verze PHP se uvádí ještě na jiném místě souboru, a to v sekci `require`. Zatímco první číslo určuje, pro jakou verzi se budou instalovat balíčky, druhé číslo říká, pro jakou verzi je napsaná samotná aplikace. +A podle něj například PhpStorm nastavuje *PHP language level*. (Samozřejmě nedává smysl, aby se tyto verze lišily, takže dvojí zápis je nedomyšlenost.) Tuto verzi nastavíte příkazem: + +```shell +composer require php 8.2.3 --no-update +``` + +Nebo přímo v souboru `composer.json`: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Planá hlášení +============= + +Při upgradu balíčků nebo změnách čísel verzí se stává, že dojde ke konfliktu. Jeden balíček má požadavky, které jsou v rozporu s jiným a podobně. Composer ale občas vypisuje plané hlášení. Hlásí konflikt, který reálně neexistuje. V takovém případě pomůže smazat soubor `composer.lock` a zkusit to znovu. + +Pokud chybová hláška přetrvává, pak je myšlena vážně a je potřeba z ní vyčíst, co a jak upravit. + Packagist.org - centrální repozitář =================================== diff --git a/best-practices/cs/dynamic-snippets.texy b/best-practices/cs/dynamic-snippets.texy index 348c68cf94..600dc4b9bd 100644 --- a/best-practices/cs/dynamic-snippets.texy +++ b/best-practices/cs/dynamic-snippets.texy @@ -51,7 +51,7 @@ Dynamický snippet znamená v terminologii Latte specifický případ užití ma <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>to se mi líbí</a> {else} diff --git a/best-practices/cs/editors-and-tools.texy b/best-practices/cs/editors-and-tools.texy index 6fc1f2a67e..e65c322423 100644 --- a/best-practices/cs/editors-and-tools.texy +++ b/best-practices/cs/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan je nástroj, který odhalí logické chyby v kódu dřív, než jej spus Nainstalujeme jej pomocí Composeru: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: A následně jej necháme zanalyzovat třídy ve složce `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/cs/form-reuse.texy b/best-practices/cs/form-reuse.texy index 549a5c33e1..c659b8bcb4 100644 --- a/best-practices/cs/form-reuse.texy +++ b/best-practices/cs/form-reuse.texy @@ -2,43 +2,208 @@ Znovupoužití formulářů na více místech ************************************** .[perex] -Jak použít stejný formulář na více místech a neduplikovat kód? To je v Nette opravdu snadné a máte na výběr víc způsobů. +V Nette máte k dispozici několik možností, jak použít stejný formulář na více místech a neduplikovat kód. V tomto článku si ukážeme různá řešení, včetně těch, kterým byste se měli vyhnout. -Továrna na formulář -=================== +Továrna na formuláře +==================== -Vytvoříme si třídu, která umí formulář vyrobit. Takové třídě se říká továrna. V místě, kde budeme chtít formulář použít (např. v presenteru), si továrnu [vyžádáme jako závislosti|dependency-injection:passing-dependencies]. +Jedním ze základních přístupů k použití stejné komponenty na více místech je vytvoření metody nebo třídy, která tuto komponentu generuje, a následné volání této metody na různých místech aplikace. Takové metodě nebo třídě se říká *továrna*. Nezaměňujte prosím s návrhovým vzorem *factory method*, který popisuje specifický způsob využití továren a s tímto tématem nesouvisí. -Součástí továrny je i kód, který po úspěšném odeslaní formuláře předá data k dalšímu zpracování. Obvykle do modelové vrstvy. Zároveň zkontroluje, zda vše proběhlo v pořádku, a případné chyby [předá zpět |forms:validation#Chyby při zpracování] do formuláře. Model v následujícím příkladu reprezentuje třída `Facade`: +Jako příklad si vytvoříme továrnu, která bude sestavovat editační formulář: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Titulek:'); + // zde se přidávají další formulářová pole + $form->addSubmit('send', 'Odeslat'); + return $form; + } +} +``` + +Nyní můžete použít tuto továrnu na různých místech ve vaší aplikaci, například v presenterech nebo komponentách. A to tak, že si ji [vyžádáme jako závislost|dependency-injection:passing-dependencies]. Nejprve tedy třídu zapíšeme do konfiguračního souboru: + +```neon +services: + - FormFactory +``` + +A poté ji použijeme v presenteru: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, + ) { + } + + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // zpracování odeslaných dat + }; + return $form; + } +} +``` + +Formulářovou továrnu můžete rozšířit o další metody pro vytváření dalších druhů formulářů podle potřeby vaší aplikace. A samozřejmě můžeme přidat i metodu, která vytvoří základní formulář bez prvků, a tu budou ostatní metody využívat: + +```php +class FormFactory +{ + public function createForm(): Form + { + $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Titulek:'); + // zde se přidávají další formulářová pole + $form->addSubmit('send', 'Odeslat'); + return $form; + } +} +``` + +Metoda `createForm()` zatím nedělá nic užitečného, ale to se rychle změní. + + +Závislosti továrny +================== + +Časem se ukáže, že potřebujeme, aby formuláře byly multijazyčné. To znamená, že všem formulářům musíme nastavit tzv. [translator|forms:rendering#Překládání]. Za tím účelem upravíme třídu `FormFactory` tak, aby přijímala objekt `Translator` jako závislost v konstruktoru, a předáme jej formuláři: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, ) { } - public function create(/* parametry */): Form + public function createForm(): Form { $form = new Form; + $form->setTranslator($this->translator); + return $form; + } + + // ... +} +``` + +Jelikož metodu `createForm()` volají i ostatní metody tvořící specifické formuláře, stačí translator nastavit jen v ní. A máme hotovo. Není potřeba měnit kód žádného presenteru nebo komponenty, což je skvělé. + + +Více továrních tříd +=================== - // přidáme prvky do formuláře +Alternativně můžete vytvořit více tříd pro každý formulář, který chcete použít ve vaší aplikaci. +Tento přístup může zvýšit čitelnost kódu a usnadnit správu formulářů. Původní `FormFactory` necháme vytvářet jen čistý formulář se základní konfigurací (například s podporou překladů) a pro editační formulář vytvoříme novou továrnu `EditFormFactory`. +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form + { + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} + + +// ✅ použití kompozice +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // zde se přidávají další formulářová pole $form->addSubmit('send', 'Odeslat'); - $form->onSuccess[] = [$this, 'processForm']; + return $form; + } +} +``` +Velmi důležité je, aby vazba mezi třídami `FormFactory` a `EditFormFactory` byla realizována kompozicí, nikoliv objektovou dědičností: + +```php +// ⛔ TAKHLE NE! SEM DĚDIČNOST NEPATŘÍ +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Titulek:'); + // zde se přidávají další formulářová pole + $form->addSubmit('send', 'Odeslat'); return $form; } +} +``` + +Použití dedičnosti by bylo v tomto případě zcela kontraproduktivní. Na problémy byste narazili velmi rychle. Třeba ve chvíli, kdybyste chtěli přidat metodě `create()` parametry; PHP by zahlásilo chybu, že se její signatura liší od rodičovské. +Nebo při předávání závislosti do třídy `EditFormFactory` přes konstruktor. Nastala by situace, které říkáme [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. + +Obecně je lepší dávat přednost kompozici před dědičností. - public function processForm(Form $form, array $values): void + +Obsluha formuláře +================= + +Obsluha formuláře, která se zavolá po úspěšném odeslání, může být také součástí tovární třídy. Bude fungovat tak, že odeslaná data předá modelu ke zpracování. Případné chyby [předá zpět |forms:validation#Chyby při zpracování] do formuláře. Model v následujícím příkladu reprezentuje třída `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Titulek:'); + // zde se přidávají další formulářová pole + $form->addSubmit('send', 'Odeslat'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + + public function processForm(Form $form, array $data): void { try { - // zpracování formuláře - $this->facade->process($values); + // zpracování odeslaných dat + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -47,17 +212,7 @@ class EditFormFactory } ``` -Továrna může být samozřejmě parametrická, tj. může přijímat parametery, které ovlivní podobu vytvářeného formuláře. - -Nyní si ukážeme předání továrny do presenteru. Nejprve ji zapíšeme do konfiguračního souboru: - -```neon -services: - - EditFormFactory -``` - -A poté vyžádáme v presenteru. Tam také následuje další krok zpracování odeslaného formuláře a tím je přesměrování na další stránku: - +Samotné přesměrování ale necháme na presenteru. Ten přidá události `onSuccess` další handler, který přesmerování provede. Díky tomu bude možné formulář použít v různých presenterech a v každém přesměrovat jinam. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,23 +225,47 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Tím, že přesměrování řeší až handler v presenteru, lze komponentu použít na více místech a na každém přesměrovat jinam. +Toto řešení využívá vlastnost formulářů, že když se nad formulářem nebo jeho prvkem zavolá `addError()`, už další handler `onSuccess` se nevolá. + + +Dědění od třídy Form +==================== + +Sestavený formulář nemá být potomkem formuláře. Jinými slovy, nepoužívejte toto řešení: + +```php +// ⛔ TAKHLE NE! SEM DĚDIČNOST NEPATŘÍ +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Titulek:'); + // zde se přidávají další formulářová pole + $form->addSubmit('send', 'Odeslat'); + $form->setTranslator($translator); + } +} +``` + +Místo sestavování formuláře v konstruktoru použijte továrnu. + +Je potřeba si uvědomit, že třída `Form` je v první řadě nástrojem pro sestavení formuláře, tedy *form builder*. A sestavený formulář lze chápat jako její produkt. Jenže produkt není specifickým případem builderu, není mezi nimi vazba *is a* tvořící základ dědičnosti. Komponenta s formulářem ======================= -Další možností je vytvořit novou [komponentu|application:components], jejíž součástí bude formulář. To nám dává možnost například renderovat formulář specifickým způsobem, neboť součástí komponenty je i šablona. +Zcela jiný přístup představuje tvorba [komponenty|application:components], jejíž součástí je formulář. To dává nové možnosti, například renderovat formulář specifickým způsobem, neboť součástí komponenty je i šablona. Nebo lze využít signály pro AJAXovou komunikaci a donačítání informací do formuláře, například pro napovídání, atd. @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // přidáme prvky do formuláře - + $form->addText('title', 'Titulek:'); + // zde se přidávají další formulářová pole $form->addSubmit('send', 'Odeslat'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // zpracování formuláře - $this->facade->process($values); + // zpracování odeslaných dat + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,14 +304,13 @@ class EditControl extends Nette\Application\UI\Control } // vyvolání události - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` Ještě vytvoříme továrnu, která bude tuto komponentu vyrábět. Stačí [zapsat její rozhraní|application:components#Komponenty se závislostmi]: - ```php interface EditControlFactory { @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Best Practices}} diff --git a/best-practices/cs/inject-method-attribute.texy b/best-practices/cs/inject-method-attribute.texy index eae3c153ca..9bd667a09f 100644 --- a/best-practices/cs/inject-method-attribute.texy +++ b/best-practices/cs/inject-method-attribute.texy @@ -2,13 +2,20 @@ Metody a atributy inject ************************ .[perex] -Na konkrétních případech si přiblížíme možnosti předávání závislostí do presenterů a vysvětlíme si metody a atributy/anotace `inject`. +V tomto článku se zaměříme na různé způsoby předávání závislostí do presenterů v Nette frameworku. Porovnáme preferovaný způsob, kterým je konstruktor, s dalšími možnostmi, jako jsou metody a atributy `inject`. + +I pro presentery platí, že předání závislostí pomocí [konstruktoru |dependency-injection:passing-dependencies#Předávání konstruktorem] je preferovaná cesta. +Pokud ale vytváříte společného předka, od kterého dědí ostatní presentery (např. `BasePresenter`), a tento předek má také závislosti, nastane problém, kterému říkáme [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. +Ten lze obejít pomocí alternativních cest, které představují metody a atributy (anotace) `inject`. Metody `inject*()` ================== -V presenterech, stejně jako v každém jiném kódu, je preferovaný způsob předávání závislostí pomocí [konstruktoru |dependency-injection:passing-dependencies#Předávání konstruktorem]. Pokud však presenter dědí od společného předka (např. `BasePresenter`), je lepší v tomto předkovi použít metody `inject*()`. Jedná se o zvláštní případ setteru, kdy metoda začíná prefixem `inject`. Jeho použitím si totiž ponecháme konstruktor volný pro potomky: +Jde o formu předávání závislosti [setterem |dependency-injection:passing-dependencies#Předávání setterem]. Název těchto setterů začíná předponou `inject`. +Nette DI takto pojmenované metody automaticky zavolá hned po vytvoření instance presenteru a předá jim všechny požadované závislosti. Musí být tudíž deklarované jako public. + +Metody `inject*()` lze považovat za jakési rozšíření konstruktoru do více metod. Díky tomu může `BasePresenter` převzít závislosti přes jinou metodu a ponechat konstruktor volný pro své potomky: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Základní rozdíl od setteru je ten, že Nette DI takto pojmenované metody v presenterech automaticky volá hned po vytvoření instance a předá jim všechny požadované závislosti. Metod `inject*()` může presenter obsahovat více a každá může mít libovolný počet parametrů. - -Pokud bychom závislosti předávali předkům skrze jejich konstruktory, museli bychom ve všech potomcích získávat i jejich závislosti a předávat je do `parent::__construct()`, což komplikuje kód: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // tohle je komplikace - $this->bar = $bar; - } -} -``` - -Metody `inject*()` se hodí také v případech, kdy je presenter [složen z trait |presenter-traits] a každá z nich si vyžádá vlastní závislost. +Metod `inject*()` může presenter obsahovat libovolný počet a každá může mít libovolný počet parametrů. Skvěle se hodí také v případech, kdy je presenter [složen z trait |presenter-traits] a každá z nich si žádá vlastní závislost. -Je také možné použít anotaci `@inject`, je však třeba mít na paměti, že dojde k porušení zapouzdření. +Atributy `Inject` +================= -Anotace `inject` -================ - -Jedná se o automatické předávání závislosti do veřejné členské proměnné presenteru, která je označená anotací `@inject` v dokumentačním komentáři. Typ závislosti je možné uvést také v dokumentačním komentáři, pokud používáte PHP nižší než 7.4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Jde o formu [injektování do property |dependency-injection:passing-dependencies#Nastavením proměnné]. Stačí označit, do kterých proměnných se má injektovat, a Nette DI automaticky předá závislosti hned po vytvoření instance presenteru. Aby je mohl vložit, je nutné je deklarovat jako public. -Od PHP 8.0 lze proměnnou označit pomocí atributu `Inject`: +Properites označíme atributem: (dříve se používala anotace `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // tento řádek je důležitý class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Nette DI opět takto anotovaným proměnným v presenteru automaticky předá závislosti hned po vytvoření instance. +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. -Tento způsob má stejné nedostatky, jako předávání závislosti do veřejné proměnné. V presenterech se používá proto, že nekomplikuje kód a vyžaduje jen minimum psaní. +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í. {{sitename: Best Practices}} diff --git a/best-practices/cs/lets-create-contact-form.texy b/best-practices/cs/lets-create-contact-form.texy new file mode 100644 index 0000000000..78f2f92886 --- /dev/null +++ b/best-practices/cs/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Vytváříme kontaktní formulář +**************************** + +.[perex] +Podíváme se na to, jak v Nette vytvořit kontaktní formulář včetně odesílání na email. Tak tedy do toho! + +Nejprve musíme vytvořit nový projekt. Jak na to vysvětluje stránka [Začínáme |nette:installation]. A pak už můžeme začít s tvorbou formuláře. + +Nejjednodušší je vytvoření [formuláře přímo v presenteru |forms:in-presenter]. Můžeme využít předpřipravený `HomePresenter`. Do něj přidáme komponentu `contactForm` představující formulář. Uděláme to tak, že do kódu zapíšeme tovární metodu `createComponentContactForm()`, která komponentu vyrobí: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Jméno:') + ->setRequired('Zadejte jméno'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Zadejte e-mail'); + $form->addTextarea('message', 'Zpráva:') + ->setRequired('Zadejte zprávu'); + $form->addSubmit('send', 'Odeslat'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // odeslání emailu + } +} +``` + +Jak vidíte, vytvořili jsme dvě metody. První metoda `createComponentContactForm()` vytváří nový formulář. Ten má políčka pro jméno, email a zprávu, která přidáváme metodami `addText()`, `addEmail()` a `addTextArea()`. Také jsme přidali tlačítko pro odeslání formuláře. +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`: + +```latte +{block content} +<h1>Kontantní formulář</h1> +{control contactForm} +``` + +Pro samotné odeslání emailu vytvoříme novou třídu, kterou nazveme `ContactFacade` a umístíme ji do souboru `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // váš email + ->setFrom($email, $name) + ->setSubject('Zpráva z kontaktního formuláře') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +Metoda `sendMessage()` vytvoří a odešle email. Využívá k tomu tzv. mailer, který si nechá předat jako závislost přes konstruktor. Přečtete si více o [odesílání emailů |mail:]. + +Nyní se vrátíme zpátky k presenteru a dokončíme metodu `contactFormSucceeded()`. Ta zavolá metodu `sendMessage()` třídy `ContactFacade` a předá jí údaje z formuláře. A jak získáme objekt `ContactFacade`? Necháme si jej předat konstruktorem: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('Zpráva byla odeslána'); + $this->redirect('this'); + } +} +``` + +Poté, co se email odešle, ještě zobrazíme uživateli tzv. [flash message |application:components#flash-zpravy], potvrzující, že zpráva se odeslala, a poté přesměrujeme na další stránku, aby nebylo možné formulář opakovaně odeslat pomocí *refresh* v prohlížeči. + + +Tak, a pokud všechno funguje, měli byste být schopni odeslat email z vašeho kontaktního formuláře. Gratuluji! + + +HTML šablona emailu +------------------- + +Zatím se odesílá prostý textový email obsahující pouze zprávu odeslanou formulářem. V emailu ale můžeme využít HTML a udělat jeho podobu atraktivnější. Vytvoříme pro něj šablonu v Latte, kterou zapíšeme do `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Zpráva z kontaktního formuláře + + +

Jméno: {$name}

+

E-mail: {$email}

+

Zpráva: {$message}

+ + +``` + +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é. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // váš email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Vygenerovaný HTML email pak předáme metodě `setHtmlBody()` místo původní `setBody()`. Taktéž nemusíme uvádět předmět emailu v `setSubject()`, protože si jej knihovna vezme z elementu `` šablony. + + +Konfigurace +----------- + +V kódu třídy `ContactFacade` je pořád natvrdo zapsaný náš administrátorský email `admin@example.com`. Bylo by lepší jej přesunout do konfiguračního souboru. Jak na to? + +Nejprve upravíme třídu `ContactFacade` a řetězec s emailem nahradíme proměnnou předanou konstruktorem: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +A druhým krokem je uvedení hodnoty této proměnné v konfiguraci. Do souboru `app/config/services.neon` zapíšeme: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +A je to. Pokud by položek v sekci `services` bylo hodně a měli byste pocit, že email se mezi nimi ztrácí, můžeme z něj udělat proměnnou. Upravíme zápis na: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +A v souboru `app/config/common.neon` nadefinujeme tuto proměnnou: + +```neon +parameters: + adminEmail: admin@example.com +``` + +A je hotovo! + + +{{sitename: Best Practices}} diff --git a/best-practices/cs/pagination.texy b/best-practices/cs/pagination.texy index b93dda4860..46a00cd256 100644 --- a/best-practices/cs/pagination.texy +++ b/best-practices/cs/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Následně se pustíme do úprav presenteru. Do render metody budeme předávat číslo aktuálně zobrazené stránky. Pro případ, kdy nebude toto číslo součástí URL, nastavíme výchozí hodnotu první stránky. -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ě. HomepagePresenter bude po úpravách vypadat takto: +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/cs/restore-request.texy b/best-practices/cs/restore-request.texy index b8e2a18238..1a3562ccea 100644 --- a/best-practices/cs/restore-request.texy +++ b/best-practices/cs/restore-request.texy @@ -31,9 +31,11 @@ Presenter `SignPresenter` bude krom formuláře pro přihlášení obsahovat i p ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/de/@home.texy b/best-practices/de/@home.texy index 55f416f42a..9a057ba919 100644 --- a/best-practices/de/@home.texy +++ b/best-practices/de/@home.texy @@ -26,6 +26,7 @@ Formulare --------- - [Wiederverwendung von Formularen |form-reuse] - [Formular zum Erstellen und Bearbeiten von Datensätzen |creating-editing-form] +- [Erstellen wir ein Kontakt-Formular |lets-create-contact-form] - [Abhängige Selectboxen |https://blog.nette.org/de/abhaengige-selectboxen-elegant-in-nette-und-purem-js] </div> diff --git a/best-practices/de/composer.texy b/best-practices/de/composer.texy index b7dfaf1032..eaaa86a842 100644 --- a/best-practices/de/composer.texy +++ b/best-practices/de/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Update auf die neueste Version .[#toc-update-to-the-latest-version] -=================================================================== +Pakete auf die neuesten Versionen aktualisieren .[#toc-update-packages-to-the-latest-versions] +============================================================================================== Um alle verwendeten Pakete auf die neueste Version gemäß den in `composer.json` definierten Versionsbeschränkungen zu aktualisieren, verwenden Sie den Befehl `composer update`. Zum Beispiel wird für die Abhängigkeit `"nette/database": "^3.0"` die neueste Version 3.x.x installiert, aber nicht Version 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project Anstelle von `name-of-the-project` sollten Sie den Namen des Verzeichnisses für Ihr Projekt angeben und den Befehl ausführen. Composer wird das Repository `nette/web-project` von GitHub holen, das bereits die Datei `composer.json` enthält, und gleich danach das Nette Framework selbst installieren. Nun müssen nur noch die [Schreibrechte |nette:troubleshooting#setting-directory-permissions] für die Verzeichnisse `temp/` und `log/` [überprüft |nette:troubleshooting#setting-directory-permissions] werden und schon kann es losgehen. +Wenn Sie wissen, mit welcher PHP-Version das Projekt gehostet werden soll, sollten Sie [diese |#PHP Version] unbedingt einrichten. + PHP-Version .[#toc-php-version] =============================== -Composer installiert immer die Versionen der Pakete, die mit der PHP-Version kompatibel sind, die Sie gerade verwenden. Das kann natürlich nicht die gleiche Version sein wie die von PHP auf Ihrem Webhost. Daher ist es sinnvoll, der Datei `composer.json` Informationen über die PHP-Version auf dem Host hinzuzufügen, damit nur die mit dem Host kompatiblen Versionen der Pakete installiert werden: +Composer installiert immer die Versionen der Pakete, die mit der PHP-Version kompatibel sind, die Sie gerade verwenden (oder besser gesagt, die PHP-Version, die auf der Kommandozeile verwendet wird, wenn Sie Composer starten). Das ist wahrscheinlich nicht die Version, die Ihr Webhost verwendet. Deshalb ist es sehr wichtig, dass Sie Informationen über die PHP-Version Ihres Hosts in Ihre Datei `composer.json` aufnehmen. Danach werden nur noch die Versionen der Pakete installiert, die mit dem Host kompatibel sind. + +Um zum Beispiel das Projekt auf PHP 8.2.3 einzustellen, verwenden Sie den Befehl: + +```shell +composer config platform.php 8.2.3 +``` + +So wird die Version in die Datei `composer.json` geschrieben: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +Die PHP-Versionsnummer wird jedoch auch an anderer Stelle in der Datei aufgeführt, im Abschnitt `require`. Während die erste Zahl die Version angibt, für die Pakete installiert werden, sagt die zweite Zahl, für welche Version die Anwendung selbst geschrieben wurde. +(Natürlich macht es keinen Sinn, dass diese Versionen unterschiedlich sind, daher ist die doppelte Angabe eine Redundanz). Sie setzen diese Version mit dem Befehl: + +```shell +composer require php 8.2.3 --no-update +``` + +Oder direkt in der Datei "Composer.json": + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +False Berichte .[#toc-false-reports] +==================================== + +Bei der Aktualisierung von Paketen oder der Änderung von Versionsnummern kommt es zu Konflikten. Ein Paket hat Anforderungen, die mit einem anderen in Konflikt stehen, und so weiter. Composer gibt jedoch gelegentlich eine falsche Meldung aus. Er meldet einen Konflikt, der nicht wirklich existiert. In diesem Fall hilft es, die Datei `composer.lock` zu löschen und es erneut zu versuchen. + +Bleibt die Fehlermeldung bestehen, dann ist sie ernst gemeint und Sie müssen ihr entnehmen, was Sie wie ändern müssen. + Packagist.org - Globales Repository .[#toc-packagist-org-global-repository] =========================================================================== diff --git a/best-practices/de/dynamic-snippets.texy b/best-practices/de/dynamic-snippets.texy index 253cf8ff12..c75affd1e4 100644 --- a/best-practices/de/dynamic-snippets.texy +++ b/best-practices/de/dynamic-snippets.texy @@ -51,7 +51,7 @@ In der Latte-Terminologie ist ein dynamisches Snippet ein spezieller Anwendungsf <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/de/editors-and-tools.texy b/best-practices/de/editors-and-tools.texy index b72808e4b1..46d986e163 100644 --- a/best-practices/de/editors-and-tools.texy +++ b/best-practices/de/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan ist ein Werkzeug, das logische Fehler in Ihrem Code aufspürt, bevor Sie Installieren Sie es über Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: Und lassen Sie dann die Klassen im Ordner `app/` analysieren: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/de/form-reuse.texy b/best-practices/de/form-reuse.texy index f0a134919e..08cc17d64f 100644 --- a/best-practices/de/form-reuse.texy +++ b/best-practices/de/form-reuse.texy @@ -2,62 +2,217 @@ Formulare an mehreren Stellen wiederverwenden ********************************************* .[perex] -Wie kann man ein und dasselbe Formular an mehreren Stellen wiederverwenden, ohne den Code zu duplizieren? Das ist in Nette wirklich einfach und Sie haben mehrere Möglichkeiten zur Auswahl. +In Nette haben Sie mehrere Möglichkeiten, dasselbe Formular an mehreren Stellen wiederzuverwenden, ohne Code zu duplizieren. In diesem Artikel gehen wir auf die verschiedenen Lösungen ein, einschließlich derer, die Sie vermeiden sollten. Formular-Fabrik .[#toc-form-factory] ==================================== -Lassen Sie uns eine Klasse erstellen, die ein Formular erstellen kann. Eine solche Klasse wird als Fabrik bezeichnet. An der Stelle, an der wir das Formular verwenden wollen (z. B. im Presenter), fordern wir die [Factory als Abhängigkeit |dependency-injection:passing-dependencies] an. +Ein grundlegender Ansatz für die Verwendung derselben Komponente an mehreren Stellen besteht darin, eine Methode oder Klasse zu erstellen, die die Komponente erzeugt, und diese Methode dann an verschiedenen Stellen in der Anwendung aufzurufen. Eine solche Methode oder Klasse wird als *Factory* bezeichnet. Bitte nicht mit dem Entwurfsmuster *Fabrikmethode* verwechseln, das eine spezielle Art der Verwendung von Fabriken beschreibt und nicht mit diesem Thema zusammenhängt. -Teil der Fabrik ist der Code, der die Daten zur weiteren Verarbeitung weitergibt, wenn das Formular erfolgreich abgeschickt wurde. Normalerweise an die Modellschicht. Sie prüft auch, ob alles gut gelaufen ist, und gibt eventuelle Fehler an das Formular [zurück |forms:validation#Processing-errors]. Das Modell im folgenden Beispiel wird durch die Klasse `Facade` repräsentiert: +Lassen Sie uns als Beispiel eine Fabrik erstellen, die ein Bearbeitungsformular erstellt: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // zusätzliche Formularfelder werden hier hinzugefügt + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Jetzt können Sie diese Fabrik an verschiedenen Stellen in Ihrer Anwendung verwenden, zum Beispiel in Presentern oder Komponenten. Und wir tun dies, indem wir [sie als Abhängigkeit anfordern |dependency-injection:passing-dependencies]. Zuerst schreiben wir also die Klasse in die Konfigurationsdatei: + +```neon +services: + - FormFactory +``` + +Und dann verwenden wir sie im Präsentator: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // Verarbeitung der gesendeten Daten + }; + return $form; + } +} +``` + +Sie können die Formularfabrik mit zusätzlichen Methoden erweitern, um andere Arten von Formularen für Ihre Anwendung zu erstellen. Und natürlich können Sie auch eine Methode hinzufügen, die ein Basisformular ohne Elemente erstellt, das die anderen Methoden verwenden werden: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // zusätzliche Formularfelder werden hier hinzugefügt + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // Elemente zum Formular hinzufügen +Die Methode `createForm()` tut noch nichts Nützliches, aber das wird sich schnell ändern. - $form->addSubmit('send', 'Absenden'); - $form->onSuccess[] = [$this, 'processForm']; +Abhängigkeiten von der Fabrik .[#toc-factory-dependencies] +========================================================== + +Mit der Zeit wird sich herausstellen, dass die Formulare mehrsprachig sein müssen. Das bedeutet, dass wir einen [Übersetzer |forms:rendering#Translating] für alle Formulare einrichten müssen. Zu diesem Zweck ändern wir die Klasse `FormFactory` so, dass sie das Objekt `Translator` als Abhängigkeit im Konstruktor akzeptiert und an das Formular übergibt: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Da die Methode `createForm()` auch von anderen Methoden aufgerufen wird, die bestimmte Formulare erstellen, müssen wir den Übersetzer nur in dieser Methode festlegen. Und schon sind wir fertig. Es ist nicht nötig, den Code des Presenters oder der Komponente zu ändern, was großartig ist. + + +Weitere Factory-Klassen .[#toc-more-factory-classes] +==================================================== + +Alternativ können Sie für jedes Formular, das Sie in Ihrer Anwendung verwenden möchten, mehrere Klassen erstellen. +Dieser Ansatz kann die Lesbarkeit des Codes erhöhen und die Verwaltung der Formulare erleichtern. Belassen Sie das Original `FormFactory`, um nur ein reines Formular mit Grundkonfiguration zu erstellen (z. B. mit Übersetzungsunterstützung), und erstellen Sie eine neue Fabrik `EditFormFactory` für das Bearbeitungsformular. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // Verarbeitung des Formulars - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ Verwendung der Zusammensetzung +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // hier werden zusätzliche Formularfelder hinzugefügt + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Natürlich kann die Fabrik parametrisch sein, d.h. sie kann Parameter erhalten, die das Aussehen des zu erstellenden Formulars beeinflussen. +Es ist sehr wichtig, dass die Bindung zwischen den Klassen `FormFactory` und `EditFormFactory` durch Komposition und nicht durch Objektvererbung implementiert wird: -Wir werden nun demonstrieren, wie wir die Fabrik an den Präsentator übergeben. Zuerst schreiben wir sie in die Konfigurationsdatei: - -```neon -services: - - EditFormFactory +```php +// ⛔ NEIN! VERERBUNG GEHÖRT HIER NICHT HIN +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // zusätzliche Formularfelder werden hier hinzugefügt + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -Und fordern sie dann im Presenter an. Dort folgt auch der nächste Schritt der Verarbeitung des eingereichten Formulars und das ist die Routing zur nächsten Seite: +Die Verwendung von Vererbung wäre in diesem Fall völlig kontraproduktiv. Sie würden sehr schnell auf Probleme stoßen. Wenn Sie z.B. der Methode `create()` Parameter hinzufügen wollten, würde PHP einen Fehler melden, dass sich die Signatur der Methode von der des Elternteils unterscheidet. +Oder bei der Übergabe einer Abhängigkeit an die Klasse `EditFormFactory` über den Konstruktor. Dies würde zu dem führen, was wir [Konstruktorhölle |dependency-injection:passing-dependencies#Constructor hell] nennen. + +Im Allgemeinen ist es besser, Komposition der Vererbung vorzuziehen. + + +Handhabung von Formularen .[#toc-form-handling] +=============================================== + +Der Formular-Handler, der nach einer erfolgreichen Übermittlung aufgerufen wird, kann auch Teil einer Fabrikklasse sein. Er arbeitet, indem er die übermittelten Daten zur Verarbeitung an das Modell weitergibt. Eventuelle Fehler werden [an |forms:validation#Processing Errors] das Formular [zurückgegeben |forms:validation#Processing Errors]. Das Modell im folgenden Beispiel wird durch die Klasse `Facade` repräsentiert: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // zusätzliche Formularfelder werden hier hinzugefügt + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // Verarbeitung der übermittelten Daten + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Der Präsentator soll die Umleitung selbst durchführen. Er fügt dem Ereignis `onSuccess` einen weiteren Handler hinzu, der die Umleitung durchführt. Auf diese Weise kann das Formular in verschiedenen Präsentatoren verwendet werden, und jeder kann an eine andere Stelle weiterleiten. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Da die Routing durch den Presenter-Handler erfolgt, kann die Komponente an mehreren Stellen verwendet und an jeder Stelle an eine andere Stelle weitergeleitet werden. +Diese Lösung macht sich die Eigenschaft von Formularen zunutze, dass beim Aufruf von `addError()` auf einem Formular oder seinem Element der nächste `onSuccess` -Handler nicht aufgerufen wird. + +Vererbung von der Formularklasse .[#toc-inheriting-from-the-form-class] +======================================================================= -Komponente mit Formular .[#toc-component-with-form] -=================================================== +Ein erstelltes Formular sollte nicht das Kind eines Formulars sein. Mit anderen Worten: Verwenden Sie diese Lösung nicht: -Eine andere Möglichkeit besteht darin, eine neue [Komponente |application:components] zu erstellen, die ein Formular enthält. Dies gibt uns die Möglichkeit, das Formular auf eine bestimmte Art und Weise zu rendern, z. B. weil die Komponente eine Vorlage enthält. -Oder wir können Signale für die AJAX-Kommunikation und das Laden von Informationen in das Formular verwenden, z. B. für die automatische Vervollständigung usw. +```php +// ⛔ NEIN! VERERBUNG GEHÖRT HIER NICHT HIN +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // zusätzliche Formularfelder werden hier hinzugefügt + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Anstatt das Formular im Konstruktor zu erstellen, verwenden Sie die Fabrik. + +Es ist wichtig zu erkennen, dass die Klasse `Form` in erster Linie ein Werkzeug zum Zusammenstellen eines Formulars ist, d.h. ein Formularersteller. Und das zusammengesetzte Formular kann als ihr Produkt betrachtet werden. Das Produkt ist jedoch kein Sonderfall des Builders; es gibt keine *ist eine* Beziehung zwischen ihnen, die die Grundlage der Vererbung bildet. + + +Formular-Komponente .[#toc-form-component] +========================================== + +Ein völlig anderer Ansatz besteht darin, eine [Komponente |application:components] zu erstellen, die ein Formular enthält. Dadurch ergeben sich neue Möglichkeiten, z. B. um das Formular auf eine bestimmte Art und Weise zu rendern, da die Komponente eine Vorlage enthält. +Oder es können Signale für die AJAX-Kommunikation und das Laden von Informationen in das Formular verwendet werden, z. B. für Hinting usw. ```php @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // Elemente zum Formular hinzufügen - - $form->addSubmit('send', 'Absenden'); + $form->addText('title', 'Title:'); + // zusätzliche Formularfelder werden hier hinzugefügt + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // Verarbeitung des Formulars - $this->facade->process($values); + // Verarbeitung der übermittelten Daten + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,13 +304,12 @@ class EditControl extends Nette\Application\UI\Control } // Ereignisaufruf - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -Als Nächstes erstellen wir die Fabrik, die diese Komponente produzieren wird. [Schreiben |application:components#Components with Dependencies] Sie einfach [seine Schnittstelle |application:components#Components with Dependencies]: - +Lassen Sie uns eine Fabrik erstellen, die diese Komponente produzieren wird. Es genügt, [ihre Schnittstelle |application:components#Components with Dependencies] zu schreiben: ```php interface EditControlFactory @@ -141,7 +318,7 @@ interface EditControlFactory } ``` -Und fügen Sie der Konfigurationsdatei hinzu: +Und fügen Sie sie der Konfigurationsdatei hinzu: ```neon services: @@ -164,7 +341,7 @@ class MyPresenter extends Nette\Application\UI\Presenter $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // oder auf das Ergebnis der Bearbeitung umleiten, z.B.: + // oder auf das Ergebnis der Bearbeitung umleiten, z. B: // $this->redirect('detail', ['id' => $data->id]); }; @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Bewährte Praktiken}} diff --git a/best-practices/de/inject-method-attribute.texy b/best-practices/de/inject-method-attribute.texy index ebe9624311..546eca4b00 100644 --- a/best-practices/de/inject-method-attribute.texy +++ b/best-practices/de/inject-method-attribute.texy @@ -2,13 +2,20 @@ Injektionsmethoden und -attribute ********************************* .[perex] -Anhand konkreter Beispiele werden wir die Möglichkeiten der Übergabe von Abhängigkeiten an Presenter betrachten und die `inject` Methoden und Attribute/Anmerkungen erläutern. +In diesem Artikel werden wir uns auf verschiedene Möglichkeiten konzentrieren, Abhängigkeiten an Presenter im Nette-Framework zu übergeben. Wir werden die bevorzugte Methode, nämlich den Konstruktor, mit anderen Optionen wie `inject` Methoden und Attributen vergleichen. + +Auch für Presenter ist die Übergabe von Abhängigkeiten über den [Konstruktor |dependency-injection:passing-dependencies#Constructor Injection] die bevorzugte Methode. +Wenn Sie jedoch einen gemeinsamen Vorfahren erstellen, von dem andere Präsentatoren erben (z. B. BasePresenter), und dieser Vorfahre ebenfalls Abhängigkeiten hat, tritt ein Problem auf, das wir [Konstruktorhölle |dependency-injection:passing-dependencies#Constructor hell] nennen. +Dieses Problem kann durch alternative Methoden umgangen werden, die Methoden und Attribute (Annotationen) einschließen. `inject*()` Methoden .[#toc-inject-methods] =========================================== -In Presenter, wie in jedem anderen Code auch, ist die bevorzugte Art der Übergabe von Abhängigkeiten die Verwendung von [Konstruktoren |dependency-injection:passing-dependencies#Constructor Injection]. Wenn der Präsentator jedoch von einem gemeinsamen Vorgänger erbt (z. B. `BasePresenter`), ist es besser, die Methoden von `inject*()` in diesem Vorgänger zu verwenden. Es handelt sich um einen Spezialfall eines Setters, bei dem die Methode mit einem Präfix `inject` beginnt. Dies liegt daran, dass wir den Konstruktor für Nachkommen frei halten: +Dies ist eine Form der Übergabe von Abhängigkeiten unter Verwendung von [Settern |dependency-injection:passing-dependencies#Setter Injection]. Die Namen dieser Setzer beginnen mit dem Präfix inject. +Nette DI ruft solche benannten Methoden unmittelbar nach dem Erzeugen der Presenter-Instanz automatisch auf und übergibt ihnen alle erforderlichen Abhängigkeiten. Sie müssen daher als public deklariert werden. + +`inject*()` Methoden können als eine Art Konstruktorerweiterung in mehrere Methoden betrachtet werden. Dadurch kann `BasePresenter` Abhängigkeiten durch eine andere Methode aufnehmen und den Konstruktor für seine Nachkommen frei lassen: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Der grundlegende Unterschied zu einem Setter besteht darin, dass Nette DI die so benannten Methoden in Presentern automatisch aufruft, sobald die Instanz erzeugt wird, und ihnen alle erforderlichen Abhängigkeiten übergibt. Ein Presenter kann mehrere Methoden enthalten `inject*()` und jede Methode kann eine beliebige Anzahl von Parametern haben. - -Würden wir die Abhängigkeiten an die Vorfahren über deren Konstruktoren übergeben, müssten wir ihre Abhängigkeiten in allen Nachkommen abrufen und an `parent::__construct()` übergeben, was den Code verkompliziert: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // dies ist eine Komplikation - $this->bar = $bar; - } -} -``` - -Die Methoden von `inject*()` sind auch in Fällen nützlich, in denen der Präsentator [aus Traits |presenter-traits] besteht und jeder von ihnen seine eigene Abhängigkeit benötigt. +Der Präsentator kann eine beliebige Anzahl von `inject*()` Methoden enthalten, und jede kann eine beliebige Anzahl von Parametern haben. Dies eignet sich auch hervorragend für Fälle, in denen der Presenter [aus Traits |presenter-traits] besteht, von denen jeder seine eigene Abhängigkeit erfordert. -Es ist auch möglich, die Annotation `@inject` zu verwenden, aber es ist wichtig zu bedenken, dass die Kapselung bricht. +`Inject` Attribute .[#toc-inject-attributes] +============================================ -`Inject` Annotationen .[#toc-inject-annotations] -================================================ - -Dies ist eine automatische Übergabe der Abhängigkeit an die öffentliche Member-Variable des Präsentators, die im Dokumentationskommentar mit `@inject` kommentiert ist. Der Typ kann auch im Dokumentationskommentar angegeben werden, wenn Sie PHP kleiner als 7.4 verwenden. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Dies ist eine Form der [Injektion in Eigenschaften |dependency-injection:passing-dependencies#Property Injection]. Es genügt, anzugeben, welche Eigenschaften injiziert werden sollen, und Nette DI übergibt die Abhängigkeiten automatisch sofort nach der Erstellung der Presenter-Instanz. Um sie einzufügen, ist es notwendig, sie als öffentlich zu deklarieren. -Seit PHP 8.0 kann eine Eigenschaft mit einem Attribut `Inject` gekennzeichnet werden: +Eigenschaften werden mit einem Attribut gekennzeichnet: (früher wurde die Annotation `/** @inject */` verwendet) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // diese Zeile ist wichtig class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Auch hier wird Nette DI automatisch Abhängigkeiten zu Eigenschaften, die auf diese Weise im Presenter annotiert sind, übergeben, sobald die Instanz erstellt wird. +Der Vorteil dieser Methode der Übergabe von Abhängigkeiten war die sehr sparsame Notation. Mit der Einführung der [Förderung von Konstruktoreigenschaften |https://blog.nette.org/de/php-8-0-vollstaendiger-ueberblick-ueber-die-neuigkeiten#toc-constructor-property-promotion] scheint die Verwendung des Konstruktors jedoch einfacher zu sein. -Diese Methode hat die gleichen Mängel wie die Übergabe von Abhängigkeiten an eine öffentliche Eigenschaft. Sie wird im Presenter verwendet, weil sie den Code nicht verkompliziert und nur ein Minimum an Tipparbeit erfordert. +Andererseits leidet diese Methode unter denselben Mängeln wie die Übergabe von Abhängigkeiten in Eigenschaften im Allgemeinen: Wir haben keine Kontrolle über Änderungen an der Variablen, und gleichzeitig wird die Variable Teil der öffentlichen Schnittstelle der Klasse, was unerwünscht ist. {{sitename: Bewährte Praktiken}} diff --git a/best-practices/de/lets-create-contact-form.texy b/best-practices/de/lets-create-contact-form.texy new file mode 100644 index 0000000000..67b26710b2 --- /dev/null +++ b/best-practices/de/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Erstellen wir ein Kontakt-Formular +********************************** + +.[perex] +Schauen wir uns an, wie man in Nette ein Kontaktformular erstellt und es an eine E-Mail sendet. Also, los geht's! + +Zuerst müssen wir ein neues Projekt erstellen. Wie auf der Seite " [Erste Schritte" |nette:installation] erklärt wird. Und dann können wir mit der Erstellung des Formulars beginnen. + +Der einfachste Weg ist, das [Formular direkt in Presenter |forms:in-presenter] zu erstellen. Wir können die vorgefertigten `HomePresenter` verwenden. Wir werden die Komponente `contactForm` hinzufügen, die das Formular darstellt. Dies geschieht, indem wir die `createComponentContactForm()` Factory-Methode in den Code schreiben, der die Komponente erzeugt: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Wie Sie sehen können, haben wir zwei Methoden erstellt. Die erste Methode `createComponentContactForm()` erstellt ein neues Formular. Dieses hat Felder für Name, E-Mail und Nachricht, die wir mit den Methoden `addText()`, `addEmail()` und `addTextArea()` hinzufügen. Wir haben auch eine Schaltfläche zum Absenden des Formulars hinzugefügt. +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: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Um die E-Mail selbst zu versenden, erstellen wir eine neue Klasse namens `ContactFacade` und platzieren sie in der Datei `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +Die Methode `sendMessage()` wird die E-Mail erstellen und versenden. Sie verwendet dazu einen so genannten Mailer, den sie als Abhängigkeit über den Konstruktor übergibt. Lesen Sie mehr über das [Versenden von E-Mails |mail:]. + +Nun kehren wir zum Presenter zurück und vervollständigen die Methode `contactFormSucceeded()`. Sie ruft die Methode `sendMessage()` der Klasse `ContactFacade` auf und übergibt ihr die Formulardaten. Und wie erhalten wir das `ContactFacade` Objekt? Wir lassen es uns vom Konstruktor übergeben: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +Nachdem die E-Mail versendet wurde, zeigen wir dem Benutzer die so genannte [Flash-Nachricht |application:components#flash-messages] an, die bestätigt, dass die Nachricht versendet wurde, und leiten dann zur nächsten Seite weiter, damit das Formular nicht mit *refresh* im Browser erneut abgeschickt werden kann. + + +Nun, wenn alles funktioniert, sollten Sie in der Lage sein, eine E-Mail über Ihr Kontaktformular zu versenden. Herzlichen Glückwunsch! + + +HTML-E-Mail-Vorlage .[#toc-html-email-template] +----------------------------------------------- + +Im Moment wird eine einfache Text-E-Mail verschickt, die nur die vom Formular gesendete Nachricht enthält. Aber wir können HTML in der E-Mail verwenden und sie attraktiver gestalten. Wir werden dafür eine Vorlage in Latte erstellen, die wir unter `app/Model/contactEmail.latte` speichern werden: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Es bleibt noch `ContactFacade` zu ändern, um diese Vorlage zu verwenden. Im Konstruktor fordern wir die Klasse `LatteFactory` an, die das Objekt `Latte\Engine` erzeugen kann, einen [Latte-Vorlagen-Renderer |latte:develop#how-to-render-a-template]. Wir verwenden die Methode `renderToString()`, um die Vorlage in eine Datei zu rendern; der erste Parameter ist der Pfad zur Vorlage und der zweite die Variablen. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Anschließend übergeben wir die generierte HTML-E-Mail an die Methode `setHtmlBody()` anstelle der ursprünglichen `setBody()`. Wir müssen auch den Betreff der E-Mail in `setSubject()` nicht angeben, da die Bibliothek ihn aus dem Element `` in der Vorlage übernimmt. + + +Konfigurieren von .[#toc-configuring] +------------------------------------- + +Im Code der Klasse `ContactFacade` ist unsere Admin-E-Mail `admin@example.com` noch fest codiert. Es wäre besser, sie in die Konfigurationsdatei zu verschieben. Wie kann man das tun? + +Zunächst ändern wir die Klasse `ContactFacade` und ersetzen den E-Mail-String durch eine Variable, die vom Konstruktor übergeben wird: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +Der zweite Schritt besteht darin, den Wert dieser Variable in die Konfiguration aufzunehmen. In der Datei `app/config/services.neon` fügen wir hinzu: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +Und das war's. Wenn es viele Einträge im Abschnitt `services` gibt und Sie das Gefühl haben, dass die E-Mail unter den Einträgen verloren geht, können wir sie zu einer Variablen machen. Wir ändern den Eintrag in: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +Und definieren diese Variable in der Datei `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +Und schon ist es geschafft! + + +{{sitename: Bewährte Praktiken}} diff --git a/best-practices/de/pagination.texy b/best-practices/de/pagination.texy index 7ab9e78376..5775e52df1 100644 --- a/best-practices/de/pagination.texy +++ b/best-practices/de/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Der nächste Schritt besteht darin, den Präsentator zu bearbeiten. Wir werden die Nummer der aktuell angezeigten Seite an die Render-Methode weiterleiten. Für den Fall, dass diese Nummer nicht Teil der URL ist, müssen wir den Standardwert auf die erste Seite setzen. -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 HomepagePresenter wird wie folgt aussehen: +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/de/restore-request.texy b/best-practices/de/restore-request.texy index 5e16a1efe4..16471c65d1 100644 --- a/best-practices/de/restore-request.texy +++ b/best-practices/de/restore-request.texy @@ -31,9 +31,11 @@ Der Präsentator `SignPresenter` enthält einen persistenten Parameter `$backlin ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/el/@home.texy b/best-practices/el/@home.texy index 76213b98cb..ba364faa14 100644 --- a/best-practices/el/@home.texy +++ b/best-practices/el/@home.texy @@ -26,6 +26,7 @@ ------ - [Επαναχρησιμοποίηση εντύπων |form-reuse] - [Φόρμα για τη δημιουργία και επεξεργασία εγγραφής |creating-editing-form] +- [Ας δημιουργήσουμε μια φόρμα επικοινωνίας |lets-create-contact-form] - [Εξαρτώμενα πλαίσια επιλογής |https://blog.nette.org/el/exartomena-selectboxes-kompsa-se-nette-kai-katharo-js] </div> diff --git a/best-practices/el/composer.texy b/best-practices/el/composer.texy index c8725bbb67..0971df08e3 100644 --- a/best-practices/el/composer.texy +++ b/best-practices/el/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Ενημέρωση στην τελευταία έκδοση .[#toc-update-to-the-latest-version] -==================================================================== +Ενημέρωση πακέτων στις τελευταίες εκδόσεις .[#toc-update-packages-to-the-latest-versions] +========================================================================================= Για να ενημερώσετε όλα τα χρησιμοποιούμενα πακέτα στην τελευταία έκδοση σύμφωνα με τους περιορισμούς έκδοσης που ορίζονται στο `composer.json` χρησιμοποιήστε την εντολή `composer update`. Για παράδειγμα, για την εξάρτηση `"nette/database": "^3.0"` θα εγκαταστήσει την τελευταία έκδοση 3.x.x, αλλά όχι την έκδοση 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project Αντί για το `name-of-the-project` θα πρέπει να δώσετε το όνομα του καταλόγου για το έργο σας και να εκτελέσετε την εντολή. Το Composer θα φέρει το αποθετήριο `nette/web-project` από το GitHub, το οποίο περιέχει ήδη το αρχείο `composer.json`, και αμέσως μετά θα εγκαταστήσει το ίδιο το Nette Framework. Το μόνο που απομένει είναι να [ελέγξετε τα δικαιώματα εγγραφής |nette:troubleshooting#setting-directory-permissions] στους καταλόγους `temp/` και `log/` και είστε έτοιμοι να ξεκινήσετε. +Αν γνωρίζετε σε ποια έκδοση PHP θα φιλοξενηθεί το έργο, φροντίστε να [το ρυθμίσετε |#PHP Version]. + Έκδοση PHP .[#toc-php-version] ============================== -Το Composer εγκαθιστά πάντα τις εκδόσεις των πακέτων που είναι συμβατές με την έκδοση της PHP που χρησιμοποιείτε αυτή τη στιγμή. Η οποία, βέβαια, μπορεί να μην είναι η ίδια έκδοση με την PHP του web host σας. Ως εκ τούτου, είναι χρήσιμο να προσθέσετε πληροφορίες σχετικά με την έκδοση της PHP στον κεντρικό υπολογιστή στο αρχείο `composer.json`, και τότε θα εγκατασταθούν μόνο οι εκδόσεις των πακέτων που είναι συμβατές με τον κεντρικό υπολογιστή: +Το Composer εγκαθιστά πάντα τις εκδόσεις των πακέτων που είναι συμβατές με την έκδοση της PHP που χρησιμοποιείτε αυτή τη στιγμή (ή μάλλον, την έκδοση της PHP που χρησιμοποιείται στη γραμμή εντολών όταν εκτελείτε το Composer). Η οποία πιθανότατα δεν είναι η ίδια έκδοση που χρησιμοποιεί ο web host σας. Γι' αυτό είναι πολύ σημαντικό να προσθέσετε πληροφορίες σχετικά με την έκδοση της PHP στη φιλοξενία σας στο αρχείο `composer.json`. Μετά από αυτό, θα εγκατασταθούν μόνο οι εκδόσεις των πακέτων που είναι συμβατές με τον host. + +Για παράδειγμα, για να ρυθμίσετε το έργο να τρέχει σε PHP 8.2.3, χρησιμοποιήστε την εντολή: + +```shell +composer config platform.php 8.2.3 +``` + +Έτσι γράφεται η έκδοση στο αρχείο `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +Ωστόσο, ο αριθμός έκδοσης της PHP αναφέρεται και σε άλλο σημείο του αρχείου, στην ενότητα `require`. Ενώ ο πρώτος αριθμός καθορίζει την έκδοση για την οποία θα εγκατασταθούν τα πακέτα, ο δεύτερος αριθμός λέει για ποια έκδοση είναι γραμμένη η ίδια η εφαρμογή. +(Φυσικά, δεν έχει νόημα αυτές οι εκδόσεις να είναι διαφορετικές, οπότε η διπλή καταχώρηση είναι πλεονασμός). Ορίζετε αυτή την έκδοση με την εντολή: + +```shell +composer require php 8.2.3 --no-update +``` + +Ή απευθείας στο αρχείο `composer.json`: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Ψευδείς αναφορές .[#toc-false-reports] +====================================== + +Όταν αναβαθμίζετε πακέτα ή αλλάζετε αριθμούς εκδόσεων, συμβαίνουν συγκρούσεις. Ένα πακέτο έχει απαιτήσεις που συγκρούονται με ένα άλλο και ούτω καθεξής. Ωστόσο, το Composer εκτυπώνει περιστασιακά ψευδή μηνύματα. Αναφέρει μια σύγκρουση που στην πραγματικότητα δεν υπάρχει. Σε αυτή την περίπτωση, βοηθάει να διαγράψετε το αρχείο `composer.lock` και να δοκιμάσετε ξανά. + +Αν το μήνυμα σφάλματος επιμένει, τότε εννοείται σοβαρά και πρέπει να διαβάσετε από αυτό τι πρέπει να τροποποιήσετε και πώς. + Packagist.org - Παγκόσμιο αποθετήριο .[#toc-packagist-org-global-repository] ============================================================================ diff --git a/best-practices/el/dynamic-snippets.texy b/best-practices/el/dynamic-snippets.texy index 55bed1e70f..59031461f0 100644 --- a/best-practices/el/dynamic-snippets.texy +++ b/best-practices/el/dynamic-snippets.texy @@ -51,7 +51,7 @@ Ajaxization .[#toc-ajaxization] <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/el/editors-and-tools.texy b/best-practices/el/editors-and-tools.texy index 5d29827e06..3e894b27ea 100644 --- a/best-practices/el/editors-and-tools.texy +++ b/best-practices/el/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan .[#toc-phpstan] Εγκαταστήστε το μέσω του Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: Και στη συνέχεια αφήστε το να αναλύσει τις κλάσεις στο φάκελο `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/el/form-reuse.texy b/best-practices/el/form-reuse.texy index 293df65dd0..ec91ec79e1 100644 --- a/best-practices/el/form-reuse.texy +++ b/best-practices/el/form-reuse.texy @@ -2,62 +2,217 @@ ******************************************** .[perex] -Πώς να επαναχρησιμοποιήσετε την ίδια φόρμα σε πολλαπλά σημεία και να μην αντιγράφετε κώδικα; Αυτό είναι πολύ εύκολο να γίνει στη Nette και έχετε πολλούς τρόπους για να επιλέξετε. +Στη Nette, έχετε αρκετές επιλογές για να επαναχρησιμοποιήσετε την ίδια φόρμα σε πολλά σημεία χωρίς να αντιγράψετε κώδικα. Σε αυτό το άρθρο, θα εξετάσουμε τις διάφορες λύσεις, συμπεριλαμβανομένων αυτών που πρέπει να αποφύγετε. Εργοστάσιο φορμών .[#toc-form-factory] ====================================== -Ας δημιουργήσουμε μια κλάση που μπορεί να δημιουργήσει μια φόρμα. Μια τέτοια κλάση ονομάζεται εργοστάσιο. Στο μέρος όπου θέλουμε να χρησιμοποιήσουμε τη φόρμα (π.χ. στον παρουσιαστή), ζητάμε το [εργοστάσιο ως εξάρτηση |dependency-injection:passing-dependencies]. +Μια βασική προσέγγιση για τη χρήση του ίδιου συστατικού σε πολλά σημεία είναι η δημιουργία μιας μεθόδου ή κλάσης που παράγει το συστατικό και στη συνέχεια η κλήση αυτής της μεθόδου σε διαφορετικά σημεία της εφαρμογής. Μια τέτοια μέθοδος ή κλάση ονομάζεται *factory*. Μην συγχέετε με το πρότυπο σχεδίασης *factory method*, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των εργοστασίων και δεν σχετίζεται με αυτό το θέμα. -Μέρος του εργοστασίου είναι ο κώδικας που περνάει τα δεδομένα για περαιτέρω επεξεργασία όταν η φόρμα υποβληθεί επιτυχώς. Συνήθως στο επίπεδο μοντέλου. Επίσης, ελέγχει αν όλα πήγαν καλά και [περνάει πίσω |forms:validation#Processing-errors] στη φόρμα τυχόν σφάλματα. Το μοντέλο στο ακόλουθο παράδειγμα αντιπροσωπεύεται από την κλάση `Facade`: +Για παράδειγμα, ας δημιουργήσουμε ένα εργοστάσιο που θα δημιουργήσει μια φόρμα επεξεργασίας: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // πρόσθετα πεδία φόρμας προστίθενται εδώ + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Τώρα μπορείτε να χρησιμοποιήσετε αυτό το εργοστάσιο σε διάφορα σημεία της εφαρμογής σας, για παράδειγμα σε παρουσιαστές ή στοιχεία. Και το κάνουμε αυτό [ζητώντας το ως εξάρτηση |dependency-injection:passing-dependencies]. Έτσι, πρώτα, θα γράψουμε την κλάση στο αρχείο ρυθμίσεων: + +```neon +services: + - FormFactory +``` + +Και στη συνέχεια τη χρησιμοποιούμε στον παρουσιαστή: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // επεξεργασία των δεδομένων που αποστέλλονται + }; + return $form; + } +} +``` + +Μπορείτε να επεκτείνετε το εργοστάσιο φορμών με πρόσθετες μεθόδους για να δημιουργήσετε άλλους τύπους φορμών ανάλογα με την εφαρμογή σας. Και, φυσικά, μπορείτε να προσθέσετε μια μέθοδο που δημιουργεί μια βασική φόρμα χωρίς στοιχεία, την οποία θα χρησιμοποιούν οι άλλες μέθοδοι: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } - // προσθήκη στοιχείων στη φόρμα + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // πρόσθετα πεδία φόρμας προστίθενται εδώ + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Η μέθοδος `createForm()` δεν κάνει τίποτα χρήσιμο ακόμα, αλλά αυτό θα αλλάξει γρήγορα. + + +Εξαρτήσεις εργοστασίων .[#toc-factory-dependencies] +=================================================== +Με τον καιρό, θα γίνει προφανές ότι χρειαζόμαστε τις φόρμες να είναι πολύγλωσσες. Αυτό σημαίνει ότι πρέπει να δημιουργήσουμε έναν [μεταφραστή |forms:rendering#Translating] για όλες τις φόρμες. Για να το κάνουμε αυτό, τροποποιούμε την κλάση `FormFactory` ώστε να δέχεται το αντικείμενο `Translator` ως εξάρτηση στον κατασκευαστή και να το περνάει στη φόρμα: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Δεδομένου ότι η μέθοδος `createForm()` καλείται και από άλλες μεθόδους που δημιουργούν συγκεκριμένες φόρμες, χρειάζεται να ορίσουμε τον μεταφραστή μόνο σε αυτή τη μέθοδο. Και τελειώσαμε. Δεν χρειάζεται να αλλάξουμε τον κώδικα του παρουσιαστή ή του συστατικού, πράγμα που είναι υπέροχο. + + +Περισσότερες εργοστασιακές κλάσεις .[#toc-more-factory-classes] +=============================================================== + +Εναλλακτικά, μπορείτε να δημιουργήσετε πολλαπλές κλάσεις για κάθε φόρμα που θέλετε να χρησιμοποιήσετε στην εφαρμογή σας. +Αυτή η προσέγγιση μπορεί να αυξήσει την αναγνωσιμότητα του κώδικα και να διευκολύνει τη διαχείριση των φορμών. Αφήστε το αρχικό `FormFactory` για να δημιουργήσετε μόνο μια καθαρή φόρμα με βασικές ρυθμίσεις (για παράδειγμα, με υποστήριξη μετάφρασης) και δημιουργήστε ένα νέο εργοστάσιο `EditFormFactory` για τη φόρμα επεξεργασίας. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // επεξεργασία φόρμας - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ χρήση της σύνθεσης +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // εδώ προστίθενται πρόσθετα πεδία φόρμας + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Φυσικά, το εργοστάσιο μπορεί να είναι παραμετρικό, δηλαδή μπορεί να λαμβάνει παραμέτρους που θα επηρεάζουν την εμφάνιση της φόρμας που δημιουργείται. +Είναι πολύ σημαντικό ότι η σύνδεση μεταξύ των κλάσεων `FormFactory` και `EditFormFactory` υλοποιείται με σύνθεση και όχι με κληρονομικότητα αντικειμένων: -Θα επιδείξουμε τώρα την παράδοση του εργοστασίου στον παρουσιαστή. Αρχικά, το γράφουμε στο αρχείο ρυθμίσεων: - -```neon -services: - - EditFormFactory +```php +// ⛔ ΟΧΙ! Η ΚΛΗΡΟΝΟΜΙΆ ΔΕΝ ΑΝΉΚΕΙ ΕΔΏ +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // πρόσθετα πεδία φόρμας προστίθενται εδώ + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -Και στη συνέχεια το ζητάμε στον παρουσιαστή. Εκεί ακολουθεί και το επόμενο βήμα επεξεργασίας της υποβληθείσας φόρμας και αυτό είναι η ανακατεύθυνση στην επόμενη σελίδα: +Η χρήση κληρονομικότητας σε αυτή την περίπτωση θα ήταν εντελώς αντιπαραγωγική. Θα αντιμετωπίζατε προβλήματα πολύ γρήγορα. Για παράδειγμα, αν θέλατε να προσθέσετε παραμέτρους στη μέθοδο `create()`, η PHP θα ανέφερε ένα σφάλμα ότι η υπογραφή της ήταν διαφορετική από την υπογραφή του γονέα. +Ή όταν περνούσατε μια εξάρτηση στην κλάση `EditFormFactory` μέσω του κατασκευαστή. Αυτό θα προκαλούσε αυτό που ονομάζουμε [κόλαση του κατασκευαστή |dependency-injection:passing-dependencies#Constructor hell]. +Γενικά, είναι προτιμότερο να προτιμάτε τη σύνθεση από την κληρονομικότητα. + + +Χειρισμός φόρμας .[#toc-form-handling] +====================================== + +Ο χειριστής φόρμας που καλείται μετά από μια επιτυχή υποβολή μπορεί επίσης να είναι μέρος μιας εργοστασιακής κλάσης. Θα λειτουργεί περνώντας τα υποβληθέντα δεδομένα στο μοντέλο για επεξεργασία. Θα μεταβιβάζει τυχόν σφάλματα [πίσω |forms:validation#Processing Errors] στη φόρμα. Το μοντέλο στο ακόλουθο παράδειγμα αντιπροσωπεύεται από την κλάση `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // πρόσθετα πεδία φόρμας προστίθενται εδώ + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + + public function processForm(Form $form, array $data): void + { + try { + // επεξεργασία των υποβληθέντων δεδομένων + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Αφήστε τον παρουσιαστή να χειριστεί ο ίδιος την ανακατεύθυνση. Θα προσθέσει έναν άλλο χειριστή στο συμβάν `onSuccess`, ο οποίος θα εκτελέσει την ανακατεύθυνση. Αυτό θα επιτρέψει τη χρήση της φόρμας σε διαφορετικούς παρουσιαστές και ο καθένας θα μπορεί να κάνει ανακατεύθυνση σε διαφορετική τοποθεσία. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Δεδομένου ότι η ανακατεύθυνση γίνεται από τον χειριστή του παρουσιαστή, το στοιχείο μπορεί να χρησιμοποιηθεί σε πολλά μέρη και να ανακατευθύνεται αλλού σε κάθε μέρος. +Αυτή η λύση εκμεταλλεύεται την ιδιότητα των φορμών ότι, όταν καλείται το `addError()` σε μια φόρμα ή το στοιχείο της, δεν καλείται ο επόμενος χειριστής `onSuccess`. + + +Κληρονομικότητα από την κλάση Form .[#toc-inheriting-from-the-form-class] +========================================================================= +Μια δομημένη φόρμα δεν πρέπει να είναι παιδί μιας φόρμας. Με άλλα λόγια, μην χρησιμοποιείτε αυτή τη λύση: -Εξάρτημα με φόρμα .[#toc-component-with-form] -============================================= +```php +// ⛔ ΟΧΙ! Η ΚΛΗΡΟΝΟΜΙΆ ΔΕΝ ΑΝΉΚΕΙ ΕΔΏ +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // πρόσθετα πεδία φόρμας προστίθενται εδώ + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` -Ένας άλλος τρόπος είναι να δημιουργήσετε ένα νέο [στοιχείο |application:components] που περιέχει μια φόρμα. Αυτό μας δίνει τη δυνατότητα να αποδώσουμε τη φόρμα με συγκεκριμένο τρόπο, για παράδειγμα, αφού το συστατικό περιλαμβάνει ένα πρότυπο. -Ή μπορούμε να χρησιμοποιήσουμε σήματα για επικοινωνία AJAX και φόρτωση πληροφοριών στη φόρμα, για παράδειγμα για αυτόματη συμπλήρωση κ.λπ. +Αντί να δημιουργείτε τη φόρμα στον κατασκευαστή, χρησιμοποιήστε το εργοστάσιο. + +Είναι σημαντικό να συνειδητοποιήσετε ότι η κλάση `Form` είναι πρωτίστως ένα εργαλείο για τη συναρμολόγηση μιας φόρμας, δηλαδή ένας κατασκευαστής φόρμας. Και η συναρμολογημένη φόρμα μπορεί να θεωρηθεί το προϊόν της. Ωστόσο, το προϊόν δεν είναι μια ειδική περίπτωση του κατασκευαστή- δεν υπάρχει *είναι μια* σχέση μεταξύ τους, η οποία αποτελεί τη βάση της κληρονομικότητας. + + +Στοιχείο φόρμας .[#toc-form-component] +====================================== + +Μια εντελώς διαφορετική προσέγγιση είναι η δημιουργία ενός [συστατικού |application:components] που περιλαμβάνει μια φόρμα. Αυτό δίνει νέες δυνατότητες, για παράδειγμα να αποδώσετε τη φόρμα με συγκεκριμένο τρόπο, αφού το συστατικό περιλαμβάνει ένα πρότυπο. +Ή μπορούν να χρησιμοποιηθούν σήματα για επικοινωνία AJAX και φόρτωση πληροφοριών στη φόρμα, για παράδειγμα για υποδείξεις κ.λπ. ```php @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // προσθήκη στοιχείων στη φόρμα - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // πρόσθετα πεδία φόρμας προστίθενται εδώ + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // επεξεργασία φόρμας - $this->facade->process($values); + // επεξεργασία των υποβληθέντων δεδομένων + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,13 +304,12 @@ class EditControl extends Nette\Application\UI\Control } // κλήση συμβάντος - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -Στη συνέχεια, θα δημιουργήσουμε το εργοστάσιο που θα παράγει αυτό το συστατικό. Απλά [γράψτε τη διεπαφή του |application:components#Components with Dependencies]: - +Ας δημιουργήσουμε ένα εργοστάσιο που θα παράγει αυτό το στοιχείο. Αρκεί να [γράψουμε τη διεπαφή του |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,14 +318,14 @@ interface EditControlFactory } ``` -Και προσθέστε το στο αρχείο ρυθμίσεων: +Και να το προσθέσουμε στο αρχείο ρυθμίσεων: ```neon services: - EditControlFactory ``` -Και τώρα μπορούμε να απαιτήσουμε το εργοστάσιο και να το χρησιμοποιήσουμε στον παρουσιαστή: +Και τώρα μπορούμε να ζητήσουμε το εργοστάσιο και να το χρησιμοποιήσουμε στον παρουσιαστή: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -164,7 +341,7 @@ class MyPresenter extends Nette\Application\UI\Presenter $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // ή ανακατευθύνετε στο αποτέλεσμα της επεξεργασίας, π.χ.: + // ή ανακατεύθυνση στο αποτέλεσμα της επεξεργασίας, π.χ.: // $this->redirect('detail', ['id' => $data->id]), }; @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Best Practices}} diff --git a/best-practices/el/inject-method-attribute.texy b/best-practices/el/inject-method-attribute.texy index 2103423c5e..df67326bd8 100644 --- a/best-practices/el/inject-method-attribute.texy +++ b/best-practices/el/inject-method-attribute.texy @@ -2,13 +2,20 @@ ********************************* .[perex] -Χρησιμοποιώντας συγκεκριμένα παραδείγματα, θα εξετάσουμε τις δυνατότητες μετάδοσης εξαρτήσεων στους παρουσιαστές και θα εξηγήσουμε τις μεθόδους και τα χαρακτηριστικά/προσδιορισμούς `inject`. +Σε αυτό το άρθρο, θα επικεντρωθούμε σε διάφορους τρόπους μετάδοσης εξαρτήσεων σε παρουσιαστές στο πλαίσιο Nette. Θα συγκρίνουμε την προτιμώμενη μέθοδο, που είναι ο κατασκευαστής, με άλλες επιλογές, όπως οι μέθοδοι `inject` και τα χαρακτηριστικά. + +Και για τους παρουσιαστές, το πέρασμα εξαρτήσεων με τη χρήση του [κατασκευαστή |dependency-injection:passing-dependencies#Constructor Injection] είναι ο προτιμώμενος τρόπος. +Ωστόσο, αν δημιουργήσετε έναν κοινό πρόγονο από τον οποίο κληρονομούν άλλοι παρουσιαστές (π.χ. BasePresenter) και αυτός ο πρόγονος έχει επίσης εξαρτήσεις, προκύπτει ένα πρόβλημα, το οποίο ονομάζουμε [κόλαση του κατασκευαστή |dependency-injection:passing-dependencies#Constructor hell]. +Αυτό μπορεί να παρακαμφθεί με τη χρήση εναλλακτικών μεθόδων, οι οποίες περιλαμβάνουν μεθόδους inject και χαρακτηριστικά (annotations). `inject*()` Μέθοδοι .[#toc-inject-methods] ========================================== -Στον παρουσιαστή, όπως και σε κάθε άλλο κώδικα, ο προτιμώμενος τρόπος μεταφοράς εξαρτήσεων είναι η χρήση [του κατασκευαστή |dependency-injection:passing-dependencies#Constructor Injection]. Ωστόσο, εάν ο παρουσιαστής κληρονομεί από έναν κοινό πρόγονο (π.χ. `BasePresenter`), είναι προτιμότερο να χρησιμοποιούνται οι μέθοδοι του `inject*()` σε αυτόν τον πρόγονο. Πρόκειται για μια ειδική περίπτωση ενός setter, όπου η μέθοδος αρχίζει με πρόθεμα `inject`. Αυτό συμβαίνει επειδή κρατάμε τον κατασκευαστή ελεύθερο για τους απογόνους: +Πρόκειται για μια μορφή μεταβίβασης εξαρτήσεων με χρήση [ρυθμιστών |dependency-injection:passing-dependencies#Setter Injection]. Τα ονόματα αυτών των ρυθμιστών αρχίζουν με το πρόθεμα inject. +Η Nette DI καλεί αυτόματα τέτοιες ονομαστικές μεθόδους αμέσως μετά τη δημιουργία της περίπτωσης παρουσιαστή και τους μεταβιβάζει όλες τις απαιτούμενες εξαρτήσεις. Επομένως, πρέπει να δηλωθούν ως δημόσιες. + +`inject*()` μέθοδοι μπορούν να θεωρηθούν ως ένα είδος επέκτασης του κατασκευαστή σε πολλαπλές μεθόδους. Χάρη σε αυτό, το `BasePresenter` μπορεί να λάβει εξαρτήσεις μέσω μιας άλλης μεθόδου και να αφήσει τον κατασκευαστή ελεύθερο για τους απογόνους του: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Η βασική διαφορά από έναν setter είναι ότι η Nette DI καλεί αυτόματα μεθόδους που ονομάζονται με αυτόν τον τρόπο στους παρουσιαστές μόλις δημιουργηθεί το instance, περνώντας σε αυτές όλες τις απαιτούμενες εξαρτήσεις. Ένας παρουσιαστής μπορεί να περιέχει πολλές μεθόδους `inject*()` και κάθε μέθοδος μπορεί να έχει οποιονδήποτε αριθμό παραμέτρων. - -Αν περνούσαμε εξαρτήσεις στους προγόνους μέσω των κατασκευαστών τους, θα έπρεπε να πάρουμε τις εξαρτήσεις τους σε όλους τους απογόνους και να τις περάσουμε στο `parent::__construct()`, πράγμα που περιπλέκει τον κώδικα: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // αυτή είναι μια επιπλοκή - $this->bar = $bar; - } -} -``` - -Οι μέθοδοι `inject*()` είναι επίσης χρήσιμες σε περιπτώσεις όπου ο παρουσιαστής [αποτελείται από γνωρίσματα |presenter-traits] και κάθε ένα από αυτά απαιτεί τη δική του εξάρτηση. +Ο παρουσιαστής μπορεί να περιέχει οποιονδήποτε αριθμό μεθόδων `inject*()` και κάθε μία από αυτές μπορεί να έχει οποιονδήποτε αριθμό παραμέτρων. Αυτό είναι επίσης εξαιρετικό για περιπτώσεις όπου ο παρουσιαστής [αποτελείται από γνωρίσματα |presenter-traits] και κάθε ένα από αυτά απαιτεί τη δική του εξάρτηση. -Είναι επίσης δυνατή η χρήση του σχολιασμού `@inject`, αλλά είναι σημαντικό να έχετε κατά νου ότι η ενθυλάκωση διακόπτεται. +`Inject` Χαρακτηριστικά .[#toc-inject-attributes] +================================================= -`Inject` Σημειώσεις .[#toc-inject-annotations] -============================================== - -Πρόκειται για αυτόματη μεταβίβαση της εξάρτησης στη δημόσια μεταβλητή μέλος του παρουσιαστή, η οποία σχολιάζεται με το `@inject` στο σχόλιο τεκμηρίωσης. Ο τύπος μπορεί επίσης να καθοριστεί στο σχόλιο τεκμηρίωσης, αν χρησιμοποιείτε PHP μικρότερη της 7.4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Αυτή είναι μια μορφή [έγχυσης σε ιδιότητες |dependency-injection:passing-dependencies#Property Injection]. Αρκεί να υποδείξετε ποιες ιδιότητες πρέπει να εγχυθούν, και το Nette DI περνάει αυτόματα τις εξαρτήσεις αμέσως μετά τη δημιουργία της παρουσιαζόμενης περίπτωσης. Για την εισαγωγή τους, είναι απαραίτητο να δηλωθούν ως δημόσιες. -Από την PHP 8.0, μια ιδιότητα μπορεί να επισημανθεί με ένα χαρακτηριστικό `Inject`: +Οι ιδιότητες επισημαίνονται με μια ιδιότητα: (προηγουμένως, χρησιμοποιούνταν ο σχολιασμός `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // αυτή η γραμμή είναι σημαντική class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Και πάλι, η Nette DI θα περάσει αυτόματα εξαρτήσεις στις ιδιότητες που έχουν σημειωθεί με αυτόν τον τρόπο στον παρουσιαστή μόλις δημιουργηθεί η περίπτωση. +Το πλεονέκτημα αυτής της μεθόδου μετάδοσης εξαρτήσεων ήταν η πολύ οικονομική μορφή συμβολισμού της. Ωστόσο, με την εισαγωγή της [προώθησης των ιδιοτήτων του κατασκευαστή |https://blog.nette.org/el/php-8-0-pleres-episkopese-ton-neon#toc-constructor-property-promotion], η χρήση του κατασκευαστή φαίνεται ευκολότερη. -Αυτή η μέθοδος έχει τις ίδιες ελλείψεις με τη μετάδοση εξαρτήσεων σε μια δημόσια ιδιότητα. Χρησιμοποιείται στον presenter επειδή δεν περιπλέκει τον κώδικα και απαιτεί ελάχιστη πληκτρολόγηση. +Από την άλλη πλευρά, η μέθοδος αυτή πάσχει από τις ίδιες αδυναμίες με τη μεταβίβαση εξαρτήσεων σε ιδιότητες γενικά: δεν έχουμε κανέναν έλεγχο των αλλαγών στη μεταβλητή και ταυτόχρονα, η μεταβλητή γίνεται μέρος της δημόσιας διεπαφής της κλάσης, πράγμα ανεπιθύμητο. {{sitename: Best Practices}} diff --git a/best-practices/el/lets-create-contact-form.texy b/best-practices/el/lets-create-contact-form.texy new file mode 100644 index 0000000000..aeded4979a --- /dev/null +++ b/best-practices/el/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Ας δημιουργήσουμε μια φόρμα επικοινωνίας +**************************************** + +.[perex] +Ας δούμε πώς μπορείτε να δημιουργήσετε μια φόρμα επικοινωνίας στη Nette, συμπεριλαμβανομένης της αποστολής της σε ένα email. Ας το κάνουμε λοιπόν! + +Πρώτα πρέπει να δημιουργήσουμε ένα νέο έργο. Όπως εξηγεί η σελίδα [Getting Started |nette:installation]. Και στη συνέχεια μπορούμε να ξεκινήσουμε τη δημιουργία της φόρμας. + +Ο ευκολότερος τρόπος είναι να δημιουργήσουμε τη [φόρμα απευθείας στο Presenter |forms:in-presenter]. Μπορούμε να χρησιμοποιήσουμε την προκατασκευασμένη διεύθυνση `HomePresenter`. Θα προσθέσουμε το στοιχείο `contactForm` που αντιπροσωπεύει τη φόρμα. Αυτό το κάνουμε γράφοντας τη μέθοδο `createComponentContactForm()` factory στον κώδικα που θα παράγει το συστατικό: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Όπως μπορείτε να δείτε, έχουμε δημιουργήσει δύο μεθόδους. Η πρώτη μέθοδος `createComponentContactForm()` δημιουργεί μια νέα φόρμα. Αυτή έχει πεδία για το όνομα, το email και το μήνυμα, τα οποία προσθέτουμε χρησιμοποιώντας τις μεθόδους `addText()`, `addEmail()` και `addTextArea()`. Προσθέσαμε επίσης ένα κουμπί για την υποβολή της φόρμας. +Τι γίνεται όμως αν ο χρήστης δεν συμπληρώσει κάποια πεδία; Σε αυτή την περίπτωση, θα πρέπει να τον ενημερώσουμε ότι πρόκειται για υποχρεωτικό πεδίο. Αυτό το κάναμε με τη μέθοδο `setRequired()`. +Τέλος, προσθέσαμε και ένα [συμβάν |nette:glossary#events] `onSuccess`, το οποίο ενεργοποιείται αν η φόρμα υποβληθεί με επιτυχία. Στην περίπτωσή μας, καλεί τη μέθοδο `contactFormSucceeded`, η οποία αναλαμβάνει την επεξεργασία της υποβληθείσας φόρμας. Θα το προσθέσουμε αυτό στον κώδικα σε λίγο. + +Αφήστε το στοιχείο `contantForm` να αποδοθεί στο πρότυπο `templates/Home/default.latte`: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Για να στείλουμε το ίδιο το email, δημιουργούμε μια νέα κλάση με όνομα `ContactFacade` και την τοποθετούμε στο αρχείο `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +Η μέθοδος `sendMessage()` θα δημιουργήσει και θα στείλει το email. Χρησιμοποιεί έναν λεγόμενο mailer για να το κάνει αυτό, τον οποίο περνάει ως εξάρτηση μέσω του κατασκευαστή. Διαβάστε περισσότερα σχετικά με την [αποστολή μηνυμάτων ηλεκτρονικού ταχυδρομείου |mail:]. + +Τώρα, θα επιστρέψουμε στον παρουσιαστή και θα ολοκληρώσουμε τη μέθοδο `contactFormSucceeded()`. Καλεί τη μέθοδο `sendMessage()` της κλάσης `ContactFacade` και της περνάει τα δεδομένα της φόρμας. Και πώς παίρνουμε το αντικείμενο `ContactFacade`; Θα μας το περάσει ο κατασκευαστής: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +Μετά την αποστολή του ηλεκτρονικού ταχυδρομείου, εμφανίζουμε στο χρήστη το λεγόμενο [flash message |application:components#flash-messages], επιβεβαιώνοντας ότι το μήνυμα έχει σταλεί, και στη συνέχεια ανακατευθύνουμε στην επόμενη σελίδα, έτσι ώστε να μην μπορεί να ξαναστείλει τη φόρμα χρησιμοποιώντας *refresh* στο πρόγραμμα περιήγησης. + + +Λοιπόν, αν όλα λειτουργούν, θα πρέπει να είστε σε θέση να στείλετε ένα μήνυμα ηλεκτρονικού ταχυδρομείου από τη φόρμα επικοινωνίας σας. Συγχαρητήρια! + + +Πρότυπο ηλεκτρονικού ταχυδρομείου HTML .[#toc-html-email-template] +------------------------------------------------------------------ + +Προς το παρόν, αποστέλλεται ένα email απλού κειμένου που περιέχει μόνο το μήνυμα που αποστέλλεται από τη φόρμα. Μπορούμε όμως να χρησιμοποιήσουμε HTML στο email και να το κάνουμε πιο ελκυστικό. Θα δημιουργήσουμε ένα πρότυπο για αυτό στο Latte, το οποίο θα αποθηκεύσουμε στο `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Μένει να τροποποιήσουμε το `ContactFacade` για να χρησιμοποιήσουμε αυτό το πρότυπο. Στον κατασκευαστή, ζητάμε την κλάση `LatteFactory`, η οποία μπορεί να παράγει το αντικείμενο `Latte\Engine`, ένα [πρότυπο απόδοσης Latte |latte:develop#how-to-render-a-template]. Χρησιμοποιούμε τη μέθοδο `renderToString()` για την απόδοση του προτύπου σε ένα αρχείο, η πρώτη παράμετρος είναι η διαδρομή προς το πρότυπο και η δεύτερη οι μεταβλητές. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Στη συνέχεια, περνάμε το παραγόμενο email HTML στη μέθοδο `setHtmlBody()` αντί του αρχικού `setBody()`. Επίσης, δεν χρειάζεται να καθορίσουμε το θέμα του email στο `setSubject()`, επειδή η βιβλιοθήκη το παίρνει από το στοιχείο `` στο πρότυπο. + + +Διαμόρφωση του .[#toc-configuring] +---------------------------------- + +Στον κώδικα της κλάσης `ContactFacade`, το email του διαχειριστή μας `admin@example.com` εξακολουθεί να είναι σκληρά κωδικοποιημένο. Θα ήταν καλύτερα να το μεταφέρουμε στο αρχείο ρυθμίσεων. Πώς να το κάνετε; + +Πρώτα, τροποποιούμε την κλάση `ContactFacade` και αντικαθιστούμε το αλφαριθμητικό email με μια μεταβλητή που περνάει από τον κατασκευαστή: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +Και το δεύτερο βήμα είναι να τοποθετήσουμε την τιμή αυτής της μεταβλητής στη διαμόρφωση. Στο αρχείο `app/config/services.neon` προσθέτουμε: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +Και αυτό είναι όλο. Αν υπάρχουν πολλά στοιχεία στην ενότητα `services` και νιώθετε ότι το email χάνεται ανάμεσά τους, μπορούμε να το κάνουμε μεταβλητή. Θα τροποποιήσουμε την καταχώρηση σε: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +Και θα ορίσουμε αυτή τη μεταβλητή στο αρχείο `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +Και τελείωσε! + + +{{sitename: Best Practices}} diff --git a/best-practices/el/pagination.texy b/best-practices/el/pagination.texy index 3d509a8fe4..3003b38a84 100644 --- a/best-practices/el/pagination.texy +++ b/best-practices/el/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Το επόμενο βήμα είναι να επεξεργαστούμε τον παρουσιαστή. Θα προωθήσουμε τον αριθμό της τρέχουσας σελίδας που εμφανίζεται στη μέθοδο render. Στην περίπτωση που αυτός ο αριθμός δεν αποτελεί μέρος της διεύθυνσης URL, πρέπει να ορίσουμε την προεπιλεγμένη τιμή στην πρώτη σελίδα. -Επεκτείνουμε επίσης τη μέθοδο render για να λάβουμε την περίπτωση Paginator, να τη ρυθμίσουμε και να επιλέξουμε τα σωστά άρθρα που θα εμφανίζονται στο πρότυπο. Το HomepagePresenter θα μοιάζει με αυτό: +Επεκτείνουμε επίσης τη μέθοδο render για να λάβουμε την περίπτωση Paginator, να τη ρυθμίσουμε και να επιλέξουμε τα σωστά άρθρα που θα εμφανίζονται στο πρότυπο. Το HomePresenter θα μοιάζει με αυτό: ```php namespace App\Presenters; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/el/restore-request.texy b/best-practices/el/restore-request.texy index b33146fb79..66883715a1 100644 --- a/best-practices/el/restore-request.texy +++ b/best-practices/el/restore-request.texy @@ -31,9 +31,11 @@ class AdminPresenter extends Nette\Application\UI\Presenter ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/en/@home.texy b/best-practices/en/@home.texy index 37add52888..43f088951b 100644 --- a/best-practices/en/@home.texy +++ b/best-practices/en/@home.texy @@ -26,6 +26,7 @@ Forms ----- - [Reuse of forms |form-reuse] - [Form for creating and editing record |creating-editing-form] +- [Let's Create a Contact Form |lets-create-contact-form] - [Dependent selectboxes |https://blog.nette.org/en/dependent-selectboxes-elegantly-in-nette-and-pure-js] </div> diff --git a/best-practices/en/composer.texy b/best-practices/en/composer.texy index efee53307c..e87a29b0f7 100644 --- a/best-practices/en/composer.texy +++ b/best-practices/en/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Update to the Latest Version -============================ +Update Packages to the Latest Versions +====================================== To update all used packages to the latest version according to version constraints defined in `composer.json` use command `composer update`. For example for dependency `"nette/database": "^3.0"` it will install the latest version 3.x.x, but not version 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project Instead the `name-of-the-project` you should provide the name of the directory for your project and execute the command. Composer will fetch the `nette/web-project` repository from GitHub, which already contains the `composer.json` file, and right after that install the Nette Framework itself. The only thing which remains is to [check write permissions |nette:troubleshooting#setting-directory-permissions] on directories `temp/` and `log/` and you're ready to go. +If you know what version of PHP the project will be hosted on, be sure to [set it up |#PHP Version]. + PHP Version =========== -Composer always installs those versions of packages that are compatible with the version of PHP you are currently using. Which, of course, may not be the same version as PHP on your web host. Therefore, it is useful to add information about the PHP version on the host to the `composer.json` file, and then only versions of packages compatible with host will be installed: +Composer always installs the versions of packages that are compatible with the version of PHP you are currently using (or rather, the version of PHP used on the command line when you run Composer). Which is probably not the same version your web host is using. That's why it's very important to add information about the PHP version on your hosting to your `composer.json` file. After that, only versions of packages compatible with the host will be installed. + +For example, to set the project to run on PHP 8.2.3, use the command: + +```shell +composer config platform.php 8.2.3 +``` + +This is how the version is written to the `composer.json` file: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +However, the PHP version number is also listed elsewhere in the file, in the `require` section. While the first number specifies the version for which packages will be installed, the second number tells what version the application itself is written for. +(Of course, it doesn't make sense for these versions to be different, so double entry is a redundancy.) You set this version with the command: + +```shell +composer require php 8.2.3 --no-update +``` + +Or directly in the `composer.json` file: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +False Reports +============= + +When upgrading packages or changing version numbers, conflicts happen. One package has requirements that conflict with another and so on. However, Composer occasionally prints a false messages. It reports a conflict that doesn't really exist. In this case, it helps to delete the `composer.lock` file and try again. + +If the error message persists, then it is meant seriously and you need to read from it what to modify and how. + Packagist.org - Global Repository ================================= diff --git a/best-practices/en/dynamic-snippets.texy b/best-practices/en/dynamic-snippets.texy index 78026a9a7b..a0e8e09cae 100644 --- a/best-practices/en/dynamic-snippets.texy +++ b/best-practices/en/dynamic-snippets.texy @@ -51,7 +51,7 @@ In Latte terminology, a dynamic snippet is a specific use case of the `{snippet} <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/en/editors-and-tools.texy b/best-practices/en/editors-and-tools.texy index 8241088593..b8446fea90 100644 --- a/best-practices/en/editors-and-tools.texy +++ b/best-practices/en/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan is a tool that detects logical errors in your code before you run it. Install it via Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: And then let it analyze the classes in the `app/` folder: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/en/form-reuse.texy b/best-practices/en/form-reuse.texy index 1be78efe68..176a6cb783 100644 --- a/best-practices/en/form-reuse.texy +++ b/best-practices/en/form-reuse.texy @@ -1,63 +1,218 @@ -Reuse Forms in Multiple Places -****************************** +Reusing Forms in Multiple Places +******************************** .[perex] -How to reuse the same form in multiple places and not duplicate code? This is really easy to do in Nette and you have multiple ways to choose from. +In Nette, you have several options to reuse the same form in multiple places without duplicating code. In this article, we'll go over the different solutions, including the ones you should avoid. Form Factory ============ -Let's create a class that can create a form. Such a class is called a factory. In the place where we want to use the form (e.g. in the presenter), we request the [factory as dependency|dependency-injection:passing-dependencies]. +One basic approach to using the same component in multiple places is to create a method or class that generates the component, and then call that method in different places in the application. Such a method or class is called a *factory*. Please do not confuse with the *factory method* design pattern, which describes a specific way of using factories and is not related to this topic. -Part of the factory is the code that passes the data for further processing when the form is successfully submitted. Usually to the model layer. It also checks if everything went well, and [passes back |forms:validation#Processing-errors] any errors to the form. The model in the following example is represented by the `Facade` class: +As an example, let's create a factory that will build an edit form: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // additional form fields are added here + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Now you can use this factory in different places in your application, for example in presenters or components. And we do this by [requesting it as a dependency |dependency-injection:passing-dependencies]. So first, we'll write the class to the configuration file: + +```neon +services: + - FormFactory +``` + +And then we use it in the presenter: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // processing of sent data + }; + return $form; + } +} +``` + +You can extend the form factory with additional methods to create other types of forms to suit your application. And, of course, you can add a method that creates a basic form without elements, which the other methods will use: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // additional form fields are added here + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // add elements to the form +The `createForm()` method doesn't do anything useful yet, but that will change quickly. - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Factory Dependencies +==================== + +In time, it will become apparent that we need forms to be multilingual. This means that we need to set up a [translator |forms:rendering#Translating] for all forms. To do this, we modify the `FormFactory` class to accept the `Translator` object as a dependency in the constructor, and pass it to the form: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + // ... +} +``` + +Since the `createForm()` method is also called by other methods that create specific forms, we only need to set the translator in that method. And we're done. No need to change any presenter or component code, which is great. + + +More Factory Classes +==================== + +Alternatively, you can create multiple classes for each form you want to use in your application. +This approach can increase code readability and make forms easier to manage. Leave the original `FormFactory` to create just a pure form with basic configuration (for example, with translation support) and create a new factory `EditFormFactory` for the edit form. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // form processing - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ use of composition +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // additional form fields are added here + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Of course, the factory can be parametric, i.e. it can receive parameters that will affect the appearance of the form being created. +It is very important that the binding between the `FormFactory` and `EditFormFactory` classes is implemented by composition, not object inheritance: -We will now demonstrate passing the factory to the presenter. First, we write it to the configuration file: - -```neon -services: - - EditFormFactory +```php +// ⛔ NO! INHERITANCE DOESN'T BELONG HERE +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // additional form fields are added here + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -And then request it in the presenter. There also follows the next step of processing the submitted form and that is redirecting to the next page: +Using inheritance in this case would be completely counterproductive. You would run into problems very quickly. For example, if you wanted to add parameters to the `create()` method; PHP would report an error that its signature was different from the parent. +Or when passing a dependency to the `EditFormFactory` class via the constructor. This would cause what we call [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. + +In general, it is better to prefer composition over inheritance. + + +Form Handling +============= + +The form handler that is called after a successful submission can also be part of a factory class. It will work by passing the submitted data to the model for processing. It will pass any errors [back to |forms:validation#Processing Errors] the form. The model in the following example is represented by the class `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // additional form fields are added here + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // processing of submitted data + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Let the presenter handle the redirection itself. It will add another handler to the `onSuccess` event, which will perform the redirection. This will allow the form to be used in different presenters, and each can redirect to a different location. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Since the redirection is handled by the presenter handler, the component can be used in multiple places and redirected elsewhere in each place. +This solution takes advantage of the property of forms that, when `addError()` is called on a form or its element, the next `onSuccess` handler is not invoked. + +Inheriting from the Form Class +============================== -Component with Form -=================== +A built form is not supposed to be a child of a form. In other words, don't use this solution: -Another way is to create a new [component|application:components] that contains a form. This gives us the ability to render the form in a specific way, for example, since the component includes a template. -Or we can use signals for AJAX communication and loading information into the form, for example for auto-completion, etc. +```php +// ⛔ NO! INHERITANCE DOESN'T BELONG HERE +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // additional form fields are added here + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Instead of building the form in the constructor, use the factory. + +It's important to realize that the `Form` class is primarily a tool for assembling a form, i.e., a form builder. And the assembled form can be considered its product. However, the product is not a specific case of the builder; there is no *is a* relationship between them, which forms the basis of inheritance. + + +Form Component +============== + +A completely different approach is to create a [component |application:components] that includes a form. This gives new possibilities, for example to render the form in a specific way, since the component includes a template. +Or signals can be used for AJAX communication and loading information into the form, for example for hinting, etc. ```php @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // add elements to the form - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // additional form fields are added here + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // form processing - $this->facade->process($values); + // processing of submitted data + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,13 +304,12 @@ class EditControl extends Nette\Application\UI\Control } // event invocation - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -Next, we'll create the factory that will produce this component. Just [write its interface|application:components#Components with Dependencies]: - +Let's create a factory that will produce this component. It's enough to [write its interface|application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,14 +318,14 @@ interface EditControlFactory } ``` -And add to the configuration file: +And add it to the configuration file: ```neon services: - EditControlFactory ``` -And now we can require the factory and use it in the presenter: +And now we can request the factory and use it in the presenter: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -164,7 +341,7 @@ class MyPresenter extends Nette\Application\UI\Presenter $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // or redirect to the result of the edit, e.g.: + // or redirect to the result of editing, e.g.: // $this->redirect('detail', ['id' => $data->id]); }; @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Best Practices}} diff --git a/best-practices/en/inject-method-attribute.texy b/best-practices/en/inject-method-attribute.texy index 852b29b539..e619002214 100644 --- a/best-practices/en/inject-method-attribute.texy +++ b/best-practices/en/inject-method-attribute.texy @@ -2,13 +2,20 @@ Inject Methods and Attributes ***************************** .[perex] -Using specific examples, we will look at the possibilities of passing dependencies to presenters and explain the `inject` methods and attributes/annotations. +In this article, we will focus on various ways of passing dependencies to presenters in the Nette framework. We will compare the preferred method, which is the constructor, with other options such as `inject` methods and attributes. + +For presenters as well, passing dependencies using the [constructor |dependency-injection:passing-dependencies#Constructor Injection] is the preferred way. +However, if you create a common ancestor from which other presenters inherit (e.g., BasePresenter), and this ancestor also has dependencies, a problem arises, which we call [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. +This can be bypassed using alternative methods, which include inject methods and attributes (annotations). `inject*()` Methods =================== -In presenter, as in any other code, the preferred way of passing dependencies is by using [constructor |dependency-injection:passing-dependencies#Constructor Injection]. However, if the presenter inherits from a common ancestor (e.g. `BasePresenter`), it is better to use the methods of `inject*()` in that ancestor. It is a special case of a setter, where the method starts with a prefix `inject`. This is because we keep the constructor free for descendants: +This is a form of dependency passing using [setters |dependency-injection:passing-dependencies#Setter Injection]. The names of these setters begin with the prefix inject. +Nette DI automatically calls such named methods immediately after creating the presenter instance and passes all required dependencies to them. They must therefore be declared as public. + +`inject*()` methods can be considered as a kind of constructor extension into multiple methods. Thanks to this, the `BasePresenter` can take dependencies through another method and leave the constructor free for its descendants: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -The basic difference from a setter is that Nette DI automatically calls methods named this way in presenters as soon as the instance is created, passing all required dependencies to them. A presenter can contain multiple methods `inject*()` and each method can have any number of parameters. +The presenter can contain any number of `inject*()` methods, and each can have any number of parameters. This is also great for cases where the presenter is [composed of traits |presenter-traits], and each of them requires its own dependency. -If we passed dependencies to ancestors through their constructors, we would have to get their dependencies in all descendants and pass them to `parent::__construct()`, which complicates the code: -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // this is a complication - $this->bar = $bar; - } -} -``` - -The `inject*()` methods are also useful in cases where the presenter is [composed of traits |presenter-traits] and each of them requires its own dependency. - -It is also possible to use annotation `@inject`, but it is important to keep in mind that encapsulation breaks. - - -`Inject` Annotations -==================== - -This is an automatic passing of the dependency to the presenter's public member variable, which is annotated with `@inject` in the documentation comment. The type can also be specified in the documentation comment if you are using PHP lower than 7.4. +`Inject` Attributes +=================== -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +This is a form of [injection into properties |dependency-injection:passing-dependencies#Property Injection]. It is enough to indicate which properties should be injected, and Nette DI automatically passes dependencies immediately after creating the presenter instance. To insert them, it is necessary to declare them as public. -Since PHP 8.0, a property can be marked with an attribute `Inject`: +Properties are marked with an attribute: (previously, the annotation `/** @inject */` was used) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // this line is important class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Again, Nette DI will automatically pass dependencies to properties annotated in this way in the presenter as soon as the instance is created. +The advantage of this method of passing dependencies was its very economical form of notation. However, with the introduction of [constructor property promotion |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], using the constructor seems easier. -This method has the same shortcomings as passing dependencies to a public property. It is used in presenter because it does not complicate the code and requires only a minimum of typing. +On the other hand, this method suffers from the same shortcomings as passing dependencies into properties in general: we have no control over changes in the variable, and at the same time, the variable becomes part of the public interface of the class, which is undesirable. {{sitename: Best Practices}} diff --git a/best-practices/en/lets-create-contact-form.texy b/best-practices/en/lets-create-contact-form.texy new file mode 100644 index 0000000000..ad2472ed15 --- /dev/null +++ b/best-practices/en/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Let's Create a Contact Form +*************************** + +.[perex] +Let's take a look at how to create a contact form in Nette, including sending it to an email. So let's do it! + +First we have to create a new project. As the [Getting Started |nette:installation] page explains. And then we can start creating the form. + +The easiest way is to create the [form directly in Presenter |forms:in-presenter]. We can use the pre-made `HomePresenter`. We will add the `contactForm` component representing the form. We do this by writing the `createComponentContactForm()` factory method into the code that will produce the component: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +As you can see, we have created two methods. The first method `createComponentContactForm()` creates a new form. This has fields for name, email and message, which we add using the `addText()`, `addEmail()` and `addTextArea()` methods. We also added a button to submit the form. +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: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +To send the email itself, we create a new class called `ContactFacade` and place it in the `app/Model/ContactFacade.php` file: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +The `sendMessage()` method will create and send the email. It uses a so-called mailer to do this, which it passes as a dependency via the constructor. Read more about [sending emails |mail:]. + +Now, we'll go back to the presenter and complete the `contactFormSucceeded()` method. It calls the `sendMessage()` method of the `ContactFacade` class and passes it the form data. And how do we get the `ContactFacade` object ? We'll have it passed to us by the constructor: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +After the email is sent, we show the user the so-called [flash message |application:components#flash-messages], confirming that the message has been sent, and then redirect to the next page so that the form cannot be resubmitted using *refresh* in the browser. + + +Well, if everything works, you should be able to send an email from your contact form. Congratulations! + + +HTML Email Template +------------------- + +For now, a plain text email containing only the message sent by the form is sent. But we can use HTML in the email and make it more attractive. We will create a template for it in Latte, which we will save into `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +It remains to modify `ContactFacade` to use this template. In the constructor, we request the `LatteFactory` class, which can produce the `Latte\Engine` object, a [Latte template renderer |latte:develop#how-to-render-a-template]. We use the `renderToString()` method to render the template to a file, the first parameter is the path to the template and the second is the variables. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +We then pass the generated HTML email to the `setHtmlBody()` method instead of the original `setBody()`. We also don't need to specify the subject of the email in `setSubject()`, because the library takes it from the element `` in template. + + +Configuring +----------- + +In the `ContactFacade` class code, our admin email `admin@example.com` is still hardcoded. It would be better to move it to the configuration file. How to do it? + +First, we modify the `ContactFacade` class and replace the email string with a variable passed by the constructor: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +And the second step is to put the value of this variable in the configuration. In the `app/config/services.neon` file we add: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +And that's it. If there are a lot of items in the `services` section and you feel like the email is getting lost among them, we can make it a variable. We'll modify the entry to: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +And define this variable in the `app/config/common.neon` file: + +```neon +parameters: + adminEmail: admin@example.com +``` + +And it's done! + + +{{sitename: Best Practices}} diff --git a/best-practices/en/pagination.texy b/best-practices/en/pagination.texy index 107ea8f3cd..c0c0007e9b 100644 --- a/best-practices/en/pagination.texy +++ b/best-practices/en/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository The next step is to edit the presenter. We will forward the number of the currently displayed page to the render method. In the case that this number is not part of the URL, we need to set the default value to the first page. -We also expand the render method to get the Paginator instance, setting it up, and selecting the correct articles to display in the template. HomepagePresenter will look like this: +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/en/restore-request.texy b/best-practices/en/restore-request.texy index f602b3d232..04f407afc6 100644 --- a/best-practices/en/restore-request.texy +++ b/best-practices/en/restore-request.texy @@ -31,9 +31,11 @@ The `SignPresenter` presenter will contain a persistent `$backlink` parameter to ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/es/@home.texy b/best-practices/es/@home.texy index d731a04f42..2bbf67aa4b 100644 --- a/best-practices/es/@home.texy +++ b/best-practices/es/@home.texy @@ -26,6 +26,7 @@ Formularios ----------- - [Reutilización de formularios |form-reuse] - [Formulario de creación y edición de registros |creating-editing-form] +- [Creemos un formulario de contacto |lets-create-contact-form] - [Casillas de selección dependientes |https://blog.nette.org/es/selectboxes-dependientes-elegantemente-en-nette-y-js-puro] </div> diff --git a/best-practices/es/composer.texy b/best-practices/es/composer.texy index be50786337..c550b6a92f 100644 --- a/best-practices/es/composer.texy +++ b/best-practices/es/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Actualizar a la última versión .[#toc-update-to-the-latest-version] -=================================================================== +Actualizar paquetes a las últimas versiones .[#toc-update-packages-to-the-latest-versions] +========================================================================================== Para actualizar todos los paquetes utilizados a la última versión según las restricciones de versión definidas en `composer.json` utilice el comando `composer update`. Por ejemplo, para la dependencia `"nette/database": "^3.0"` instalará la última versión 3.x.x, pero no la versión 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project En lugar de `name-of-the-project` debe proporcionar el nombre del directorio para su proyecto y ejecutar el comando. Composer obtendrá el repositorio `nette/web-project` de GitHub, que ya contiene el archivo `composer.json`, y justo después instalará el propio Nette Framework. La única cosa que queda es [comprobar los permisos de escritura |nette:troubleshooting#setting-directory-permissions] en los directorios `temp/` y `log/` y ya está listo para ir. +Si sabes en qué versión de PHP se alojará el proyecto, asegúrate de [configurarlo |#PHP Version]. + Versión PHP .[#toc-php-version] =============================== -Composer siempre instala las versiones de los paquetes que son compatibles con la versión de PHP que está utilizando actualmente. Que, por supuesto, puede no ser la misma versión que PHP en su host web. Por lo tanto, es útil añadir información sobre la versión de PHP en el host al archivo `composer.json`, y entonces sólo se instalarán las versiones de paquetes compatibles con el host: +Composer siempre instala las versiones de los paquetes que son compatibles con la versión de PHP que está utilizando actualmente (o más bien, la versión de PHP utilizada en la línea de comandos cuando se ejecuta Composer). Que probablemente no es la misma versión que utiliza su proveedor de alojamiento web. Por eso es muy importante añadir información sobre la versión de PHP de tu alojamiento a tu archivo `composer.json`. Después de eso, sólo se instalarán las versiones de paquetes compatibles con el host. + +Por ejemplo, para configurar el proyecto para que se ejecute en PHP 8.2.3, utilice el comando: + +```shell +composer config platform.php 8.2.3 +``` + +Así se escribe la versión en el archivo `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +Sin embargo, el número de versión de PHP también aparece en otra parte del archivo, en la sección `require`. Mientras que el primer número especifica la versión para la que se instalarán los paquetes, el segundo indica para qué versión está escrita la aplicación. +(Por supuesto, no tiene sentido que estas versiones sean diferentes, así que la doble entrada es una redundancia). Esta versión se establece con el comando + +```shell +composer require php 8.2.3 --no-update +``` + +O directamente en el archivo `composer.json`: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Informes falsos .[#toc-false-reports] +===================================== + +Cuando se actualizan paquetes o se cambian los números de versión, surgen conflictos. Un paquete tiene requisitos que entran en conflicto con otro y así sucesivamente. Sin embargo, Composer ocasionalmente imprime un mensaje falso. Informa de un conflicto que realmente no existe. En este caso, ayuda borrar el archivo `composer.lock` e intentarlo de nuevo. + +Si el mensaje de error persiste, entonces va en serio y hay que leer en él qué modificar y cómo. + Packagist.org - Repositorio global .[#toc-packagist-org-global-repository] ========================================================================== diff --git a/best-practices/es/dynamic-snippets.texy b/best-practices/es/dynamic-snippets.texy index b347eaee53..2133b4a573 100644 --- a/best-practices/es/dynamic-snippets.texy +++ b/best-practices/es/dynamic-snippets.texy @@ -51,7 +51,7 @@ En la terminología de Latte, un fragmento dinámico es un caso de uso específi <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/es/editors-and-tools.texy b/best-practices/es/editors-and-tools.texy index 54ce688d1b..cb037a479b 100644 --- a/best-practices/es/editors-and-tools.texy +++ b/best-practices/es/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan es una herramienta que detecta errores lógicos en su código antes de e Instálelo a través de Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: Y luego deja que analice las clases en la carpeta `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/es/form-reuse.texy b/best-practices/es/form-reuse.texy index 2b73a8fd9e..12e62fa802 100644 --- a/best-practices/es/form-reuse.texy +++ b/best-practices/es/form-reuse.texy @@ -1,63 +1,218 @@ -Reutilizar formularios en varios sitios -*************************************** +Reutilización de formularios en varios sitios +********************************************* .[perex] -¿Cómo reutilizar el mismo formulario en múltiples lugares y no duplicar código? Esto es realmente fácil de hacer en Nette y tienes múltiples formas para elegir. +En Nette, tienes varias opciones para reutilizar el mismo formulario en múltiples lugares sin duplicar código. En este artículo, repasaremos las diferentes soluciones, incluyendo las que deberías evitar. Fábrica de formularios .[#toc-form-factory] =========================================== -Vamos a crear una clase que pueda crear un formulario. Una clase de este tipo se llama fábrica. En el lugar donde queremos utilizar el formulario (por ejemplo, en el presentador), solicitamos la fábrica [como dependencia |dependency-injection:passing-dependencies]. +Un enfoque básico para utilizar el mismo componente en múltiples lugares es crear un método o clase que genere el componente, y luego llamar a ese método en diferentes lugares de la aplicación. Este método o clase se llama *factory*. Por favor, no confundir con el patrón de diseño *método de fábrica*, que describe una forma específica de utilizar fábricas y no está relacionado con este tema. -Parte de la fábrica es el código que pasa los datos para su posterior procesamiento cuando el formulario se envía correctamente. Normalmente a la capa del modelo. También comprueba si todo ha ido bien, y [devuelve |forms:validation#Processing-errors] cualquier error al formulario. El modelo en el siguiente ejemplo está representado por la clase `Facade`: +Como ejemplo, vamos a crear una fábrica que construirá un formulario de edición: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // aquí se añaden campos de formulario adicionales + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Ahora puedes usar esta fábrica en diferentes lugares de tu aplicación, por ejemplo en presentadores o componentes. Y hacemos esto [solicitándola como una dependencia |dependency-injection:passing-dependencies]. Así que primero, escribiremos la clase en el archivo de configuración: + +```neon +services: + - FormFactory +``` + +Y luego la usamos en el presentador: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // tratamiento de los datos enviados + }; + return $form; + } +} +``` + +Puedes extender la fábrica de formularios con métodos adicionales para crear otros tipos de formularios que se adapten a tu aplicación. Y, por supuesto, puedes añadir un método que cree un formulario básico sin elementos, que utilizarán los demás métodos: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } - // añadir elementos al formulario + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // aquí se añaden campos de formulario adicionales + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +El método `createForm()` no hace nada útil todavía, pero eso cambiará rápidamente. + + +Dependencias de fábrica .[#toc-factory-dependencies] +==================================================== +Con el tiempo, se hará evidente que necesitamos que los formularios sean multilingües. Esto significa que necesitamos configurar un [traductor |forms:rendering#Translating] para todos los formularios. Para ello, modificamos la clase `FormFactory` para que acepte el objeto `Translator` como dependencia en el constructor, y lo pase al formulario: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Como el método `createForm()` también es llamado por otros métodos que crean formularios específicos, sólo necesitamos establecer el traductor en ese método. Y ya está. No hay necesidad de cambiar ningún código de presentador o componente, lo cual es genial. + + +Más clases de fábrica .[#toc-more-factory-classes] +================================================== + +Alternativamente, puede crear múltiples clases para cada formulario que desee utilizar en su aplicación. +Este enfoque puede aumentar la legibilidad del código y hacer que los formularios sean más fáciles de gestionar. Deje el original `FormFactory` para crear sólo un formulario puro con configuración básica (por ejemplo, con soporte de traducción) y cree una nueva fábrica `EditFormFactory` para el formulario de edición. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // procesamiento de formularios - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ uso de la composición +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // aquí se añaden campos de formulario adicionales + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Por supuesto, la fábrica puede ser paramétrica, es decir, puede recibir parámetros que afectarán a la apariencia del formulario que se está creando. - -Ahora demostraremos cómo pasar la fábrica al presentador. Primero, la escribimos en el fichero de configuración: +Es muy importante que la unión entre las clases `FormFactory` y `EditFormFactory` se implemente por composición, no por herencia de objetos: -```neon -services: - - EditFormFactory +```php +// ⛔ ¡NO! LA HERENCIA NO PERTENECE AQUÍ +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // los campos de formulario adicionales se añaden aquí + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -Y luego la solicitamos en el presentador. También sigue el siguiente paso de procesamiento del formulario enviado y que está redirigiendo a la página siguiente: +Utilizar la herencia en este caso sería totalmente contraproducente. Se encontraría con problemas muy rápidamente. Por ejemplo, si quisiera agregar parámetros al método `create()`; PHP reportaría un error de que su firma es diferente a la del padre. +O al pasar una dependencia a la clase `EditFormFactory` a través del constructor. Esto causaría lo que llamamos el infierno del [constructor |dependency-injection:passing-dependencies#Constructor hell]. + +En general, es mejor preferir la composición a la herencia. + + +Manejo de Formularios .[#toc-form-handling] +=========================================== + +El manejador de formularios que es llamado después de un envío exitoso también puede ser parte de una clase fábrica. Funcionará pasando los datos enviados al modelo para su procesamiento. [Devolverá |forms:validation#Processing Errors] cualquier error al formulario. El modelo en el siguiente ejemplo está representado por la clase `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // aquí se añaden campos de formulario adicionales + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // tratamiento de los datos enviados + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Deje que el presentador se encargue de la redirección. Añadirá otro manejador al evento `onSuccess`, que realizará la redirección. Esto permitirá utilizar el formulario en diferentes presentadores, y cada uno puede redirigir a una ubicación diferente. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Dado que la redirección es manejada por el manejador del presentador, el componente puede ser usado en múltiples lugares y redirigido a otro lugar en cada lugar. +Esta solución aprovecha la propiedad de los formularios de que, cuando se llama a `addError()` sobre un formulario o su elemento, no se invoca al siguiente manejador `onSuccess`. + +Heredando de la clase Form .[#toc-inheriting-from-the-form-class] +================================================================= -Componente con formulario .[#toc-component-with-form] -===================================================== +Un formulario creado no debe ser hijo de un formulario. En otras palabras, no utilice esta solución: -Otra forma es crear un nuevo [componente |application:components] que contenga un formulario. Esto nos da la posibilidad de renderizar el formulario de una manera específica, por ejemplo, ya que el componente incluye una plantilla. -O podemos usar señales para comunicación AJAX y cargar información en el formulario, por ejemplo para autocompletar, etc. +```php +// ⛔ ¡NO! LA HERENCIA NO PERTENECE AQUÍ +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // los campos de formulario adicionales se añaden aquí + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +En lugar de construir el formulario en el constructor, utilice la fábrica. + +Es importante darse cuenta de que la clase `Form` es principalmente una herramienta para ensamblar un formulario, es decir, un constructor de formularios. Y el formulario ensamblado puede considerarse su producto. Sin embargo, el producto no es un caso específico del constructor; no existe una relación *es a* entre ellos, que constituye la base de la herencia. + + +Componente Form .[#toc-form-component] +====================================== + +Un enfoque completamente diferente es crear un [componente |application:components] que incluya un formulario. Esto da nuevas posibilidades, por ejemplo para renderizar el formulario de una manera específica, ya que el componente incluye una plantilla. +O se pueden utilizar señales para la comunicación AJAX y cargar información en el formulario, por ejemplo para sugerencias, etc. ```php @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // añadir elementos al formulario - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // aquí se añaden campos de formulario adicionales + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // procesamiento de formularios - $this->facade->process($values); + // procesamiento de los datos enviados + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,13 +304,12 @@ class EditControl extends Nette\Application\UI\Control } // invocación de eventos - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -A continuación, crearemos la fábrica que producirá este componente. Sólo tienes que [escribir su interfaz |application:components#Components with Dependencies]: - +Vamos a crear una fábrica que producirá este componente. Basta con [escribir su interfaz |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,14 +318,14 @@ interface EditControlFactory } ``` -Y añadir al archivo de configuración: +Y añadirla al fichero de configuración: ```neon services: - EditControlFactory ``` -Y ahora podemos requerir la fábrica y utilizarla en el presentador: +Y ahora podemos solicitar la fábrica y utilizarla en el presentador: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Buenas prácticas}} diff --git a/best-practices/es/inject-method-attribute.texy b/best-practices/es/inject-method-attribute.texy index 68afa22db5..df0ea93d35 100644 --- a/best-practices/es/inject-method-attribute.texy +++ b/best-practices/es/inject-method-attribute.texy @@ -2,13 +2,20 @@ Métodos y atributos de inyección ******************************** .[perex] -Utilizando ejemplos concretos, veremos las posibilidades de pasar dependencias a los presentadores y explicaremos los métodos y atributos/anotaciones de `inject`. +En este artículo, nos centraremos en varias formas de pasar dependencias a los presentadores en el framework Nette. Compararemos el método preferido, que es el constructor, con otras opciones como los métodos y atributos de `inject`. + +En el caso de los presentadores, el método preferido es pasar las dependencias mediante el [constructor |dependency-injection:passing-dependencies#Constructor Injection]. +Sin embargo, si creas un ancestro común del que heredan otros presentadores (por ejemplo, BasePresenter), y este ancestro también tiene dependencias, surge un problema, que llamamos [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. +Esto puede evitarse utilizando métodos alternativos, que incluyen inyectar métodos y atributos (anotaciones). `inject*()` Métodos .[#toc-inject-methods] ========================================== -En el presentador, como en cualquier otro código, la forma preferida de pasar dependencias es utilizando el [constructor |dependency-injection:passing-dependencies#Constructor Injection]. Sin embargo, si el presentador hereda de un ancestro común (por ejemplo, `BasePresenter`), es mejor utilizar los métodos de `inject*()` en ese ancestro. Es un caso especial de un setter, donde el método comienza con un prefijo `inject`. Esto se debe a que mantenemos el constructor libre para los descendientes: +Se trata de una forma de pasar dependencias mediante [setters |dependency-injection:passing-dependencies#Setter Injection]. Los nombres de estos setters comienzan con el prefijo inject. +Nette DI llama automáticamente a estos métodos con nombre inmediatamente después de crear la instancia del presentador y les pasa todas las dependencias necesarias. Por lo tanto, deben declararse como públicos. + +`inject*()` pueden considerarse como una especie de extensión del constructor en varios métodos. Gracias a esto, el `BasePresenter` puede tomar dependencias a través de otro método y dejar el constructor libre para sus descendientes: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -La diferencia básica con un setter es que Nette DI llama automáticamente a los métodos así nombrados en los presentadores en cuanto se crea la instancia, pasándoles todas las dependencias necesarias. Un presentador puede contener múltiples métodos `inject*()` y cada método puede tener cualquier número de parámetros. - -Si pasáramos dependencias a los ancestros a través de sus constructores, tendríamos que obtener sus dependencias en todos los descendientes y pasarlas a `parent::__construct()`, lo que complica el código: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // esto es una complicación - $this->bar = $bar; - } -} -``` - -Los métodos de `inject*()` también son útiles en los casos en los que el presentador está [compuesto por traits |presenter-traits] y cada uno de ellos requiere su propia dependencia. +El presentador puede contener cualquier número de métodos `inject*()`, y cada uno puede tener cualquier número de parámetros. Esto también es genial para los casos en que el presentador [se compone de rasgos |presenter-traits], y cada uno de ellos requiere su propia dependencia. -También es posible utilizar la anotación `@inject`, pero es importante tener en cuenta que se rompe la encapsulación. +`Inject` Atributos .[#toc-inject-attributes] +============================================ -`Inject` Anotaciones .[#toc-inject-annotations] -=============================================== - -Esto es un paso automático de la dependencia a la variable miembro pública del presentador, que está anotada con `@inject` en el comentario de la documentación. El tipo también puede ser especificado en el comentario de la documentación si está utilizando PHP inferior a 7.4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Esta es una forma de inyección [en propiedades |dependency-injection:passing-dependencies#Property Injection]. Basta con indicar qué propiedades deben inyectarse, y Nette DI pasa automáticamente las dependencias inmediatamente después de crear la instancia del presentador. Para insertarlas, es necesario declararlas como públicas. -Desde PHP 8.0, una propiedad puede ser marcada con un atributo `Inject`: +Las propiedades se marcan con un atributo: (antes se utilizaba la anotación `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // esta línea es importante class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -De nuevo, Nette DI pasará automáticamente las dependencias a las propiedades anotadas de esta manera en el presentador tan pronto como se cree la instancia. +La ventaja de este método de pasar dependencias era su forma de notación muy económica. Sin embargo, con la introducción de la [promoción de propiedades |https://blog.nette.org/es/php-8-0-vision-completa-de-las-novedades#toc-constructor-property-promotion] del constructor, el uso del constructor parece más sencillo. -Este método tiene las mismas deficiencias que pasar dependencias a una propiedad pública. Se utiliza en el presentador porque no complica el código y sólo requiere un mínimo de escritura. +Por otro lado, este método adolece de los mismos defectos que pasar dependencias a propiedades en general: no tenemos control sobre los cambios en la variable y, al mismo tiempo, la variable pasa a formar parte de la interfaz pública de la clase, lo cual no es deseable. {{sitename: Buenas prácticas}} diff --git a/best-practices/es/lets-create-contact-form.texy b/best-practices/es/lets-create-contact-form.texy new file mode 100644 index 0000000000..2c7e155167 --- /dev/null +++ b/best-practices/es/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Creemos un formulario de contacto +********************************* + +.[perex] +Veamos cómo crear un formulario de contacto en Nette, incluido el envío a un correo electrónico. ¡Hagámoslo! + +Primero tenemos que crear un nuevo proyecto. Como explica la página de [introducción |nette:installation]. Y luego podemos empezar a crear el formulario. + +La forma más sencilla es crear el formulario [directamente en Presenter |forms:in-presenter]. Podemos utilizar el pre-hecho `HomePresenter`. Añadiremos el componente `contactForm` que representa el formulario. Hacemos esto escribiendo el método de fábrica `createComponentContactForm()` en el código que producirá el componente: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Como puedes ver, hemos creado dos métodos. El primer método `createComponentContactForm()` crea un nuevo formulario. Este tiene campos para nombre, email y mensaje, que añadimos usando los métodos `addText()`, `addEmail()` y `addTextArea()`. También añadimos un botón para enviar el formulario. +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`: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Para enviar el correo electrónico propiamente dicho, creamos una nueva clase llamada `ContactFacade` y la colocamos en el archivo `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +El método `sendMessage()` creará y enviará el correo electrónico. Para ello utiliza el llamado mailer, que pasa como dependencia a través del constructor. Más información sobre el envío de [correos electrónicos |mail:]. + +Ahora, volveremos al presentador y completaremos el método `contactFormSucceeded()`. Llama al método `sendMessage()` de la clase `ContactFacade` y le pasa los datos del formulario. ¿Y cómo obtenemos el objeto `ContactFacade`? Nos lo pasará el constructor: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +Una vez enviado el email, mostramos al usuario el llamado [mensaje flash |application:components#flash-messages], confirmando que el mensaje ha sido enviado, y luego redirigimos a la siguiente página para que el formulario no pueda ser reenviado usando *refresh* en el navegador. + + +Bien, si todo funciona, deberías poder enviar un correo electrónico desde tu formulario de contacto. ¡Enhorabuena! + + +Plantilla HTML de correo electrónico .[#toc-html-email-template] +---------------------------------------------------------------- + +Por ahora, se envía un email de texto plano que contiene sólo el mensaje enviado por el formulario. Pero podemos utilizar HTML en el email y hacerlo más atractivo. Crearemos una plantilla para ello en Latte, que guardaremos en `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Queda modificar `ContactFacade` para utilizar esta plantilla. En el constructor, solicitamos la clase `LatteFactory`, que puede producir el objeto `Latte\Engine`, un [renderizador de |latte:develop#how-to-render-a-template] plantillas Latte. Usamos el método `renderToString()` para renderizar la plantilla a un fichero, el primer parámetro es la ruta a la plantilla y el segundo las variables. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Luego pasamos el correo HTML generado al método `setHtmlBody()` en lugar del original `setBody()`. Tampoco necesitamos especificar el asunto del correo electrónico en `setSubject()`, porque la biblioteca lo toma del elemento `` de la plantilla. + + +Configuración de .[#toc-configuring] +------------------------------------ + +En el código de la clase `ContactFacade`, nuestro email de administrador `admin@example.com` está todavía codificado. Sería mejor moverlo al archivo de configuración. ¿Cómo hacerlo? + +Primero, modificamos la clase `ContactFacade` y reemplazamos la cadena de email con una variable pasada por el constructor: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +Y el segundo paso es poner el valor de esta variable en la configuración. En el fichero `app/config/services.neon` añadimos: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +Y ya está. Si hay muchos elementos en la sección `services` y te parece que el correo electrónico se pierde entre ellos, podemos convertirlo en una variable. Modificaremos la entrada a: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +Y definiremos esta variable en el fichero `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +¡Y ya está! + + +{{sitename: Buenas prácticas}} diff --git a/best-practices/es/pagination.texy b/best-practices/es/pagination.texy index 36b9ee472b..4638f53da7 100644 --- a/best-practices/es/pagination.texy +++ b/best-practices/es/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository El siguiente paso es editar el presentador. Enviaremos el número de la página actualmente mostrada al método render. En el caso de que este número no forme parte de la URL, debemos establecer el valor por defecto en la primera 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. HomepagePresenter tendrá este aspecto: +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/es/restore-request.texy b/best-practices/es/restore-request.texy index 056605ef11..b4f08ec217 100644 --- a/best-practices/es/restore-request.texy +++ b/best-practices/es/restore-request.texy @@ -31,9 +31,11 @@ El presentador `SignPresenter` contendrá un parámetro persistente `$backlink` ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/fr/@home.texy b/best-practices/fr/@home.texy index 1c5ca89185..0b3d8c26f6 100644 --- a/best-practices/fr/@home.texy +++ b/best-practices/fr/@home.texy @@ -26,6 +26,7 @@ Formulaires ----------- - [Réutilisation des formulaires |form-reuse] - [Formulaire de création et d'édition d'une fiche |creating-editing-form] +- [Créons un formulaire de contact |lets-create-contact-form] - [Boîtes de sélection dépendantes |https://blog.nette.org/fr/des-boites-de-selection-dependantes-de-facon-elegante-en-nette-et-en-js-pur] </div> diff --git a/best-practices/fr/composer.texy b/best-practices/fr/composer.texy index 1b7fcc58e3..485cd78e67 100644 --- a/best-practices/fr/composer.texy +++ b/best-practices/fr/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Mise à jour de la dernière version .[#toc-update-to-the-latest-version] -======================================================================= +Mise à jour des paquets vers les dernières versions .[#toc-update-packages-to-the-latest-versions] +================================================================================================== Pour mettre à jour tous les paquets utilisés à la dernière version selon les contraintes de version définies dans `composer.json`, utilisez la commande `composer update`. Par exemple, pour la dépendance `"nette/database": "^3.0"`, elle installera la dernière version 3.x.x, mais pas la version 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project A la place de `name-of-the-project`, vous devez fournir le nom du répertoire de votre projet et exécuter la commande. Composer ira chercher le dépôt `nette/web-project` de GitHub, qui contient déjà le fichier `composer.json`, et juste après, il installera le Framework Nette lui-même. La seule chose qui reste à faire est de [vérifier les droits d'écriture |nette:troubleshooting#setting-directory-permissions] sur les répertoires `temp/` et `log/` et vous êtes prêt à partir. +Si vous connaissez la version de PHP sur laquelle le projet sera hébergé, assurez-vous de la [configurer |#PHP Version]. + Version PHP .[#toc-php-version] =============================== -Composer installe toujours les versions des paquets qui sont compatibles avec la version de PHP que vous utilisez actuellement. Cette version peut, bien entendu, ne pas être la même que celle de PHP sur votre hôte Web. Il est donc utile d'ajouter au fichier `composer.json` des informations sur la version de PHP sur l'hôte, et seules les versions des paquets compatibles avec l'hôte seront alors installées : +Composer installe toujours les versions des paquets compatibles avec la version de PHP que vous utilisez actuellement (ou plutôt, la version de PHP utilisée en ligne de commande lorsque vous lancez Composer). Ce qui n'est probablement pas la même version que celle utilisée par votre hébergeur. C'est pourquoi il est très important d'ajouter l'information sur la version de PHP de votre hébergement dans votre fichier `composer.json`. Ensuite, seules les versions des paquets compatibles avec l'hébergeur seront installées. + +Par exemple, pour configurer le projet pour qu'il fonctionne avec PHP 8.2.3, utilisez la commande : + +```shell +composer config platform.php 8.2.3 +``` + +C'est ainsi que la version est écrite dans le fichier `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +Cependant, le numéro de version de PHP est également indiqué ailleurs dans le fichier, dans la section `require`. Alors que le premier numéro spécifie la version pour laquelle les paquets seront installés, le second numéro indique la version pour laquelle l'application elle-même est écrite. +(Bien sûr, il n'y a pas de sens à ce que ces versions soient différentes, donc la double entrée est une redondance). Vous définissez cette version à l'aide de la commande : + +```shell +composer require php 8.2.3 --no-update +``` + +Ou directement dans le fichier `composer.json` : + +```js +{ + "require" : { + "php" : "8.2.3" + } +} +``` + + +Faux rapports .[#toc-false-reports] +=================================== + +Lors de la mise à niveau de paquets ou du changement de numéro de version, des conflits se produisent. Un paquet a des exigences qui entrent en conflit avec un autre, et ainsi de suite. Cependant, Composer imprime parfois un faux message. Il signale un conflit qui n'existe pas réellement. Dans ce cas, il est utile de supprimer le fichier `composer.lock` et de réessayer. + +Si le message d'erreur persiste, c'est qu'il est sérieux et que vous devez y lire ce qu'il faut modifier et comment. + Packagist.org - Dépôt global .[#toc-packagist-org-global-repository] ==================================================================== diff --git a/best-practices/fr/dynamic-snippets.texy b/best-practices/fr/dynamic-snippets.texy index 74cb1c2eb8..5bc4743f67 100644 --- a/best-practices/fr/dynamic-snippets.texy +++ b/best-practices/fr/dynamic-snippets.texy @@ -51,7 +51,7 @@ Dans la terminologie Latte, un extrait dynamique est un cas d'utilisation spéci <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/fr/editors-and-tools.texy b/best-practices/fr/editors-and-tools.texy index a04115630b..61513bfaf6 100644 --- a/best-practices/fr/editors-and-tools.texy +++ b/best-practices/fr/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan est un outil qui détecte les erreurs logiques dans votre code avant que Installez-le via Composer : -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: Et ensuite laissez-le analyser les classes dans le dossier `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/fr/form-reuse.texy b/best-practices/fr/form-reuse.texy index c18044de42..a5a2829f81 100644 --- a/best-practices/fr/form-reuse.texy +++ b/best-practices/fr/form-reuse.texy @@ -1,63 +1,218 @@ -Réutiliser les formulaires à plusieurs endroits -*********************************************** +Réutilisation de formulaires à plusieurs endroits +************************************************* .[perex] -Comment réutiliser le même formulaire à plusieurs endroits sans dupliquer le code ? C'est très facile à faire dans Nette et vous avez le choix entre plusieurs méthodes. +Dans Nette, vous avez plusieurs options pour réutiliser le même formulaire à plusieurs endroits sans dupliquer le code. Dans cet article, nous allons passer en revue les différentes solutions, y compris celles que vous devriez éviter. Form Factory .[#toc-form-factory] ================================= -Créons une classe qui peut créer un formulaire. Une telle classe est appelée une fabrique. À l'endroit où nous voulons utiliser le formulaire (par exemple, dans le présentateur), nous demandons la [fabrique comme dépendance |dependency-injection:passing-dependencies]. +Une approche de base pour utiliser le même composant à plusieurs endroits consiste à créer une méthode ou une classe qui génère le composant, puis à appeler cette méthode à différents endroits de l'application. Une telle méthode ou classe est appelée *factory*. Ne pas confondre avec le modèle de conception *méthode usine*, qui décrit une manière spécifique d'utiliser les usines et n'est pas lié à ce sujet. -Une partie de la fabrique est le code qui transmet les données pour un traitement ultérieur lorsque le formulaire est soumis avec succès. Généralement vers la couche modèle. Elle vérifie également si tout s'est bien passé, et [renvoie |forms:validation#Processing-errors] toute erreur au formulaire. Dans l'exemple suivant, le modèle est représenté par la classe `Facade`: +A titre d'exemple, créons une fabrique qui construira un formulaire d'édition : ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // les champs supplémentaires du formulaire sont ajoutés ici + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Vous pouvez maintenant utiliser cette fabrique à différents endroits de votre application, par exemple dans des présentateurs ou des composants. Pour ce faire, nous la [demandons en tant que dépendance |dependency-injection:passing-dependencies]. Tout d'abord, nous allons écrire la classe dans le fichier de configuration : + +```neon +services: + - FormFactory +``` + +Puis nous l'utilisons dans le présentateur : + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // traitement des données envoyées + }; + return $form; + } +} +``` + +Vous pouvez étendre la fabrique de formulaires avec des méthodes supplémentaires pour créer d'autres types de formulaires adaptés à votre application. Et, bien sûr, vous pouvez ajouter une méthode qui crée un formulaire de base sans éléments, que les autres méthodes utiliseront : + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // les champs supplémentaires du formulaire sont ajoutés ici + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // ajouter des éléments au formulaire +La méthode `createForm()` ne fait rien d'utile pour l'instant, mais cela changera rapidement. - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Dépendances de l'usine .[#toc-factory-dependencies] +=================================================== + +Avec le temps, il deviendra évident que nous avons besoin de formulaires multilingues. Cela signifie que nous devons mettre en place un [traducteur |forms:rendering#Translating] pour tous les formulaires. Pour ce faire, nous modifions la classe `FormFactory` afin qu'elle accepte l'objet `Translator` en tant que dépendance dans le constructeur et qu'elle le transmette au formulaire : + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Comme la méthode `createForm()` est également appelée par d'autres méthodes qui créent des formulaires spécifiques, nous n'avons besoin de définir le traducteur que dans cette méthode. Et le tour est joué. Il n'est pas nécessaire de modifier le code du présentateur ou du composant, ce qui est très bien. + + +Autres classes d'usine .[#toc-more-factory-classes] +=================================================== + +Vous pouvez également créer plusieurs classes pour chaque formulaire que vous souhaitez utiliser dans votre application. +Cette approche peut améliorer la lisibilité du code et faciliter la gestion des formulaires. Laissez la classe originale `FormFactory` pour créer un formulaire pur avec une configuration de base (par exemple, avec un support de traduction) et créez une nouvelle classe d'usine `EditFormFactory` pour le formulaire d'édition. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // traitement du formulaire - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ utilisation de la composition +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // des champs de formulaire supplémentaires sont ajoutés ici + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Bien sûr, la fabrique peut être paramétrique, c'est-à-dire qu'elle peut recevoir des paramètres qui affecteront l'apparence du formulaire en cours de création. +Il est très important que la liaison entre les classes `FormFactory` et `EditFormFactory` soit mise en œuvre par composition et non par héritage d'objets : -Nous allons maintenant montrer comment passer la fabrique au présentateur. Tout d'abord, nous l'écrivons dans le fichier de configuration : - -```neon -services: - - EditFormFactory +```php +// ⛔ NO ! L'HÉRITAGE N'A PAS SA PLACE ICI +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // des champs de formulaire supplémentaires sont ajoutés ici + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -puis nous la demandons dans le présentateur. Il s'ensuit également l'étape suivante du traitement du formulaire soumis, qui consiste à rediriger vers la page suivante : +L'utilisation de l'héritage dans ce cas serait totalement contre-productive. Vous rencontreriez des problèmes très rapidement. Par exemple, si vous vouliez ajouter des paramètres à la méthode `create()`, PHP signalerait une erreur parce que sa signature est différente de celle du parent. +Ou lorsque vous passez une dépendance à la classe `EditFormFactory` via le constructeur. Cela provoquerait ce que nous appelons l'[enfer du constructeur |dependency-injection:passing-dependencies#Constructor hell]. + +En général, il est préférable de préférer la composition à l'héritage. + + +Traitement des formulaires .[#toc-form-handling] +================================================ + +Le gestionnaire de formulaire qui est appelé après une soumission réussie peut également faire partie d'une classe d'usine. Il transmet les données soumises au modèle pour traitement. Il renvoie les erreurs éventuelles [au |forms:validation#Processing Errors] formulaire. Dans l'exemple suivant, le modèle est représenté par la classe `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // les champs supplémentaires du formulaire sont ajoutés ici + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // traitement des données soumises + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Laissez le présentateur gérer lui-même la redirection. Il ajoutera un autre gestionnaire à l'événement `onSuccess`, qui effectuera la redirection. Le formulaire pourra ainsi être utilisé par différents présentateurs et chacun d'entre eux pourra rediriger vers un emplacement différent. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Puisque la redirection est gérée par le gestionnaire du présentateur, le composant peut être utilisé à plusieurs endroits et redirigé ailleurs à chaque endroit. +Cette solution tire parti de la propriété des formulaires selon laquelle, lorsque `addError()` est appelé sur un formulaire ou son élément, le gestionnaire `onSuccess` suivant n'est pas invoqué. + +Héritage de la classe de formulaire .[#toc-inheriting-from-the-form-class] +========================================================================== -Composant avec formulaire .[#toc-component-with-form] -===================================================== +Un formulaire construit n'est pas censé être un enfant d'un formulaire. En d'autres termes, n'utilisez pas cette solution : -Une autre façon de procéder consiste à créer un nouveau [composant |application:components] qui contient un formulaire. Cela nous donne la possibilité de rendre le formulaire d'une manière spécifique, par exemple, puisque le composant inclut un modèle. -Nous pouvons également utiliser des signaux pour la communication AJAX et le chargement d'informations dans le formulaire, par exemple pour l'autocomplétion, etc. +```php +// ⛔ NO ! L'HÉRITAGE N'A PAS SA PLACE ICI +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // des champs de formulaire supplémentaires sont ajoutés ici + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Au lieu de construire le formulaire dans le constructeur, utilisez la fabrique. + +Il est important de comprendre que la classe `Form` est avant tout un outil permettant d'assembler un formulaire, c'est-à-dire un constructeur de formulaire. Le formulaire assemblé peut être considéré comme son produit. Cependant, le produit n'est pas un cas spécifique du constructeur ; il n'y a pas de relation *is a* entre eux, ce qui constitue la base de l'héritage. + + +Composant de formulaire .[#toc-form-component] +============================================== + +Une approche complètement différente consiste à créer un [composant |application:components] qui inclut un formulaire. Cela offre de nouvelles possibilités, par exemple pour rendre le formulaire d'une manière spécifique, puisque le composant inclut un modèle. +Des signaux peuvent également être utilisés pour la communication AJAX et le chargement d'informations dans le formulaire, par exemple pour des indications, etc. ```php @@ -105,34 +284,32 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // ajoute des éléments au formulaire - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // les champs supplémentaires du formulaire sont ajoutés ici + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // traitement du formulaire - $this->facade->process($values); + // traitement des données soumises + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); return; } - // invocation de l'événement - $this->onSave($this, $values); + // invocation d'événements + $this->onSave($this, $data); } } ``` -Ensuite, nous allons créer la fabrique qui produira ce composant. Il suffit d'[écrire son interface |application:components#Components with Dependencies]: - +Créons une fabrique qui produira ce composant. Il suffit d'[écrire son interface |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -148,7 +325,7 @@ services: - EditControlFactory ``` -Et maintenant nous pouvons exiger l'usine et l'utiliser dans le présentateur : +Nous pouvons maintenant demander la fabrique et l'utiliser dans le présentateur : ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -164,7 +341,7 @@ class MyPresenter extends Nette\Application\UI\Presenter $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // ou redirige vers le résultat de l'édition, par exemple: + // ou rediriger vers le résultat de l'édition, par exemple: // $this->redirect('detail', ['id' => $data->id]); }; @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Meilleures pratiques}} diff --git a/best-practices/fr/inject-method-attribute.texy b/best-practices/fr/inject-method-attribute.texy index 3506c42d6a..0cd31cc567 100644 --- a/best-practices/fr/inject-method-attribute.texy +++ b/best-practices/fr/inject-method-attribute.texy @@ -2,13 +2,20 @@ Méthodes et attributs d'injection ********************************* .[perex] -À l'aide d'exemples spécifiques, nous examinerons les possibilités de transmettre des dépendances aux présentateurs et expliquerons les méthodes et attributs/annotations de `inject`. +Dans cet article, nous allons nous concentrer sur les différentes façons de transmettre des dépendances aux présentateurs dans le cadre de Nette. Nous comparerons la méthode préférée, qui est le constructeur, avec d'autres options telles que les méthodes et les attributs `inject`. + +Pour les présentateurs également, la transmission des dépendances à l'aide du [constructeur |dependency-injection:passing-dependencies#Constructor Injection] est la méthode préférée. +Cependant, si vous créez un ancêtre commun dont les autres présentateurs héritent (par exemple, BasePresenter) et que cet ancêtre possède également des dépendances, un problème se pose, que nous appelons l'[enfer du constructeur |dependency-injection:passing-dependencies#Constructor hell]. +Ce problème peut être contourné en utilisant des méthodes alternatives, qui incluent l'injection de méthodes et d'attributs (annotations). `inject*()` Méthodes .[#toc-inject-methods] =========================================== -Dans le présentateur, comme dans tout autre code, la manière préférée de passer les dépendances est d'utiliser le [constructeur |dependency-injection:passing-dependencies#Constructor Injection]. Toutefois, si le présentateur hérite d'un ancêtre commun (par exemple, `BasePresenter`), il est préférable d'utiliser les méthodes de `inject*()` dans cet ancêtre. C'est un cas particulier de setter, où la méthode commence par un préfixe `inject`. Cela s'explique par le fait que nous gardons le constructeur libre pour les descendants : +Il s'agit d'une forme de transfert de dépendance à l'aide de [fixateurs |dependency-injection:passing-dependencies#Setter Injection]. Les noms de ces setters commencent par le préfixe inject. +Nette DI appelle automatiquement ces méthodes nommées immédiatement après la création de l'instance du présentateur et leur transmet toutes les dépendances nécessaires. Elles doivent donc être déclarées comme publiques. + +`inject*()` Les méthodes publiques peuvent être considérées comme une sorte d'extension de constructeur en plusieurs méthodes. Grâce à cela, `BasePresenter` peut prendre des dépendances par le biais d'une autre méthode et laisser le constructeur libre pour ses descendants : ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -La différence fondamentale avec un setter est que Nette DI appelle automatiquement les méthodes nommées de cette façon dans les présentateurs dès que l'instance est créée, en leur passant toutes les dépendances requises. Un présentateur peut contenir plusieurs méthodes `inject*()` et chaque méthode peut avoir un nombre quelconque de paramètres. - -Si nous transmettions les dépendances aux ancêtres par le biais de leurs constructeurs, nous devrions obtenir leurs dépendances dans tous les descendants et les transmettre à `parent::__construct()`, ce qui complique le code : - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // c'est une complication - $this->bar = $bar; - } -} -``` - -Les méthodes `inject*()` sont également utiles dans les cas où le diffuseur est [composé de traits |presenter-traits] et que chacun d'entre eux nécessite sa propre dépendance. +Le présentateur peut contenir n'importe quel nombre de méthodes `inject*()`, et chacune peut avoir n'importe quel nombre de paramètres. C'est également très utile lorsque le présentateur est [composé de traits |presenter-traits] et que chacun d'entre eux nécessite sa propre dépendance. -Il est également possible d'utiliser l'annotation `@inject`, mais il est important de garder à l'esprit que l'encapsulation est rompue. +`Inject` Attributs .[#toc-inject-attributes] +============================================ -`Inject` Annotations .[#toc-inject-annotations] -=============================================== - -Il s'agit d'un passage automatique de la dépendance à la variable membre publique du présentateur, qui est annotée avec `@inject` dans le commentaire de la documentation. Le type peut également être spécifié dans le commentaire de la documentation si vous utilisez un PHP inférieur à 7.4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Il s'agit d'une forme d'[injection dans les propriétés |dependency-injection:passing-dependencies#Property Injection]. Il suffit d'indiquer quelles propriétés doivent être injectées, et Nette DI passe automatiquement les dépendances immédiatement après la création de l'instance du présentateur. Pour les insérer, il est nécessaire de les déclarer comme publiques. -Depuis PHP 8.0, une propriété peut être marquée avec un attribut `Inject`: +Les propriétés sont marquées par un attribut : (auparavant, l'annotation `/** @inject */` était utilisée) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject ; // cette ligne est importante class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Là encore, Nette DI passera automatiquement les dépendances aux propriétés annotées de cette manière dans le présentateur dès que l'instance sera créée. +L'avantage de cette méthode de transmission des dépendances était sa forme de notation très économique. Cependant, avec l'introduction de la [promotion des propriétés du constructeur |https://blog.nette.org/fr/php-8-0-apercu-complet-des-nouveautes#toc-constructor-property-promotion], l'utilisation du constructeur semble plus facile. -Cette méthode présente les mêmes inconvénients que le passage des dépendances à une propriété publique. Elle est utilisée dans le presenter car elle ne complique pas le code et ne nécessite qu'un minimum de saisie. +D'autre part, cette méthode souffre des mêmes défauts que le passage des dépendances dans les propriétés en général : nous n'avons aucun contrôle sur les changements de la variable, et en même temps, la variable devient une partie de l'interface publique de la classe, ce qui n'est pas souhaitable. {{sitename: Meilleures pratiques}} diff --git a/best-practices/fr/lets-create-contact-form.texy b/best-practices/fr/lets-create-contact-form.texy new file mode 100644 index 0000000000..50afc50327 --- /dev/null +++ b/best-practices/fr/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Créons un formulaire de contact +******************************* + +.[perex] +Voyons comment créer un formulaire de contact dans Nette, y compris l'envoi vers un e-mail. C'est parti ! + +Tout d'abord, nous devons créer un nouveau projet. Comme l'explique la page " [Getting Started" |nette:installation]. Ensuite, nous pouvons commencer à créer le formulaire. + +La manière la plus simple est de créer le [formulaire directement dans Presenter |forms:in-presenter]. Nous pouvons utiliser le formulaire préétabli `HomePresenter`. Nous ajouterons le composant `contactForm` représentant le formulaire. Pour ce faire, nous écrivons la méthode d'usine `createComponentContactForm()` dans le code qui produira le composant : + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Comme vous pouvez le voir, nous avons créé deux méthodes. La première méthode `createComponentContactForm()` crée un nouveau formulaire. Celui-ci comporte des champs pour le nom, l'adresse électronique et le message, que nous ajoutons à l'aide des méthodes `addText()`, `addEmail()` et `addTextArea()`. Nous avons également ajouté un bouton pour soumettre le formulaire. +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`: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Pour envoyer le courrier électronique lui-même, nous créons une nouvelle classe appelée `ContactFacade` et la plaçons dans le fichier `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +La méthode `sendMessage()` créera et enverra le courrier électronique. Pour ce faire, elle utilise ce que l'on appelle un "mailer", qu'elle transmet en tant que dépendance via le constructeur. En savoir plus sur l'[envoi de courriels |mail:]. + +Nous allons maintenant retourner au présentateur et compléter la méthode `contactFormSucceeded()`. Elle appelle la méthode `sendMessage()` de la classe `ContactFacade` et lui transmet les données du formulaire. Et comment obtenir l'objet `ContactFacade`? Il nous sera transmis par le constructeur : + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +Après l'envoi du courrier électronique, nous montrons à l'utilisateur le " [message flash |application:components#flash-messages]", qui confirme que le message a été envoyé, puis nous le redirigeons vers la page suivante, de sorte que le formulaire ne puisse pas être soumis à nouveau en utilisant la fonction *refresh* du navigateur. + + +Si tout fonctionne, vous devriez être en mesure d'envoyer un courriel à partir de votre formulaire de contact. Nous vous félicitons ! + + +Modèle d'e-mail HTML .[#toc-html-email-template] +------------------------------------------------ + +Pour l'instant, un courriel en texte brut contenant uniquement le message envoyé par le formulaire est envoyé. Mais nous pouvons utiliser le HTML dans l'e-mail et le rendre plus attrayant. Nous allons créer un modèle dans Latte, que nous enregistrerons dans `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Il reste à modifier `ContactFacade` pour utiliser ce modèle. Dans le constructeur, nous demandons la classe `LatteFactory`, qui peut produire l'objet `Latte\Engine`, un [moteur de rendu de modèle Latte |latte:develop#how-to-render-a-template]. Nous utilisons la méthode `renderToString()` pour rendre le modèle dans un fichier, le premier paramètre étant le chemin vers le modèle et le second les variables. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Nous passons ensuite l'e-mail HTML généré à la méthode `setHtmlBody()` au lieu de l'original `setBody()`. Nous n'avons pas non plus besoin de spécifier le sujet de l'e-mail dans `setSubject()`, car la bibliothèque l'extrait de l'élément `` dans le modèle. + + +Configuration de .[#toc-configuring] +------------------------------------ + +Dans le code de la classe `ContactFacade`, notre email d'administration `admin@example.com` est encore codé en dur. Il serait préférable de la déplacer dans le fichier de configuration. Comment faire ? + +Tout d'abord, nous modifions la classe `ContactFacade` et remplaçons la chaîne de l'email par une variable passée par le constructeur : + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +Et la deuxième étape consiste à mettre la valeur de cette variable dans la configuration. Dans le fichier `app/config/services.neon` nous ajoutons : + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +Et c'est tout. S'il y a beaucoup d'éléments dans la section `services` et que vous avez l'impression que le courriel se perd parmi eux, nous pouvons en faire une variable. Nous modifierons l'entrée en : + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +et définir cette variable dans le fichier `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +Et c'est fait ! + + +{{sitename: Meilleures pratiques}} diff --git a/best-practices/fr/pagination.texy b/best-practices/fr/pagination.texy index 56b6d0a74a..9cb7e34af0 100644 --- a/best-practices/fr/pagination.texy +++ b/best-practices/fr/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository L'étape suivante consiste à modifier le présentateur. Nous allons transmettre le numéro de la page actuellement affichée à la méthode de rendu. Dans le cas où ce numéro ne fait pas partie de l'URL, nous devons définir la valeur par défaut sur la première page. -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 HomepagePresenter ressemblera à ceci : +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/fr/restore-request.texy b/best-practices/fr/restore-request.texy index cbd17d72e2..a97f5eaab3 100644 --- a/best-practices/fr/restore-request.texy +++ b/best-practices/fr/restore-request.texy @@ -31,9 +31,11 @@ Le présentateur `SignPresenter` contiendra un paramètre persistant `$backlink` ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/hu/@home.texy b/best-practices/hu/@home.texy index a18d377fbd..5d9c323f64 100644 --- a/best-practices/hu/@home.texy +++ b/best-practices/hu/@home.texy @@ -26,6 +26,7 @@ Nyomtatványok ------------- - [Nyomtatványok újrafelhasználása |form-reuse] - [Nyomtatvány rekord létrehozásához és szerkesztéséhez |creating-editing-form] +- [Hozzunk létre egy kapcsolatfelvételi űrlapot |lets-create-contact-form] - [Függő kiválasztó mezők |https://blog.nette.org/hu/fueggo-selectboxok-elegansan-nette-es-tiszta-js-ben] </div> diff --git a/best-practices/hu/composer.texy b/best-practices/hu/composer.texy index d2a075e1ba..a4159ed069 100644 --- a/best-practices/hu/composer.texy +++ b/best-practices/hu/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Frissítés a legújabb verzióra .[#toc-update-to-the-latest-version] -================================================================== +Csomagok frissítése a legújabb verziókra .[#toc-update-packages-to-the-latest-versions] +======================================================================================= Az összes használt csomag frissítéséhez a `composer.json` pontban meghatározott verziókövetelményeknek megfelelően a `composer update` paranccsal frissítheti az összes használt csomagot a legújabb verzióra. Például a `"nette/database": "^3.0"` függőség esetében a parancs a legújabb, 3.x.x.x verziót fogja telepíteni, de a 4-es verziót nem. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project A `name-of-the-project` helyett meg kell adnia a projekt könyvtárának nevét, és végre kell hajtania a parancsot. A Composer le fogja hívni a `nette/web-project` tárolót a GitHubról, amely már tartalmazza a `composer.json` fájlt, és rögtön ezután telepíti magát a Nette keretrendszert. Már csak a `temp/` és a `log/` könyvtárak [írási jogosultságainak ellenőrzése |nette:troubleshooting#setting-directory-permissions] van hátra, és már mehet is. +Ha tudod, hogy a PHP melyik verzióján lesz a projekt, mindenképpen [állítsd be |#PHP Version]. + PHP verzió .[#toc-php-version] ============================== -A Composer mindig azokat a csomagverziókat telepíti, amelyek kompatibilisek a PHP aktuálisan használt verziójával. Ami természetesen nem biztos, hogy ugyanaz a verzió, mint a PHP az Ön webtárhelyén. Ezért hasznos, ha a `composer.json` fájlhoz hozzáadjuk a tárhelyen lévő PHP verziójára vonatkozó információt, és akkor csak a tárhelyen lévő csomagoknak a tárhelyhez kompatibilis verziói lesznek telepítve: +A Composer mindig a csomagok azon verzióit telepíti, amelyek kompatibilisek a PHP aktuálisan használt verziójával (vagy inkább a PHP-nak a parancssorban a Composer futtatásakor használt verziójával). Ami valószínűleg nem ugyanaz a verzió, mint amit a webtárhelye használ. Ezért nagyon fontos, hogy a `composer.json` fájlodhoz hozzáadj információt a tárhelyeden használt PHP verziójáról. Ezután csak a tárhelyével kompatibilis csomagok verziói fognak települni. + +Például, ha a projektet a PHP 8.2.3-as verzióján szeretné futtatni, használja a következő parancsot: + +```shell +composer config platform.php 8.2.3 +``` + +Így íródik a verzió a `composer.json` fájlba: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +A PHP verziószáma azonban a fájlban máshol is szerepel, a `require` szakaszban. Míg az első szám azt a verziót adja meg, amelyhez a csomagokat telepíteni kell, addig a második szám azt mondja meg, hogy maga az alkalmazás milyen verzióra íródott. +(Természetesen nincs értelme, hogy ezek a verziók különbözőek legyenek, így a dupla bejegyzés csak redundancia.) Ezt a verziót a paranccsal állítjuk be: + +```shell +composer require php 8.2.3 --no-update +``` + +Vagy közvetlenül a `composer.json` fájlban: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Hamis jelentések .[#toc-false-reports] +====================================== + +A csomagok frissítésekor vagy a verziószámok megváltoztatásakor konfliktusok fordulnak elő. Az egyik csomagnak vannak olyan követelményei, amelyek ütköznek egy másikkal, és így tovább. A Composer azonban időnként hamis üzeneteket ír ki. Olyan konfliktust jelent, amely valójában nem létezik. Ebben az esetben segít, ha törli a `composer.lock` fájlt, és újra megpróbálja. + +Ha a hibaüzenet továbbra is fennáll, akkor komolyan gondolja, és ki kell olvasni belőle, hogy mit és hogyan kell módosítani. + Packagist.org - Globális tárolóhely .[#toc-packagist-org-global-repository] =========================================================================== diff --git a/best-practices/hu/dynamic-snippets.texy b/best-practices/hu/dynamic-snippets.texy index 22c6857ab9..508065649e 100644 --- a/best-practices/hu/dynamic-snippets.texy +++ b/best-practices/hu/dynamic-snippets.texy @@ -51,7 +51,7 @@ A Latte terminológiában a dinamikus snippet a `{snippet}` címke egy speciáli <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/hu/editors-and-tools.texy b/best-practices/hu/editors-and-tools.texy index 4819d8ea71..9d0a0f1cc6 100644 --- a/best-practices/hu/editors-and-tools.texy +++ b/best-practices/hu/editors-and-tools.texy @@ -30,7 +30,7 @@ A PHPStan egy olyan eszköz, amely még a futtatás előtt felismeri a logikai h Telepítse a Composer segítségével: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: Majd hagyja, hogy elemezze a `app/` mappában lévő osztályokat: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/hu/form-reuse.texy b/best-practices/hu/form-reuse.texy index 75bb988bc5..9245624d97 100644 --- a/best-practices/hu/form-reuse.texy +++ b/best-practices/hu/form-reuse.texy @@ -2,62 +2,217 @@ Formanyomtatványok újrafelhasználása több helyen ************************************************ .[perex] -Hogyan lehet ugyanazt az űrlapot több helyen újra felhasználni, és nem duplikálni a kódot? A Nette-ben ez nagyon könnyen megoldható, és többféle módszer közül választhat. +A Nette-ben több lehetőséged is van arra, hogy ugyanazt az űrlapot több helyen újra felhasználd anélkül, hogy a kódot duplikálnád. Ebben a cikkben áttekintjük a különböző megoldásokat, beleértve azokat is, amelyeket érdemes elkerülni. Form Factory .[#toc-form-factory] ================================= -Hozzunk létre egy olyan osztályt, amely képes űrlapot létrehozni. Az ilyen osztályt gyárnak nevezzük. Azon a helyen, ahol az űrlapot használni akarjuk (pl. a prezenterben), kérjük a [gyárat függőségként |dependency-injection:passing-dependencies]. +Egy komponens több helyen történő használatának egyik alapvető megközelítése, hogy létrehozunk egy metódust vagy osztályt, amely létrehozza a komponenst, majd ezt a metódust az alkalmazás különböző helyein hívjuk meg. Az ilyen metódust vagy osztályt *gyárnak* nevezzük. Kérjük, ne keverjük össze a *factory method* tervezési mintával, amely a gyárak használatának egy speciális módját írja le, és nem kapcsolódik ehhez a témához. -A factory része az a kód, amely az űrlap sikeres elküldése után átadja az adatokat a további feldolgozáshoz. Általában a modell rétegnek. Azt is ellenőrzi, hogy minden rendben ment-e, és az esetleges hibákat [visszaadja |forms:validation#Processing-errors] az űrlapnak. A modellt a következő példában a `Facade` osztály képviseli: +Példaként hozzunk létre egy gyárat, amely egy szerkesztési űrlapot készít: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // itt további űrlapmezők kerülnek hozzáadásra + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Ezt a gyárat az alkalmazás különböző helyein használhatja, például prezenterekben vagy komponensekben. Ezt pedig úgy tesszük, hogy [függőségként kérjük be |dependency-injection:passing-dependencies]. Tehát először írjuk be az osztályt a konfigurációs fájlba: + +```neon +services: + - FormFactory +``` + +És aztán használjuk a prezenterben: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* paraméterek */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // a küldött adatok feldolgozása + }; + return $form; + } +} +``` + +A form factory-t további metódusokkal bővíthetjük, hogy más típusú űrlapokat hozzunk létre az alkalmazásunknak megfelelően. És természetesen hozzáadhat egy olyan metódust is, amely egy elemek nélküli alap űrlapot hoz létre, amelyet a többi metódus használni fog: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // itt további űrlapmezők kerülnek hozzáadásra + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // elemek hozzáadása az űrlaphoz +A `createForm()` metódus egyelőre semmi hasznosat nem csinál, de ez hamarosan megváltozik. - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Gyári függőségek .[#toc-factory-dependencies] +============================================= + +Idővel nyilvánvalóvá válik, hogy az űrlapoknak többnyelvűnek kell lenniük. Ez azt jelenti, hogy minden űrlaphoz be kell állítanunk egy [fordítót |forms:rendering#Translating]. Ehhez módosítjuk a `FormFactory` osztályt, hogy a konstruktorban függőségként elfogadja a `Translator` objektumot, és átadja azt az űrlapnak: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Mivel a `createForm()` metódust más, konkrét űrlapokat létrehozó metódusok is meghívják, a fordítót csak ebben a metódusban kell beállítanunk. És kész is vagyunk. Nem kell semmilyen prezenter vagy komponens kódot módosítani, ami nagyszerű. + + +További gyári osztályok .[#toc-more-factory-classes] +==================================================== + +Alternatívaként több osztályt is létrehozhat minden egyes űrlaphoz, amelyet az alkalmazásban használni szeretne. +Ez a megközelítés növelheti a kód olvashatóságát és megkönnyítheti az űrlapok kezelését. Hagyja meg az eredeti `FormFactory` címet, hogy csak egy tiszta űrlapot hozzon létre alapvető konfigurációval (például fordítástámogatással), és hozzon létre egy új gyári `EditFormFactory` címet a szerkesztési űrlaphoz. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // űrlap feldolgozása - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ a kompozíció használata +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // itt további űrlapmezők kerülnek hozzáadásra + $form->addSubmit('send', 'Save'); + return $form; } } ``` -A gyár természetesen lehet parametrikus, azaz kaphat olyan paramétereket, amelyek befolyásolják a létrehozandó űrlap megjelenését. +Nagyon fontos, hogy a `FormFactory` és a `EditFormFactory` osztályok közötti kötés kompozícióval, nem pedig objektumörökléssel valósuljon meg: -Most bemutatjuk a factory átadását a prezenternek. Először is írjuk be a konfigurációs fájlba: - -```neon -services: - - EditFormFactory +```php +// ⛔ NO! AZ ÖRÖKSÉG NEM TARTOZIK IDE +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // további űrlapmezők kerülnek ide + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -Majd pedig bekérjük a prezenterben. Ott is következik a következő lépés a beküldött űrlap feldolgozása, és ez az átirányítás a következő oldalra: +Az öröklés használata ebben az esetben teljesen kontraproduktív lenne. Nagyon gyorsan problémákba ütközne. Például, ha paramétereket akarnánk hozzáadni a `create()` metódushoz; a PHP hibát jelezne, hogy a metódus aláírása eltér a szülőétől. +Vagy ha a konstruktoron keresztül átadnánk egy függőséget a `EditFormFactory` osztálynak. Ez azt okozná, amit mi [konstruktor pokolnak |dependency-injection:passing-dependencies#Constructor hell] hívunk. + +Általánosságban elmondható, hogy jobb a kompozíciót előnyben részesíteni az örökléssel szemben. + + +Form kezelés .[#toc-form-handling] +================================== + +A sikeres elküldés után meghívott űrlapkezelő lehet egy gyári osztály része is. Ez úgy fog működni, hogy a beküldött adatokat átadja a modellnek feldolgozásra. Az esetleges hibákat [visszaadja |forms:validation#Processing Errors] az űrlapnak. A következő példában a modellt a `Facade` osztály képviseli: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // itt további űrlapmezők kerülnek hozzáadásra + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // a beküldött adatok feldolgozása + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Hagyjuk, hogy a prezenter maga kezelje az átirányítást. A `onSuccess` eseményhez hozzáad egy másik kezelőt, amely elvégzi az átirányítást. Ez lehetővé teszi, hogy az űrlapot különböző prezenterekben lehessen használni, és mindegyik más helyre irányíthasson át. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Mivel az átirányítást a bemutató kezelője kezeli, a komponens több helyen is használható, és mindegyik helyen máshová irányítható át. +Ez a megoldás kihasználja az űrlapok azon tulajdonságát, hogy amikor a `addError()` meghívásra kerül egy űrlapon vagy annak elemén, a következő `onSuccess` kezelő nem hívódik meg. + +Öröklés a Form osztályból .[#toc-inheriting-from-the-form-class] +================================================================ -Komponens űrlappal .[#toc-component-with-form] -============================================== +Egy épített űrlap nem lehet egy űrlap gyermeke. Más szóval, ne használja ezt a megoldást: -Egy másik lehetőség egy új [komponens |application:components] létrehozása, amely egy űrlapot tartalmaz. Ez lehetőséget ad arra, hogy például az űrlapot meghatározott módon rendereljük, mivel a komponens tartalmaz egy sablont. -Vagy használhatunk jeleket az AJAX-kommunikációhoz és az információk betöltéséhez az űrlapba, például az automatikus kitöltéshez stb. +```php +// ⛔ NO! AZ ÖRÖKSÉG NEM TARTOZIK IDE +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // további űrlapmezők kerülnek ide + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Ahelyett, hogy az űrlapot a konstruktorban építenéd fel, használd a gyárat. + +Fontos tisztában lenni azzal, hogy a `Form` osztály elsősorban egy űrlap összeállításának eszköze, azaz egy űrlapépítő. Az összerakott űrlap pedig a termékének tekinthető. A termék azonban nem az építő speciális esete; nincs köztük *is a* kapcsolat, ami az öröklés alapját képezi. + + +Form komponens .[#toc-form-component] +===================================== + +Egy teljesen más megközelítés egy olyan [komponens |application:components] létrehozása, amely egy űrlapot tartalmaz. Ez új lehetőségeket ad, például az űrlap meghatározott módon történő megjelenítésére, mivel a komponens tartalmaz egy sablont. +Vagy jeleket lehet használni az AJAX-kommunikációhoz és az információk betöltéséhez az űrlapba, például a hintinghez stb. ```php @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // elemek hozzáadása az űrlaphoz - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // itt további űrlapmezők kerülnek hozzáadásra + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // űrlap feldolgozása - $this->facade->process($values); + // a beküldött adatok feldolgozása + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,13 +304,12 @@ class EditControl extends Nette\Application\UI\Control } // eseményhívás - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -Ezután létrehozzuk a gyárat, amely ezt a komponenst fogja előállítani. Csak [írjuk meg az interfészét |application:components#Components with Dependencies]: - +Hozzunk létre egy gyárat, amely ezt a komponenst fogja előállítani. Elég, ha [megírjuk az interfészét |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -148,7 +325,7 @@ services: - EditControlFactory ``` -És most már igényelhetjük a gyárat, és használhatjuk a prezenterben: +És most már kérhetjük a gyárat, és használhatjuk a prezenterben: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -165,7 +342,7 @@ class MyPresenter extends Nette\Application\UI\Presenter $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); // vagy átirányítás a szerkesztés eredményére, pl.: - // $this->redirect('detail', ['id' => $data->id]); + // $this->redirect('részlet', ['id' => $data->id]); }; return $control; @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/hu/inject-method-attribute.texy b/best-practices/hu/inject-method-attribute.texy index 3caedc9e66..212606a7d2 100644 --- a/best-practices/hu/inject-method-attribute.texy +++ b/best-practices/hu/inject-method-attribute.texy @@ -2,13 +2,20 @@ Injektálási módszerek és attribútumok ************************************* .[perex] -Konkrét példákon keresztül megnézzük a függőségek átadásának lehetőségeit a prezentereknek, és elmagyarázzuk a `inject` metódusokat és attribútumokat/annotációkat. +Ebben a cikkben a Nette keretrendszerben a függőségek bemutatóknak való átadásának különböző módjaira fogunk összpontosítani. Összehasonlítjuk az előnyben részesített módszert, azaz a konstruktort, más lehetőségekkel, például a `inject` módszerekkel és attribútumokkal. + +A prezenterek esetében is a függőségek átadása a [konstruktor |dependency-injection:passing-dependencies#Constructor Injection] segítségével az előnyben részesített mód. +Ha azonban létrehozunk egy közös őst, amelytől más prezenterek is örökölnek (pl. BasePresenter), és ez az ős is rendelkezik függőségekkel, akkor felmerül egy probléma, amelyet [konstruktorpokolnak |dependency-injection:passing-dependencies#Constructor hell] nevezünk. +Ez megkerülhető alternatív módszerekkel, amelyek közé tartoznak az injektáló metódusok és attribútumok (annotációk). `inject*()` Módszerek .[#toc-inject-methods] ============================================ -A prezenterben, mint minden más kódban, a függőségek átadásának előnyös módja a [konstruktor |dependency-injection:passing-dependencies#Constructor Injection] használata. Ha azonban a prezenter egy közös őstől (pl. `BasePresenter`) örököl, akkor jobb, ha a `inject*()` metódusait használjuk ebben az ősben. Ez a setter speciális esete, ahol a metódus a `inject` előtaggal kezdődik. Ennek oka, hogy a konstruktort szabadon tartjuk a leszármazottak számára: +Ez a függőségi átadás egy formája [a setterek |dependency-injection:passing-dependencies#Setter Injection] használatával. Ezeknek a beállítóknak a neve az inject előtaggal kezdődik. +A Nette DI automatikusan meghívja az ilyen nevű metódusokat közvetlenül a prezentáló példány létrehozása után, és átadja az összes szükséges függőséget. Ezért ezeket a metódusokat nyilvánosnak kell deklarálni. + +`inject*()` A metódusok egyfajta konstruktor-bővítésnek tekinthetők több metódusra. Ennek köszönhetően a `BasePresenter` átveheti a függőségeket egy másik metóduson keresztül, és a konstruktort szabadon hagyhatja a leszármazottak számára: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Az alapvető különbség a setterhez képest az, hogy a Nette DI automatikusan meghívja az így elnevezett metódusokat a prezenterekben, amint a példány létrejön, és átadja nekik az összes szükséges függőséget. Egy prezenter több metódust is tartalmazhat `inject*()` és minden metódusnak tetszőleges számú paramétere lehet. - -Ha a függőségeket az ősöknek a konstruktoraikon keresztül adnánk át, akkor az összes leszármazottban meg kellene szereznünk a függőségeket, és át kellene adnunk a `parent::__construct()`, ami bonyolítja a kódot: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // ez egy komplikáció - $this->bar = $bar; - } -} -``` - -A `inject*()` metódusok olyan esetekben is hasznosak, amikor a bemutató [vonásokból áll |presenter-traits], és mindegyiknek szüksége van a saját függőségére. +A prezenter tetszőleges számú `inject*()` metódust tartalmazhat, és mindegyiknek tetszőleges számú paramétere lehet. Ez olyan esetekben is nagyszerű, amikor a prezenter [vonásokból áll |presenter-traits], és mindegyiknek saját függőségre van szüksége. -Lehetőség van a `@inject` megjegyzések használatára is, de fontos szem előtt tartani, hogy a kapszulázás megszakad. +`Inject` Attribútumok .[#toc-inject-attributes] +=============================================== -`Inject` Annotációk .[#toc-inject-annotations] -============================================== - -Ez a függőség automatikus átadása a bemutató nyilvános tagváltozójának, amely a dokumentációs megjegyzésben a `@inject` megjegyzéssel van ellátva. A típus a dokumentációs megjegyzésben is megadható, ha 7.4-nél alacsonyabb PHP-t használsz. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Ez a [tulajdonságokba való befecskendezés |dependency-injection:passing-dependencies#Property Injection] egy formája. Elegendő megadni, hogy mely tulajdonságokat kell befecskendezni, és a Nette DI automatikusan átadja a függőségeket közvetlenül a prezentáló példány létrehozása után. A beillesztéshez szükséges, hogy publicként deklaráljuk őket. -A PHP 8.0 óta egy tulajdonságot a `Inject` attribútummal is lehet jelölni: +A tulajdonságokat egy attribútummal jelöljük: (korábban a `/** @inject */` megjegyzést használtuk) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // ez a sor fontos class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -A Nette DI ismét automatikusan átadja a függőségeket az ilyen módon megjegyzett tulajdonságoknak a prezenterben, amint a példányt létrehozzák. +A függőségek átadásának ezen módszerének előnye a nagyon gazdaságos jelölési forma volt. A [konstruktori tulajdonságok promóciójának |https://blog.nette.org/hu/php-8-0-teljes-attekintes-az-ujdonsagokrol#toc-constructor-property-promotion] bevezetésével azonban a konstruktor használata egyszerűbbnek tűnik. -Ennek a módszernek ugyanazok a hiányosságai vannak, mint a függőségek átadásának egy nyilvános tulajdonsághoz. Azért használjuk a prezenterben, mert nem bonyolítja a kódot, és csak minimális gépelést igényel. +Másrészt ez a módszer ugyanazokkal a hiányosságokkal küzd, mint a függőségek tulajdonságokba történő átadása általában: nincs kontrollunk a változó változásai felett, ugyanakkor a változó az osztály nyilvános interfészének részévé válik, ami nem kívánatos. {{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/hu/lets-create-contact-form.texy b/best-practices/hu/lets-create-contact-form.texy new file mode 100644 index 0000000000..0f6e9b14d8 --- /dev/null +++ b/best-practices/hu/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Hozzunk létre egy kapcsolatfelvételi űrlapot +******************************************** + +.[perex] +Nézzük meg, hogyan hozhatunk létre egy kapcsolatfelvételi űrlapot a Nette-ben, beleértve annak e-mailben történő elküldését is. Akkor csináljuk! + +Először is létre kell hoznunk egy új projektet. Ahogy a [Kezdő lépések |nette:installation] oldal elmagyarázza. Ezután pedig elkezdhetjük létrehozni az űrlapot. + +A legegyszerűbb, ha [közvetlenül a Presenterben |forms:in-presenter] hozzuk létre az [űrlapot |forms:in-presenter]. Használhatjuk az előre elkészített `HomePresenter`. Hozzáadjuk az űrlapot reprezentáló `contactForm` komponenst. Ezt úgy tesszük, hogy a `createComponentContactForm()` gyári metódust írjuk be a komponens előállítását végző kódba: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Mint látható, két metódust hoztunk létre. Az első metódus `createComponentContactForm()` létrehoz egy új űrlapot. Ez rendelkezik a név, az e-mail és az üzenet mezőivel, amelyeket a `addText()`, `addEmail()` és `addTextArea()` metódusokkal adunk hozzá. Hozzáadtunk egy gombot is az űrlap elküldéséhez. +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: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Magához az e-mail elküldéséhez hozzunk létre egy új osztályt `ContactFacade` néven, és helyezzük el a `app/Model/ContactFacade.php` fájlban: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +A `sendMessage()` metódus fogja létrehozni és elküldeni az e-mailt. Ehhez egy úgynevezett mailert használ, amelyet függőségként ad át a konstruktoron keresztül. Olvasson többet az [e-mailek küldéséről |mail:]. + +Most visszamegyünk a prezenterhez, és befejezzük a `contactFormSucceeded()` metódust. Meghívja a `ContactFacade` osztály `sendMessage()` metódusát, és átadja neki az űrlap adatait. És hogyan kapjuk meg a `ContactFacade` objektumot ? A konstruktor fogja átadni nekünk: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +Az e-mail elküldése után megjelenítjük a felhasználónak az úgynevezett [flash üzenetet |application:components#flash-messages], amely megerősíti, hogy az üzenet elküldésre került, majd átirányítjuk a következő oldalra, hogy az űrlapot ne lehessen újra elküldeni a böngésző *frissítésével*. + + +Nos, ha minden működik, akkor a kapcsolatfelvételi űrlapról már tudsz e-mailt küldeni. Gratulálunk! + + +HTML e-mail sablon .[#toc-html-email-template] +---------------------------------------------- + +Egyelőre egy egyszerű szöveges e-mailt küldünk, amely csak az űrlap által küldött üzenetet tartalmazza. De használhatunk HTML-t az e-mailben, és vonzóbbá tehetjük azt. Létrehozunk hozzá egy sablont a Latte-ban, amelyet a `app/Model/contactEmail.latte` címre mentünk el: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Már csak a `ContactFacade` -t kell módosítani, hogy ezt a sablont használhassuk. A konstruktorban kérjük a `LatteFactory` osztályt, amely képes előállítani a `Latte\Engine` objektumot, egy [Latte sablon renderelőt |latte:develop#how-to-render-a-template]. A `renderToString()` metódust használjuk a sablon renderelésére egy fájlba, az első paraméter a sablon elérési útvonala, a második pedig a változók. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Ezután a generált HTML e-mailt átadjuk a `setHtmlBody()` metódusnak az eredeti `setBody()` helyett. Az e-mail tárgyát sem kell megadnunk a `setSubject()` metódusban, mert a könyvtár azt az elemből veszi át. `` sablonból veszi át. + + +A konfigurálása .[#toc-configuring] +------------------------------------ + +A `ContactFacade` osztály kódjában az admin e-mail címünk, a `admin@example.com` még mindig keményen kódolva van. Jobb lenne, ha áthelyeznénk a konfigurációs fájlba. Hogyan kell ezt megtenni? + +Először is módosítjuk a `ContactFacade` osztályt, és az email karakterláncot a konstruktor által átadott változóval helyettesítjük: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +A második lépés pedig az, hogy ennek a változónak az értékét a konfigurációba helyezzük. A `app/config/services.neon` fájlban hozzáadjuk: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +És ennyi. Ha sok elem van a `services` részben, és úgy érezzük, hogy az e-mail elveszik közöttük, akkor változtathatóvá tehetjük. Módosítjuk a bejegyzést: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +És definiáljuk ezt a változót a `app/config/common.neon` fájlban: + +```neon +parameters: + adminEmail: admin@example.com +``` + +És kész! + + +{{sitename: Legjobb gyakorlatok}} diff --git a/best-practices/hu/pagination.texy b/best-practices/hu/pagination.texy index cb1e94513f..eee3c4024b 100644 --- a/best-practices/hu/pagination.texy +++ b/best-practices/hu/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository A következő lépés a bemutató szerkesztése. Az aktuálisan megjelenített oldal számát továbbítjuk a render metódusnak. Abban az esetben, ha ez a szám nem része az URL-nek, akkor az alapértelmezett értéket az első oldalra kell beállítanunk. -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 HomepagePresenter így fog kinézni: +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/hu/restore-request.texy b/best-practices/hu/restore-request.texy index 699a097b57..c1f770beba 100644 --- a/best-practices/hu/restore-request.texy +++ b/best-practices/hu/restore-request.texy @@ -31,9 +31,11 @@ A `SignPresenter` prezenter a bejelentkezési űrlapon kívül tartalmazni fog e ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/it/@home.texy b/best-practices/it/@home.texy index a828a46bce..10f16cec1d 100644 --- a/best-practices/it/@home.texy +++ b/best-practices/it/@home.texy @@ -26,6 +26,7 @@ Moduli ------ - [Riutilizzo dei moduli |form-reuse] - [Modulo per la creazione e la modifica di un record |creating-editing-form] +- [Creiamo un modulo di contatto |lets-create-contact-form] - [Caselle di selezione dipendenti |https://blog.nette.org/it/caselle-di-selezione-dipendenti-in-modo-elegante-in-nette-e-puro-js] </div> diff --git a/best-practices/it/composer.texy b/best-practices/it/composer.texy index 4eb632ac29..c30fc6d908 100644 --- a/best-practices/it/composer.texy +++ b/best-practices/it/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Aggiornamento alla versione più recente .[#toc-update-to-the-latest-version] -============================================================================ +Aggiornare i pacchetti alle ultime versioni .[#toc-update-packages-to-the-latest-versions] +========================================================================================== Per aggiornare tutti i pacchetti utilizzati all'ultima versione in base ai vincoli di versione definiti in `composer.json`, usare il comando `composer update`. Ad esempio, per la dipendenza `"nette/database": "^3.0"` verrà installata l'ultima versione 3.x.x, ma non la versione 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project Al posto di `name-of-the-project` si deve fornire il nome della directory del progetto ed eseguire il comando. Composer recupererà il repository `nette/web-project` da GitHub, che contiene già il file `composer.json`, e subito dopo installerà il framework Nette. L'unica cosa che resta da fare è [controllare i permessi di scrittura |nette:troubleshooting#setting-directory-permissions] sulle directory `temp/` e `log/` e il gioco è fatto. +Se si conosce la versione di PHP su cui verrà ospitato il progetto, assicurarsi di [impostarla |#PHP Version]. + Versione PHP .[#toc-php-version] ================================ -Composer installa sempre le versioni dei pacchetti compatibili con la versione di PHP attualmente in uso. Che, ovviamente, potrebbe non essere la stessa versione di PHP presente sul vostro host web. Per questo motivo, è utile aggiungere al file `composer.json` informazioni sulla versione di PHP presente sull'host, in modo da installare solo le versioni dei pacchetti compatibili con l'host: +Composer installa sempre le versioni dei pacchetti compatibili con la versione di PHP attualmente in uso (o meglio, la versione di PHP utilizzata dalla riga di comando quando si esegue Composer). Che probabilmente non è la stessa versione utilizzata dal vostro host web. Per questo motivo è molto importante aggiungere al file `composer.json` le informazioni sulla versione di PHP presente sul vostro hosting. In questo modo, verranno installate solo le versioni dei pacchetti compatibili con l'host. + +Per esempio, per impostare il progetto in modo che venga eseguito su PHP 8.2.3, usare il comando: + +```shell +composer config platform.php 8.2.3 +``` + +In questo modo la versione viene scritta nel file `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +Tuttavia, il numero di versione di PHP è elencato anche altrove nel file, nella sezione `require`. Mentre il primo numero specifica la versione per la quale verranno installati i pacchetti, il secondo numero dice per quale versione è stata scritta l'applicazione stessa. +(Naturalmente, non ha senso che queste versioni siano diverse, quindi la doppia indicazione è una ridondanza). La versione viene impostata con il comando: + +```shell +composer require php 8.2.3 --no-update +``` + +Oppure direttamente nel file `composer.json`: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Rapporti falsi .[#toc-false-reports] +==================================== + +Quando si aggiornano i pacchetti o si cambiano i numeri di versione, si verificano dei conflitti. Un pacchetto ha requisiti in conflitto con un altro e così via. Tuttavia, Composer a volte stampa dei falsi messaggi. Segnala un conflitto che in realtà non esiste. In questo caso, è utile cancellare il file `composer.lock` e riprovare. + +Se il messaggio di errore persiste, allora è da intendersi seriamente e bisogna leggere da esso cosa modificare e come. + Packagist.org - Repository globale .[#toc-packagist-org-global-repository] ========================================================================== diff --git a/best-practices/it/dynamic-snippets.texy b/best-practices/it/dynamic-snippets.texy index cc8f9e2bf3..870ac5ebdc 100644 --- a/best-practices/it/dynamic-snippets.texy +++ b/best-practices/it/dynamic-snippets.texy @@ -51,7 +51,7 @@ Nella terminologia di Latte, uno snippet dinamico è un caso d'uso specifico del <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/it/editors-and-tools.texy b/best-practices/it/editors-and-tools.texy index 37b0b52500..bdafe93ca9 100644 --- a/best-practices/it/editors-and-tools.texy +++ b/best-practices/it/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan è uno strumento che rileva gli errori logici nel codice prima di esegui Si installa tramite Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: E poi lasciare che analizzi le classi nella cartella `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/it/form-reuse.texy b/best-practices/it/form-reuse.texy index 97e3ec9e9c..26e0e30dbb 100644 --- a/best-practices/it/form-reuse.texy +++ b/best-practices/it/form-reuse.texy @@ -2,62 +2,217 @@ Riutilizzare i moduli in più luoghi *********************************** .[perex] -Come riutilizzare lo stesso modulo in più punti senza duplicare il codice? Questo è molto facile da fare in Nette e ci sono diversi modi tra cui scegliere. +In Nette, esistono diverse opzioni per riutilizzare lo stesso modulo in più punti senza duplicare il codice. In questo articolo esamineremo le diverse soluzioni, comprese quelle da evitare. Fabbrica di moduli .[#toc-form-factory] ======================================= -Creiamo una classe che possa creare un modulo. Una classe di questo tipo è chiamata factory. Nel luogo in cui vogliamo usare il form (per esempio, nel presentatore), richiediamo il [factory come dipendenza |dependency-injection:passing-dependencies]. +Un approccio di base per utilizzare lo stesso componente in più punti è quello di creare un metodo o una classe che generi il componente e poi richiamare tale metodo in diversi punti dell'applicazione. Un metodo o una classe di questo tipo si chiama *factory*. Non bisogna confondersi con il modello di progettazione *factory method*, che descrive un modo specifico di usare le fabbriche e non è correlato a questo argomento. -Parte del factory è il codice che passa i dati per un'ulteriore elaborazione quando il form viene inviato con successo. Di solito al livello del modello. Inoltre, controlla se tutto è andato bene e [restituisce |forms:validation#Processing-errors] eventuali errori al form. Il modello nell'esempio seguente è rappresentato dalla classe `Facade`: +Come esempio, creiamo un factory che costruisca un form di modifica: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // I campi aggiuntivi del modulo sono aggiunti qui + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Ora è possibile utilizzare questo factory in diversi punti dell'applicazione, ad esempio nei presenter o nei componenti. Per farlo, lo [richiediamo come dipendenza |dependency-injection:passing-dependencies]. Quindi, per prima cosa, scriveremo la classe nel file di configurazione: + +```neon +services: + - FormFactory +``` + +e poi la usiamo nel presentatore: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // elaborazione dei dati inviati + }; + return $form; + } +} +``` + +È possibile estendere il factory di form con metodi aggiuntivi per creare altri tipi di form, in base alle proprie applicazioni. E, naturalmente, si può aggiungere un metodo che crea un modulo di base senza elementi, che verrà utilizzato dagli altri metodi: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // I campi aggiuntivi del modulo sono aggiunti qui + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // aggiungere elementi al form +Il metodo `createForm()` non fa ancora nulla di utile, ma questo cambierà rapidamente. - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Dipendenze della fabbrica .[#toc-factory-dependencies] +====================================================== + +Col tempo, ci si renderà conto che i moduli devono essere multilingue. Ciò significa che dobbiamo impostare un [traduttore |forms:rendering#Translating] per tutti i moduli. Per farlo, modifichiamo la classe `FormFactory` in modo che accetti l'oggetto `Translator` come dipendenza nel costruttore e lo passi al form: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Poiché il metodo `createForm()` viene richiamato anche da altri metodi che creano moduli specifici, dobbiamo impostare il traduttore solo in quel metodo. E il gioco è fatto. Non è necessario modificare il codice del presentatore o del componente, il che è fantastico. + + +Altre classi di fabbrica .[#toc-more-factory-classes] +===================================================== + +In alternativa, è possibile creare più classi per ogni modulo che si desidera utilizzare nell'applicazione. +Questo approccio può aumentare la leggibilità del codice e rendere i moduli più facili da gestire. Lasciate l'originale `FormFactory` per creare solo un form puro con una configurazione di base (ad esempio, con il supporto per la traduzione) e create un nuovo factory `EditFormFactory` per il form di modifica. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // elaborazione del form - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ uso della composizione +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // i campi aggiuntivi del modulo sono aggiunti qui + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Naturalmente, il factory può essere parametrico, cioè può ricevere parametri che influiscono sull'aspetto del form creato. +È molto importante che il legame tra le classi `FormFactory` e `EditFormFactory` sia implementato tramite composizione, non tramite ereditarietà degli oggetti: -Dimostreremo ora di passare il factory al presentatore. Per prima cosa, lo scriviamo nel file di configurazione: - -```neon -services: - - EditFormFactory +```php +// NO! L'EREDITÀ NON È QUI +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // i campi aggiuntivi del modulo sono aggiunti qui + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -e poi lo richiediamo al presentatore. Segue anche la fase successiva di elaborazione del modulo inviato, ovvero il reindirizzamento alla pagina successiva: +L'uso dell'ereditarietà in questo caso sarebbe completamente controproducente. Si incorrerebbe in problemi molto rapidamente. Per esempio, se si volessero aggiungere parametri al metodo `create()`, PHP segnalerebbe un errore perché la sua firma è diversa da quella del genitore. +Oppure quando si passa una dipendenza alla classe `EditFormFactory` tramite il costruttore. Questo causerebbe quello che chiamiamo l'[inferno dei costruttori |dependency-injection:passing-dependencies#Constructor hell]. + +In generale, è meglio preferire la composizione all'ereditarietà. + + +Gestione dei moduli .[#toc-form-handling] +========================================= + +Il gestore del form che viene chiamato dopo un invio riuscito può anche far parte di una classe factory. Funzionerà passando i dati inviati al modello per l'elaborazione. Passerà gli eventuali errori [al |forms:validation#Processing Errors] modulo. Il modello dell'esempio seguente è rappresentato dalla classe `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // i campi aggiuntivi del modulo vengono aggiunti qui + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // elaborazione dei dati inviati + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Lasciamo che sia il presentatore stesso a gestire il reindirizzamento. Aggiungerà un altro gestore all'evento `onSuccess`, che eseguirà il reindirizzamento. Ciò consentirà di utilizzare il modulo in diversi presentatori, ognuno dei quali potrà reindirizzare a una posizione diversa. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Poiché il reindirizzamento è gestito dal gestore del presentatore, il componente può essere utilizzato in più luoghi e reindirizzato altrove in ogni luogo. +Questa soluzione sfrutta la proprietà dei moduli per cui, quando `addError()` viene chiamato su un modulo o su un suo elemento, il gestore successivo `onSuccess` non viene invocato. + +Ereditare dalla classe Form .[#toc-inheriting-from-the-form-class] +================================================================== -Componente con modulo .[#toc-component-with-form] -================================================= +Un modulo costruito non dovrebbe essere figlio di un modulo. In altre parole, non utilizzate questa soluzione: -Un altro modo è quello di creare un nuovo [componente |application:components] che contenga un modulo. Questo ci dà la possibilità di rendere il modulo in un modo specifico, ad esempio, dato che il componente include un modello. -Oppure si possono usare segnali per la comunicazione AJAX e il caricamento di informazioni nel modulo, ad esempio per il completamento automatico, ecc. +```php +// NO! L'EREDITÀ NON È QUI +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // i campi aggiuntivi del modulo sono aggiunti qui + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Invece di costruire il modulo nel costruttore, utilizzare il factory. + +È importante capire che la classe `Form` è principalmente uno strumento per assemblare un modulo, cioè un costruttore di moduli. Il modulo assemblato può essere considerato il suo prodotto. Tuttavia, il prodotto non è un caso specifico del costruttore; non c'è una relazione *è a* tra loro, che è alla base dell'ereditarietà. + + +Componente Form .[#toc-form-component] +====================================== + +Un approccio completamente diverso consiste nel creare un [componente |application:components] che includa un modulo. Questo offre nuove possibilità, ad esempio per rendere il modulo in un modo specifico, dato che il componente include un modello. +Oppure si possono usare segnali per la comunicazione AJAX e il caricamento di informazioni nel modulo, ad esempio per i suggerimenti, ecc. ```php @@ -105,34 +284,32 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // aggiungere elementi al form - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // i campi aggiuntivi del modulo vengono aggiunti qui + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // elaborazione del form - $this->facade->process($values); + // elaborazione dei dati inviati + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); return; } - // invocazione dell'evento - $this->onSave($this, $values); + // invocazione di eventi + $this->onSave($this, $data); } } ``` -Successivamente, creeremo il factory che produrrà questo componente. Basta [scrivere la sua interfaccia |application:components#Components with Dependencies]: - +Creiamo un factory che produca questo componente. È sufficiente [scrivere la sua interfaccia |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,14 +318,14 @@ interface EditControlFactory } ``` -E aggiungerla al file di configurazione: +e aggiungerla al file di configurazione: ```neon services: - EditControlFactory ``` -Ora possiamo richiedere il factory e usarlo nel presenter: +Ora possiamo richiedere il factory e utilizzarlo nel presenter: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -164,7 +341,7 @@ class MyPresenter extends Nette\Application\UI\Presenter $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // oppure reindirizzare al risultato della modifica, ad esempio: + // o reindirizzare al risultato della modifica, ad es: // $this->redirect('detail', ['id' => $data->id]); }; @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Migliori pratiche}} diff --git a/best-practices/it/inject-method-attribute.texy b/best-practices/it/inject-method-attribute.texy index e44bd2721e..2c060f8ed4 100644 --- a/best-practices/it/inject-method-attribute.texy +++ b/best-practices/it/inject-method-attribute.texy @@ -2,13 +2,20 @@ Metodi e attributi di iniezione ******************************* .[perex] -Utilizzando esempi specifici, esamineremo le possibilità di passare le dipendenze ai presentatori e spiegheremo i metodi e gli attributi/animazioni di `inject`. +In questo articolo, ci concentreremo sui vari modi di passare le dipendenze ai presentatori nel framework Nette. Confronteremo il metodo preferito, ovvero il costruttore, con altre opzioni, quali i metodi e gli attributi di `inject`. + +Anche per i presenter, il passaggio delle dipendenze tramite il [costruttore |dependency-injection:passing-dependencies#Constructor Injection] è il metodo preferito. +Tuttavia, se si crea un antenato comune da cui ereditano altri presentatori (ad esempio, BasePresenter) e questo antenato ha anch'esso delle dipendenze, si verifica un problema, chiamato [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. +Questo problema può essere aggirato utilizzando metodi alternativi, che includono l'iniezione di metodi e attributi (annotazioni). `inject*()` Metodi .[#toc-inject-methods] ========================================= -Nel presentatore, come in qualsiasi altro codice, il modo preferito per passare le dipendenze è l'uso del [costruttore |dependency-injection:passing-dependencies#Constructor Injection]. Tuttavia, se il presentatore eredita da un antenato comune (ad esempio, `BasePresenter`), è meglio utilizzare i metodi di `inject*()` in tale antenato. È un caso particolare di setter, in cui il metodo inizia con il prefisso `inject`. Questo perché si mantiene il costruttore libero per i discendenti: +Si tratta di una forma di passaggio di dipendenze che utilizza i [setter |dependency-injection:passing-dependencies#Setter Injection]. I nomi di questi setter iniziano con il prefisso inject. +Nette DI chiama automaticamente questi metodi denominati subito dopo la creazione dell'istanza del presentatore e passa loro tutte le dipendenze richieste. Pertanto, devono essere dichiarati come pubblici. + +`inject*()` I metodi possono essere considerati come una sorta di estensione del costruttore in più metodi. Grazie a ciò, `BasePresenter` può prendere le dipendenze attraverso un altro metodo e lasciare il costruttore libero per i suoi discendenti: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -La differenza fondamentale rispetto a un setter è che Nette DI chiama automaticamente i metodi così denominati nei presenter non appena l'istanza viene creata, passando loro tutte le dipendenze necessarie. Un presentatore può contenere più metodi `inject*()` e ogni metodo può avere un numero qualsiasi di parametri. - -Se passassimo le dipendenze agli antenati attraverso i loro costruttori, dovremmo ottenere le loro dipendenze in tutti i discendenti e passarle a `parent::__construct()`, complicando il codice: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // questa è una complicazione - $this->bar = $bar; - } -} -``` - -I metodi `inject*()` sono utili anche nei casi in cui il presentatore è [composto da tratti |presenter-traits] e ognuno di essi richiede la propria dipendenza. +Il presentatore può contenere un numero qualsiasi di metodi `inject*()` e ognuno può avere un numero qualsiasi di parametri. Questo è ottimo anche per i casi in cui il presentatore è [composto da tratti |presenter-traits] e ognuno di essi richiede la propria dipendenza. -È anche possibile utilizzare l'annotazione `@inject`, ma è importante tenere presente che l'incapsulamento si interrompe. +`Inject` Attributi .[#toc-inject-attributes] +============================================ -`Inject` Le annotazioni .[#toc-inject-annotations] -================================================== - -Si tratta di un passaggio automatico della dipendenza alla variabile membro pubblica del presentatore, annotata con `@inject` nel commento alla documentazione. Il tipo può essere specificato anche nel commento alla documentazione, se si usa PHP inferiore a 7.4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Si tratta di una forma di [iniezione nelle proprietà |dependency-injection:passing-dependencies#Property Injection]. È sufficiente indicare quali proprietà devono essere iniettate e Nette DI passa automaticamente le dipendenze subito dopo aver creato l'istanza del presentatore. Per inserirle, è necessario dichiararle come pubbliche. -Da PHP 8.0, una proprietà può essere contrassegnata con l'attributo `Inject`: +Le proprietà sono contrassegnate da un attributo: (in precedenza, si utilizzava l'annotazione `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // questa riga è importante class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Anche in questo caso, Nette DI passerà automaticamente le dipendenze alle proprietà annotate in questo modo nel presenter, non appena l'istanza viene creata. +Il vantaggio di questo metodo di passaggio delle dipendenze era la sua forma di notazione molto economica. Tuttavia, con l'introduzione della [promozione delle proprietà del costruttore |https://blog.nette.org/it/php-8-0-panoramica-completa-delle-novita#toc-constructor-property-promotion], l'uso del costruttore sembra più semplice. -Questo metodo presenta gli stessi difetti del passaggio delle dipendenze a una proprietà pubblica. Viene utilizzato nel presenter perché non complica il codice e richiede solo un minimo di digitazione. +D'altra parte, questo metodo soffre degli stessi difetti del passaggio delle dipendenze nelle proprietà in generale: non abbiamo alcun controllo sulle modifiche della variabile e, allo stesso tempo, la variabile diventa parte dell'interfaccia pubblica della classe, il che è indesiderabile. {{sitename: Migliori pratiche}} diff --git a/best-practices/it/lets-create-contact-form.texy b/best-practices/it/lets-create-contact-form.texy new file mode 100644 index 0000000000..463ed21c39 --- /dev/null +++ b/best-practices/it/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Creiamo un modulo di contatto +***************************** + +.[perex] +Vediamo come creare un modulo di contatto in Nette, compreso l'invio a un'e-mail. Allora, facciamolo! + +Per prima cosa dobbiamo creare un nuovo progetto. Come spiega la pagina [introduttiva |nette:installation]. Poi possiamo iniziare a creare il modulo. + +Il modo più semplice è creare il [modulo direttamente in Presenter |forms:in-presenter]. Possiamo utilizzare il componente preconfezionato `HomePresenter`. Aggiungeremo il componente `contactForm` che rappresenta il modulo. Per farlo, scriviamo il metodo `createComponentContactForm()` factory nel codice che produrrà il componente: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Come si può vedere, abbiamo creato due metodi. Il primo metodo `createComponentContactForm()` crea un nuovo modulo. Questo ha campi per il nome, l'email e il messaggio, che vengono aggiunti con i metodi `addText()`, `addEmail()` e `addTextArea()`. Abbiamo anche aggiunto un pulsante per inviare il modulo. +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`: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Per inviare l'e-mail stessa, creiamo una nuova classe chiamata `ContactFacade` e la inseriamo nel file `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +Il metodo `sendMessage()` creerà e invierà l'email. Per farlo, utilizza un cosiddetto mailer, che passa come dipendenza attraverso il costruttore. Per saperne di più sull'[invio di e-mail |mail:]. + +Ora, torniamo al presentatore e completiamo il metodo `contactFormSucceeded()`. Richiama il metodo `sendMessage()` della classe `ContactFacade` e gli passa i dati del modulo. Come si ottiene l'oggetto `ContactFacade`? Ce lo passerà il costruttore: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +Dopo l'invio dell'e-mail, mostriamo all'utente il cosiddetto [messaggio flash |application:components#flash-messages], che conferma l'invio del messaggio, e poi reindirizziamo alla pagina successiva, in modo che il modulo non possa essere ripresentato usando *refresh* nel browser. + + +Se tutto funziona, dovreste essere in grado di inviare un'e-mail dal vostro modulo di contatto. Congratulazioni! + + +Modello di e-mail HTML .[#toc-html-email-template] +-------------------------------------------------- + +Per ora, viene inviata un'e-mail di testo semplice contenente solo il messaggio inviato dal modulo. Ma possiamo usare l'HTML nell'e-mail e renderla più attraente. Creeremo un modello in Latte, che salveremo in `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Resta da modificare `ContactFacade` per utilizzare questo modello. Nel costruttore, richiediamo la classe `LatteFactory`, che può produrre l'oggetto `Latte\Engine`, un [renderizzatore di template di Latte |latte:develop#how-to-render-a-template]. Utilizziamo il metodo `renderToString()` per rendere il template in un file; il primo parametro è il percorso del template e il secondo sono le variabili. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Passiamo poi l'email HTML generata al metodo `setHtmlBody()`, invece del metodo originale `setBody()`. Non è necessario specificare l'oggetto dell'email in `setSubject()`, perché la libreria lo prende dall'elemento `` nel template. + + +Configurazione di .[#toc-configuring] +------------------------------------- + +Nel codice della classe `ContactFacade`, l'email di amministrazione `admin@example.com` è ancora codificata in modo rigido. Sarebbe meglio spostarla nel file di configurazione. Come fare? + +Per prima cosa, modifichiamo la classe `ContactFacade` e sostituiamo la stringa dell'email con una variabile passata dal costruttore: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +Il secondo passo consiste nell'inserire il valore di questa variabile nella configurazione. Nel file `app/config/services.neon` aggiungiamo: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +E questo è tutto. Se ci sono molte voci nella sezione `services` e si ha la sensazione che l'e-mail si perda tra di esse, possiamo renderla una variabile. Modificheremo la voce in: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +E definiamo questa variabile nel file `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +Ed è fatta! + + +{{sitename: Migliori pratiche}} diff --git a/best-practices/it/pagination.texy b/best-practices/it/pagination.texy index a046778ed4..f88acbacb5 100644 --- a/best-practices/it/pagination.texy +++ b/best-practices/it/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Il passo successivo è modificare il presentatore. Inoltreremo il numero della pagina attualmente visualizzata al metodo render. Nel caso in cui questo numero non faccia parte dell'URL, occorre impostare il valore predefinito alla prima pagina. -Espandiamo inoltre il metodo render per ottenere l'istanza di Paginator, impostandola e selezionando gli articoli corretti da visualizzare nel template. HomepagePresenter avrà questo aspetto: +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/it/restore-request.texy b/best-practices/it/restore-request.texy index 9437c8859d..bb870fcc84 100644 --- a/best-practices/it/restore-request.texy +++ b/best-practices/it/restore-request.texy @@ -31,9 +31,11 @@ Il presentatore `SignPresenter` conterrà un parametro persistente `$backlink` i ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/pl/@home.texy b/best-practices/pl/@home.texy index 9259972a81..32416cbcfb 100644 --- a/best-practices/pl/@home.texy +++ b/best-practices/pl/@home.texy @@ -26,6 +26,7 @@ Formularze ---------- - [Ponowne wykorzystanie formularzy |form-reuse] - [Formularz do tworzenia i edycji rekordu |creating-editing-form] +- [Stwórzmy formularz kontaktowy |lets-create-contact-form] - [Zależne pola wyboru |https://blog.nette.org/pl/zalezne-selectboxy-elegancko-w-nette-i-czysty-js] </div> diff --git a/best-practices/pl/composer.texy b/best-practices/pl/composer.texy index 5b3cbade87..f208932801 100644 --- a/best-practices/pl/composer.texy +++ b/best-practices/pl/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Aktualizacja do najnowszych wersji .[#toc-update-to-the-latest-version] -======================================================================= +Aktualizacja pakietów do najnowszych wersji .[#toc-update-packages-to-the-latest-versions] +========================================================================================== Polecenie `composer update` jest odpowiedzialne za aktualizację używanych bibliotek do najnowszych wersji zgodnie z warunkami określonymi w `composer.json`. Na przykład dla zależności `"nette/database": "^3.0"` zainstaluje najnowszą wersję 3.x.x, ale już nie wersję 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project nazev-projektu Wpisz nazwę katalogu dla swojego projektu jako `nazev-projektu` i zatwierdź. Composer pobiera z GitHuba repozytorium `nette/web-project`, które zawiera już `composer.json`, a następnie Nette Framework. Powinieneś tylko [ustawić uprawnienia |nette:troubleshooting#Setting-Directory-Permissions] do [zapisu |nette:troubleshooting#Setting-Directory-Permissions] na `temp/` i `log/` i twój projekt powinien ożyć. +Jeśli wiesz, na jakiej wersji PHP będzie hostowany projekt, koniecznie [go |#PHP Version] skonfiguruj. + Wersja PHP .[#toc-php-version] ============================== -Composer zawsze instaluje wersje pakietów, które są zgodne z wersją PHP, której aktualnie używasz. Co oczywiście może nie być tą samą wersją, której używa twój hosting. Dlatego warto dodać do pliku `composer.json` informację o wersji PHP na Twoim hostingu, a wtedy zainstalowane zostaną tylko wersje pakietów zgodne z Twoim hostingiem: +Composer zawsze instaluje wersje pakietów, które są kompatybilne z wersją PHP, której aktualnie używasz (lub raczej z wersją PHP używaną w linii poleceń, gdy uruchamiasz Composera). Co prawdopodobnie nie jest tą samą wersją, której używa Twój hosting. Dlatego bardzo ważne jest dodanie informacji o wersji PHP na Twoim hostingu do pliku `composer.json`. Następnie zostaną zainstalowane tylko wersje pakietów zgodne z hostem. + +Na przykład, aby ustawić projekt tak, aby działał na PHP 8.2.3, użyj polecenia: + +```shell +composer config platform.php 8.2.3 +``` + +W ten sposób wersja jest zapisywana do pliku `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # verze PHP na hostingu + "php": "8.2.3" } } } ``` +Jednakże, numer wersji PHP jest również podany w innym miejscu pliku, w sekcji `require`. Podczas gdy pierwszy numer określa wersję, dla której zostaną zainstalowane pakiety, drugi mówi, dla jakiej wersji została napisana sama aplikacja. +(Oczywiście nie ma sensu, aby te wersje były różne, więc podwójny wpis jest redundancją). Wersję tę ustawiasz poleceniem: + +```shell +composer require php 8.2.3 --no-update +``` + +Lub bezpośrednio w pliku `composer.json`: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Fałszywe raporty .[#toc-false-reports] +====================================== + +Podczas aktualizacji pakietów lub zmiany numerów wersji zdarzają się konflikty. Jeden pakiet ma wymagania, które są sprzeczne z innym i tak dalej. Jednakże, Composer czasami drukuje fałszywe wiadomości. Zgłasza konflikt, który tak naprawdę nie istnieje. W takim przypadku pomaga usunięcie pliku `composer.lock` i ponowna próba. + +Jeśli komunikat o błędzie utrzymuje się, to znaczy, że jest on poważny i trzeba z niego wyczytać, co i jak należy zmodyfikować. + Packagist.org - centralne repozytorium .[#toc-packagist-org-global-repository] ============================================================================== diff --git a/best-practices/pl/dynamic-snippets.texy b/best-practices/pl/dynamic-snippets.texy index 897920ade0..c1ad93724d 100644 --- a/best-practices/pl/dynamic-snippets.texy +++ b/best-practices/pl/dynamic-snippets.texy @@ -51,7 +51,7 @@ W terminologii Latte, dynamiczny snippet jest szczególnym przypadkiem użycia m <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>to se mi líbí</a> {else} diff --git a/best-practices/pl/editors-and-tools.texy b/best-practices/pl/editors-and-tools.texy index 74c3c6c6fc..fee21ea5bc 100644 --- a/best-practices/pl/editors-and-tools.texy +++ b/best-practices/pl/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan to narzędzie, które wykrywa błędy logiczne w Twoim kodzie, zanim go Instalujemy go za pomocą programu Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: A potem niech analizuje klasy w folderze `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/pl/form-reuse.texy b/best-practices/pl/form-reuse.texy index c453e2c40c..46ee665dec 100644 --- a/best-practices/pl/form-reuse.texy +++ b/best-practices/pl/form-reuse.texy @@ -1,63 +1,218 @@ -Ponowne wykorzystanie formularzy w wielu miejscach -************************************************** +Ponowne użycie formularzy w wielu miejscach +******************************************* .[perex] -Jak ponownie wykorzystać ten sam formularz w wielu miejscach i nie powielać kodu? W Nette jest to naprawdę proste i masz więcej sposobów do wyboru. +W Nette masz kilka opcji, aby ponownie wykorzystać ten sam formularz w wielu miejscach bez duplikowania kodu. W tym artykule omówimy różne rozwiązania, w tym te, których powinieneś unikać. -Fabryka Form .[#toc-form-factory] -================================= +Fabryka formularzy .[#toc-form-factory] +======================================= -Stwórzmy klasę, która potrafi stworzyć formularz. Taka klasa nazywana jest fabryką. W miejscu, w którym chcemy użyć formularza (np. w prezenterze), [żądamy |dependency-injection:passing-dependencies] fabryki [jako zależności |dependency-injection:passing-dependencies]. +Jednym z podstawowych podejść do używania tego samego komponentu w wielu miejscach jest stworzenie metody lub klasy, która generuje komponent, a następnie wywołanie tej metody w różnych miejscach w aplikacji. Taka metoda lub klasa nazywana jest *factory*. Proszę nie mylić z wzorcem projektowym *factory method*, który opisuje specyficzny sposób korzystania z fabryk i nie jest związany z tym tematem. -Część fabryki to kod, który przekazuje dane do dalszego przetwarzania, gdy formularz zostanie pomyślnie przesłany. Zazwyczaj do warstwy modelu. Sprawdza również, czy wszystko poszło dobrze i [przekazuje |forms:validation#Processing-Errors] wszelkie błędy z [powrotem |forms:validation#Processing-Errors] do formularza. Model w poniższym przykładzie jest reprezentowany przez klasę `Facade`: +Jako przykład, stwórzmy fabrykę, która zbuduje formularz edycji: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // dodatkowe pola formularza są dodane tutaj + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Teraz można użyć tej fabryki w różnych miejscach w aplikacji, na przykład w prezenterach lub komponentach. A zrobimy to poprzez [zażądanie jej jako zależności |dependency-injection:passing-dependencies]. Więc najpierw zapiszemy klasę do pliku konfiguracyjnego: + +```neon +services: + - FormFactory +``` + +A potem używamy jej w prezenterze: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parametry */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // przetwarzanie przesłanych danych + }; + return $form; + } +} +``` + +Możesz rozszerzyć fabrykę formularzy o dodatkowe metody, aby stworzyć inne typy formularzy, aby dopasować je do swojej aplikacji. I, oczywiście, możesz dodać metodę, która tworzy podstawowy formularz bez elementów, z którego będą korzystać inne metody: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // dodatkowe pola formularza są dodane tutaj + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // přidáme prvky do formuláře +Metoda `createForm()` nie robi jeszcze nic użytecznego, ale to się szybko zmieni. - $form->addSubmit('send', 'Odeslat'); - $form->onSuccess[] = [$this, 'processForm']; +Zależności fabryczne .[#toc-factory-dependencies] +================================================= + +Z czasem okaże się, że potrzebujemy, aby formularze były wielojęzyczne. Oznacza to, że musimy skonfigurować [translator |forms:rendering#Translating] dla wszystkich formularzy. Aby to zrobić, modyfikujemy klasę `FormFactory`, aby zaakceptowała obiekt `Translator` jako zależność w konstruktorze i przekazała go do formularza: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Ponieważ metoda `createForm()` jest wywoływana również przez inne metody tworzące konkretne formularze, musimy ustawić translator tylko w tej metodzie. I gotowe. Nie trzeba zmieniać żadnego kodu prezentera lub komponentu, co jest świetne. + + +Więcej klas fabrycznych .[#toc-more-factory-classes] +==================================================== + +Alternatywnie, możesz stworzyć wiele klas dla każdego formularza, który chcesz użyć w swojej aplikacji. +Takie podejście może zwiększyć czytelność kodu i ułatwić zarządzanie formularzami. Pozostaw oryginalną `FormFactory`, aby stworzyć tylko czysty formularz z podstawową konfiguracją (na przykład z obsługą tłumaczeń) i utwórz nową fabrykę `EditFormFactory` dla formularza edycji. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // zpracování formuláře - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ użycie kompozycji +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // dodatkowe pola formularza są dodawane tutaj + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Fabryka może być oczywiście parametryczna, tzn. może przyjmować parametry, które wpływają na tworzony przez nią formularz. +Bardzo ważne jest, że wiązanie między klasami `FormFactory` i `EditFormFactory` jest realizowane przez kompozycję, a nie dziedziczenie obiektów: -Teraz pokażemy, jak przekazać fabrykę do prezentera. Najpierw zapisujemy ją do pliku konfiguracyjnego: - -```neon -services: - - EditFormFactory +```php +// ⛔ NIE! DZIEDZICZENIE NIE NALEŻY DO TEGO MIEJSCA +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // tutaj dodaje się dodatkowe pola formularza + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -A następnie zażądać go w prezenterze. Kolejnym etapem przetwarzania przesłanego formularza jest przekierowanie na kolejną stronę: +Używanie dziedziczenia w tym przypadku byłoby całkowicie przeciwne do zamierzonego. Bardzo szybko napotkałbyś problemy. Na przykład, gdybyś chciał dodać parametry do metody `create()`; PHP zgłosiłoby błąd, że jej podpis jest inny niż rodzica. +Albo podczas przekazywania zależności do klasy `EditFormFactory` poprzez konstruktor. To spowodowałoby coś, co nazywamy [piekłem konstru |dependency-injection:passing-dependencies#Constructor hell]ktora. + +Ogólnie rzecz biorąc, lepiej jest preferować kompozycję niż dziedziczenie. + + +Obsługa formularzy .[#toc-form-handling] +======================================== + +Obsługa formularza, która jest wywoływana po pomyślnym przesłaniu danych, może być również częścią klasy fabrycznej. Jego działanie będzie polegało na przekazaniu przesłanych danych do modelu w celu ich przetworzenia. Wszelkie błędy zostaną przekazane z [powrotem do |forms:validation#Processing Errors] formularza. Model w poniższym przykładzie jest reprezentowany przez klasę `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // tutaj dodaje się dodatkowe pola formularza + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // przetwarzanie przesłanych danych + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Niech prezenter sam zajmie się przekierowaniem. Doda on kolejny handler do zdarzenia `onSuccess`, który wykona przekierowanie. Dzięki temu formularz będzie mógł być używany w różnych prezenterach, a każdy z nich może przekierować do innej lokalizacji. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Ponieważ przekierowanie jest obsługiwane przez handler prezentera, komponent może być używany w wielu miejscach i przekierowany do innego miejsca w każdym z nich. +Rozwiązanie to wykorzystuje właściwość formularzy polegającą na tym, że po wywołaniu `addError()` na formularzu lub jego elemencie nie jest wywoływany następny handler `onSuccess`. + +Dziedziczenie po klasie Form .[#toc-inheriting-from-the-form-class] +=================================================================== -Komponent z formą .[#toc-component-with-form] -============================================= +Zbudowany formularz nie powinien być dzieckiem formularza. Innymi słowy, nie używaj tego rozwiązania: -Inną opcją jest stworzenie nowego [komponentu |application:components], który zawiera formularz. Daje nam to możliwość np. renderowania formularza w określony sposób, ponieważ komponent zawiera szablon. -Lub możemy użyć sygnałów do komunikacji AJAX i ładowania informacji do formularza, na przykład do podpowiedzi itp. +```php +// ⛔ NIE! DZIEDZICZENIE NIE NALEŻY DO TEGO MIEJSCA +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // tutaj dodaje się dodatkowe pola formularza + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Zamiast budować formularz w konstruktorze, użyj fabryki. + +Ważne jest, aby zdać sobie sprawę, że klasa `Form` jest przede wszystkim narzędziem do składania formularza, czyli konstruktorem formularzy. A złożony formularz można uznać za jej produkt. Produkt nie jest jednak szczególnym przypadkiem konstruktora; nie ma między nimi relacji *is a*, która stanowi podstawę dziedziczenia. + + +Komponent formularza .[#toc-form-component] +=========================================== + +Zupełnie innym podejściem jest stworzenie [komponentu |application:components], który zawiera formularz. Daje to nowe możliwości, na przykład renderowanie formularza w określony sposób, ponieważ komponent zawiera szablon. +Albo sygnały mogą być użyte do komunikacji AJAX i ładowania informacji do formularza, na przykład do podpowiedzi itp. ```php @@ -105,34 +284,32 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // přidáme prvky do formuláře - - $form->addSubmit('send', 'Odeslat'); + $form->addText('title', 'Title:'); + // tutaj dodaje się dodatkowe pola formularza + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // zpracování formuláře - $this->facade->process($values); + // przetwarzanie przesłanych danych + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); return; } - // vyvolání události - $this->onSave($this, $values); + // wywoływanie zdarzeń + $this->onSave($this, $data); } } ``` -Stworzymy jeszcze fabrykę, która będzie produkowała ten komponent. Wystarczy [napisać jego interfejs |application:components#Components-with-Dependencies]: - +Stwórzmy fabrykę, która będzie produkowała ten komponent. Wystarczy, że [napiszemy jej interfejs |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,7 +318,7 @@ interface EditControlFactory } ``` -I dodaj go do pliku konfiguracyjnego: +I dodać go do pliku konfiguracyjnego: ```neon services: @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/inject-method-attribute.texy b/best-practices/pl/inject-method-attribute.texy index 916ab5174a..8c36b2ffbe 100644 --- a/best-practices/pl/inject-method-attribute.texy +++ b/best-practices/pl/inject-method-attribute.texy @@ -2,13 +2,20 @@ Metody i atrybuty wstrzykiwania ******************************* .[perex] -Na konkretnych przykładach przyjrzymy się możliwościom przekazywania zależności do prezenterów oraz wyjaśnimy metody `inject` i atrybuty/anotacje. +W tym artykule skupimy się na różnych sposobach przekazywania zależności do prezenterów w frameworku Nette. Porównamy preferowaną metodę, jaką jest konstruktor, z innymi opcjami, takimi jak `inject` metody i atrybuty. + +Również dla prezenterów przekazywanie zależności za pomocą [konstruktora |dependency-injection:passing-dependencies#Constructor Injection] jest preferowanym sposobem. +Jednakże, jeśli stworzysz wspólnego przodka, z którego dziedziczą inne prezentery (np. BasePresenter), a ten przodek również posiada zależności, pojawia się problem, który nazywamy [piekłem |dependency-injection:passing-dependencies#Constructor hell] konstruktora. +Można to obejść, stosując alternatywne metody, które obejmują metody wstrzykiwania i atrybuty (adnotacje). Metody `inject*()` .[#toc-inject-methods] ========================================= -W prezenterze, jak w każdym innym kodzie, preferowanym sposobem przekazywania zależności jest użycie [konstruktora |dependency-injection:passing-dependencies#Constructor Injection]. Jeśli jednak prezenter dziedziczy po wspólnym przodku (np. `BasePresenter`), lepiej jest użyć metod `inject*()` w tym przodku. Jest to specjalny przypadek settera, w którym metoda zaczyna się od przedrostka `inject`. Dzieje się tak dlatego, że zachowujemy konstruktor wolny dla potomków: +Jest to forma przekazywania zależności za pomocą [seterów |dependency-injection:passing-dependencies#Setter Injection]. Nazwy tych seterów zaczynają się od przedrostka inject. +Nette DI automatycznie wywołuje tak nazwane metody zaraz po utworzeniu instancji prezentera i przekazuje do nich wszystkie wymagane zależności. Dlatego muszą być one zadeklarowane jako publiczne. + +`inject*()` metody można uznać za rodzaj rozszerzenia konstruktora na wiele metod. Dzięki temu `BasePresenter` może przyjmować zależności poprzez inną metodę i pozostawić konstruktor wolny dla swoich potomków: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Podstawowa różnica w stosunku do settera polega na tym, że Nette DI automatycznie wywołuje metody nazwane w ten sposób w prezenterach zaraz po utworzeniu instancji i przekazuje im wszystkie wymagane zależności. Prezenter `inject*()` może zawierać wiele metod, a każda metoda może mieć dowolną liczbę parametrów. - -Jeśli mielibyśmy przekazać zależności do przodków poprzez ich konstruktory, musielibyśmy pobrać ich zależności we wszystkich potomkach i przekazać je do `parent::__construct()`, co komplikuje kod: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // to jest komplikacja - $this->bar = $bar; - } -} -``` - -Metody `inject*()` są również przydatne w przypadkach, gdy prezenter [składa się z cech |presenter-traits] i każda z nich wymaga własnej zależności. +Prezenter może zawierać dowolną liczbę metod `inject*()`, a każda z nich może mieć dowolną liczbę parametrów. Jest to również świetne rozwiązanie dla przypadków, w których prezenter [składa się z cech |presenter-traits], a każda z nich wymaga własnej zależności. -Możliwe jest również użycie adnotacji `@inject`, ale należy pamiętać, że enkapsulacja łamie się. +`Inject` Atrybuty .[#toc-inject-attributes] +=========================================== -Annotacja `inject` .[#toc-inject-annotations] -============================================= - -Jest to automatyczne przekazanie zależności do publicznej zmiennej członkowskiej prezentera, która jest opatrzona adnotacją `@inject` w komentarzu dokumentacji. Typ może być również określony w komentarzu do dokumentacji, jeśli używasz PHP niższego niż 7.4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Jest to forma wstrzyknięcia [do właściwości |dependency-injection:passing-dependencies#Property Injection]. Wystarczy wskazać, które właściwości powinny zostać wstrzyknięte, a Nette DI automatycznie przekazuje zależności zaraz po utworzeniu instancji prezentera. Aby je wstawić, konieczne jest zadeklarowanie ich jako publicznych. -Od PHP 8.0 właściwość może być oznaczona atrybutem `Inject`: +Właściwości oznaczane są atrybutem: (wcześniej używano adnotacji `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // ta linia jest ważna. class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Nette DI ponownie automatycznie przekaże zależności do takich adnotowanych zmiennych w prezenterze, gdy tylko instancja zostanie utworzona. +Zaletą tej metody przekazywania zależności była bardzo oszczędna forma notacji. Jednak po wprowadzeniu [promocji właściwości konstru |https://blog.nette.org/pl/php-8-0-kompletny-przeglad-nowosci#toc-constructor-property-promotion]ktora, korzystanie z konstruktora wydaje się łatwiejsze. -Ta metoda ma te same wady, co przekazywanie zależności do zmiennej publicznej. Jest on używany w prezenterze, ponieważ nie komplikuje kodu i wymaga tylko minimalnego wpisywania. +Z drugiej strony, metoda ta cierpi na te same wady, co przekazywanie zależności do właściwości w ogóle: nie mamy kontroli nad zmianami w zmiennej, a jednocześnie zmienna staje się częścią publicznego interfejsu klasy, co jest niepożądane. {{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/lets-create-contact-form.texy b/best-practices/pl/lets-create-contact-form.texy new file mode 100644 index 0000000000..25c109b3c9 --- /dev/null +++ b/best-practices/pl/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Stwórzmy formularz kontaktowy +***************************** + +.[perex] +Przyjrzyjmy się jak stworzyć formularz kontaktowy w Nette, łącznie z wysłaniem go na maila. A więc do dzieła! + +Najpierw musimy stworzyć nowy projekt. Jak wyjaśnia strona [Getting Started |nette:installation]. A następnie możemy przystąpić do tworzenia formularza. + +Najprostszym sposobem jest stworzenie [formularza bezpośrednio w Presenterze |forms:in-presenter]. Możemy skorzystać z gotowego `HomePresenter`. Dodamy komponent `contactForm` reprezentujący formularz. Zrobimy to wpisując metodę `createComponentContactForm()` factory do kodu, który będzie wytwarzał komponent: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Jak widać, stworzyliśmy dwie metody. Pierwsza metoda `createComponentContactForm()` tworzy nowy formularz. Ma on pola na imię, e-mail i wiadomość, które dodajemy za pomocą metod `addText()`, `addEmail()` i `addTextArea()`. Dodaliśmy również przycisk do wysłania formularza. +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`: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Aby wysłać sam e-mail, tworzymy nową klasę o nazwie `ContactFacade` i umieszczamy ją w pliku `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +Metoda `sendMessage()` będzie tworzyć i wysyłać e-mail. Używa do tego tzw. mailera, którego przekazuje jako zależność poprzez konstruktor. Przeczytaj więcej o [wysyłaniu e-maili |mail:]. + +Teraz wrócimy do prezentera i zakończymy pracę nad metodą `contactFormSucceeded()`. Wywołuje ona metodę `sendMessage()` klasy `ContactFacade` i przekazuje jej dane formularza. A jak otrzymamy obiekt `ContactFacade`? Będziemy mieli go przekazanego nam przez konstruktor: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +Po wysłaniu maila pokazujemy użytkownikowi tzw. [flash |application:components#flash-messages] message, potwierdzający wysłanie wiadomości, a następnie przekierowujemy na następną stronę, aby nie można było ponownie wysłać formularza za pomocą *refresh* w przeglądarce. + + +No cóż, jeśli wszystko działa, powinieneś móc wysłać maila ze swojego formularza kontaktowego. Gratulacje!!! + + +Szablon HTML Email .[#toc-html-email-template] +---------------------------------------------- + +Na razie wysyłany jest zwykły email tekstowy zawierający tylko wiadomość wysłaną przez formularz. Możemy jednak wykorzystać w mailu HTML i uatrakcyjnić go. Stworzymy do tego szablon w Latte, który zapiszemy w `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Pozostaje jeszcze zmodyfikować `ContactFacade`, aby wykorzystać ten szablon. W konstruktorze żądamy klasy `LatteFactory`, która może wyprodukować obiekt `Latte\Engine`, czyli [renderer szablonu Latte |latte:develop#how-to-render-a-template]. Używamy metody `renderToString()` do renderowania szablonu do pliku, pierwszy parametr to ścieżka do szablonu, a drugi to zmienne. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Następnie przekazujemy wygenerowany e-mail HTML do metody `setHtmlBody()` zamiast oryginalnego `setBody()`. Nie musimy również określać tematu wiadomości w `setSubject()`, ponieważ biblioteka pobiera go z elementu `` w szablonie. + + +Konfiguracja .[#toc-configuring] +-------------------------------- + +W kodzie klasy `ContactFacade` nasz adminowy email `admin@example.com` jest wciąż hardcoded. Lepiej byłoby przenieść go do pliku konfiguracyjnego. Jak to zrobić? + +Najpierw modyfikujemy klasę `ContactFacade` i zamieniamy ciąg email na zmienną przekazywaną przez konstruktor: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +A drugi krok to umieszczenie wartości tej zmiennej w konfiguracji. W pliku `app/config/services.neon` dodajemy: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +I to wszystko. Jeśli w sekcji `services` jest dużo pozycji i mamy wrażenie, że mail się wśród nich gubi, możemy uczynić go zmienną. Zmodyfikujemy wpis na: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +I zdefiniujemy tę zmienną w pliku `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +I gotowe! + + +{{sitename: Najlepsze praktyki}} diff --git a/best-practices/pl/pagination.texy b/best-practices/pl/pagination.texy index 58ea4aa173..83ff2c4cd9 100644 --- a/best-practices/pl/pagination.texy +++ b/best-practices/pl/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Następnie zabierzemy się do pracy nad modyfikacją prezentera. Do metody render przekażemy numer aktualnie wyświetlanej strony. W przypadku, gdy ten numer nie jest częścią adresu URL, ustawimy domyślną wartość pierwszej strony. -Następnie rozszerzymy również metodę render, aby uzyskać instancję Paginatora, skonfigurować ją i wybrać odpowiednie artykuły do wyświetlenia w szablonie. HomepagePresenter po modyfikacjach będzie wyglądał tak: +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/pl/restore-request.texy b/best-practices/pl/restore-request.texy index b8b99a82cc..018770eab2 100644 --- a/best-practices/pl/restore-request.texy +++ b/best-practices/pl/restore-request.texy @@ -31,9 +31,11 @@ Prezenter `SignPresenter` będzie zawierał, oprócz formularza logowania, trwa ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/pt/@home.texy b/best-practices/pt/@home.texy index 5360c85ce3..5435e5eceb 100644 --- a/best-practices/pt/@home.texy +++ b/best-practices/pt/@home.texy @@ -26,6 +26,7 @@ Formulários ----------- - [Reutilização de formulários |form-reuse] - [Formulário para criação e edição de registro |creating-editing-form] +- [Vamos criar um formulário de contato |lets-create-contact-form] - [Caixas de seleção dependentes |https://blog.nette.org/pt/caixas-de-selecao-dependentes-elegantemente-em-nette-e-js-puro] </div> diff --git a/best-practices/pt/composer.texy b/best-practices/pt/composer.texy index a15e55100d..d6f7112628 100644 --- a/best-practices/pt/composer.texy +++ b/best-practices/pt/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Atualização para a versão mais recente .[#toc-update-to-the-latest-version] -=========================================================================== +Pacotes de atualização para as versões mais recentes .[#toc-update-packages-to-the-latest-versions] +=================================================================================================== Para atualizar todos os pacotes usados para a versão mais recente de acordo com as restrições de versão definidas em `composer.json` use o comando `composer update`. Por exemplo, para a dependência `"nette/database": "^3.0"` será instalada a versão mais recente 3.x.x, mas não a versão 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project Em vez disso, o `name-of-the-project` você deve fornecer o nome do diretório para seu projeto e executar o comando. O compositor buscará o repositório `nette/web-project` no GitHub, que já contém o arquivo `composer.json`, e logo em seguida instalará o próprio Nette Framework. A única coisa que resta é [verificar as permissões de escrita |nette:troubleshooting#setting-directory-permissions] nos diretórios `temp/` e `log/` e você está pronto para ir. +Se você sabe em que versão do PHP o projeto será hospedado, não deixe de [configurá-lo |#PHP Version]. + Versão PHP .[#toc-php-version] ============================== -O Composer sempre instala aquelas versões de pacotes que são compatíveis com a versão do PHP que você está usando atualmente. Que, é claro, pode não ser a mesma versão do PHP em seu web host. Portanto, é útil adicionar informações sobre a versão do PHP no host ao arquivo `composer.json`, e então somente as versões dos pacotes compatíveis com o host serão instaladas: +O Composer sempre instala as versões dos pacotes que são compatíveis com a versão do PHP que você está usando atualmente (ou melhor, a versão do PHP usada na linha de comando quando você executa o Composer). Que provavelmente não é a mesma versão que seu web host está usando. É por isso que é muito importante adicionar informações sobre a versão do PHP em sua hospedagem ao seu arquivo `composer.json`. Depois disso, somente versões de pacotes compatíveis com o host serão instaladas. + +Por exemplo, para configurar o projeto para rodar no PHP 8.2.3, use o comando: + +```shell +composer config platform.php 8.2.3 +``` + +Esta é a forma como a versão é escrita no arquivo `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +No entanto, o número da versão PHP também está listado em outra parte do arquivo, na seção `require`. Enquanto o primeiro número especifica a versão para a qual os pacotes serão instalados, o segundo número diz para qual versão o aplicativo em si é escrito. +(É claro, não faz sentido que estas versões sejam diferentes, portanto, a entrada dupla é uma redundância). Você define esta versão com o comando: + +```shell +composer require php 8.2.3 --no-update +``` + +Ou diretamente no arquivo `composer.json': + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Falsos relatórios .[#toc-false-reports] +======================================= + +Ao atualizar pacotes ou mudar números de versão, conflitos acontecem. Um pacote tem requisitos que entram em conflito com outro e assim por diante. No entanto, o Composer ocasionalmente imprime uma mensagem falsa. Ele relata um conflito que realmente não existe. Neste caso, ele ajuda a apagar o arquivo `composer.lock` e tenta novamente. + +Se a mensagem de erro persistir, então o objetivo é sério e você precisa ler a partir dela o que modificar e como. + Packagist.org - Repositório Global .[#toc-packagist-org-global-repository] ========================================================================== diff --git a/best-practices/pt/dynamic-snippets.texy b/best-practices/pt/dynamic-snippets.texy index 7ccc83b67b..660329a182 100644 --- a/best-practices/pt/dynamic-snippets.texy +++ b/best-practices/pt/dynamic-snippets.texy @@ -51,7 +51,7 @@ Na terminologia latte, um snippet dinâmico é um caso de uso específico da tag <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/pt/editors-and-tools.texy b/best-practices/pt/editors-and-tools.texy index 3e256beeaa..8eb691a59e 100644 --- a/best-practices/pt/editors-and-tools.texy +++ b/best-practices/pt/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan é uma ferramenta que detecta erros lógicos em seu código antes de exe Instale-o através do Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: E depois deixe que ele analise as classes na pasta `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/pt/form-reuse.texy b/best-practices/pt/form-reuse.texy index 8dfd2ab5fc..90da87b3c5 100644 --- a/best-practices/pt/form-reuse.texy +++ b/best-practices/pt/form-reuse.texy @@ -1,63 +1,218 @@ -Formulários de Reutilização em Vários Lugares +Reutilização de formulários em vários lugares ********************************************* .[perex] -Como reutilizar a mesma forma em vários lugares e não duplicar o código? Isto é realmente fácil de fazer em Nette e você tem várias maneiras de escolher. +Em Nette, você tem várias opções para reutilizar a mesma forma em vários lugares sem duplicar o código. Neste artigo, analisaremos as diferentes soluções, inclusive as que você deve evitar. Fábrica de formulários .[#toc-form-factory] =========================================== -Vamos criar uma classe que possa criar uma forma. Tal classe é chamada de fábrica. No local onde queremos usar o formulário (por exemplo, no apresentador), solicitamos a [fábrica como dependência |dependency-injection:passing-dependencies]. +Uma abordagem básica para usar o mesmo componente em vários lugares é criar um método ou classe que gere o componente, e depois chamar esse método em lugares diferentes na aplicação. Tal método ou classe é chamado de *fábrica*. Por favor, não confunda com o padrão de projeto *método de fábrica*, que descreve uma forma específica de usar fábricas e não está relacionado a este tópico. -Parte da fábrica é o código que passa os dados para processamento posterior quando o formulário é enviado com sucesso. Normalmente para a camada do modelo. Ele também verifica se tudo correu bem, e [passa de volta |forms:validation#Processing-errors] quaisquer erros para o formulário. O modelo no exemplo a seguir é representado pela classe `Facade`: +Como exemplo, vamos criar uma fábrica que irá construir um formulário de edição: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // campos adicionais do formulário são adicionados aqui + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Agora você pode usar esta fábrica em diferentes lugares em sua aplicação, por exemplo, em apresentadores ou componentes. E o fazemos [solicitando-o como uma dependência |dependency-injection:passing-dependencies]. Portanto, primeiro, escreveremos a classe no arquivo de configuração: + +```neon +services: + - FormFactory +``` + +E depois a usamos no apresentador: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // processamento dos dados enviados + }; + return $form; + } +} +``` + +Você pode ampliar a fábrica de formulários com métodos adicionais para criar outros tipos de formulários que se adaptem à sua aplicação. E, é claro, você pode adicionar um método que cria um formulário básico sem elementos, que os outros métodos utilizarão: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // campos adicionais do formulário são adicionados aqui + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // adicionar elementos ao formulário +O método `createForm()` ainda não faz nada de útil, mas isso vai mudar rapidamente. - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Dependências de Fábrica .[#toc-factory-dependencies] +==================================================== + +Com o tempo, tornar-se-á evidente que precisamos de formulários para sermos multilíngues. Isto significa que precisamos criar um [tradutor |forms:rendering#Translating] para todos os formulários. Para fazer isso, modificamos a classe `FormFactory` para aceitar o objeto `Translator` como uma dependência no construtor, e o passamos para o formulário: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Como o método `createForm()` também é chamado por outros métodos que criam formas específicas, só precisamos colocar o tradutor nesse método. E estamos terminados. Não há necessidade de alterar nenhum apresentador ou código de componente, o que é ótimo. + + +Mais Classes de Fábrica .[#toc-more-factory-classes] +==================================================== + +Alternativamente, você pode criar múltiplas classes para cada formulário que deseja utilizar em sua aplicação. +Esta abordagem pode aumentar a legibilidade do código e tornar os formulários mais fáceis de gerenciar. Deixe o original `FormFactory` para criar apenas um formulário puro com configuração básica (por exemplo, com suporte a tradução) e crie uma nova fábrica `EditFormFactory` para o formulário de edição. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // processamento de formulários - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ uso da composição +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // campos adicionais do formulário são adicionados aqui + $form->addSubmit('send', 'Save'); + return $form; } } ``` -É claro que a fábrica pode ser paramétrica, ou seja, pode receber parâmetros que afetarão a aparência da forma que está sendo criada. +É muito importante que a ligação entre as classes `FormFactory` e `EditFormFactory` seja implementada por composição e não por herança de objetos: -Vamos agora demonstrar a passagem da fábrica para o apresentador. Primeiro, a escrevemos no arquivo de configuração: - -```neon -services: - - EditFormFactory +```php +// ⛔ NÃO! A HERANÇA NÃO PERTENCE AQUI +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // campos adicionais do formulário são adicionados aqui + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -E depois solicite-o no apresentador. Também segue o próximo passo de processamento do formulário apresentado e que é redirecionado para a página seguinte: +Usar a herança neste caso seria completamente contraproducente. Você se depararia com problemas muito rapidamente. Por exemplo, se você quisesse adicionar parâmetros ao método `create()`; o PHP relataria um erro de que sua assinatura era diferente da dos pais. +Ou ao passar uma dependência para a classe `EditFormFactory` através do construtor. Isto causaria o que chamamos de [inferno do construtor |dependency-injection:passing-dependencies#Constructor hell]. + +Em geral, é melhor preferir a composição do que a herança. + + +Manuseio de formulários .[#toc-form-handling] +============================================= + +O manipulador de formulários que é chamado após uma apresentação bem-sucedida também pode fazer parte de uma classe de fábrica. Ele funcionará passando os dados enviados para o modelo para processamento. Ele passará quaisquer erros de [volta para o |forms:validation#Processing Errors] formulário. O modelo no exemplo a seguir é representado pela classe `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // campos adicionais do formulário são adicionados aqui + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // processamento dos dados apresentados + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Deixe o apresentador cuidar do redirecionamento em si. Ele adicionará outro manipulador ao evento `onSuccess`, que realizará o redirecionamento. Isto permitirá que o formulário seja utilizado em diferentes apresentadores, e cada um poderá ser redirecionado para um local diferente. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Como o redirecionamento é feito pelo apresentador, o componente pode ser usado em vários lugares e redirecionado para outro lugar em cada lugar. +Esta solução aproveita a propriedade dos formulários que, quando `addError()` é chamado em um formulário ou em seu elemento, o próximo manipulador `onSuccess` não é invocado. + +Herdando da classe do formulário .[#toc-inheriting-from-the-form-class] +======================================================================= -Componente com forma .[#toc-component-with-form] -================================================ +Uma forma construída não deve ser uma criança de uma forma. Em outras palavras, não use esta solução: -Outra maneira é criar um novo [componente |application:components] que contenha uma forma. Isto nos dá a capacidade de renderizar o formulário de uma forma específica, por exemplo, uma vez que o componente inclui um modelo. -Ou podemos usar sinais para comunicação AJAX e carregar informações no formulário, por exemplo, para auto-completar, etc. +```php +// ⛔ NÃO! A HERANÇA NÃO PERTENCE AQUI +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // campos adicionais do formulário são adicionados aqui + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Em vez de construir a forma na construtora, use a fábrica. + +É importante perceber que a classe `Form` é principalmente uma ferramenta para montagem de um formulário, ou seja, um construtor de formulários. E a forma montada pode ser considerada seu produto. Entretanto, o produto não é um caso específico do construtor; não há *é uma* relação entre eles, o que forma a base da herança. + + +Componente do formulário .[#toc-form-component] +=============================================== + +Uma abordagem completamente diferente é criar um [componente |application:components] que inclua uma forma. Isto dá novas possibilidades, por exemplo, de tornar o formulário de uma forma específica, uma vez que o componente inclui um modelo. +Ou sinais podem ser usados para comunicação AJAX e carregamento de informações no formulário, por exemplo, para insinuação, etc. ```php @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // adicionar elementos ao formulário - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // campos adicionais do formulário são adicionados aqui + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // processamento de formulários - $this->facade->process($values); + // processamento dos dados apresentados + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,13 +304,12 @@ class EditControl extends Nette\Application\UI\Control } // invocação de eventos - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -Em seguida, vamos criar a fábrica que produzirá este componente. Basta [escrever sua interface |application:components#Components with Dependencies]: - +Vamos criar uma fábrica que produzirá este componente. É o suficiente para [escrever sua interface |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,14 +318,14 @@ interface EditControlFactory } ``` -E acrescente ao arquivo de configuração: +E adicione-a ao arquivo de configuração: ```neon services: - EditControlFactory ``` -E agora podemos exigir a fábrica e utilizá-la no apresentador: +E agora podemos solicitar a fábrica e utilizá-la no apresentador: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -164,7 +341,7 @@ class MyPresenter extends Nette\Application\UI\Presenter $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // or redirect to the result of the edit, e.g.: + // ou redirecionar para o resultado da edição, por exemplo // $this->redirect('detail', ['id' => $data->id]); }; @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Melhores Práticas}} diff --git a/best-practices/pt/inject-method-attribute.texy b/best-practices/pt/inject-method-attribute.texy index 94afc671b2..594be4c602 100644 --- a/best-practices/pt/inject-method-attribute.texy +++ b/best-practices/pt/inject-method-attribute.texy @@ -2,13 +2,20 @@ Métodos de Injeção e Atributos ****************************** .[perex] -Usando exemplos específicos, analisaremos as possibilidades de passar dependências aos apresentadores e explicaremos os métodos e atributos/anotações `inject`. +Neste artigo, vamos nos concentrar em várias formas de passar dependências aos apresentadores na estrutura da Nette. Vamos comparar o método preferido, que é o construtor, com outras opções como `inject` métodos e atributos. + +Também para os apresentadores, a passagem de dependências utilizando o [construtor |dependency-injection:passing-dependencies#Constructor Injection] é a forma preferida. +Entretanto, se você criar um ancestral comum do qual outros apresentadores herdam (por exemplo, BasePresenter), e este ancestral também tiver dependências, surge um problema, ao qual chamamos de [construtor inferno |dependency-injection:passing-dependencies#Constructor hell]. +Isto pode ser contornado usando métodos alternativos, que incluem métodos de injeção e atributos (anotações). `inject*()` Métodos .[#toc-inject-methods] ========================================== -No apresentador, como em qualquer outro código, a forma preferida de passar as dependências é usando o [construtor |dependency-injection:passing-dependencies#Constructor Injection]. Entretanto, se o apresentador herdar de um ancestral comum (por exemplo, `BasePresenter`), é melhor usar os métodos do `inject*()` nesse ancestral. É um caso especial de um compositor, onde o método começa com um prefixo `inject`. Isto porque mantemos o construtor livre para os descendentes: +Esta é uma forma de passagem de dependência utilizando [setters |dependency-injection:passing-dependencies#Setter Injection]. Os nomes desses setters começam com o prefixo injetar. +Nette DI chama automaticamente tais métodos nomeados imediatamente após criar a instância apresentadora e passa todas as dependências necessárias a eles. Portanto, eles devem ser declarados como públicos. + +`inject*()` métodos podem ser considerados como uma espécie de extensão do construtor em múltiplos métodos. Graças a isso, o `BasePresenter` pode levar dependências através de outro método e deixar o construtor livre para seus descendentes: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -A diferença básica de um setter é que a Nette DI chama automaticamente os métodos assim nomeados nos apresentadores assim que a instância é criada, passando todas as dependências necessárias para eles. Um apresentador pode conter vários métodos `inject*()` e cada método pode ter qualquer número de parâmetros. - -Se passássemos as dependências aos antepassados através de seus construtores, teríamos que obter suas dependências em todos os descendentes e passá-las para `parent::__construct()`, o que complica o código: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // esta é uma complicação - $this->bar = $bar; - } -} -``` - -Os métodos `inject*()` também são úteis nos casos em que o apresentador é [composto de traços |presenter-traits] e cada um deles requer sua própria dependência. +O apresentador pode conter qualquer número de métodos `inject*()`, e cada um pode ter qualquer número de parâmetros. Isto também é ótimo para casos onde o apresentador é [composto de traços |presenter-traits], e cada um deles requer sua própria dependência. -Também é possível utilizar a anotação `@inject`, mas é importante ter em mente que o encapsulamento quebra. +`Inject` Atributos .[#toc-inject-attributes] +============================================ -`Inject` Anotações .[#toc-inject-annotations] -============================================= - -Esta é uma passagem automática da dependência para a variável de membro público do apresentador, que é anotada com `@inject` no comentário da documentação. O tipo também pode ser especificado no comentário da documentação se você estiver usando PHP inferior a 7,4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Esta é uma forma de [injeção em propriedades |dependency-injection:passing-dependencies#Property Injection]. Basta indicar quais propriedades devem ser injetadas, e a Nette DI passa automaticamente as dependências imediatamente após a criação da instância apresentadora. Para inseri-las, é necessário declará-las como públicas. -Desde o PHP 8.0, uma propriedade pode ser marcada com um atributo `Inject`: +As propriedades são marcadas com um atributo: (anteriormente, foi utilizada a anotação `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // esta linha é importante class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Novamente, a Nette DI passará automaticamente as dependências às propriedades anotadas desta forma no apresentador, assim que a instância for criada. +A vantagem deste método de passar dependências foi sua forma muito econômica de notação. Entretanto, com a introdução da [promoção da propriedade do construtor |https://blog.nette.org/pt/php-8-0-visao-geral-completa-das-noticias#toc-constructor-property-promotion], o uso do construtor parece mais fácil. -Este método tem as mesmas deficiências que a passagem de dependências para um bem público. É utilizado no apresentador porque não complica o código e requer apenas um mínimo de digitação. +Por outro lado, este método sofre das mesmas deficiências que a passagem de dependências para propriedades em geral: não temos controle sobre as mudanças na variável e, ao mesmo tempo, a variável se torna parte da interface pública da classe, o que é indesejável. {{sitename: Melhores Práticas}} diff --git a/best-practices/pt/lets-create-contact-form.texy b/best-practices/pt/lets-create-contact-form.texy new file mode 100644 index 0000000000..73db878db9 --- /dev/null +++ b/best-practices/pt/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Vamos criar um formulário de contato +************************************ + +.[perex] +Vamos ver como criar um formulário de contato em Nette, inclusive enviando-o para um e-mail. Então, vamos fazer isso! + +Primeiro temos que criar um novo projeto. Como explica a página [Primeiros Pass |nette:installation] os. E depois podemos começar a criar o formulário. + +A maneira mais fácil é criar o [formulário diretamente no Apresentador |forms:in-presenter]. Podemos usar o pré-fabricado `HomePresenter`. Acrescentaremos o componente `contactForm` que representa o formulário. Fazemos isso escrevendo o método de fábrica `createComponentContactForm()` no código que irá produzir o componente: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Como você pode ver, nós criamos dois métodos. O primeiro método `createComponentContactForm()` cria uma nova forma. Este tem campos para nome, e-mail e mensagem, que adicionamos usando os métodos `addText()`, `addEmail()` e `addTextArea()`. Também adicionamos um botão para enviar o formulário. +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`: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Para enviar o e-mail em si, criamos uma nova classe chamada `ContactFacade` e a colocamos no arquivo `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +O método `sendMessage()` irá criar e enviar o e-mail. Para isso, ele usa o chamado mailer, que passa como uma dependência através do construtor. Leia mais sobre o [envio de e-mails |mail:]. + +Agora, vamos voltar ao apresentador e completar o método `contactFormSucceeded()`. Ele chama o método `sendMessage()` da classe `ContactFacade` e lhe passa os dados do formulário. E como obtemos o objeto `ContactFacade`? O construtor nos passará os dados: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +Após o envio do e-mail, mostramos ao usuário a chamada [mensagem flash |application:components#flash-messages], confirmando que a mensagem foi enviada, e então redirecionamos para a página seguinte para que o formulário não possa ser reapresentado usando *refresh* no navegador. + + +Bem, se tudo funcionar, você deve poder enviar um e-mail a partir de seu formulário de contato. Parabéns! + + +Modelo de e-mail HTML .[#toc-html-email-template] +------------------------------------------------- + +Por enquanto, é enviado um e-mail com texto simples contendo apenas a mensagem enviada pelo formulário. Mas podemos usar HTML no e-mail e torná-lo mais atraente. Criaremos um modelo para ele em Latte, que salvaremos em `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Resta modificar `ContactFacade` para usar este modelo. No construtor, solicitamos a classe `LatteFactory`, que pode produzir o objeto `Latte\Engine`, um [renderizador de modelos Latte |latte:develop#how-to-render-a-template]. Usamos o método `renderToString()` para renderizar o modelo em um arquivo, o primeiro parâmetro é o caminho para o modelo e o segundo são as variáveis. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Passamos então o e-mail HTML gerado para o método `setHtmlBody()` em vez do original `setBody()`. Também não precisamos especificar o assunto do e-mail em `setSubject()`, pois a biblioteca o retira do elemento `` em modelo. + + +Configurando .[#toc-configuring] +-------------------------------- + +No código de classe `ContactFacade`, nosso e-mail administrativo `admin@example.com` ainda está codificado. Seria melhor movê-lo para o arquivo de configuração. Como fazer isso? + +Primeiro, modificamos a classe `ContactFacade` e substituímos a string de e-mail por uma variável passada pelo construtor: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +E o segundo passo é colocar o valor desta variável na configuração. No arquivo `app/config/services.neon`, adicionamos: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +E é isso aí. Se houver muitos itens na seção `services` e você sentir que o e-mail está se perdendo entre eles, podemos torná-lo uma variável. Modificaremos a entrada para: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +E defina esta variável no arquivo `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +E está feito! + + +{{sitename: Melhores Práticas}} diff --git a/best-practices/pt/pagination.texy b/best-practices/pt/pagination.texy index a57ad3f8ed..dfce31dfc6 100644 --- a/best-practices/pt/pagination.texy +++ b/best-practices/pt/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/pt/restore-request.texy b/best-practices/pt/restore-request.texy index 5529732ab5..ddb376221d 100644 --- a/best-practices/pt/restore-request.texy +++ b/best-practices/pt/restore-request.texy @@ -31,9 +31,11 @@ O apresentador `SignPresenter` conterá um parâmetro persistente `$backlink` no ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/ro/@home.texy b/best-practices/ro/@home.texy index f7c325ff16..823591e492 100644 --- a/best-practices/ro/@home.texy +++ b/best-practices/ro/@home.texy @@ -26,6 +26,7 @@ Formulare --------- - [Reutilizarea formularelor |form-reuse] - [Formular pentru crearea și editarea înregistrării |creating-editing-form] +- [Să creăm un formular de contact |lets-create-contact-form] - [Căsuțe de selectare dependente |https://blog.nette.org/ro/casete-de-selectare-dependente-in-mod-elegant-in-nette-si-js-pur] </div> diff --git a/best-practices/ro/composer.texy b/best-practices/ro/composer.texy index 9579098ee3..ef98ebdb69 100644 --- a/best-practices/ro/composer.texy +++ b/best-practices/ro/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Actualizare la cea mai recentă versiune .[#toc-update-to-the-latest-version] -============================================================================ +Actualizarea pachetelor la cele mai recente versiuni .[#toc-update-packages-to-the-latest-versions] +=================================================================================================== Pentru a actualiza toate pachetele utilizate la cea mai recentă versiune în conformitate cu constrângerile de versiune definite în `composer.json`, utilizați comanda `composer update`. De exemplu, pentru dependența `"nette/database": "^3.0"`, se va instala cea mai recentă versiune 3.x.x, dar nu și versiunea 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project În loc de `name-of-the-project` trebuie să furnizați numele directorului pentru proiectul dumneavoastră și să executați comanda. Composer va prelua depozitul `nette/web-project` de pe GitHub, care conține deja fișierul `composer.json`, și imediat după aceea va instala chiar Nette Framework. Singurul lucru care rămâne este să [verificați permisiunile de scriere |nette:troubleshooting#setting-directory-permissions] pe directoarele `temp/` și `log/` și sunteți gata de plecare. +Dacă știți pe ce versiune de PHP va fi găzduit proiectul, nu uitați să [o configura |#PHP Version]ți. + Versiunea PHP .[#toc-php-version] ================================= -Composer instalează întotdeauna acele versiuni de pachete care sunt compatibile cu versiunea de PHP pe care o utilizați în prezent. Care, bineînțeles, poate să nu fie aceeași versiune cu cea a PHP de pe gazda dvs. web. Prin urmare, este util să adăugați informații despre versiunea PHP de pe gazdă în fișierul `composer.json`, iar apoi vor fi instalate numai versiunile de pachete compatibile cu gazda: +Composer instalează întotdeauna versiunile de pachete care sunt compatibile cu versiunea de PHP pe care o folosiți în prezent (sau, mai degrabă, versiunea de PHP utilizată în linia de comandă atunci când executați Composer). Care, probabil, nu este aceeași versiune pe care o folosește gazda dvs. web. De aceea, este foarte important să adăugați informații despre versiunea PHP de pe gazda dumneavoastră în fișierul `composer.json`. După aceea, vor fi instalate numai versiunile de pachete compatibile cu gazda. + +De exemplu, pentru a seta proiectul să ruleze pe PHP 8.2.3, utilizați comanda: + +```shell +composer config platform.php 8.2.3 +``` + +În acest fel, versiunea este scrisă în fișierul `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +Cu toate acestea, numărul versiunii PHP este, de asemenea, listat în altă parte în fișier, în secțiunea `require`. În timp ce primul număr specifică versiunea pentru care vor fi instalate pachetele, al doilea număr indică versiunea pentru care este scrisă aplicația în sine. +(Desigur, nu are sens ca aceste versiuni să fie diferite, așa că dubla înscriere este o redundanță). Setați această versiune cu ajutorul comenzii: + +```shell +composer require php 8.2.3 --no-update +``` + +Sau direct în fișierul `composer.json`: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Rapoarte false .[#toc-false-reports] +==================================== + +La actualizarea pachetelor sau la schimbarea numerelor de versiune, apar conflicte. Un pachet are cerințe care intră în conflict cu un alt pachet și așa mai departe. Cu toate acestea, Composer tipărește ocazional un mesaj fals. Acesta raportează un conflict care nu există cu adevărat. În acest caz, este util să ștergeți fișierul `composer.lock` și să încercați din nou. + +Dacă mesajul de eroare persistă, atunci acesta are o intenție serioasă și trebuie să citiți din el ce trebuie să modificați și cum. + Packagist.org - Depozitul global .[#toc-packagist-org-global-repository] ======================================================================== diff --git a/best-practices/ro/dynamic-snippets.texy b/best-practices/ro/dynamic-snippets.texy index 071d4c86a1..c5201295d5 100644 --- a/best-practices/ro/dynamic-snippets.texy +++ b/best-practices/ro/dynamic-snippets.texy @@ -51,7 +51,7 @@ Metoda snippeturilor dinamice .[#toc-the-dynamic-snippets-way] <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/ro/editors-and-tools.texy b/best-practices/ro/editors-and-tools.texy index 136ed55b3f..c16ba93df7 100644 --- a/best-practices/ro/editors-and-tools.texy +++ b/best-practices/ro/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan este un instrument care detectează erorile logice din codul dvs. înain Instalați-l prin Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: Și apoi lăsați-l să analizeze clasele din dosarul `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/ro/form-reuse.texy b/best-practices/ro/form-reuse.texy index fab821e4b0..0f1e60600b 100644 --- a/best-practices/ro/form-reuse.texy +++ b/best-practices/ro/form-reuse.texy @@ -2,63 +2,218 @@ Reutilizarea formularelor în mai multe locuri ********************************************* .[perex] -Cum să reutilizați același formular în mai multe locuri și să nu duplicați codul? Acest lucru este foarte ușor de făcut în Nette și aveți mai multe moduri de a alege. +În Nette, aveți mai multe opțiuni pentru a reutiliza același formular în mai multe locuri fără a duplica codul. În acest articol, vom trece în revistă diferitele soluții, inclusiv pe cele pe care ar trebui să le evitați. Fabrica de formulare .[#toc-form-factory] ========================================= -Să creăm o clasă care poate crea un formular. O astfel de clasă se numește fabrică. În locul în care dorim să folosim formularul (de exemplu, în prezentator), solicităm [fabrica ca dependență |dependency-injection:passing-dependencies]. +O abordare de bază pentru utilizarea aceleiași componente în mai multe locuri este de a crea o metodă sau o clasă care generează componenta și apoi de a apela acea metodă în diferite locuri din aplicație. O astfel de metodă sau clasă se numește *factory*. Vă rugăm să nu faceți confuzie cu modelul de proiectare *factory method*, care descrie un mod specific de utilizare a fabricilor și nu are legătură cu acest subiect. -O parte a fabricii este codul care transmite datele pentru procesare ulterioară atunci când formularul este trimis cu succes. De obicei, la nivelul modelului. De asemenea, verifică dacă totul a decurs bine și [transmite înapoi |forms:validation#Processing-errors] orice eroare către formular. Modelul din exemplul următor este reprezentat de clasa `Facade`: +Ca exemplu, să creăm o fabrică care va construi un formular de editare: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // câmpurile suplimentare ale formularului sunt adăugate aici + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Acum puteți utiliza această fabrică în diferite locuri din aplicația dumneavoastră, de exemplu în prezentări sau componente. Și facem acest lucru [solicitând-o ca dependență |dependency-injection:passing-dependencies]. Deci, mai întâi, vom scrie clasa în fișierul de configurare: + +```neon +services: + - FormFactory +``` + +Și apoi o vom folosi în prezentator: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // prelucrarea datelor trimise + }; + return $form; + } +} +``` + +Puteți extinde fabrica de formulare cu metode suplimentare pentru a crea alte tipuri de formulare care să se potrivească aplicației dumneavoastră. Și, bineînțeles, puteți adăuga o metodă care să creeze un formular de bază fără elemente, pe care celelalte metode îl vor utiliza: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } - // adăugarea de elemente la formular + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // câmpurile suplimentare ale formularului sunt adăugate aici + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Metoda `createForm()` nu face încă nimic util, dar acest lucru se va schimba rapid. + + +Dependențe de fabrică .[#toc-factory-dependencies] +================================================== + +În timp, va deveni evident că avem nevoie ca formularele să fie multilingve. Acest lucru înseamnă că trebuie să configurăm un [traducător |forms:rendering#Translating] pentru toate formularele. Pentru a face acest lucru, modificăm clasa `FormFactory` pentru a accepta obiectul `Translator` ca dependență în constructor și îl transmitem formularului: + +```php +use Nette\Localization\Translator; +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Deoarece metoda `createForm()` este apelată și de alte metode care creează formulare specifice, trebuie să setăm translatorul doar în acea metodă. Și am terminat. Nu este nevoie să modificăm niciun cod de prezentator sau de componentă, ceea ce este minunat. + + +Mai multe clase fabrică .[#toc-more-factory-classes] +==================================================== + +Alternativ, puteți crea mai multe clase pentru fiecare formular pe care doriți să îl utilizați în aplicația dumneavoastră. +Această abordare poate crește lizibilitatea codului și face ca formularele să fie mai ușor de gestionat. Lăsați originalul `FormFactory` pentru a crea doar un formular pur cu o configurație de bază (de exemplu, cu suport pentru traducere) și creați o nouă fabrică `EditFormFactory` pentru formularul de editare. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // procesarea formularului - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ utilizarea compoziției +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // aici se adaugă câmpuri suplimentare de formular + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Desigur, fabrica poate fi parametrică, adică poate primi parametri care vor afecta aspectul formularului creat. - -Vom demonstra acum trecerea fabricii către prezentator. Mai întâi, o scriem în fișierul de configurare: +Este foarte important ca legătura dintre clasele `FormFactory` și `EditFormFactory` să fie implementată prin compoziție, nu prin moștenirea obiectelor: -```neon -services: - - EditFormFactory +```php +// ⛔ NU! MOȘTENIREA NU ARE CE CĂUTA AICI +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // câmpurile suplimentare ale formularului se adaugă aici + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -Și apoi o solicităm în prezentator. Urmează și următorul pas de procesare a formularului trimis și anume redirecționarea către pagina următoare: +Utilizarea moștenirii în acest caz ar fi complet contraproductivă. Ați întâmpina foarte repede probleme. De exemplu, dacă ați dori să adăugați parametri la metoda `create()`, PHP ar raporta o eroare deoarece semnătura acesteia este diferită de cea a metodei părinte. +Sau atunci când treceți o dependență clasei `EditFormFactory` prin intermediul constructorului. Acest lucru ar cauza ceea ce numim " [iadul constructorilor |dependency-injection:passing-dependencies#Constructor hell]". + +În general, este mai bine să preferați compoziția decât moștenirea. +Gestionarea formularelor .[#toc-form-handling] +============================================== + +Gestionatorul de formulare care este apelat după o trimitere reușită poate fi, de asemenea, parte a unei clase fabrică. Acesta va funcționa prin transmiterea datelor trimise către model pentru procesare. El va transmite orice eroare [înapoi la |forms:validation#Processing Errors] formular. Modelul din exemplul următor este reprezentat de clasa `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // câmpurile suplimentare ale formularului sunt adăugate aici + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + + public function processForm(Form $form, array $data): void + { + try { + // procesarea datelor trimise + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Lăsați prezentatorul să se ocupe singur de redirecționare. Acesta va adăuga un alt gestionar la evenimentul `onSuccess`, care va efectua redirecționarea. Acest lucru va permite ca formularul să fie utilizat în prezentatori diferiți, iar fiecare poate redirecționa către o locație diferită. + ```php class MyPresenter extends Nette\Application\UI\Presenter { @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Deoarece redirecționarea este gestionată de către gestionarul de prezentare, componenta poate fi utilizată în mai multe locuri și redirecționată în altă parte în fiecare loc. +Această soluție profită de proprietatea formularelor conform căreia, atunci când `addError()` este apelat pe un formular sau pe un element al acestuia, nu este invocat următorul procesator `onSuccess`. -Componentă cu formular .[#toc-component-with-form] -================================================== +Moștenirea din clasa Form .[#toc-inheriting-from-the-form-class] +================================================================ + +Un formular construit nu ar trebui să fie un copil al unui formular. Cu alte cuvinte, nu utilizați această soluție: -O altă modalitate este de a crea o nouă [componentă |application:components] care să conțină un formular. Acest lucru ne oferă posibilitatea de a reda formularul într-un mod specific, de exemplu, deoarece componenta include un șablon. -Sau putem utiliza semnale pentru comunicarea AJAX și încărcarea informațiilor în formular, de exemplu pentru autocompletare etc. +```php +// ⛔ NU! MOȘTENIREA NU ARE CE CĂUTA AICI +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // câmpurile suplimentare ale formularului se adaugă aici + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +În loc să construiți formularul în constructor, utilizați fabrica. + +Este important să realizăm că clasa `Form` este în primul rând un instrument de asamblare a unui formular, adică un constructor de formulare. Iar formularul asamblat poate fi considerat produsul său. Cu toate acestea, produsul nu este un caz specific al constructorului; nu există o relație *este a* între ele, care stă la baza moștenirii. + + +Componenta Form .[#toc-form-component] +====================================== + +O abordare complet diferită constă în crearea unei [componente |application:components] care include un formular. Acest lucru oferă noi posibilități, de exemplu, pentru a reda formularul într-un mod specific, deoarece componenta include un șablon. +Sau pot fi utilizate semnale pentru comunicarea AJAX și încărcarea de informații în formular, de exemplu pentru indicii etc. ```php @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // adăugarea de elemente la formular - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // câmpurile suplimentare ale formularului sunt adăugate aici + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // procesarea formularului - $this->facade->process($values); + // procesarea datelor trimise + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,13 +304,12 @@ class EditControl extends Nette\Application\UI\Control } // invocarea unui eveniment - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -În continuare, vom crea fabrica care va produce această componentă. [Scrieți |application:components#Components with Dependencies] doar [interfața acesteia |application:components#Components with Dependencies]: - +Să creăm o fabrică care va produce această componentă. Este suficient să [îi scriem interfața |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,7 +318,7 @@ interface EditControlFactory } ``` -Și adăugați-o la fișierul de configurare: +și să o adăugăm la fișierul de configurare: ```neon services: @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Best Practices}} diff --git a/best-practices/ro/inject-method-attribute.texy b/best-practices/ro/inject-method-attribute.texy index 016eb35b59..f72ed67e2b 100644 --- a/best-practices/ro/inject-method-attribute.texy +++ b/best-practices/ro/inject-method-attribute.texy @@ -2,13 +2,20 @@ Metode și atribute de injectare ******************************* .[perex] -Folosind exemple specifice, vom examina posibilitățile de transmitere a dependențelor către prezentatori și vom explica metodele și atributele/notațiile `inject`. +În acest articol, ne vom concentra asupra diferitelor modalități de transmitere a dependențelor către prezentatori în cadrul Nette. Vom compara metoda preferată, care este constructorul, cu alte opțiuni, cum ar fi metodele și atributele `inject`. + +Și pentru prezentatori, transmiterea dependențelor folosind [constructorul |dependency-injection:passing-dependencies#Constructor Injection] este metoda preferată. +Cu toate acestea, dacă creați un strămoș comun din care moștenesc alți prezentatori (de exemplu, BasePresenter), iar acest strămoș are, de asemenea, dependențe, apare o problemă, pe care o numim [constructor hell |dependency-injection:passing-dependencies#Constructor hell]. +Aceasta poate fi ocolită folosind metode alternative, care includ metode și atribute de injectare (adnotări). `inject*()` Metode .[#toc-inject-methods] ========================================= -În presenter, ca și în orice alt cod, modul preferat de transmitere a dependențelor este prin utilizarea [constructorului |dependency-injection:passing-dependencies#Constructor Injection]. Cu toate acestea, dacă presenterul moștenește de la un strămoș comun (de exemplu, `BasePresenter`), este mai bine să se utilizeze metodele din `inject*()` în acel strămoș. Acesta este un caz special de setter, în care metoda începe cu prefixul `inject`. Acest lucru se datorează faptului că păstrăm constructorul liber pentru descendenți: +Aceasta este o formă de transmitere a dependențelor prin intermediul [setorilor |dependency-injection:passing-dependencies#Setter Injection]. Numele acestor setteri încep cu prefixul inject. +Nette DI apelează automat astfel de metode numite imediat după crearea instanței de prezentator și le transmite toate dependențele necesare. Prin urmare, acestea trebuie să fie declarate ca fiind publice. + +`inject*()` metodele pot fi considerate ca un fel de extindere a constructorului în mai multe metode. Datorită acestui fapt, `BasePresenter` poate prelua dependențele printr-o altă metodă și poate lăsa constructorul liber pentru descendenții săi: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Diferența de bază față de un setter este că Nette DI apelează automat metodele denumite astfel în prezentatori imediat ce este creată instanța, trecându-le toate dependențele necesare. Un prezentator poate conține mai multe metode `inject*()` și fiecare metodă poate avea orice număr de parametri. - -Dacă am trece dependențele la strămoși prin intermediul constructorilor acestora, ar trebui să obținem dependențele în toți descendenții și să le transmitem la `parent::__construct()`, ceea ce complică codul: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // aceasta este o complicație - $this->bar = $bar; - } -} -``` - -Metodele `inject*()` sunt, de asemenea, utile în cazurile în care prezentatorul este [compus din trăsături |presenter-traits] și fiecare dintre ele necesită propria dependență. +Prezentatorul poate conține un număr nelimitat de metode `inject*()`, iar fiecare poate avea un număr nelimitat de parametri. Acest lucru este, de asemenea, excelent pentru cazurile în care prezentatorul este [compus din trăsături |presenter-traits], iar fiecare dintre acestea necesită propria dependență. -De asemenea, este posibil să se utilizeze adnotarea `@inject`, dar este important să se țină cont de faptul că încapsularea se întrerupe. +`Inject` Atribute .[#toc-inject-attributes] +=========================================== -`Inject` Adnotări .[#toc-inject-annotations] -============================================ - -Aceasta este o trecere automată a dependenței către variabila membră publică a prezentatorului, care este adnotată cu `@inject` în comentariul din documentație. Tipul poate fi, de asemenea, specificat în comentariul documentației dacă utilizați un PHP mai mic de 7.4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Aceasta este o formă de [injecție în proprietăți |dependency-injection:passing-dependencies#Property Injection]. Este suficient să indicați ce proprietăți trebuie injectate, iar Nette DI trece automat dependențele imediat după crearea instanței de prezentator. Pentru a le insera, este necesar să le declarați ca fiind publice. -Începând cu PHP 8.0, o proprietate poate fi marcată cu un atribut `Inject`: +Proprietățile sunt marcate cu un atribut: (anterior, se folosea adnotarea `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // această linie este importantă class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Din nou, Nette DI va trece în mod automat dependențele proprietăților notate în acest mod în prezentator imediat ce este creată instanța. +Avantajul acestei metode de transmitere a dependențelor a fost forma foarte economică de notare. Cu toate acestea, odată cu introducerea [promovării proprietăților constructorului |https://blog.nette.org/ro/php-8-0-prezentare-completa-a-noutatilor#toc-constructor-property-promotion], utilizarea constructorului pare mai ușoară. -Această metodă are aceleași neajunsuri ca și trecerea dependențelor la o proprietate publică. Ea este utilizată în presenter deoarece nu complică codul și necesită doar un minim de tastare. +Pe de altă parte, această metodă suferă de aceleași neajunsuri ca și trecerea dependențelor în proprietăți în general: nu avem niciun control asupra modificărilor variabilei și, în același timp, variabila devine parte a interfeței publice a clasei, ceea ce nu este de dorit. {{sitename: Best Practices}} diff --git a/best-practices/ro/lets-create-contact-form.texy b/best-practices/ro/lets-create-contact-form.texy new file mode 100644 index 0000000000..eecffd9791 --- /dev/null +++ b/best-practices/ro/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Să creăm un formular de contact +******************************* + +.[perex] +Haideți să vedem cum se creează un formular de contact în Nette, inclusiv trimiterea acestuia la un e-mail. Deci, să o facem! + +Mai întâi trebuie să creăm un nou proiect. După cum explică pagina de [început |nette:installation]. Și apoi putem începe să creăm formularul. + +Cel mai simplu mod este să creăm [formularul direct în Presenter |forms:in-presenter]. Putem folosi formularul pre-fabricat `HomePresenter`. Vom adăuga componenta `contactForm` care reprezintă formularul. Vom face acest lucru scriind metoda factory `createComponentContactForm()` în codul care va produce componenta: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +După cum puteți vedea, am creat două metode. Prima metodă `createComponentContactForm()` creează un nou formular. Acesta are câmpuri pentru nume, e-mail și mesaj, pe care le adăugăm cu ajutorul metodelor `addText()`, `addEmail()` și `addTextArea()`. De asemenea, am adăugat un buton pentru a trimite formularul. +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`: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Pentru a trimite e-mailul propriu-zis, creăm o nouă clasă numită `ContactFacade` și o plasăm în fișierul `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +Metoda `sendMessage()` va crea și va trimite e-mailul. Pentru a face acest lucru, folosește un așa-numit mailer, pe care îl transmite ca dependență prin intermediul constructorului. Citiți mai multe despre [trimiterea de e-mailuri |mail:]. + +Acum, ne vom întoarce la prezentator și vom finaliza metoda `contactFormSucceeded()`. Acesta apelează metoda `sendMessage()` a clasei `ContactFacade` și îi transmite datele formularului. Și cum obținem obiectul `ContactFacade`? Ne va fi transmis de către constructor: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +După ce e-mailul este trimis, îi arătăm utilizatorului așa-numitul [mesaj flash |application:components#flash-messages], confirmând că mesajul a fost trimis, și apoi îl redirecționăm către pagina următoare, astfel încât formularul să nu poată fi trimis din nou folosind *refresh* în browser. + + +Ei bine, dacă totul funcționează, ar trebui să puteți trimite un e-mail din formularul de contact. Felicitări! + + +Șablon de e-mail HTML .[#toc-html-email-template] +------------------------------------------------- + +Deocamdată, se trimite un e-mail text simplu care conține doar mesajul trimis prin formular. Dar putem folosi HTML în e-mail și să-l facem mai atractiv. Vom crea un șablon pentru aceasta în Latte, pe care îl vom salva în `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Rămâne să modificăm `ContactFacade` pentru a utiliza acest șablon. În constructor, solicităm clasa `LatteFactory`, care poate produce obiectul `Latte\Engine`, un [renderizator de șabloane Latte |latte:develop#how-to-render-a-template]. Utilizăm metoda `renderToString()` pentru a reda șablonul într-un fișier, primul parametru este calea către șablon, iar al doilea sunt variabilele. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Apoi, trecem e-mailul HTML generat la metoda `setHtmlBody()` în loc de originalul `setBody()`. De asemenea, nu trebuie să specificăm subiectul e-mailului în `setSubject()`, deoarece biblioteca îl preia din elementul `` din șablon. + + +Configurarea .[#toc-configuring] +-------------------------------- + +În codul clasei `ContactFacade`, e-mailul nostru de administrare `admin@example.com` este încă codificat în mod greșit. Ar fi mai bine să îl mutați în fișierul de configurare. Cum se face acest lucru? + +Mai întâi, modificăm clasa `ContactFacade` și înlocuim șirul de e-mail cu o variabilă transmisă de constructor: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +Iar al doilea pas este să introducem valoarea acestei variabile în configurație. În fișierul `app/config/services.neon` adăugăm: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +Și asta e tot. Dacă există multe elemente în secțiunea `services` și aveți impresia că e-mailul se pierde printre ele, îl putem transforma într-o variabilă. Vom modifica intrarea în: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +Și vom defini această variabilă în fișierul `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +Și gata! + + +{{sitename: Best Practices}} diff --git a/best-practices/ro/pagination.texy b/best-practices/ro/pagination.texy index 1a87368cee..4175e89bd9 100644 --- a/best-practices/ro/pagination.texy +++ b/best-practices/ro/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Următorul pas este să modificăm prezentatorul. Vom transmite numărul paginii afișate în prezent către metoda de randare. În cazul în care acest număr nu face parte din URL, trebuie să stabilim valoarea implicită la prima pagină. -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. HomepagePresenter va arăta astfel: +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/ro/restore-request.texy b/best-practices/ro/restore-request.texy index 6e5c7c6d50..65ae422103 100644 --- a/best-practices/ro/restore-request.texy +++ b/best-practices/ro/restore-request.texy @@ -31,9 +31,11 @@ Prezentatorul `SignPresenter` va conține un parametru persistent `$backlink` î ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/ru/@home.texy b/best-practices/ru/@home.texy index 29783408dd..05f2905032 100644 --- a/best-practices/ru/@home.texy +++ b/best-practices/ru/@home.texy @@ -26,6 +26,7 @@ ----- - [Повторное использование форм |form-reuse] - [Форма для создания и редактирования записи |creating-editing-form] +- [Давайте создадим контактную форму |lets-create-contact-form] - [Зависимые поля выбора |https://blog.nette.org/ru/zavisimye-selekboksy-elegantno-v-nette-i-cistom-js] </div> diff --git a/best-practices/ru/composer.texy b/best-practices/ru/composer.texy index c01a91dbc6..8b6c3fca4a 100644 --- a/best-practices/ru/composer.texy +++ b/best-practices/ru/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Обновление до последней версии .[#toc-update-to-the-latest-version] -=================================================================== +Обновление пакетов до последних версий .[#toc-update-packages-to-the-latest-versions] +===================================================================================== Для обновления всех используемых пакетов до последней версии в соответствии с ограничениями версий, определенными в файле `composer.json`, используйте команду `composer update`. Например, для зависимости `"nette/database": "^3.0"` будет установлена последняя версия 3.x.x, но не версия 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project Вместо `name-of-the-project` укажите имя каталога для вашего проекта и выполните команду. Composer получит репозиторий `nette/web-project` с GitHub, который уже содержит файл `composer.json`, и сразу после этого установит сам фреймворк Nette. Осталось только [проверить права на запись |nette:troubleshooting#Setting-Directory-Permissions] для директорий `temp/` и `log/`, и вы готовы к работе. +Если вы знаете, на какой версии PHP будет размещен проект, обязательно установите [ее |#PHP Version]. + Версия PHP .[#toc-php-version] ============================== -Composer всегда устанавливает те версии пакетов, которые совместимы с версией PHP, используемой вами в данный момент. Это, конечно, может быть не та версия PHP, которая установлена на вашем хосте. Поэтому полезно добавить информацию о версии PHP на хосте в файл `composer.json`, и тогда будут установлены только версии пакетов, совместимые с хостом: +Composer всегда устанавливает версии пакетов, совместимые с версией PHP, которую вы используете в данный момент (точнее, версию PHP, используемую в командной строке при запуске Composer). А это, скорее всего, не та версия, которую использует ваш веб-хост. Поэтому очень важно добавить информацию о версии PHP на вашем хостинге в файл `composer.json`. После этого будут установлены только версии пакетов, совместимые с хостом. + +Например, чтобы настроить проект для работы на PHP 8.2.3, используйте команду: + +```shell +composer config platform.php 8.2.3 +``` + +Таким образом версия записывается в файл `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP версия сервера + "php": "8.2.3" } } } ``` +Однако номер версии PHP также указывается в другом месте файла, в секции `require`. В то время как первое число указывает версию, для которой будут установлены пакеты, второе число говорит о том, для какой версии написано само приложение. +(Конечно, нет смысла в том, чтобы эти версии были разными, поэтому двойная запись является излишеством). Вы устанавливаете эту версию с помощью команды: + +```shell +composer require php 8.2.3 --no-update +``` + +Или непосредственно в файле `composer.json`: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Ложные отчеты .[#toc-false-reports] +=================================== + +При обновлении пакетов или изменении номеров версий возникают конфликты. Один пакет имеет требования, которые конфликтуют с другим и так далее. Однако иногда Composer выдает ложные сообщения. Он сообщает о конфликте, которого на самом деле не существует. В этом случае следует удалить файл `composer.lock` и повторить попытку. + +Если сообщение об ошибке не исчезает, значит, оно имеет серьезное значение, и вам нужно прочитать из него, что и как нужно изменить. + Packagist.org — глобальный репозиторий .[#toc-packagist-org-global-repository] ============================================================================== diff --git a/best-practices/ru/dynamic-snippets.texy b/best-practices/ru/dynamic-snippets.texy index 075a7eca61..744b905458 100644 --- a/best-practices/ru/dynamic-snippets.texy +++ b/best-practices/ru/dynamic-snippets.texy @@ -51,7 +51,7 @@ Template: <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>Мне нравится</a> {else} diff --git a/best-practices/ru/editors-and-tools.texy b/best-practices/ru/editors-and-tools.texy index e9021eae7b..fcb05471e7 100644 --- a/best-practices/ru/editors-and-tools.texy +++ b/best-practices/ru/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan — это инструмент, который обнаруживает Установите его через Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: А затем позвольте ему проанализировать классы в папке `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/ru/form-reuse.texy b/best-practices/ru/form-reuse.texy index a5b4f9294b..534ce944d8 100644 --- a/best-practices/ru/form-reuse.texy +++ b/best-practices/ru/form-reuse.texy @@ -2,62 +2,217 @@ ************************************************ .[perex] -Как повторно использовать одну и ту же форму в нескольких местах и не дублировать код? Это очень легко сделать в Nette, и у вас есть несколько способов на выбор. +В Nette у вас есть несколько вариантов повторного использования одной и той же формы в нескольких местах без дублирования кода. В этой статье мы рассмотрим различные решения, включая те, которых следует избегать. Фабрика форм .[#toc-form-factory] ================================= -Давайте создадим класс, который может создавать форму. Такой класс называется фабрикой. В том месте, где мы хотим использовать форму (например, в презентере), мы запрашиваем [фабрику как зависимость|dependency-injection:passing-dependencies]. +Один из основных подходов к использованию одного и того же компонента в нескольких местах заключается в создании метода или класса, который генерирует компонент, а затем вызывает этот метод в разных местах приложения. Такой метод или класс называется *фабрикой*. Пожалуйста, не путайте с шаблоном проектирования *фабричный метод*, который описывает особый способ использования фабрик и не относится к данной теме. -Часть фабрики — это код, который передает данные для дальнейшей обработки, когда форма успешно отправлена. Обычно на слой модели. Он также проверяет, всё ли прошло успешно, и [передает обратно |forms:validation#Processing-errors] любые ошибки в форму. Модель в следующем примере представлена классом `Facade`: +В качестве примера давайте создадим фабрику, которая будет создавать форму редактирования: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // здесь добавляются дополнительные поля формы + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Теперь вы можете использовать эту фабрику в различных местах вашего приложения, например, в презентаторах или компонентах. И мы сделаем это, [запросив ее как зависимость |dependency-injection:passing-dependencies]. Итак, сначала мы запишем класс в конфигурационный файл: + +```neon +services: + - FormFactory +``` + +А затем используем его в презентаторе: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // обработка отправленных данных + }; + return $form; + } +} +``` + +Вы можете расширить фабрику форм дополнительными методами для создания других типов форм в соответствии с вашим приложением. И, конечно, вы можете добавить метод, создающий базовую форму без элементов, которую будут использовать другие методы: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // здесь добавляются дополнительные поля формы + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // добавляем элементы к форме +Метод `createForm()` пока не делает ничего полезного, но это быстро изменится. - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Зависимости фабрики .[#toc-factory-dependencies] +================================================ + +Со временем станет очевидно, что формы должны быть многоязычными. Это означает, что нам необходимо установить [переводчик |forms:rendering#Translating] для всех форм. Для этого мы модифицируем класс `FormFactory`, чтобы он принимал объект `Translator` в качестве зависимости в конструкторе и передавал его форме: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Поскольку метод `createForm()` вызывается и другими методами, создающими конкретные формы, нам нужно установить транслятор только в этом методе. И все готово. Нет необходимости изменять код презентатора или компонента, что очень хорошо. + + +Другие фабричные классы .[#toc-more-factory-classes] +==================================================== + +В качестве альтернативы вы можете создать несколько классов для каждой формы, которую вы хотите использовать в своем приложении. +Такой подход может повысить читаемость кода и облегчить управление формами. Оставьте исходный `FormFactory` для создания только чистой формы с базовой конфигурацией (например, с поддержкой перевода) и создайте новую фабрику `EditFormFactory` для формы редактирования. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // обработка формы - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ использование композиции +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // здесь добавляются дополнительные поля формы + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Конечно, фабрика может быть параметрической, т. е. он может принимать параметры, которые будут влиять на внешний вид создаваемой формы. +Очень важно, чтобы связь между классами `FormFactory` и `EditFormFactory` была реализована композицией, а не наследованием объектов: -Теперь мы продемонстрируем передачу фабрики презентеру. Сначала мы записываем её в конфигурационный файл: - -```neon -services: - - EditFormFactory +```php +// ⛔ НЕТ! НАСЛЕДСТВУ ЗДЕСЬ НЕ МЕСТО +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // дополнительные поля формы добавляются здесь + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -А затем запрашиваем в презентере. Последний шаг обработки отправленной формы — перенаправление на следующую страницу: +Использование наследования в этом случае было бы совершенно непродуктивным. Вы бы очень быстро столкнулись с проблемами. Например, если бы вы захотели добавить параметры в метод `create()`, PHP выдал бы ошибку, что его сигнатура отличается от родительской. +Или при передаче зависимости классу `EditFormFactory` через конструктор. Это привело бы к тому, что мы называем " [ад конструктора |dependency-injection:passing-dependencies#Constructor hell]". + +В целом, лучше предпочесть композицию наследованию. + + +Работа с формами .[#toc-form-handling] +====================================== + +Обработчик формы, вызываемый после успешной отправки, также может быть частью класса-фабрики. Он будет работать, передавая отправленные данные в модель для обработки. Любые ошибки он будет передавать [обратно |forms:validation#Processing Errors] в форму. Модель в следующем примере представлена классом `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // здесь добавляются дополнительные поля формы + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // обработка предоставленных данных + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Пусть ведущий сам выполняет перенаправление. Он добавит еще один обработчик к событию `onSuccess`, который будет выполнять перенаправление. Это позволит использовать форму в разных презентаторах, и каждый из них может перенаправлять на разные места. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Поскольку перенаправление обрабатывается обработчиком презентера, компонент можно использовать в нескольких местах и в каждом из них перенаправлять в другое место. +Это решение использует свойство форм: когда `addError()` вызывается на форме или ее элементе, следующий обработчик `onSuccess` не вызывается. + +Наследование от класса формы .[#toc-inheriting-from-the-form-class] +=================================================================== -Компонент с формой .[#toc-component-with-form] -============================================== +Построенная форма не должна быть дочерней по отношению к форме. Другими словами, не используйте это решение: -Другой способ — создать новый [компонент|application:components], содержащий форму. Это дает нам возможность визуализировать форму определенным образом, если компонент включает в себя шаблон. -Или мы можем использовать сигналы для связи AJAX и загрузки информации в форму, например, для автозаполнения и т. д. +```php +// ⛔ НЕТ! НАСЛЕДСТВУ ЗДЕСЬ НЕ МЕСТО +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // дополнительные поля формы добавляются здесь + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Вместо того чтобы создавать форму в конструкторе, используйте фабрику. + +Важно понимать, что класс `Form` - это прежде всего инструмент для сборки формы, то есть конструктор форм. А собранную форму можно считать его продуктом. Однако продукт не является частным случаем конструктора; между ними нет отношения *is a*, которое лежит в основе наследования. + + +Компонент формы .[#toc-form-component] +====================================== + +Совершенно другой подход - создать [компонент |application:components], включающий форму. Это дает новые возможности, например, отображение формы определенным образом, поскольку компонент включает в себя шаблон. +Или сигналы могут быть использованы для AJAX-коммуникации и загрузки информации в форму, например, для подсказок и т.д. ```php @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // добавляем элементы к форме - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // здесь добавляются дополнительные поля формы + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // обработка формы - $this->facade->process($values); + // обработка отправленных данных + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,13 +304,12 @@ class EditControl extends Nette\Application\UI\Control } // вызов события - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -Далее мы создадим фабрику, которая будет производить этот компонент. Просто [напишите его интерфейс|application:components#Components with Dependencies]: - +Давайте создадим фабрику, которая будет производить этот компонент. Достаточно [написать ее интерфейс |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,14 +318,14 @@ interface EditControlFactory } ``` -И добавьте в файл конфигурации: +И добавить его в конфигурационный файл: ```neon services: - EditControlFactory ``` -И теперь мы можем потребовать фабрику и использовать её в презентере: +И теперь мы можем запросить фабрику и использовать ее в презентере: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Лучшие практики}} diff --git a/best-practices/ru/inject-method-attribute.texy b/best-practices/ru/inject-method-attribute.texy index 8831535c84..cc51a356bb 100644 --- a/best-practices/ru/inject-method-attribute.texy +++ b/best-practices/ru/inject-method-attribute.texy @@ -2,13 +2,20 @@ ************************** .[perex] -На конкретных примерах мы рассмотрим возможности передачи зависимостей ведущим и объясним методы `inject` и атрибуты/аннотации. +В этой статье мы рассмотрим различные способы передачи зависимостей презентаторам во фреймворке Nette. Мы сравним предпочтительный метод, которым является конструктор, с другими вариантами, такими как методы `inject` и атрибуты. + +Для ведущих также передача зависимостей с помощью [конструктора |dependency-injection:passing-dependencies#Constructor Injection] является предпочтительным способом. +Однако если вы создаете общего предка, от которого наследуют другие презентаторы (например, BasePresenter), и этот предок также имеет зависимости, возникает проблема, которую мы называем [адом конструктора |dependency-injection:passing-dependencies#Constructor hell]. +Ее можно обойти с помощью альтернативных методов, которые включают в себя методы инъекции и атрибуты (аннотации). `inject*()` Методы .[#toc-inject-methods] ========================================= -В презентаторе, как и в любом другом коде, предпочтительным способом передачи зависимостей является использование [конструктора |dependency-injection:passing-dependencies#Constructor Injection]. Однако если презентер наследуется от общего предка (например, `BasePresenter`), то лучше использовать методы `inject*()` в этом предке. Это особый случай сеттера, когда метод начинается с префикса `inject`. Это происходит потому, что мы сохраняем конструктор свободным для потомков: +Это форма передачи зависимостей с помощью [сеттеров |dependency-injection:passing-dependencies#Setter Injection]. Имена этих сеттеров начинаются с префикса inject. +Nette DI автоматически вызывает такие именованные методы сразу после создания экземпляра ведущего и передает им все необходимые зависимости. Поэтому они должны быть объявлены как public. + +`inject*()` Методы можно рассматривать как своего рода расширение конструктора на несколько методов. Благодаря этому `BasePresenter` может принимать зависимости через другой метод и оставлять конструктор свободным для его потомков: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Основное отличие от сеттера заключается в том, что Nette DI автоматически вызывает методы с такими именами в презентерах сразу после создания экземпляра, передавая им все необходимые зависимости. Презентер может содержать несколько методов `inject*()`, и каждый метод может иметь любое количество параметров. - -Внедрение через конструктор не рекомендуется для общих предков, так как во время наследования необходимо получить зависимость всех родительских презентеров и передавать их в `parent::__construct()`: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // это осложнение - $this->bar = $bar; - } -} -``` - -Методы `inject*()` также полезны в случаях, когда ведущий [состоит из признаков |presenter-traits], и каждый из них требует своей собственной зависимости. +Ведущий может содержать любое количество методов `inject*()`, и каждый из них может иметь любое количество параметров. Это также отлично подходит для случаев, когда ведущий [состоит из признаков |presenter-traits], и каждый из них требует своей собственной зависимости. -Также можно использовать аннотацию `@inject`, но при этом важно помнить, что инкапсуляция нарушается. +`Inject` Атрибуты .[#toc-inject-attributes] +=========================================== -Аннотации `Inject` .[#toc-inject-annotations] -============================================= - -В этом случае свойство аннотируется как `@inject` в комментарии документации. Тип также может быть указан в комментарии к документации, если вы используете PHP ниже 7.4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Это форма [инъекции в свойства |dependency-injection:passing-dependencies#Property Injection]. Достаточно указать, какие свойства должны быть инжектированы, и Nette DI автоматически передает зависимости сразу после создания экземпляра ведущего. Для инъекции необходимо объявить их как public. -Начиная с версии PHP 8.0, свойство может быть помечено атрибутом `Inject`: +Свойства помечаются атрибутом: (ранее использовалась аннотация `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // эта строка важна class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Опять же, Nette DI автоматически передаст зависимости свойствам, аннотированным таким образом в презентере, как только экземпляр будет создан. +Преимуществом этого метода передачи зависимостей была очень экономичная форма нотации. Однако, с введением [продвижения свойств конструктора |https://blog.nette.org/ru/php-8-0-polnyj-obzor-novostej#toc-constructor-property-promotion], использование конструктора кажется более простым. -Этот метод имеет те же недостатки, что и передача зависимостей в публичное свойство. Он используется в презентерах, поскольку не усложняет код и требует минимального набора текста. +С другой стороны, этот метод страдает теми же недостатками, что и передача зависимостей в свойства в целом: у нас нет контроля над изменениями переменной, и в то же время переменная становится частью публичного интерфейса класса, что нежелательно. {{sitename: Лучшие практики}} diff --git a/best-practices/ru/lets-create-contact-form.texy b/best-practices/ru/lets-create-contact-form.texy new file mode 100644 index 0000000000..f81335f4ef --- /dev/null +++ b/best-practices/ru/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Давайте создадим контактную форму +********************************* + +.[perex] +Давайте рассмотрим, как создать контактную форму в Nette, включая отправку ее на электронную почту. Итак, давайте сделаем это! + +Сначала мы должны создать новый проект. Как объясняется на странице " [Начало работы" |nette:installation]. А затем мы можем приступить к созданию формы. + +Самый простой способ - создать [форму непосредственно в Presenter |forms:in-presenter]. Мы можем использовать готовый `HomePresenter`. Мы добавим компонент `contactForm`, представляющий форму. Для этого мы напишем фабричный метод `createComponentContactForm()` в коде, который будет создавать компонент: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Как вы можете видеть, мы создали два метода. Первый метод `createComponentContactForm()` создает новую форму. В ней есть поля для имени, электронной почты и сообщения, которые мы добавляем с помощью методов `addText()`, `addEmail()` и `addTextArea()`. Мы также добавили кнопку для отправки формы. +Но что, если пользователь не заполнит некоторые поля? В этом случае мы должны сообщить ему, что это обязательное поле. Мы сделали это с помощью метода `setRequired()`. +Наконец, мы также добавили [событие |nette:glossary#events] `onSuccess`, которое срабатывает в случае успешной отправки формы. В нашем случае оно вызывает метод `contactFormSucceeded`, который обрабатывает отправленную форму. Мы добавим это в код через некоторое время. + +Пусть компонент `contantForm` будет отображен в шаблоне `templates/Home/default.latte`: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Для отправки самого письма мы создадим новый класс `ContactFacade` и поместим его в файл `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +Метод `sendMessage()` будет создавать и отправлять электронное письмо. Для этого он использует так называемый mailer, который он передает как зависимость через конструктор. Подробнее об отправке [электронных писем |mail:]. + +Теперь вернемся к ведущему и завершим метод `contactFormSucceeded()`. Он вызывает метод `sendMessage()` класса `ContactFacade` и передает ему данные формы. А как мы получим объект `ContactFacade`? Он будет передан нам конструктором: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +После отправки письма мы показываем пользователю так называемое [flash-сообщение |application:components#flash-messages], подтверждающее, что письмо отправлено, а затем перенаправляем на следующую страницу, чтобы форма не могла быть повторно отправлена с помощью *refresh* в браузере. + + +Ну, если все работает, вы должны быть в состоянии отправить электронное письмо из вашей контактной формы. Поздравляем! + + +Шаблон электронной почты HTML .[#toc-html-email-template] +--------------------------------------------------------- + +Пока что отправляется обычное текстовое письмо, содержащее только сообщение, отправленное формой. Но мы можем использовать HTML в письме и сделать его более привлекательным. Для этого мы создадим шаблон в Latte, который сохраним в `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Осталось модифицировать `ContactFacade`, чтобы использовать этот шаблон. В конструкторе мы запрашиваем класс `LatteFactory`, который может произвести объект `Latte\Engine`, [рендерер шаблона Latte |latte:develop#how-to-render-a-template]. Мы используем метод `renderToString()` для рендеринга шаблона в файл, первый параметр - путь к шаблону, второй - переменные. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Затем мы передаем сгенерированное HTML-письмо в метод `setHtmlBody()` вместо исходного `setBody()`. Нам также не нужно указывать тему письма в `setSubject()`, потому что библиотека берет ее из элемента `` в шаблоне. + + +Настройка .[#toc-configuring] +----------------------------- + +В коде класса `ContactFacade` наш администраторский email `admin@example.com` все еще жестко закодирован. Было бы лучше перенести его в конфигурационный файл. Как это сделать? + +Во-первых, мы изменим класс `ContactFacade` и заменим строку email на переменную, передаваемую конструктором: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +И второй шаг - поместить значение этой переменной в конфигурацию. В файле `app/config/services.neon` мы добавляем: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +И все. Если в разделе `services` много элементов и вам кажется, что письмо теряется среди них, мы можем сделать его переменной. Мы изменим запись на: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +И определим эту переменную в файле `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +И готово! + + +{{sitename: Лучшие практики}} diff --git a/best-practices/ru/pagination.texy b/best-practices/ru/pagination.texy index 5907e5cf1c..11f52f40f2 100644 --- a/best-practices/ru/pagination.texy +++ b/best-practices/ru/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Следующим шагом будет редактирование презентера. Мы передадим номер текущей отображаемой страницы в метод `render`. В случае, если этот номер не является частью URL, нам нужно установить значение по умолчанию для первой страницы. -Мы также расширяем метод `render` для получения экземпляра Paginator, его настройки и выбора нужных статей для отображения в шаблоне. HomepagePresenter будет выглядеть следующим образом: +Мы также расширяем метод `render` для получения экземпляра Paginator, его настройки и выбора нужных статей для отображения в шаблоне. HomePresenter будет выглядеть следующим образом: ```php namespace App\Presenters; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/ru/restore-request.texy b/best-practices/ru/restore-request.texy index 4651092425..f93b41ae3c 100644 --- a/best-practices/ru/restore-request.texy +++ b/best-practices/ru/restore-request.texy @@ -31,9 +31,11 @@ class AdminPresenter extends Nette\Application\UI\Presenter ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/sl/@home.texy b/best-practices/sl/@home.texy index 1f852cdb12..ea4899f2ec 100644 --- a/best-practices/sl/@home.texy +++ b/best-practices/sl/@home.texy @@ -26,6 +26,7 @@ Obrazci ------- - [Ponovna uporaba obrazcev |form-reuse] - [Obrazec za ustvarjanje in urejanje zapisa |creating-editing-form] +- [Ustvarimo obrazec za stike |lets-create-contact-form] - [Odvisna izbirna polja |https://blog.nette.org/sl/odvisna-izbirna-polja-elegantno-v-nette-in-cistem-js] </div> diff --git a/best-practices/sl/composer.texy b/best-practices/sl/composer.texy index d9c3780059..52eb9cc2da 100644 --- a/best-practices/sl/composer.texy +++ b/best-practices/sl/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Posodobitev na najnovejšo različico .[#toc-update-to-the-latest-version] -======================================================================== +Posodobitev paketov na najnovejše različice .[#toc-update-packages-to-the-latest-versions] +========================================================================================== Za posodobitev vseh uporabljenih paketov na najnovejšo različico v skladu z omejitvami različic, opredeljenimi v `composer.json`, uporabite ukaz `composer update`. Na primer za odvisnost `"nette/database": "^3.0"` bo namestil najnovejšo različico 3.x.x, ne pa tudi različice 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project Namesto `name-of-the-project` morate navesti ime imenika za vaš projekt in izvesti ukaz. Composer bo iz GitHuba pobral skladišče `nette/web-project`, ki že vsebuje datoteko `composer.json`, in takoj zatem namestil samo ogrodje Nette. Edina stvar, ki vam preostane, je, da [preverite dovoljenja za pisanje |nette:troubleshooting#setting-directory-permissions] v imenikih `temp/` in `log/`, in že ste pripravljeni za delo. +Če veste, na kateri različici PHP bo projekt gostoval, [jo |#PHP Version] obvezno [nastavite |#PHP Version]. + Različica PHP .[#toc-php-version] ================================= -Composer vedno namesti tiste različice paketov, ki so združljive z različico PHP, ki jo trenutno uporabljate. Ta različica seveda ni nujno enaka različici PHP v vašem spletnem gostitelju. Zato je koristno, da v datoteko `composer.json` dodate informacije o različici PHP na gostitelju, nato pa bodo nameščene samo različice paketov, ki so združljive z gostiteljem: +Composer vedno namesti različice paketov, ki so združljive z različico PHP, ki jo trenutno uporabljate (oziroma z različico PHP, ki je uporabljena v ukazni vrstici, ko zaženete Composer). Ta različica verjetno ni enaka različici, ki jo uporablja vaš spletni gostitelj. Zato je zelo pomembno, da v datoteko `composer.json` dodate informacije o različici PHP na vašem gostovanju. Nato bodo nameščene samo različice paketov, ki so združljive z gostiteljem. + +Na primer, če želite projekt nastaviti tako, da bo deloval na PHP 8.2.3, uporabite ukaz: + +```shell +composer config platform.php 8.2.3 +``` + +Tako se različica zapiše v datoteko `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +Vendar je številka različice PHP navedena tudi na drugem mestu v datoteki, v razdelku `require`. Medtem ko prva številka določa različico, za katero bodo nameščeni paketi, druga številka pove, za katero različico je napisana sama aplikacija. +(Seveda ni smiselno, da bi se različici razlikovali, zato je dvojni vpis odveč.) To različico nastavite z ukazom: + +```shell +composer require php 8.2.3 --no-update +``` + +Ali neposredno v datoteki `composer.json`: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Napačna poročila .[#toc-false-reports] +====================================== + +Pri nadgradnji paketov ali spreminjanju številk različic prihaja do konfliktov. En paket ima zahteve, ki so v nasprotju z drugim, in tako naprej. Vendar program Composer občasno izpiše lažna sporočila. Poroča o konfliktu, ki v resnici ne obstaja. V tem primeru pomaga, če izbrišete datoteko `composer.lock` in poskusite znova. + +Če sporočilo o napaki vztraja, je mišljeno resno in iz njega morate razbrati, kaj in kako morate spremeniti. + Packagist.org - Globalni repozitorij .[#toc-packagist-org-global-repository] ============================================================================ diff --git a/best-practices/sl/dynamic-snippets.texy b/best-practices/sl/dynamic-snippets.texy index b7146ee67c..dcd25eb0c7 100644 --- a/best-practices/sl/dynamic-snippets.texy +++ b/best-practices/sl/dynamic-snippets.texy @@ -51,7 +51,7 @@ V terminologiji Latte je dinamični izsek poseben primer uporabe oznake `{snippe <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/sl/editors-and-tools.texy b/best-practices/sl/editors-and-tools.texy index 8f1c46b036..b0cb974942 100644 --- a/best-practices/sl/editors-and-tools.texy +++ b/best-practices/sl/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan je orodje, ki odkriva logične napake v vaši kodi, preden jo zaženete. Namestite ga prek programa Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: Nato naj analizira razrede v mapi `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/sl/form-reuse.texy b/best-practices/sl/form-reuse.texy index b5a4fddb6d..398e4490b0 100644 --- a/best-practices/sl/form-reuse.texy +++ b/best-practices/sl/form-reuse.texy @@ -2,62 +2,217 @@ Ponovna uporaba obrazcev na več mestih ************************************** .[perex] -Kako ponovno uporabiti isti obrazec na več mestih in ne podvajati kode? To je v programu Nette zelo enostavno, na voljo pa imate več načinov, med katerimi lahko izbirate. +V Nette imate več možnosti za ponovno uporabo istega obrazca na več mestih brez podvajanja kode. V tem članku bomo pregledali različne rešitve, vključno s tistimi, ki se jim morate izogniti. Tovarna obrazcev .[#toc-form-factory] ===================================== -Ustvarimo razred, ki lahko ustvari obrazec. Takšen razred se imenuje tovarna. Na mestu, kjer želimo uporabiti obrazec (npr. v predstavitvenem programu), zahtevamo [tovarno kot odvisnost |dependency-injection:passing-dependencies]. +Osnovni pristop k uporabi iste komponente na več mestih je ustvarjanje metode ali razreda, ki generira komponento, in nato klicanje te metode na različnih mestih v aplikaciji. Takšna metoda ali razred se imenuje *factory*. Ne zamenjujte z načrtovalskim vzorcem *tovarniška metoda*, ki opisuje poseben način uporabe tovarn in ni povezan s to temo. -Del tovarne je koda, ki posreduje podatke v nadaljnjo obdelavo, ko je obrazec uspešno oddan. Običajno na modelno plast. Prav tako preveri, ali je vse potekalo dobro, in morebitne napake [posreduje nazaj |forms:validation#Processing-errors] obrazcu. Model v naslednjem primeru predstavlja razred `Facade`: +Kot primer ustvarimo tovarno, ki bo ustvarila obrazec za urejanje: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // tukaj so dodana dodatna polja obrazca + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Zdaj lahko to tovarno uporabite na različnih mestih v aplikaciji, na primer v predstavitvah ali komponentah. To storimo tako, da [jo zahtevamo kot odvisnost |dependency-injection:passing-dependencies]. Najprej bomo razred zapisali v konfiguracijsko datoteko: + +```neon +services: + - FormFactory +``` + +Nato ga bomo uporabili v predstavitvi: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // obdelava poslanih podatkov + }; + return $form; + } +} +``` + +Tovarno obrazcev lahko razširite z dodatnimi metodami in tako ustvarite druge vrste obrazcev, ki ustrezajo vaši aplikaciji. Seveda lahko dodate tudi metodo, ki ustvari osnovni obrazec brez elementov, ki ga bodo uporabljale druge metode: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // tukaj so dodana dodatna polja obrazca + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // dodajanje elementov v obrazec +Metoda `createForm()` še ne počne ničesar uporabnega, vendar se bo to hitro spremenilo. - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Tovarniške odvisnosti .[#toc-factory-dependencies] +================================================== + +Sčasoma se bo izkazalo, da potrebujemo večjezične obrazce. To pomeni, da moramo za vse obrazce vzpostaviti [prevajalnik |forms:rendering#Translating]. V ta namen spremenimo razred `FormFactory` tako, da v konstruktorju sprejme objekt `Translator` kot odvisnost in ga posreduje obrazcu: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Ker metodo `createForm()` kličejo tudi druge metode, ki ustvarjajo določene obrazce, moramo prevajalnik nastaviti le v tej metodi. In končali smo. Ni treba spreminjati kode predstavnika ali komponente, kar je odlično. + + +Več tovarniških razredov .[#toc-more-factory-classes] +===================================================== + +Ustvarite lahko tudi več razredov za vsak obrazec, ki ga želite uporabiti v svoji aplikaciji. +Ta pristop lahko poveča berljivost kode in olajša upravljanje obrazcev. Pustite prvotni `FormFactory` za ustvarjanje samo čistega obrazca z osnovno konfiguracijo (na primer s podporo za prevajanje) in ustvarite novo tovarno `EditFormFactory` za obrazec za urejanje. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // obdelava obrazca - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ uporaba sestave +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // tu so dodana dodatna polja obrazca + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Tovarna je seveda lahko parametrična, kar pomeni, da lahko prejme parametre, ki bodo vplivali na videz ustvarjenega obrazca. +Zelo pomembno je, da se vezava med razredoma `FormFactory` in `EditFormFactory` izvaja s kompozicijo in ne z dedovanjem objektov: -Sedaj bomo prikazali, kako tovarno posredujemo predstavniku. Najprej jo zapišemo v konfiguracijsko datoteko: - -```neon -services: - - EditFormFactory +```php +// ⛔ NE! DEDIŠČINA NE SPADA SEM +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // tu so dodana dodatna polja obrazca + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -Nato jo zahtevamo v predstavitvenem programu. Sledi tudi naslednji korak obdelave oddanega obrazca, to je preusmeritev na naslednjo stran: +Uporaba dedovanja bi bila v tem primeru popolnoma neproduktivna. Zelo hitro bi naleteli na težave. Na primer, če bi želeli metodi `create()` dodati parametre; PHP bi sporočil napako, ker se njen podpis razlikuje od podpisa nadrejene metode. +Ali pa pri posredovanju odvisnosti razredu `EditFormFactory` prek konstruktorja. To bi povzročilo tako imenovani [konstruktorski pekel |dependency-injection:passing-dependencies#Constructor hell]. + +Na splošno je bolje dati prednost kompoziciji pred dedovanjem. + + +Ravnanje z obrazci .[#toc-form-handling] +======================================== + +Obvladovalnik obrazca, ki se pokliče po uspešni oddaji, je lahko tudi del tovarniškega razreda. Deloval bo tako, da bo predložene podatke posredoval modelu v obdelavo. Morebitne napake bo posredoval [nazaj |forms:validation#Processing Errors] v obrazec. Model v naslednjem primeru predstavlja razred `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // tukaj so dodana dodatna polja obrazca + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // obdelava posredovanih podatkov + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Naj predstavnik sam poskrbi za preusmeritev. Dogodku `onSuccess` bo dodal še eno izvajalko, ki bo izvedla preusmeritev. To bo omogočilo uporabo obrazca v različnih predstavitvenih programih, pri čemer lahko vsak od njih preusmeri na drugo mesto. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Ker je preusmeritev urejena s strani izvajalca predstavitve, lahko komponento uporabimo na več mestih in jo na vsakem mestu preusmerimo drugam. +Ta rešitev izkorišča lastnost obrazcev, da se ob klicu `addError()` na obrazcu ali njegovem elementu ne prikliče naslednji izvajalec `onSuccess`. + +Dedovanje iz razreda Form .[#toc-inheriting-from-the-form-class] +================================================================ -Komponenta z obrazcem .[#toc-component-with-form] -================================================= +Vgrajeni obrazec naj ne bi bil otrok obrazca. Z drugimi besedami, ne uporabljajte te rešitve: -Drug način je, da ustvarite novo [komponento |application:components], ki vsebuje obrazec. To nam daje možnost, da obrazec prikažemo na določen način, na primer zato, ker komponenta vsebuje predlogo. -Lahko pa uporabimo signale za komunikacijo AJAX in nalaganje informacij v obrazec, na primer za samodejno dokončanje itd. +```php +// ⛔ NE! DEDIŠČINA NE SPADA SEM +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // tu so dodana dodatna polja obrazca + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Namesto da bi obrazec zgradili v konstruktorju, uporabite tovarno. + +Pomembno se je zavedati, da je razred `Form` predvsem orodje za sestavljanje obrazca, tj. gradnik obrazca. Sestavljeni obrazec pa lahko štejemo za njegov izdelek. Vendar izdelek ni poseben primer gradnika; med njima ni razmerja *is a*, ki je osnova dedovanja. + + +Komponenta obrazca .[#toc-form-component] +========================================= + +Povsem drugačen pristop je ustvarjanje [komponente |application:components], ki vključuje obrazec. To daje nove možnosti, na primer prikazovanje obrazca na poseben način, saj komponenta vključuje predlogo. +Ali pa se lahko signali uporabijo za komunikacijo AJAX in nalaganje informacij v obrazec, na primer za namige itd. ```php @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // dodajanje elementov v obrazec - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // tukaj so dodana dodatna polja obrazca + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // obdelava obrazca - $this->facade->process($values); + // obdelava posredovanih podatkov + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,13 +304,12 @@ class EditControl extends Nette\Application\UI\Control } // priklic dogodka - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -Nato bomo ustvarili tovarno, ki bo izdelala to komponento. [Napišite njen vmesnik |application:components#Components with Dependencies]: - +Ustvarimo tovarno, ki bo izdelala to komponento. Dovolj je, če [napišemo njen vmesnik |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,7 +318,7 @@ interface EditControlFactory } ``` -In dodajte v konfiguracijsko datoteko: +in ga dodamo v konfiguracijsko datoteko: ```neon services: @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Best Practices}} diff --git a/best-practices/sl/inject-method-attribute.texy b/best-practices/sl/inject-method-attribute.texy index 1393e13949..9512ab3e0d 100644 --- a/best-practices/sl/inject-method-attribute.texy +++ b/best-practices/sl/inject-method-attribute.texy @@ -2,13 +2,20 @@ Metode in atributi injiciranja ****************************** .[perex] -Na konkretnih primerih si bomo ogledali možnosti posredovanja odvisnosti predstavnikom in razložili metode in atribute/anotacije `inject`. +V tem članku se bomo osredotočili na različne načine posredovanja odvisnosti predstavnikom v ogrodju Nette. Primerjali bomo prednostni način, to je konstruktor, z drugimi možnostmi, kot so metode in atributi `inject`. + +Tudi za predstavnike je prednostni način posredovanja odvisnosti z uporabo [konstruktorja |dependency-injection:passing-dependencies#Constructor Injection]. +Če pa ustvarite skupnega prednika, od katerega dedujejo drugi predstavniki (npr. BasePresenter), in ima ta prednik tudi odvisnosti, nastane težava, ki jo imenujemo [konstruktorski pekel |dependency-injection:passing-dependencies#Constructor hell]. +To lahko obidemo z uporabo alternativnih metod, ki vključujejo metode inject in atribute (anotacije). `inject*()` Metode .[#toc-inject-methods] ========================================= -V programu presenter, tako kot v kateri koli drugi kodi, je najprimernejši način posredovanja odvisnosti uporaba [konstruktorja |dependency-injection:passing-dependencies#Constructor Injection]. Če pa presenter deduje od skupnega prednika (npr. `BasePresenter`), je bolje uporabiti metode `inject*()` v tem predniku. Gre za poseben primer setterja, kjer se metoda začne s predpono `inject`. To je zato, ker ohranjamo konstruktor prost za potomce: +To je oblika posredovanja odvisnosti z uporabo [nastavljalcev |dependency-injection:passing-dependencies#Setter Injection]. Imena teh nastavljalcev se začnejo s predpono inject. +Nette DI samodejno pokliče tako poimenovane metode takoj po ustvarjanju primerka predstavnika in jim posreduje vse zahtevane odvisnosti. Zato morajo biti deklarirane kot javne. + +`inject*()` metode lahko obravnavamo kot nekakšno razširitev konstruktorja na več metod. Zahvaljujoč temu lahko `BasePresenter` prevzame odvisnosti prek druge metode, konstruktor pa ostane prost za svoje potomce: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Osnovna razlika od setterja je v tem, da Nette DI samodejno pokliče tako poimenovane metode v predstavnikih takoj, ko je primerek ustvarjen, in jim posreduje vse zahtevane odvisnosti. Predstavnik lahko vsebuje več metod `inject*()` in vsaka metoda ima lahko poljubno število parametrov. - -Če bi odvisnosti prednikom posredovali prek njihovih konstruktorjev, bi morali pridobiti njihove odvisnosti v vseh potomcih in jih posredovati `parent::__construct()`, kar zaplete kodo: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // to je zaplet - $this->bar = $bar; - } -} -``` - -Metode `inject*()` so uporabne tudi v primerih, ko je predstavnik [sestavljen iz lastnosti |presenter-traits] in vsaka od njih zahteva svojo odvisnost. +Predstavitelj lahko vsebuje poljubno število metod `inject*()`, vsaka pa ima lahko poljubno število parametrov. To je odlično tudi v primerih, ko je predstavnik [sestavljen iz lastnosti |presenter-traits] in vsaka od njih zahteva svojo odvisnost. -Mogoče je uporabiti tudi anotacijo `@inject`, vendar je treba upoštevati, da se pri tem prekine enkapsulacija. +`Inject` Atributi .[#toc-inject-attributes] +=========================================== -`Inject` Anotacije .[#toc-inject-annotations] -============================================= - -To je samodejni prenos odvisnosti v javno člansko spremenljivko predstavnika, ki je v komentarju dokumentacije anotirana s `@inject`. Če uporabljate PHP, ki je nižji od različice 7.4, lahko v komentarju dokumentacije navedete tudi tip. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +To je oblika [vbrizgavanja v lastnosti |dependency-injection:passing-dependencies#Property Injection]. Dovolj je, da navedete, katere lastnosti naj se injicirajo, in Nette DI samodejno prenese odvisnosti takoj po ustvarjanju primerka predstavnika. Če jih želite vstaviti, jih je treba deklarirati kot javne. -Od različice PHP 8.0 lahko lastnost označimo z atributom `Inject`: +Lastnosti so označene z atributom: (prej se je uporabljala anotacija `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // ta vrstica je pomembna class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Tudi v tem primeru bo Nette DI samodejno posredoval odvisnosti tako označenim lastnostim v predstavniku, takoj ko bo primerek ustvarjen. +Prednost tega načina posredovanja odvisnosti je bila zelo ekonomična oblika zapisa. Z uvedbo [napredovanja lastnosti konstruktorja |https://blog.nette.org/sl/php-8-0-popoln-pregled-novic#toc-constructor-property-promotion] pa se zdi uporaba konstruktorja lažja. -Ta metoda ima enake pomanjkljivosti kot posredovanje odvisnosti javni lastnosti. V predstavniku se uporablja, ker ne zapleta kode in zahteva le minimalno tipkanje. +Po drugi strani pa ima ta metoda enake pomanjkljivosti kot posredovanje odvisnosti v lastnosti na splošno: nimamo nadzora nad spremembami spremenljivke, hkrati pa spremenljivka postane del javnega vmesnika razreda, kar ni zaželeno. {{sitename: Best Practices}} diff --git a/best-practices/sl/lets-create-contact-form.texy b/best-practices/sl/lets-create-contact-form.texy new file mode 100644 index 0000000000..feeae86ee8 --- /dev/null +++ b/best-practices/sl/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Ustvarimo kontaktni obrazec +*************************** + +.[perex] +Oglejmo si, kako ustvariti obrazec za stik v Nette, vključno s pošiljanjem v e-pošto. Naredimo to! + +Najprej moramo ustvariti nov projekt. Kot je razloženo na strani [Začetek |nette:installation]. Nato pa lahko začnemo ustvarjati obrazec. + +Najlažje je [obrazec |forms:in-presenter] ustvariti [neposredno v programu Presenter |forms:in-presenter]. Uporabimo lahko vnaprej pripravljene spletne strani `HomePresenter`. Dodali bomo komponento `contactForm`, ki predstavlja obrazec. To storimo tako, da v kodo, ki bo izdelala komponento, zapišemo tovarniško metodo `createComponentContactForm()`: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Kot lahko vidite, smo ustvarili dve metodi. Prva metoda `createComponentContactForm()` ustvari nov obrazec. Ta ima polja za ime, e-pošto in sporočilo, ki jih dodamo z metodami `addText()`, `addEmail()` in `addTextArea()`. Dodali smo tudi gumb za pošiljanje obrazca. +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`: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Za pošiljanje samega elektronskega sporočila ustvarimo nov razred z imenom `ContactFacade` in ga postavimo v datoteko `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +Metoda `sendMessage()` bo ustvarila in poslala elektronsko sporočilo. Za to uporablja tako imenovani mailer, ki ga prek konstruktorja posreduje kot odvisnost. Preberite več o [pošiljanju e-pošte |mail:]. + +Zdaj se bomo vrnili k predstavniku in dokončali metodo `contactFormSucceeded()`. Pokliče metodo `sendMessage()` razreda `ContactFacade` in mu posreduje podatke iz obrazca. In kako dobimo objekt `ContactFacade`? Predal nam ga bo konstruktor: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +Ko je elektronsko sporočilo poslano, uporabniku prikažemo tako imenovano [sporočilo flash |application:components#flash-messages], ki potrjuje, da je bilo sporočilo poslano, nato pa ga preusmerimo na naslednjo stran, tako da obrazca ni mogoče ponovno poslati z uporabo *refresh* v brskalniku. + + +Če vse deluje, bi morali biti sposobni poslati elektronsko sporočilo iz kontaktnega obrazca. Čestitamo! + + +Predloga e-pošte HTML .[#toc-html-email-template] +------------------------------------------------- + +Za zdaj je poslano navadno besedilno e-poštno sporočilo, ki vsebuje samo sporočilo, poslano z obrazcem. Vendar lahko v e-poštnem sporočilu uporabimo HTML in ga naredimo privlačnejšega. Za to bomo v Latte ustvarili predlogo, ki jo bomo shranili v `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Za uporabo te predloge je treba spremeniti spletno mesto `ContactFacade`. V konstruktorju zahtevamo razred `LatteFactory`, ki lahko ustvari objekt `Latte\Engine`, [upodobljevalnik predloge Latte |latte:develop#how-to-render-a-template]. Uporabimo metodo `renderToString()` za upodabljanje predloge v datoteko, pri čemer je prvi parameter pot do predloge, drugi pa spremenljivke. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Ustvarjeno elektronsko sporočilo HTML nato posredujemo metodi `setHtmlBody()` namesto prvotnega `setBody()`. Prav tako nam ni treba določiti predmeta e-pošte v `setSubject()`, saj ga knjižnica prevzame iz elementa `` v predlogi. + + +Konfiguracija .[#toc-configuring] +--------------------------------- + +V kodi razreda `ContactFacade` je še vedno trdno zakodirano naše upraviteljevo e-poštno sporočilo `admin@example.com`. Bolje bi bilo, če bi ga prenesli v konfiguracijsko datoteko. Kako to storiti? + +Najprej spremenimo razred `ContactFacade` in niz elektronske pošte nadomestimo s spremenljivko, ki jo posreduje konstruktor: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +Drugi korak je, da vrednost te spremenljivke vnesemo v konfiguracijo. V datoteko `app/config/services.neon` dodamo: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +In to je to. Če je v razdelku `services` veliko elementov in se vam zdi, da se elektronsko sporočilo izgublja med njimi, ga lahko naredimo za spremenljivko. Vnos bomo spremenili v: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +in to spremenljivko opredelimo v datoteki `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +In končano je! + + +{{sitename: Best Practices}} diff --git a/best-practices/sl/pagination.texy b/best-practices/sl/pagination.texy index 208bf2d409..b5322e1cd1 100644 --- a/best-practices/sl/pagination.texy +++ b/best-practices/sl/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Naslednji korak je urejanje predstavnika. Številko trenutno prikazane strani bomo posredovali metodi render. V primeru, da ta številka ni del naslova URL, moramo privzeto vrednost nastaviti na prvo stran. -Metodo upodabljanja razširimo tudi na pridobitev primerka Paginatorja, njegovo nastavitev in izbiro pravilnih člankov za prikaz v predlogi. HomepagePresenter bo videti takole: +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/sl/restore-request.texy b/best-practices/sl/restore-request.texy index ff8fad2b12..4790fc3457 100644 --- a/best-practices/sl/restore-request.texy +++ b/best-practices/sl/restore-request.texy @@ -31,9 +31,11 @@ Predstavnik `SignPresenter` bo poleg obrazca za prijavo vseboval tudi trajni par ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/tr/@home.texy b/best-practices/tr/@home.texy index b7115269ea..1cc97db6f5 100644 --- a/best-practices/tr/@home.texy +++ b/best-practices/tr/@home.texy @@ -26,6 +26,7 @@ Formlar ------- - [Formların yeniden kullanımı |form-reuse] - [Kayıt oluşturma ve düzenleme formu |creating-editing-form] +- [Bir İletişim Formu Oluşturalım |lets-create-contact-form] - [Bağımlı seçim kutuları |https://blog.nette.org/tr/bagimli-secim-kutulari-nette-ve-saf-js-de-zarif-bir-sekilde] </div> diff --git a/best-practices/tr/composer.texy b/best-practices/tr/composer.texy index b134c309b0..74d2c032f1 100644 --- a/best-practices/tr/composer.texy +++ b/best-practices/tr/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -En Son Sürüme Güncelleyin .[#toc-update-to-the-latest-version] -============================================================== +Paketleri En Son Sürümlere Güncelleyin .[#toc-update-packages-to-the-latest-versions] +===================================================================================== Kullanılan tüm paketleri `composer.json` adresinde tanımlanan sürüm kısıtlamalarına göre en son sürüme güncellemek için `composer update` komutunu kullanın. Örneğin `"nette/database": "^3.0"` bağımlılığı için en son 3.x.x sürümünü yükleyecek, ancak sürüm 4'ü yüklemeyecektir. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project Bunun yerine `name-of-the-project` adresine projenizin dizininin adını girmeli ve komutu çalıştırmalısınız. Composer, `composer.json` dosyasını zaten içeren GitHub'dan `nette/web-project` deposunu getirecek ve hemen ardından Nette Framework'ün kendisini yükleyecektir. Geriye kalan tek şey `temp/` ve `log/` dizinleri üzerindeki [yazma izinlerini kontrol |nette:troubleshooting#setting-directory-permissions] etmektir ve artık hazırsınız. +Projenin hangi PHP sürümünde barındırılacağını biliyorsanız, [bunu ayarladığınızdan |#PHP Version] emin olun. + PHP Sürümü .[#toc-php-version] ============================== -Composer her zaman kullanmakta olduğunuz PHP sürümü ile uyumlu olan paket sürümlerini yükler. Elbette bu, web barındırıcınızdaki PHP ile aynı sürüm olmayabilir. Bu nedenle, `composer.json` dosyasına ana bilgisayardaki PHP sürümü hakkında bilgi eklemek yararlıdır ve ardından yalnızca ana bilgisayarla uyumlu paket sürümleri yüklenecektir: +Composer her zaman kullanmakta olduğunuz PHP sürümüyle (ya da Composer'ı çalıştırdığınızda komut satırında kullanılan PHP sürümüyle) uyumlu olan paket sürümlerini yükler. Bu sürüm muhtemelen web barındırıcınızın kullandığı sürümle aynı değildir. Bu nedenle `composer.json` dosyanıza barındırıcınızdaki PHP sürümü hakkında bilgi eklemeniz çok önemlidir. Bundan sonra, yalnızca ana bilgisayarla uyumlu paket sürümleri yüklenecektir. + +Örneğin, projeyi PHP 8.2.3 üzerinde çalışacak şekilde ayarlamak için şu komutu kullanın: + +```shell +composer config platform.php 8.2.3 +``` + +Sürüm `composer.json` dosyasına bu şekilde yazılır: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP version on host + "php": "8.2.3" } } } ``` +Bununla birlikte, PHP sürüm numarası dosyanın başka bir yerinde, `require` bölümünde de listelenir. İlk numara paketlerin hangi sürüm için yükleneceğini belirtirken, ikinci numara uygulamanın kendisinin hangi sürüm için yazıldığını söyler. +(Tabii ki, bu sürümlerin farklı olması mantıklı değildir, bu nedenle çift giriş bir fazlalıktır). Bu sürümü şu komutla ayarlarsınız: + +```shell +composer require php 8.2.3 --no-update +``` + +Veya doğrudan `composer.json` dosyasında: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Yanlış Raporlar .[#toc-false-reports] +===================================== + +Paketleri yükseltirken veya sürüm numaralarını değiştirirken çakışmalar meydana gelir. Bir paketin gereksinimleri diğeriyle çakışır ve bu böyle devam eder. Ancak, Composer bazen yanlış bir mesaj yazdırır. Gerçekte var olmayan bir çakışma bildirir. Bu durumda, `composer.lock` dosyasını silmek ve tekrar denemek yardımcı olur. + +Hata mesajı devam ederse, ciddi bir mesajdır ve neyi nasıl değiştireceğinizi okumanız gerekir. + Packagist.org - Küresel Depo .[#toc-packagist-org-global-repository] ==================================================================== diff --git a/best-practices/tr/dynamic-snippets.texy b/best-practices/tr/dynamic-snippets.texy index bd64f20275..bae43f06d3 100644 --- a/best-practices/tr/dynamic-snippets.texy +++ b/best-practices/tr/dynamic-snippets.texy @@ -51,7 +51,7 @@ Latte terminolojisinde, dinamik bir snippet, snippet adında bir değişkenin ku <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>I like it</a> {else} diff --git a/best-practices/tr/editors-and-tools.texy b/best-practices/tr/editors-and-tools.texy index 233a7bf3ab..6c40de4d40 100644 --- a/best-practices/tr/editors-and-tools.texy +++ b/best-practices/tr/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan, siz çalıştırmadan önce kodunuzdaki mantıksal hataları tespit ede Composer aracılığıyla yükleyin: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: Ve sonra `app/` klasöründeki sınıfları analiz etmesine izin verin: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/tr/form-reuse.texy b/best-practices/tr/form-reuse.texy index 3826d275f4..338dee09ef 100644 --- a/best-practices/tr/form-reuse.texy +++ b/best-practices/tr/form-reuse.texy @@ -1,63 +1,218 @@ -Formları Birden Fazla Yerde Yeniden Kullanın +Formları Birden Fazla Yerde Yeniden Kullanma ******************************************** .[perex] -Aynı formu birden fazla yerde nasıl tekrar kullanabilir ve kodu kopyalamayabilirsiniz? Nette bunu yapmak gerçekten çok kolay ve aralarından seçim yapabileceğiniz birden fazla yol var. +Nette, aynı formu kod kopyalamadan birden fazla yerde yeniden kullanmak için çeşitli seçeneklere sahipsiniz. Bu makalede, kaçınmanız gerekenler de dahil olmak üzere farklı çözümlerin üzerinden geçeceğiz. Form Fabrikası .[#toc-form-factory] =================================== -Form oluşturabilen bir sınıf oluşturalım. Böyle bir sınıfa fabrika denir. Formu kullanmak istediğimiz yerde (örneğin sunucuda), fabrikayı [bağımlılık olarak |dependency-injection:passing-dependencies] talep ederiz. +Aynı bileşeni birden fazla yerde kullanmaya yönelik temel yaklaşımlardan biri, bileşeni oluşturan bir yöntem veya sınıf oluşturmak ve daha sonra bu yöntemi uygulamanın farklı yerlerinde çağırmaktır. Böyle bir yöntem veya sınıf *factory* olarak adlandırılır. Lütfen fabrikaları kullanmanın belirli bir yolunu açıklayan ve bu konuyla ilgili olmayan *factory method* tasarım modeliyle karıştırmayın. -Fabrikanın bir parçası, form başarıyla gönderildiğinde verileri daha sonraki işlemler için aktaran koddur. Genellikle model katmanına. Ayrıca her şeyin yolunda gidip gitmediğini kontrol eder ve hataları forma [geri ilet |forms:validation#Processing-errors] ir. Aşağıdaki örnekteki model `Facade` sınıfı tarafından temsil edilmektedir: +Örnek olarak, bir düzenleme formu oluşturacak bir fabrika oluşturalım: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // ek form alanları buraya eklenir + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Artık bu fabrikayı uygulamanızın farklı yerlerinde, örneğin sunumlarda veya bileşenlerde kullanabilirsiniz. Ve bunu [bir bağımlılık olarak talep |dependency-injection:passing-dependencies] ederek yapıyoruz. Bu yüzden önce sınıfı yapılandırma dosyasına yazacağız: + +```neon +services: + - FormFactory +``` + +Ve sonra bunu sunumda kullanıyoruz: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* parameters */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // gönderilen verilerin işlenmesi + }; + return $form; + } +} +``` + +Uygulamanıza uygun diğer form türlerini oluşturmak için form fabrikasını ek yöntemlerle genişletebilirsiniz. Ve elbette, diğer yöntemlerin kullanacağı öğeler olmadan temel bir form oluşturan bir yöntem ekleyebilirsiniz: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } + + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // ek form alanları buraya eklenir + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - // forma öğeler ekleyin + `createForm()` yöntemi henüz yararlı bir şey yapmıyor, ancak bu hızla değişecek. - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Fabrika Bağımlılıkları .[#toc-factory-dependencies] +=================================================== + +Zamanla, formların çok dilli olması gerektiği ortaya çıkacaktır. Bu, tüm formlar için bir [çevirmen |forms:rendering#Translating] ayarlamamız gerektiği anlamına gelir. Bunu yapmak için `FormFactory` sınıfını, `Translator` nesnesini kurucuda bir bağımlılık olarak kabul edecek ve forma aktaracak şekilde değiştiriyoruz: + +```php +use Nette\Localization\Translator; + +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + + `createForm()` yöntemi, belirli formları oluşturan diğer yöntemler tarafından da çağrıldığından, çevirmeni yalnızca bu yöntemde ayarlamamız gerekir. Ve işimiz bitti. Herhangi bir sunumcu veya bileşen kodunu değiştirmeye gerek yok, ki bu harika. + + +Daha Fazla Fabrika Sınıfı .[#toc-more-factory-classes] +====================================================== + +Alternatif olarak, uygulamanızda kullanmak istediğiniz her form için birden fazla sınıf oluşturabilirsiniz. +Bu yaklaşım kodun okunabilirliğini artırabilir ve formların yönetimini kolaylaştırabilir. Temel yapılandırmaya sahip (örneğin çeviri destekli) saf bir form oluşturmak için orijinal `FormFactory` adresini bırakın ve düzenleme formu için yeni bir fabrika `EditFormFactory` oluşturun. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // form işleme - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { - $form->addError('...'); - } + +// ✅ kompozisyon kullanımı +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // ek form alanları buraya eklenir + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Elbette, fabrika parametrik olabilir, yani oluşturulmakta olan formun görünümünü etkileyecek parametreler alabilir. + `FormFactory` ve `EditFormFactory` sınıfları arasındaki bağın nesne kalıtımı ile değil, bileşim ile uygulanması çok önemlidir: -Şimdi fabrikayı sunucuya aktarmayı göstereceğiz. İlk olarak, bunu yapılandırma dosyasına yazıyoruz: - -```neon -services: - - EditFormFactory +```php +// ⛔ HAYIR! MİRAS BURAYA AİT DEĞİL +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // ek form alanları buraya eklenir + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -Ve sonra sunum yapan kişiden talep edin. Gönderilen formun işlenmesinin bir sonraki adımı da bir sonraki sayfaya yönlendirmektir: +Bu durumda kalıtım kullanmak tamamen ters etki yaratacaktır. Çok hızlı bir şekilde sorunlarla karşılaşırsınız. Örneğin, `create()` yöntemine parametre eklemek isterseniz; PHP, imzasının ebeveynden farklı olduğuna dair bir hata bildirecektir. +Ya da `EditFormFactory` sınıfına yapıcı aracılığıyla bir bağımlılık aktarırken. Bu, yapıcı [cehennemi |dependency-injection:passing-dependencies#Constructor hell] dediğimiz şeye neden olur. + +Genel olarak, kalıtım yerine bileşimi tercih etmek daha iyidir. + + +Form İşleme .[#toc-form-handling] +================================= + +Başarılı bir gönderim sonrasında çağrılan form işleyici de bir fabrika sınıfının parçası olabilir. Gönderilen verileri işlenmek üzere modele aktararak çalışacaktır. Herhangi bir hatayı forma [geri |forms:validation#Processing Errors] iletecektir. Aşağıdaki örnekteki model `Facade` sınıfı tarafından temsil edilmektedir: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // ek form alanları buraya eklenir + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + public function processForm(Form $form, array $data): void + { + try { + // sunulan veri̇leri̇n i̇şlenmesi̇ + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Sunucunun yeniden yönlendirmeyi kendisinin yapmasına izin verin. Yeniden yönlendirmeyi gerçekleştirecek olan `onSuccess` olayına başka bir işleyici ekleyecektir. Bu, formun farklı sunumcularda kullanılmasına olanak tanıyacak ve her biri farklı bir konuma yönlendirebilecektir. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Yeniden yönlendirme sunum işleyicisi tarafından gerçekleştirildiğinden, bileşen birden fazla yerde kullanılabilir ve her bir yerde başka bir yere yönlendirilebilir. +Bu çözüm, bir form veya öğesi üzerinde `addError()` çağrıldığında, bir sonraki `onSuccess` işleyicisinin çağrılmaması şeklindeki form özelliğinden yararlanır. + +Form Sınıfından Devralma .[#toc-inheriting-from-the-form-class] +=============================================================== -Formlu Bileşen .[#toc-component-with-form] -========================================== +Oluşturulan bir formun bir formun çocuğu olmaması gerekir. Başka bir deyişle, bu çözümü kullanmayın: -Başka bir yol da form içeren yeni bir [bileşen |application:components] oluşturmaktır. Bu bize, örneğin bileşen bir şablon içerdiğinden, formu belirli bir şekilde oluşturma yeteneği verir. -Veya AJAX iletişimi için sinyalleri kullanabilir ve örneğin otomatik tamamlama vb. için forma bilgi yükleyebiliriz. +```php +// ⛔ HAYIR! MİRAS BURAYA AİT DEĞİL +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // ek form alanları buraya eklenir + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Formu yapıcıda oluşturmak yerine fabrikayı kullanın. + + `Form` sınıfının öncelikle bir formu bir araya getirmek için bir araç, yani bir form oluşturucu olduğunu anlamak önemlidir. Ve birleştirilmiş form onun ürünü olarak düşünülebilir. Ancak, ürün oluşturucunun özel bir durumu değildir; aralarında kalıtımın temelini oluşturan *is a* ilişkisi yoktur. + + +Form Bileşeni .[#toc-form-component] +==================================== + +Tamamen farklı bir yaklaşım, bir form içeren bir [bileşen |application:components] oluşturmaktır. Bu, örneğin bileşen bir şablon içerdiğinden formu belirli bir şekilde oluşturmak gibi yeni olanaklar sağlar. +Veya sinyaller AJAX iletişimi ve forma bilgi yüklemek için kullanılabilir, örneğin ipucu vb. için. ```php @@ -105,20 +284,19 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // forma öğeler ekleyin - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // ek form alanları buraya eklenir + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // form işleme - $this->facade->process($values); + // sunulan veri̇leri̇n i̇şlenmesi̇ + $this->facade->process($data); } catch (AnyModelException $e) { $form->addError('...'); @@ -126,13 +304,12 @@ class EditControl extends Nette\Application\UI\Control } // olay çağrısı - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -Daha sonra, bu bileşeni üretecek fabrikayı oluşturacağız. Sadece [arayüzünü yazın |application:components#Components with Dependencies]: - +Bu bileşeni üretecek bir fabrika oluşturalım. [Arayüzünü yazmak |application:components#Components with Dependencies] yeterli: ```php interface EditControlFactory @@ -148,7 +325,7 @@ services: - EditControlFactory ``` -Ve şimdi fabrikaya ihtiyaç duyabilir ve onu sunumda kullanabiliriz: +Ve şimdi fabrikayı talep edebilir ve sunumda kullanabiliriz: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -164,7 +341,7 @@ class MyPresenter extends Nette\Application\UI\Presenter $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // veya düzenlemenin sonucuna yönlendirin, örn: + // veya düzenleme sonucuna yönlendirin, örn: // $this->redirect('detail', ['id' => $data->id]); }; @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: En İyi Uygulamalar}} diff --git a/best-practices/tr/inject-method-attribute.texy b/best-practices/tr/inject-method-attribute.texy index 5dec1dd4a1..4a586935d6 100644 --- a/best-practices/tr/inject-method-attribute.texy +++ b/best-practices/tr/inject-method-attribute.texy @@ -2,13 +2,20 @@ Inject Yöntemleri ve Öznitelikleri ********************************** .[perex] -Belirli örnekler kullanarak, bağımlılıkları sunum yapanlara aktarma olasılıklarını inceleyeceğiz ve `inject` yöntemlerini ve özniteliklerini/açıklamalarını açıklayacağız. +Bu makalede, Nette çerçevesinde bağımlılıkları sunum yapanlara aktarmanın çeşitli yollarına odaklanacağız. Tercih edilen yöntem olan kurucu ile `inject` yöntemleri ve nitelikleri gibi diğer seçenekleri karşılaştıracağız. + +Sunucular için de bağımlılıkların [kurucu |dependency-injection:passing-dependencies#Constructor Injection] kullanılarak aktarılması tercih edilen yoldur. +Ancak, diğer sunum yapanların miras aldığı ortak bir ata oluşturursanız (örn. BasePresenter) ve bu atanın da bağımlılıkları varsa, [yapıcı cehennemi |dependency-injection:passing-dependencies#Constructor hell] dediğimiz bir sorun ortaya çıkar. +Bu sorun, inject yöntemleri ve nitelikleri (ek açıklamalar) içeren alternatif yöntemler kullanılarak atlatılabilir. `inject*()` Yöntemler .[#toc-inject-methods] ============================================ -Diğer kodlarda olduğu gibi presenter'da da bağımlılıkları aktarmanın tercih edilen yolu [yapıcı |dependency-injection:passing-dependencies#Constructor Injection] kullanmaktır. Ancak, sunucu ortak bir atadan miras alıyorsa (örneğin `BasePresenter`), bu atadaki `inject*()` yöntemlerini kullanmak daha iyidir. Bu, yöntemin `inject` önekiyle başladığı özel bir setter durumudur. Bunun nedeni, kurucuyu torunlar için serbest tutmamızdır: +Bu, [ayarlayıcıları |dependency-injection:passing-dependencies#Setter Injection] kullanan bir bağımlılık aktarma biçimidir. Bu ayarlayıcıların adları inject önekiyle başlar. +Nette DI, sunum örneğini oluşturduktan hemen sonra bu tür adlandırılmış yöntemleri otomatik olarak çağırır ve gerekli tüm bağımlılıkları bunlara aktarır. Bu nedenle public olarak bildirilmelidirler. + +`inject*()` metotları, birden fazla metoda bir tür yapıcı uzantısı olarak düşünülebilir. Bu sayede, `BasePresenter` bağımlılıkları başka bir yöntem aracılığıyla alabilir ve yapıcıyı torunları için serbest bırakabilir: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Bir setter'dan temel farkı, Nette DI'nın örnek oluşturulur oluşturulmaz bu şekilde adlandırılan yöntemleri otomatik olarak çağırması ve gerekli tüm bağımlılıkları bunlara aktarmasıdır. Bir sunucu birden fazla yöntem içerebilir `inject*()` ve her yöntemin herhangi bir sayıda parametresi olabilir. - -Bağımlılıkları atalara kurucuları aracılığıyla aktarsaydık, tüm torunlarda bağımlılıklarını almamız ve bunları `parent::__construct()` adresine aktarmamız gerekirdi ki bu da kodu karmaşıklaştırırdı: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // bu bir komplikasyon - $this->bar = $bar; - } -} -``` - -`inject*()` yöntemleri, sunum yapan kişinin [özelliklerden oluştuğu |presenter-traits] ve her birinin kendi bağımlılığını gerektirdiği durumlarda da kullanışlıdır. +Sunum yapan kişi herhangi bir sayıda `inject*()` yöntemi içerebilir ve her biri herhangi bir sayıda parametreye sahip olabilir. Bu, sunum yapan kişinin [özelliklerden oluştuğu |presenter-traits] ve her birinin kendi bağımlılığını gerektirdiği durumlar için de harikadır. -`@inject` ek açıklamasını kullanmak da mümkündür, ancak kapsüllemenin bozulduğunu akılda tutmak önemlidir. +`Inject` Nitelikler .[#toc-inject-attributes] +============================================= -`Inject` Ek Açıklamalar .[#toc-inject-annotations] -================================================== - -Bu, bağımlılığın, belgeleme açıklamasında `@inject` ile açıklanan sunumcunun genel üye değişkenine otomatik olarak aktarılmasıdır. PHP 7.4'ten daha düşük bir sürüm kullanıyorsanız, tür de belgeleme açıklamasında belirtilebilir. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Bu, [özelliklere enjeksiyonun |dependency-injection:passing-dependencies#Property Injection] bir şeklidir. Hangi özelliklerin enjekte edilmesi gerektiğini belirtmek yeterlidir ve Nette DI, sunum örneğini oluşturduktan hemen sonra bağımlılıkları otomatik olarak geçirir. Bunları eklemek için public olarak bildirmek gerekir. -PHP 8.0'dan bu yana, bir özellik `Inject` özniteliği ile işaretlenebilir: +Özellikler bir öznitelikle işaretlenir: (daha önce `/** @inject */` ek açıklaması kullanılıyordu) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // bu satır önemlidir class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Yine, Nette DI, örnek oluşturulur oluşturulmaz bu şekilde açıklanan özelliklere bağımlılıkları otomatik olarak aktaracaktır. +Bağımlılıkları aktarmanın bu yönteminin avantajı çok ekonomik bir gösterim biçimine sahip olmasıydı. Ancak, yapıcı [özellik tanıtımının |https://blog.nette.org/tr/php-8-0-haberlere-genel-bakis#toc-constructor-property-promotion] kullanılmaya başlanmasıyla birlikte, yapıcıyı kullanmak daha kolay görünmektedir. -Bu yöntem, bağımlılıkları bir public özelliğe aktarmakla aynı eksikliklere sahiptir. Kodu karmaşıklaştırmadığı ve yalnızca minimum yazım gerektirdiği için presenter'da kullanılır. +Öte yandan, bu yöntem genel olarak bağımlılıkları özelliklere aktarmakla aynı eksikliklerden muzdariptir: değişkendeki değişiklikler üzerinde hiçbir kontrolümüz yoktur ve aynı zamanda değişken sınıfın genel arayüzünün bir parçası haline gelir ki bu istenmeyen bir durumdur. {{sitename: En İyi Uygulamalar}} diff --git a/best-practices/tr/lets-create-contact-form.texy b/best-practices/tr/lets-create-contact-form.texy new file mode 100644 index 0000000000..52d307b8bf --- /dev/null +++ b/best-practices/tr/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Bir İletişim Formu Oluşturalım +****************************** + +.[perex] +Bir e-postaya göndermek de dahil olmak üzere Nette'de bir iletişim formunun nasıl oluşturulacağına bir göz atalım. Hadi yapalım o zaman! + +Öncelikle yeni bir proje oluşturmamız gerekiyor. [Başlarken |nette:installation] sayfasında açıklandığı gibi. Ve sonra formu oluşturmaya başlayabiliriz. + +En kolay yol, [formu doğrudan Presenter'da |forms:in-presenter] oluşturmaktır. Önceden hazırlanmış `HomePresenter` adresini kullanabiliriz. Formu temsil eden `contactForm` bileşenini ekleyeceğiz. Bunu `createComponentContactForm()` factory metodunu bileşeni üretecek kodun içine yazarak yapacağız: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Gördüğünüz gibi iki metot oluşturduk. İlk yöntem `createComponentContactForm()` yeni bir form oluşturur. Bu formda `addText()`, `addEmail()` ve `addTextArea()` yöntemlerini kullanarak eklediğimiz isim, e-posta ve mesaj alanları bulunmaktadır. Ayrıca formu göndermek için bir düğme ekledik. +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: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +E-postanın kendisini göndermek için `ContactFacade` adında yeni bir sınıf oluşturuyoruz ve bunu `app/Model/ContactFacade.php` dosyasına yerleştiriyoruz: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + + `sendMessage()` yöntemi e-postayı oluşturur ve gönderir. Bunu yapmak için, yapıcı aracılığıyla bir bağımlılık olarak aktardığı sözde bir mailer kullanır. [E-posta gönderme |mail:] hakkında daha fazla bilgi edinin. + +Şimdi, sunucuya geri döneceğiz ve `contactFormSucceeded()` yöntemini tamamlayacağız. `ContactFacade` sınıfının `sendMessage()` metodunu çağırır ve form verilerini ona aktarır. Peki `ContactFacade` nesnesini nasıl elde edeceğiz? Yapıcı tarafından bize aktarılacak: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +E-posta gönderildikten sonra, kullanıcıya mesajın gönderildiğini onaylayan sözde [flaş mesajı |application:components#flash-messages] gösteriyoruz ve ardından formun tarayıcıda *yenile* kullanılarak yeniden gönderilememesi için bir sonraki sayfaya yönlendiriyoruz. + + +Her şey çalışıyorsa, iletişim formunuzdan bir e-posta gönderebilmeniz gerekir. Tebrikler! + + +HTML E-posta Şablonu .[#toc-html-email-template] +------------------------------------------------ + +Şimdilik, yalnızca form tarafından gönderilen mesajı içeren düz metin bir e-posta gönderiliyor. Ancak e-postada HTML kullanabilir ve daha çekici hale getirebiliriz. Bunun için Latte'de bir şablon oluşturacağız ve bu şablonu `app/Model/contactEmail.latte` adresine kaydedeceğiz: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Geriye bu şablonu kullanmak için `ContactFacade` adresini değiştirmek kalıyor. Yapıcıda, bir [Latte şablon işleyicisi |latte:develop#how-to-render-a-template] olan `Latte\Engine` nesnesini üretebilen `LatteFactory` sınıfını talep ediyoruz. Şablonu bir dosyaya işlemek için `renderToString()` yöntemini kullanırız, ilk parametre şablonun yolu ve ikincisi değişkenlerdir. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Daha sonra oluşturulan HTML e-postasını orijinal `setBody()` yerine `setHtmlBody()` yöntemine aktarıyoruz. Ayrıca `setSubject()` adresinde e-postanın konusunu belirtmemize gerek yoktur, çünkü kütüphane bunu `` Şablon içinde. + + +Yapılandırma .[#toc-configuring] +-------------------------------- + + `ContactFacade` sınıf kodunda, yönetici e-postamız `admin@example.com` hala sabit kodludur. Bunu yapılandırma dosyasına taşımak daha iyi olacaktır. Nasıl Yapılır? + +İlk olarak, `ContactFacade` sınıfını değiştiriyoruz ve e-posta dizesini yapıcı tarafından geçirilen bir değişkenle değiştiriyoruz: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +İkinci adım ise bu değişkenin değerini yapılandırmaya koymaktır. `app/config/services.neon` dosyasına ekliyoruz: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +Ve işte bu kadar. `services` bölümünde çok sayıda öğe varsa ve e-postanın bunların arasında kaybolduğunu düşünüyorsanız, bunu bir değişken haline getirebiliriz. Girişi şu şekilde değiştireceğiz: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +Ve bu değişkeni `app/config/common.neon` dosyasında tanımlayın: + +```neon +parameters: + adminEmail: admin@example.com +``` + +Ve bitti! + + +{{sitename: En İyi Uygulamalar}} diff --git a/best-practices/tr/pagination.texy b/best-practices/tr/pagination.texy index 6dc3b85abb..5ae4641731 100644 --- a/best-practices/tr/pagination.texy +++ b/best-practices/tr/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Bir sonraki adım sunucuyu düzenlemektir. Şu anda görüntülenen sayfanın numarasını render yöntemine ileteceğiz. Bu numaranın URL'nin bir parçası olmaması durumunda, varsayılan değeri ilk sayfaya ayarlamamız gerekir. -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. HomepagePresenter şu şekilde görünecektir: +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; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/tr/restore-request.texy b/best-practices/tr/restore-request.texy index 71875f1f41..93546e81dd 100644 --- a/best-practices/tr/restore-request.texy +++ b/best-practices/tr/restore-request.texy @@ -31,9 +31,11 @@ class AdminPresenter extends Nette\Application\UI\Presenter ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/best-practices/uk/@home.texy b/best-practices/uk/@home.texy index 2b1f50a8d2..25d1fa8493 100644 --- a/best-practices/uk/@home.texy +++ b/best-practices/uk/@home.texy @@ -26,6 +26,7 @@ ----- - [Повторне використання форм |form-reuse] - [Форма для створення та редагування запису |creating-editing-form] +- [Створимо контактну форму |lets-create-contact-form] - [Залежні поля вибору |https://blog.nette.org/uk/zalezni-selektboksi-elegantno-v-nette-i-cistomu-js] </div> diff --git a/best-practices/uk/composer.texy b/best-practices/uk/composer.texy index b0f5c66048..2eead46d70 100644 --- a/best-practices/uk/composer.texy +++ b/best-practices/uk/composer.texy @@ -67,8 +67,8 @@ $db = new Nette\Database\Connection('sqlite::memory:'); ``` -Оновлення до останньої версії .[#toc-update-to-the-latest-version] -================================================================== +Оновлення пакетів до останніх версій .[#toc-update-packages-to-the-latest-versions] +=================================================================================== Для оновлення всіх використовуваних пакетів до останньої версії відповідно до обмежень версій, визначених у файлі `composer.json`, використовуйте команду `composer update`. Наприклад, для залежності `"nette/database": "^3.0"` буде встановлено останню версію 3.x.x, але не версію 4. @@ -98,25 +98,57 @@ composer create-project nette/web-project name-of-the-project Замість `name-of-the-project` вкажіть ім'я каталогу для вашого проєкту і виконайте команду. Composer отримає репозиторій `nette/web-project` з GitHub, який вже містить файл `composer.json`, і відразу після цього встановить сам фреймворк Nette. Залишилося тільки [перевірити права на запис |nette:troubleshooting#Setting-Directory-Permissions] для директорій `temp/` і `log/`, і ви готові до роботи. +Якщо ви знаєте, на якій версії PHP буде розміщено проект, обов'язково встановіть [її |#PHP Version]. + Версія PHP .[#toc-php-version] ============================== -Composer завжди встановлює ті версії пакетів, які сумісні з версією PHP, використовуваною вами на даний момент. Це, звичайно, може бути не та версія PHP, яка встановлена на вашому хості. Тому корисно додати інформацію про версію PHP на хості до файлу `composer.json`, і тоді будуть встановлені тільки версії пакетів, сумісні з хостом: +Composer завжди встановлює версії пакунків, сумісні з версією PHP, яку ви зараз використовуєте (точніше, з версією PHP, яка використовується у командному рядку під час запуску Composer). А це, ймовірно, не та версія, яку використовує ваш веб-хостинг. Ось чому дуже важливо додати інформацію про версію PHP на вашому хостингу до файлу `composer.json`. Після цього будуть встановлені лише версії пакунків, сумісні з хостом. + +Наприклад, щоб налаштувати роботу проекту на PHP 8.2.3, скористайтеся командою: + +```shell +composer config platform.php 8.2.3 +``` + +Таким чином версія буде записана у файл `composer.json`: ```js { - "require": { - ... - }, "config": { "platform": { - "php": "7.2" # PHP версия сервера + "php": "8.2.3" } } } ``` +Однак, номер версії PHP також вказано в іншому місці файлу, в розділі `require`. У той час як перше число вказує на версію, для якої будуть встановлені пакунки, друге число вказує на версію, для якої написано саму програму. +(Звичайно, немає сенсу, щоб ці версії відрізнялися, тому подвійний запис є надмірністю). Ви встановлюєте цю версію за допомогою команди: + +```shell +composer require php 8.2.3 --no-update +``` + +Або безпосередньо у файлі `composer.json`: + +```js +{ + "require": { + "php": "8.2.3" + } +} +``` + + +Неправдиві повідомлення .[#toc-false-reports] +============================================= + +При оновленні пакунків або зміні номерів версій виникають конфлікти. Один пакунок має вимоги, які конфліктують з іншим і так далі. Утім, іноді Composer видає хибні повідомлення. Він повідомляє про конфлікт, якого насправді не існує. У цьому випадку рекомендується видалити файл `composer.lock` і спробувати ще раз. + +Якщо повідомлення про помилку не зникає, це означає, що проблема серйозна, і вам потрібно прочитати, що і як потрібно змінити. + Packagist.org - глобальний репозиторій .[#toc-packagist-org-global-repository] ============================================================================== diff --git a/best-practices/uk/dynamic-snippets.texy b/best-practices/uk/dynamic-snippets.texy index c5499ad0ed..40867a88cf 100644 --- a/best-practices/uk/dynamic-snippets.texy +++ b/best-practices/uk/dynamic-snippets.texy @@ -51,7 +51,7 @@ Template: <article n:foreach="$articles as $article"> <h2>{$article->title}</h2> <div class="content">{$article->content}</div> - {snippet article-$article->id} + {snippet article-{$article->id}} {if !$article->liked} <a n:href="like! $article->id" class=ajax>Мне нравится</a> {else} diff --git a/best-practices/uk/editors-and-tools.texy b/best-practices/uk/editors-and-tools.texy index b7e51b9620..66372f474e 100644 --- a/best-practices/uk/editors-and-tools.texy +++ b/best-practices/uk/editors-and-tools.texy @@ -30,7 +30,7 @@ PHPStan - це інструмент, який виявляє логічні по Встановіть його через Composer: -```bash +```shell composer require --dev phpstan/phpstan-nette ``` @@ -49,7 +49,7 @@ parameters: А потім дозвольте йому проаналізувати класи в папці `app/`: -```bash +```shell vendor/bin/phpstan analyse app ``` diff --git a/best-practices/uk/form-reuse.texy b/best-practices/uk/form-reuse.texy index a8861b84dd..7993290a51 100644 --- a/best-practices/uk/form-reuse.texy +++ b/best-practices/uk/form-reuse.texy @@ -1,63 +1,218 @@ -Повторне використання форм у кількох місцях -******************************************* +Повторне використання форм у різних місцях +****************************************** .[perex] -Як повторно використовувати одну й ту саму форму в декількох місцях і не дублювати код? Це дуже легко зробити в Nette, і у вас є кілька способів на вибір. +У Nette є кілька варіантів повторного використання однієї і тієї ж форми в різних місцях без дублювання коду. У цій статті ми розглянемо різні рішення, включаючи ті, яких варто уникати. Фабрика форм .[#toc-form-factory] ================================= -Давайте створимо клас, який може створювати форму. Такий клас називається фабрикою. У тому місці, де ми хочемо використовувати форму (наприклад, у презентері), ми запитуємо [фабрику як залежність |dependency-injection:passing-dependencies]. +Один з основних підходів до використання одного і того ж компонента в різних місцях - це створення методу або класу, який генерує компонент, а потім виклик цього методу в різних місцях програми. Такий метод або клас називається *фабрикою*. Будь ласка, не плутайте з паттерном проектування *фабричний метод*, який описує специфічний спосіб використання фабрик і не має відношення до цієї теми. -Частина фабрики - це код, який передає дані для подальшого опрацювання, коли форма успішно відправлена. Зазвичай на шар моделі. Він також перевіряє, чи все пройшло успішно, і [передає назад |forms:validation#Processing-errors] будь-які помилки у форму. Модель у наступному прикладі представлена класом `Facade`: +Для прикладу, давайте створимо фабрику, яка буде створювати форму редагування: ```php use Nette\Application\UI\Form; -class EditFormFactory +class FormFactory +{ + public function createEditForm(): Form + { + $form = new Form; + $form->addText('title', 'Title:'); + // тут додаються додаткові поля форми + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` + +Тепер ви можете використовувати цю фабрику в різних місцях вашого додатку, наприклад, у презентаторах або компонентах. І ми зробимо це, [запросивши її як залежність |dependency-injection:passing-dependencies]. Отже, спочатку ми запишемо клас до конфігураційного файлу: + +```neon +services: + - FormFactory +``` + +А потім використаємо його у презентаторі: + + +```php +class MyPresenter extends Nette\Application\UI\Presenter { public function __construct( - private Facade $facade, + private FormFactory $formFactory, ) { } - public function create(/* параметри */): Form + protected function createComponentEditForm(): Form + { + $form = $this->formFactory->createEditForm(); + $form->onSuccess[] = function () { + // обробка надісланих даних + }; + return $form; + } +} +``` + +Ви можете розширити фабрику форм додатковими методами, щоб створювати інші типи форм відповідно до вашої програми. І, звичайно, ви можете додати метод, який створює базову форму без елементів, яку будуть використовувати інші методи: + +```php +class FormFactory +{ + public function createForm(): Form { $form = new Form; + return $form; + } - // додаємо елементи до форми + public function createEditForm(): Form + { + $form = $this->createForm(); + $form->addText('title', 'Title:'); + // тут додаються додаткові поля форми + $form->addSubmit('send', 'Save'); + return $form; + } +} +``` - $form->addSubmit('send', 'Submit'); - $form->onSuccess[] = [$this, 'processForm']; +Метод `createForm()` поки що не робить нічого корисного, але це швидко зміниться. + + +Заводські залежності .[#toc-factory-dependencies] +================================================= + +З часом стане очевидно, що нам потрібно, щоб форми були багатомовними. Це означає, що нам потрібно налаштувати [перекладач |forms:rendering#Translating] для всіх форм. Для цього ми модифікуємо клас `FormFactory`, щоб він приймав об'єкт `Translator` як залежність у конструкторі, і передавав його формі: + +```php +use Nette\Localization\Translator; +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function createForm(): Form + { + $form = new Form; + $form->setTranslator($this->translator); return $form; } - public function processForm(Form $form, array $values): void + //... +} +``` + +Оскільки метод `createForm()` також викликається іншими методами, які створюють конкретні форми, нам потрібно встановити транслятор тільки в цьому методі. І все готово. Не потрібно змінювати код доповідача або компонента, що дуже добре. + + +Більше фабричних класів .[#toc-more-factory-classes] +==================================================== + +Крім того, ви можете створити кілька класів для кожної форми, яку хочете використовувати у своєму додатку. +Такий підхід може підвищити читабельність коду і полегшити керування формами. Залиште оригінальний `FormFactory` для створення простої форми з базовою конфігурацією (наприклад, з підтримкою перекладу) і створіть новий заводський `EditFormFactory` для форми редагування. + +```php +class FormFactory +{ + public function __construct( + private Translator $translator, + ) { + } + + public function create(): Form { - try { - // обробка форми - $this->facade->process($values); + $form = new Form; + $form->setTranslator($this->translator); + return $form; + } +} - } catch (AnyModelException $e) { // обробка форми - $form->addError('...'); - } + +// ✅ використання композиції +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + // тут додаються додаткові поля форми + $form->addSubmit('send', 'Save'); + return $form; } } ``` -Звісно, фабрика може бути параметричною, тобто вона може приймати параметри, які впливатимуть на зовнішній вигляд створюваної форми. +Дуже важливо, щоб зв'язок між класами `FormFactory` і `EditFormFactory` був реалізований за допомогою композиції, а не успадкування об'єктів: -Тепер ми продемонструємо передачу фабрики презентеру. Спочатку ми записуємо її в конфігураційний файл: - -```neon -services: - - EditFormFactory +```php +// НІ! СПАДЩИНА ТУТ НЕ МАЄ ЗНАЧЕННЯ +class EditFormFactory extends FormFactory +{ + public function create(): Form + { + $form = parent::create(); + $form->addText('title', 'Title:'); + // тут додаються додаткові поля форми + $form->addSubmit('send', 'Save'); + return $form; + } +} ``` -А потім запитуємо в презентері. Останній крок обробки відправленої форми - перенаправлення на наступну сторінку: +Використання успадкування в цьому випадку було б абсолютно контрпродуктивним. Ви б дуже швидко зіткнулися з проблемами. Наприклад, якщо ви захочете додати параметри до методу `create()`; PHP повідомить про помилку, що його сигнатура відрізняється від батьківської. +Або при передачі залежності класу `EditFormFactory` через конструктор. Це призведе до того, що ми називаємо пеклом [конструктора |dependency-injection:passing-dependencies#Constructor hell]. +Загалом, краще надавати перевагу композиції, а не успадкуванню. + + +Обробка форм .[#toc-form-handling] +================================== + +Обробник форми, який викликається після успішного відправлення, також може бути частиною фабричного класу. Він буде працювати, передаючи надіслані дані моделі для обробки. Будь-які помилки будуть передані [назад |forms:validation#Processing Errors] у форму. У наступному прикладі модель представлена класом `Facade`: + +```php +class EditFormFactory +{ + public function __construct( + private FormFactory $formFactory, + private Facade $facade, + ) { + } + + public function create(): Form + { + $form = $this->formFactory->create(); + $form->addText('title', 'Title:'); + // тут додаються додаткові поля форми + $form->addSubmit('send', 'Save'); + $form->onSuccess[] = [$this, 'processForm']; + return $form; + } + + public function processForm(Form $form, array $data): void + { + try { + // обробка наданих даних + $this->facade->process($data); + + } catch (AnyModelException $e) { + $form->addError('...'); + } + } +} +``` + +Дозвольте доповідачу самому обробляти перенаправлення. Він додасть ще один обробник до події `onSuccess`, який виконає перенаправлення. Це дозволить використовувати форму в різних презентерах, і кожен з них зможе перенаправляти в інше місце. ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -70,24 +225,48 @@ class MyPresenter extends Nette\Application\UI\Presenter protected function createComponentEditForm(): Form { $form = $this->formFactory->create(); - - $form->onSuccess[] = function (Form $form) { - $this->redirect('this'); + $form->onSuccess[] = function () { + $this->flashMessage('Záznam byl uložen'); + $this->redirect('Homepage:'); }; - return $form; } } ``` -Оскільки перенаправлення обробляється обробником презентера, компонент можна використовувати в кількох місцях і в кожному з них перенаправляти в інше місце. +Це рішення використовує властивість форм, яка полягає в тому, що коли на формі або її елементі викликається `addError()`, наступний обробник `onSuccess` не викликається. + + +Успадкування від класу Form .[#toc-inheriting-from-the-form-class] +================================================================== + +Побудована форма не повинна бути дочірньою формою. Іншими словами, не використовуйте це рішення: + +```php +// НІ! СПАДЩИНА ТУТ НЕ МАЄ ЗНАЧЕННЯ +class EditForm extends Form +{ + public function __construct(Translator $translator) + { + parent::__construct(); + $form->addText('title', 'Title:'); + // тут додаються додаткові поля форми + $form->addSubmit('send', 'Save'); + $form->setTranslator($translator); + } +} +``` + +Замість того, щоб створювати форму в конструкторі, скористайтеся фабрикою. +Важливо розуміти, що клас `Form` - це насамперед інструмент для збирання форми, тобто конструктор форм. А зібрану форму можна вважати його продуктом. Однак продукт не є окремим випадком конструктора, між ними немає зв'язку *is a*, який лежить в основі успадкування. -Компонент із формою .[#toc-component-with-form] -=============================================== -Інший спосіб - створити новий [компонент |application:components], що містить форму. Це дає нам можливість візуалізувати форму певним чином, якщо компонент містить у собі шаблон. -Або ми можемо використовувати сигнали для зв'язку AJAX і завантаження інформації у форму, наприклад, для автозаповнення тощо. +Компонент форми .[#toc-form-component] +====================================== + +Зовсім інший підхід - створити [компонент |application:components], який містить форму. Це дає нові можливості, наприклад, рендерити форму певним чином, оскільки компонент містить шаблон. +Або можна використовувати сигнали для AJAX-комунікації та завантаження інформації у форму, наприклад, для підказки тощо. ```php @@ -105,34 +284,32 @@ class EditControl extends Nette\Application\UI\Control protected function createComponentForm(): Form { $form = new Form; - - // додаємо елементи до форми - - $form->addSubmit('send', 'Submit'); + $form->addText('title', 'Title:'); + // тут додаються додаткові поля форми + $form->addSubmit('send', 'Save'); $form->onSuccess[] = [$this, 'processForm']; return $form; } - public function processForm(Form $form, array $values): void + public function processForm(Form $form, array $data): void { try { - // обробка форми - $this->facade->process($values); + // обробка надісланих даних + $this->facade->process($data); - } catch (AnyModelException $e) { // обробка форми + } catch (AnyModelException $e) { $form->addError('...'); return; } // виклик події - $this->onSave($this, $values); + $this->onSave($this, $data); } } ``` -Далі ми створимо фабрику, яка вироблятиме цей компонент. Просто [напишіть його інтерфейс |application:components#Components with Dependencies]: - +Створимо фабрику, яка буде виробляти цей компонент. Достатньо [написати її інтерфейс |application:components#Components with Dependencies]: ```php interface EditControlFactory @@ -141,14 +318,14 @@ interface EditControlFactory } ``` -І додайте у файл конфігурації: +І додати його до конфігураційного файлу: ```neon services: - EditControlFactory ``` -І тепер ми можемо вимагати фабрику і використовувати її в презентері: +І тепер ми можемо запитувати фабрику і використовувати її в презентері: ```php class MyPresenter extends Nette\Application\UI\Presenter @@ -164,7 +341,7 @@ class MyPresenter extends Nette\Application\UI\Presenter $control->onSave[] = function (EditControl $control, $data) { $this->redirect('this'); - // або перенаправити на результат редагування, наприклад: + // або перенаправлення на результат редагування, наприклад // $this->redirect('detail', ['id' => $data->id]); }; @@ -173,5 +350,4 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -{{priority: -1}} {{sitename: Найкращі практики}} diff --git a/best-practices/uk/inject-method-attribute.texy b/best-practices/uk/inject-method-attribute.texy index e17e75a9a3..5c6b4d9057 100644 --- a/best-practices/uk/inject-method-attribute.texy +++ b/best-practices/uk/inject-method-attribute.texy @@ -2,13 +2,20 @@ *************************** .[perex] -На конкретних прикладах ми розглянемо можливості передачі залежностей доповідачам і пояснимо методи та атрибути/анотації `inject`. +У цій статті ми розглянемо різні способи передачі залежностей доповідачам у фреймворку Nette. Ми порівняємо основний метод, яким є конструктор, з іншими варіантами, такими як `inject` методи та атрибути. + +Для доповідачів передача залежностей за допомогою [конструктора |dependency-injection:passing-dependencies#Constructor Injection] є найкращим способом. +Однак, якщо ви створюєте спільного предка, від якого успадковують інші доповідачі (наприклад, BasePresenter), і цей предок також має залежності, виникає проблема, яку ми називаємо пеклом [конструктора |dependency-injection:passing-dependencies#Constructor hell]. +Це можна обійти за допомогою альтернативних методів, до яких відносяться методи ін'єкції та атрибути (анотації). `inject*()` Методи .[#toc-inject-methods] ========================================= -У presenter, як і в будь-якому іншому коді, найкращим способом передачі залежностей є використання [конструктора |dependency-injection:passing-dependencies#Constructor Injection]. Однак, якщо presenter успадковується від спільного предка (наприклад, `BasePresenter`), краще використовувати методи `inject*()` у цьому предку. Це окремий випадок сеттера, де метод починається з префікса `inject`. Це робиться тому, що ми залишаємо конструктор вільним для нащадків: +Це форма передачі залежності за допомогою [сетерів |dependency-injection:passing-dependencies#Setter Injection]. Назви цих сеттерів починаються з префікса inject. +Nette DI автоматично викликає такі іменовані методи одразу після створення екземпляра презентатора і передає їм усі необхідні залежності. Тому вони повинні бути оголошені як загальнодоступні. + +`inject*()` методи можна розглядати як своєрідне розширення конструктора на декілька методів. Завдяки цьому `BasePresenter` може отримувати залежності через інший метод і залишати конструктор вільним для своїх нащадків: ```php abstract class BasePresenter extends Nette\Application\UI\Presenter @@ -32,55 +39,18 @@ class MyPresenter extends BasePresenter } ``` -Основна відмінність від сетера полягає в тому, що Nette DI автоматично викликає методи з такими іменами в презентерах одразу після створення екземпляра, передаючи їм усі необхідні залежності. Презентер може містити кілька методів `inject*()`, і кожен метод може мати будь-яку кількість параметрів. - -Впровадження через конструктор не рекомендується для загальних предків, оскільки під час успадкування необхідно отримати залежність усіх батьківських презентерів і передавати їх у `parent::__construct()`: - -```php -abstract class BasePresenter extends Nette\Application\UI\Presenter -{ - private Foo $foo; - - public function __construct(Foo $foo) - { - $this->foo = $foo; - } -} - -class MyPresenter extends BasePresenter -{ - private Bar $bar; - - public function __construct(Foo $foo, Bar $bar) - { - parent::__construct($foo); // це ускладнення - $this->bar = $bar; - } -} -``` - -Методи `inject*()` також корисні у випадках, коли презентер [складається з ознак |presenter-traits], і кожна з них вимагає своєї власної залежності. +Ведучий може містити будь-яку кількість методів `inject*()`, і кожен з них може мати будь-яку кількість параметрів. Це також чудово підходить для випадків, коли презентер [складається з ознак |presenter-traits], і для кожної з них потрібна своя залежність. -Також можна використовувати анотацію `@inject`, але при цьому важливо пам'ятати, що інкапсуляція порушується. +`Inject` Атрибути .[#toc-inject-attributes] +=========================================== -Анотації `Inject` .[#toc-inject-annotations] -============================================ - -У цьому випадку властивість анотується як `@inject` у коментарі документації. Тип також може бути вказано в коментарі до документації, якщо ви використовуєте PHP нижче 7.4. - -```php -class MyPresenter extends Nette\Application\UI\Presenter -{ - /** @inject */ - public Cache $cache; -} -``` +Це форма ін' [єкції у властивості |dependency-injection:passing-dependencies#Property Injection]. Достатньо вказати, які властивості потрібно вставити, і Nette DI автоматично передасть залежності одразу після створення екземпляра доповідача. Щоб вставити їх, необхідно оголосити їх загальнодоступними. -Починаючи з версії PHP 8.0, властивість може бути позначена атрибутом `Inject`: +Властивості позначаються атрибутом: (раніше використовувалася анотація `/** @inject */`) ```php -use Nette\DI\Attributes\Inject; +use Nette\DI\Attributes\Inject; // цей рядок важливий class MyPresenter extends Nette\Application\UI\Presenter { @@ -89,9 +59,9 @@ class MyPresenter extends Nette\Application\UI\Presenter } ``` -Знову ж таки, Nette DI автоматично передасть залежності властивостям, анотованим таким чином у презентері, щойно екземпляр буде створено. +Перевагою такого способу передачі залежностей була дуже економна форма запису. Однак, із запровадженням популяризації [властивостей конструк |https://blog.nette.org/uk/php-8-0-povnij-oglyad-novin#toc-constructor-property-promotion]тора, використання конструктора видається простішим. -Цей метод має ті самі недоліки, що й передача залежностей у публічну властивість. Він використовується в презентерах, оскільки не ускладнює код і вимагає мінімального набору тексту. +З іншого боку, цей спосіб страждає тими ж недоліками, що і передача залежностей у властивості взагалі: ми не маємо контролю над змінами змінної, і в той же час змінна стає частиною публічного інтерфейсу класу, що є небажаним. {{sitename: Найкращі практики}} diff --git a/best-practices/uk/lets-create-contact-form.texy b/best-practices/uk/lets-create-contact-form.texy new file mode 100644 index 0000000000..724f0c58d7 --- /dev/null +++ b/best-practices/uk/lets-create-contact-form.texy @@ -0,0 +1,226 @@ +Створюємо контактну форму +************************* + +.[perex] +Давайте розглянемо, як створити контактну форму в Nette, в тому числі відправити її на електронну пошту. Тож давайте зробимо це! + +Спочатку нам потрібно створити новий проект. Як пояснюється на сторінці [Початок |nette:installation] роботи. А потім ми можемо почати створювати форму. + +Найпростіший спосіб - створити [форму безпосередньо у Presenter |forms:in-presenter]. Ми можемо використати готову форму `HomePresenter`. Ми додамо компонент `contactForm`, який представлятиме форму. Ми зробимо це, додавши фабричний метод `createComponentContactForm()` до коду, який буде створювати компонент: + +```php +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + protected function createComponentContactForm(): Form + { + $form = new Form; + $form->addText('name', 'Name:') + ->setRequired('Enter your name'); + $form->addEmail('email', 'E-mail:') + ->setRequired('Enter your e-mail'); + $form->addTextarea('message', 'Message:') + ->setRequired('Enter message'); + $form->addSubmit('send', 'Send'); + $form->onSuccess[] = [$this, 'contactFormSucceeded']; + return $form; + } + + public function contactFormSucceeded(Form $form, $data): void + { + // sending an email + } +} +``` + +Як бачите, ми створили два методи. Перший метод `createComponentContactForm()` створює нову форму. Вона має поля для імені, електронної пошти та повідомлення, які ми додаємо за допомогою методів `addText()`, `addEmail()` та `addTextArea()`. Ми також додали кнопку для відправки форми. +Але що, якщо користувач не заповнить деякі поля? У такому випадку ми повинні повідомити йому, що це поле є обов'язковим для заповнення. Ми зробили це за допомогою методу `setRequired()`. +Нарешті, ми також додали [подію |nette:glossary#events] `onSuccess`, яка спрацьовує в разі успішного відправлення форми. У нашому випадку вона викликає метод `contactFormSucceeded`, який відповідає за обробку надісланої форми. Ми додамо його до коду за мить. + +Нехай компонент `contantForm` рендериться в шаблоні `templates/Home/default.latte`: + +```latte +{block content} +<h1>Contant Form</h1> +{control contactForm} +``` + +Для відправки самого листа ми створюємо новий клас з ім'ям `ContactFacade` і розміщуємо його у файлі `app/Model/ContactFacade.php`: + +```php +<?php +declare(strict_types=1); + +namespace App\Model; + +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setSubject('Message from the contact form') + ->setBody($message); + + $this->mailer->send($mail); + } +} +``` + +Метод `sendMessage()` буде створювати і відправляти лист. Для цього він використовує так званий мейлер, який передається як залежність через конструктор. Дізнайтеся більше про надсилання [електронних |mail:] листів. + +Тепер повернемося до доповідача і завершимо метод `contactFormSucceeded()`. Він викликає метод `sendMessage()` класу `ContactFacade` і передає йому дані форми. А як ми отримаємо об'єкт `ContactFacade`? Він буде переданий нам конструктором: + +```php +use App\Model\ContactFacade; +use Nette\Application\UI\Form; +use Nette\Application\UI\Presenter; + +class HomePresenter extends Presenter +{ + public function __construct( + private ContactFacade $facade, + ) { + } + + protected function createComponentContactForm(): Form + { + // ... + } + + public function contactFormSucceeded(stdClass $data): void + { + $this->facade->sendMessage($data->email, $data->name, $data->message); + $this->flashMessage('The message has been sent'); + $this->redirect('this'); + } +} +``` + +Після відправлення листа ми показуємо користувачеві так зване [флеш-повідомлення |application:components#flash-messages], підтверджуючи, що лист відправлено, а потім перенаправляємо на наступну сторінку, щоб форму не можна було повторно відправити за допомогою *refresh* в браузері. + + +Що ж, якщо все працює, ви зможете відправити електронного листа зі своєї контактної форми. Вітаємо вас! + + +HTML шаблон електронного листа .[#toc-html-email-template] +---------------------------------------------------------- + +Наразі надсилається звичайний текстовий лист, що містить лише повідомлення, надіслане за допомогою форми. Але ми можемо використовувати HTML в листі і зробити його більш привабливим. Ми створимо для цього шаблон в Latte, який збережемо в `app/Model/contactEmail.latte`: + +```latte +<html> + <title>Message from the contact form + + +

Name: {$name}

+

E-mail: {$email}

+

Message: {$message}

+ + +``` + +Залишилося модифікувати `ContactFacade`, щоб використовувати цей шаблон. У конструкторі ми запитуємо клас `LatteFactory`, який може створити об'єкт `Latte\Engine`, [рендеринг шаблону Latte |latte:develop#how-to-render-a-template]. Ми використовуємо метод `renderToString()` для рендерингу шаблону у файл, першим параметром якого є шлях до шаблону, а другим - змінні. + +```php +namespace App\Model; + +use Nette\Bridges\ApplicationLatte\LatteFactory; +use Nette\Mail\Mailer; +use Nette\Mail\Message; + +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + $latte = $this->latteFactory->create(); + $body = $latte->renderToString(__DIR__ . '/contactEmail.latte', [ + 'email' => $email, + 'name' => $name, + 'message' => $message, + ]); + + $mail = new Message; + $mail->addTo('admin@example.com') // your email + ->setFrom($email, $name) + ->setHtmlBody($body); + + $this->mailer->send($mail); + } +} +``` + +Потім ми передаємо згенерований HTML-лист методу `setHtmlBody()` замість оригінального `setBody()`. Нам також не потрібно вказувати тему листа в `setSubject()`, оскільки бібліотека бере її з елемента `` в шаблоні. + + +Налаштування .[#toc-configuring] +-------------------------------- + +У коді класу `ContactFacade` наш email адміністратора `admin@example.com` все ще жорстко закодований. Було б краще перенести її в конфігураційний файл. Як це зробити? + +Спочатку модифікуємо клас `ContactFacade` і замінюємо рядок email на змінну, що передається конструктором: + +```php +class ContactFacade +{ + public function __construct( + private Mailer $mailer, + private LatteFactory $latteFactory, + private string $adminEmail, + ) { + } + + public function sendMessage(string $email, string $name, string $message): void + { + // ... + $mail = new Message; + $mail->addTo($this->adminEmail) + ->setFrom($email, $name) + ->setHtmlBody($body); + // ... + } +} +``` + +А другим кроком ми вносимо значення цієї змінної в конфігурацію. У файлі `app/config/services.neon` додаємо: + +```neon +services: + - App\Model\ContactFacade(adminEmail: admin@example.com) +``` + +І все. Якщо в розділі `services` багато елементів, і ви відчуваєте, що лист губиться серед них, ми можемо зробити його змінною. Змінимо запис на: + +```neon +services: + - App\Model\ContactFacade(adminEmail: %adminEmail%) +``` + +І визначимо цю змінну у файлі `app/config/common.neon`: + +```neon +parameters: + adminEmail: admin@example.com +``` + +І все готово! + + +{{sitename: Найкращі практики}} diff --git a/best-practices/uk/pagination.texy b/best-practices/uk/pagination.texy index 5c2d9cef64..b0883298b5 100644 --- a/best-practices/uk/pagination.texy +++ b/best-practices/uk/pagination.texy @@ -39,7 +39,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -111,7 +111,7 @@ class ArticleRepository Наступним кроком буде редагування презентера. Ми передамо номер поточної відображуваної сторінки в метод `render`. У разі, якщо цей номер не є частиною URL, нам потрібно встановити значення за замовчуванням для першої сторінки. -Ми також розширюємо метод `render` для отримання екземпляра Paginator, його налаштування та вибору потрібних статей для відображення в шаблоні. HomepagePresenter матиме такий вигляд: +Ми також розширюємо метод `render` для отримання екземпляра Paginator, його налаштування та вибору потрібних статей для відображення в шаблоні. HomePresenter матиме такий вигляд: ```php namespace App\Presenters; @@ -119,7 +119,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, @@ -216,7 +216,7 @@ namespace App\Presenters; use Nette; use App\Model\ArticleRepository; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { public function __construct( private ArticleRepository $articleRepository, diff --git a/best-practices/uk/restore-request.texy b/best-practices/uk/restore-request.texy index b08018709c..611d41a472 100644 --- a/best-practices/uk/restore-request.texy +++ b/best-practices/uk/restore-request.texy @@ -31,9 +31,11 @@ class AdminPresenter extends Nette\Application\UI\Presenter ```php +use Nette\Application\Attributes\Persistent; + class SignPresenter extends Nette\Application\UI\Presenter { - /** @persistent */ + #[Persistent] public string $backlink = ''; protected function createComponentSignInForm() diff --git a/caching/cs/@home.texy b/caching/cs/@home.texy index da7442c05c..bedde082c0 100644 --- a/caching/cs/@home.texy +++ b/caching/cs/@home.texy @@ -17,7 +17,7 @@ Používání cache je v Nette velmi snadné, přitom pokrývá i velmi pokroči Instalace ========= -Knihovnu stáhěte a nainstalujete pomocí nástroje [Composer|best-practices:composer]: +Knihovnu stáhnete a nainstalujete pomocí nástroje [Composer|best-practices:composer]: ```shell composer require nette/caching @@ -27,7 +27,7 @@ composer require nette/caching Základní použití ================ -Středobodem práce s cache neboli mezipamětí představuje objekt [api:Nette\Caching\Cache]. Vytvoříme si jeho instanci a jako parametr předáme konstruktoru tzv. úložiště. Což je objekt reprezentující místo, kam se budou data fyzicky ukládat (databáze, Memcached, soubory na disku, ...). K úložišti se dostaneme tak, že si jej necháte předat pomocí [dependency injection |dependency-injection:passing-dependencies] s typem `Nette\Caching\Storage`. Vše podstatné se dozvíte v [části Úložiště|#Úložiště]. +Středobodem práce s cache neboli mezipamětí představuje objekt [api:Nette\Caching\Cache]. Vytvoříme si jeho instanci a jako parametr předáme konstruktoru tzv. úložiště. Což je objekt reprezentující místo, kam se budou data fyzicky ukládat (databáze, Memcached, soubory na disku, ...). K úložišti se dostaneme tak, že si jej necháme předat pomocí [dependency injection |dependency-injection:passing-dependencies] s typem `Nette\Caching\Storage`. Vše podstatné se dozvíte v [části Úložiště|#Úložiště]. .[warning] Ve verzi 3.0 mělo rozhraní ještě prefix `I`, takže název byl `Nette\Caching\IStorage`. A dále konstanty třídy `Cache` byly psané velkými písmeny, takže třeba `Cache::EXPIRE` místo `Cache::Expire`. @@ -135,7 +135,7 @@ V dalších ukázkách budeme předpokládat druhou variantu a tedy existenci pr Expirace -------- -Nejjednodušší exirace představuje časový limit. Takto uložíme do cache data s platností 20 minut: +Nejjednodušší expirace představuje časový limit. Takto uložíme do cache data s platností 20 minut: ```php // akceptuje i počet sekund nebo UNIX timestamp @@ -175,7 +175,7 @@ $dependencies[Cache::Callbacks] = [ ]; ``` -Všechny kritéria je samozřejmě možné kombinovat. Cache pak vyexpiruje, když alespoň jedno kritérium není splněno. +Všechna kritéria je samozřejmě možné kombinovat. Cache pak vyexpiruje, když alespoň jedno kritérium není splněno. ```php $dependencies[Cache::Expire] = '20 minutes'; @@ -256,7 +256,7 @@ Pro hromadné čtení a zápisy do cache slouží metoda `bulkLoad()`, které p $values = $cache->bulkLoad($keys); ``` -Metoda `bulkLoad()` funguje podobně jako `load()` i s druhým parameterm callbackem, kterému se předává klíč generované položky: +Metoda `bulkLoad()` funguje podobně jako `load()` i s druhým parametrem callbackem, kterému se předává klíč generované položky: ```php $values = $cache->bulkLoad($keys, function ($key, &$dependencies) { @@ -395,7 +395,7 @@ Speciální implementací úložiště je `Nette\Caching\Storages\DevNullStorage Použití cache v kódu ==================== -Při používání cache v kódu máme dva způsoby, jak na to. První z nich je ten, že si necháme předat pomocí [dependency injection |dependency-injection:passing-dependencies] úložište a vytvoříme objekt `Cache`: +Při používání cache v kódu máme dva způsoby, jak na to. První z nich je ten, že si necháme předat pomocí [dependency injection |dependency-injection:passing-dependencies] úložiště a vytvoříme objekt `Cache`: ```php use Nette; diff --git a/component-model/en/@home.texy b/component-model/en/@home.texy index 76a116ce1c..3ae5c4a808 100644 --- a/component-model/en/@home.texy +++ b/component-model/en/@home.texy @@ -33,7 +33,7 @@ Returns a component. Attempt to call undefined child causes invoking of factory Iterating over Children ----------------------- -The [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] method is used for iteration. The first parameter specifies whether to traverse the components in depth (or recursively). With `true`, it not only iterates all its children, but also all children of its children, etc. Second parameter servers as an optional filter by class or interface. +The [getComponents($deep = false, $type = null) |api:Nette\ComponentModel\Container::getComponents()] method is used for iteration. The first parameter specifies whether to traverse the components in depth (or recursively). With `true`, it not only iterates all its children, but also all children of its children, etc. Second parameter serves as an optional filter by class or interface. ```php foreach ($form->getComponents(true, Nette\Forms\IControl::class) as $control) { diff --git a/contributing/bg/code.texy b/contributing/bg/code.texy index 4a25357eec..b4105cf6ce 100644 --- a/contributing/bg/code.texy +++ b/contributing/bg/code.texy @@ -1,110 +1,118 @@ -Предлагане на промяна в кодекса -******************************* +Принос към кода +*************** -Nette Framework използва Git и [GitHub |https://github.com/nette/nette] за поддържане на базата с код. Най-добрият начин да дадете своя принос е да запишете промените си в собственото си разклонение и след това да направите заявка за изтегляне в GitHub. В този документ са обобщени основните стъпки за успешно допринасяне. +.[perex] +Планирате да дадете своя принос към рамката на Nette и трябва да се запознаете с правилата и процедурите? Това ръководство за начинаещи ще ви преведе през стъпките за ефективно допринасяне към кода, работа с хранилища и въвеждане на промени. -Подготовка на средата .[#toc-preparing-environment] -=================================================== +Процедура .[#toc-procedure] +=========================== -Започнете с [разклоняване на |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette в GitHub |https://github.com/nette]. Внимателно [настройте |https://help.github.com/en/github/getting-started-with-github/set-up-git] локалната си среда на Git, конфигурирайте потребителското си име и имейл, тези данни ще идентифицират промените ви в историята на Nette Framework. +За да допринесете към кода, е необходимо да имате акаунт в [GitHub |https://github.com] и да сте запознати с основите на работата със системата за контрол на версиите Git. Ако не сте запознати с Git, можете да разгледате [ръководство git - the simple guide |https://rogerdudler.github.io/git-guide/] и да помислите за използване на някой от многото [графични клиенти |https://git-scm.com/downloads/guis]. -Работа по вашата кръпка .[#toc-working-on-your-patch] -===================================================== +Подготовка на средата и хранилището .[#toc-preparing-the-environment-and-repository] +------------------------------------------------------------------------------------ + +1) В GitHub създайте [разклонение на |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [хранилището на пакета |www:packages], който възнамерявате да модифицирате +2) [Клонирайте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] това хранилище на вашия компютър +3) Инсталирайте зависимостите, включително [Nette Tester |tester:], като използвате командата `composer install` +4) Проверете дали тестовете работят, като стартирате `composer tester` +5) Създайте [нов клон |#New Branch], базиран на последната публикувана версия + + +Внедряване на собствени промени .[#toc-implementing-your-own-changes] +--------------------------------------------------------------------- + +Сега можете да направите собствени промени в кода: + +1) Извършете желаните промени и не забравяйте за тестовете +2) Уверете се, че тестовете се изпълняват успешно, като използвате `composer tester` +3) Проверете дали кодът отговаря на [стандартите за кодиране |#coding standards] +4) Запазете (commit) промените с описание в [този формат |#Commit Description] + +Можете да създадете няколко коммита, по един за всяка логическа стъпка. Всеки commit трябва да е смислен сам по себе си. -Преди да започнете да работите по своята кръпка, създайте нов клон за промените си. -```shell -git checkout -b new_branch_name -``` -Можете да работите върху промяната на кода си. +Изпращане на промени .[#toc-submitting-changes] +----------------------------------------------- -Ако е възможно, направете промените от последната публикувана версия. +След като сте доволни от промените, можете да ги изпратите: +1) Изпратете промените в GitHub към вашето разклонение +2) Оттам ги изпратете в хранилището на Nette, като създадете [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) Предоставете [достатъчно информация |#pull request description] в описанието -Тестване на промените .[#toc-testing-your-changes] -================================================== -Трябва да инсталирате Nette Tester. Най-лесният начин е да извикате `composer install` в главното хранилище. Сега трябва да можете да стартирате тестове с `./vendor/bin/tester` в терминала. +Включване на обратна връзка .[#toc-incorporating-feedback] +---------------------------------------------------------- -Някои тестове може да се провалят поради липса на php.ini. Затова трябва да извикате runner-а с параметър -c и да посочите пътя до php.ini, например `./vendor/bin/tester -c ./tests/php.ini`. +Вашите ангажименти вече са видими за другите. Обичайно е да получавате коментари с предложения: -След като успеете да стартирате тестовете, можете да реализирате свои собствени или да промените провалянето, за да съответства на новото поведение. Прочетете повече за тестването с Nette Tester в [страницата с документация |tester:]. +1) Следете предложените промени +2) Включете ги като нови коммити или [ги слейте с предишните |https://help.github.com/en/github/using-git/about-git-rebase] +3) Изпратете отново коммитите в GitHub и те автоматично ще се появят в заявката за изтегляне + +Никога не създавайте нова заявка за изтегляне, за да промените съществуваща. + + +Документация .[#toc-documentation] +---------------------------------- + +Ако сте променили функционалност или сте добавили нова, не забравяйте да [я добавите и в документацията |documentation]. + + +Нов клон .[#toc-new-branch] +=========================== + +Ако е възможно, направете промени спрямо последната публикувана версия, т.е. последния таг в клона. За тага v3.2.1 създайте клон, като използвате тази команда: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Стандарти за кодиране .[#toc-coding-standards] ============================================== -Вашият код трябва да следва [стандартите за кодиране, |coding standard] използвани в Nette Framework. Това е лесно, тъй като има автоматична програма за проверка и поправка. Той може да бъде инсталиран чрез Composer в избрана от вас глобална директория: +Вашият код трябва да отговаря на [стандартите за кодиране, |coding standard] използвани в Nette Framework. Налице е автоматичен инструмент за проверка и поправка на кода. Можете да го инсталирате **глобално** чрез Composer в папка по ваш избор: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Сега трябва да можете да стартирате инструмента в терминала. Например, тази команда проверява и поправя кода в папките `src` и `tests` в текущата директория: +Сега трябва да можете да стартирате инструмента в терминала. Първата команда проверява, а втората поправя кода в папките `src` и `tests` в текущата директория: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Предаване на промените .[#toc-committing-the-changes] -===================================================== - -След като сте променили кода, трябва да предадете промените. Създайте повече предавания, по едно за всяка логическа стъпка. Всеки commit трябва да може да се използва в този си вид - без други commit-ове. Така че съответните тестове също трябва да бъдат включени в същия commit. +Ангажимент Описание .[#toc-commit-description] +============================================== -Моля, проверете два пъти дали кодът ви отговаря на правилата: -- Кодът не генерира грешки -- Кодът ви не нарушава никакви тестове. -- Промяната в кода ви е тествана. -- Не извършвате безполезни промени в бялото пространство. +В Nette темите на ангажиментите имат следния формат: `Presenter: fixed AJAX detection [Closes #69]` -Съобщението за предаване трябва да следва формата `Latte: fixed multi template rendering [Closes # 69]` т.е: - област, последвана от двоеточие -- целта на предаването в миналото, ако е възможно, започнете с "добавено.", "поправено.", "префактурирано.", променено, премахнато -- евентуална връзка към тракер на проблеми -- ако предаването отменя обратната съвместимост, добавете "BC break". -- може да има един свободен ред след темата и по-подробно описание, включително връзки към форума. - +- цел на ангажимента в минало време; ако е възможно, започвайте с думи като: added, fixed, refactored, changed, removed +- ако ангажиментът нарушава обратната съвместимост, добавете "BC break". +- всякаква връзка с органа за проследяване на проблеми, като например `(#123)` или `[Closes #69]` +- след темата може да има един празен ред, последван от по-подробно описание, включително например връзки към форума -Изтегляне на искания за допълнения .[#toc-pull-requesting-the-commits] -====================================================================== -Ако сте доволни от промените в кода си и извършените от вас промени, трябва да изпратите промените в GitHub. +Описание на заявката за изтегляне .[#toc-pull-request-description] +================================================================== -```shell -git push origin new_branch_name -``` +Когато създавате заявка за изтегляне, интерфейсът на GitHub ще ви позволи да въведете заглавие и описание. Посочете кратко заглавие, а в описанието включете възможно най-много информация за причините за вашата промяна. -Промените са публични, но трябва да предложите промените си за интегриране в главния клон на Nette. За целта направете [заявка за изтегляне |https://help.github.com/articles/creating-a-pull-request]. -Всяка заявка за изтегляне има заглавие и описание. Моля, посочете някакво описателно заглавие. То често е подобно на името на клона, например "Защита на сигналите срещу CSRF атака". +Също така посочете в заглавието дали става въпрос за нова функция или за поправка на грешка и дали тя може да предизвика проблеми с обратната съвместимост (BC break). Ако има свързан проблем, поставете връзка към него, така че той да бъде затворен при одобряване на заявката за промяна. -Описанието на заявката за изтегляне трябва да съдържа някаква по-конкретна информация за промените в кода ви: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Моля, променете информационната таблица, за да съответства на вашата заявка за изтегляне. Коментари към всеки елемент от списъка: -- Пише дали заявката за изтегляне добавя **функция** или е **поправка на грешка**. -- Позовава се на евентуално **свързан проблем**, който ще бъде затворен след обединяването на заявката за изтегляне. -- Казва дали заявката за изтегляне се нуждае от **промени в документацията**, ако да, предоставя препратки към съответните заявки за изтегляне. Не е необходимо да предоставяте промените в документацията веднага, обаче заявката за изтегляне няма да бъде обединена, ако са необходими промени в документацията. Промяната в документацията трябва да бъде подготвена за документация на английски език, други езикови мутации не са задължителни. -- Казва, ако заявката за изтегляне създава **прекъсване на БК**. Моля, считайте всичко, което променя публичния интерфейс, за прекъсване на БК. - -Окончателната таблица може да изглежда така: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Преработване на промените .[#toc-reworking-your-changes] -======================================================== -Много често се случва да получавате коментари към промените в кода си. Моля, опитайте се да следвате предложените промени и преработете своите ангажименти, за да го направите. Можете да ангажирате предложените промени като нови ангажименти и след това да ги смачкате към предишните. Вижте главата [Интерактивно преизчисляване в |https://help.github.com/en/github/using-git/about-git-rebase] GitHub. След като ребазирате промените си, форсирайте промените си към отдалеченото си разклонение, като всичко ще се разпространи автоматично към заявката за изтегляне. {{priority: -1}} diff --git a/contributing/bg/coding-standard.texy b/contributing/bg/coding-standard.texy index 463792fdbc..1fe5ef3256 100644 --- a/contributing/bg/coding-standard.texy +++ b/contributing/bg/coding-standard.texy @@ -38,7 +38,7 @@ - Функциите със стрелки се записват без интервал преди скобата, т.е. `fn($a) => $b`. - не се изисква празен ред между различните видове оператори за внос `use` -- типът на връщане на функцията/метода и отварящата скоба трябва да са на отделни редове за по-добра четливост: +- типът на връщане на функция/метод и отварящата къдрава скоба винаги са на отделни редове: ```php public function find( @@ -50,6 +50,10 @@ } ``` +Откриващата къдрава скоба на отделен ред е важна за визуалното отделяне на сигнатурата на функцията/метода от тялото. Ако сигнатурата е на един ред, разделянето е ясно (изображението вляво), ако е на няколко реда, в PSR сигнатурите и телата се сливат (в средата), докато в стандарта Nette те остават разделени (вдясно): + +[* new-line-after.webp *] + Блокове за документация (phpDoc) .[#toc-documentation-blocks-phpdoc] ==================================================================== diff --git a/contributing/bg/documentation.texy b/contributing/bg/documentation.texy index 9da3087e70..309d6cd434 100644 --- a/contributing/bg/documentation.texy +++ b/contributing/bg/documentation.texy @@ -1,53 +1,69 @@ -Писане на документацията -************************ +Принос към документацията +************************* .[perex] -Приносът към документацията е един от многото начини, по които можете да помогнете на Nette. Това е и една от най-удовлетворяващите дейности, тъй като помагате на другите да разберат рамката. +Приносът към документацията е една от най-ценните дейности, тъй като помага на другите да разберат рамката. -Как да пиша? .[#toc-how-to-write] ---------------------------------- +Как да пишем? .[#toc-how-to-write] +---------------------------------- -Документацията е предназначена предимно за хора, които тепърва се запознават с темата. Затова тя трябва да отговаря на няколко важни момента: +Документацията е предназначена предимно за хора, които са нови по темата. Затова тя трябва да отговаря на няколко важни момента: -- **Когато пишете, започвайте с прости и общи неща, а накрая преминете към по-напреднали теми**. -- Предоставяйте само информацията, която потребителят наистина трябва да знае за темата. -- Проверете дали информацията, която предоставяте, е действително вярна. Първо тествайте примера, преди да го дадете. -- Бъдете кратки - съкратете написаното наполовина. А след това не се колебайте да го направите отново. -- Опитайте се да обясните въпроса възможно най-добре. Например, опитайте се първо да обясните темата на колега. +- Започнете с прости и общи теми. В края преминете към по-напреднали теми +- Опитайте се да обясните темата възможно най-ясно. Например, опитайте се първо да обясните темата на колега +- Предоставяйте само информацията, която потребителят действително трябва да знае за дадена тема +- Уверете се, че информацията ви е точна. Тествайте всеки код +- Бъдете кратки - съкратете написаното наполовина. И след това не се колебайте да го направите отново +- Използвайте пестеливо подчертаване, от удебелени шрифтове до рамки като `.[note]` +- Спазвайте [стандарта за кодиране |Coding Standard] в кода -Имайте предвид тези точки по време на целия процес на писане. Документацията е написана на езика [Texy! |https://texy.info], затова научете неговия [синтаксис |syntax]. Можете да използвате редактора на документация на адрес https://editor.nette.org/, за да прегледате статията, докато я пишете. +Също така научете [синтаксиса |syntax]. За предварителен преглед на статията по време на писане можете да използвате [редактора за предварителен преглед |https://editor.nette.org/]. -Наред с общите правила за писане, изброени по-рано, моля, придържайте се към следните: -- Вашият код трябва да е в съответствие със [Стандарта за кодиране |Coding Standard]. -- Напишете имената на променливите, класовете и методите на английски език. -- Пространствата от имена трябва да се споменават само при първото им споменаване. -- Опитайте се да форматирате кода така, че да не се появяват ленти за превъртане. -- Спестете всички видове маркери за подчертаване, от удебелен шрифт до `.[note]` полета. -- От документацията се позовавайте само на документацията или на `www`. +Мутации на езика .[#toc-language-mutations] +------------------------------------------- +Английският е основният език, така че промените трябва да бъдат на английски. Ако английският не е силната ви страна, използвайте [DeepL Translator |https://www.deepl.com/translator] и други ще проверят текста ви. -Структура на документацията .[#toc-documentation-structure] ------------------------------------------------------------ +Преводът на други езици ще бъде извършен автоматично след одобрение и прецизиране на вашата редакция. + + +Тривиални редакции .[#toc-trivial-edits] +---------------------------------------- + +За да допринесете за документацията, трябва да имате акаунт в [GitHub |https://github.com]. -Пълната документация е поместена в GitHub в хранилището [nette/docs |https://github.com/nette/docs]. Това хранилище е разделено на клонове в зависимост от версията на документацията, например клонът `doc-3.1` съдържа документацията за версия 3.1. А след това има клон `nette.org`, който съдържа съдържанието на другите поддомейни на nette.org. +Най-лесният начин да направите малка промяна в документацията е да използвате връзките в края на всяка страница: -След това всеки клон е разделен на няколко папки: +- *Покажи в GitHub* отваря изходната версия на страницата в GitHub. След това просто натиснете бутона `E` и можете да започнете да редактирате (трябва да сте влезли в GitHub) +- *Отваряне на визуализацията* отваря редактор, в който можете веднага да видите окончателния визуален вид -* `cs` и `en`: съдържа файлове с документация за всяка езикова версия -* `files`: изображения, които могат да се вграждат в страниците с документация +Тъй като [редакторът за предварителен преглед |https://editor.nette.org/] няма възможност за запазване на промените директно в GitHub, трябва да копирате изходния текст в клипборда (с помощта на бутона *Копирай в клипборда*) и след това да го поставите в редактора в GitHub. +Под полето за редактиране се намира форма за изпращане. Тук не забравяйте да обобщите накратко и да обясните причината за вашата редакция. След подаване се създава т.нар. заявка за теглене (PR), която може да се редактира допълнително. -Пътят до файл без разширение съответства на URL адреса на страница от документацията. Така например файлът `en/quickstart/single-post.texy` ще има URL адрес `doc.nette.org/en/quickstart/single-post`. +По-големи редакции .[#toc-larger-edits] +--------------------------------------- -Внасяне на .[#toc-contributing] -------------------------------- +По-подходящо е да се запознаете с основите на работата със системата за контрол на версиите Git, вместо да разчитате единствено на интерфейса на GitHub. Ако не сте запознати с Git, можете да се обърнете към [ръководство git - the simple |https://rogerdudler.github.io/git-guide/] и да помислите за използване на някой от многото налични [графични клиенти |https://git-scm.com/downloads/guis]. -За да допринесете за документацията, трябва да имате акаунт в [GitHub |https://github.com] и да знаете основите на Git. Ако не сте запознати с Git, можете да разгледате краткото ръководство: [git - the simple guide (git - простото ръководство) |https://rogerdudler.github.io/git-guide/] или да използвате някой от многото графични инструменти: [GIT - GUI clients |https://git-scm.com/downloads/guis]. +Редактирайте документацията по следния начин: -Можете да правите прости промени директно в интерфейса на GitHub. По-удобно е обаче да създадете разклонение на хранилището [nette/docs |https://github.com/nette/docs] и да го клонирате на компютъра си. След това направете промени в съответния клон, предайте промяната, избутайте я във вашия GitHub и изпратете заявка за изтегляне в оригиналното хранилище `nette/docs`. +1) в GitHub създайте [разклонение на |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] хранилището [nette/docs |https://github.com/nette/docs] +2) [клонирайте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] това хранилище на вашия компютър +3) след това направете промени в [съответния клон |#Documentation Structure] +4) проверете за допълнителни интервали в текста с помощта на инструмента [Code-Checker |code-checker:] +5) запишете (commit) промените +6) ако сте доволни от промените, изпратете ги в GitHub във вашето разклонение +7) оттам ги изпратете в хранилището `nette/docs`, като създадете [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +Обичайно е да получавате коментари с предложения. Следете за предложените промени и ги включвайте. Добавете предложените промени като нови коммити и ги изпратете отново в GitHub. Никога не създавайте нова заявка за привличане само за да промените съществуваща такава. + + +Структура на документацията .[#toc-documentation-structure] +----------------------------------------------------------- -Преди всяка заявка за изтегляне е добре да стартирате [Code-Checker |code-checker:], за да проверите допълнителните бели полета в текста. +Цялата документация се намира в GitHub в хранилището [nette/docs |https://github.com/nette/docs]. Текущата версия се намира в главния клон, а по-старите версии са разположени в клонове като `doc-3.x`, `doc-2.x`. -{{priority: -1}} +Съдържанието на всеки клон е разделено на основни папки, представляващи отделните области на документацията. Например `application/` съответства на https://doc.nette.org/en/application, `latte/` съответства на https://latte.nette.org, и т.н. Всяка от тези папки съдържа подпапки, представляващи езиковите мутации (`cs`, `en`, ...), и по желание подпапка `files` с изображения, които могат да се вмъкват в страниците в документацията. diff --git a/contributing/cs/code.texy b/contributing/cs/code.texy index c5cb874e87..67d5b97ead 100644 --- a/contributing/cs/code.texy +++ b/contributing/cs/code.texy @@ -1,110 +1,118 @@ -Navrhování změn v kódu -********************** +Jak přispět do kódu +******************* -Nette Framework používá Git a [GitHub |https://github.com/nette/nette] jako úložiště pro kód. Nejlepší způsob, jak přispět, je provést změny ve vaší větvi a poté poslat pull request (PR) na GitHub. Tento dokument shrnuje hlavní kroky jak na to. +.[perex] +Chystáte se přispět do Nette Frameworku a potřebujete se zorientovat v pravidlech a postupech? Tento průvodce pro začátečníky vám krok za krokem ukáže, jak efektivně přispívat do kódu, pracovat s repozitáři a implementovat změny. -Příprava prostředí -================== +Postup +====== -Nejprve si [forkněte |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette z GitHubu |https://github.com/nette]. Pečlivě [nastavte |https://help.github.com/en/github/getting-started-with-github/set-up-git] prostředí Gitu, nakonfigurujte své uživatelské jméno a e-mail, tyto údaje vás budou identifikovat v historii změn kódu. +Pro přispívání do kódu je nezbytné mít účet na [GitHub|https://github.com] a být obeznámen se základy práce s verzovacím systémem Git. Pokud neovládáte práci s Gitem, můžete se podívat na průvodce [git - the simple guide |https://rogerdudler.github.io/git-guide/] a případně využít některého z mnoha [grafických klientů |https://git-scm.com/downloads/guis]. -Editace kódu -============ +Příprava prostředí a repozitáře +------------------------------- -Než začnete programovat, vytvořte si novou větev. -```shell -git checkout -b new_branch_name -``` +1) na GitHubu si vytvořte [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] repositáře [balíčku |www:packages], který se chystáte upravit +2) tento repositář [naklonujete |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] na svůj počítač +3) nainstalujte závislosti, včetně [Nette Testeru |tester:], pomocí příkazu `composer install` +4) zkontrolujte, že testy fungují, spuštěním `composer tester` +5) vytvořte si [novou větev |#Nová větev] založenou na poslední vydané verzi + + +Implementace vlastních změn +--------------------------- + +Nyní můžete provést své vlastní úpravy kódu: + +1) naprogramujte požadované změny a nezapomeňte na testy +2) ujistěte se, že testy proběhnou úspěšně, pomocí `composer tester` +3) zkontrolujte, zda kód splňuje [kódovací standard|#Coding Standards] +4) změny uložte (commitněte) s popisem v [tomto formátu|#Popis komitu] + +Můžete vytvořit několik commitů, jeden pro každý logický krok. Každý commit by měl být smysluplný samostatně. + + +Odeslání změn +------------- + +Jakmile budete se změnami spokojeni, můžete je odeslat: + +1) odešlete (pushněte) změny na GitHub do vašeho forku +2) odtud je odešlete do Nette repositáře vytvořením [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) uveďte v popisu [dostatek informací|#popis pull requestu] + + +Zapracování připomínek +---------------------- + +Vaše commity nyní uvidí i ostatní. Je běžné, že dostanete komentáře s připomínkami: -A můžete začít upravovat kód. +1) sledujte navrhované úpravy +2) zapracujte je jako nové commity nebo je [slučte s předchozími |https://help.github.com/en/github/using-git/about-git-rebase] +3) znovu odešlete commity na GitHub a automaticky se objeví v pull requestu -Pokud je to možné, dělejte změny oproti poslední vydané verzi. +Nikdy nevytvářejte nový pull request kvůli úpravě stávajícího. -Testování změn -============== +Dokumentace +----------- -Nainstalujte si Nette Tester. Nejjednodušší je zavolat `composer install` v kořenovém adresáři repositáře. Nyní byste měli být schopni spustit testy v terminálu pomocí `./vendor/bin/tester`. +Pokud jste změnili funkčnost nebo přidali novou, nezapomeňte ji také [přidat do dokumentace |documentation]. -Některé testy mohou selhat kvůli chybějícímu php.ini. Proto byste měli volat Tester s parametrem -c a zadat cestu k php.ini, například `./vendor/bin/tester -c ./tests/php.ini`. -Poté, co funguje spouštění testů, můžete implementovat vlastní změny v kódu. Další informace o testování pomocí nástroje Nette Tester najdete v [jeho dokumentaci |tester:]. +Nová větev +========== + +Pokud je to možné, provádějte změny vůči poslední vydané verzi, tj. poslednímu tagu v dané větvi. Pro tag `v3.2.1` vytvoříte větev tímto příkazem: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Coding Standards ================ -Váš kód musí splňovat [coding standard] používaný v Nette Framework. Je to snadné, protože existuje automatický nástroj pro kontrolu a opravu kódu. Lze jej nainstalovat přes Composer do vámi zvolené globální složky: +Váš kód musí splňovat [coding standard] používaný v Nette Framework. Pro kontrolu a opravu kódu je k dispozici automatický nástroj. Lze jej nainstalovat přes Composer **globálně** do vámi zvolené složky: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Nyní byste měli mít možnost spustit nástroj v terminálu. Tímto příkazem například zkontrolujete a opravíte kód ve složkách `src` a `tests` v aktuálním adresáři: +Nyní byste měli mít možnost spustit nástroj v terminálu. Prvním příkazem zkontrolujete a druhým i opravíte kód ve složkách `src` a `tests` v aktuálním adresáři: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Komitování změn -=============== - -Po úpravě kódu jej tzv. komitujete. Komitů klidně vytvořte několik, pro každý logický krok jeden. Každý commit by měl být smysluplný sám o sobě. Měl by zahrnovat i testy. +Popis komitu +============ -Zkontrolujte prosím, zda váš kód odpovídá pravidlům: -- Kód nevytváří žádné chyby -- Neporušuje žádné testy. -- Změny v kódu jsou testované. -- Neděláte zbytečné změny v bílém prostoru. +V Nette mají předměty komitů formát: `Presenter: fixed AJAX detection [Closes #69]` -Popis komitu by měl odpovídat formátu `Latte: fixed multi-template rendering [Closes #69]`, tj. - oblast následovaná dvojtečkou - účel commitu v minulém čase, je-li to možné, začněte slovem: "added .(přidaná nová vlastnost)", "fixed .(oprava)", "refactored .(změna v kódu beze změny chování)", changed, removed -- případná vazba na issue tracker - pokud commit přeruší zpětnou kompatibilitu, doplňte "BC break" -- za subjektem může být jeden volný řádek a podrobnější popis včetně odkazů na fórum. +- případná vazba na issue tracker jako `(#123)` nebo `[Closes #69]` +- za subjektem může následovat jeden volný řádek a poté podrobnější popis včetně třeba odkazů na fórum -Posílání Pull requestu -====================== +Popis pull requestu +=================== -Pokud jste se změnami spokojeni, odešlete je na GitHub. +Při vytváření pull requestu vám rozhraní GitHubu umožní zadat název a popis. Uveďte výstižný název a v popisu poskytněte co nejvíce informací o důvodech pro vaši změnu. -```shell -git push původu new_branch_name -``` +Zobrazí se také záhlaví, kde specifikujte, zda se jedná o novou funkci nebo opravu chyby a zda může dojít k narušení zpětné kompatibility (BC break). Pokud je k dispozici související problém (issue), odkazujte na něj, aby byl uzavřen po schválení pull requestu. -Kód už je veřejně dostupný, ale musíte je odeslat do hlavní větve (masteru) v repozitáři Nette. Udělejte tzv. [pull request |https://help.github.com/articles/creating-a-pull-request]. -Každá žádost má název a popis. Uveďte prosím výstižný název. Například "Zabezpečení signálů proti útoku CSRF". - -Popis pull requestu by měl obsahovat další specifické informace: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Upravte prosím tabulku informací tak, aby odpovídala vašemu pull requestu. -- uveďte, jestli jde o novou funkci, nebo o bugfix -- odkažte na případnou **související issue**, která bude uzavřena po schválení pull requestu. -- uveďte, zda požadavek potřebuje **dokumentační změny**, pokud ano, uveďte odkazy na příslušné issue. Změnu dokumentace nemusíte dělat okamžitě, ale pull request nebude přijat, pokud je zapotřebí změnit dokumentaci. Změna dokumentace musí být připravena pro anglickou dokumentaci, jiné jazykové mutace jsou nepovinné. -- uveďte, zda změna v kódu způsobí **BC break**. Vezměte prosím v úvahu všechno, co jej může způsobit. - -Konečná tabulka by mohla vypadat takto: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Přepracování změn -================= -Je běžné, že budete dostávat komentáře s připomínkami. Sledujte navrhované změny a zapracujte je. Navrhované změny můžete přidávat jako nové commity a sloučit je s předchozími. Viz kapitola [Interactive rebase |https://help.github.com/en/github/using-git/about-git-rebase] na stránce GitHubu. Poté opět odešlete commity na GitHub a vše se automaticky objeví v pull requestu. {{priority: -1}} diff --git a/contributing/cs/coding-standard.texy b/contributing/cs/coding-standard.texy index d3c8e37eff..3407d0ab3d 100644 --- a/contributing/cs/coding-standard.texy +++ b/contributing/cs/coding-standard.texy @@ -38,7 +38,7 @@ Nette Coding Standard odpovídá PSR-12 (resp. PER Coding Style), v některých - arrow funkce se píší bez mezery před závorkou, tj. `fn($a) => $b` - nevyžaduje se prázdný řádek mezi různými typy `use` import statements -- návratový typ funkce/metody a úvodní složená závorka by měly být umístěny na samostatných řádcích pro lepší čitelnost: +- návratový typ funkce/metody a úvodní složená závorka jsou vždy na samostatných řádcích: ```php public function find( @@ -50,6 +50,10 @@ Nette Coding Standard odpovídá PSR-12 (resp. PER Coding Style), v některých } ``` +Úvodní složená závorka na samostatném řádku je důležitá pro vizuální oddělení signatury funkce/metody od těla. Pokud je signatura na jednom řádku, je oddělení zřetelné (obrázek vlevo), pokud je na více řádcích, v PSR signatury a těla splývají (uprostřed), zatímco v Nette standardu jsou nadále oddělené (vpravo): + +[* new-line-after.webp *] + Bloky dokumentace (phpDoc) ========================== diff --git a/contributing/cs/documentation.texy b/contributing/cs/documentation.texy index 6e51001407..2e4da280da 100644 --- a/contributing/cs/documentation.texy +++ b/contributing/cs/documentation.texy @@ -1,55 +1,69 @@ -Psaní dokumentace -***************** +Jak přispět do dokumentace +************************** .[perex] -Přispívání do dokumentace je jednou z mnoha cest, jak lze pomoci Nette. Zároveň jde také o jednu z nejpřínosnějších činností, neboť pomáháte druhým frameworku porozumět. +Přispívání do dokumentace je jednou z nejpřínosnějších činností, neboť pomáháte druhým porozumět frameworku. Jak psát? --------- -Dokumentace je určena především lidem, kteří se s tématem teprve seznamují. Proto by měla splňovat několik důležitých bodů: +Dokumentace je určena především lidem, kteří se s tématem seznamují. Proto by měla splňovat několik důležitých bodů: -- **Při psaní začněte od jednoduchého a obecného, k pokročilejším tématům přejděte až na konci.** -- Uvádějte jen ty informace, které uživatel skutečně k danému tématu potřebuje vědět. -- Ověřte si, že vaše informace jsou skutečně pravdivé. Před uvedením kódu příklad nejprve vyzkoušejte. -- Buďte struční - co napíšete, zkraťte na polovinu. A pak klidně ještě jednou. -- Snažte se věc co nejlépe vysvětlit. Zkuste například téma nejprve vysvětlit kolegovi. +- Začněte od jednoduchého a obecného. K pokročilejším tématům přejděte až na konci +- Snažte se věc co nejlépe vysvětlit. Zkuste například téma nejprve vysvětlit kolegovi +- Uvádějte jen ty informace, které uživatel skutečně k danému tématu potřebuje vědět +- Ověřte si, že vaše informace jsou skutečně pravdivé. Každý kód otestujte +- Buďte struční - co napíšete, zkraťte na polovinu. A pak klidně ještě jednou +- Šetřete zvýrazňovači všeho druhu, od tučného písma po rámečky jako `.[note]` +- V kódech dodržujte [Coding Standard] -Tyto body mějte na paměti po celou dobu psaní. Další tipy naleznete v článku [Píšeme pro web |https://www.lupa.cz/clanky/piseme-pro-web/]. Dokumentace je psaná v [Texy! |https://texy.info], proto se naučte jeho [syntax]. Pro náhled článku během jeho psaní můžete použít editor dokumentace na adrese [https://editor.nette.org/]. +Osvojte si také [syntax]. Pro náhled článku během jeho psaní můžete použít [editor s náhledem |https://editor.nette.org/]. -Kromě výše uvedených bodů také dodržujte následující zásady: -- Primárním jazykem je angličtina, vaše změny by tedy měly být v obou jazycích. Pokud angličtina není vaší silnou stránkou, použijte [DeepL Translator |https://www.deepl.com/translator] a ostatní vám váš text zkontrolují. -- V textu dokumentace spíše "mykáme" a jsme zdvořilí. -- V příkladech dodržujte [Coding Standard]. -- Názvy proměnných, tříd a metod pište anglicky. -- Namespaces stačí uvádět při první zmínce. -- Kód se snažte formátovat tak, aby se nezobrazovaly posuvníky. -- Šetřete zvýrazňovači všeho druhu, od tučného písma po rámečky `.[note]`. -- Z dokumentace se odkazujte pouze na dokumentaci nebo `www`. +Jazykové mutace +--------------- +Primárním jazykem je angličtina, vaše změny by tedy měly být v češtině i angličtině. Pokud angličtina není vaší silnou stránkou, použijte [DeepL Translator |https://www.deepl.com/translator] a ostatní vám text zkontrolují. -Struktura dokumentace ---------------------- +Překlad do ostatních jazyků bude proveden automaticky po schválení a doladění vaší úpravy. + + +Triviální úpravy +---------------- + +Pro přispívání do dokumentace je nezbytné mít účet na [GitHub|https://github.com]. -Celá dokumentace je umístěna na GitHubu v repositáři [nette/docs |https://github.com/nette/docs]. Tento repositář je rozdělen do větví podle verze dokumentace, například větev `doc-3.1` obsahuje dokumentaci pro verzi 3.1. A dále je tu větev `nette.org`, ve které je obsah ostatních subdomén webu nette.org. +Nejjednodušší způsob, jak provést drobnou změnu v dokumentaci, je využít odkazy na konci každé stránky: -Každá větev je pak rozdělena do několika složek: +- *Ukaž na GitHubu* otevře zdrojovou podobu dané stránky na GitHubu. Poté stačí stisknout tlačítko `E` a můžete začít editovat (je nutné být na GitHubu přihlášený) +- *Otevři náhled* otevře editor, kde rovnou vidíte i výslednou vizuální podobu -* `cs` a `en`: obsahuje soubory dokumentace pro jednotlivé jazykové verze -* `files`: obrázky, které je možné do stránek v dokumentaci vkládat +Protože [editor s náhledem |https://editor.nette.org/] nemá možnost ukládat změny přímo na GitHub, je nutné po dokončení úprav zdrojový text zkopírovat do schránky (tlačítkem *Copy to clipboard*) a poté jej vložit do editoru na GitHubu. +Pod editačním polem je formulář pro odeslání. Zde nezapomeňte stručně shrnout a vysvětlit důvod vaší úpravy. Po odeslání vznikne tzv. pull request (PR), který je možné dále editovat. -Cesta k souboru bez přípony odpovídá URL adrese stránky v dokumentaci. Soubor `cs/quickstart/single-post.texy` tedy bude mít adresu `doc.nette.org/cs/quickstart/single-post`. +Větší úpravy +------------ -Přispívání do dokumentace -------------------------- +Vhodnější, než využít rozhraní GitHubu, je být obeznámen se základy práce s verzovacím systémem Git. Pokud neovládáte práci s Gitem, můžete se podívat na průvodce [git - the simple guide |https://rogerdudler.github.io/git-guide/] a případně využít některého z mnoha [grafických klientů |https://git-scm.com/downloads/guis]. -Pro přispívání do dokumentace je nutné mít účet na [GitHub|https://github.com] a znát základy práce s verzovacím systémem Git. Pokud s gitem nekamarádíte, můžete se podívat na rychlý návod: [git - the simple guide |https://rogerdudler.github.io/git-guide/], nebo si pomoci některým z mnoha grafických nástrojů: [GIT - GUI clients |https://git-scm.com/downloads/guis]. +Dokumentaci upravujte tímto způsobem: -Jednoduché změny můžete provádět přímo v rozhraní GitHubu. Vhodnější je ale vytvořit si fork repositáře [nette/docs |https://github.com/nette/docs] a ten si naklonovat na počítač. Poté v příslušné větvi proveďte změny, změnu commitněte, pushněte do svého repositáře na GitHub a pošlete pull request do původního repositáře `nette/docs`. Pamatujte, že hlavním jazykem dokumentace je angličtina, změny proto provádějte v obou jazycích. +1) na GitHubu si vytvořte [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] repositáře [nette/docs |https://github.com/nette/docs] +2) tento repositář [naklonujete |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] na svůj počítač +3) poté v [příslušné větvi|#Struktura dokumentace] proveďte změny +4) zkontroluje přebytečné mezery v textu pomocí nástroje [Code-Checker |code-checker:] +4) změny uložte (commitněte) +6) pokud jste se změnami spokojeni, odešlete (pushněte) je na GitHub do vašeho forku +7) odtud je odešlete do repositáře `nette/docs` vytvořením [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +Je běžné, že budete dostávat komentáře s připomínkami. Sledujte navrhované změny a zapracujte je. Navrhované změny přidejte jako nové commity a znovu odešlete na GitHub. Nikdy nevytvářejte kvůli úpravě pull requestu nový pull request. + + +Struktura dokumentace +--------------------- -Před každým pull requestem je dobré si spustit [Code-Checker |code-checker:], který nám zkontroluje přebytečné mezery v textu. +Celá dokumentace je umístěna na GitHubu v repositáři [nette/docs |https://github.com/nette/docs]. Aktuální verze je v masteru, starší verze jsou umístěny ve větvích jako `doc-3.x`, `doc-2.x`. -{{priority: -1}} +Obsah každé větve se dělí do hlavních složek představujících jednotlivé oblasti dokumentace. Například `application/` odpovídá https://doc.nette.org/cs/application, `latte/` odpovídá https://latte.nette.org atd. Každá tato složka obsahuje podsložky představující jazykové mutace (`cs`, `en`, ...) a případně podsložku `files` s obrázky, které je možné do stránek v dokumentaci vkládat. diff --git a/contributing/de/code.texy b/contributing/de/code.texy index a9b16442a7..820a1d7d88 100644 --- a/contributing/de/code.texy +++ b/contributing/de/code.texy @@ -1,110 +1,118 @@ -Vorschlag für eine Änderung des Kodex -************************************* +Zum Code beitragen +****************** -Nette Framework verwendet Git und [GitHub |https://github.com/nette/nette] für die Pflege der Codebasis. Der beste Weg, einen Beitrag zu leisten, ist, Ihre Änderungen in Ihren eigenen Fork zu übertragen und dann einen Pull-Request auf GitHub zu stellen. Dieses Dokument fasst die wichtigsten Schritte für einen erfolgreichen Beitrag zusammen. +.[perex] +Planen Sie, zum Nette Framework beizutragen, und müssen Sie sich mit den Regeln und Verfahren vertraut machen? Dieser Leitfaden für Anfänger führt Sie durch die Schritte, um effektiv zum Code beizutragen, mit Repositories zu arbeiten und Änderungen zu implementieren. -Vorbereiten der Umgebung .[#toc-preparing-environment] -====================================================== +Verfahren .[#toc-procedure] +=========================== -Beginnen Sie mit dem [Forking |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] von [Nette auf GitHub |https://github.com/nette]. Richten Sie Ihre lokale Git-Umgebung sorgfältig [ein |https://help.github.com/en/github/getting-started-with-github/set-up-git], konfigurieren Sie Ihren Benutzernamen und Ihre E-Mail-Adresse, da diese Anmeldedaten Ihre Änderungen in der Nette-Framework-Historie identifizieren. +Um zum Code beizutragen, ist es wichtig, ein Konto auf [GitHub |https://github.com] zu haben und mit den Grundlagen der Arbeit mit dem Versionskontrollsystem Git vertraut zu sein. Wenn Sie mit Git nicht vertraut sind, können Sie sich den [Leitfaden "Git - die einfache Anleitung |https://rogerdudler.github.io/git-guide/] " ansehen und die Verwendung eines der vielen [grafischen Clients |https://git-scm.com/downloads/guis] in Betracht ziehen. -Arbeiten Sie an Ihrem Patch .[#toc-working-on-your-patch] -========================================================= +Vorbereiten der Umgebung und des Repositorys .[#toc-preparing-the-environment-and-repository] +--------------------------------------------------------------------------------------------- -Bevor Sie mit der Arbeit an Ihrem Patch beginnen, erstellen Sie einen neuen Zweig für Ihre Änderungen. -```shell -git checkout -b new_branch_name -``` +1) Erstellen Sie auf GitHub einen [Fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] des [Paket-Repositorys |www:packages], das Sie ändern möchten. +2) [Klonen |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] Sie dieses Repository auf Ihren Computer +3) Installieren Sie die Abhängigkeiten, einschließlich [Nette Tester |tester:], mit dem Befehl `composer install` +4) Überprüfen Sie, ob die Tests funktionieren, indem Sie den Befehl `composer tester` +5) Erstellen Sie eine [neue Verzweigung |#New Branch] auf der Grundlage der letzten veröffentlichten Version + + +Eigene Änderungen implementieren .[#toc-implementing-your-own-changes] +---------------------------------------------------------------------- + +Jetzt können Sie Ihre eigenen Code-Anpassungen vornehmen: + +1) Implementieren Sie die gewünschten Änderungen und vergessen Sie dabei nicht die Tests +2) Stellen Sie sicher, dass die Tests erfolgreich laufen. `composer tester` +3) Prüfen Sie, ob der Code den [Kodierungsstandards |#coding standards]entspricht +4) Speichern (Commit) Sie die Änderungen mit einer Beschreibung in [diesem Format |#Commit Description] + +Sie können mehrere Übertragungen erstellen, eine für jeden logischen Schritt. Jeder Commit sollte für sich genommen sinnvoll sein. + + +Einreichen von Änderungen .[#toc-submitting-changes] +---------------------------------------------------- + +Wenn Sie mit den Änderungen zufrieden sind, können Sie sie übermitteln: + +1) Verschieben Sie die Änderungen auf GitHub in Ihren Fork +2) Übermitteln Sie sie von dort aus an das Nette-Repository, indem Sie einen [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) erstellen +3) Geben Sie [ausreichende Informationen |#pull request description] in der Beschreibung an + + +Feedback einbeziehen .[#toc-incorporating-feedback] +--------------------------------------------------- + +Ihre Übertragungen sind nun für andere sichtbar. Es ist üblich, dass Sie Kommentare mit Vorschlägen erhalten: -Sie können an Ihrer Codeänderung arbeiten. +1) Behalten Sie die vorgeschlagenen Änderungen im Auge +2) Fügen Sie sie als neue Commits ein oder [fügen Sie sie mit früheren zusammen |https://help.github.com/en/github/using-git/about-git-rebase] +3) Senden Sie die Commits erneut an GitHub, und sie erscheinen automatisch in der Pull-Anfrage -Wenn möglich, nehmen Sie Änderungen an der zuletzt veröffentlichten Version vor. +Erstellen Sie niemals einen neuen Pull Request, um einen bestehenden zu ändern. -Testen Ihrer Änderungen .[#toc-testing-your-changes] -==================================================== +Dokumentation .[#toc-documentation] +----------------------------------- -Sie müssen Nette Tester installieren. Der einfachste Weg ist der Aufruf von `composer install` im Repository root. Nun sollten Sie in der Lage sein, Tests mit `./vendor/bin/tester` im Terminal auszuführen. +Wenn Sie eine Funktion geändert oder eine neue hinzugefügt haben, vergessen Sie nicht, [diese auch in die Dokumentation aufzunehmen |documentation]. -Einige Tests können aufgrund einer fehlenden php.ini fehlschlagen. Daher sollten Sie den Runner mit dem Parameter -c aufrufen und den Pfad zur php.ini angeben, zum Beispiel `./vendor/bin/tester -c ./tests/php.ini`. -Nachdem Sie die Tests ausführen können, können Sie Ihre eigenen Tests implementieren oder die fehlgeschlagenen Tests an das neue Verhalten anpassen. Lesen Sie mehr über das Testen mit Nette Tester auf der [Dokumentationsseite |tester:]. +Neuer Zweig .[#toc-new-branch] +============================== + +Wenn möglich, nehmen Sie Änderungen an der letzten veröffentlichten Version vor, d.h. am letzten Tag im Zweig. Für das Tag v3.2.1 erstellen Sie einen Zweig mit diesem Befehl: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Kodierungsstandards .[#toc-coding-standards] ============================================ -Ihr Code muss den im Nette Framework verwendeten [Kodierungsstandards |coding standard] entsprechen. Das ist einfach, denn es gibt einen automatischen Checker und Fixer. Er kann über Composer in das von Ihnen gewählte globale Verzeichnis installiert werden: +Ihr Code muss den im Nette Framework verwendeten [Kodierungsstandards |coding standard] entsprechen. Es gibt ein automatisches Tool zur Überprüfung und Korrektur des Codes. Sie können es **global** über Composer in einen Ordner Ihrer Wahl installieren: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Nun sollten Sie in der Lage sein, das Tool im Terminal auszuführen. Dieser Befehl prüft und korrigiert zum Beispiel den Code in den Ordnern `src` und `tests` im aktuellen Verzeichnis: +Nun sollten Sie in der Lage sein, das Tool im Terminal auszuführen. Der erste Befehl überprüft und der zweite korrigiert den Code in den Ordnern `src` und `tests` im aktuellen Verzeichnis: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Übertragen der Änderungen .[#toc-committing-the-changes] -======================================================== +Commit Beschreibung .[#toc-commit-description] +============================================== -Nachdem Sie den Code geändert haben, müssen Sie Ihre Änderungen festschreiben. Erstellen Sie mehrere Commits, einen für jeden logischen Schritt. Jeder Commit sollte so wie er ist verwendbar sein - ohne andere Commits. Daher sollten auch die entsprechenden Tests im selben Commit enthalten sein. +In Nette haben Commit-Themen das folgende Format: `Presenter: fixed AJAX detection [Closes #69]` -Bitte überprüfen Sie, ob Ihr Code den Regeln entspricht: -- Der Code erzeugt keine Fehler -- Ihr Code bricht keine Tests. -- Ihre Codeänderung ist getestet. -- Sie nehmen keine unnötigen Änderungen am Leerraum vor. +- Bereich, gefolgt von einem Doppelpunkt +- Zweck des Commits in der Vergangenheitsform; wenn möglich, beginnen Sie mit Worten wie: added, fixed, refactored, changed, removed +- wenn der Commit die Abwärtskompatibilität bricht, fügen Sie "BC break" hinzu +- jede Verbindung zum Issue Tracker, wie `(#123)` oder `[Closes #69]` +- nach dem Betreff kann eine Leerzeile folgen, gefolgt von einer detaillierteren Beschreibung, z.B. mit Links zum Forum -Die Commit-Nachricht sollte das folgende Format haben `Latte: fixed multi template rendering [Closes # 69]` d. h: -- ein Bereich gefolgt von einem Doppelpunkt -- der Zweck des Commits in der Vergangenheit, wenn möglich, beginnen Sie mit "hinzugefügt.", "behoben.", "überarbeitet.", geändert, entfernt -- eventueller Link zum Issue Tracker -- wenn der Commit die Abwärtskompatibilität aufhebt, füge "BC break" hinzu -- eine freie Zeile nach dem Betreff und eine ausführlichere Beschreibung mit Links zum Forum. +Pull Request Beschreibung .[#toc-pull-request-description] +========================================================== -Pull-Request für die Commits .[#toc-pull-requesting-the-commits] -================================================================ - -Wenn Sie mit Ihren Codeänderungen und Commits zufrieden sind, müssen Sie Ihre Commits auf GitHub veröffentlichen. - -```shell -git push origin new_branch_name -``` +Wenn Sie eine Pull-Anfrage erstellen, können Sie über die GitHub-Schnittstelle einen Titel und eine Beschreibung eingeben. Geben Sie einen prägnanten Titel an und fügen Sie in der Beschreibung so viele Informationen wie möglich über die Gründe für Ihre Änderung ein. -Die Änderungen sind öffentlich zugänglich, aber Sie müssen Ihre Änderungen zur Integration in den Master-Zweig von Nette vorschlagen. Dazu müssen Sie [einen Pull-Request erstellen |https://help.github.com/articles/creating-a-pull-request]. -Jeder Pull Request hat einen Titel und eine Beschreibung. Bitte geben Sie einen beschreibenden Titel an. Er ist oft ähnlich wie der Name des Zweiges, zum Beispiel "Absicherung von Signalen gegen CSRF-Angriffe". +Geben Sie außerdem in der Kopfzeile an, ob es sich um eine neue Funktion oder eine Fehlerbehebung handelt und ob sie zu Problemen mit der Abwärtskompatibilität führen kann (BC-Break). Falls es ein verwandtes Problem gibt, verlinken Sie es, damit es bei Genehmigung des Pull Requests geschlossen wird. -Die Beschreibung der Pull-Anfrage sollte einige spezifischere Informationen über Ihre Codeänderungen enthalten: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Bitte ändern Sie die Informationstabelle, damit sie zu Ihrem Pull Request passt. Kommentare zu jedem Listenpunkt: -- Gibt an, ob der Pull Request ein **Feature** hinzufügt oder ein **Bugfix** ist. -- Verweist auf einen **verwandten Fehler**, der nach dem Zusammenführen des Pull Requests geschlossen werden wird. -- Sagt, ob der Pull Request **Änderungen an der Dokumentation** benötigt, wenn ja, gib Referenzen zu den entsprechenden Pull Requests an. Sie müssen die Dokumentationsänderung nicht sofort zur Verfügung stellen, jedoch wird der Pull Request nicht zusammengeführt, wenn die Dokumentationsänderung benötigt wird. Die Dokumentationsänderung muss für die englische Dokumentation vorbereitet werden, andere Sprachmutationen sind optional. -- Sagt, wenn der Pull Request **einen BC-Break** erzeugt. Bitte betrachten Sie alles, was die öffentliche Schnittstelle ändert, als einen BC-Break. - -Die endgültige Tabelle könnte wie folgt aussehen: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Überarbeitung Ihrer Änderungen .[#toc-reworking-your-changes] -============================================================= -Es ist durchaus üblich, dass Sie Kommentare zu Ihren Codeänderungen erhalten. Bitte versuchen Sie, die vorgeschlagenen Änderungen zu befolgen und Ihre Commits entsprechend zu überarbeiten. Sie können vorgeschlagene Änderungen als neue Commits committen und sie dann mit den vorherigen Commits zusammenführen. Siehe Kapitel [Interaktives Rebase |https://help.github.com/en/github/using-git/about-git-rebase] auf GitHub. Nachdem Sie Ihre Änderungen rebasiert haben, pushen Sie Ihre Änderungen auf Ihren entfernten Fork, alles wird automatisch auf den Pull Request übertragen. {{priority: -1}} diff --git a/contributing/de/coding-standard.texy b/contributing/de/coding-standard.texy index 218b0f5c4d..32aa073db3 100644 --- a/contributing/de/coding-standard.texy +++ b/contributing/de/coding-standard.texy @@ -38,7 +38,7 @@ Nette Coding Standard entspricht der PSR-12 (oder PER Coding Style), in einigen - Pfeilfunktionen werden ohne ein Leerzeichen vor der Klammer geschrieben, d.h. `fn($a) => $b` - zwischen verschiedenen Arten von `use` import-Anweisungen ist keine Leerzeile erforderlich -- der Rückgabetyp der Funktion/Methode und die öffnende Klammer sollten zur besseren Lesbarkeit in getrennten Zeilen stehen: +- Der Rückgabetyp einer Funktion/Methode und die öffnende geschweifte Klammer stehen immer in getrennten Zeilen: ```php public function find( @@ -50,6 +50,10 @@ Nette Coding Standard entspricht der PSR-12 (oder PER Coding Style), in einigen } ``` +Die öffnende geschweifte Klammer in einer separaten Zeile ist wichtig, um die Funktions-/Methodensignatur visuell vom Körper zu trennen. Wenn die Signatur in einer Zeile steht, ist die Trennung klar (Bild links), wenn sie in mehreren Zeilen steht, verschmelzen in PSR die Signaturen und Körper miteinander (in der Mitte), während sie im Nette-Standard getrennt bleiben (rechts): + +[* new-line-after.webp *] + Dokumentationsblöcke (phpDoc) .[#toc-documentation-blocks-phpdoc] ================================================================= diff --git a/contributing/de/documentation.texy b/contributing/de/documentation.texy index 296f6893c1..4c925709ec 100644 --- a/contributing/de/documentation.texy +++ b/contributing/de/documentation.texy @@ -1,53 +1,69 @@ -Verfassen der Dokumentation +Zur Dokumentation beitragen *************************** .[perex] -Die Mitarbeit an der Dokumentation ist eine der vielen Möglichkeiten, wie Sie Nette helfen können. Es ist auch eine der lohnendsten Aktivitäten, da Sie anderen helfen, das Framework zu verstehen. +Die Mitarbeit an der Dokumentation ist eine der wertvollsten Aktivitäten, da sie anderen hilft, den Rahmen zu verstehen. Wie schreibt man? .[#toc-how-to-write] -------------------------------------- -Die Dokumentation ist in erster Linie für Personen gedacht, die sich gerade erst mit dem Thema vertraut machen. Daher sollte sie einige wichtige Punkte erfüllen: +Die Dokumentation richtet sich in erster Linie an Personen, die mit dem Thema noch nicht vertraut sind. Daher sollte sie mehrere wichtige Punkte erfüllen: -- **Beginnen Sie beim Schreiben mit dem Einfachen und Allgemeinen und gehen Sie am Ende zu fortgeschritteneren Themen über.** -- Geben Sie nur die Informationen, die der Benutzer wirklich über das Thema wissen muss. -- Vergewissern Sie sich, dass Ihre Informationen tatsächlich wahr sind. Testen Sie das Beispiel zuerst, bevor Sie es geben. -- Seien Sie kurz und bündig - kürzen Sie das, was Sie schreiben, auf die Hälfte. Und dann können Sie es gerne wiederholen. -- Versuchen Sie, das Thema so gut wie möglich zu erklären. Versuchen Sie zum Beispiel, das Thema zuerst einem Kollegen zu erklären. +- Beginnen Sie mit einfachen und allgemeinen Themen. Gehen Sie am Ende zu fortgeschritteneren Themen über +- Versuchen Sie, das Thema so klar wie möglich zu erklären. Versuchen Sie zum Beispiel, das Thema zuerst einem Kollegen zu erklären. +- Geben Sie nur die Informationen, die der Benutzer für ein bestimmtes Thema tatsächlich benötigt +- Stellen Sie sicher, dass Ihre Informationen korrekt sind. Testen Sie jeden Code +- Seien Sie prägnant - kürzen Sie, was Sie schreiben, um die Hälfte. Und dann können Sie es gerne wiederholen +- Verwenden Sie Hervorhebungen sparsam, von fetten Schriftarten bis hin zu Rahmen wie `.[note]` +- Befolgen Sie den [Kodierungsstandard |Coding Standard] im Code -Behalten Sie diese Punkte während des gesamten Schreibprozesses im Hinterkopf. Die Dokumentation ist in [Texy! |https://texy.info] geschrieben, lernen Sie also dessen [Syntax |syntax]. Sie können den Dokumentationseditor auf https://editor.nette.org/ verwenden, um den Artikel während des Schreibens in der Vorschau anzuzeigen. +Lernen Sie auch die [Syntax |syntax]. Für eine Vorschau des Artikels während des Schreibens, können Sie die [Vorschau-Editor |https://editor.nette.org/] verwenden. -Neben den oben aufgeführten allgemeinen Schreibregeln sollten Sie sich auch an die folgenden halten: -- Ihr Code sollte mit dem [Coding Standard |Coding Standard] übereinstimmen. -- Schreiben Sie die Namen von Variablen, Klassen und Methoden in Englisch. -- Namensräume müssen nur bei der ersten Erwähnung genannt werden. -- Versuchen Sie, den Code so zu formatieren, dass keine Bildlaufleisten angezeigt werden. -- Sparen Sie sich alle Arten von Hervorhebungen, von Fettschrift bis zu `.[note]` Kästen. -- Verweisen Sie in der Dokumentation nur auf die Dokumentation oder `www`. +Sprachmutationen .[#toc-language-mutations] +------------------------------------------- +Englisch ist die Hauptsprache, daher sollten Ihre Änderungen auf Englisch erfolgen. Wenn Englisch nicht Ihre Stärke ist, verwenden Sie [DeepL Translator |https://www.deepl.com/translator] und andere werden Ihren Text überprüfen. -Aufbau der Dokumentation .[#toc-documentation-structure] --------------------------------------------------------- +Die Übersetzung in andere Sprachen erfolgt automatisch nach der Genehmigung und Feinabstimmung Ihrer Bearbeitung. -Die vollständige Dokumentation wird auf GitHub im Repository [nette/docs |https://github.com/nette/docs] gehostet. Dieses Repository ist in Zweige unterteilt, die auf der Version der Dokumentation basieren, z. B. enthält der Zweig `doc-3.1` die Dokumentation für Version 3.1. Und dann gibt es noch den Zweig `nette.org`, der den Inhalt der anderen Subdomains von nette.org enthält. -Jeder Zweig ist dann in mehrere Ordner unterteilt: +Triviale Bearbeitungen .[#toc-trivial-edits] +-------------------------------------------- -* `cs` und `en`: enthält die Dokumentationsdateien für jede Sprachversion -* `files`: Bilder, die in die Dokumentationsseiten eingebettet werden können +Um zur Dokumentation beizutragen, müssen Sie ein Konto auf [GitHub |https://github.com] haben. -Der Pfad zu einer Datei ohne Erweiterung entspricht der URL einer Seite in der Dokumentation. So hat die Datei `en/quickstart/single-post.texy` die URL `doc.nette.org/en/quickstart/single-post`. +Der einfachste Weg, eine kleine Änderung an der Dokumentation vorzunehmen, ist die Verwendung der Links am Ende jeder Seite: +- *Auf GitHub anzeigen* öffnet die Quellversion der Seite auf GitHub. Klicken Sie dann einfach auf die Schaltfläche `E` und Sie können mit der Bearbeitung beginnen (Sie müssen bei GitHub angemeldet sein) +- *Vorschau öffnen* öffnet einen Editor, in dem Sie sofort die endgültige visuelle Form sehen können -Beitrag von .[#toc-contributing] --------------------------------- +Da der [Vorschau-Editor |https://editor.nette.org/] nicht die Möglichkeit bietet, Änderungen direkt auf GitHub zu speichern, müssen Sie den Quelltext in die Zwischenablage kopieren (mit der Schaltfläche *In die Zwischenablage kopieren*) und dann in den Editor auf GitHub einfügen. +Unterhalb des Bearbeitungsfeldes befindet sich ein Formular zum Einreichen. Vergessen Sie hier nicht, den Grund für Ihre Bearbeitung kurz zusammenzufassen und zu erläutern. Nach dem Einreichen wird ein sogenannter Pull Request (PR) erstellt, der weiter bearbeitet werden kann. -Um zur Dokumentation beizutragen, müssen Sie ein Konto bei [GitHub |https://github.com] haben und die Grundlagen von Git kennen. Wenn Sie mit Git nicht vertraut sind, können Sie sich die Kurzanleitung ansehen: [git - the simple guide |https://rogerdudler.github.io/git-guide/], oder eines der vielen grafischen Tools verwenden: [GIT - GUI-Clients |https://git-scm.com/downloads/guis]. -Sie können einfache Änderungen direkt in der GitHub-Oberfläche vornehmen. Es ist jedoch bequemer, einen Fork des Repository [nette/docs |https://github.com/nette/docs] zu erstellen und es auf Ihren Computer zu klonen. Nehmen Sie dann Änderungen im entsprechenden Zweig vor, übertragen Sie die Änderungen, pushen Sie sie auf Ihr GitHub und senden Sie eine Pull-Anfrage an das ursprüngliche Repository `nette/docs`. +Größere Bearbeitungen .[#toc-larger-edits] +------------------------------------------ -Vor jedem Pull-Request ist es eine gute Idee, [Code-Checker |code-checker:] laufen zu lassen, um zusätzliche Leerzeichen im Text zu überprüfen. +Es ist sinnvoller, mit den Grundlagen der Arbeit mit dem Versionskontrollsystem Git vertraut zu sein, als sich ausschließlich auf die GitHub-Schnittstelle zu verlassen. Wenn Sie mit Git nicht vertraut sind, können Sie den Leitfaden [Git - die einfache Anleitung |https://rogerdudler.github.io/git-guide/] zu Rate ziehen und die Verwendung eines der vielen verfügbaren [grafischen Clients |https://git-scm.com/downloads/guis] in Betracht ziehen. -{{priority: -1}} +Bearbeiten Sie die Dokumentation auf die folgende Weise: + +1) Erstellen Sie auf GitHub einen [Fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] des [nette/docs-Repository |https://github.com/nette/docs] +2) [klonen |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] Sie dieses Repository auf Ihren Computer +3) nehmen Sie dann Änderungen im [entsprechenden Zweig |#Documentation Structure]vor +4) Prüfen Sie mit dem [Code-Checker |code-checker:] auf zusätzliche Leerzeichen im Text +5) speichern (committen) Sie die Änderungen +6) wenn du mit den Änderungen zufrieden bist, veröffentliche sie auf GitHub in deinem Fork +7) übermitteln Sie sie von dort aus an das Repository `nette/docs`, indem Sie einen [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) erstellen + +Es ist üblich, dass Sie Kommentare mit Vorschlägen erhalten. Behalten Sie die vorgeschlagenen Änderungen im Auge und nehmen Sie sie auf. Fügen Sie die vorgeschlagenen Änderungen als neue Commits hinzu und senden Sie sie erneut an GitHub. Erstellen Sie niemals einen neuen Pull Request, nur um einen bestehenden zu ändern. + + +Struktur der Dokumentation .[#toc-documentation-structure] +---------------------------------------------------------- + +Die gesamte Dokumentation befindet sich auf GitHub im Repository [nette/docs |https://github.com/nette/docs]. Die aktuelle Version befindet sich im Master-Zweig, während ältere Versionen in Zweigen wie `doc-3.x`, `doc-2.x` zu finden sind. + +Der Inhalt jedes Zweigs ist in Hauptordner unterteilt, die einzelne Bereiche der Dokumentation repräsentieren. So entspricht beispielsweise `application/` dem Ordner https://doc.nette.org/en/application, `latte/` dem Ordner https://latte.nette.org, usw. Jeder dieser Ordner enthält Unterordner für Sprachmutationen (`cs`, `en`, ...) und optional einen Unterordner `files` mit Bildern, die in die Seiten der Dokumentation eingefügt werden können. diff --git a/contributing/el/code.texy b/contributing/el/code.texy index e59fb50bb4..782bb06fea 100644 --- a/contributing/el/code.texy +++ b/contributing/el/code.texy @@ -1,110 +1,118 @@ -Πρόταση αλλαγής του κώδικα -************************** +Συμβολή στον κώδικα +******************* -Το Nette Framework χρησιμοποιεί το Git και το [GitHub |https://github.com/nette/nette] για τη συντήρηση της βάσης κώδικα. Ο καλύτερος τρόπος για να συνεισφέρετε είναι να δεσμεύσετε τις αλλαγές σας στο δικό σας fork και στη συνέχεια να κάνετε ένα pull request στο GitHub. Αυτό το έγγραφο συνοψίζει τα κύρια βήματα για την επιτυχή συνεισφορά. +.[perex] +Σχεδιάζετε να συνεισφέρετε στο Nette Framework και πρέπει να εξοικειωθείτε με τους κανόνες και τις διαδικασίες; Αυτός ο οδηγός για αρχάριους θα σας καθοδηγήσει στα βήματα για να συνεισφέρετε αποτελεσματικά στον κώδικα, να εργαστείτε με αποθετήρια και να εφαρμόσετε αλλαγές. -Προετοιμασία περιβάλλοντος .[#toc-preparing-environment] -======================================================== +Διαδικασία .[#toc-procedure] +============================ -Ξεκινήστε με το [forking |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [του Nette στο GitHub |https://github.com/nette]. [Ρυθμίστε |https://help.github.com/en/github/getting-started-with-github/set-up-git] προσεκτικά το τοπικό σας περιβάλλον Git, ρυθμίστε το όνομα χρήστη και το email σας, αυτά τα διαπιστευτήρια θα αναγνωρίσουν τις αλλαγές σας στο ιστορικό του Nette Framework. +Για να συνεισφέρετε στον κώδικα, είναι απαραίτητο να έχετε έναν λογαριασμό στο [GitHub |https://github.com] και να είστε εξοικειωμένοι με τα βασικά στοιχεία της εργασίας με το σύστημα ελέγχου εκδόσεων Git. Αν δεν είστε εξοικειωμένοι με το Git, μπορείτε να διαβάσετε το [git - the simple |https://rogerdudler.github.io/git-guide/] guide και να εξετάσετε το ενδεχόμενο να χρησιμοποιήσετε έναν από τους πολλούς [γραφικούς πελάτες |https://git-scm.com/downloads/guis]. -Επεξεργασία του Patch σας .[#toc-working-on-your-patch] -======================================================= +Προετοιμασία του περιβάλλοντος και του αποθετηρίου .[#toc-preparing-the-environment-and-repository] +--------------------------------------------------------------------------------------------------- + +1) Στο GitHub, δημιουργήστε μια [διακλάδωση |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] του [αποθετηρίου πακέτων |www:packages] που σκοπεύετε να τροποποιήσετε +2) [Κλωνοποιήστε |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] αυτό το αποθετήριο στον υπολογιστή σας +3) Εγκαταστήστε τις εξαρτήσεις, συμπεριλαμβανομένου του [Nette Tester |tester:], χρησιμοποιώντας την εντολή `composer install` +4) Επαληθεύστε ότι οι δοκιμές λειτουργούν εκτελώντας την εντολή `composer tester` +5) Δημιουργήστε έναν [νέο κλάδο |#New Branch] με βάση την τελευταία έκδοση που κυκλοφόρησε + + +Εφαρμογή των δικών σας αλλαγών .[#toc-implementing-your-own-changes] +-------------------------------------------------------------------- + +Τώρα μπορείτε να κάνετε τις δικές σας προσαρμογές στον κώδικα: + +1) Εφαρμόστε τις επιθυμητές αλλαγές και μην ξεχνάτε τις δοκιμές +2) Βεβαιωθείτε ότι οι δοκιμές εκτελούνται με επιτυχία χρησιμοποιώντας `composer tester` +3) Ελέγξτε αν ο κώδικας πληροί τα [πρότυπα κωδικοποίησης |#coding standards] +4) Αποθηκεύστε (commit) τις αλλαγές με μια περιγραφή [αυτής της μορφής |#Commit Description] + +Μπορείτε να δημιουργήσετε πολλαπλές δεσμεύσεις, μία για κάθε λογικό βήμα. Κάθε commit θα πρέπει να έχει νόημα από μόνο του. -Πριν ξεκινήσετε να εργάζεστε πάνω στο patch σας, δημιουργήστε ένα νέο κλάδο για τις αλλαγές σας. -```shell -git checkout -b new_branch_name -``` -Μπορείτε να εργαστείτε πάνω στην αλλαγή του κώδικά σας. +Υποβολή αλλαγών .[#toc-submitting-changes] +------------------------------------------ -Αν είναι δυνατόν, κάντε αλλαγές από την τελευταία έκδοση που κυκλοφόρησε. +Αφού είστε ικανοποιημένοι με τις αλλαγές, μπορείτε να τις υποβάλετε: +1) Σπρώξτε τις αλλαγές στο GitHub στο fork σας +2) Από εκεί, υποβάλετε τις στο αποθετήριο Nette δημιουργώντας ένα [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) Παρέχετε [επαρκείς πληροφορίες |#pull request description] στην περιγραφή -Δοκιμή των αλλαγών σας .[#toc-testing-your-changes] -=================================================== -Πρέπει να εγκαταστήσετε το Nette Tester. Ο ευκολότερος τρόπος είναι να καλέσετε το `composer install` στη ρίζα του αποθετηρίου. Τώρα θα πρέπει να είστε σε θέση να εκτελέσετε δοκιμές με το `./vendor/bin/tester` στο τερματικό. +Ενσωμάτωση ανατροφοδότησης .[#toc-incorporating-feedback] +--------------------------------------------------------- -Ορισμένες δοκιμές μπορεί να αποτύχουν λόγω έλλειψης του php.ini. Επομένως, θα πρέπει να καλέσετε το runner με την παράμετρο -c και να καθορίσετε τη διαδρομή προς το php.ini, για παράδειγμα `./vendor/bin/tester -c ./tests/php.ini`. +Οι δεσμεύσεις σας είναι τώρα ορατές στους άλλους. Είναι σύνηθες να λαμβάνετε σχόλια με προτάσεις: -Αφού είστε σε θέση να εκτελέσετε τις δοκιμές, μπορείτε να υλοποιήσετε τις δικές σας ή να αλλάξετε την αποτυχία ώστε να ταιριάζει με τη νέα συμπεριφορά. Διαβάστε περισσότερα για τις δοκιμές με το Nette Tester στη [σελίδα τεκμηρίωσης |tester:]. +1) Να παρακολουθείτε τις προτεινόμενες αλλαγές +2) Ενσωματώστε τις ως νέες commits ή [συγχωνεύστε τις με προηγούμενες |https://help.github.com/en/github/using-git/about-git-rebase] +3) Υποβάλετε εκ νέου τα commits στο GitHub, και θα εμφανιστούν αυτόματα στο pull request + +Ποτέ μην δημιουργείτε νέο pull request για να τροποποιήσετε ένα υπάρχον. + + +Τεκμηρίωση .[#toc-documentation] +-------------------------------- + +Εάν έχετε αλλάξει τη λειτουργικότητα ή έχετε προσθέσει μια νέα, μην ξεχάσετε να [την προσθέσετε και στην τεκμηρίωση |documentation]. + + +Νέος κλάδος .[#toc-new-branch] +============================== + +Αν είναι δυνατόν, κάντε αλλαγές σε σχέση με την τελευταία έκδοση που κυκλοφόρησε, δηλαδή την τελευταία ετικέτα του κλάδου. Για την ετικέτα v3.2.1, δημιουργήστε έναν κλάδο χρησιμοποιώντας αυτή την εντολή: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Πρότυπα κωδικοποίησης .[#toc-coding-standards] ============================================== -Ο κώδικάς σας πρέπει να ακολουθεί τα [πρότυπα κωδικοποίησης |coding standard] που χρησιμοποιούνται στο Nette Framework. Είναι εύκολο επειδή υπάρχει αυτοματοποιημένος ελεγκτής & διορθωτής. Μπορεί να εγκατασταθεί μέσω του Composer στον παγκόσμιο κατάλογο που έχετε επιλέξει: +Ο κώδικάς σας πρέπει να πληροί τα [πρότυπα κωδικοποίησης |coding standard] που χρησιμοποιούνται στο Nette Framework. Υπάρχει διαθέσιμο ένα αυτόματο εργαλείο για τον έλεγχο και τη διόρθωση του κώδικα. Μπορείτε να το εγκαταστήσετε **σφαιρικά** μέσω του Composer σε έναν φάκελο της επιλογής σας: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Τώρα θα πρέπει να είστε σε θέση να εκτελέσετε το εργαλείο στο τερματικό. Για παράδειγμα, αυτή η εντολή ελέγχει και διορθώνει τον κώδικα στους φακέλους `src` και `tests` στον τρέχοντα κατάλογο: +Τώρα θα πρέπει να μπορείτε να εκτελέσετε το εργαλείο στο τερματικό. Η πρώτη εντολή ελέγχει και η δεύτερη διορθώνει τον κώδικα στους φακέλους `src` και `tests` στον τρέχοντα κατάλογο: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Μεταφορά των αλλαγών .[#toc-committing-the-changes] -=================================================== - -Αφού αλλάξετε τον κώδικα, πρέπει να δεσμεύσετε τις αλλαγές σας. Δημιουργήστε περισσότερες δεσμεύσεις, μία για κάθε λογικό βήμα. Κάθε δέσμευση θα πρέπει να είναι χρησιμοποιήσιμη ως έχει - χωρίς άλλες δεσμεύσεις. Έτσι, οι κατάλληλες δοκιμές θα πρέπει επίσης να περιλαμβάνονται στο ίδιο commit. - -Παρακαλούμε, ελέγξτε δύο φορές ότι ο κώδικάς σας ταιριάζει με τους κανόνες: -- Ο κώδικας δεν παράγει σφάλματα -- Ο κώδικάς σας δεν παραβιάζει καμία δοκιμή. -- Η αλλαγή του κώδικά σας είναι δοκιμασμένη. -- Δεν διαπράττετε άχρηστες αλλαγές λευκού χώρου. +Περιγραφή δέσμευσης .[#toc-commit-description] +============================================== -Το μήνυμα δέσμευσης πρέπει να ακολουθεί τη μορφή `Latte: fixed multi template rendering [Closes # 69]` δηλ: -- μια περιοχή ακολουθούμενη από μια άνω και κάτω τελεία -- ο σκοπός της δέσμευσης στο παρελθόν, αν είναι δυνατόν, ξεκινήστε με "added.", "fixed.", "refactored.", changed, removed -- ενδεχόμενος σύνδεσμος προς τον ιχνηλάτη ζητημάτων -- αν η δέσμευση ακυρώνει την προς τα πίσω συμβατότητα, προσθέστε "BC break" -- μπορεί να υπάρχει μια ελεύθερη γραμμή μετά το θέμα και μια πιο λεπτομερής περιγραφή, συμπεριλαμβανομένων των συνδέσμων προς το φόρουμ. +Στη Nette, τα θέματα των δεσμεύσεων έχουν την ακόλουθη μορφή: `Presenter: fixed AJAX detection [Closes #69]` +- περιοχή ακολουθούμενη από άνω και κάτω τελεία +- σκοπός της δέσμευσης σε παρελθοντικό χρόνο- αν είναι δυνατόν, αρχίστε με λέξεις όπως: "added .(νέο χαρακτηριστικό)", "fixed .(διόρθωση)", "refactored .(αλλαγή κώδικα χωρίς αλλαγή συμπεριφοράς)", changed, removed +- αν η δέσμευση σπάει την προς τα πίσω συμβατότητα, προσθέστε "BC break" +- οποιαδήποτε σύνδεση με τον ανιχνευτή ζητημάτων, όπως `(#123)` ή `[Closes #69]` +- μετά το θέμα, μπορεί να υπάρχει μία κενή γραμμή, ακολουθούμενη από μία πιο λεπτομερή περιγραφή, συμπεριλαμβανομένων, για παράδειγμα, συνδέσμων προς το φόρουμ -Pull-Requesting των Commits .[#toc-pull-requesting-the-commits] -=============================================================== -Αν είστε ικανοποιημένοι με τις αλλαγές και τις δεσμεύσεις του κώδικά σας, πρέπει να προωθήσετε τις δεσμεύσεις σας στο GitHub. +Περιγραφή αιτήματος μετακίνησης .[#toc-pull-request-description] +================================================================ -```shell -git push origin new_branch_name -``` +Κατά τη δημιουργία ενός pull request, η διεπαφή του GitHub θα σας επιτρέψει να εισαγάγετε έναν τίτλο και μια περιγραφή. Δώστε έναν συνοπτικό τίτλο και συμπεριλάβετε όσο το δυνατόν περισσότερες πληροφορίες στην περιγραφή σχετικά με τους λόγους της αλλαγής σας. -Οι αλλαγές είναι παρούσες δημόσια, ωστόσο, πρέπει να προτείνετε τις αλλαγές σας για ενσωμάτωση στον κύριο κλάδο του Nette. Για να το κάνετε αυτό, [κάντε ένα pull request |https://help.github.com/articles/creating-a-pull-request]. -Κάθε pull request έχει έναν τίτλο και μια περιγραφή. Παρακαλούμε δώστε κάποιον περιγραφικό τίτλο. Συχνά είναι παρόμοιος με το όνομα του κλάδου, για παράδειγμα "Securing signals against CSRF attack". +Επίσης, προσδιορίστε στην επικεφαλίδα αν πρόκειται για νέο χαρακτηριστικό ή διόρθωση σφάλματος και αν μπορεί να προκαλέσει προβλήματα συμβατότητας προς τα πίσω (BC break). Αν υπάρχει σχετικό θέμα, παραπέμψτε σε αυτό, ώστε να κλείσει με την έγκριση του pull request. -Η περιγραφή του pull request θα πρέπει να περιέχει κάποιες πιο συγκεκριμένες πληροφορίες σχετικά με τις αλλαγές στον κώδικά σας: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Παρακαλούμε αλλάξτε τον πίνακα πληροφοριών ώστε να ταιριάζει στο pull request σας. Σχόλια σε κάθε στοιχείο του καταλόγου: -- Λέει αν το pull request προσθέτει **feature** ή είναι **bugfix**. -- Παραπέμπει τελικά σε **σχετικό θέμα**, το οποίο θα κλείσει μετά τη συγχώνευση του pull request. -- Λέει αν το pull request χρειάζεται τις **αλλαγές τεκμηρίωσης**, αν ναι, παρέχετε παραπομπές στα κατάλληλα pull requests. Δεν χρειάζεται να παρέχετε αμέσως την αλλαγή τεκμηρίωσης, ωστόσο, το pull request δεν θα συγχωνευθεί αν η αλλαγή τεκμηρίωσης είναι απαραίτητη. Η αλλαγή τεκμηρίωσης πρέπει να προετοιμαστεί για την αγγλική τεκμηρίωση, οι μεταλλάξεις άλλων γλωσσών είναι προαιρετικές. -- Λέει αν το pull request δημιουργεί **ένα σπάσιμο της BC**. Παρακαλώ, θεωρήστε οτιδήποτε αλλάζει τη δημόσια διεπαφή ως BC break. - -Ο τελικός πίνακας θα μπορούσε να μοιάζει ως εξής: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Επεξεργασία των αλλαγών σας .[#toc-reworking-your-changes] -========================================================== -Είναι πολύ συνηθισμένο να λαμβάνετε σχόλια για την αλλαγή του κώδικά σας. Παρακαλούμε, προσπαθήστε να ακολουθήσετε τις προτεινόμενες αλλαγές και επεξεργαστείτε τις δεσμεύσεις σας για να το κάνετε αυτό. Μπορείτε να δεσμεύσετε τις προτεινόμενες αλλαγές ως νέες δεσμεύσεις και στη συνέχεια να τις συμπιέσετε με τις προηγούμενες. Ανατρέξτε στο κεφάλαιο [Interactive rebase |https://help.github.com/en/github/using-git/about-git-rebase] στο GitHub. Αφού επαναπροσδιορίσετε τις αλλαγές σας, προωθήστε τις αλλαγές σας με δύναμη στην απομακρυσμένη διχάλα σας, όλα θα διαδοθούν αυτόματα στο pull request. {{priority: -1}} diff --git a/contributing/el/coding-standard.texy b/contributing/el/coding-standard.texy index 7bd7523916..c6570caba1 100644 --- a/contributing/el/coding-standard.texy +++ b/contributing/el/coding-standard.texy @@ -38,7 +38,7 @@ - Οι συναρτήσεις βέλους γράφονται χωρίς κενό πριν από την παρένθεση, δηλ. `fn($a) => $b` - δεν απαιτείται κενή γραμμή μεταξύ διαφορετικών τύπων δηλώσεων εισαγωγής `use` -- ο τύπος επιστροφής της συνάρτησης/μεθόδου και η εισαγωγική παρένθεση πρέπει να τοποθετούνται σε ξεχωριστές γραμμές για καλύτερη αναγνωσιμότητα: +- ο τύπος επιστροφής μιας συνάρτησης/μεθόδου και η εισαγωγική αγκύλη βρίσκονται πάντα σε ξεχωριστές γραμμές: ```php public function find( @@ -50,6 +50,10 @@ } ``` +Η εισαγωγική αγκύλη σε ξεχωριστή γραμμή είναι σημαντική για τον οπτικό διαχωρισμό της υπογραφής της συνάρτησης/μεθόδου από το σώμα. Εάν η υπογραφή βρίσκεται σε μία γραμμή, ο διαχωρισμός είναι σαφής (εικόνα στα αριστερά), εάν βρίσκεται σε περισσότερες γραμμές, στο PSR οι υπογραφές και τα σώματα αναμειγνύονται μεταξύ τους (στη μέση), ενώ στο πρότυπο Nette παραμένουν χωριστά (στα δεξιά): + +[* new-line-after.webp *] + Μπλοκ τεκμηρίωσης (phpDoc) .[#toc-documentation-blocks-phpdoc] ============================================================== diff --git a/contributing/el/documentation.texy b/contributing/el/documentation.texy index 315b2a0925..7040e029fe 100644 --- a/contributing/el/documentation.texy +++ b/contributing/el/documentation.texy @@ -1,53 +1,69 @@ -Συγγραφή της τεκμηρίωσης -************************ +Συμβολή στην τεκμηρίωση +*********************** .[perex] -Η συνεισφορά στην τεκμηρίωση είναι ένας από τους πολλούς τρόπους με τους οποίους μπορείτε να βοηθήσετε τη Nette. Είναι επίσης μία από τις πιο ικανοποιητικές δραστηριότητες, καθώς βοηθάτε άλλους να κατανοήσουν το πλαίσιο. +Η συνεισφορά στην τεκμηρίωση είναι μια από τις πιο πολύτιμες δραστηριότητες, καθώς βοηθάει τους άλλους να κατανοήσουν το πλαίσιο. Πώς να γράψετε; .[#toc-how-to-write] ------------------------------------ -Η τεκμηρίωση προορίζεται κυρίως για άτομα που μόλις εξοικειώνονται με το θέμα. Ως εκ τούτου, θα πρέπει να ανταποκρίνεται σε διάφορα σημαντικά σημεία: +Η τεκμηρίωση απευθύνεται κυρίως σε άτομα που είναι καινούργια στο θέμα. Ως εκ τούτου, θα πρέπει να ανταποκρίνεται σε διάφορα σημαντικά σημεία: -- **Όταν γράφετε, ξεκινήστε με τα απλά και γενικά και προχωρήστε σε πιο προχωρημένα θέματα στο τέλος.** -- Παρέχετε μόνο τις πληροφορίες που ο χρήστης χρειάζεται πραγματικά να γνωρίζει για το θέμα. -- Επαληθεύστε ότι οι πληροφορίες σας είναι πράγματι αληθινές. Δοκιμάστε πρώτα το παράδειγμα πριν δώσετε το παράδειγμα. -- Να είστε συνοπτικοί - κόψτε αυτά που γράφετε στη μέση. Και στη συνέχεια μη διστάσετε να το ξανακάνετε. -- Προσπαθήστε να εξηγήσετε το θέμα όσο το δυνατόν καλύτερα. Για παράδειγμα, δοκιμάστε να εξηγήσετε το θέμα πρώτα σε έναν συνάδελφο. +- Ξεκινήστε με απλά και γενικά θέματα. Να προχωράτε σε πιο προχωρημένα θέματα στο τέλος +- Προσπαθήστε να εξηγείτε το θέμα όσο το δυνατόν πιο ξεκάθαρα. Για παράδειγμα, προσπαθήστε να εξηγήσετε το θέμα πρώτα σε έναν συνάδελφο +- Παρέχετε μόνο τις πληροφορίες που πραγματικά χρειάζεται να γνωρίζει ο χρήστης για ένα συγκεκριμένο θέμα +- Βεβαιωθείτε ότι οι πληροφορίες σας είναι ακριβείς. Δοκιμάστε κάθε κώδικα +- Να είστε συνοπτικοί - κόψτε αυτά που γράφετε στη μέση. Και μετά μη διστάσετε να το ξανακάνετε +- Χρησιμοποιήστε με φειδώ την επισήμανση, από έντονες γραμματοσειρές μέχρι πλαίσια όπως `.[note]` +- Ακολουθήστε το [πρότυπο κωδικοποίησης |Coding Standard] στον κώδικα -Έχετε αυτά τα σημεία κατά νου καθ' όλη τη διάρκεια της συγγραφής. Η τεκμηρίωση είναι γραμμένη σε [Texy! |https://texy.info], οπότε μάθετε τη [σύνταξή |syntax] της. Μπορείτε να χρησιμοποιήσετε τον επεξεργαστή τεκμηρίωσης στη διεύθυνση https://editor.nette.org/ για να κάνετε προεπισκόπηση του άρθρου καθώς το γράφετε. +Επίσης, μάθετε τη [σύνταξη |syntax]. Για μια προεπισκόπηση του άρθρου κατά τη διάρκεια της συγγραφής, μπορείτε να χρησιμοποιήσετε τον [επεξεργαστή προεπισκόπησης |https://editor.nette.org/]. -Μεταξύ των γενικών κανόνων συγγραφής που αναφέρθηκαν προηγουμένως, παρακαλούμε να τηρείτε τους ακόλουθους: -- Ο κώδικάς σας θα πρέπει να είναι σύμφωνος με το [Πρότυπο Κωδικοποίησης |Coding Standard]. -- Γράψτε τα ονόματα των μεταβλητών, των κλάσεων και των μεθόδων στα αγγλικά. -- Οι χώροι ονομάτων χρειάζεται να αναφέρονται μόνο κατά την πρώτη αναφορά. -- Προσπαθήστε να μορφοποιήσετε τον κώδικα έτσι ώστε να μην εμφανίζονται μπάρες κύλισης. -- Αποφύγετε κάθε είδους υπογραμμίσεις, από έντονη γραφή έως `.[note]` πλαίσια. -- Από την τεκμηρίωση, ανατρέξτε μόνο στην τεκμηρίωση ή στο `www`. +Μεταλλάξεις της γλώσσας .[#toc-language-mutations] +-------------------------------------------------- +Τα αγγλικά είναι η κύρια γλώσσα, οπότε οι αλλαγές σας θα πρέπει να είναι στα αγγλικά. Αν τα αγγλικά δεν είναι το δυνατό σας σημείο, χρησιμοποιήστε [το DeepL Translator |https://www.deepl.com/translator] και άλλοι θα ελέγξουν το κείμενό σας. -Δομή τεκμηρίωσης .[#toc-documentation-structure] ------------------------------------------------- +Η μετάφραση σε άλλες γλώσσες θα γίνει αυτόματα μετά την έγκριση και την τελειοποίηση της επεξεργασίας σας. + + +Trivial Edits .[#toc-trivial-edits] +----------------------------------- + +Για να συνεισφέρετε στην τεκμηρίωση, πρέπει να έχετε έναν λογαριασμό στο [GitHub |https://github.com]. -Η πλήρης τεκμηρίωση φιλοξενείται στο GitHub στο αποθετήριο [nette/docs |https://github.com/nette/docs]. Αυτό το αποθετήριο χωρίζεται σε κλάδους με βάση την έκδοση της τεκμηρίωσης, για παράδειγμα ο κλάδος `doc-3.1` περιέχει την τεκμηρίωση για την έκδοση 3.1. Και έπειτα υπάρχει ο κλάδος `nette.org`, ο οποίος περιέχει το περιεχόμενο των άλλων υποτομέων του nette.org. +Ο ευκολότερος τρόπος για να κάνετε μια μικρή αλλαγή στην τεκμηρίωση είναι να χρησιμοποιήσετε τους συνδέσμους στο τέλος κάθε σελίδας: -Στη συνέχεια, κάθε κλάδος χωρίζεται σε διάφορους φακέλους: +- *Εμφάνιση στο GitHub* ανοίγει την πηγαία έκδοση της σελίδας στο GitHub. Στη συνέχεια, απλά πατήστε το κουμπί `E` και μπορείτε να ξεκινήσετε την επεξεργασία (πρέπει να είστε συνδεδεμένοι στο GitHub) +- *Άνοιγμα προεπισκόπησης* ανοίγει έναν επεξεργαστή όπου μπορείτε να δείτε αμέσως την τελική οπτική μορφή -* `cs` και `en`: περιέχει αρχεία τεκμηρίωσης για κάθε γλωσσική έκδοση -* `files`: εικόνες που μπορούν να ενσωματωθούν στις σελίδες τεκμηρίωσης +Επειδή ο [επεξεργαστής προεπισκόπησης |https://editor.nette.org/] δεν έχει τη δυνατότητα να αποθηκεύσετε τις αλλαγές απευθείας στο GitHub, πρέπει να αντιγράψετε το πηγαίο κείμενο στο πρόχειρο (χρησιμοποιώντας το κουμπί *Κοπή στο πρόχειρο*) και στη συνέχεια να το επικολλήσετε στον επεξεργαστή στο GitHub. +Κάτω από το πεδίο επεξεργασίας υπάρχει μια φόρμα για την υποβολή. Εδώ, μην ξεχάσετε να συνοψίσετε εν συντομία και να εξηγήσετε τον λόγο της επεξεργασίας σας. Μετά την υποβολή, δημιουργείται ένα λεγόμενο pull request (PR), το οποίο μπορείτε να επεξεργαστείτε περαιτέρω. -Η διαδρομή προς ένα αρχείο χωρίς επέκταση αντιστοιχεί στη διεύθυνση URL μιας σελίδας της τεκμηρίωσης. Έτσι, το αρχείο `en/quickstart/single-post.texy` θα έχει τη διεύθυνση URL `doc.nette.org/en/quickstart/single-post`. +Μεγαλύτερες επεξεργασίες .[#toc-larger-edits] +--------------------------------------------- -Συμβολή .[#toc-contributing] ----------------------------- +Είναι προτιμότερο να είστε εξοικειωμένοι με τα βασικά στοιχεία της εργασίας με το σύστημα ελέγχου εκδόσεων Git παρά να βασίζεστε αποκλειστικά στη διεπαφή του GitHub. Αν δεν είστε εξοικειωμένοι με το Git, μπορείτε να ανατρέξετε στον οδηγό [git - the simple |https://rogerdudler.github.io/git-guide/] και να εξετάσετε το ενδεχόμενο να χρησιμοποιήσετε έναν από τους πολλούς διαθέσιμους [γραφικούς πελάτες |https://git-scm.com/downloads/guis]. -Για να συνεισφέρετε στην τεκμηρίωση, πρέπει να έχετε λογαριασμό στο [GitHub |https://github.com] και να γνωρίζετε τα βασικά του Git. Αν δεν είστε εξοικειωμένοι με το Git, μπορείτε να δείτε τον γρήγορο οδηγό: [git - ο απλός οδηγός |https://rogerdudler.github.io/git-guide/], ή να χρησιμοποιήσετε ένα από τα πολλά γραφικά εργαλεία: [GIT - πελάτες GUI |https://git-scm.com/downloads/guis]. +Επεξεργαστείτε την τεκμηρίωση με τον ακόλουθο τρόπο: -Μπορείτε να κάνετε απλές αλλαγές απευθείας στο περιβάλλον εργασίας του GitHub. Ωστόσο, είναι πιο βολικό να δημιουργήσετε μια διακλάδωση του αποθετηρίου [nette/docs |https://github.com/nette/docs] και να το κλωνοποιήσετε στον υπολογιστή σας. Στη συνέχεια, κάντε αλλαγές στον κατάλληλο κλάδο, δεσμεύστε την αλλαγή, προωθήστε την στο GitHub σας και στείλτε ένα pull request στο αρχικό αποθετήριο `nette/docs`. +1) στο GitHub, δημιουργήστε μια [διακλάδωση |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] του αποθετηρίου [nette/docs |https://github.com/nette/docs] +2) [κλωνοποιήστε |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] αυτό το αποθετήριο στον υπολογιστή σας +3) στη συνέχεια, κάντε αλλαγές στον [κατάλληλο κλάδο |#Documentation Structure] +4) ελέγξτε για επιπλέον κενά στο κείμενο χρησιμοποιώντας το εργαλείο [Code-Checker |code-checker:] +5) αποθηκεύστε (commit) τις αλλαγές +6) αν είστε ικανοποιημένοι με τις αλλαγές, προωθήστε τις στο GitHub στο fork σας +7) από εκεί, υποβάλετε τις στο αποθετήριο `nette/docs` δημιουργώντας ένα [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +Είναι σύνηθες να λαμβάνετε σχόλια με προτάσεις. Παρακολουθήστε τις προτεινόμενες αλλαγές και ενσωματώστε τις. Προσθέστε τις προτεινόμενες αλλαγές ως νέες δεσμεύσεις και στείλτε τις εκ νέου στο GitHub. Ποτέ μην δημιουργείτε ένα νέο pull request μόνο και μόνο για να τροποποιήσετε ένα υπάρχον. + + +Δομή τεκμηρίωσης .[#toc-documentation-structure] +------------------------------------------------ -Πριν από κάθε pull request, καλό είναι να τρέχετε το [Code-Checker |code-checker:] για να ελέγχετε τα επιπλέον κενά στο κείμενο. +Ολόκληρη η τεκμηρίωση βρίσκεται στο GitHub στο αποθετήριο [nette/docs |https://github.com/nette/docs]. Η τρέχουσα έκδοση βρίσκεται στον κλάδο master, ενώ παλαιότερες εκδόσεις βρίσκονται σε κλάδους όπως `doc-3.x`, `doc-2.x`. -{{priority: -1}} +Το περιεχόμενο κάθε κλάδου χωρίζεται σε κύριους φακέλους που αντιπροσωπεύουν μεμονωμένες περιοχές της τεκμηρίωσης. Για παράδειγμα, το `application/` αντιστοιχεί στο https://doc.nette.org/en/application, το `latte/` αντιστοιχεί στο https://latte.nette.org, κ.λπ. Κάθε ένας από αυτούς τους φακέλους περιέχει υποφακέλους που αντιπροσωπεύουν γλωσσικές μεταλλάξεις (`cs`, `en`, ...) και προαιρετικά έναν υποφάκελο `files` με εικόνες που μπορούν να εισαχθούν στις σελίδες της τεκμηρίωσης. diff --git a/contributing/en/code.texy b/contributing/en/code.texy index e769275107..6714b027ad 100644 --- a/contributing/en/code.texy +++ b/contributing/en/code.texy @@ -1,110 +1,118 @@ -Proposing a Change in Code -************************** +Contributing to Code +******************** -Nette Framework uses Git and [GitHub |https://github.com/nette/nette] for maintaining the code base. The best way to contribute is to commit your changes to your own fork and then make a pull request on GitHub. This document summarize the major steps for successful contributing. +.[perex] +Are you planning to contribute to the Nette Framework and need to familiarize yourself with the rules and procedures? This beginner's guide will walk you through the steps to effectively contribute to the code, work with repositories, and implement changes. -Preparing Environment -===================== +Procedure +========= -Start with [forking |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette on GitHub |https://github.com/nette]. Carefully [set up |https://help.github.com/en/github/getting-started-with-github/set-up-git] your local Git environment, configure your username and email, these credentials will identify your changes in Nette Framework history. +To contribute to the code, it is essential to have an account on [GitHub|https://github.com] and be familiar with the basics of working with the Git version control system. If you are not familiar with Git, you can check out the [git - the simple guide|https://rogerdudler.github.io/git-guide/] and consider using one of the many [graphical clients|https://git-scm.com/downloads/guis]. -Working on Your Patch -===================== +Preparing the Environment and Repository +---------------------------------------- -Before you start working on your patch, create a new branch for your changes. -```shell -git checkout -b new_branch_name -``` +1) On GitHub, create a [fork|https://help.github.com/en/github/getting-started-with-github/fork-a-repo] of the [package repository|www:packages] that you intend to modify +2) [Clone|https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] this repository to your computer +3) Install the dependencies, including [Nette Tester|tester:], using the `composer install` command +4) Verify that the tests are working by running `composer tester` +5) Create a [new branch|#New Branch] based on the latest released version + + +Implementing Your Own Changes +----------------------------- + +Now you can make your own code adjustments: + +1) Implement the desired changes and do not forget about the tests +2) Make sure the tests run successfully using `composer tester` +3) Check if the code meets the [#coding standards] +4) Save (commit) the changes with a description in [this format|#Commit Description] + +You can create multiple commits, one for each logical step. Each commit should be meaningful on its own. + + +Submitting Changes +------------------ + +Once you are satisfied with the changes, you can submit them: + +1) Push the changes to GitHub to your fork +2) From there, submit them to the Nette repository by creating a [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) Provide [sufficient information|#pull request description] in the description + + +Incorporating Feedback +---------------------- + +Your commits are now visible to others. It is common to receive comments with suggestions: -You can work on your code change. +1) Keep track of the proposed changes +2) Incorporate them as new commits or [merge them with previous ones|https://help.github.com/en/github/using-git/about-git-rebase] +3) Resubmit the commits to GitHub, and they will automatically appear in the pull request -If possible, make changes from the last released version. +Never create a new pull request to modify an existing one. -Testing Your Changes -==================== +Documentation +------------- -You need to install Nette Tester. The easiest way is to call `composer install` in repository root. Now you should be able to run tests with `./vendor/bin/tester` in the terminal. +If you have changed functionality or added a new one, don't forget to [add it to the documentation|documentation] as well. -Some tests may fail due to missing php.ini. Therefore you should call the runner with parameter -c and specify the path to php.ini, for example `./vendor/bin/tester -c ./tests/php.ini`. -After you are able to run the tests, you can implement your own or change the failing to match the new behavior. Read more about testing with Nette Tester in [documentation page |tester:]. +New Branch +========== + +If possible, make changes against the latest released version, i.e., the last tag in the branch. For the tag v3.2.1, create a branch using this command: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Coding Standards ================ -Your code must follow [coding standard] used in Nette Framework. It is easy because there is automated checker & fixer. It can be installed via Composer to your chosen global directory: +Your code must meet the [coding standard] used in the Nette Framework. There is an automatic tool available for checking and fixing the code. You can install it **globally** through Composer to a folder of your choice: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Now you should be able to run tool in the terminal. For example, this command checks and fixes code in folders `src` and `tests` in current directory: +Now you should be able to run the tool in the terminal. The first command checks and the second one fixes the code in the `src` and `tests` folders in the current directory: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Committing the Changes -====================== +Commit Description +================== -After you have changed the code, you have to commit your changes. Create more commits, one for each logical step. Each commit should have been usable as is - without other commits. So, the appropriate tests should be also included in the same commit. +In Nette, commit subjects have the following format: `Presenter: fixed AJAX detection [Closes #69]` -Please, double-check your code fits the rules: -- Code does not generate any errors -- Your code does not break any tests. -- Your code change is tested. -- You are not committing useless white-space changes. +- area followed by a colon +- purpose of the commit in the past tense; if possible, start with words like: "added .(new feature)", "fixed .(correction)", "refactored .(code change without behavior change)", changed, removed +- if the commit breaks backward compatibility, add "BC break" +- any connection to the issue tracker, such as `(#123)` or `[Closes #69]` +- after the subject, there can be one blank line followed by a more detailed description, including, for example, links to the forum -Commit message should follow format `Latte: fixed multi template rendering [Closes # 69]` ie: -- an area followed by a colon -- the purpose of the commit in the past, if possible, start with "added.", "fixed.", "refactored.", changed, removed -- eventual link to issue tracker -- if commit cancels backward compatibility, add "BC break" -- there may be one free line after the subject and a more detailed description including links to the forum. +Pull Request Description +======================== -Pull-Requesting the Commits -=========================== - -If you are satisfied with your code changes & commits, you have to push you commits to GitHub. - -```shell -git push origin new_branch_name -``` +When creating a pull request, the GitHub interface will allow you to enter a title and description. Provide a concise title and include as much information as possible in the description about the reasons for your change. -Changes are present publicly, however, you have to propose your changes for integration into master branch of Nette. To do that, [make a pull request |https://help.github.com/articles/creating-a-pull-request]. -Each pull request has a title and a description. Please provide some describing title. It's often similar to the branch name, for example "Securing signals against CSRF attack." +Also, specify in the header whether it is a new feature or a bug fix and whether it may cause backward compatibility issues (BC break). If there is a related issue, link to it so that it will be closed upon approval of the pull request. -Pull request description should have contain some more specific information about your code changes: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Please change the information table to fit your pull request. Comments to each list item: -- Says if the pull request adds **feature** or it is a **bugfix**. -- References eventually **related issue**, which will be closed after merging the pull request. -- Says if the pull request needs the **documentation changes**, if yes, provide references to the appropriate pull requests. You don't have to provide the documentation change immediately, however, the pull request won't be merged if the documentation change is needed. The documentation change must be prepared for English documentation, other language mutations are optional. -- Says if the pull request creates **a BC break**. Please, consider everything which changes public interface as a BC break. - -The final table could look like: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Reworking Your Changes -====================== -It is really common to receive comments to your code change. Please, try to follow proposed changes and rework your commits to do that. You can commit proposed changes as new commits and then squash them to the previous ones. See [Interactive rebase |https://help.github.com/en/github/using-git/about-git-rebase] chapter on GitHub. After rebasing your changes, force-push your changes to your remote fork, everything will be automatically propagate to the pull request. {{priority: -1}} diff --git a/contributing/en/coding-standard.texy b/contributing/en/coding-standard.texy index 81a8a48176..d625046ec3 100644 --- a/contributing/en/coding-standard.texy +++ b/contributing/en/coding-standard.texy @@ -38,7 +38,7 @@ Nette Coding Standard corresponds to PSR-12 (or PER Coding Style), in some point - arrow functions are written without a space before the parenthesis, i.e. `fn($a) => $b` - no empty line is required between different types of `use` import statements -- the return type of the function/method and the opening parenthesis should be placed on separate lines for better readability: +- the return type of a function/method and the opening curly bracket are always on separate lines: ```php public function find( @@ -50,6 +50,10 @@ Nette Coding Standard corresponds to PSR-12 (or PER Coding Style), in some point } ``` +The opening curly bracket on a separate line is important for visually separating the function/method signature from the body. If the signature is on one line, the separation is clear (image on the left), if it's on multiple lines, in PSR the signatures and bodies blend together (in the middle), while in the Nette standard they remain separated (on the right): + +[* new-line-after.webp *] + Documentation Blocks (phpDoc) ============================= diff --git a/contributing/en/documentation.texy b/contributing/en/documentation.texy index 5b0b12ce2f..0aec6761dc 100644 --- a/contributing/en/documentation.texy +++ b/contributing/en/documentation.texy @@ -1,53 +1,69 @@ -Writing the Documentation -************************* +Contributing to Documentation +***************************** .[perex] -Contributing to the documentation is one of the many ways you can help Nette. It is also one of the most rewarding activities, as you help others understand the framework. +Contributing to documentation is one of the most valuable activities, as it helps others understand the framework. How to Write? ------------- -The documentation is primarily intended for people who are just getting familiar with the topic. Therefore, it should meet several important points: +Documentation is primarily intended for people who are new to the topic. Therefore, it should meet several important points: -- **When writing, start with the simple and general, and move to more advanced topics at the end.** -- Provide only the information that the user really needs to know about the topic. -- Verify that your information is actually true. Test the example first before giving the example. -- Be concise - cut what you write in half. And then feel free to do it again. -- Try to explain the matter as well as possible. For example, try explaining the topic to a colleague first. +- Start with simple and general topics. Move on to more advanced topics at the end +- Try to explain the topic as clearly as possible. For example, try explaining the topic to a colleague first +- Only provide information that the user actually needs to know for a given topic +- Make sure your information is accurate. Test every code +- Be concise - cut what you write in half. And then feel free to do it again +- Use highlighting sparingly, from bold fonts to frames like `.[note]` +- Follow the [Coding Standard] in the code -Keep these points in mind throughout the writing process. The documentation is written in [Texy! |https://texy.info], so learn its [syntax]. You can use the documentation editor at [https://editor.nette.org/] to preview the article as you write it. +Also, learn the [syntax]. For a preview of the article during writing, you can use the [preview editor |https://editor.nette.org/]. -Among the general writing rules listed earlier, please stick to the following: -- Your code should be in compliance with the [Coding Standard]. -- Write the names of variables, classes and methods in English. -- Namespaces need only be mentioned at first mention. -- Try to format the code so that scroll bars are not displayed. -- Spare all kinds of highlighters, from boldface to `.[note]` boxes. -- From the documentation, refer only to the documentation or `www`. +Language Mutations +------------------ +English is the primary language, so your changes should be in English. If English is not your strong suit, use [DeepL Translator |https://www.deepl.com/translator] and others will check your text. + +Translation into other languages will be done automatically after approval and fine-tuning of your edit. -Documentation Structure ------------------------ -The full documentation is hosted on GitHub in the [nette/docs |https://github.com/nette/docs] repository. This repository is divided into branches based on the version of the documentation, for example the `doc-3.1` branch contains the documentation for version 3.1. And then there is the `nette.org` branch, which contains the content of the other subdomains of nette.org. +Trivial Edits +------------- + +To contribute to the documentation, you need to have an account on [GitHub |https://github.com]. -Each branch is then divided into several folders: +The easiest way to make a small change in the documentation is to use the links at the end of each page: -* `cs` and `en`: contains documentation files for each language version -* `files`: images that can be embedded in the documentation pages +- *Show on GitHub* opens the source version of the page on GitHub. Then just press the `E` button and you can start editing (you must be logged in to GitHub) +- *Open preview* opens an editor where you can immediately see the final visual form -The path to a file without an extension corresponds to the URL of a page in the documentation. Thus, the file `en/quickstart/single-post.texy` will have the URL `doc.nette.org/en/quickstart/single-post`. +Because the [preview editor |https://editor.nette.org/] does not have the ability to save changes directly to GitHub, you need to copy the source text to the clipboard (using the *Copy to clipboard button*) and then paste it into the editor on GitHub. +Below the editing field is a form for submission. Here, don't forget to briefly summarize and explain the reason for your edit. After submitting, a so-called pull request (PR) is created, which can be further edited. -Contributing +Larger Edits ------------ -To contribute to the documentation, you must have an account at [GitHub|https://github.com] and know the basics of Git. If you're not familiar with Git, you can check out the quick guide: [git - the simple guide |https://rogerdudler.github.io/git-guide/], or use one of the many graphical tools: [GIT - GUI clients |https://git-scm.com/downloads/guis]. +It is more appropriate to be familiar with the basics of working with the Git version control system rather than relying solely on the GitHub interface. If you're not familiar with Git, you can refer to the [git - the simple guide |https://rogerdudler.github.io/git-guide/] and consider using one of the many [graphical clients |https://git-scm.com/downloads/guis] available. + +Edit the documentation in the following way: -You can make simple changes directly in the GitHub interface. However, it is more convenient to create a fork of the repository [nette/docs |https://github.com/nette/docs] and clone it to your computer. Then make changes in the appropriate branch, commit the change, push to your GitHub, and send a pull request to the original `nette/docs` repository. +1) on GitHub, create a [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] of the [nette/docs |https://github.com/nette/docs] repository +2) [clone |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] this repository to your computer +3) then, make changes in the [appropriate branch|#Documentation Structure] +4) check for extra spaces in the text using the [Code-Checker |code-checker:] tool +5) save (commit) the changes +6) if you are satisfied with the changes, push them to GitHub to your fork +7) from there, submit them to the `nette/docs` repository by creating a [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +It is common to receive comments with suggestions. Keep track of the proposed changes and incorporate them. Add the suggested changes as new commits and resend them to GitHub. Never create a new pull request just to modify an existing one. + + +Documentation Structure +----------------------- -Before each pull request, it's a good idea to run [Code-Checker |code-checker:] to check extra whitespace in the text. +The entire documentation is located on GitHub in the [nette/docs |https://github.com/nette/docs] repository. The current version is in the master branch, while older versions are located in branches like `doc-3.x`, `doc-2.x`. -{{priority: -1}} +The content of each branch is divided into main folders representing individual areas of documentation. For example, `application/` corresponds to https://doc.nette.org/cs/application, `latte/` corresponds to https://latte.nette.org, etc. Each of these folders contains subfolders representing language mutations (`cs`, `en`, ...) and optionally a `files` subfolder with images that can be inserted into the pages in the documentation. diff --git a/contributing/es/code.texy b/contributing/es/code.texy index bf3088e863..4d35b1187d 100644 --- a/contributing/es/code.texy +++ b/contributing/es/code.texy @@ -1,110 +1,118 @@ -Proponer un cambio de código -**************************** +Contribuir al código +******************** -Nette Framework utiliza Git y [GitHub |https://github.com/nette/nette] para mantener el código base. La mejor manera de contribuir es confirmar los cambios en tu propio fork y luego hacer un pull request en GitHub. Este documento resume los principales pasos para contribuir con éxito. +.[perex] +¿Está pensando en contribuir al marco de Nette y necesita familiarizarse con las normas y procedimientos? Esta guía para principiantes le guiará a través de los pasos necesarios para contribuir eficazmente al código, trabajar con repositorios e implementar cambios. -Preparación del entorno .[#toc-preparing-environment] -===================================================== +Procedimiento .[#toc-procedure] +=============================== -Comience con la [bifurcación |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] de [Nette en GitHub |https://github.com/nette]. [Configure |https://help.github.com/en/github/getting-started-with-github/set-up-git] cuidadosamente su entorno Git local, configure su nombre de usuario y correo electrónico, estas credenciales identificarán sus cambios en el historial de Nette Framework. +Para contribuir al código, es esencial tener una cuenta en [GitHub |https://github.com] y estar familiarizado con los conceptos básicos de trabajo con el sistema de control de versiones Git. Si no estás familiarizado con Git, puedes consultar la [guía git - the simple guide |https://rogerdudler.github.io/git-guide/] y considerar el uso de uno de los muchos [clientes gráficos |https://git-scm.com/downloads/guis]. -Trabajar en su parche .[#toc-working-on-your-patch] -=================================================== +Preparar el entorno y el repositorio .[#toc-preparing-the-environment-and-repository] +------------------------------------------------------------------------------------- -Antes de empezar a trabajar en su parche, cree una nueva rama para sus cambios. -```shell -git checkout -b new_branch_name -``` +1) En GitHub, crea un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del repositorio del [paquete |www:packages] que pretendes modificar +2) [Clone |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositorio en su ordenador +3) Instale las dependencias, incluyendo [Nette Tester |tester:], utilizando el comando `composer install` +4) Compruebe que las pruebas funcionan ejecutando `composer tester` +5) Cree una [nueva rama |#New Branch] basada en la última versión publicada -Usted puede trabajar en su cambio de código. -Si es posible, realice los cambios desde la última versión publicada. +Implementar tus propios cambios .[#toc-implementing-your-own-changes] +--------------------------------------------------------------------- +Ahora puede realizar sus propios ajustes en el código: -Pruebe sus cambios .[#toc-testing-your-changes] -=============================================== +1) Implementa los cambios deseados y no te olvides de las pruebas +2) Asegúrate de que las pruebas se ejecutan correctamente `composer tester` +3) Comprueba si el código cumple las [normas de codificación |#coding standards] +4) Guarda (confirma) los cambios con una descripción en [este formato |#Commit Description] + +Puedes crear varios commits, uno para cada paso lógico. Cada commit debe tener sentido por sí mismo. + + +Envío de cambios .[#toc-submitting-changes] +------------------------------------------- + +Una vez que esté satisfecho con los cambios, puede enviarlos: + +1) Empuje los cambios a GitHub a su bifurcación. +2) Desde allí, envíalos al repositorio de Nette creando un [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) Proporcione [información suficiente |#pull request description] en la descripción + + +Incorporación de comentarios .[#toc-incorporating-feedback] +----------------------------------------------------------- + +Tus commits son ahora visibles para los demás. Es habitual recibir comentarios con sugerencias: + +1) Hacer un seguimiento de los cambios propuestos +2) Incorpórelos como nuevos commits o [fusiónelos con los |https://help.github.com/en/github/using-git/about-git-rebase]anteriores +3) Vuelve a enviar los commits a GitHub, y aparecerán automáticamente en el pull request + +Nunca crees un nuevo pull request para modificar uno ya existente. + + +Documentación .[#toc-documentation] +----------------------------------- -Necesitas instalar Nette Tester. La forma más fácil es llamar a `composer install` en la raíz del repositorio. Ahora debería ser capaz de ejecutar pruebas con `./vendor/bin/tester` en el terminal. +Si has cambiado alguna funcionalidad o añadido una nueva, no olvides [añadirla también a la |documentation] documentación. -Algunas pruebas pueden fallar debido a la falta de php.ini. Por lo tanto debe llamar al ejecutor con el parámetro -c y especificar la ruta a php.ini, por ejemplo `./vendor/bin/tester -c ./tests/php.ini`. -Después de que pueda ejecutar las pruebas, puede implementar las suyas propias o cambiar el fallo para que coincida con el nuevo comportamiento. Lea más sobre las pruebas con Nette Tester en la [página de documentación |tester:]. +Nueva rama .[#toc-new-branch] +============================= + +Si es posible, realice los cambios contra la última versión publicada, es decir, la última etiqueta de la rama. Para la etiqueta v3.2.1, cree una rama utilizando este comando: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Normas de codificación .[#toc-coding-standards] =============================================== -Tu código debe seguir [los estándares |coding standard] de codificación utilizados en Nette Framework. Es fácil porque hay un comprobador y corrector automático. Se puede instalar a través de Composer a su directorio global elegido: +Tu código debe cumplir las [normas |coding standard] de codificación utilizadas en Nette Framework. Existe una herramienta automática para comprobar y corregir el código. Puedes instalarla **globalmente** a través de Composer en una carpeta de tu elección: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Ahora debería ser capaz de ejecutar la herramienta en el terminal. Por ejemplo, este comando comprueba y corrige el código en las carpetas `src` y `tests` en el directorio actual: +Ahora deberías poder ejecutar la herramienta en el terminal. El primer comando comprueba y el segundo corrige el código en las carpetas `src` y `tests` en el directorio actual: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Confirmación de los cambios .[#toc-committing-the-changes] -========================================================== - -Después de haber cambiado el código, tienes que confirmar los cambios. Crea más confirmaciones, una para cada paso lógico. Cada commit debe ser utilizable tal cual - sin otros commits. Por lo tanto, las pruebas apropiadas también deben incluirse en el mismo commit. +Descripción del compromiso .[#toc-commit-description] +===================================================== -Por favor, compruebe que su código se ajusta a las reglas: -- El código no genera errores -- Su código no rompe ninguna prueba. -- El cambio de código se ha probado. -- No está realizando cambios inútiles en el espacio en blanco. +En Nette, los asuntos de commit tienen el siguiente formato: `Presenter: fixed AJAX detection [Closes #69]` -El mensaje de confirmación debe seguir el formato `Latte: fixed multi template rendering [Closes # 69]` es decir -- un área seguida de dos puntos -- el propósito del commit en el pasado, si es posible, empezar con "añadido", "corregido", "refactorizado", cambiado, eliminado -- eventual enlace al issue tracker -- si la confirmación anula la compatibilidad con versiones anteriores, añada "BC break". -- puede haber una línea libre después del asunto y una descripción más detallada que incluya enlaces al foro. +- área seguida de dos puntos +- propósito del commit en pasado; si es posible, comience con palabras como: added, fixed, refactored, changed, removed +- si la confirmación rompe la compatibilidad con versiones anteriores, añada "BC break". +- cualquier conexión con el gestor de incidencias, como `(#123)` o `[Closes #69]` +- después del asunto, puede haber una línea en blanco seguida de una descripción más detallada, incluyendo, por ejemplo, enlaces al foro -Solicitud de los commits .[#toc-pull-requesting-the-commits] +Descripción de la solicitud .[#toc-pull-request-description] ============================================================ -Si estás satisfecho con los cambios y commits de tu código, tienes que empujar tus commits a GitHub. +Al crear una pull request, la interfaz de GitHub te permitirá introducir un título y una descripción. Proporcione un título conciso e incluya tanta información como sea posible en la descripción sobre las razones de su cambio. -```shell -git push origin new_branch_name -``` +Especifica también en la cabecera si se trata de una nueva función o de una corrección de errores y si puede causar problemas de retrocompatibilidad (BC break). Si existe una incidencia relacionada, vincúlela para que se cierre cuando se apruebe la solicitud de extracción. -Los cambios se presentan públicamente, sin embargo, usted tiene que proponer sus cambios para la integración en la rama maestra de Nette. Para ello, [haga un pull request |https://help.github.com/articles/creating-a-pull-request]. -Cada pull request tiene un título y una descripción. Por favor, proporcione un título descriptivo. A menudo es similar al nombre de la rama, por ejemplo "Securing signals against CSRF attack". - -La descripción del pull request debe contener información más específica sobre los cambios en el código: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Por favor, cambie la tabla de información para adaptarse a su pull request. Comentarios a cada elemento de la lista: -- Indica si el pull request añade **función** o es una **corrección de errores**. -- Eventualmente hace referencia a un **problema relacionado**, que se cerrará tras fusionar la pull request. -- Dice si el pull request necesita **cambios en la documentación**, en caso afirmativo, proporciona referencias a los pull requests apropiados. No tienes que proporcionar el cambio de documentación inmediatamente, sin embargo, el pull request no se fusionará si el cambio de documentación es necesario. El cambio de documentación debe prepararse para la documentación en inglés, las mutaciones en otros idiomas son opcionales. -- Dice si el pull request crea **una ruptura de BC**. Por favor, considere todo lo que cambie la interfaz pública como una ruptura de BC. - -La tabla final podría tener este aspecto: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Reformulando sus cambios .[#toc-reworking-your-changes] -======================================================= -Es muy común recibir comentarios a tus cambios de código. Por favor, intenta seguir los cambios propuestos y reelabora tus commits para ello. Puedes confirmar los cambios propuestos como nuevos commits y luego aplastarlos a los anteriores. Consulta el capítulo [Rebase interactivo |https://help.github.com/en/github/using-git/about-git-rebase] en GitHub. Después de rebase de sus cambios, la fuerza de empuje de sus cambios a su bifurcación remota, todo se propagará automáticamente a la solicitud de extracción. {{priority: -1}} diff --git a/contributing/es/coding-standard.texy b/contributing/es/coding-standard.texy index 37b8b1dba7..557bb92916 100644 --- a/contributing/es/coding-standard.texy +++ b/contributing/es/coding-standard.texy @@ -38,7 +38,7 @@ La Norma de Codificación Nette corresponde a la PSR-12 (o Estilo de Codificaci - las funciones de flecha se escriben sin espacio antes del paréntesis, es decir `fn($a) => $b` - no se requiere una línea vacía entre los distintos tipos de sentencias import de `use` -- el tipo de retorno de la función/método y el paréntesis de apertura deben colocarse en líneas separadas para una mejor legibilidad: +- el tipo de retorno de una función/método y la llave de apertura están siempre en líneas separadas: ```php public function find( @@ -50,6 +50,10 @@ La Norma de Codificación Nette corresponde a la PSR-12 (o Estilo de Codificaci } ``` +La llave de apertura en una línea separada es importante para separar visualmente la firma de la función/método del cuerpo. Si la firma está en una línea, la separación es clara (imagen de la izquierda); si está en varias líneas, en PSR las firmas y los cuerpos se mezclan (en el centro), mientras que en la norma Nette permanecen separados (a la derecha): + +[* new-line-after.webp *] + Bloques de documentación (phpDoc) .[#toc-documentation-blocks-phpdoc] ===================================================================== diff --git a/contributing/es/documentation.texy b/contributing/es/documentation.texy index 6474661330..d259f9d79b 100644 --- a/contributing/es/documentation.texy +++ b/contributing/es/documentation.texy @@ -1,53 +1,69 @@ -Redactar la documentación -************************* +Contribuir a la documentación +***************************** .[perex] -Contribuir a la documentación es una de las muchas maneras de ayudar a Nette. También es una de las actividades más gratificantes, ya que ayudas a otros a entender el framework. +Contribuir a la documentación es una de las actividades más valiosas, ya que ayuda a otros a entender el marco. ¿Cómo escribir? .[#toc-how-to-write] ------------------------------------ -La documentación está destinada principalmente a personas que se están familiarizando con el tema. Por lo tanto, debe cumplir varios puntos importantes: +La documentación está destinada principalmente a personas que se inician en el tema. Por lo tanto, debe cumplir varios puntos importantes: -- **Cuando escriba, empiece por lo sencillo y general, y pase a temas más avanzados al final.** -- Proporcione sólo la información que el usuario realmente necesita saber sobre el tema. -- Verifique que su información es realmente cierta. Pruebe primero el ejemplo antes de darlo. -- Sea conciso: reduzca lo que escribe a la mitad. Y luego no dudes en volver a hacerlo. -- Intenta explicar el tema lo mejor posible. Por ejemplo, intenta explicar primero el tema a un colega. +- Empezar con temas sencillos y generales. Pasar al final a temas más avanzados. +- Intente explicar el tema con la mayor claridad posible. Por ejemplo, intente explicárselo primero a un colega. +- Proporcione sólo la información que el usuario realmente necesita saber sobre un tema determinado. +- Asegúrese de que la información es exacta. Pruebe cada código +- Sea conciso: reduzca lo que escribe a la mitad. Y luego no dude en volver a hacerlo. +- Utilice el resaltado con moderación, desde fuentes en negrita hasta marcos como `.[note]` +- Siga la [norma de codificación |Coding Standard] en el código -Tenga en cuenta estos puntos durante todo el proceso de redacción. La documentación está escrita en [Texy! |https://texy.info], así que aprenda su [sintaxis |syntax]. Puedes utilizar el editor de documentación en https://editor.nette.org/ para previsualizar el artículo mientras lo escribes. +Aprenda también la [sintaxis |syntax]. Para obtener una vista previa del artículo durante la escritura, puede utilizar el [editor de vista previa |https://editor.nette.org/]. -Entre las reglas generales de redacción enumeradas anteriormente, respete las siguientes: -- Tu código debe cumplir la [Norma de Codificación |Coding Standard]. -- Escriba los nombres de variables, clases y métodos en inglés. -- Los espacios de nombres sólo deben mencionarse a la primera. -- Intente formatear el código de modo que no aparezcan barras de desplazamiento. -- Prescinda de todo tipo de resaltadores, desde negritas hasta `.[note]` cajas. -- De la documentación, remítase sólo a ella o a `www`. +Mutaciones lingüísticas .[#toc-language-mutations] +-------------------------------------------------- +El inglés es el idioma principal, así que tus cambios deben ser en inglés. Si el inglés no es tu fuerte, utiliza [DeepL Translator |https://www.deepl.com/translator] y otros revisarán tu texto. -Estructura de la documentación .[#toc-documentation-structure] --------------------------------------------------------------- +La traducción a otros idiomas se hará automáticamente tras la aprobación y puesta a punto de tu edición. + + +Ediciones triviales .[#toc-trivial-edits] +----------------------------------------- + +Para contribuir a la documentación, es necesario tener una cuenta en [GitHub |https://github.com]. -La documentación completa está alojada en GitHub en el repositorio [nette/docs |https://github.com/nette/docs]. Este repositorio está dividido en ramas en función de la versión de la documentación, por ejemplo la rama `doc-3.1` contiene la documentación de la versión 3.1. Y luego está la rama `nette.org`, que contiene el contenido de los otros subdominios de nette.org. +La forma más sencilla de hacer un pequeño cambio en la documentación es utilizar los enlaces que aparecen al final de cada página: -Cada rama se divide a su vez en varias carpetas: +- *Mostrar en GitHub* abre la versión fuente de la página en GitHub. A continuación, sólo tienes que pulsar el botón `E` y podrás empezar a editar (debes haber iniciado sesión en GitHub) +- *Abrir vista previa* abre un editor donde puedes ver inmediatamente la forma visual final -* `cs` y `en`: contiene los archivos de documentación de cada versión lingüística -* `files`: imágenes que pueden incrustarse en las páginas de documentación. +Como el editor de vista [previa |https://editor.nette.org/] no tiene la capacidad de guardar los cambios directamente en GitHub, necesitas copiar el texto fuente al portapapeles (usando el botón *Copiar al portapapeles*) y luego pegarlo en el editor en GitHub. +Debajo del campo de edición hay un formulario de envío. Aquí no olvides resumir y explicar brevemente el motivo de tu edición. Tras el envío, se crea una solicitud de extracción (pull request, PR) que puede seguir editándose. -La ruta de un archivo sin extensión corresponde a la URL de una página de la documentación. Así, el fichero `en/quickstart/single-post.texy` tendrá la URL `doc.nette.org/en/quickstart/single-post`. +Ediciones más extensas .[#toc-larger-edits] +------------------------------------------- -Contribución .[#toc-contributing] ---------------------------------- +Es más apropiado estar familiarizado con los fundamentos del trabajo con el sistema de control de versiones Git que confiar únicamente en la interfaz de GitHub. Si no estás familiarizado con Git, puedes consultar la [guía git - the simple guide |https://rogerdudler.github.io/git-guide/] y considerar el uso de uno de los muchos [clientes gráficos |https://git-scm.com/downloads/guis] disponibles. -Para contribuir a la documentación, debes tener una cuenta en [GitHub |https://github.com] y conocer los fundamentos de Git. Si no estás familiarizado con Git, puedes consultar la guía rápida: git - [the simple guide |https://rogerdudler.github.io/git-guide/], o utilizar una de las muchas herramientas gráficas: GIT - [clientes GUI |https://git-scm.com/downloads/guis]. +Edita la documentación de la siguiente manera: -Puedes realizar cambios sencillos directamente en la interfaz de GitHub. Sin embargo, es más conveniente crear un fork del repositorio [nette/docs |https://github.com/nette/docs] y clonarlo en tu ordenador. Luego haz cambios en la rama apropiada, confirma el cambio, empuja a tu GitHub, y envía un pull request al repositorio original `nette/docs`. +1) en GitHub, crea un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del repositorio [nette/docs |https://github.com/nette/docs] +2) [clona |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositorio en tu ordenador +3) a continuación, realice cambios en la [rama correspondiente |#Documentation Structure] +4) compruebe si hay espacios de más en el texto utilizando la herramienta [Code-Checker |code-checker:] +5) guarde (confirme) los cambios +6) si estás satisfecho con los cambios, envíalos a tu bifurcación en GitHub +7) desde allí, envíalos al repositorio `nette/docs` creando un [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +Es habitual recibir comentarios con sugerencias. No pierdas de vista los cambios propuestos e incorpóralos. Añade los cambios sugeridos como nuevos commits y reenvíalos a GitHub. Nunca crees un nuevo pull request sólo para modificar uno ya existente. + + +Estructura de la documentación .[#toc-documentation-structure] +-------------------------------------------------------------- -Antes de cada pull request, es una buena idea ejecutar [Code-Checker |code-checker:] para comprobar los espacios en blanco sobrantes en el texto. +Toda la documentación se encuentra en GitHub, en el repositorio [nette/docs |https://github.com/nette/docs]. La versión actual se encuentra en la rama maestra, mientras que las versiones anteriores se encuentran en ramas como `doc-3.x`, `doc-2.x`. -{{priority: -1}} +El contenido de cada rama se divide en carpetas principales que representan áreas individuales de la documentación. Por ejemplo, `application/` corresponde a https://doc.nette.org/en/application, `latte/` corresponde a https://latte.nette.org, etc. Cada una de estas carpetas contiene subcarpetas que representan mutaciones lingüísticas (`cs`, `en`, ...) y, opcionalmente, una subcarpeta `files` con imágenes que pueden insertarse en las páginas de la documentación. diff --git a/contributing/files/new-line-after.webp b/contributing/files/new-line-after.webp new file mode 100644 index 0000000000..419492d01f Binary files /dev/null and b/contributing/files/new-line-after.webp differ diff --git a/contributing/fr/code.texy b/contributing/fr/code.texy index 84cded40e7..16e2c5f41e 100644 --- a/contributing/fr/code.texy +++ b/contributing/fr/code.texy @@ -1,110 +1,118 @@ -Proposition de modification du code -*********************************** +Contribuer au code +****************** -Nette Framework utilise Git et [GitHub |https://github.com/nette/nette] pour maintenir la base de code. La meilleure façon de contribuer est de commettre vos changements dans votre propre fork et ensuite de faire une demande de pull sur GitHub. Ce document résume les principales étapes d'une contribution réussie. +.[perex] +Vous envisagez de contribuer au Nette Framework et avez besoin de vous familiariser avec les règles et les procédures ? Ce guide du débutant vous guidera à travers les étapes pour contribuer efficacement au code, travailler avec les dépôts et mettre en œuvre les changements. -Préparation de l'environnement .[#toc-preparing-environment] -============================================================ +Procédure .[#toc-procedure] +=========================== -Commencez par [forker |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette sur GitHub |https://github.com/nette]. [Configurez |https://help.github.com/en/github/getting-started-with-github/set-up-git] soigneusement votre environnement Git local, configurez votre nom d'utilisateur et votre adresse e-mail, ces identifiants permettront d'identifier vos modifications dans l'historique du Framework Nette. +Pour contribuer au code, il est essentiel d'avoir un compte sur [GitHub |https://github.com] et d'être familiarisé avec les bases du système de contrôle de version Git. Si vous n'êtes pas familier avec Git, vous pouvez consulter [git - the simple guide |https://rogerdudler.github.io/git-guide/] et envisager d'utiliser l'un des nombreux [clients graphiques |https://git-scm.com/downloads/guis]. -Travailler sur votre correctif .[#toc-working-on-your-patch] -============================================================ +Préparation de l'environnement et du dépôt .[#toc-preparing-the-environment-and-repository] +------------------------------------------------------------------------------------------- -Avant de commencer à travailler sur votre patch, créez une nouvelle branche pour vos modifications. -```shell -git checkout -b new_branch_name -``` +1) Sur GitHub, créez un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] du [dépôt de paquets |www:packages] que vous avez l'intention de modifier. +2) [Clonez |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] ce dépôt sur votre ordinateur +3) Installez les dépendances, y compris [Nette Tester |tester:], en utilisant la commande `composer install` +4) Vérifiez que les tests fonctionnent en exécutant la commande `composer tester` +5) Créer une [nouvelle branche |#New Branch] basée sur la dernière version publiée. -Vous pouvez travailler sur votre changement de code. -Si possible, effectuez les changements à partir de la dernière version publiée. +Mise en œuvre de vos propres modifications .[#toc-implementing-your-own-changes] +-------------------------------------------------------------------------------- +Vous pouvez maintenant apporter vos propres modifications au code : -Test de vos modifications .[#toc-testing-your-changes] -====================================================== +1) Mettez en œuvre les changements souhaités et n'oubliez pas les tests. +2) Assurez-vous que les tests s'exécutent avec succès en utilisant `composer tester` +3) Vérifier si le code répond aux [normes de codage |#coding standards] +4) Sauvegarder (commit) les changements avec une description dans [ce format |#Commit Description] + +Vous pouvez créer plusieurs livraisons, une pour chaque étape logique. Chaque validation doit être significative en soi. + + +Soumettre des modifications .[#toc-submitting-changes] +------------------------------------------------------ + +Une fois que vous êtes satisfait des modifications, vous pouvez les soumettre : + +1) Pousser les changements sur GitHub vers votre fork +2) De là, soumettez-les au dépôt Nette en créant une [pull request|https://help.github.com/articles/creating-a-pull-request] (PR). +3) Fournissez [suffisamment d'informations |#pull request description] dans la description + + +Incorporer le retour d'information .[#toc-incorporating-feedback] +----------------------------------------------------------------- + +Vos modifications sont maintenant visibles par les autres. Il est courant de recevoir des commentaires contenant des suggestions : + +1) Garder une trace des changements proposés +2) Incorporez-les en tant que nouveaux commits ou [fusionnez-les avec les précédents |https://help.github.com/en/github/using-git/about-git-rebase] +3) Resoumettez les commits à GitHub, et ils apparaîtront automatiquement dans la demande d'extraction. + +Ne créez jamais une nouvelle demande d'extraction pour modifier une demande existante. + + +Documentation .[#toc-documentation] +----------------------------------- -Vous devez installer Nette Tester. Le moyen le plus simple est d'appeler `composer install` à la racine du référentiel. Maintenant vous devriez être capable d'exécuter des tests avec `./vendor/bin/tester` dans le terminal. +Si vous avez modifié une fonctionnalité ou en avez ajouté une nouvelle, n'oubliez pas de l'ajouter également [à la documentation |documentation]. -Certains tests peuvent échouer à cause d'un php.ini manquant. Par conséquent, vous devez appeler le runner avec le paramètre -c et spécifier le chemin vers le php.ini, par exemple `./vendor/bin/tester -c ./tests/php.ini`. -Une fois que vous êtes en mesure d'exécuter les tests, vous pouvez implémenter les vôtres ou modifier l'échec pour correspondre au nouveau comportement. Pour en savoir plus sur les tests avec Nette Tester, consultez la [page de documentation |tester:]. +Nouvelle branche .[#toc-new-branch] +=================================== + +Si possible, effectuez les modifications par rapport à la dernière version publiée, c'est-à-dire la dernière balise de la branche. Pour l'étiquette v3.2.1, créez une branche en utilisant cette commande : + +```shell +git checkout -b new_branch_name v3.2.1 +``` Normes de codage .[#toc-coding-standards] ========================================= -Votre code doit respecter les [normes de codage |coding standard] utilisées dans Nette Framework. C'est facile car il existe un vérificateur et un fixateur automatiques. Il peut être installé via Composer dans le répertoire global de votre choix : +Votre code doit respecter les [normes de codage |coding standard] utilisées dans le Nette Framework. Il existe un outil automatique pour vérifier et corriger le code. Vous pouvez l'installer **globalement** via Composer dans un dossier de votre choix : ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Maintenant vous devriez être capable d'exécuter l'outil dans le terminal. Par exemple, cette commande vérifie et corrige le code dans les dossiers `src` et `tests` dans le répertoire actuel : +Vous devriez maintenant être en mesure de lancer l'outil dans le terminal. La première commande vérifie et la seconde corrige le code dans les dossiers `src` et `tests` dans le répertoire courant : ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Validation des modifications .[#toc-committing-the-changes] -=========================================================== +Description de l'engagement .[#toc-commit-description] +====================================================== -Après avoir modifié le code, vous devez valider vos modifications. Créez plusieurs commits, un pour chaque étape logique. Chaque commit doit être utilisable tel quel - sans autres commits. Ainsi, les tests appropriés doivent également être inclus dans le même commit. +Dans Nette, les sujets de commit ont le format suivant : `Presenter: fixed AJAX detection [Closes #69]` -S'il vous plaît, vérifiez que votre code respecte les règles : -- Le code ne génère pas d'erreurs -- Votre code ne casse aucun test. -- Votre changement de code est testé. -- Vous ne commettez pas de modifications inutiles en espace blanc. +- domaine suivi de deux points +- l'objet du commit au passé ; si possible, commencer par des mots comme : added, fixed, refactored, changed, removed +- si le commit rompt la compatibilité ascendante, ajouter "BC break" (rupture de la compatibilité ascendante) +- toute connexion à l'outil de suivi des problèmes, comme `(#123)` ou `[Closes #69]` +- après le sujet, il peut y avoir une ligne vide suivie d'une description plus détaillée, y compris, par exemple, des liens vers le forum. -Le message de validation doit suivre le format suivant `Latte: fixed multi template rendering [Closes # 69]` c'est-à-dire -- une zone suivie de deux points -- le but du commit dans le passé, si possible, commencez par "ajouté.", "corrigé.", "refactorisé.", changé, supprimé -- lien éventuel vers le suivi des problèmes -- si le commit annule la rétrocompatibilité, ajoutez "BC break". -- il peut y avoir une ligne libre après le sujet et une description plus détaillée incluant des liens vers le forum. +Description de la demande de retrait .[#toc-pull-request-description] +===================================================================== -Pull-Requesting des commits .[#toc-pull-requesting-the-commits] -=============================================================== +Lors de la création d'une demande d'extraction, l'interface GitHub vous permet de saisir un titre et une description. Fournissez un titre concis et incluez autant d'informations que possible dans la description sur les raisons de votre changement. -Si vous êtes satisfait de vos modifications de code et de vos commits, vous devez pousser vos commits vers GitHub. +Précisez également dans l'en-tête s'il s'agit d'une nouvelle fonctionnalité ou d'une correction de bogue et si elle peut entraîner des problèmes de compatibilité ascendante (BC break). S'il existe un problème connexe, créez un lien vers celui-ci afin qu'il soit fermé lors de l'approbation de la demande d'extraction. -```shell -git push origin new_branch_name ``` - -Les changements sont présents publiquement, cependant, vous devez proposer vos changements pour l'intégration dans la branche master de Nette. Pour ce faire, [faites une demande de modification (pull request) |https://help.github.com/articles/creating-a-pull-request]. -Chaque pull request a un titre et une description. Veuillez fournir un titre descriptif. Il est souvent similaire au nom de la branche, par exemple "Securing signals against CSRF attack". - -La description de la demande de téléchargement doit contenir des informations plus spécifiques sur les modifications apportées au code : -``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Veuillez modifier le tableau d'information pour l'adapter à votre demande de retrait. Commentaires pour chaque élément de la liste : -- Indique si la demande ajoute une **fonctionnalité** ou s'il s'agit d'un **bugfix**. -- Fait éventuellement référence à **un problème connexe**, qui sera fermé après la fusion de la demande. -- Indique si la demande nécessite des **modifications de la documentation**, si oui, fournit des références aux demandes pull appropriées. Vous n'êtes pas obligé de fournir la modification de la documentation immédiatement, cependant, la demande pull ne sera pas fusionnée si la modification de la documentation est nécessaire. Le changement de documentation doit être préparé pour la documentation en anglais, les mutations dans d'autres langues sont facultatives. -- Indique si la pull request crée **une rupture de la BC**. S'il vous plaît, considérez tout ce qui change l'interface publique comme une rupture de la CB. - -Le tableau final pourrait ressembler à ceci : +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Retravailler vos modifications .[#toc-reworking-your-changes] -============================================================= -Il est très courant de recevoir des commentaires sur vos modifications de code. Essayez de suivre les changements proposés et retravaillez vos commits pour y parvenir. Vous pouvez commiter les changements proposés en tant que nouveaux commits et ensuite les écraser sur les commits précédents. Voir le chapitre sur le [rebasement interactif |https://help.github.com/en/github/using-git/about-git-rebase] sur GitHub. Après avoir rebasé vos modifications, forcez vos modifications à votre fork distant, tout sera automatiquement propagé à la pull request. {{priority: -1}} diff --git a/contributing/fr/coding-standard.texy b/contributing/fr/coding-standard.texy index a765bc0a32..6deb452aa8 100644 --- a/contributing/fr/coding-standard.texy +++ b/contributing/fr/coding-standard.texy @@ -38,7 +38,7 @@ La norme de codage Nette correspond au PSR-12 (ou style de codage PER), en certa - les fonctions flèches sont écrites sans espace avant la parenthèse, c'est-à-dire que `fn($a) => $b` - aucune ligne vide n'est requise entre les différents types d'instructions d'importation `use` -- le type de retour de la fonction/méthode et la parenthèse ouvrante doivent être placés sur des lignes séparées pour une meilleure lisibilité : +- le type de retour d'une fonction/méthode et l'accolade d'ouverture sont toujours sur des lignes séparées : ```php public function find( @@ -50,6 +50,10 @@ La norme de codage Nette correspond au PSR-12 (ou style de codage PER), en certa } ``` +La parenthèse ouvrante sur une ligne distincte est importante pour séparer visuellement la signature de la fonction/méthode du corps. Si la signature est sur une seule ligne, la séparation est claire (image de gauche), si elle est sur plusieurs lignes, dans PSR les signatures et les corps se confondent (au milieu), alors que dans la norme Nette ils restent séparés (à droite) : + +[* new-line-after.webp *] + Blocs de documentation (phpDoc) .[#toc-documentation-blocks-phpdoc] =================================================================== diff --git a/contributing/fr/documentation.texy b/contributing/fr/documentation.texy index 4eabe728e8..7fa839dfdc 100644 --- a/contributing/fr/documentation.texy +++ b/contributing/fr/documentation.texy @@ -1,53 +1,69 @@ -Rédaction de la documentation +Contribuer à la documentation ***************************** .[perex] -Contribuer à la documentation est l'une des nombreuses façons dont vous pouvez aider Nette. C'est également l'une des activités les plus gratifiantes, car vous aidez les autres à comprendre le framework. +Contribuer à la documentation est l'une des activités les plus utiles, car elle aide les autres à comprendre le cadre. Comment écrire ? .[#toc-how-to-write] ------------------------------------- -La documentation est principalement destinée aux personnes qui commencent à se familiariser avec le sujet. Elle doit donc répondre à plusieurs points importants : +La documentation est principalement destinée aux personnes qui découvrent le sujet. Elle doit donc répondre à plusieurs points importants : -- **Lorsque vous rédigez, commencez par le simple et le général, et passez aux sujets plus avancés à la fin.** -- Ne fournissez que les informations que l'utilisateur a réellement besoin de connaître sur le sujet. -- Vérifiez que vos informations sont réellement vraies. Testez d'abord l'exemple avant de le donner. -- Soyez concis - coupez en deux ce que vous écrivez. Et n'hésitez pas à le faire à nouveau. -- Essayez d'expliquer le sujet aussi bien que possible. Par exemple, essayez d'abord d'expliquer le sujet à un collègue. +- Commencer par des sujets simples et généraux. Passer à des sujets plus avancés à la fin +- Essayez d'expliquer le sujet aussi clairement que possible. Par exemple, essayez d'abord d'expliquer le sujet à un collègue. +- Ne fournissez que les informations que l'utilisateur a réellement besoin de connaître pour un sujet donné. +- Assurez-vous que vos informations sont exactes. Testez chaque code +- Soyez concis - coupez ce que vous écrivez en deux. Et n'hésitez pas à recommencer +- Utilisez la mise en évidence avec parcimonie, qu'il s'agisse de polices en gras ou de cadres tels que `.[note]` +- Respectez la [norme de codage |Coding Standard] dans le code -Gardez ces points à l'esprit tout au long du processus de rédaction. La documentation est écrite en [Texy ! |https://texy.info], apprenez donc sa [syntaxe |syntax]. Vous pouvez utiliser l'éditeur de documentation à l'adresse https://editor.nette.org/ pour prévisualiser l'article à mesure que vous le rédigez. +Apprenez également la [syntaxe |syntax]. Pour avoir un aperçu de l'article en cours de rédaction, vous pouvez utiliser l'[éditeur de prévisualisation |https://editor.nette.org/]. -Parmi les règles générales de rédaction énumérées précédemment, veuillez vous en tenir aux suivantes : -- Votre code doit être conforme à la [norme de codage |Coding Standard]. -- Écrivez les noms des variables, des classes et des méthodes en anglais. -- Les espaces de noms ne doivent être mentionnés qu'à la première mention. -- Essayez de formater le code de manière à ce que les barres de défilement ne s'affichent pas. -- Épargnez toutes sortes de surligneurs, des caractères gras aux `.[note]` boîtes. -- À partir de la documentation, ne faites référence qu'à la documentation ou à `www`. +Mutations linguistiques .[#toc-language-mutations] +-------------------------------------------------- +L'anglais étant la langue principale, vos modifications doivent être rédigées en anglais. Si l'anglais n'est pas votre fort, utilisez [DeepL Translator |https://www.deepl.com/translator] et d'autres personnes vérifieront votre texte. -Structure de la documentation .[#toc-documentation-structure] -------------------------------------------------------------- +La traduction dans d'autres langues se fera automatiquement après l'approbation et la mise au point de votre texte. + + +Modifications mineures .[#toc-trivial-edits] +-------------------------------------------- + +Pour contribuer à la documentation, vous devez avoir un compte sur [GitHub |https://github.com]. -La documentation complète est hébergée sur GitHub dans le dépôt [nette/docs |https://github.com/nette/docs]. Ce dépôt est divisé en branches basées sur la version de la documentation, par exemple la branche `doc-3.1` contient la documentation de la version 3.1. Et puis il y a la branche `nette.org`, qui contient le contenu des autres sous-domaines de nette.org. +La manière la plus simple d'apporter une petite modification à la documentation est d'utiliser les liens qui se trouvent à la fin de chaque page : -Chaque branche est ensuite divisée en plusieurs dossiers : +- *Show on GitHub* ouvre la version source de la page sur GitHub. Il suffit ensuite d'appuyer sur le bouton `E` pour commencer à éditer (vous devez être connecté à GitHub). +- *Open preview* ouvre un éditeur où vous pouvez immédiatement voir la forme visuelle finale. -* `cs` et `en`: contient les fichiers de documentation pour chaque version linguistique -* `files`: images qui peuvent être intégrées dans les pages de documentation +Comme l'[éditeur de prévisualisation |https://editor.nette.org/] ne permet pas d'enregistrer les modifications directement sur GitHub, vous devez copier le texte source dans le presse-papiers (à l'aide du bouton *Copier dans le presse-papiers*) et le coller ensuite dans l'éditeur sur GitHub. +Sous le champ d'édition se trouve un formulaire de soumission. N'oubliez pas de résumer et d'expliquer brièvement la raison de votre modification. Après la soumission, une demande d'extraction (PR) est créée, qui peut être modifiée ultérieurement. -Le chemin d'un fichier sans extension correspond à l'URL d'une page de la documentation. Ainsi, le fichier `en/quickstart/single-post.texy` aura l'URL `doc.nette.org/en/quickstart/single-post`. +Modifications plus importantes .[#toc-larger-edits] +--------------------------------------------------- -Contribuer à .[#toc-contributing] ---------------------------------- +Il est plus approprié de se familiariser avec les bases du travail avec le système de contrôle de version Git plutôt que de se fier uniquement à l'interface GitHub. Si vous n'êtes pas familier avec Git, vous pouvez vous référer à [git - le guide simple |https://rogerdudler.github.io/git-guide/] et envisager d'utiliser l'un des nombreux [clients graphiques |https://git-scm.com/downloads/guis] disponibles. -Pour contribuer à la documentation, vous devez avoir un compte sur [GitHub |https://github.com] et connaître les bases de Git. Si vous n'êtes pas familier avec Git, vous pouvez consulter le guide rapide : [git - the simple guide |https://rogerdudler.github.io/git-guide/], ou utiliser l'un des nombreux outils graphiques : [GIT - GUI clients |https://git-scm.com/downloads/guis]. +Modifiez la documentation de la manière suivante : -Vous pouvez effectuer des modifications simples directement dans l'interface GitHub. Cependant, il est plus pratique de créer un fork du dépôt [nette/docs |https://github.com/nette/docs] et de le cloner sur votre ordinateur. Ensuite, apportez des modifications dans la branche appropriée, livrez les changements, poussez-les vers votre GitHub et envoyez une demande de modification au dépôt original `nette/docs`. +1) sur GitHub, créez une [fourche |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] du dépôt [nette/docs |https://github.com/nette/docs] +2) [clonez |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] ce dépôt sur votre ordinateur +3) ensuite, faites des changements dans la [branche appropriée |#Documentation Structure] +4) vérifiez qu'il n'y a pas d'espaces supplémentaires dans le texte à l'aide de l'outil [Code-Checker |code-checker:] +5) sauvegarder (commit) les changements +6) si vous êtes satisfait des changements, poussez-les sur GitHub vers votre fork +7) à partir de là, soumettez-les au dépôt `nette/docs` en créant une [pull request|https://help.github.com/articles/creating-a-pull-request] (PR). + +Il est courant de recevoir des commentaires contenant des suggestions. Gardez une trace des changements proposés et incorporez-les. Ajoutez les modifications suggérées en tant que nouveaux commits et renvoyez-les à GitHub. Ne créez jamais une nouvelle demande d'extraction juste pour modifier une demande existante. + + +Structure de la documentation .[#toc-documentation-structure] +------------------------------------------------------------- -Avant chaque demande de retrait, il est bon d'exécuter [Code-Checker |code-checker:] pour vérifier les espaces blancs supplémentaires dans le texte. +L'ensemble de la documentation se trouve sur GitHub dans le dépôt [nette/docs |https://github.com/nette/docs]. La version actuelle se trouve dans la branche master, tandis que les versions plus anciennes se trouvent dans des branches telles que `doc-3.x`, `doc-2.x`. -{{priority: -1}} +Le contenu de chaque branche est divisé en dossiers principaux représentant les différents domaines de la documentation. Par exemple, `application/` correspond à https://doc.nette.org/en/application, `latte/` correspond à https://latte.nette.org, etc. Chacun de ces dossiers contient des sous-dossiers représentant les mutations linguistiques (`cs`, `en`, ...) et éventuellement un sous-dossier `files` contenant des images qui peuvent être insérées dans les pages de la documentation. diff --git a/contributing/hu/code.texy b/contributing/hu/code.texy index e0825cf876..f18bb335bc 100644 --- a/contributing/hu/code.texy +++ b/contributing/hu/code.texy @@ -1,110 +1,118 @@ -Javaslat a szabályzat módosítására -********************************** +Hozzájárulás a kódhoz +********************* -A Nette Framework a Git-et és a [GitHubot |https://github.com/nette/nette] használja a kódbázis karbantartására. A legjobb módja a hozzájárulásnak, ha a változtatásokat a saját elágazásodban rögzíted, majd a GitHubon pull requestet teszel. Ez a dokumentum összefoglalja a sikeres hozzájárulás főbb lépéseit. +.[perex] +Tervezi, hogy hozzájárul a Nette keretrendszerhez, és meg kell ismerkednie a szabályokkal és eljárásokkal? Ez a kezdő útmutató végigvezet a kódhoz való hatékony hozzájárulás, a tárolókkal való munka és a változtatások megvalósításának lépésein. -A környezet előkészítése .[#toc-preparing-environment] -====================================================== +Eljárás .[#toc-procedure] +========================= -Kezdjük a [Nette |https://github.com/nette] [elágazásával |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [a GitHubon |https://github.com/nette]. Gondosan [állítsa be |https://help.github.com/en/github/getting-started-with-github/set-up-git] a helyi Git környezetét, konfigurálja a felhasználónevét és az e-mail címét, ezek a hitelesítő adatok azonosítani fogják a módosításait a Nette Framework történetében. +A kódhoz való hozzájáruláshoz elengedhetetlen, hogy rendelkezz egy fiókkal a [GitHubon |https://github.com], és ismerd a Git verziókezelő rendszerrel való munka alapjait. Ha nem ismered a Git-et, akkor nézd meg a [git - az egyszerű útmutatót |https://rogerdudler.github.io/git-guide/], és fontold meg a számos [grafikus kliens |https://git-scm.com/downloads/guis] egyikének használatát. -A javításon való munka .[#toc-working-on-your-patch] -==================================================== +A környezet és a tároló előkészítése .[#toc-preparing-the-environment-and-repository] +------------------------------------------------------------------------------------- -Mielőtt elkezdenél dolgozni a javításodon, hozz létre egy új ágat a változtatásaidnak. -```shell -git checkout -b new_branch_name -``` +1) A GitHubon hozzon létre egy [elágazást |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] a [csomagtárolóból |www:packages], amelyet módosítani kíván. +2) [Klónozzuk |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] ezt a tárolót a számítógépünkre. +3) Telepítse a függőségeket, beleértve a [Nette Tester-t |tester:] is, a `composer install` parancs segítségével. +4) Ellenőrizze, hogy a tesztek működnek-e a következő futtatással `composer tester` +5) Hozzon létre egy [új ágat |#New Branch] a legfrissebb kiadott verzió alapján. -Dolgozhatsz a kódváltoztatásodon. -Ha lehetséges, végezze el a változtatásokat a legutóbb kiadott verzióhoz képest. +Saját változtatások végrehajtása .[#toc-implementing-your-own-changes] +---------------------------------------------------------------------- +Most már elvégezheti saját kódjainak módosítását: -A változtatások tesztelése .[#toc-testing-your-changes] -======================================================= +1) Végezze el a kívánt változtatásokat, és ne feledkezzen meg a tesztekről. +2) Győződjön meg róla, hogy a tesztek sikeresen futnak a `composer tester` +3) Ellenőrizze, hogy a kód megfelel-e a [kódolási szabványoknak |#coding standards]. +4) Mentse (commit) a változtatásokat egy leírással [ebben a formátumban |#Commit Description] + +Több commitot is létrehozhat, egyet-egyet minden egyes logikai lépéshez. Minden commitnak önmagában is értelmesnek kell lennie. + + +Változások elküldése .[#toc-submitting-changes] +----------------------------------------------- + +Ha elégedett a módosításokkal, elküldheti azokat: + +1) Tolja a változtatásokat a GitHubra a saját elágazásához +2) Onnan küldje el őket a Nette tárolóba egy [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) létrehozásával. +3) Adjon meg [elegendő információt |#pull request description] a leírásban + + +Visszajelzések beépítése .[#toc-incorporating-feedback] +------------------------------------------------------- + +A commitjaid most már mások számára is láthatóak. Gyakori, hogy javaslatokat tartalmazó megjegyzéseket kapunk: + +1) Tartsa nyomon a javasolt változtatásokat +2) építse be őket új commitként vagy [egyesítse őket a korábbiakkal |https://help.github.com/en/github/using-git/about-git-rebase] +3) Küldje el újra a commitokat a GitHubra, és azok automatikusan megjelennek a pull requestben. + +Soha ne hozzon létre új pull requestet egy meglévő módosításához. + + +Dokumentáció .[#toc-documentation] +---------------------------------- -Telepítenie kell a Nette Testert. A legegyszerűbb, ha a `composer install` címet hívod meg a repository root-ban. Most már képesnek kell lennie a tesztek futtatására a `./vendor/bin/tester` segítségével a terminálban. +Ha megváltoztattál egy funkciót, vagy új funkciót adtál hozzá, ne felejtsd el [azt is hozzáadni a dokumentációhoz |documentation]. -Néhány teszt sikertelen lehet a hiányzó php.ini miatt. Ezért a futót a -c paraméterrel kell hívni, és meg kell adni a php.ini elérési útvonalát, például `./vendor/bin/tester -c ./tests/php.ini`. -Miután a tesztek futtatására képes, megvalósíthatja a saját tesztjeit, vagy megváltoztathatja a hibás működést, hogy megfeleljen az új viselkedésnek. A Nette Testerrel való tesztelésről bővebben a [dokumentációs oldalon |tester:] olvashat. +Új ág .[#toc-new-branch] +======================== + +Ha lehetséges, végezze el a változtatásokat a legutolsó kiadott verzióval, azaz az ág utolsó tagjével szemben. A v3.2.1 címkéhez hozzon létre egy ágat ezzel a paranccsal: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Kódolási szabványok .[#toc-coding-standards] ============================================ -A kódnak követnie kell a Nette Frameworkben használt [kódolási szabványt |coding standard]. Ez könnyű, mert van automatikus ellenőrző és javító program. A Composer segítségével telepíthető a kiválasztott globális könyvtárba: +A kódnak meg kell felelnie a Nette Frameworkben használt [kódolási szabványnak |coding standard]. A kód ellenőrzésére és javítására automatikus eszköz áll rendelkezésre. Ezt **globálisan** telepítheted a Composer segítségével egy általad választott mappába: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Most már képesnek kell lennie arra, hogy futtassa az eszközt a terminálban. Például ez a parancs ellenőrzi és javítja a kódot az aktuális könyvtár `src` és `tests` mappáiban: +Most már képesnek kell lennie az eszköz futtatására a terminálban. Az első parancs ellenőrzi, a második pedig javítja a kódot az aktuális könyvtárban lévő `src` és `tests` mappákban: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -A változtatások átadása .[#toc-committing-the-changes] -====================================================== +Kötelezettségvállalás Leírás .[#toc-commit-description] +======================================================= -Miután megváltoztattad a kódot, a változtatásokat rögzítened kell. Hozzon létre több commitot, egyet-egyet minden logikai lépéshez. Minden commitnak önmagában - más commitok nélkül - használhatónak kell lennie. Tehát a megfelelő teszteket is ugyanabban a commitban kell szerepeltetni. +A Nette-ben a commit témák a következő formátumúak: `Presenter: fixed AJAX detection [Closes #69]` -Kérjük, ellenőrizd kétszer is, hogy a kódod megfelel-e a szabályoknak: -- A kód nem generál hibát -- A kódod nem törik meg egyetlen tesztet sem. -- A kódváltoztatásod tesztelve van. -- Nem követsz el haszontalan white-space változtatásokat. +- terület, amelyet kettőspont követ +- a kötelezettségvállalás célja múlt időben; ha lehetséges, kezdje a következő szavakkal: added, fixed, refactored, changed, removed +- ha a commit megszakítja a visszafelé kompatibilitást, írja be, hogy "BC break". +- bármilyen kapcsolat a hibakövetővel, például `(#123)` vagy `[Closes #69]` +- a tárgy után egy üres sor következhet, amelyet egy részletesebb leírás követhet, beleértve például a fórumra mutató linkeket is. -A Commit üzenetnek a következő formátumot kell követnie `Latte: fixed multi template rendering [Closes # 69]` azaz: -- egy terület, amelyet kettőspont követ -- a commit célja a múltban, ha lehetséges, kezdje "added.", "fixed.", "refactored.", changed, removed -- esetleges link a hibakövetésre -- ha a commit visszaveti a visszafelé kompatibilitást, hozzá kell adni a "BC break" szót. -- a tárgy után lehet egy szabad sor és egy részletesebb leírás, beleértve a fórumra mutató linkeket is. +Pull Request leírása .[#toc-pull-request-description] +===================================================== -A commitok kihúzása-kérése .[#toc-pull-requesting-the-commits] -============================================================== +A GitHub felületén a pull request létrehozásakor megadhat egy címet és egy leírást. Adjon tömör címet, és a leírásban adjon meg minél több információt a változtatás okairól. -Ha elégedett vagy a kódváltoztatásokkal és commitokkal, akkor a commitokat a GitHub-ra kell tolnod. +A fejlécben azt is adja meg, hogy új funkcióról vagy hibajavításról van-e szó, és hogy okozhat-e visszafelé kompatibilitási problémákat (BC break). Ha van kapcsolódó probléma, hivatkozzon rá, hogy az a pull request jóváhagyásakor lezárásra kerüljön. -```shell -git push origin new_branch_name ``` - -A változtatások nyilvánosan jelen vannak, azonban a Nette master ágába való integrálásra fel kell ajánlanod a változtatásokat. Ehhez [tegyen egy pull requestet |https://help.github.com/articles/creating-a-pull-request]. -Minden pull requestnek van egy címe és egy leírása. Kérjük, adjon meg valamilyen leíró címet. Ez gyakran hasonlít az ág nevéhez, például "Securing signals against CSRF attack". - -A pull request leírásának tartalmaznia kell néhány konkrétabb információt a kódváltoztatásokról: -``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Kérjük, módosítsa az információs táblázatot a pull request-nek megfelelően. Megjegyzések az egyes listaelemekhez: -- Megadja, hogy a pull request **feature**-t ad hozzá, vagy **bugfix**. -- Hivatkozás az esetleges **kapcsolódó problémára**, amely a pull request egyesítése után lezárásra kerül. -- Megmondja, hogy a pull requestnek szüksége van-e **dokumentációs változtatásokra**, ha igen, adjon meg hivatkozásokat a megfelelő pull requestekre. Nem kell azonnal megadni a dokumentációs változtatást, azonban a pull request nem lesz egyesítve, ha a dokumentációs változtatásra szükség van. A dokumentációváltozást az angol nyelvű dokumentációhoz kell elkészíteni, más nyelvi mutációk nem kötelezőek. -- Jelzi, ha a pull request **a BC törést** hoz létre. Kérjük, tekintsen BC breaknek mindent, ami a nyilvános interfészt megváltoztatja. - -A végső táblázat így nézhet ki: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -A változtatások átdolgozása .[#toc-reworking-your-changes] -========================================================== -Nagyon gyakori, hogy megjegyzéseket kapunk a kódváltoztatásunkhoz. Kérjük, próbáld meg követni a javasolt változtatásokat, és ennek megfelelően dolgozzátok át a commitokat. A javasolt változtatásokat új commit-ként commit-olhatja, majd összenyomhatja a korábbiakkal. Lásd az [Interaktív újraközlés |https://help.github.com/en/github/using-git/about-git-rebase] fejezetet a GitHubon. A változtatások újraalapozása után force-push a változtatásokat a távoli elágazásodra, minden automatikusan átkerül a pull requestbe. {{priority: -1}} diff --git a/contributing/hu/coding-standard.texy b/contributing/hu/coding-standard.texy index 870513ab8d..6fe0792887 100644 --- a/contributing/hu/coding-standard.texy +++ b/contributing/hu/coding-standard.texy @@ -38,7 +38,7 @@ A Nette kódolási szabvány megfelel a PSR-12-nek (vagy PER kódolási stílusn - A nyílfüggvényeket a zárójelek előtt szóköz nélkül írjuk, pl. `fn($a) => $b` - nincs szükség üres sorra a különböző típusú `use` import utasítások között. -- a függvény/módszer visszatérési típusát és a nyitó zárójelet a jobb olvashatóság érdekében külön sorokban kell elhelyezni: +- a függvény/módszer visszatérési típusa és a nyitó szögletes zárójel mindig külön sorban van: ```php public function find( @@ -50,6 +50,10 @@ A Nette kódolási szabvány megfelel a PSR-12-nek (vagy PER kódolási stílusn } ``` +A külön sorban lévő nyitó szögletes zárójel fontos a függvény/módszer aláírásának a testtől való vizuális elválasztásához. Ha az aláírás egy sorban van, a szétválasztás egyértelmű (bal oldali kép), ha több sorban van, a PSR-ben az aláírás és a testek összemosódnak (középen), míg a Nette-szabványban külön maradnak (jobbra): + +[* new-line-after.webp *] + Dokumentációs blokkok (phpDoc) .[#toc-documentation-blocks-phpdoc] ================================================================== diff --git a/contributing/hu/documentation.texy b/contributing/hu/documentation.texy index 9ceb0dbafd..7e28cbe78c 100644 --- a/contributing/hu/documentation.texy +++ b/contributing/hu/documentation.texy @@ -1,53 +1,69 @@ -A dokumentáció megírása -*********************** +Hozzájárulás a dokumentációhoz +****************************** .[perex] -A dokumentációhoz való hozzájárulás a Nette-nek nyújtott segítség egyik módja. Ez egyben az egyik leghálásabb tevékenység is, mivel segítesz másoknak megérteni a keretrendszert. +A dokumentációhoz való hozzájárulás az egyik legértékesebb tevékenység, mivel segít másoknak megérteni a keretrendszert. -Hogyan kell írni? .[#toc-how-to-write] --------------------------------------- +Hogyan írjunk? .[#toc-how-to-write] +----------------------------------- -A dokumentáció elsősorban azoknak szól, akik még csak most ismerkednek a témával. Ezért több fontos pontnak kell megfelelnie: +A dokumentáció elsősorban azoknak szól, akik újak a témában. Ezért több fontos pontnak kell megfelelnie: -- **Az írás során kezdje az egyszerű és általános témákkal, és a végén térjen át a haladóbb témákra.** -- Csak azokat az információkat adja meg, amelyeket a felhasználónak valóban tudnia kell a témáról. -- Ellenőrizze, hogy az információi valóban igazak-e. Először tesztelje a példát, mielőtt példát adna. -- Legyen tömör - vágja félbe, amit ír. És aztán nyugodtan csináld újra. -- Próbáld meg a lehető legjobban elmagyarázni a dolgot. Próbáld meg például először egy kollégának elmagyarázni a témát. +- Kezdje egyszerű és általános témákkal. A végén térjen át a haladóbb témákra +- Igyekezzen a lehető legvilágosabban elmagyarázni a témát. Például próbálja meg először elmagyarázni a témát egy kollégának. +- Csak olyan információkat adjon meg, amelyeket a felhasználónak valóban tudnia kell az adott témához. +- Győződjön meg arról, hogy az információi pontosak. Teszteljen minden kódot +- Legyen tömör - vágja félbe, amit ír. És aztán nyugodtan csináld újra +- Használjon takarékosan kiemelést, a félkövér betűtípusoktól kezdve a keretekig, mint pl. `.[note]` +- Kövesse a [kódolási szabványt |Coding Standard] a kódban -Ezeket a pontokat tartsa szem előtt az írás során. A dokumentáció [Texy! |https://texy.info] nyelven íródott, ezért tanulja meg a [szintaxisát |syntax]. A https://editor.nette.org/ oldalon található dokumentációs szerkesztővel a cikket írás közben előnézetben megtekintheti. +Tanuld meg a [szintaxist |syntax] is. A cikk írás közbeni előnézetéhez használhatja az [előnézeti szerkesztőt |https://editor.nette.org/]. -A korábban felsorolt általános írási szabályok közül tartsa be a következőket: -- A kódodnak meg kell felelnie a [Kódolási szabványnak |Coding Standard]. -- A változók, osztályok és metódusok nevét angolul írja. -- A névtereket csak első említéskor kell megemlíteni. -- Igyekezzen úgy formázni a kódot, hogy ne jelenjenek meg görgetősávok. -- Kíméljen meg mindenféle kiemeléstől, a félkövér betűtől kezdve a `.[note]` dobozoktól. -- A dokumentációból csak a dokumentációra vagy a `www` címre hivatkozzon. +Nyelvi mutációk .[#toc-language-mutations] +------------------------------------------ +Az angol az elsődleges nyelv, ezért a módosításoknak angolul kell történniük. Ha az angol nem az erősséged, használd a [DeepL fordítót |https://www.deepl.com/translator], és mások ellenőrzik a szövegedet. -A dokumentáció felépítése .[#toc-documentation-structure] ---------------------------------------------------------- +A más nyelvekre történő fordítás automatikusan megtörténik a szerkesztésed jóváhagyása és finomhangolása után. -A teljes dokumentáció a GitHubon található a [nette/docs |https://github.com/nette/docs] tárolóban. Ez a tároló a dokumentáció verziója alapján ágakra van osztva, például a `doc-3.1` ág tartalmazza a 3.1-es verzió dokumentációját. Aztán van a `nette.org` ág, amely a nette.org többi aldomainjének tartalmát tartalmazza. -Az egyes ágak ezután több mappára oszlanak: +Triviális szerkesztések .[#toc-trivial-edits] +--------------------------------------------- -* `cs` és `en`: az egyes nyelvi verziók dokumentációs fájljait tartalmazza. -* `files`: a dokumentációs oldalakba beágyazható képek. +A dokumentációhoz való hozzájáruláshoz egy fiókkal kell rendelkeznie a [GitHubon |https://github.com]. -A kiterjesztés nélküli fájl elérési útja a dokumentáció egy oldalának URL-jének felel meg. Így a `en/quickstart/single-post.texy` fájl URL címe `doc.nette.org/en/quickstart/single-post` lesz. +A legegyszerűbb módja annak, hogy egy apró változtatást végezzünk a dokumentációban, ha az egyes oldalak végén található linkeket használjuk: +- *Megjelenítés a GitHubon* megnyitja az oldal forrásváltozatát a GitHubon. Ezután csak nyomd meg a `E` gombot, és máris kezdheted a szerkesztést (be kell jelentkezned a GitHubra). +- *Előnézet megnyitása* megnyit egy szerkesztőt, ahol azonnal láthatja a végleges vizuális formát -Hozzájárulás .[#toc-contributing] ---------------------------------- +Mivel [az előnézeti szerkesztő |https://editor.nette.org/] nem képes a változtatások közvetlen mentésére a GitHubra, a forrásszöveget a vágólapra kell másolnia (a *Másolás a vágólapra* gomb segítségével), majd beillesztenie a GitHubon lévő szerkesztőbe. +A szerkesztőmező alatt található egy űrlap a beküldéshez. Itt ne felejtse el röviden összefoglalni és megmagyarázni a szerkesztés okát. A beküldés után egy úgynevezett pull request (PR) jön létre, amelyet tovább lehet szerkeszteni. -A dokumentációhoz való hozzájáruláshoz rendelkeznie kell egy fiókkal a [GitHubon |https://github.com], és ismernie kell a Git alapjait. Ha nem ismered a Gitet, akkor nézd meg a gyors útmutatót: [git - az egyszerű útmutató |https://rogerdudler.github.io/git-guide/], vagy használd a számos grafikus eszköz egyikét: [GIT - GUI kliensek |https://git-scm.com/downloads/guis]. -Egyszerű módosításokat közvetlenül a GitHub felületén végezhet. Kényelmesebb azonban, ha létrehoz egy elágazást a [nette/docs |https://github.com/nette/docs] tárolóból, és azt klónozza a számítógépére. Ezután végezzen módosításokat a megfelelő ágban, rögzítse a változást, tolja fel a GitHubra, és küldjön pull requestet az eredeti `nette/docs` tárolóba. +Nagyobb szerkesztések .[#toc-larger-edits] +------------------------------------------ -Minden pull request előtt érdemes lefuttatni a [Code-Checker |code-checker:] programot, hogy ellenőrizze a szövegben lévő extra szóközöket. +Célszerűbb a Git verziókezelő rendszerrel való munka alapjait ismerni, mint kizárólag a GitHub felületére hagyatkozni. Ha nem ismeri a Gitet, olvassa el a [git - az egyszerű útmutatót |https://rogerdudler.github.io/git-guide/], és fontolja meg a számos elérhető [grafikus kliens |https://git-scm.com/downloads/guis] egyikének használatát. -{{priority: -1}} +Szerkessze a dokumentációt a következő módon: + +1) a GitHubon hozzon létre egy [elágazást |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] a [nette/docs |https://github.com/nette/docs] tárolóból. +2) [klónozza |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] ezt a tárolót a számítógépére +3) ezután végezzen módosításokat a [megfelelő ágban |#Documentation Structure] +4) a [Code-Checker |code-checker:] eszközzel ellenőrizze, hogy nincsenek-e extra szóközök a szövegben. +5) mentse (commit) a változtatásokat +6) ha elégedett vagy a változtatásokkal, tedd fel a GitHubra a saját elágazásodba. +7) onnan küldje el őket a `nette/docs` tárolóba egy [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) létrehozásával. + +Gyakori, hogy javaslatokat tartalmazó megjegyzéseket kapunk. Tartsd szemmel a javasolt változtatásokat és építsd be őket. A javasolt változtatásokat új commitként add hozzá, és küldd el újra a GitHubra. Soha ne hozzon létre új pull requestet csak azért, hogy egy meglévőt módosítson. + + +Dokumentáció szerkezete .[#toc-documentation-structure] +------------------------------------------------------- + +A teljes dokumentáció a GitHubon található a [nette/docs |https://github.com/nette/docs] tárolóban. Az aktuális verzió a master ágban található, míg a régebbi verziók a `doc-3.x`, `doc-2.x` ágakban találhatók. + +Az egyes ágak tartalma fő mappákra van osztva, amelyek a dokumentáció egyes területeit képviselik. Például a `application/` megfelel a https://doc.nette.org/en/application, a `latte/` megfelel a https://latte.nette.org, stb. Mindegyik mappa tartalmaz nyelvi mutációkat képviselő almappákat (`cs`, `en`, ...) és opcionálisan egy `files` almappát a dokumentáció oldalaira beilleszthető képekkel. diff --git a/contributing/it/code.texy b/contributing/it/code.texy index c42e7e8ce0..1e1cb654a4 100644 --- a/contributing/it/code.texy +++ b/contributing/it/code.texy @@ -1,110 +1,118 @@ -Proposta di modifica del codice -******************************* +Contribuire al codice +********************* -Nette Framework utilizza Git e [GitHub |https://github.com/nette/nette] per la manutenzione del codice base. Il modo migliore per contribuire è fare il commit delle modifiche al proprio fork e poi fare una richiesta di pull su GitHub. Questo documento riassume i passi principali per contribuire con successo. +.[perex] +Avete intenzione di contribuire al framework Nette e avete bisogno di familiarizzare con le regole e le procedure? Questa guida per principianti vi guiderà attraverso i passaggi per contribuire efficacemente al codice, lavorare con i repository e implementare le modifiche. -Preparazione dell'ambiente .[#toc-preparing-environment] -======================================================== +Procedura .[#toc-procedure] +=========================== -Iniziare con il [fork di |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette su GitHub |https://github.com/nette]. [Impostate |https://help.github.com/en/github/getting-started-with-github/set-up-git] con cura il vostro ambiente Git locale, configurate il vostro nome utente e la vostra e-mail; queste credenziali identificheranno le vostre modifiche nella cronologia del framework Nette. +Per contribuire al codice, è essenziale avere un account su [GitHub |https://github.com] e conoscere le basi del sistema di controllo di versione Git. Se non si ha familiarità con Git, si può consultare la [guida git - the simple guide |https://rogerdudler.github.io/git-guide/] e prendere in considerazione l'utilizzo di uno dei tanti [client grafici |https://git-scm.com/downloads/guis]. -Lavorare sulla patch .[#toc-working-on-your-patch] -================================================== +Preparazione dell'ambiente e del repository .[#toc-preparing-the-environment-and-repository] +-------------------------------------------------------------------------------------------- -Prima di iniziare a lavorare sulla patch, create un nuovo ramo per le vostre modifiche. -```shell -git checkout -b new_branch_name -``` +1) Su GitHub, creare un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del [repository del pacchetto |www:packages] che si intende modificare +2) [Clonare |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] questo repository sul proprio computer +3) Installare le dipendenze, compreso [Nette Tester |tester:], usando il comando `composer install`. +4) Verificate che i test funzionino eseguendo `composer tester` +5) Creare un [nuovo ramo |#New Branch] basato sull'ultima versione rilasciata + + +Implementare le proprie modifiche .[#toc-implementing-your-own-changes] +----------------------------------------------------------------------- + +Ora è possibile apportare le proprie modifiche al codice: + +1) Implementare le modifiche desiderate e non dimenticare i test. +2) Assicurarsi che i test vengano eseguiti con successo `composer tester` +3) Verificare se il codice è conforme agli standard di [codifica |#coding standards] +4) Salvare (commit) le modifiche con una descrizione in [questo formato |#Commit Description] + +È possibile creare più commit, uno per ogni fase logica. Ogni commit deve essere significativo di per sé. + + +Invio delle modifiche .[#toc-submitting-changes] +------------------------------------------------ + +Una volta soddisfatti delle modifiche, è possibile inviarle: + +1) Spingere le modifiche su GitHub al proprio fork +2) Da lì, inviarle al repository di Nette creando una [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) Fornire [informazioni sufficienti |#pull request description] nella descrizione + + +Incorporare il feedback .[#toc-incorporating-feedback] +------------------------------------------------------ + +I vostri commit sono ora visibili agli altri. È frequente ricevere commenti con suggerimenti: -È possibile lavorare sulla modifica del codice. +1) Tenere traccia delle modifiche proposte +2) incorporarle come nuovi commit o [unirle a quelle precedenti |https://help.github.com/en/github/using-git/about-git-rebase] +3) Ripresentare i commit a GitHub, che appariranno automaticamente nella richiesta di pull. -Se possibile, apportate le modifiche dall'ultima versione rilasciata. +Non creare mai una nuova richiesta di pull per modificarne una esistente. -Testare le modifiche .[#toc-testing-your-changes] -================================================= +Documentazione .[#toc-documentation] +------------------------------------ -È necessario installare Nette Tester. Il modo più semplice è chiamare `composer install` nella root del repository. Ora si dovrebbe essere in grado di eseguire i test con `./vendor/bin/tester` nel terminale. +Se avete modificato una funzionalità o ne avete aggiunta una nuova, non dimenticate di [aggiungerla |documentation] anche [alla documentazione |documentation]. -Alcuni test potrebbero fallire a causa della mancanza di php.ini. Pertanto, si dovrebbe chiamare il runner con il parametro -c e specificare il percorso di php.ini, per esempio `./vendor/bin/tester -c ./tests/php.ini`. -Dopo aver eseguito i test, è possibile implementare i propri o modificare i fallimenti per adattarli al nuovo comportamento. Per saperne di più sui test con Nette Tester, consultare la [pagina della documentazione |tester:]. +Nuovo ramo .[#toc-new-branch] +============================= + +Se possibile, apportare le modifiche all'ultima versione rilasciata, cioè all'ultimo tag del ramo. Per il tag v3.2.1, creare un ramo usando questo comando: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Standard di codifica .[#toc-coding-standards] ============================================= -Il codice deve seguire gli [standard di codifica |coding standard] utilizzati in Nette Framework. È facile, perché c'è un programma di controllo e correzione automatico. Può essere installato tramite Composer nella cartella globale scelta: +Il codice deve essere conforme agli [standard di codifica |coding standard] utilizzati da Nette Framework. È disponibile uno strumento automatico per la verifica e la correzione del codice. È possibile installarlo **globalmente** tramite Composer in una cartella a scelta: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Ora si dovrebbe essere in grado di eseguire lo strumento nel terminale. Ad esempio, questo comando controlla e corregge il codice nelle cartelle `src` e `tests` della directory corrente: +Ora dovreste essere in grado di eseguire lo strumento nel terminale. Il primo comando controlla e il secondo corregge il codice nelle cartelle `src` e `tests` nella directory corrente: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Applica le modifiche .[#toc-committing-the-changes] +Descrizione dell'impegno .[#toc-commit-description] =================================================== -Dopo aver modificato il codice, è necessario eseguire il commit delle modifiche. Creare più commit, uno per ogni passo logico. Ogni commit deve essere utilizzabile così com'è, senza altri commit. Quindi, anche i test appropriati devono essere inclusi nello stesso commit. +In Nette, gli argomenti dei commit hanno il seguente formato: `Presenter: fixed AJAX detection [Closes #69]` -Verificare che il codice sia conforme alle regole: -- Il codice non genera errori -- Il codice non infrange alcun test. -- La modifica del codice è testata. -- Non state apportando modifiche inutili allo spazio bianco. +- area seguita da due punti +- scopo del commit al passato; se possibile, iniziare con parole come: added, fixed, refactored, changed, removed +- se il commit rompe la compatibilità all'indietro, aggiungere "BC break". +- qualsiasi collegamento al tracker dei problemi, come `(#123)` o `[Closes #69]` +- dopo l'oggetto, può esserci una riga vuota seguita da una descrizione più dettagliata, che includa, per esempio, collegamenti al forum -Il messaggio di commit dovrebbe seguire il formato `Latte: fixed multi template rendering [Closes # 69]` cioè -- un'area seguita da due punti -- lo scopo del commit in passato, se possibile, iniziare con "added.", "fixed.", "refactored.", changed, removed -- eventuale link al tracker dei problemi -- se il commit annulla la retrocompatibilità, aggiungere "BC break". -- può esserci una riga libera dopo l'oggetto e una descrizione più dettagliata, compresi i link al forum. +Descrizione della richiesta .[#toc-pull-request-description] +============================================================ -Richiedere i commit .[#toc-pull-requesting-the-commits] -======================================================= - -Se si è soddisfatti delle modifiche e dei commit apportati al codice, è necessario inviare i commit a GitHub. - -```shell -git push origin new_branch_name -``` +Quando si crea una richiesta di pull, l'interfaccia di GitHub consente di inserire un titolo e una descrizione. Fornire un titolo conciso e includere nella descrizione il maggior numero possibile di informazioni sulle ragioni della modifica. -Le modifiche sono presenti pubblicamente, tuttavia è necessario proporre le proprie modifiche per l'integrazione nel ramo master di Nette. Per farlo, bisogna fare [una richiesta di pull |https://help.github.com/articles/creating-a-pull-request]. -Ogni richiesta di pull ha un titolo e una descrizione. Si prega di fornire un titolo descrittivo. Spesso è simile al nome del ramo, ad esempio "Messa in sicurezza dei segnali contro gli attacchi CSRF". +Inoltre, specificare nell'intestazione se si tratta di una nuova funzionalità o di una correzione di un bug e se può causare problemi di retrocompatibilità (BC break). Se esiste un problema correlato, collegarlo ad esso in modo che venga chiuso dopo l'approvazione della richiesta di pull. -La descrizione della richiesta di pull dovrebbe contenere alcune informazioni più specifiche sulle modifiche al codice: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Modificare la tabella delle informazioni per adattarla alla richiesta di pull. Commenti a ogni voce dell'elenco: -- Dice se la richiesta di pull aggiunge una **caratteristica** o se è un **bugfix**. -- Riferisce eventualmente un **problema correlato**, che sarà chiuso dopo l'unione della richiesta di pull. -- Dice se la richiesta di pull necessita di **modifiche alla documentazione**, se sì, fornire i riferimenti alle richieste di pull appropriate. Non è necessario fornire immediatamente la modifica della documentazione, tuttavia la richiesta di pull non sarà unita se la modifica della documentazione è necessaria. La modifica della documentazione deve essere preparata per la documentazione in inglese, le altre mutazioni linguistiche sono opzionali. -- Dice se la richiesta di pull crea **una rottura del BC**. Si prega di considerare tutto ciò che modifica l'interfaccia pubblica come un'interruzione del BC. - -La tabella finale potrebbe essere simile a: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Rielaborazione delle modifiche .[#toc-reworking-your-changes] -============================================================= -È molto comune ricevere commenti alle proprie modifiche al codice. Cercate di seguire le modifiche proposte e rielaborate i vostri commit per farlo. È possibile fare il commit delle modifiche proposte come nuovi commit e poi schiacciarli su quelli precedenti. Vedere il capitolo sul [rebase interattivo |https://help.github.com/en/github/using-git/about-git-rebase] su GitHub. Dopo aver effettuato il rebase delle modifiche, spingere con forza le modifiche al proprio fork remoto: tutto verrà automaticamente propagato alla richiesta di pull. {{priority: -1}} diff --git a/contributing/it/coding-standard.texy b/contributing/it/coding-standard.texy index 974b96db96..ea44732fb7 100644 --- a/contributing/it/coding-standard.texy +++ b/contributing/it/coding-standard.texy @@ -38,7 +38,7 @@ Lo standard di codifica Nette corrisponde al PSR-12 (o stile di codifica PER), i - le funzioni freccia sono scritte senza uno spazio prima delle parentesi, cioè `fn($a) => $b` - non è richiesta una riga vuota tra i diversi tipi di dichiarazioni di importazione `use` -- il tipo di ritorno della funzione/metodo e la parentesi di apertura dovrebbero essere posti su righe separate per una migliore leggibilità: +- il tipo di ritorno di una funzione/metodo e la parentesi graffa di apertura sono sempre su righe separate: ```php public function find( @@ -50,6 +50,10 @@ Lo standard di codifica Nette corrisponde al PSR-12 (o stile di codifica PER), i } ``` +La parentesi graffa di apertura su una riga separata è importante per separare visivamente la firma della funzione/metodo dal corpo. Se la firma è su una riga, la separazione è netta (immagine a sinistra), mentre se è su più righe, nel PSR le firme e i corpi si fondono insieme (al centro), mentre nello standard Nette rimangono separati (a destra): + +[* new-line-after.webp *] + Blocchi di documentazione (phpDoc) .[#toc-documentation-blocks-phpdoc] ====================================================================== diff --git a/contributing/it/documentation.texy b/contributing/it/documentation.texy index 96c25d9e3c..d1ef26f5d3 100644 --- a/contributing/it/documentation.texy +++ b/contributing/it/documentation.texy @@ -1,53 +1,69 @@ -Scrivere la documentazione -************************** +Contribuire alla documentazione +******************************* .[perex] -Contribuire alla documentazione è uno dei tanti modi in cui si può aiutare Nette. È anche una delle attività più gratificanti, perché aiuta gli altri a comprendere il framework. +Contribuire alla documentazione è una delle attività più preziose, perché aiuta gli altri a comprendere il framework. Come scrivere? .[#toc-how-to-write] ----------------------------------- -La documentazione è destinata principalmente a persone che stanno iniziando a familiarizzare con l'argomento. Pertanto, deve soddisfare diversi punti importanti: +La documentazione è destinata principalmente a persone che non conoscono l'argomento. Pertanto, deve soddisfare diversi punti importanti: -- **Quando si scrive, iniziare con argomenti semplici e generali, per poi passare ad argomenti più avanzati alla fine. -- Fornire solo le informazioni che l'utente ha realmente bisogno di sapere sull'argomento. -- Verificate che le vostre informazioni siano effettivamente vere. Testate l'esempio prima di fornirlo. -- Siate concisi: tagliate a metà ciò che scrivete. E poi sentitevi liberi di rifarlo. -- Cercate di spiegare l'argomento nel modo migliore possibile. Ad esempio, provate prima a spiegare l'argomento a un collega. +- Iniziare con argomenti semplici e generali. Passare ad argomenti più avanzati alla fine +- Cercare di spiegare l'argomento nel modo più chiaro possibile. Ad esempio, provate prima a spiegare l'argomento a un collega. +- Fornite solo le informazioni che l'utente ha effettivamente bisogno di conoscere per un determinato argomento. +- Assicuratevi che le informazioni siano accurate. Testate ogni codice +- Siate concisi: tagliate a metà ciò che scrivete. E poi sentitevi liberi di rifarlo +- Usate con parsimonia l'evidenziazione, dai caratteri in grassetto ai riquadri, come ad esempio `.[note]` +- Seguire lo [standard di codifica |Coding Standard] nel codice -Tenete a mente questi punti durante tutto il processo di scrittura. La documentazione è scritta in [Texy! |https://texy.info], quindi imparate la sua [sintassi |syntax]. È possibile utilizzare l'editor di documentazione all'indirizzo https://editor.nette.org/ per visualizzare un'anteprima dell'articolo mentre lo si scrive. +Imparate anche la [sintassi |syntax]. Per avere un'anteprima dell'articolo durante la scrittura, si può usare l'[editor di anteprima |https://editor.nette.org/]. -Tra le regole generali di scrittura elencate in precedenza, si consiglia di attenersi alle seguenti: -- Il codice deve essere conforme allo [standard di codifica |Coding Standard]. -- Scrivere i nomi delle variabili, delle classi e dei metodi in inglese. -- Gli spazi dei nomi devono essere menzionati solo al primo colpo. -- Cercate di formattare il codice in modo da non visualizzare le barre di scorrimento. -- Risparmiate tutti i tipi di evidenziatori, dal grassetto alle `.[note]` caselle. -- Dalla documentazione, fare riferimento solo alla documentazione o a `www`. +Mutazioni linguistiche .[#toc-language-mutations] +------------------------------------------------- +L'inglese è la lingua principale, quindi le modifiche devono essere in inglese. Se l'inglese non è il vostro forte, usate [DeepL Translator |https://www.deepl.com/translator] e altri controlleranno il vostro testo. -Struttura della documentazione .[#toc-documentation-structure] --------------------------------------------------------------- +La traduzione in altre lingue avverrà automaticamente dopo l'approvazione e la messa a punto delle modifiche. + + +Modifiche banali .[#toc-trivial-edits] +-------------------------------------- + +Per contribuire alla documentazione, è necessario avere un account su [GitHub |https://github.com]. -La documentazione completa è ospitata su GitHub nel repository [nette/docs |https://github.com/nette/docs]. Questo repository è diviso in rami in base alla versione della documentazione, ad esempio il ramo `doc-3.1` contiene la documentazione per la versione 3.1. C'è poi il ramo `nette.org`, che contiene i contenuti degli altri sottodomini di nette.org. +Il modo più semplice per apportare piccole modifiche alla documentazione è utilizzare i link alla fine di ogni pagina: -Ogni ramo è poi suddiviso in diverse cartelle: +- *Mostra su GitHub* apre la versione sorgente della pagina su GitHub. Poi basta premere il pulsante `E` e si può iniziare a modificare (è necessario aver effettuato l'accesso a GitHub). +- *Apri anteprima* apre un editor in cui è possibile vedere immediatamente la forma visiva finale -* `cs` e `en`: contengono i file di documentazione per ogni versione linguistica -* `files`: immagini che possono essere incorporate nelle pagine di documentazione +Poiché l'[editor di anteprima |https://editor.nette.org/] non ha la possibilità di salvare le modifiche direttamente su GitHub, è necessario copiare il testo sorgente negli appunti (usando il pulsante *Copia negli appunti*) e poi incollarlo nell'editor su GitHub. +Sotto il campo di modifica c'è un modulo per l'invio. Qui, non dimenticate di riassumere brevemente e spiegare il motivo della vostra modifica. Dopo l'invio, viene creata una cosiddetta richiesta di pull (PR), che può essere ulteriormente modificata. -Il percorso di un file senza estensione corrisponde all'URL di una pagina della documentazione. Così, il file `en/quickstart/single-post.texy` avrà l'URL `doc.nette.org/en/quickstart/single-post`. +Modifiche più ampie .[#toc-larger-edits] +---------------------------------------- -Contribuire .[#toc-contributing] --------------------------------- +È più opportuno conoscere le basi del lavoro con il sistema di controllo di versione Git, piuttosto che affidarsi esclusivamente all'interfaccia di GitHub. Se non si ha familiarità con Git, si può fare riferimento alla [guida git - the simple |https://rogerdudler.github.io/git-guide/] e prendere in considerazione l'utilizzo di uno dei tanti [client grafici |https://git-scm.com/downloads/guis] disponibili. -Per contribuire alla documentazione, è necessario avere un account su [GitHub |https://github.com] e conoscere le basi di Git. Se non si ha familiarità con Git, si può consultare la guida rapida: [git - la guida semplice |https://rogerdudler.github.io/git-guide/], oppure utilizzare uno dei tanti strumenti grafici: [GIT - Client GUI |https://git-scm.com/downloads/guis]. +Modificare la documentazione nel modo seguente: -È possibile apportare semplici modifiche direttamente nell'interfaccia di GitHub. Tuttavia, è più comodo creare un fork del repository [nette/docs |https://github.com/nette/docs] e clonarlo sul proprio computer. Quindi apportare le modifiche nel ramo appropriato, eseguire il commit della modifica, eseguire il push sul proprio GitHub e inviare una richiesta di pull al repository `nette/docs` originale. +1) su GitHub, creare un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] del repository [nette/docs |https://github.com/nette/docs] +2) [clonare |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] questo repository sul proprio computer +3) quindi, apportare le modifiche nel [ramo appropriato |#Documentation Structure] +4) verificare la presenza di spazi extra nel testo utilizzando lo strumento [Code-Checker |code-checker:] +5) salvare (commit) le modifiche +6) se si è soddisfatti delle modifiche, inviarle su GitHub al proprio fork +7) da lì, inviarle al repository `nette/docs` creando una [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +È frequente ricevere commenti con suggerimenti. Tenere traccia delle modifiche proposte e incorporarle. Aggiungere le modifiche suggerite come nuovi commit e inviarle nuovamente a GitHub. Non creare mai una nuova richiesta di pull solo per modificare una richiesta esistente. + + +Struttura della documentazione .[#toc-documentation-structure] +-------------------------------------------------------------- -Prima di ogni richiesta di pull, è una buona idea eseguire [Code-Checker |code-checker:] per controllare gli spazi bianchi extra nel testo. +L'intera documentazione si trova su GitHub nel repository [nette/docs |https://github.com/nette/docs]. La versione attuale si trova nel ramo master, mentre le versioni precedenti si trovano nei rami `doc-3.x`, `doc-2.x`. -{{priority: -1}} +Il contenuto di ogni ramo è suddiviso in cartelle principali che rappresentano le singole aree della documentazione. Per esempio, `application/` corrisponde a https://doc.nette.org/en/application, `latte/` corrisponde a https://latte.nette.org, ecc. Ognuna di queste cartelle contiene sottocartelle che rappresentano le mutazioni linguistiche (`cs`, `en`, ...) e, opzionalmente, una sottocartella `files` con immagini che possono essere inserite nelle pagine della documentazione. diff --git a/contributing/pl/code.texy b/contributing/pl/code.texy index c8f9f08485..6b340f4f81 100644 --- a/contributing/pl/code.texy +++ b/contributing/pl/code.texy @@ -1,110 +1,118 @@ -Proponowanie zmiany w kodeksie -****************************** +Wkład do kodu +************* -Nette Framework używa Git i [GitHub |https://github.com/nette/nette] do utrzymania bazy kodu. Najlepszym sposobem na wniesienie wkładu w kod jest commitowanie swoich zmian do własnego forka, a następnie zgłoszenie pull request na GitHubie. Ten dokument podsumowuje główne kroki, które należy podjąć, aby skutecznie przyczynić się do rozwoju kodu. +.[perex] +Planujesz wnieść swój wkład do Nette Framework i potrzebujesz zapoznać się z zasadami i procedurami? Ten przewodnik dla początkujących poprowadzi Cię przez kroki, które pozwolą Ci efektywnie współtworzyć kod, pracować z repozytoriami i wprowadzać zmiany. -Przygotowanie środowiska .[#toc-preparing-environment] -====================================================== +Procedura .[#toc-procedure] +=========================== -Zacznij od [rozwidlenia |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette na GitHubie |https://github.com/nette]. Starannie [skonfiguruj |https://help.github.com/en/github/getting-started-with-github/set-up-git] swoje lokalne środowisko Git, skonfiguruj swoją nazwę użytkownika i e-mail, te poświadczenia będą identyfikować twoje zmiany w historii Nette Framework. +Aby wnieść wkład do kodu, konieczne jest posiadanie konta na [GitHubie |https://github.com] i znajomość podstaw pracy z systemem kontroli wersji Git. Jeśli nie jesteś zaznajomiony z Gitem, możesz sprawdzić [git - prosty przewodnik |https://rogerdudler.github.io/git-guide/] i rozważyć użycie jednego z wielu [graficznych klientów |https://git-scm.com/downloads/guis]. -Praca nad twoją łatką .[#toc-working-on-your-patch] -=================================================== +Przygotowanie środowiska i repozytorium .[#toc-preparing-the-environment-and-repository] +---------------------------------------------------------------------------------------- + +1) Na GitHubie utwórz [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [repozytorium pakietów |www:packages], które zamierzasz zmodyfikować +2) [Sklonuj |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] to repozytorium na swój komputer +3) Zainstaluj zależności, w tym [Nette Tester |tester:], używając polecenia `composer install` +4) Sprawdź, czy testy działają, uruchamiając `composer tester` +5) Utwórz [nową gałąź |#New Branch] opartą na najnowszej wydanej wersji + + +Wdrażanie własnych zmian .[#toc-implementing-your-own-changes] +-------------------------------------------------------------- + +Teraz możesz wprowadzić własne poprawki do kodu: + +1) Zaimplementuj pożądane zmiany i nie zapomnij o testach +2) Upewnij się, że testy przebiegają pomyślnie używając `composer tester` +3) Sprawdź, czy kod spełnia [standardy kodowania |#coding standards] +4) Zapisz (commit) zmiany z opisem w [tym formacie |#Commit Description] + +Możesz stworzyć wiele commitów, po jednym dla każdego logicznego kroku. Każdy commit powinien być znaczący sam w sobie. -Zanim zaczniesz pracować nad swoim patchem, utwórz nową gałąź dla swoich zmian. -```shell -git checkout -b new_branch_name -``` -Możesz pracować nad swoją zmianą kodu. +Przesyłanie zmian .[#toc-submitting-changes] +-------------------------------------------- -Jeśli to możliwe, wprowadź zmiany z ostatniej wydanej wersji. +Gdy zmiany są zadowalające, można je przesłać: +1) Przesuń zmiany na GitHub do swojego forka +2) Stamtąd prześlij je do repozytorium Nette, tworząc [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) Podaj [wystarczającą ilość informacji |#pull request description] w opisie -Testowanie zmian .[#toc-testing-your-changes] -============================================= -Musisz zainstalować Nette Tester. Najprostszym sposobem jest wywołanie `composer install` w korzeniu repozytorium. Teraz powinieneś być w stanie uruchomić testy z `./vendor/bin/tester` w terminalu. +Uwzględnianie informacji zwrotnych .[#toc-incorporating-feedback] +----------------------------------------------------------------- -Niektóre testy mogą się nie powieść z powodu braku php.ini. Dlatego należy wywołać runner z parametrem -c i podać ścieżkę do php.ini, na przykład `./vendor/bin/tester -c ./tests/php.ini`. +Twój commit jest teraz widoczny dla innych. Powszechne jest otrzymywanie komentarzy z sugestiami: -Po uruchomieniu testów, możesz zaimplementować własne lub zmienić awarie, aby dopasować je do nowego zachowania. Przeczytaj więcej o testowaniu za pomocą Nette Tester na [stronie dokumentacji |tester:]. +1) Śledzić proponowane zmiany +2) Włącz je jako nowe commity lub [połącz z poprzednimi |https://help.github.com/en/github/using-git/about-git-rebase] +3) Prześlij ponownie commit na GitHub, a automatycznie pojawi się on w żądaniu ściągnięcia. + +Nigdy nie twórz nowego pull requesta, aby zmodyfikować istniejący. + + +Dokumentacja .[#toc-documentation] +---------------------------------- + +Jeśli zmieniłeś funkcjonalność lub dodałeś nową, nie zapomnij [dodać jej |documentation] również [do dokumentacji |documentation]. + + +Nowa gałąź .[#toc-new-branch] +============================= + +Jeśli to możliwe, dokonuj zmian względem najnowszej wydanej wersji, czyli ostatniego tagu w gałęzi. Dla tagu v3.2.1 utwórz gałąź używając tego polecenia: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Standardy kodowania .[#toc-coding-standards] ============================================ -Twój kod musi spełniać [standardy kodowania |coding standard] używane w Nette Framework. Jest to łatwe, ponieważ istnieje automatyczny checker i fixer. Można go zainstalować poprzez Composer do wybranego przez siebie globalnego katalogu: +Twój kod musi spełniać [standardy kodowania |coding standard] stosowane w Nette Framework. Dostępne jest automatyczne narzędzie do sprawdzania i poprawiania kodu. Możesz je zainstalować **globalnie** poprzez Composera do wybranego przez siebie folderu: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Teraz powinieneś być w stanie uruchomić narzędzie w terminalu. Na przykład, to polecenie sprawdza i naprawia kod w folderach `src` i `tests` w bieżącym katalogu: +Teraz powinieneś być w stanie uruchomić narzędzie w terminalu. Pierwsze polecenie sprawdza, a drugie naprawia kod w folderach `src` i `tests` w bieżącym katalogu: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Zobowiązanie do zmiany .[#toc-committing-the-changes] -===================================================== - -Po zmianie kodu, musisz zatwierdzić swoje zmiany. Utwórz więcej commitów, po jednym dla każdego logicznego kroku. Każdy commit powinien nadawać się do użytku jako taki - bez innych commitów. Tak więc, odpowiednie testy powinny być również zawarte w tym samym commicie. +Opis zobowiązania .[#toc-commit-description] +============================================ -Proszę, sprawdź dwukrotnie czy twój kod pasuje do zasad: -- Kod nie generuje żadnych błędów -- Twój kod nie łamie żadnych testów. -- Twoja zmiana kodu jest przetestowana. -- Nie popełniasz bezużytecznych zmian w białej przestrzeni. +W Nette, tematy commitów mają następujący format: `Presenter: fixed AJAX detection [Closes #69]` -Wiadomość o popełnieniu zmiany powinna być zgodna z formatem `Latte: fixed multi template rendering [Closes # 69]` tj: - obszar, po którym następuje dwukropek -- cel commitu w przeszłości, jeśli to możliwe, zacznij od "added.", "fixed.", "refactored.", changed, removed -- ewentualny link do issue tracker -- jeśli commit anuluje kompatybilność wsteczną, dodaj "BC break" -- może być jedna wolna linia po temacie i bardziej szczegółowy opis, w tym linki do forum. - +- cel commitu w czasie przeszłym; jeśli to możliwe, zacznij od słów takich jak: added, fixed, refactored, changed, removed +- jeśli commit łamie wsteczną kompatybilność, dodaj "BC break" +- wszelkie połączenia z issue trackerem, takie jak `(#123)` lub `[Closes #69]` +- po temacie może być jedna pusta linia, po której następuje bardziej szczegółowy opis, zawierający np. linki do forum -Wysyłanie żądania wyciągnięcia .[#toc-pull-requesting-the-commits] -================================================================== -Jeśli jesteś zadowolony ze swoich zmian w kodzie i commitów, musisz wypchnąć swoje commity na GitHub. +Opis Pull Request .[#toc-pull-request-description] +================================================== -```shell -git push origin new_branch_name -``` +Podczas tworzenia pull request, interfejs GitHub pozwoli Ci wprowadzić tytuł i opis. Podaj zwięzły tytuł i zawrzyj jak najwięcej informacji w opisie o powodach zmiany. -Zmiany są prezentowane publicznie, jednak musisz zaproponować swoje zmiany do integracji w gałęzi głównej Nette. Aby to zrobić, należy [złożyć pull request |https://help.github.com/articles/creating-a-pull-request]. -Każdy pull request ma tytuł i opis. Proszę podać jakiś opisujący tytuł. Często jest on podobny do nazwy gałęzi, na przykład "Securing signals against CSRF attack". +Określ również w nagłówku, czy jest to nowa funkcja czy poprawka błędu i czy może spowodować problemy z kompatybilnością wsteczną (BC break). Jeśli istnieje powiązany problem, umieść do niego link, aby został zamknięty po zatwierdzeniu pull requesta. -Opis pull request powinien zawierać jakieś bardziej szczegółowe informacje na temat zmian w Twoim kodzie: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Proszę zmienić tabelę informacji, aby dopasować ją do swojego pull requesta. Komentarze do każdej pozycji listy: -- Mówi czy pull request dodaje **feature** czy jest to **bugfix**. -- Odnosi się ewentualnie do **powiązanego problemu**, który zostanie zamknięty po połączeniu pull requesta. -- Mówi czy pull request wymaga **zmian w dokumentacji**, jeśli tak, podaj referencje do odpowiednich pull requestów. Nie musisz dostarczać zmian w dokumentacji od razu, jednak pull request nie zostanie scalony, jeśli zmiany w dokumentacji są potrzebne. Zmiana dokumentacji musi być przygotowana dla dokumentacji angielskiej, inne mutacje językowe są opcjonalne. -- Mówi, jeśli żądanie pociągnięcia tworzy **przerwę BC**. Proszę, rozważ wszystko, co zmienia publiczny interfejs jako przerwę BC. - -Ostateczna tabela może wyglądać jak: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Przerabianie zmian .[#toc-reworking-your-changes] -================================================= -Otrzymywanie komentarzy do zmian w kodzie jest naprawdę częste. Proszę, spróbuj zastosować się do proponowanych zmian i przerobić swoje commit'y, aby to zrobić. Możesz popełnić proponowane zmiany jako nowe commity, a następnie zgnieść je do poprzednich. Zobacz [interaktywny |https://help.github.com/en/github/using-git/about-git-rebase] rozdział [rebase |https://help.github.com/en/github/using-git/about-git-rebase] na GitHubie. Po rebasingowaniu zmian, wymuś zmiany do swojego zdalnego widelca, wszystko zostanie automatycznie propagowane do pull request. {{priority: -1}} diff --git a/contributing/pl/coding-standard.texy b/contributing/pl/coding-standard.texy index bdf2a84a68..00a618769d 100644 --- a/contributing/pl/coding-standard.texy +++ b/contributing/pl/coding-standard.texy @@ -38,7 +38,7 @@ Standard Kodowania Nette odpowiada PSR-12 (czyli stylowi kodowania PER), w niekt - Funkcje strzałkowe zapisujemy bez spacji przed nawiasem, tzn. `fn($a) => $b` - nie jest wymagany pusty wiersz pomiędzy różnymi typami `use` deklaracji importowych -- typ zwrotny funkcji/metody oraz wiodący nawias złożony powinny być umieszczone w oddzielnych liniach dla lepszej czytelności: +- typ zwracany funkcji/metody i otwierający nawias klamrowy są zawsze w oddzielnych wierszach: ```php public function find( @@ -50,6 +50,10 @@ Standard Kodowania Nette odpowiada PSR-12 (czyli stylowi kodowania PER), w niekt } ``` +Otwierający nawias klamrowy w osobnej linii jest ważny dla wizualnego oddzielenia sygnatury funkcji/metody od treści. Jeśli sygnatura znajduje się w jednej linii, separacja jest wyraźna (obraz po lewej), jeśli znajduje się w wielu liniach, w PSR sygnatury i ciała zlewają się ze sobą (w środku), podczas gdy w standardzie Nette pozostają oddzielone (po prawej): + +[* new-line-after.webp *] + Bloki dokumentacji (phpDoc) .[#toc-documentation-blocks-phpdoc] =============================================================== diff --git a/contributing/pl/documentation.texy b/contributing/pl/documentation.texy index 219d2598dd..bc8e0c101a 100644 --- a/contributing/pl/documentation.texy +++ b/contributing/pl/documentation.texy @@ -1,55 +1,69 @@ -Pisanie dokumentacji +Wkład w dokumentację ******************** .[perex] -Wkład w dokumentację jest jednym z wielu sposobów, w jaki możesz pomóc Nette. Jest to również jedno z najbardziej satysfakcjonujących zajęć, ponieważ pomagasz innym zrozumieć ramy. +Wkład w dokumentację jest jednym z najbardziej wartościowych działań, ponieważ pomaga innym zrozumieć framework. Jak pisać? .[#toc-how-to-write] ------------------------------- -Dokumentacja przeznaczona jest głównie dla osób, które dopiero zapoznają się z tematem. Dlatego powinien on spełniać kilka ważnych punktów: +Dokumentacja jest przeznaczona przede wszystkim dla osób, które są nowe w temacie. Dlatego powinna spełniać kilka ważnych punktów: -- **Pisząc, zacznij od tego, co proste i ogólne, a na końcu przejdź do bardziej zaawansowanych tematów**. -- Dostarczaj tylko te informacje, które użytkownik naprawdę potrzebuje wiedzieć na dany temat. -- Zweryfikuj, czy Twoje informacje są rzeczywiście prawdziwe. Przetestuj najpierw przykład przed podaniem kodu. -- Bądź zwięzły - skróć to, co piszesz o połowę. A potem nie krępuj się zrobić tego ponownie. -- Postaraj się wyjaśnić sprawę najlepiej jak potrafisz. Na przykład spróbuj najpierw wyjaśnić temat koledze. +- Zacznij od prostych i ogólnych tematów. Przejdź do bardziej zaawansowanych tematów na końcu +- Staraj się wyjaśnić temat tak jasno, jak to możliwe. Na przykład, spróbuj najpierw wytłumaczyć temat koledze. +- Podawaj tylko te informacje, które użytkownik rzeczywiście musi znać dla danego tematu +- Upewnij się, że informacje są dokładne. Testuj każdy kod +- Bądź zwięzły - skróć to, co piszesz o połowę. A potem nie krępuj się zrobić tego ponownie +- Oszczędnie używaj wyróżnień, od pogrubionych czcionek po ramki typu `.[note]` +- Stosuj się do [Standardów Kodowania |Coding Standard] w kodzie -Pamiętajcie o tych punktach w trakcie procesu pisania. Więcej wskazówek znajdziesz w artykule [Pisanie dla |https://www.lupa.cz/clanky/piseme-pro-web/] sieci. Dokumentacja jest napisana w [Texy! |https://texy.info], więc naucz się jej [składni |syntax]. Możesz użyć edytora dokumentacji na stronie https://editor.nette.org/, aby wyświetlić podgląd artykułu w trakcie jego pisania. +Ucz się również [składni |syntax]. Aby uzyskać podgląd artykułu podczas pisania, możesz użyć [edytora |https://editor.nette.org/] podglądu. -Oprócz powyższych punktów prosimy również o przestrzeganie następujących wskazówek: -- Angielski jest językiem podstawowym, więc Twoje zmiany powinny być w obu językach. Jeśli język angielski nie jest Twoją mocną stroną, skorzystaj z [DeepL Translator |https://www.deepl.com/translator], a inni dokonają korekty Twojego tekstu. -- W tekście dokumentacji mamy tendencję do "machania" i bycia grzecznym. -- W przykładach postępuj zgodnie ze [Standardem Kodowania |Coding Standard]. -- Pisz nazwy zmiennych, klas i metod w języku angielskim. -- Przestrzenie nazw powinny być podawane przy pierwszej wzmiance. -- Spróbuj sformatować swój kod, aby paski przewijania nie pojawiały się. -- Daruj sobie wszelkiego rodzaju zakreślacze, od pogrubień po obramowania. `.[note]`. -- Z dokumentacji należy odnosić się tylko do dokumentacji lub `www`. +Mutacje językowe .[#toc-language-mutations] +------------------------------------------- +Angielski jest językiem podstawowym, więc twoje zmiany powinny być w języku angielskim. Jeśli angielski nie jest twoją mocną stroną, użyj [DeepL Translator |https://www.deepl.com/translator] i inni sprawdzą twój tekst. -Struktura dokumentacji .[#toc-documentation-structure] ------------------------------------------------------- +Tłumaczenie na inne języki zostanie wykonane automatycznie po zatwierdzeniu i dopracowaniu twojej edycji. + + +Trywialne edycje .[#toc-trivial-edits] +-------------------------------------- + +Aby wnieść swój wkład w dokumentację, musisz mieć konto na [GitHubie |https://github.com]. -Cała dokumentacja jest hostowana na GitHubie w repozytorium [nette/docs |https://github.com/nette/docs]. To repozytorium jest podzielone na gałęzie w zależności od wersji dokumentacji, na przykład gałąź `doc-3.1` zawiera dokumentację dla wersji 3.1. Następnie jest gałąź `nette.org`, która zawiera zawartość pozostałych subdomen nette.org. +Najłatwiejszym sposobem na dokonanie niewielkiej zmiany w dokumentacji jest użycie linków na końcu każdej strony: -Każdy oddział jest następnie podzielony na kilka folderów: +- *Show on GitHub* otwiera wersję źródłową strony na GitHubie. Następnie wystarczy nacisnąć przycisk `E` i można rozpocząć edycję (trzeba być zalogowanym na GitHubie) +- *Open preview* otwiera edytor, w którym można od razu zobaczyć ostateczną formę wizualną -* `cs` i `en`: zawiera pliki dokumentacji dla każdej wersji językowej. -* `files`: obrazy, które mogą być osadzone na stronach dokumentacji +Ponieważ [edytor |https://editor.nette.org/] podglądu nie ma możliwości zapisywania zmian bezpośrednio na GitHubie, należy skopiować tekst źródłowy do schowka (za pomocą przycisku *Copy to clipboard*), a następnie wkleić go do edytora na GitHubie. +Poniżej pola edycyjnego znajduje się formularz do przesłania. Tutaj nie zapomnij krótko podsumować i wyjaśnić powodu swojej edycji. Po przesłaniu tworzony jest tzw. pull request (PR), który można dalej edytować. -Ścieżka do pliku bez rozszerzenia odpowiada adresowi URL strony w dokumentacji. W ten sposób plik `cs/quickstart/single-post.texy` będzie miał adres `doc.nette.org/en/quickstart/single-post`. +Większe edycje .[#toc-larger-edits] +----------------------------------- -Wkład w dokumentację .[#toc-contributing] ------------------------------------------ +Bardziej właściwe jest zapoznanie się z podstawami pracy z systemem kontroli wersji Git, niż poleganie wyłącznie na interfejsie GitHub. Jeśli nie jesteś zaznajomiony z Gitem, możesz zapoznać się z [git - prostym przewodnikiem |https://rogerdudler.github.io/git-guide/] i rozważyć użycie jednego z wielu dostępnych [klientów graficznych |https://git-scm.com/downloads/guis]. -Aby wnieść wkład w dokumentację, musisz mieć konto na [GitHubie |https://github.com] i podstawowe zrozumienie systemu wersjonowania Git. Jeśli nie jesteś zaznajomiony z git, możesz sprawdzić ten szybki przewodnik: [git - prosty przewodnik |https://rogerdudler.github.io/git-guide/], lub użyć jednego z wielu graficznych narzędzi: [GIT - klienci GUI |https://git-scm.com/downloads/guis]. +Edytuj dokumentację w następujący sposób: -Możesz wprowadzić proste zmiany bezpośrednio w interfejsie GitHub. Wygodniej jest jednak stworzyć fork repozytorium [nette/docs |https://github.com/nette/docs] i sklonować go na swój komputer. Następnie wprowadź zmiany w odpowiedniej gałęzi, popełnij zmianę, popchnij do swojego repozytorium GitHub i wyślij pull request do oryginalnego repozytorium `nette/docs`. Zauważ, że głównym językiem dokumentacji jest angielski, więc dokonaj zmian w obu językach. +1) na GitHubie utwórz [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] repozytorium [nette/docs |https://github.com/nette/docs] +2) [sklonuj |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] to repozytorium na swój komputer +3) następnie wprowadź zmiany w [odpowiedniej gałęzi |#Documentation Structure] +4) sprawdzić, czy w tekście nie ma dodatkowych spacji za pomocą narzędzia [Code-Checker |code-checker:] +5) zapisz (commit) zmiany +6) jeśli jesteś zadowolony ze zmian, wepchnij je na GitHub do swojego forka +7) stamtąd prześlij je do repozytorium `nette/docs` tworząc [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +Powszechne jest otrzymywanie komentarzy z sugestiami. Śledź proponowane zmiany i włączaj je. Dodaj sugerowane zmiany jako nowe commity i wyślij je ponownie do GitHub. Nigdy nie twórz nowego pull requesta tylko po to, by zmodyfikować istniejący. + + +Struktura dokumentacji .[#toc-documentation-structure] +------------------------------------------------------ -Przed każdym pull requestem warto uruchomić [Code-Checker |code-checker:], aby sprawdzić czy w tekście nie ma dodatkowych spacji. +Cała dokumentacja znajduje się na GitHubie w repozytorium [nette/docs |https://github.com/nette/docs]. Aktualna wersja znajduje się w gałęzi master, natomiast starsze wersje znajdują się w gałęziach takich jak `doc-3.x`, `doc-2.x`. -{{priority: -1}} +Zawartość każdej gałęzi podzielona jest na główne foldery reprezentujące poszczególne obszary dokumentacji. Na przykład `application/` odpowiada https://doc.nette.org/en/application, `latte/` odpowiada https://latte.nette.org, itd. Każdy z tych folderów zawiera podfoldery reprezentujące mutacje językowe (`cs`, `en`, ...) oraz opcjonalnie podfolder `files` z obrazkami, które można wstawić na strony w dokumentacji. diff --git a/contributing/pt/code.texy b/contributing/pt/code.texy index 48a8f12bb7..72f3a22d40 100644 --- a/contributing/pt/code.texy +++ b/contributing/pt/code.texy @@ -1,110 +1,118 @@ -Propondo uma mudança no código -****************************** +Contribuindo para o Código +************************** -Nette Framework utiliza Git e [GitHub |https://github.com/nette/nette] para manter a base de códigos. A melhor maneira de contribuir é comprometer suas mudanças em seu próprio garfo e depois fazer um pedido de puxar em GitHub. Este documento resume os principais passos para contribuir com sucesso. +.[perex] +Você está planejando contribuir para a Estrutura Nette e precisa se familiarizar com as regras e procedimentos? Este guia para iniciantes irá guiá-lo através dos passos para contribuir efetivamente com o código, trabalhar com os repositórios e implementar mudanças. -Preparando o ambiente .[#toc-preparing-environment] -=================================================== +Procedimento .[#toc-procedure] +============================== -Comece com o [forking |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette no GitHub |https://github.com/nette]. [Configure |https://help.github.com/en/github/getting-started-with-github/set-up-git] cuidadosamente seu ambiente Git local, configure seu nome de usuário e e-mail, estas credenciais identificarão suas mudanças no histórico do Nette Framework. +Para contribuir com o código, é essencial ter uma conta no [GitHub |https://github.com] e estar familiarizado com os fundamentos do trabalho com o sistema de controle de versões Git. Se você não está familiarizado com Git, você pode verificar o [git - o guia simples |https://rogerdudler.github.io/git-guide/] e considerar o uso de um dos muitos [clientes gráficos |https://git-scm.com/downloads/guis]. -Trabalhando em seu Patch .[#toc-working-on-your-patch] -====================================================== +Preparação do Meio Ambiente e Repositório .[#toc-preparing-the-environment-and-repository] +------------------------------------------------------------------------------------------ -Antes de começar a trabalhar em seu patch, crie uma nova filial para suas mudanças. -```shell -git checkout -b new_branch_name -``` +1) No GitHub, crie um [garfo |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] do [repositório de pacotes |www:packages] que você pretende modificar +2) [Clonar |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositório em seu computador +3) Instale as dependências, incluindo o [Nette Tester |tester:], usando o comando `composer install` +4) Verificar se os testes estão funcionando `composer tester` +5) Criar uma [nova filial |#New Branch] com base na última versão lançada -Você pode trabalhar na mudança do seu código. -Se possível, fazer alterações a partir da última versão lançada. +Implementando suas próprias mudanças .[#toc-implementing-your-own-changes] +-------------------------------------------------------------------------- +Agora você pode fazer seus próprios ajustes de código: -Testando suas mudanças .[#toc-testing-your-changes] -=================================================== +1) Implementar as mudanças desejadas e não esquecer os testes +2) Certifique-se de que os testes sejam executados com sucesso usando `composer tester` +3) Verificar se o código atende às [normas de codificação |#coding standards] +4) Salvar (comprometer) as mudanças com uma descrição [neste formato |#Commit Description] + +Você pode criar vários compromissos, um para cada passo lógico. Cada compromisso deve ser significativo por si só. + + +Submetendo mudanças .[#toc-submitting-changes] +---------------------------------------------- + +Uma vez satisfeitos com as mudanças, você pode apresentá-las: + +1) Empurre as mudanças no GitHub para o seu garfo +2) A partir daí, submetê-los ao repositório Nette, criando uma [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) Forneça [informações suficientes |#pull request description] na descrição + + +Incorporando Feedback .[#toc-incorporating-feedback] +---------------------------------------------------- + +Seus compromissos agora são visíveis para os outros. É comum receber comentários com sugestões: + +1) Acompanhe as mudanças propostas +2) Incorporá-los como novos compromissos ou [fundi-los com os anteriores |https://help.github.com/en/github/using-git/about-git-rebase] +3) Reenviar os compromissos ao GitHub, e eles aparecerão automaticamente no pedido de puxar + +Nunca criar um novo pedido de puxar para modificar um já existente. + + +Documentação .[#toc-documentation] +---------------------------------- -Você precisa instalar o Nette Tester. A maneira mais fácil é ligar para `composer install` na raiz do repositório. Agora você deve ser capaz de executar testes com `./vendor/bin/tester` no terminal. +Se você mudou de funcionalidade ou adicionou uma nova, não se esqueça de [adicioná-la |documentation] também [à documentação |documentation]. -Alguns testes podem falhar devido à falta do php.ini. Portanto, você deve chamar o corredor com o parâmetro -c e especificar o caminho para o php.ini, por exemplo `./vendor/bin/tester -c ./tests/php.ini`. -Depois que você for capaz de executar os testes, você pode implementar seus próprios testes ou mudar a falha para se adequar ao novo comportamento. Leia mais sobre os testes com o Nette Tester na [página de documentação |tester:]. +Nova filial .[#toc-new-branch] +============================== + +Se possível, faça alterações em relação à última versão lançada, ou seja, a última tag no ramo. Para a tag v3.2.1, criar um ramo usando este comando: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Normas de Codificação .[#toc-coding-standards] ============================================== -Seu código deve seguir o [padrão de codificação |coding standard] utilizado no Nette Framework. É fácil porque há um verificador e um reparador automáticos. Ele pode ser instalado via Composer em seu diretório global escolhido: +Seu código deve atender ao [padrão de codificação |coding standard] utilizado no Nette Framework. Há uma ferramenta automática disponível para verificar e fixar o código. Você pode instalá-lo **globalmente** através do Composer em uma pasta de sua escolha: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Agora você deve ser capaz de executar a ferramenta no terminal. Por exemplo, este comando verifica e corrige o código nas pastas `src` e `tests` no diretório atual: +Agora você deve ser capaz de executar a ferramenta no terminal. O primeiro comando verifica e o segundo conserta o código nas pastas `src` e `tests` no diretório atual: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Cometendo as mudanças .[#toc-committing-the-changes] -==================================================== +Descrição do compromisso .[#toc-commit-description] +=================================================== -Depois de ter mudado o código, você tem que comprometer suas mudanças. Crie mais compromissos, um para cada passo lógico. Cada compromisso deveria ter sido utilizável como está - sem outros compromissos. Portanto, os testes apropriados também devem ser incluídos no mesmo commit. +Em Nette, os assuntos de compromisso têm o seguinte formato: `Presenter: fixed AJAX detection [Closes #69]` -Por favor, verifique novamente se seu código se encaixa nas regras: -- O código não gera nenhum erro -- Seu código não quebra nenhum teste. -- Sua mudança de código é testada. -- Você não está cometendo mudanças inúteis no espaço branco. +- área seguida por um cólon +- objetivo do compromisso no passado; se possível, comece com palavras como: added, fixed, refactored, changed, removed +- se o compromisso quebra a compatibilidade para trás, adicionar "BC break" +- qualquer conexão com o rastreador de problemas, como `(#123)` ou `[Closes #69]` +- após o assunto, pode haver uma linha em branco seguida por uma descrição mais detalhada, incluindo, por exemplo, links para o fórum -A mensagem de compromisso deve seguir o formato `Latte: fixed multi template rendering [Closes # 69]` ou seja -- uma área seguida por um cólon -- o objetivo do compromisso no passado, se possível, começar com "adicionado", "fixo", "refatorado", alterado, removido -- eventual link para o rastreador de emissões -- se a compatibilidade retroativa for cancelada, adicionar "BC break". -- pode haver uma linha gratuita após o assunto e uma descrição mais detalhada incluindo links para o fórum. +Descrição do Pedido de Puxar .[#toc-pull-request-description] +============================================================= -Puxar-Requisitar os Compromissos .[#toc-pull-requesting-the-commits] -==================================================================== +Ao criar um pedido de puxar, a interface GitHub permitirá que você insira um título e uma descrição. Forneça um título conciso e inclua o máximo de informações possíveis na descrição sobre os motivos de sua mudança. -Se você está satisfeito com suas mudanças de código e se compromete, você tem que empurrar seu compromisso com o GitHub. +Além disso, especificar no cabeçalho se é uma nova característica ou uma correção de bug e se pode causar problemas de retrocompatibilidade (BC break). Se houver um problema relacionado, estabeleça um link com ele para que seja fechado após a aprovação do pedido de puxar. -```shell -git push origin new_branch_name ``` - -As mudanças estão presentes publicamente, no entanto, você tem que propor suas mudanças para integração no ramo principal da Nette. Para fazer isso, [faça um pedido de puxar |https://help.github.com/articles/creating-a-pull-request]. -Cada pedido de puxar tem um título e uma descrição. Por favor, forneça algum título descritivo. Muitas vezes é semelhante ao nome da filial, por exemplo "Assegurando sinais contra ataque do CSRF". - -A descrição do pedido deve conter algumas informações mais específicas sobre suas mudanças de código: -``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Favor alterar a tabela de informações para se adequar ao seu pedido de puxar. Comentários a cada item da lista: -- Diz se a solicitação de puxar adiciona ** recurso*** ou é um **bugfix***. -- Referências eventualmente** relacionadas ao ** problema**, que serão fechadas após a fusão da solicitação de puxar. -- Diz se a solicitação pull precisar da **documentação mudar**, se sim, fornecer referências para as solicitações pull apropriadas. Não é necessário fornecer a mudança de documentação imediatamente, entretanto, a solicitação pull não será fundida se for necessária a mudança de documentação. A alteração da documentação deve ser preparada para a documentação em inglês, as mutações em outros idiomas são opcionais. -- Diz que se a solicitação pull criar **a BC break***. Por favor, considere tudo o que muda a interface pública como um intervalo BC. - -A mesa final poderia ser parecida: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Reestruturando suas mudanças .[#toc-reworking-your-changes] -=========================================================== -É realmente comum receber comentários para sua mudança de código. Por favor, tente seguir as mudanças propostas e retrabalhe seus compromissos para fazer isso. Você pode comprometer-se com as mudanças propostas como novos compromissos e depois esmagá-las em relação aos anteriores. Veja o capítulo sobre o GitHub no [Rebase Interactive |https://help.github.com/en/github/using-git/about-git-rebase]. Depois de rebasear suas mudanças, force-push suas mudanças para seu garfo remoto, tudo será automaticamente propagado para o pedido de puxar. {{priority: -1}} diff --git a/contributing/pt/coding-standard.texy b/contributing/pt/coding-standard.texy index 0a3eaec9d3..a7c89117b3 100644 --- a/contributing/pt/coding-standard.texy +++ b/contributing/pt/coding-standard.texy @@ -38,7 +38,7 @@ O Nette Coding Standard corresponde ao PSR-12 (ou PER Coding Style), em alguns p - As funções da seta são escritas sem um espaço antes do parêntese, ou seja `fn($a) => $b` - não é necessária nenhuma linha vazia entre os diferentes tipos de declarações de importação `use` -- o tipo de retorno da função/método e o parêntese de abertura devem ser colocados em linhas separadas para uma melhor legibilidade: +- o tipo de retorno de uma função/método e o colchete de abertura estão sempre em linhas separadas: ```php public function find( @@ -50,6 +50,10 @@ O Nette Coding Standard corresponde ao PSR-12 (ou PER Coding Style), em alguns p } ``` +A chave de abertura em uma linha separada é importante para separar visualmente a assinatura da função/método do corpo. Se a assinatura estiver em uma linha, a separação é clara (imagem à esquerda); se estiver em várias linhas, no PSR as assinaturas e os corpos se misturam (no meio), enquanto no padrão Nette eles permanecem separados (à direita): + +[* new-line-after.webp *] + Blocos de Documentação (phpDoc) .[#toc-documentation-blocks-phpdoc] =================================================================== diff --git a/contributing/pt/documentation.texy b/contributing/pt/documentation.texy index de3552924c..8a1b434f8c 100644 --- a/contributing/pt/documentation.texy +++ b/contributing/pt/documentation.texy @@ -1,53 +1,69 @@ -Escrevendo a documentação -************************* +Contribuição para a documentação +******************************** .[perex] -Contribuir com a documentação é uma das muitas maneiras pelas quais você pode ajudar a Nette. É também uma das atividades mais gratificantes, pois você ajuda os outros a entender a estrutura. +Contribuir com a documentação é uma das atividades mais valiosas, pois ajuda os outros a entender a estrutura. Como Escrever? .[#toc-how-to-write] ----------------------------------- -A documentação é destinada principalmente às pessoas que estão apenas se familiarizando com o tema. Portanto, ela deve atender a vários pontos importantes: +A documentação é destinada principalmente às pessoas que são novatas no assunto. Portanto, ela deve atender a vários pontos importantes: -- ** Ao escrever, comece com o simples e geral, e passe para tópicos mais avançados no final.** -- Fornecer apenas as informações que o usuário realmente precisa saber sobre o tópico. -- Verifique se suas informações são realmente verdadeiras. Teste o exemplo primeiro antes de dar o exemplo. -- Seja conciso - corte o que você escreve pela metade. E depois sinta-se à vontade para fazer isso novamente. -- Tente explicar o assunto da melhor forma possível. Por exemplo, tente explicar o assunto a um colega primeiro. +- Comece com temas simples e gerais. Passar para tópicos mais avançados no final +- Tente explicar o tópico da maneira mais clara possível. Por exemplo, tente explicar o tópico a um colega primeiro +- Fornecer apenas informações que o usuário realmente precisa saber para um determinado tópico +- Certifique-se de que suas informações sejam precisas. Teste cada código +- Seja conciso - corte o que você escreve pela metade. E depois sinta-se livre para fazê-lo novamente +- Use o destaque com moderação, desde fontes ousadas até molduras como `.[note]` +- Siga a [Norma de Codificação |Coding Standard] no código -Mantenha estes pontos em mente durante todo o processo de escrita. A documentação é escrita em [Texy! |https://texy.info], portanto, aprenda sua [sintaxe |syntax]. Você pode usar o editor da documentação em https://editor.nette.org/ para visualizar o artigo enquanto o escreve. +Além disso, aprenda a [sintaxe |syntax]. Para uma prévia do artigo durante a redação, você pode usar o [editor de prévia |https://editor.nette.org/]. -Entre as regras gerais de redação listadas anteriormente, por favor, mantenha as seguintes: -- Seu código deve estar em conformidade com a [Norma de Codificação |Coding Standard]. -- Escreva os nomes das variáveis, classes e métodos em inglês. -- Namespaces só precisam ser mencionados na primeira menção. -- Tente formatar o código de modo que as barras de rolagem não sejam exibidas. -- Poupe todos os tipos de marcadores, desde boldface até `.[note]` caixas. -- A partir da documentação, consulte apenas a documentação ou `www`. +Mutações de idiomas .[#toc-language-mutations] +---------------------------------------------- +O inglês é o idioma principal, portanto, suas mudanças devem ser em inglês. Se o inglês não for o seu forte, use [DeepL Tradutor |https://www.deepl.com/translator] e outros verificarão seu texto. -Estrutura de documentação .[#toc-documentation-structure] ---------------------------------------------------------- +A tradução para outros idiomas será feita automaticamente após a aprovação e o ajuste fino de sua edição. + + +Edições triviais .[#toc-trivial-edits] +-------------------------------------- + +Para contribuir com a documentação, você precisa ter uma conta no [GitHub |https://github.com]. -A documentação completa está hospedada no GitHub, no repositório [nette/docs |https://github.com/nette/docs]. Este repositório é dividido em filiais com base na versão da documentação, por exemplo, a filial `doc-3.1` contém a documentação da versão 3.1. E depois há a filial `nette.org`, que contém o conteúdo dos outros subdomínios da nette.org. +A maneira mais fácil de fazer uma pequena mudança na documentação é usar os links no final de cada página: -Cada ramo é então dividido em várias pastas: +- *Show on GitHub* abre a versão de origem da página no GitHub. Depois basta pressionar o botão `E` e você pode começar a editar (você deve estar logado no GitHub) +- *Abrir visualização* abre um editor onde você pode ver imediatamente a forma visual final -* `cs` e `en`: contém arquivos de documentação para cada versão linguística -* `files`: imagens que podem ser incorporadas nas páginas de documentação +Como o [editor de visualização |https://editor.nette.org/] não tem a capacidade de salvar as mudanças diretamente no GitHub, você precisa copiar o texto fonte para a área de transferência (usando o botão *Copy to clipboard*) e depois colá-lo no editor no GitHub. +Abaixo do campo de edição, há um formulário para submissão. Aqui, não se esqueça de resumir e explicar brevemente o motivo de sua edição. Após a submissão, é criado um chamado pull request (PR), que pode ser editado posteriormente. -O caminho para um arquivo sem extensão corresponde ao URL de uma página na documentação. Assim, o arquivo `en/quickstart/single-post.texy` terá a URL `doc.nette.org/en/quickstart/single-post`. +Maiores edições .[#toc-larger-edits] +------------------------------------ -Contribuindo .[#toc-contributing] ---------------------------------- +É mais apropriado estar familiarizado com o básico de trabalhar com o sistema de controle de versões Git, em vez de depender apenas da interface GitHub. Se você não estiver familiarizado com Git, você pode se referir ao [git - o guia simples |https://rogerdudler.github.io/git-guide/] e considerar o uso de um dos muitos [clientes gráficos |https://git-scm.com/downloads/guis] disponíveis. -Para contribuir com a documentação, você deve ter uma conta no [GitHub |https://github.com] e conhecer o básico de [GitHub |https://github.com]. Se você não está familiarizado com Git, pode verificar o guia rápido: [git - o guia simples |https://rogerdudler.github.io/git-guide/], ou usar uma das muitas ferramentas gráficas: [GIT - clientes da GUI |https://git-scm.com/downloads/guis]. +Edite a documentação da seguinte maneira: -Você pode fazer mudanças simples diretamente na interface do GitHub. Entretanto, é mais conveniente criar um garfo do repositório [nette/docs |https://github.com/nette/docs] e cloná-lo em seu computador. Em seguida, faça mudanças no ramo apropriado, comprometa a mudança, empurre para seu GitHub e envie um pedido de puxar para o repositório original `nette/docs`. +1) no GitHub, criar um [garfo |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] do repositório [nette/docs |https://github.com/nette/docs] +2) [clone |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] este repositório em seu computador +3) então, fazer mudanças no [ramo apropriado |#Documentation Structure] +4) verificar se há espaços extras no texto usando a ferramenta [Code-Checker |code-checker:] +5) salvar (comprometer) as mudanças +6) se você estiver satisfeito com as mudanças, empurre-as para GitHub até sua bifurcação +7) a partir daí, submetê-los ao repositório `nette/docs`, criando um [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +É comum receber comentários com sugestões. Mantenha-se informado sobre as mudanças propostas e incorpore-as. Adicione as mudanças sugeridas como novos compromissos e reenvie-as ao GitHub. Nunca crie um novo pedido de puxar só para modificar um já existente. + + +Estrutura de documentação .[#toc-documentation-structure] +--------------------------------------------------------- -Antes de cada pedido de puxar, é uma boa idéia executar o [Code-Checker |code-checker:] para verificar o espaço extra em branco no texto. +Toda a documentação está localizada no GitHub, no repositório [nette/docs |https://github.com/nette/docs]. A versão atual está na filial principal, enquanto as versões mais antigas estão localizadas em filiais como `doc-3.x`, `doc-2.x`. -{{priority: -1}} +O conteúdo de cada ramo é dividido em pastas principais que representam áreas individuais de documentação. Por exemplo, `application/` corresponde a https://doc.nette.org/en/application, `latte/` corresponde a https://latte.nette.org, etc. Cada uma destas pastas contém subpastas representando as mutações lingüísticas (`cs`, `en`, ...) e opcionalmente uma subpasta `files` com imagens que podem ser inseridas nas páginas da documentação. diff --git a/contributing/ro/code.texy b/contributing/ro/code.texy index 5fd47ccdc0..c75032d3c9 100644 --- a/contributing/ro/code.texy +++ b/contributing/ro/code.texy @@ -1,110 +1,118 @@ -Propunerea de modificare a codului -********************************** +Contribuția la cod +****************** -Nette Framework folosește Git și [GitHub |https://github.com/nette/nette] pentru menținerea bazei de cod. Cel mai bun mod de a contribui este să vă confirmați modificările în propriul fork și apoi să faceți o cerere de extragere pe GitHub. Acest document rezumă principalii pași pentru a contribui cu succes. +.[perex] +Intenționați să contribuiți la Nette Framework și trebuie să vă familiarizați cu regulile și procedurile? Acest ghid pentru începători vă va ghida prin pașii pentru a contribui eficient la cod, a lucra cu depozitele și a implementa modificări. -Pregătirea mediului .[#toc-preparing-environment] -================================================= +Procedura .[#toc-procedure] +=========================== -Începeți prin a [face forking pentru |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette pe GitHub |https://github.com/nette]. [Pregătiți |https://help.github.com/en/github/getting-started-with-github/set-up-git] cu atenție mediul Git local, configurați-vă numele de utilizator și adresa de e-mail, aceste credențiale vor identifica modificările dvs. în istoricul Nette Framework. +Pentru a contribui la cod, este esențial să aveți un cont pe [GitHub |https://github.com] și să fiți familiarizați cu elementele de bază ale lucrului cu sistemul de control al versiunilor Git. Dacă nu sunteți familiarizat cu Git, puteți consulta [Ghidul git - the simple guide |https://rogerdudler.github.io/git-guide/] și puteți lua în considerare utilizarea unuia dintre numeroșii [clienți grafici |https://git-scm.com/downloads/guis]. -Lucrați la patch-ul dvs. .[#toc-working-on-your-patch] -====================================================== +Pregătirea mediului și a depozitului .[#toc-preparing-the-environment-and-repository] +------------------------------------------------------------------------------------- -Înainte de a începe să lucrați la patch-ul dumneavoastră, creați o nouă ramură pentru modificările dumneavoastră. -```shell -git checkout -b new_branch_name -``` +1) Pe GitHub, creați un [fork |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] al [depozitului de pachete |www:packages] pe care intenționați să îl modificați +2) [Clonați |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] acest depozit pe computerul dvs. +3) Instalați dependențele, inclusiv [Nette Tester |tester:], utilizând comanda `composer install` +4) Verificați dacă testele funcționează, rulând `composer tester` +5) Creați o [nouă ramură |#New Branch] bazată pe cea mai recentă versiune lansată -Puteți lucra la modificarea codului dumneavoastră. -Dacă este posibil, efectuați modificările din ultima versiune lansată. +Implementarea propriilor modificări .[#toc-implementing-your-own-changes] +------------------------------------------------------------------------- +Acum puteți face propriile modificări de cod: -Testarea modificărilor dvs. .[#toc-testing-your-changes] -======================================================== +1) Implementați modificările dorite și nu uitați de teste +2) Asigurați-vă că testele se execută cu succes folosind `composer tester` +3) Verificați dacă codul respectă [standardele de codare |#coding standards] +4) Salvați (confirmați) modificările cu o descriere în [acest format |#Commit Description] -Trebuie să instalați Nette Tester. Cel mai simplu este să apelați `composer install` în rădăcina depozitului. Acum ar trebui să puteți rula teste cu `./vendor/bin/tester` în terminal. +Puteți crea mai multe comenzi, câte una pentru fiecare etapă logică. Fiecare commit ar trebui să fie semnificativ în sine. -Este posibil ca unele teste să eșueze din cauza lipsei php.ini. Prin urmare, ar trebui să apelați runner-ul cu parametrul -c și să specificați calea către php.ini, de exemplu `./vendor/bin/tester -c ./tests/php.ini`. -După ce puteți rula testele, puteți să le implementați pe ale dumneavoastră sau să modificați eșecul pentru a corespunde noului comportament. Citiți mai multe despre testarea cu Nette Tester în [pagina de documentație |tester:]. +Trimiterea modificărilor .[#toc-submitting-changes] +--------------------------------------------------- +După ce sunteți mulțumit de modificări, le puteți trimite: -Standarde de codare .[#toc-coding-standards] -============================================ +1) Împingeți modificările pe GitHub în furculița dvs. +2) De acolo, trimiteți-le la depozitul Nette prin crearea unei [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) Furnizați [informații suficiente |#pull request description] în descriere -Codul dumneavoastră trebuie să respecte [standardele de codare |coding standard] utilizate în Nette Framework. Este ușor, deoarece există un verificator și un fixator automat. Acesta poate fi instalat prin Composer în directorul global ales de dumneavoastră: -```shell -composer create-project nette/coding-standard /path/to/nette-coding-standard -``` +Încorporarea feedback-ului .[#toc-incorporating-feedback] +--------------------------------------------------------- -Acum ar trebui să puteți rula instrumentul în terminal. De exemplu, această comandă verifică și corectează codul din dosarele `src` și `tests` din directorul curent: +Modificările dvs. sunt acum vizibile pentru ceilalți. Este obișnuit să primiți comentarii cu sugestii: -```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix -``` +1) Țineți evidența modificărilor propuse +2) Încorporați-le ca noi comenzi sau [fuzionați-le cu cele anterioare |https://help.github.com/en/github/using-git/about-git-rebase] +3) Trimiteți din nou comentariile pe GitHub, iar acestea vor apărea automat în cererea de extragere (pull request) +Nu creați niciodată un nou pull request pentru a modifica unul existent. -Trimiterea modificărilor .[#toc-committing-the-changes] -======================================================= -După ce ați modificat codul, trebuie să vă confirmați modificările. Creați mai multe comisioane, câte una pentru fiecare pas logic. Fiecare commit trebuie să fi fost utilizabil ca atare - fără alte commits. Așadar, testele corespunzătoare ar trebui să fie, de asemenea, incluse în același commit. +Documentație .[#toc-documentation] +---------------------------------- -Vă rugăm să verificați de două ori dacă codul dvs. se încadrează în reguli: -- Codul nu generează nicio eroare -- Codul dumneavoastră nu întrerupe niciun test. -- Modificarea codului dvs. este testată. -- Nu comiteți modificări inutile de spațiu alb. +Dacă ați modificat o funcționalitate sau ați adăugat una nouă, nu uitați să [o adăugați și în documentație |documentation]. -Mesajul de confirmare trebuie să respecte formatul `Latte: fixed multi template rendering [Closes # 69]` adică: -- o zonă urmată de două puncte -- scopul confirmării în trecut, dacă este posibil, începeți cu "added.", "fixed.", "refactored.", changed, removed -- o eventuală legătură către issue tracker -- dacă trimiterea anulează compatibilitatea retroactivă, adăugați "BC break". -- poate exista o linie liberă după subiect și o descriere mai detaliată, inclusiv linkuri către forum. +Ramură nouă .[#toc-new-branch] +============================== -Solicitarea de comenzi de tip Pull-Request .[#toc-pull-requesting-the-commits] -============================================================================== - -Dacă sunteți mulțumiți de modificările aduse codului și de angajamentele dvs., trebuie să trimiteți aceste angajamente pe GitHub. +Dacă este posibil, efectuați modificările în raport cu ultima versiune publicată, adică ultima etichetă din ramură. Pentru eticheta v3.2.1, creați o ramură folosind această comandă: ```shell -git push origin new_branch_name +git checkout -b new_branch_name v3.2.1 ``` -Modificările sunt prezente în mod public, însă trebuie să vă propuneți modificările pentru a fi integrate în ramura principală a Nette. Pentru a face acest lucru, [faceți o cerere de extragere |https://help.github.com/articles/creating-a-pull-request]. -Fiecare pull request are un titlu și o descriere. Vă rugăm să furnizați un titlu descriptiv. Acesta este adesea similar cu numele ramurii, de exemplu "Securing signals against CSRF attack". -Descrierea pull request ar trebui să fi conținut câteva informații mai specifice despre modificările aduse codului dvs: -``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no -- BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` +Standarde de codificare .[#toc-coding-standards] +================================================ -Vă rugăm să modificați tabelul de informații pentru a se potrivi cererii dvs. de extragere. Comentarii la fiecare element din listă: -- Spune dacă cererea de extragere adaugă **funcționalitate** sau este o **reparare de erori**. -- Face trimitere la o eventuală **problemă conexă**, care va fi închisă după fuzionarea cererii de extragere. -- Spune dacă pull request-ul are nevoie de **modificări ale documentației**, dacă da, oferă referințe la pull requests corespunzătoare. Nu trebuie să furnizezi imediat modificarea documentației, însă cererea nu va fi fuzionată dacă este necesară modificarea documentației. Modificarea documentației trebuie să fie pregătită pentru documentația în limba engleză, alte mutații de limbă sunt opționale. -- Spune dacă pull request-ul creează **o întrerupere a BC**. Vă rugăm să considerați tot ceea ce modifică interfața publică ca fiind o întrerupere BC. +Codul dumneavoastră trebuie să respecte [standardele de codare |coding standard] utilizate în cadrul Nette Framework. Există un instrument automat disponibil pentru verificarea și corectarea codului. Îl puteți instala **global** prin Composer într-un dosar la alegere: -Tabelul final ar putea arăta astfel: +```shell +composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no + +Acum ar trebui să puteți rula instrumentul în terminal. Prima comandă verifică, iar cea de-a doua corectează codul din dosarele `src` și `tests` din directorul curent: + +```shell +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Refacerea modificărilor tale .[#toc-reworking-your-changes] -=========================================================== +Angajare Descriere .[#toc-commit-description] +============================================= + +În Nette, subiectele de commit au următorul format: `Presenter: fixed AJAX detection [Closes #69]` + +- area urmată de două puncte +- scopul angajamentului la timpul trecut; dacă este posibil, începeți cu cuvinte de genul: added, fixed, refactored, changed, removed +- în cazul în care confirmarea încalcă compatibilitatea retroactivă, adăugați "BC break" +- orice legătură cu sistemul de urmărire a problemelor, cum ar fi `(#123)` sau `[Closes #69]` +- după subiect, poate exista o linie goală, urmată de o descriere mai detaliată, incluzând, de exemplu, linkuri către forum + + +Descrierea cererii de tip pull request .[#toc-pull-request-description] +======================================================================= + +Atunci când creați un pull request, interfața GitHub vă va permite să introduceți un titlu și o descriere. Furnizați un titlu concis și includeți în descriere cât mai multe informații despre motivele modificării dumneavoastră. + +De asemenea, precizați în antet dacă este o caracteristică nouă sau o remediere a unei erori și dacă poate cauza probleme de compatibilitate retroactivă (BC break). Dacă există o problemă conexă, creați un link către aceasta, astfel încât aceasta să fie închisă în momentul aprobării cererii de modificare. + +``` +- bug fix / new feature? <!-- #issue numbers, if any --> +- BC break? yes/no +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> +``` -Este foarte frecvent să primiți comentarii la modificarea codului dumneavoastră. Vă rugăm să încercați să respectați modificările propuse și să vă reelaborați comenzile pentru a face acest lucru. Puteți să confirmați modificările propuse ca noi confirmări și apoi să le striviți cu cele anterioare. Consultați capitolul [Rebase interactiv |https://help.github.com/en/github/using-git/about-git-rebase] pe GitHub. După ce ați făcut rebase, împingeți forțat modificările în furculița dvs. la distanță, totul se va propaga automat în cererea de extragere. {{priority: -1}} diff --git a/contributing/ro/coding-standard.texy b/contributing/ro/coding-standard.texy index 0aed6a115b..663be314a3 100644 --- a/contributing/ro/coding-standard.texy +++ b/contributing/ro/coding-standard.texy @@ -38,7 +38,7 @@ Standardul de codificare Nette corespunde PSR-12 (sau stilului de codificare PER - funcțiile săgeată se scriu fără spațiu înaintea parantezei, adică `fn($a) => $b` - nu este necesară o linie goală între diferitele tipuri de instrucțiuni de import `use` -- tipul de returnare al funcției/metodei și paranteza de deschidere trebuie plasate pe linii separate pentru o mai bună lizibilitate: +- tipul de retur al unei funcții/metode și paranteza curbă de deschidere se află întotdeauna pe linii separate: ```php public function find( @@ -50,6 +50,10 @@ Standardul de codificare Nette corespunde PSR-12 (sau stilului de codificare PER } ``` +Paranteza de deschidere pe o linie separată este importantă pentru separarea vizuală a semnăturii funcției/metodei de corp. Dacă semnătura se află pe o singură linie, separarea este clară (imaginea din stânga), dacă se află pe mai multe linii, în PSR semnăturile și corpurile se amestecă (în mijloc), în timp ce în standardul Nette acestea rămân separate (în dreapta): + +[* new-line-after.webp *] + Blocuri de documentație (phpDoc) .[#toc-documentation-blocks-phpdoc] ==================================================================== diff --git a/contributing/ro/documentation.texy b/contributing/ro/documentation.texy index 3d6cdf83dc..78df00669c 100644 --- a/contributing/ro/documentation.texy +++ b/contributing/ro/documentation.texy @@ -1,53 +1,69 @@ -Scrierea documentației -********************** +Contribuția la documentație +*************************** .[perex] -Contribuția la documentație este unul dintre multele moduri în care îl puteți ajuta pe Nette. Este, de asemenea, una dintre cele mai satisfăcătoare activități, deoarece îi ajutați pe alții să înțeleagă cadrul. +Contribuția la documentație este una dintre cele mai valoroase activități, deoarece îi ajută pe ceilalți să înțeleagă cadrul. -Cum să scrieți? .[#toc-how-to-write] ------------------------------------- +Cum se scrie? .[#toc-how-to-write] +---------------------------------- -Documentația este destinată în primul rând persoanelor care abia se familiarizează cu acest subiect. Prin urmare, ar trebui să îndeplinească câteva puncte importante: +Documentația este destinată în primul rând persoanelor care nu cunosc acest subiect. Prin urmare, ar trebui să îndeplinească mai multe puncte importante: -- **Când scrieți, începeți cu lucruri simple și generale, iar la sfârșit treceți la subiecte mai avansate.** -- Furnizați numai informațiile pe care utilizatorul trebuie să le știe cu adevărat despre subiect. -- Verificați dacă informațiile pe care le oferiți sunt de fapt adevărate. Testați mai întâi exemplul înainte de a da exemplul. -- Fiți concis - reduceți la jumătate ceea ce scrieți. Și apoi nu ezitați să o faceți din nou. -- Încercați să explicați cât mai bine subiectul. De exemplu, încercați să explicați mai întâi subiectul unui coleg. +- Începeți cu subiecte simple și generale. Treceți la subiecte mai avansate la sfârșit +- Încercați să explicați subiectul cât mai clar posibil. De exemplu, încercați să explicați mai întâi subiectul unui coleg +- Furnizați numai informațiile pe care utilizatorul trebuie să le știe cu adevărat pentru un anumit subiect +- Asigurați-vă că informațiile pe care le furnizați sunt corecte. Testați fiecare cod +- Fiți concis - reduceți la jumătate ceea ce scrieți. Și apoi nu ezitați să o faceți din nou +- Folosiți evidențierea cu moderație, de la fonturi îngroșate la cadre de genul `.[note]` +- Respectați [standardul de codi |Coding Standard] ficare în cod -Țineți minte aceste puncte pe tot parcursul procesului de scriere. Documentația este scrisă în [Texy!" |https://texy.info], așa că învățați [sintaxa |syntax] acesteia. Puteți utiliza editorul de documentație de la https://editor.nette.org/ pentru a previzualiza articolul pe măsură ce îl scrieți. +De asemenea, învățați [sintaxa |syntax]. Pentru o previzualizare a articolului în timpul scrierii, puteți utiliza [editorul de previzualizare |https://editor.nette.org/]. -Printre regulile generale de scriere enumerate anterior, respectați următoarele: -- Codul dumneavoastră trebuie să fie în conformitate cu [Standardul de codare |Coding Standard]. -- Scrieți numele variabilelor, claselor și metodelor în limba engleză. -- Spațiile de nume trebuie să fie menționate doar la prima mențiune. -- Încercați să formatați codul astfel încât să nu fie afișate bare de defilare. -- Scăpați de toate tipurile de evidențiere, de la boldface la `.[note]` casete. -- Din documentație, faceți trimitere doar la documentație sau la `www`. +Mutații lingvistice .[#toc-language-mutations] +---------------------------------------------- +Engleza este limba principală, așa că modificările trebuie să fie în limba engleză. Dacă engleza nu este punctul dumneavoastră forte, utilizați [DeepL Translator |https://www.deepl.com/translator] și alții vă vor verifica textul. -Structura documentației .[#toc-documentation-structure] -------------------------------------------------------- +Traducerea în alte limbi se va face automat după aprobarea și punerea la punct a modificării dumneavoastră. + + +Modificări triviale .[#toc-trivial-edits] +----------------------------------------- + +Pentru a contribui la documentație, trebuie să aveți un cont pe [GitHub |https://github.com]. -Documentația completă este găzduită pe GitHub, în depozitul [nette/docs |https://github.com/nette/docs]. Acest depozit este împărțit în ramuri în funcție de versiunea documentației, de exemplu, ramura `doc-3.1` conține documentația pentru versiunea 3.1. Și apoi există ramura `nette.org`, care conține conținutul celorlalte subdomenii ale nette.org. +Cel mai simplu mod de a face o mică modificare în documentație este să folosiți linkurile de la sfârșitul fiecărei pagini: -Fiecare ramură este apoi împărțită în mai multe dosare: +- *Show on GitHub* deschide versiunea sursă a paginii pe GitHub. Apoi, trebuie doar să apăsați butonul `E` și puteți începe să editați (trebuie să fiți logat pe GitHub) +- *Open preview* deschide un editor în care puteți vedea imediat forma vizuală finală -* `cs` și `en`: conține fișiere de documentație pentru fiecare versiune de limbă. -* `files`: imagini care pot fi încorporate în paginile de documentație +Deoarece [editorul de previzualizare |https://editor.nette.org/] nu are posibilitatea de a salva modificările direct pe GitHub, trebuie să copiați textul sursă în clipboard (folosind butonul *Copy to clipboard*) și apoi să îl lipiți în editorul de pe GitHub. +Sub câmpul de editare se află un formular pentru trimitere. Aici, nu uitați să rezumați pe scurt și să explicați motivul modificării dvs. După trimitere, se creează o așa-numită pull request (PR), care poate fi editată în continuare. -Calea către un fișier fără extensie corespunde URL-ului unei pagini din documentație. Astfel, fișierul `en/quickstart/single-post.texy` va avea URL-ul `doc.nette.org/en/quickstart/single-post`. +Modificări mai mari .[#toc-larger-edits] +---------------------------------------- -Contribuția .[#toc-contributing] --------------------------------- +Este mai indicat să vă familiarizați cu elementele de bază ale lucrului cu sistemul de control al versiunilor Git decât să vă bazați exclusiv pe interfața GitHub. Dacă nu sunteți familiarizat cu Git, puteți consulta [ghidul git - the simple guide |https://rogerdudler.github.io/git-guide/] și puteți lua în considerare utilizarea unuia dintre numeroșii [clienți grafici |https://git-scm.com/downloads/guis] disponibili. -Pentru a contribui la documentație, trebuie să aveți un cont pe [GitHub |https://github.com] și să cunoașteți elementele de bază ale Git. Dacă nu sunteți familiarizat cu Git, puteți consulta ghidul rapid: [git - ghidul simplu |https://rogerdudler.github.io/git-guide/] sau puteți utiliza unul dintre numeroasele instrumente grafice: [GIT - clienți GUI |https://git-scm.com/downloads/guis]. +Modificați documentația în felul următor: -Puteți face modificări simple direct în interfața GitHub. Cu toate acestea, este mai convenabil să creați o bifurcație a depozitului [nette/docs |https://github.com/nette/docs] și să o clonați pe computerul dumneavoastră. Apoi, efectuați modificări în ramura corespunzătoare, confirmați modificarea, împingeți-o în GitHub-ul dvs. și trimiteți un pull request la depozitul original `nette/docs`. +1) pe GitHub, creați o [bifurcație |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] a depozitului [nette/docs |https://github.com/nette/docs] +2) [clonați |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] acest depozit pe computerul dvs. +3) apoi, efectuați modificări în [ramura corespunzătoare |#Documentation Structure] +4) verificați dacă există spații în plus în text cu ajutorul instrumentului [Code-Checker |code-checker:] +5) salvați (confirmați) modificările +6) dacă sunteți mulțumit de modificări, împingeți-le pe GitHub în bifurcația dvs. +7) de acolo, trimiteți-le la depozitul `nette/docs` creând o [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +Este obișnuit să primiți comentarii cu sugestii. Țineți evidența modificărilor propuse și încorporați-le. Adăugați modificările sugerate ca noi comisioane și retrimiteți-le la GitHub. Nu creați niciodată un nou pull request doar pentru a modifica unul existent. + + +Structura documentației .[#toc-documentation-structure] +------------------------------------------------------- -Înainte de fiecare pull request, este o idee bună să rulați [Code-Checker |code-checker:] pentru a verifica spațiile albe suplimentare din text. +Întreaga documentație se găsește pe GitHub, în depozitul [nette/docs |https://github.com/nette/docs]. Versiunea curentă se află în ramura master, în timp ce versiunile mai vechi se află în ramuri precum `doc-3.x`, `doc-2.x`. -{{priority: -1}} +Conținutul fiecărei ramuri este împărțit în dosare principale care reprezintă domenii individuale ale documentației. De exemplu, `application/` corespunde la https://doc.nette.org/en/application, `latte/` corespunde la https://latte.nette.org, etc. Fiecare dintre aceste dosare conține subdosare care reprezintă mutații lingvistice (`cs`, `en`, ...) și, opțional, un subdosar `files` cu imagini care pot fi inserate în paginile din documentație. diff --git a/contributing/ru/code.texy b/contributing/ru/code.texy index 801c7418a0..9e2239ab22 100644 --- a/contributing/ru/code.texy +++ b/contributing/ru/code.texy @@ -1,110 +1,118 @@ -Предложение об изменении кодекса -******************************** +Вклад в код +*********** -Nette Framework использует Git и [GitHub |https://github.com/nette/nette] для поддержки кодовой базы. Лучший способ внести свой вклад - зафиксировать свои изменения в собственном форке, а затем сделать pull request на GitHub. В этом документе кратко описаны основные шаги для успешного внесения изменений. +.[perex] +Вы планируете внести свой вклад в Nette Framework и вам необходимо ознакомиться с правилами и процедурами? Это руководство для начинающих расскажет вам о том, как эффективно вносить вклад в код, работать с репозиториями и внедрять изменения. -Подготовка среды .[#toc-preparing-environment] -============================================== +Процедура .[#toc-procedure] +=========================== -Начните с [форка |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette на GitHub |https://github.com/nette]. Тщательно [настройте |https://help.github.com/en/github/getting-started-with-github/set-up-git] локальную среду Git, задайте имя пользователя и email, эти учетные данные будут идентифицировать ваши изменения в истории Nette Framework. +Чтобы внести свой вклад в код, необходимо иметь учетную запись на [GitHub |https://github.com] и быть знакомым с основами работы с системой контроля версий Git. Если вы не знакомы с Git, вы можете ознакомиться с [git - простым руководством |https://rogerdudler.github.io/git-guide/] и рассмотреть возможность использования одного из многочисленных [графических клиентов |https://git-scm.com/downloads/guis]. -Работа над вашим патчем .[#toc-working-on-your-patch] -===================================================== +Подготовка среды и репозитория .[#toc-preparing-the-environment-and-repository] +------------------------------------------------------------------------------- -Прежде чем начать работу над патчем, создайте новую ветвь для своих изменений. -```shell -git checkout -b new_branch_name -``` +1) На GitHub создайте [форк |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [репозитория пакета |www:packages], который вы собираетесь изменить +2) [Клонируйте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] этот репозиторий на свой компьютер +3) Установите зависимости, включая [Nette Tester |tester:], с помощью команды `composer install`. +4) Убедитесь, что тесты работают, выполнив команду `composer tester` +5) Создайте [новую ветку |#New Branch] на основе последней выпущенной версии + + +Внедрение собственных изменений .[#toc-implementing-your-own-changes] +--------------------------------------------------------------------- + +Теперь вы можете внести собственные изменения в код: + +1) Внесите желаемые изменения и не забудьте о тестах +2) Убедитесь, что тесты успешно выполняются `composer tester` +3) Проверьте, соответствует ли код [стандартам кодирования |#coding standards] +4) Сохраните (зафиксируйте) изменения с описанием в [таком формате |#Commit Description] + +Вы можете создать несколько коммитов, по одному для каждого логического шага. Каждый коммит должен быть значимым сам по себе. -Вы можете работать над изменением своего кода. -Если возможно, вносите изменения из последней выпущенной версии. +Представление изменений .[#toc-submitting-changes] +-------------------------------------------------- +После того как вы будете удовлетворены изменениями, вы можете отправить их: -Тестирование ваших изменений .[#toc-testing-your-changes] -========================================================= +1) Внесите изменения на GitHub в свой форк. +2) Оттуда отправьте их в репозиторий Nette, создав [pull request|https://help.github.com/articles/creating-a-pull-request] внесение изменений (PR). +3) Предоставьте [достаточную информацию |#pull request description] в описании -Вам необходимо установить Nette Tester. Самый простой способ - вызвать `composer install` в корне репозитория. Теперь вы должны иметь возможность запускать тесты с `./vendor/bin/tester` в терминале. -Некоторые тесты могут не работать из-за отсутствия php.ini. Поэтому следует вызвать бегунок с параметром -c и указать путь к php.ini, например, `./vendor/bin/tester -c ./tests/php.ini`. +Включение обратной связи .[#toc-incorporating-feedback] +------------------------------------------------------- -После того, как вы сможете запустить тесты, вы можете реализовать свои собственные или изменить отказ, чтобы соответствовать новому поведению. Подробнее о тестировании с помощью Nette Tester читайте на [странице документации |tester:]. +Теперь ваши коммиты видны другим. Часто можно получить комментарии с предложениями: + +1) Следить за предлагаемыми изменениями +2) Включить их в новые коммиты или [объединить с предыдущими |https://help.github.com/en/github/using-git/about-git-rebase] +3) Повторно отправить исправления на GitHub, и они автоматически появятся в запросе на исправление. + +Никогда не создавайте новый pull request для изменения существующего. + + +Документация .[#toc-documentation] +---------------------------------- + +Если вы изменили функциональность или добавили новую, не забудьте также [добавить ее в документацию |documentation]. + + +Новый филиал .[#toc-new-branch] +=============================== + +Если возможно, вносите изменения в последнюю выпущенную версию, т.е. в последний тег в ветке. Для тега v3.2.1 создайте ветку с помощью этой команды: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Стандарты кодирования .[#toc-coding-standards] ============================================== -Ваш код должен соответствовать [стандартам кодирования |coding standard], используемым в Nette Framework. Это легко, потому что есть автоматический чекер и фиксер. Он может быть установлен через Composer в выбранный вами глобальный каталог: +Ваш код должен соответствовать [стандартам кодирования |coding standard], используемым в Nette Framework. Существует автоматический инструмент для проверки и исправления кода. Вы можете установить его **глобально** через Composer в выбранную вами папку: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Теперь вы должны быть в состоянии запустить инструмент в терминале. Например, эта команда проверяет и исправляет код в папках `src` и `tests` в текущем каталоге: +Теперь вы должны быть в состоянии запустить инструмент в терминале. Первая команда проверяет, а вторая исправляет код в папках `src` и `tests` в текущем каталоге: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Зафиксировать изменения .[#toc-committing-the-changes] -====================================================== - -После того как вы изменили код, необходимо зафиксировать изменения. Создайте несколько коммитов, по одному на каждый логический шаг. Каждый коммит должен быть пригоден для использования как есть - без других коммитов. Поэтому соответствующие тесты должны быть включены в тот же коммит. +Описание обязательств .[#toc-commit-description] +================================================ -Пожалуйста, дважды проверьте, соответствует ли ваш код правилам: -- Код не генерирует никаких ошибок -- Ваш код не нарушает никаких тестов. -- Ваше изменение кода протестировано. -- Вы не совершаете бесполезных изменений в белых пятнах. +В Nette темы коммитов имеют следующий формат: `Presenter: fixed AJAX detection [Closes #69]` -Сообщение о фиксации должно соответствовать формату `Latte: fixed multi template rendering [Closes # 69]` т.е: - область, за которой следует двоеточие -- цель фиксации в прошлом, если возможно, начинайте с "добавлено.", "исправлено.", "рефакторинговано.", изменено, удалено -- возможная ссылка на трекер проблем -- если коммит отменяет обратную совместимость, добавьте "BC break". -- после темы может быть одна свободная строка и более подробное описание, включая ссылки на форум. +- цель фиксации в прошедшем времени; если возможно, начинайте с таких слов, как: added, fixed, refactored, changed, removed +- если коммит нарушает обратную совместимость, добавьте "BC break" +- любая связь с трекером проблем, например, `(#123)` или `[Closes #69]` +- после темы может быть одна пустая строка, за которой следует более подробное описание, включая, например, ссылки на форум -Pull-Requesting the Commits .[#toc-pull-requesting-the-commits] -=============================================================== +Описание Pull Request .[#toc-pull-request-description] +====================================================== -Если вы удовлетворены своими изменениями и коммитами в коде, вы должны опубликовать свои коммиты на GitHub. +При создании pull request интерфейс GitHub позволит вам ввести название и описание. Укажите лаконичное название и включите в описание как можно больше информации о причинах вашего изменения. -```shell -git push origin new_branch_name -``` +Также укажите в заголовке, является ли это новой функцией или исправлением ошибки, и может ли это привести к проблемам обратной совместимости (BC break). Если существует связанная с ним проблема, укажите ссылку на нее, чтобы она была закрыта после одобрения запроса. -Изменения представлены публично, однако вы должны предложить свои изменения для интеграции в мастер-ветку Nette. Для этого сделайте [запрос на |https://help.github.com/articles/creating-a-pull-request] исправление. -Каждый запрос имеет заголовок и описание. Пожалуйста, предоставьте какое-нибудь описывающее название. Часто оно похоже на название ветки, например, "Защита сигналов от CSRF-атак". - -Описание запроса на исправление должно содержать более конкретную информацию о ваших изменениях кода: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Пожалуйста, измените информационную таблицу в соответствии с вашим запросом. Комментарии к каждому пункту списка: -- Указывается, добавляет ли запрос на исправление **фичу** или это **исправление**. -- Ссылается на **связанный вопрос**, который будет закрыт после объединения заявки. -- Говорится, нужны ли в заявке **изменения документации**, если да, то укажите ссылки на соответствующие заявки. Вы не обязаны предоставлять изменения документации немедленно, однако запрос на исправление не будет объединен, если изменения документации необходимы. Изменение документации должно быть подготовлено для английской документации, мутации других языков необязательны. -- Говорится, если запрос на изменение создаёт **разрыв BC**. Пожалуйста, считайте все, что изменяет публичный интерфейс, нарушением BC. - -Итоговая таблица может выглядеть следующим образом: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Переработка ваших изменений .[#toc-reworking-your-changes] -========================================================== -Очень часто приходится получать комментарии к вашим изменениям в коде. Пожалуйста, старайтесь следовать предложенным изменениям и перерабатывайте свои коммиты для этого. Вы можете зафиксировать предложенные изменения как новые коммиты, а затем раздавить их на предыдущие. См. главу [Интерактивный rebase |https://help.github.com/en/github/using-git/about-git-rebase] на GitHub. После перебазирования изменений, принудительно отправьте изменения в ваш удаленный форк, все будет автоматически распространено в pull request. {{priority: -1}} diff --git a/contributing/ru/coding-standard.texy b/contributing/ru/coding-standard.texy index 169b2b7c88..19d30dc750 100644 --- a/contributing/ru/coding-standard.texy +++ b/contributing/ru/coding-standard.texy @@ -38,7 +38,7 @@ Nette Coding Standard соответствует PSR-12 (или PER Coding Style - стрелочные функции записываются без пробела перед скобкой, т.е. `fn($a) => $b`. - между различными типами операторов импорта `use` не требуется пустая строка -- возвращаемый тип функции/метода и открывающая скобка должны располагаться на отдельных строках для лучшей читабельности: +- возвращаемый тип функции/метода и открывающая фигурная скобка всегда находятся на отдельных строках: ```php public function find( @@ -50,6 +50,10 @@ Nette Coding Standard соответствует PSR-12 (или PER Coding Style } ``` +Открывающая фигурная скобка на отдельной строке важна для визуального отделения подписи функции/метода от тела. Если подпись находится на одной строке, то разделение четкое (изображение слева), если на нескольких строках, то в PSR подписи и тела сливаются вместе (посередине), а в стандарте Nette они остаются разделенными (справа): + +[* new-line-after.webp *] + Блоки документации (phpDoc) .[#toc-documentation-blocks-phpdoc] =============================================================== diff --git a/contributing/ru/documentation.texy b/contributing/ru/documentation.texy index bf8c104aca..0cd5eaf805 100644 --- a/contributing/ru/documentation.texy +++ b/contributing/ru/documentation.texy @@ -1,53 +1,69 @@ -Написание документации -********************** +Вклад в документацию +******************** .[perex] -Вклад в документацию - это один из многих способов помочь Nette. Это также один из самых полезных видов деятельности, поскольку вы помогаете другим понять фреймворк. +Вклад в документацию является одним из наиболее ценных видов деятельности, поскольку он помогает другим понять концепцию. Как писать? .[#toc-how-to-write] -------------------------------- -Документация предназначена в первую очередь для людей, которые только знакомятся с темой. Поэтому она должна отвечать нескольким важным требованиям: +Документация в первую очередь предназначена для людей, которые впервые сталкиваются с этой темой. Поэтому она должна отвечать нескольким важным требованиям: -- **При написании документации начинайте с простого и общего, а к более сложным темам переходите в конце**. -- Предоставляйте только ту информацию, которую пользователю действительно необходимо знать о теме. -- Убедитесь, что ваша информация действительно правдива. Прежде чем привести пример, сначала проверьте его на практике. -- Будьте лаконичны - сократите то, что вы пишете, вдвое. А затем не стесняйтесь сделать это еще раз. -- Постарайтесь объяснить суть вопроса как можно лучше. Например, попробуйте сначала объяснить тему коллеге. +- Начинайте с простых и общих тем. В конце переходите к более сложным темам. +- Старайтесь объяснять тему как можно понятнее. Например, попробуйте сначала объяснить тему коллеге. +- Предоставляйте только ту информацию, которую пользователю действительно необходимо знать по данной теме. +- Убедитесь, что ваша информация точна. Тестируйте каждый код +- Будьте лаконичны - сократите то, что вы пишете, вдвое. А затем не стесняйтесь сделать это снова. +- Используйте выделение экономно, от жирного шрифта до рамок типа `.[note]` +- Следуйте [стандарту кодирования |Coding Standard] в коде -Помните об этих моментах на протяжении всего процесса написания. Документация написана на языке [Texy! |https://texy.info], поэтому изучите его [синтаксис |syntax]. Вы можете использовать редактор документации на сайте https://editor.nette.org/ для предварительного просмотра статьи в процессе ее написания. +Также изучите [синтаксис |syntax]. Для предварительного просмотра статьи во время написания вы можете использовать [редактор предварительного просмотра |https://editor.nette.org/]. -Помимо общих правил написания, перечисленных ранее, придерживайтесь следующих: -- Ваш код должен соответствовать [Стандарту кодирования |Coding Standard]. -- Пишите имена переменных, классов и методов на английском языке. -- Пространства имен должны упоминаться только при первом упоминании. -- Старайтесь форматировать код так, чтобы не отображались полосы прокрутки. -- Избавьтесь от всех видов выделения, от жирного шрифта до `.[note]` квадратиков. -- Из документации ссылайтесь только на документацию или `www`. +Мутации языка .[#toc-language-mutations] +---------------------------------------- +Английский является основным языком, поэтому ваши изменения должны быть на английском. Если английский не является вашей сильной стороной, используйте [DeepL Переводчик |https://www.deepl.com/translator], и другие проверят ваш текст. -Структура документации .[#toc-documentation-structure] ------------------------------------------------------- +Перевод на другие языки будет выполнен автоматически после одобрения и доработки вашей правки. + + +Тривиальные правки .[#toc-trivial-edits] +---------------------------------------- + +Чтобы внести свой вклад в документацию, вам необходимо иметь учетную запись на [GitHub |https://github.com]. -Полная документация размещена на GitHub в репозитории [nette/docs |https://github.com/nette/docs]. Этот репозиторий разделен на ветви в зависимости от версии документации, например, ветвь `doc-3.1` содержит документацию для версии 3.1. Кроме того, есть ветка `nette.org`, которая содержит содержимое других поддоменов nette.org. +Самый простой способ внести небольшие изменения в документацию - воспользоваться ссылками в конце каждой страницы: -Затем каждая ветка делится на несколько папок: +- *Показать на GitHub* открывает исходную версию страницы на GitHub. Затем просто нажмите кнопку `E`, и вы сможете начать редактирование (вы должны быть зарегистрированы на GitHub). +- *Открыть предварительный просмотр* открывает редактор, где вы можете сразу увидеть окончательный визуальный вид -* `cs` и `en`: содержит файлы документации для каждой языковой версии. -* `files`: изображения, которые могут быть вставлены в страницы документации +Поскольку [редактор предварительного просмотра |https://editor.nette.org/] не имеет возможности сохранять изменения непосредственно на GitHub, вам необходимо скопировать исходный текст в буфер обмена (с помощью кнопки *Копировать в буфер обмена*), а затем вставить его в редактор на GitHub. +Ниже поля редактирования находится форма для отправки. Здесь не забудьте кратко изложить и объяснить причину вашей правки. После отправки создается так называемый pull request (PR), который можно в дальнейшем редактировать. -Путь к файлу без расширения соответствует URL страницы в документации. Таким образом, файл `en/quickstart/single-post.texy` будет иметь URL `doc.nette.org/en/quickstart/single-post`. +Более крупные правки .[#toc-larger-edits] +----------------------------------------- -Внесение вклада .[#toc-contributing] ------------------------------------- +Целесообразнее ознакомиться с основами работы с системой контроля версий Git, чем полагаться только на интерфейс GitHub. Если вы не знакомы с Git, вы можете обратиться к [git - простому руководству |https://rogerdudler.github.io/git-guide/] и рассмотреть возможность использования одного из множества доступных [графических клиентов |https://git-scm.com/downloads/guis]. -Чтобы внести свой вклад в документацию, вы должны иметь учетную запись на [GitHub |https://github.com] и знать основы Git. Если вы не знакомы с Git'ом, вы можете ознакомиться с кратким руководством: [git - the simple guide |https://rogerdudler.github.io/git-guide/], или воспользоваться одним из многочисленных графических инструментов: [GIT - GUI-клиенты |https://git-scm.com/downloads/guis]. +Отредактируйте документацию следующим образом: -Вы можете вносить простые изменения непосредственно в интерфейсе GitHub. Однако удобнее создать форк репозитория [nette/docs |https://github.com/nette/docs] и клонировать его на свой компьютер. Затем внесите изменения в соответствующую ветку, зафиксируйте изменения, переместите их на свой GitHub и отправьте запрос pull request в исходный репозиторий `nette/docs`. +1) на GitHub создайте [форк |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] репозитория [nette/docs |https://github.com/nette/docs] +2) [клонируйте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] этот репозиторий на свой компьютер +3) затем внесите изменения в [соответствующую ветку |#Documentation Structure] +4) проверьте наличие лишних пробелов в тексте с помощью инструмента [Code-Checker |code-checker:] +5) сохраните (зафиксируйте) изменения +6) если вы удовлетворены изменениями, отправьте их на GitHub в свой форк +7) оттуда отправьте их в репозиторий `nette/docs`, создав [pull request|https://help.github.com/articles/creating-a-pull-request] исправление (PR). + +Обычно вы получаете комментарии с предложениями. Отслеживайте предложенные изменения и учитывайте их. Добавьте предложенные изменения как новые коммиты и повторно отправьте их в GitHub. Никогда не создавайте новый pull request только для того, чтобы изменить существующий. + + +Структура документации .[#toc-documentation-structure] +------------------------------------------------------ -Перед каждым запросом на исправление рекомендуется запустить [Code-Checker |code-checker:] для проверки лишних пробелов в тексте. +Вся документация находится на GitHub в репозитории [nette/docs |https://github.com/nette/docs]. Текущая версия находится в ветке master, а старые версии расположены в ветках `doc-3.x`, `doc-2.x`. -{{priority: -1}} +Содержимое каждой ветки разделено на основные папки, представляющие отдельные области документации. Например, `application/` соответствует https://doc.nette.org/en/application, `latte/` соответствует https://latte.nette.org, и т.д. Каждая из этих папок содержит вложенные папки, представляющие языковые мутации (`cs`, `en`, ...) и, по желанию, вложенную папку `files` с изображениями, которые могут быть вставлены в страницы документации. diff --git a/contributing/sl/code.texy b/contributing/sl/code.texy index ba69b54e6f..968d13cd56 100644 --- a/contributing/sl/code.texy +++ b/contributing/sl/code.texy @@ -1,110 +1,118 @@ -Predlaganje spremembe kodeksa -***************************** +Prispevek h kodi +**************** -Nette Framework za vzdrževanje baze kode uporablja Git in [GitHub |https://github.com/nette/nette]. Najboljši način za prispevanje je, da svoje spremembe prenesete v svojo vilico in nato v GitHub oddate zahtevek za povleko. Ta dokument povzema glavne korake za uspešno prispevanje. +.[perex] +Ali nameravate prispevati k ogrodju Nette in se morate seznaniti s pravili in postopki? Ta priročnik za začetnike vas bo popeljal skozi korake za učinkovito prispevanje h kodi, delo z repozitoriji in izvajanje sprememb. -Priprava okolja .[#toc-preparing-environment] -============================================= +Postopek .[#toc-procedure] +========================== -Začnite z [viličenjem |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette na GitHubu |https://github.com/nette]. Skrbno [nastavite |https://help.github.com/en/github/getting-started-with-github/set-up-git] lokalno okolje Git, konfigurirajte svoje uporabniško ime in e-pošto, saj bodo te poverilnice identificirale vaše spremembe v zgodovini ogrodja Nette. +Če želite prispevati h kodi, morate imeti račun na [GitHubu |https://github.com] in poznati osnove dela s sistemom za nadzor različic Git. Če sistema Git ne poznate, si lahko ogledate priročnik [git - the simple guide |https://rogerdudler.github.io/git-guide/] in razmislite o uporabi enega od številnih [grafičnih odjemalcev |https://git-scm.com/downloads/guis]. -Delo na vašem popravku .[#toc-working-on-your-patch] -==================================================== +Priprava okolja in skladišča .[#toc-preparing-the-environment-and-repository] +----------------------------------------------------------------------------- -Preden začnete delati na popravku, ustvarite novo vejo za svoje spremembe. -```shell -git checkout -b new_branch_name -``` +1) Na spletnem mestu GitHub ustvarite [vilico |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [skladišča paketov |www:packages], ki ga nameravate spremeniti +2) [Klonirate |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] to shrambo v svoj računalnik +3) Z ukazom `composer install` namestite odvisnosti, vključno s [programom Nette Tester |tester:]. +4) Preverite, ali testi delujejo, tako da zaženete `composer tester` +5) Ustvarite [novo vejo, ki |#New Branch] temelji na zadnji izdani različici + + +Izvajanje lastnih sprememb .[#toc-implementing-your-own-changes] +---------------------------------------------------------------- + +Zdaj lahko izvedete lastne prilagoditve kode: + +1) Izvedite želene spremembe in ne pozabite na teste +2) Poskrbite, da se testi uspešno izvedejo z uporabo `composer tester` +3) Preverite, ali koda ustreza [standardom kodiranja |#coding standards] +4) Shranite (commit) spremembe z opisom v [tej |#Commit Description]obliki + +Ustvarite lahko več zavez, po eno za vsak logični korak. Vsaka oddaja mora biti smiselna sama po sebi. + + +Oddaja sprememb .[#toc-submitting-changes] +------------------------------------------ + +Ko ste s spremembami zadovoljni, jih lahko predložite: + +1) Spremembe prenesite na GitHub v svojo vilico +2) Od tam jih predložite v skladišče Nette tako, da ustvarite [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) V opisu navedite [dovolj informacij |#pull request description] + + +Vključevanje povratnih informacij .[#toc-incorporating-feedback] +---------------------------------------------------------------- + +Vaše oddaje so zdaj vidne drugim. Pogosto prejmete komentarje s predlogi: -Tako lahko delate na spremembi kode. +1) Spremljajte predlagane spremembe +2) Vključite jih kot nove spremembe ali [jih združite s prejšnjimi |https://help.github.com/en/github/using-git/about-git-rebase] +3) Ponovno pošljite spremembe v GitHub in samodejno se bodo pojavile v zahtevi za povlečenje. -Če je mogoče, spremembe izvedite iz zadnje izdane različice. +Nikoli ne ustvarjajte nove zahteve za prenos, da bi spremenili obstoječo zahtevo. -Preizkušanje sprememb .[#toc-testing-your-changes] -================================================== +Dokumentacija .[#toc-documentation] +----------------------------------- -Namestiti morate program Nette Tester. Najlažje je, če v korenskem naslovu skladišča pokličete `composer install`. Zdaj bi morali biti sposobni izvajati teste s `./vendor/bin/tester` v terminalu. +Če ste spremenili funkcionalnost ali dodali novo, ne pozabite [tega dodati tudi v dokumentacijo |documentation]. -Nekateri testi bodo morda neuspešni zaradi manjkajočega php.ini. Zato morate prožilec poklicati s parametrom -c in navesti pot do php.ini, na primer `./vendor/bin/tester -c ./tests/php.ini`. -Ko boste lahko zagnali teste, lahko izvedete svoje ali spremenite neuspešne, da bodo ustrezali novemu obnašanju. Več o testiranju s programom Nette Tester si preberite na [strani z dokumentacijo |tester:]. +Nova veja .[#toc-new-branch] +============================ + +Če je mogoče, spremembe izvedite glede na zadnjo izdano različico, tj. zadnjo oznako v veji. Za oznako v3.2.1 ustvarite vejo s tem ukazom: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Standardi kodiranja .[#toc-coding-standards] ============================================ -Vaša koda mora upoštevati [standard kodiranja, ki |coding standard] se uporablja v okviru Nette. To je enostavno, saj je na voljo samodejni pregledovalnik in popravljalnik. Namestite ga lahko prek programa Composer v izbrani globalni imenik: +Vaša koda mora ustrezati [standardom kodiranja, |coding standard] ki se uporabljajo v okviru Nette. Za preverjanje in popravljanje kode je na voljo samodejno orodje. Namestite ga lahko **globalno** prek programa Composer v izbrano mapo: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Zdaj morate biti sposobni zagnati orodje v terminalu. Ta ukaz na primer preveri in popravi kodo v mapah `src` in `tests` v trenutnem imeniku: +Orodje lahko zaženete v terminalu. Prvi ukaz preveri, drugi pa popravi kodo v mapah `src` in `tests` v trenutnem imeniku: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Oddajanje sprememb .[#toc-committing-the-changes] -================================================= +Obveznost Opis .[#toc-commit-description] +========================================= -Ko spremenite kodo, morate spremembe potrditi. Ustvarite več zavez, po eno za vsak logični korak. Vsaka sprememba mora biti uporabna takšna, kot je - brez drugih sprememb. Zato je treba v isti commit vključiti tudi ustrezne teste. +V sistemu Nette imajo predmeti sprememb naslednjo obliko: `Presenter: fixed AJAX detection [Closes #69]` -Dvakrat preverite, ali vaša koda ustreza pravilom: -- Koda ne ustvarja napak. -- Vaša koda ne krši nobenih testov. -- Vaša sprememba kode je testirana. -- Ne izvajate nekoristnih sprememb v belem polju. +- sledi dvopičje +- namen zaveze v preteklem času; če je mogoče, začnite z besedami, kot so: added, fixed, refactored, changed, removed +- če oddaja krši združljivost za nazaj, dodajte "BC break" +- kakršno koli povezavo s programom za sledenje težavam, na primer `(#123)` ali `[Closes #69]` +- za temo je lahko ena prazna vrstica, ki ji sledi podrobnejši opis, na primer vključno s povezavami do foruma -Sporočilo o oddaji mora biti v obliki `Latte: fixed multi template rendering [Closes # 69]` tj: -- področje, ki mu sledi dvopičje -- namen oddaje v preteklosti, če je mogoče, začnite z "dodano.", "popravljeno.", "refaktorizirano.", spremenjeno, odstranjeno -- morebitna povezava do sledilnika težav -- če sprememba prekliče združljivost za nazaj, dodajte "BC break". -- za temo je lahko ena prosta vrstica in podrobnejši opis, vključno s povezavami na forum. +Opis zahtevka za izvleček .[#toc-pull-request-description] +========================================================== -Zahteva za povišanje (Pull-Requesting) za dopolnitve .[#toc-pull-requesting-the-commits] -======================================================================================== - -Če ste zadovoljni s spremembami kode in zavezami, morate svoje zaveze poslati v GitHub. - -```shell -git push origin new_branch_name -``` +Pri ustvarjanju zahteve za prenos vam vmesnik GitHub omogoča vnos naslova in opisa. Navedite jedrnat naslov in v opis vključite čim več informacij o razlogih za vašo spremembo. -Spremembe so javno dostopne, vendar morate svoje spremembe predlagati za vključitev v glavno vejo Nette. To storite tako, da podate [zahtevo za poteg |https://help.github.com/articles/creating-a-pull-request]. -Vsak zahtevek ima naslov in opis. Navedite nekaj opisnih naslovov. Pogosto je podoben imenu veje, na primer "Zavarovanje signalov pred napadom CSRF". +V naslovu navedite tudi, ali gre za novo funkcijo ali popravek napake in ali lahko povzroči težave s povratno združljivostjo (BC break). Če obstaja povezana težava, se nanjo povežite, tako da bo po odobritvi zahteve za spremembo zaprta. -Opis zahteve za poteg bi moral vsebovati nekaj bolj specifičnih informacij o vaših spremembah kode: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Prosimo, spremenite tabelo z informacijami, da bo ustrezala vaši zahtevi za poteg. Pripombe k vsaki postavki seznama: -- Piše, ali zahtevek dodaja **funkcijo** ali gre za **opravek napake**. -- Sklicuje se na morebitno **povezano vprašanje**, ki bo zaprto po združitvi zahteve. -- Pove, ali je treba v zahtevi za prenos spremeniti **dokumentacijo**, če je tako, navedite sklice na ustrezne zahteve za prenos. Spremembe dokumentacije vam ni treba zagotoviti takoj, vendar pa zahteva za poteg ne bo združena, če je sprememba dokumentacije potrebna. Sprememba dokumentacije mora biti pripravljena za angleško dokumentacijo, druge jezikovne mutacije niso obvezne. -- Pove, če zahtevek za prenos ustvari **prekinitev BC**. Vse, kar spreminja javni vmesnik, obravnavajte kot prelom BC. - -Končna tabela bi lahko bila videti takole: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Preoblikovanje sprememb .[#toc-reworking-your-changes] -====================================================== -Pogosto se zgodi, da prejmete komentarje k spremembi kode. Poskusite slediti predlaganim spremembam in v ta namen predelajte svoje spremembe. Predlagane spremembe lahko oddate kot nove oddaje in jih nato zmečkate s prejšnjimi. Oglejte si poglavje o [interaktivni ponovni vzpostavitvi |https://help.github.com/en/github/using-git/about-git-rebase] na spletnem mestu GitHub. Po ponovnem uokvirjanju sprememb za silo potisnite spremembe v oddaljeno vilico, vse se bo samodejno razširilo v zahtevek za povleko (pull request). {{priority: -1}} diff --git a/contributing/sl/coding-standard.texy b/contributing/sl/coding-standard.texy index 395eb2dd01..067d2b5d0a 100644 --- a/contributing/sl/coding-standard.texy +++ b/contributing/sl/coding-standard.texy @@ -38,7 +38,7 @@ Nette Coding Standard ustreza PSR-12 (ali PER Coding Style), v nekaterih točkah - puščice so zapisane brez presledka pred oklepajem, tj. `fn($a) => $b` - med različnimi vrstami stavkov za uvoz `use` ni potrebna prazna vrstica -- tip vrnitve funkcije/metode in uvodni oklepaj morata biti zaradi boljše berljivosti postavljena v ločenih vrsticah: +- tip vrnitve funkcije/metode in začetni oglati oklepaj sta vedno v ločenih vrsticah: ```php public function find( @@ -50,6 +50,10 @@ Nette Coding Standard ustreza PSR-12 (ali PER Coding Style), v nekaterih točkah } ``` +Začetni oglati oklepaj v ločeni vrstici je pomemben za vizualno ločitev podpisa funkcije/metode od telesa. Če je podpis v eni vrstici, je ločitev jasna (slika na levi), če je v več vrsticah, se v PSR podpisi in telesa zlijejo (na sredini), medtem ko v standardu Nette ostanejo ločeni (na desni): + +[* new-line-after.webp *] + Bloki dokumentacije (phpDoc) .[#toc-documentation-blocks-phpdoc] ================================================================ diff --git a/contributing/sl/documentation.texy b/contributing/sl/documentation.texy index 6e671cabf7..261c76bd9a 100644 --- a/contributing/sl/documentation.texy +++ b/contributing/sl/documentation.texy @@ -1,53 +1,69 @@ -Pisanje dokumentacije -********************* +Prispevek k dokumentaciji +************************* .[perex] -Prispevek k dokumentaciji je eden od številnih načinov, kako lahko pomagate Nette. To je tudi ena najbolj koristnih dejavnosti, saj pomagate drugim razumeti ogrodje. +Prispevek k dokumentaciji je ena izmed najbolj dragocenih dejavnosti, saj drugim pomaga razumeti ogrodje. Kako pisati? .[#toc-how-to-write] --------------------------------- -Dokumentacija je v prvi vrsti namenjena ljudem, ki se šele spoznavajo s to temo. Zato mora izpolnjevati več pomembnih točk: +Dokumentacija je v prvi vrsti namenjena ljudem, ki se s tem področjem šele spoznavajo. Zato mora izpolnjevati več pomembnih točk: -- **Pri pisanju začnite s preprostimi in splošnimi temami, na koncu pa preidite na naprednejše teme.** -- Navedite le tiste informacije, ki jih uporabnik resnično potrebuje o temi. -- Preverite, ali so vaše informacije dejansko resnične. Najprej preizkusite primer, preden ga navedete. -- Bodite jedrnati - zapisano skrajšajte na polovico. In potem to lahko še enkrat ponovite. -- Poskusite zadevo čim bolje razložiti. Najprej poskusite na primer razložiti temo svojemu sodelavcu. +- Začnite s preprostimi in splošnimi temami. Na koncu preidite na naprednejše teme +- Poskusite temo razložiti čim bolj jasno. Na primer, poskusite temo najprej razložiti sodelavcu. +- Navedite le informacije, ki jih uporabnik dejansko potrebuje za določeno temo +- Prepričajte se, da so vaše informacije točne. Preizkusite vsako kodo +- Bodite jedrnati - napišite manj kot polovico. In potem to še enkrat ponovite. +- Varčno uporabljajte poudarke, od krepkih pisav do okvirjev, kot so `.[note]` +- V kodi upoštevajte [standard kodiranja |Coding Standard] -Te točke imejte v mislih ves čas pisanja. Dokumentacija je napisana v jeziku [Texy! |https://texy.info], zato se naučite njegove [sintakse |syntax]. Uporabite lahko urejevalnik dokumentacije na spletnem mestu https://editor.nette.org/ in si članek med pisanjem predhodno ogledate. +Naučite se tudi [sintakse |syntax]. Za predogled članka med pisanjem lahko uporabite [urejevalnik za predogled |https://editor.nette.org/]. -Med splošnimi pravili za pisanje, ki so bila navedena prej, se držite tudi naslednjih: -- Vaša koda mora biti v skladu s [standardom kodiranja |Coding Standard]. -- Imena spremenljivk, razredov in metod zapišite v angleščini. -- Imenski prostori morajo biti omenjeni le ob prvi omembi. -- Poskusite oblikovati kodo tako, da se ne bodo prikazovale vrstice za pomikanje. -- Prihranite vse vrste označevalnikov, od krepkega tiska do `.[note]` okvirjev. -- Iz dokumentacije se sklicujte samo na dokumentacijo ali na spletno stran `www`. +Jezikovne mutacije .[#toc-language-mutations] +--------------------------------------------- +Osnovni jezik je angleščina, zato morajo biti vaše spremembe v angleščini. Če angleščina ni vaša močna stran, uporabite [prevajalnik DeepL |https://www.deepl.com/translator] in drugi bodo preverili vaše besedilo. -Struktura dokumentacije .[#toc-documentation-structure] -------------------------------------------------------- +Prevod v druge jezike bo opravljen samodejno po odobritvi in dodelavi vašega urejanja. + + +Trivialna urejanja .[#toc-trivial-edits] +---------------------------------------- + +Če želite prispevati k dokumentaciji, morate imeti račun na [GitHubu |https://github.com]. -Celotna dokumentacija je objavljena na GitHubu v skladišču [nette/docs |https://github.com/nette/docs]. Ta skladišče je razdeljeno na veje glede na različico dokumentacije, na primer veja `doc-3.1` vsebuje dokumentacijo za različico 3.1. Potem pa je tu še veja `nette.org`, ki vsebuje vsebino drugih poddomen spletnega mesta nette.org. +Najlažji način za majhne spremembe v dokumentaciji je uporaba povezav na koncu vsake strani: -Vsaka veja je nato razdeljena na več map: +- *Pokaži na GitHubu* odpre izvorno različico strani na GitHubu. Nato samo pritisnite gumb `E` in lahko začnete urejati (prijavljeni morate biti v GitHub). +- *Open preview* odpre urejevalnik, v katerem si lahko takoj ogledate končno vizualno obliko -* `cs` in `en`: vsebujeta dokumentacijske datoteke za vsako jezikovno različico -* `files`: slike, ki jih je mogoče vgraditi v dokumentacijske strani +Ker [urejevalnik predogleda |https://editor.nette.org/] nima možnosti neposrednega shranjevanja sprememb v GitHub, morate izvorno besedilo kopirati v odložišče (z gumbom *Kopiraj v odložišče*) in ga nato prilepiti v urejevalnik na GitHubu. +Pod poljem za urejanje je obrazec za oddajo. Tu ne pozabite na kratko povzeti in pojasniti razloga za svoje urejanje. Po oddaji se ustvari tako imenovana zahteva za povleko (pull request, PR), ki jo lahko še naprej urejate. -Pot do datoteke brez končnice ustreza naslovu URL strani v dokumentaciji. Tako bo datoteka `en/quickstart/single-post.texy` imela URL `doc.nette.org/en/quickstart/single-post`. +Večja urejanja .[#toc-larger-edits] +----------------------------------- -Prispevek .[#toc-contributing] ------------------------------- +Primerneje je poznati osnove dela s sistemom za nadzor različic Git in se ne zanašati samo na vmesnik GitHub. Če sistema Git ne poznate, si lahko ogledate [priročnik git - the simple guide |https://rogerdudler.github.io/git-guide/] in razmislite o uporabi enega od številnih [grafičnih odjemalcev |https://git-scm.com/downloads/guis], ki so na voljo. -Če želite prispevati k dokumentaciji, morate imeti račun na [GitHubu |https://github.com] in poznati osnove Gita. Če sistema Git ne poznate, si lahko ogledate kratek vodnik: [git - preprost vodnik |https://rogerdudler.github.io/git-guide/] ali pa uporabite eno od številnih grafičnih orodij: [GIT - odjemalci grafičnega vmesnika |https://git-scm.com/downloads/guis]. +Dokumentacijo uredite na naslednji način: -Preproste spremembe lahko naredite neposredno v vmesniku GitHub. Vendar je bolj priročno, če ustvarite vilico skladišča [nette/docs |https://github.com/nette/docs] in ga klonirate v svoj računalnik. Nato naredite spremembe v ustrezni veji, potrdite spremembe, jih potisnite v svoj repozitorij GitHub in pošljite zahtevek za poteg v prvotni repozitorij `nette/docs`. +1) na spletnem mestu GitHub ustvarite [vilico |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] skladišča [nette/docs |https://github.com/nette/docs] +2) to skladišče [klonirajte |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] v svoj računalnik +3) nato vnesite spremembe v [ustrezno vejo |#Documentation Structure] +4) z orodjem [Code-Checker |code-checker:] preverite, ali so v besedilu dodatni presledki +5) shranite (commit) spremembe +6) če ste s spremembami zadovoljni, jih potisnite v GitHub v svojo vilico +7) od tam jih pošljite v repozitorij `nette/docs` tako, da ustvarite [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +Pogosto se zgodi, da prejmete komentarje s predlogi. Spremljajte predlagane spremembe in jih vključite. Predlagane spremembe dodajte kot nove spremembe in jih ponovno pošljite v GitHub. Nikoli ne ustvarjajte nove zahteve za prenos samo zato, da bi spremenili obstoječo zahtevo. + + +Struktura dokumentacije .[#toc-documentation-structure] +------------------------------------------------------- -Pred vsako zahtevo za povlečenje je dobro zagnati program [Code-Checker |code-checker:], da preverite dodatne bele lise v besedilu. +Celotna dokumentacija se nahaja na GitHubu v skladišču [nette/docs |https://github.com/nette/docs]. Trenutna različica je v glavni veji, starejše različice pa se nahajajo v vejah, kot so `doc-3.x`, `doc-2.x`. -{{priority: -1}} +Vsebina vsake veje je razdeljena na glavne mape, ki predstavljajo posamezna področja dokumentacije. Na primer, `application/` ustreza https://doc.nette.org/en/application, `latte/` ustreza https://latte.nette.org itd. Vsaka od teh map vsebuje podmape, ki predstavljajo jezikovne mutacije (`cs`, `en`, ...), in po želji podmapo `files` s slikami, ki jih je mogoče vstaviti na strani v dokumentaciji. diff --git a/contributing/tr/code.texy b/contributing/tr/code.texy index d35b0d7faa..ad80a6f0e0 100644 --- a/contributing/tr/code.texy +++ b/contributing/tr/code.texy @@ -1,110 +1,118 @@ -Kanun Değişikliği Önerisi -************************* +Koda Katkıda Bulunma +******************** -Nette Framework, kod tabanını korumak için Git ve [GitHub |https://github.com/nette/nette] 'ı kullanır. Katkıda bulunmanın en iyi yolu, değişikliklerinizi kendi çatalınıza işlemek ve ardından GitHub'da bir çekme isteği yapmaktır. Bu belge, başarılı bir şekilde katkıda bulunmak için önemli adımları özetlemektedir. +.[perex] +Nette Framework'e katkıda bulunmayı planlıyor ve kural ve prosedürlere aşina olmanız mı gerekiyor? Bu başlangıç kılavuzu, koda etkili bir şekilde katkıda bulunma, depolarla çalışma ve değişiklikleri uygulama adımlarında size yol gösterecektir. -Ortam Hazırlama .[#toc-preparing-environment] -============================================= +Prosedür .[#toc-procedure] +========================== -[GitHub'da Nette'yi |https://github.com/nette] [çatallamakla |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] başlayın. Yerel Git ortamınızı dikkatlice [kurun |https://help.github.com/en/github/getting-started-with-github/set-up-git], kullanıcı adınızı ve e-postanızı yapılandırın, bu kimlik bilgileri Nette Framework geçmişindeki değişikliklerinizi tanımlayacaktır. +Koda katkıda bulunmak için [GitHub |https://github.com] 'da bir hesabınızın olması ve Git sürüm kontrol sistemiyle çalışmanın temellerine aşina olmanız gerekir. Git'e aşina değilseniz, [git - the simple guide |https://rogerdudler.github.io/git-guide/] 'a göz atabilir ve birçok [grafik istemciden |https://git-scm.com/downloads/guis] birini kullanmayı düşünebilirsiniz. -Yamanız Üzerinde Çalışmak .[#toc-working-on-your-patch] -======================================================= +Ortamın ve Deponun Hazırlanması .[#toc-preparing-the-environment-and-repository] +-------------------------------------------------------------------------------- -Yamanız üzerinde çalışmaya başlamadan önce, değişiklikleriniz için yeni bir dal oluşturun. -```shell -git checkout -b new_branch_name -``` +1) GitHub'da, değiştirmeyi düşündüğünüz [paket deposunun |www:packages] bir [çatalını |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] oluşturun +2) Bu depoyu bilgisayarınıza [klonlayın |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] +3) `composer install` komutunu kullanarak [Nette Tester |tester:] dahil olmak üzere bağımlılıkları yükleyin +4) Çalıştırarak testlerin çalıştığını doğrulayın `composer tester` +5) Yayınlanan en son sürüme göre [yeni |#New Branch] bir [dal |#New Branch] oluşturun + + +Kendi Değişikliklerinizin Uygulanması .[#toc-implementing-your-own-changes] +--------------------------------------------------------------------------- + +Artık kendi kod ayarlamalarınızı yapabilirsiniz: + +1) İstenen değişiklikleri uygulayın ve testleri unutmayın +2) Testlerin başarıyla çalıştığından emin olmak için `composer tester` +3) Kodun [kodlama standartlarını |#coding standards]karşılayıp karşılamadığını kontrol edin +4) Değişiklikleri [aşağıdaki biçimde |#Commit Description]bir açıklama ile kaydedin (işleyin) + +Her mantıksal adım için bir tane olmak üzere birden fazla taahhüt oluşturabilirsiniz. Her commit kendi başına anlamlı olmalıdır. + + +Değişikliklerin Gönderilmesi .[#toc-submitting-changes] +------------------------------------------------------- + +Değişikliklerden memnun kaldığınızda, bunları gönderebilirsiniz: + +1) Değişiklikleri GitHub'a çatalınıza gönderin +2) Oradan, bir [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) oluşturarak bunları Nette deposuna gönderin +3) Açıklamada [yeterli bilgi |#pull request description] sağlayın + + +Geri Bildirimin Dahil Edilmesi .[#toc-incorporating-feedback] +------------------------------------------------------------- + +Taahhütleriniz artık başkaları tarafından görülebilir. Öneriler içeren yorumlar almak yaygındır: + +1) Önerilen değişiklikleri takip edin +2) Bunları yeni taahhütler olarak dahil edin veya [öncekilerle birleştirin |https://help.github.com/en/github/using-git/about-git-rebase] +3) İşlemleri GitHub'a yeniden gönderin; otomatik olarak çekme isteğinde görüneceklerdir -Kod değişikliğiniz üzerinde çalışabilirsiniz. +Var olanı değiştirmek için asla yeni bir çekme isteği oluşturmayın. -Mümkünse, son yayınlanan sürümden değişiklikler yapın. +Dokümantasyon .[#toc-documentation] +----------------------------------- -Değişikliklerinizi Test Etme .[#toc-testing-your-changes] -========================================================= +İşlevselliği değiştirdiyseniz veya yeni bir işlev eklediyseniz, [bunu belgelere eklem |documentation] eyi de unutmayın. -Nette Tester'ı yüklemeniz gerekir. En kolay yol, depo kökünde `composer install` adresini çağırmaktır. Şimdi terminalde `./vendor/bin/tester` ile testleri çalıştırabilmelisiniz. -Bazı testler eksik php.ini nedeniyle başarısız olabilir. Bu nedenle çalıştırıcıyı -c parametresi ile çağırmalı ve php.ini yolunu belirtmelisiniz, örneğin `./vendor/bin/tester -c ./tests/php.ini`. +Yeni Şube .[#toc-new-branch] +============================ -Testleri çalıştırabildikten sonra, kendi testlerinizi uygulayabilir veya yeni davranışa uyması için başarısız olanı değiştirebilirsiniz. Nette Tester ile test etme hakkında daha fazla bilgiyi [dokümantasyon sayfasında |tester:] bulabilirsiniz. +Mümkünse, değişiklikleri en son yayınlanan sürüme, yani daldaki son etikete göre yapın. v3.2.1 etiketi için bu komutu kullanarak bir dal oluşturun: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Kodlama Standartları .[#toc-coding-standards] ============================================= -Kodunuz Nette Framework'te kullanılan [kodlama standardına |coding standard] uygun olmalıdır. Otomatik denetleyici ve düzeltici olduğu için kolaydır. Composer aracılığıyla seçtiğiniz global dizine kurulabilir: +Kodunuz Nette Framework'te kullanılan [kodlama standardını |coding standard] karşılamalıdır. Kodu kontrol etmek ve düzeltmek için otomatik bir araç mevcuttur. Composer aracılığıyla **global olarak** istediğiniz bir klasöre yükleyebilirsiniz: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Şimdi aracı terminalde çalıştırabilmelisiniz. Örneğin, bu komut geçerli dizindeki `src` ve `tests` klasörlerindeki kodu kontrol eder ve düzeltir: +Şimdi aracı terminalde çalıştırabilmelisiniz. İlk komut kontrol eder ve ikincisi geçerli dizindeki `src` ve `tests` klasörlerindeki kodu düzeltir: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Değişikliklerin İşlenmesi .[#toc-committing-the-changes] -======================================================== - -Kodu değiştirdikten sonra değişikliklerinizi commit etmeniz gerekir. Her mantıksal adım için bir tane olmak üzere daha fazla commit oluşturun. Her bir commit, diğer commitler olmadan olduğu gibi kullanılabilir olmalıdır. Dolayısıyla, uygun testler de aynı commit'e dahil edilmelidir. - -Lütfen kodunuzun kurallara uygunluğunu iki kez kontrol edin: -- Kod herhangi bir hata oluşturmuyor -- Kodunuz herhangi bir testi bozmaz. -- Kod değişikliğiniz test edilmiştir. -- Gereksiz beyaz alan değişiklikleri yapmıyorsunuz. +Taahhüt Açıklaması .[#toc-commit-description] +============================================= -Taahhüt mesajı aşağıdaki formata uygun olmalıdır `Latte: fixed multi template rendering [Closes # 69]` Yani: -- iki nokta üst üste ile takip edilen bir alan -- commit'in geçmişteki amacı, mümkünse "added.", "fixed.", "refactored.", changed, removed" ile başlayın -- sorun izleyiciye nihai bağlantı -- taahhüt geriye dönük uyumluluğu iptal ederse, "BC break" ekleyin -- konudan sonra bir serbest satır ve forum bağlantıları da dahil olmak üzere daha ayrıntılı bir açıklama olabilir. +Nette'de taahhüt konuları aşağıdaki formata sahiptir: `Presenter: fixed AJAX detection [Closes #69]` +- alanı ve ardından iki nokta üst üste +- geçmiş zamanda commit'in amacı; mümkünse aşağıdaki gibi kelimelerle başlayın: "added .(yeni özellik)", "fixed .(düzeltme)", "refactored .(davranış değişikliği olmadan kod değişikliği)", changed, removed +- eğer taahhüt geriye dönük uyumluluğu bozuyorsa, "BC break" ekleyin +- sorun izleyiciyle herhangi bir bağlantı, örneğin `(#123)` veya `[Closes #69]` +- konudan sonra bir boş satır ve ardından, örneğin forum bağlantıları da dahil olmak üzere, daha ayrıntılı bir açıklama gelebilir -Komiteleri Çekme-Talep Etme .[#toc-pull-requesting-the-commits] -=============================================================== -Kod değişikliklerinizden ve taahhütlerinizden memnunsanız, taahhütlerinizi GitHub'a göndermeniz gerekir. +Çekme Talebi Açıklaması .[#toc-pull-request-description] +======================================================== -```shell -git push origin new_branch_name -``` +Bir çekme isteği oluştururken, GitHub arayüzü bir başlık ve açıklama girmenize izin verecektir. Kısa ve öz bir başlık girin ve açıklamada değişikliğinizin nedenleri hakkında mümkün olduğunca fazla bilgi ekleyin. -Değişiklikler herkese açıktır, ancak değişikliklerinizi Nette'nin ana dalına entegre etmek için önermeniz gerekir. Bunu yapmak için [bir çekme isteği |https://help.github.com/articles/creating-a-pull-request] oluşturun. -Her çekme isteğinin bir başlığı ve bir açıklaması vardır. Lütfen açıklayıcı bir başlık girin. Genellikle şube adına benzer, örneğin "CSRF saldırısına karşı sinyalleri güvence altına almak." +Ayrıca, başlıkta yeni bir özellik mi yoksa bir hata düzeltmesi mi olduğunu ve geriye dönük uyumluluk sorunlarına (BC break) neden olup olmayacağını belirtin. İlgili bir sorun varsa, çekme isteğinin onaylanması üzerine kapatılması için ona bağlantı verin. -Çekme isteği açıklaması, kod değişiklikleriniz hakkında daha spesifik bilgiler içermelidir: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Lütfen bilgi tablosunu çekme talebinize uyacak şekilde değiştirin. Her liste öğesine yorumlar: -- Çekme isteğinin **özellik** mi eklediğini yoksa bir **hata düzeltme** mi olduğunu belirtir. -- Çekme isteği birleştirildikten sonra kapatılacak olan **ilgili soruna** atıfta bulunur. -- Çekme isteğinin **dokümantasyon değişikliklerine** ihtiyacı olup olmadığını söyler, evet ise, uygun çekme isteklerine referanslar sağlar. Dokümantasyon değişikliğini hemen sağlamak zorunda değilsiniz, ancak dokümantasyon değişikliğine ihtiyaç duyulursa çekme isteği birleştirilmeyecektir. Dokümantasyon değişikliği İngilizce dokümantasyon için hazırlanmalıdır, diğer dil mutasyonları isteğe bağlıdır. -- Çekme isteğinin **bir BC kırılması** yaratıp yaratmadığını söylüyor. Lütfen, genel arayüzü değiştiren her şeyi bir BC kırılması olarak düşünün. - -Final masası şöyle görünebilir: -``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -Değişikliklerinizin Yeniden Düzenlenmesi .[#toc-reworking-your-changes] -======================================================================= - -Kod değişikliğinize yorum almak gerçekten yaygın bir durumdur. Lütfen önerilen değişiklikleri takip etmeye çalışın ve bunu yapmak için taahhütlerinizi yeniden düzenleyin. Önerilen değişiklikleri yeni taahhütler olarak işleyebilir ve ardından bunları öncekilere sıkıştırabilirsiniz. GitHub'da [Etkileşimli |https://help.github.com/en/github/using-git/about-git-rebase] yeniden düzenleme bölümüne bakın. Değişikliklerinizi yeniden düzenledikten sonra, değişikliklerinizi uzak çatalınıza zorla itin, her şey otomatik olarak çekme isteğine yayılacaktır. - {{priority: -1}} diff --git a/contributing/tr/coding-standard.texy b/contributing/tr/coding-standard.texy index aeaeee5225..887fa3f117 100644 --- a/contributing/tr/coding-standard.texy +++ b/contributing/tr/coding-standard.texy @@ -38,7 +38,7 @@ Nette Kodlama Standardı PSR-12'ye (veya PER Kodlama Stiline) karşılık gelir, - ok fonksiyonları parantezden önce boşluk bırakılmadan yazılır, yani `fn($a) => $b` - `use` import ifadelerinin farklı türleri arasında boş satır gerekmez -- fonksiyonun/metodun dönüş tipi ve açılış parantezi daha iyi okunabilirlik için ayrı satırlara yerleştirilmelidir: +- Bir fonksiyonun/metodun dönüş tipi ve açılış küme parantezi her zaman ayrı satırlarda yer alır: ```php public function find( @@ -50,6 +50,10 @@ Nette Kodlama Standardı PSR-12'ye (veya PER Kodlama Stiline) karşılık gelir, } ``` +Ayrı bir satırda açılan küme parantezi, fonksiyon/metot imzasını gövdeden görsel olarak ayırmak için önemlidir. İmza tek bir satırdaysa, ayrım nettir (soldaki resim), birden fazla satırdaysa, PSR'de imzalar ve gövdeler birbirine karışırken (ortada), Nette standardında ayrı kalırlar (sağda): + +[* new-line-after.webp *] + Dokümantasyon Blokları (phpDoc) .[#toc-documentation-blocks-phpdoc] =================================================================== diff --git a/contributing/tr/documentation.texy b/contributing/tr/documentation.texy index 0560b84589..cad1144972 100644 --- a/contributing/tr/documentation.texy +++ b/contributing/tr/documentation.texy @@ -1,53 +1,69 @@ -Dokümantasyonun Yazılması -************************* +Dokümantasyona Katkıda Bulunma +****************************** .[perex] -Belgelere katkıda bulunmak, Nette'e yardımcı olabileceğiniz birçok yoldan biridir. Ayrıca, başkalarının çerçeveyi anlamasına yardımcı olduğunuz için en ödüllendirici faaliyetlerden biridir. +Dokümantasyona katkıda bulunmak, başkalarının çerçeveyi anlamasına yardımcı olduğu için en değerli faaliyetlerden biridir. Nasıl Yazılır? .[#toc-how-to-write] ----------------------------------- -Dokümantasyon öncelikle konuyla yeni tanışan kişilere yöneliktir. Bu nedenle, birkaç önemli noktayı karşılamalıdır: +Dokümantasyon öncelikle konuya yeni başlayan kişilere yöneliktir. Bu nedenle, birkaç önemli noktayı karşılamalıdır: -- Yazarken, basit ve genel konularla başlayın ve sonunda daha gelişmiş konulara geçin.** -- Yalnızca kullanıcının konu hakkında gerçekten bilmesi gereken bilgileri sağlayın. -- Bilgilerinizin gerçekten doğru olduğunu doğrulayın. Örnek vermeden önce örneği test edin. -- Kısa ve öz olun - yazdıklarınızı ikiye bölün. Ve sonra tekrar yapmaktan çekinmeyin. -- Konuyu mümkün olduğunca iyi açıklamaya çalışın. Örneğin, konuyu önce bir iş arkadaşınıza anlatmayı deneyin. +- Basit ve genel konularla başlayın. Sonunda daha gelişmiş konulara geçin +- Konuyu mümkün olduğunca açık bir şekilde anlatmaya çalışın. Örneğin, konuyu önce bir meslektaşınıza açıklamayı deneyin +- Yalnızca kullanıcının belirli bir konu için gerçekten bilmesi gereken bilgileri sağlayın +- Bilgilerinizin doğru olduğundan emin olun. Her kodu test edin +- Kısa ve öz olun - yazdıklarınızı ikiye bölün. Ve sonra tekrar yapmaktan çekinmeyin +- Kalın yazı tiplerinden aşağıdaki gibi çerçevelere kadar vurgulamayı idareli kullanın `.[note]` +- Kodda [Kodlama Standardını |Coding Standard] Takip Edin -Yazım süreci boyunca bu noktaları aklınızda tutun. Belgeler [Texy! |https://texy.info] dilinde yazılmıştır, bu nedenle [sözdizimini |syntax] öğrenin. Makaleyi yazarken önizleme yapmak için https://editor.nette.org/ adresindeki dokümantasyon editörünü kullanabilirsiniz. +Ayrıca, [sözdizimini |syntax] öğrenin. Yazım sırasında makalenin önizlemesi için önizleme [düzenleyicisini |https://editor.nette.org/] kullanabilirsiniz. -Daha önce listelenen genel yazım kuralları arasında lütfen aşağıdakilere bağlı kalın: -- Kodunuz [Kodlama Standardına |Coding Standard] uygun olmalıdır. -- Değişkenlerin, sınıfların ve yöntemlerin adlarını İngilizce olarak yazın. -- Ad alanlarından yalnızca ilk bahsedildiğinde bahsedilmelidir. -- Kodu kaydırma çubuklarının görüntülenmeyeceği şekilde biçimlendirmeye çalışın. -- Her türlü vurgulayıcıdan uzak durun, kalın yazıdan `.[note]` kutular. -- Belgelerden sadece belgelere veya `www` adresine bakın. +Dil Mutasyonları .[#toc-language-mutations] +------------------------------------------- +İngilizce birincil dildir, bu nedenle değişiklikleriniz İngilizce olmalıdır. Eğer İngilizce sizin için uygun değilse, [DeepL Translator |https://www.deepl.com/translator] 'ı kullanın ve diğerleri metninizi kontrol etsin. -Dokümantasyon Yapısı .[#toc-documentation-structure] ----------------------------------------------------- +Düzenlemenizin onaylanması ve ince ayarlarının yapılmasının ardından diğer dillere çeviri otomatik olarak yapılacaktır. + + +Önemsiz Düzenlemeler .[#toc-trivial-edits] +------------------------------------------ + +Belgelere katkıda bulunmak için [GitHub |https://github.com]'da bir hesabınızın olması gerekir. -Belgelerin tamamı GitHub'da [nette/docs |https://github.com/nette/docs] deposunda barındırılmaktadır. Bu depo, dokümantasyonun sürümüne göre dallara ayrılmıştır, örneğin `doc-3.1` dalı 3.1 sürümünün dokümantasyonunu içerir. Bir de nette.org'un diğer alt alan adlarının içeriğini içeren `nette.org` dalı vardır. +Belgelerde küçük bir değişiklik yapmanın en kolay yolu, her sayfanın sonundaki bağlantıları kullanmaktır: -Her şube daha sonra birkaç klasöre ayrılır: +- *GitHub'da göster* sayfanın GitHub'daki kaynak sürümünü açar. Ardından `E` düğmesine basın ve düzenlemeye başlayabilirsiniz (GitHub'da oturum açmış olmanız gerekir) +- Önizlemeyi aç* son görsel formu hemen görebileceğiniz bir düzenleyici açar -* `cs` ve `en`: her dil sürümü için dokümantasyon dosyaları içerir -* `files`: dokümantasyon sayfalarına gömülebilen resimler + [Önizleme düzenleyicisi |https://editor.nette.org/] değişiklikleri doğrudan GitHub'a kaydetme özelliğine sahip olmadığından, kaynak metni panoya kopyalamanız (*Panoya kopyala düğmesini* kullanarak) ve ardından GitHub'daki düzenleyiciye yapıştırmanız gerekir. +Düzenleme alanının altında gönderim için bir form bulunmaktadır. Burada, düzenlemenizin nedenini kısaca özetlemeyi ve açıklamayı unutmayın. Gönderdikten sonra, daha fazla düzenlenebilen bir çekme isteği (PR) oluşturulur. -Uzantısı olmayan bir dosyanın yolu, belgelerdeki bir sayfanın URL'sine karşılık gelir. Böylece, `en/quickstart/single-post.texy` dosyası `doc.nette.org/en/quickstart/single-post` URL'sine sahip olacaktır. +Daha Büyük Düzenlemeler .[#toc-larger-edits] +-------------------------------------------- -Katkıda Bulunmak .[#toc-contributing] -------------------------------------- +Yalnızca GitHub arayüzüne güvenmek yerine Git sürüm kontrol sistemi ile çalışmanın temellerine aşina olmak daha uygundur. Git'e aşina değilseniz, [git - the simple guide |https://rogerdudler.github.io/git-guide/] 'a başvurabilir ve mevcut birçok [grafik istemciden |https://git-scm.com/downloads/guis] birini kullanmayı düşünebilirsiniz. -Belgelere katkıda bulunmak için [GitHub |https://github.com] 'da bir hesabınızın olması ve Git'in temellerini bilmeniz gerekir. Git'e aşina değilseniz, hızlı kılavuza göz atabilirsiniz: [git - basit kılavuz |https://rogerdudler.github.io/git-guide/] veya birçok grafik araçtan birini kullanın: [GIT - GUI istemcileri |https://git-scm.com/downloads/guis]. +Belgeleri aşağıdaki şekilde düzenleyin: -Doğrudan GitHub arayüzünde basit değişiklikler yapabilirsiniz. Ancak, [nette/docs |https://github.com/nette/docs] deposunun bir çatalını oluşturmak ve bilgisayarınıza klonlamak daha uygundur. Ardından uygun dalda değişiklikler yapın, değişikliği işleyin, GitHub'ınıza itin ve orijinal `nette/docs` deposuna bir çekme isteği gönderin. +1) GitHub'da [nette/docs |https://github.com/nette/docs] deposunun bir [çatalını |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] oluşturun +2) bu depoyu bilgisayarınıza [klonlayın |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] +3) daha sonra, [uygun dalda |#Documentation Structure]değişiklikler yapın +4) [Code-Checker |code-checker:] aracını kullanarak metinde fazladan boşluk olup olmadığını kontrol edin +5) değişiklikleri kaydedin (işleyin) +6) değişikliklerden memnunsanız, bunları GitHub'a çatalınıza gönderin +7) oradan, bir [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) oluşturarak bunları `nette/docs` deposuna gönderin + +Öneriler içeren yorumlar almak yaygındır. Önerilen değişiklikleri takip edin ve bunları dahil edin. Önerilen değişiklikleri yeni taahhütler olarak ekleyin ve GitHub'a yeniden gönderin. Asla sadece mevcut bir talebi değiştirmek için yeni bir talep oluşturmayın. + + +Dokümantasyon Yapısı .[#toc-documentation-structure] +---------------------------------------------------- -Her çekme isteğinden önce, metindeki fazladan boşlukları kontrol etmek için [Code-Checker |code-checker:] 'ı çalıştırmak iyi bir fikirdir. +Belgelerin tamamı GitHub'da [nette/docs |https://github.com/nette/docs] deposunda yer almaktadır. Güncel sürüm master dalında, eski sürümler ise `doc-3.x`, `doc-2.x` gibi dallarda yer almaktadır. -{{priority: -1}} +Her şubenin içeriği, ayrı dokümantasyon alanlarını temsil eden ana klasörlere bölünmüştür. Örneğin, `application/` https://doc.nette.org/en/application 'a, `latte/` https://latte.nette.org 'a, vb. karşılık gelir. Bu klasörlerin her biri dil mutasyonlarını temsil eden alt klasörler (`cs`, `en`, ...) ve isteğe bağlı olarak dokümantasyondaki sayfalara eklenebilecek resimler içeren bir `files` alt klasörü içerir. diff --git a/contributing/uk/code.texy b/contributing/uk/code.texy index 3a2cca1145..0461320b30 100644 --- a/contributing/uk/code.texy +++ b/contributing/uk/code.texy @@ -1,110 +1,118 @@ -Пропонуємо внести зміни до Кодексу -********************************** +Внесок до Коду +************** -Nette Framework використовує Git та [GitHub |https://github.com/nette/nette] для підтримки кодової бази. Найкращий спосіб внести свій вклад - це зафіксувати зміни у власному форку, а потім зробити запит на витягування на GitHub. У цьому документі коротко описані основні кроки для успішного внесення змін. +.[perex] +Ви плануєте зробити свій внесок у Nette Framework і хочете ознайомитися з правилами та процедурами? Цей посібник для початківців допоможе вам зробити ефективний внесок у код, працювати з репозиторіями та впроваджувати зміни. -Підготовка середовища .[#toc-preparing-environment] -=================================================== +Порядок дій .[#toc-procedure] +============================= -Почніть з [розгалуження |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [Nette на GitHub |https://github.com/nette]. Уважно [налаштуйте |https://help.github.com/en/github/getting-started-with-github/set-up-git] локальне середовище Git'а, задайте ім'я користувача та електронну пошту, ці облікові дані будуть ідентифікувати ваші зміни в історії Nette Framework. +Щоб долучитися до коду, необхідно мати обліковий запис на [GitHub |https://github.com] і бути знайомим з основами роботи з системою контролю версій Git. Якщо ви не знайомі з Git'ом, ви можете ознайомитися з [git - простим керівництвом |https://rogerdudler.github.io/git-guide/] і розглянути можливість використання одного з багатьох [графічних клієнтів |https://git-scm.com/downloads/guis]. -Робота над вашим патчем .[#toc-working-on-your-patch] -===================================================== +Підготовка середовища та репозиторію .[#toc-preparing-the-environment-and-repository] +------------------------------------------------------------------------------------- + +1) На GitHub створіть [форк |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] [сховища пакунків |www:packages], який ви збираєтеся модифікувати +2) [Клонуйте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] цей репозиторій на свій комп'ютер +3) Встановіть залежності, включаючи [Nette Tester |tester:], за допомогою команди `composer install` +4) Переконайтеся, що тести працюють, запустивши їх `composer tester` +5) Створіть [нову гі |#New Branch] лку на основі останньої випущеної версії + + +Впровадження власних змін .[#toc-implementing-your-own-changes] +--------------------------------------------------------------- + +Тепер ви можете вносити власні корективи в код: + +1) Впроваджуйте бажані зміни і не забувайте про тести +2) Переконайтеся, що тести успішно запускаються за допомогою `composer tester` +3) Перевірте, чи відповідає код [стандартам кодування |#coding standards] +4) Збережіть (зафіксуйте) зміни з описом у [такому форматі |#Commit Description] + +Ви можете створити декілька комітів, по одному для кожного логічного кроку. Кожен коміт повинен бути значущим сам по собі. -Перш ніж почати роботу над патчем, створіть нову гілку для своїх змін. -```shell -git checkout -b new_branch_name -``` -Ви можете працювати над зміною коду. +Подання змін .[#toc-submitting-changes] +--------------------------------------- -Якщо можливо, вносьте зміни з останньої випущеної версії. +Якщо ви задоволені змінами, ви можете відправити їх: +1) Перенесіть зміни на GitHub у свій форк +2) Звідти відправте їх до репозиторію Nette, створивши [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) +3) Надайте [достатньо інформації |#pull request description] в описі -Тестування ваших змін .[#toc-testing-your-changes] -================================================== -Вам потрібно встановити Nette Tester. Найпростіший спосіб - викликати `composer install` в корені сховища. Тепер ви зможете запускати тести за допомогою `./vendor/bin/tester` в терміналі. +Включення зворотного зв'язку .[#toc-incorporating-feedback] +----------------------------------------------------------- -Деякі тести можуть не працювати через відсутність php.ini. Тому вам слід викликати бігун з параметром -c і вказати шлях до php.ini, наприклад `./vendor/bin/tester -c ./tests/php.ini`. +Ваші комміти тепер видимі для інших. Ми часто отримуємо коментарі з пропозиціями: -Після того, як ви зможете запустити тести, ви можете реалізувати свої власні або змінити непрацюючі, щоб вони відповідали новій поведінці. Детальніше про тестування за допомогою Nette Tester читайте на [сторінці документації |tester:]. +1) Відстежуйте запропоновані зміни +2) Включіть їх як нові комміти або об'єднайте [з попередніми |https://help.github.com/en/github/using-git/about-git-rebase] +3) Повторно надішліть коміти на GitHub, і вони автоматично з'являться в запиті на витягування + +Ніколи не створюйте новий пул-запит для зміни існуючого. + + +Документація .[#toc-documentation] +---------------------------------- + +Якщо ви змінили функціонал або додали новий, не забудьте [додати його до документації |documentation]. + + +Нова гілка .[#toc-new-branch] +============================= + +Якщо можливо, вносьте зміни відповідно до останньої випущеної версії, тобто останнього тегу у гілці. Для тегу v3.2.1 створіть гілку за допомогою цієї команди: + +```shell +git checkout -b new_branch_name v3.2.1 +``` Стандарти кодування .[#toc-coding-standards] ============================================ -Ваш код повинен відповідати [стандартам кодування |coding standard], що використовуються в Nette Framework. Це легко зробити завдяки автоматизованій перевірці та виправленню помилок. Він вимагає PHP 7.1 і може бути встановлений через Composer у вибрану вами глобальну директорію: +Ваш код повинен відповідати [стандарту кодування |coding standard], що використовується в Nette Framework. Існує автоматичний інструмент для перевірки та виправлення коду. Ви можете встановити його **глобально** через Composer до папки на ваш вибір: ```shell composer create-project nette/coding-standard /path/to/nette-coding-standard ``` -Тепер ви зможете запустити інструмент у терміналі. Наприклад, ця команда перевіряє і виправляє код у теках `src` і `tests` у поточному каталозі: +Тепер ви зможете запустити інструмент у терміналі. Перша команда перевіряє, а друга - виправляє код у теках `src` і `tests` у поточному каталозі: ```shell -/path/to/nette-coding-standard/ecs check src tests --config /path/to/nette-coding-standard/coding-standard-php71.yml --fix +/path/to/nette-coding-standard/ecs check +/path/to/nette-coding-standard/ecs check --fix ``` -Фіксація змін .[#toc-committing-the-changes] -============================================ +Опис комміту .[#toc-commit-description] +======================================= -Після того, як ви змінили код, ви повинні зафіксувати зміни. Створіть більше коммітів, по одному для кожного логічного кроку. Кожен комміт має бути придатним для використання як є - без інших коммітів. Отже, відповідні тести також повинні бути включені в той самий комміт. +У Nette теми коммітів мають наступний формат: `Presenter: fixed AJAX detection [Closes #69]` -Будь ласка, перевірте, чи відповідає ваш код правилам: -- Код не генерує жодних помилок -- Ваш код не порушує жодного тесту. -- Ваша зміна коду протестована. -- Ви не вносите непотрібні зміни з пробілами. - -Повідомлення про фіксацію має відповідати формату `Latte: fixed multi template rendering [Closes # 69]` тобто - область, за якою слідує двокрапка -- мета комміту в минулому, якщо можливо, починайте зі слів "додано", "виправлено", "перероблено", "змінено", "вилучено -- можливе посилання на трекер випусків -- якщо комміт скасовує зворотну сумісність, додайте "BC break" -- після теми може бути один вільний рядок і більш детальний опис, включаючи посилання на форум. - +- мета комміту в минулому часі; якщо можливо, починайте зі слів на кшталт added, fixed, refactored, changed, removed +- якщо комміт порушує зворотну сумісність, додайте "BC break" +- будь-яке з'єднання з трекером випусків, наприклад, `(#123)` або `[Closes #69]` +- після теми може бути один порожній рядок, за яким слідує більш детальний опис, включаючи, наприклад, посилання на форум -Витягнення запиту на комміти .[#toc-pull-requesting-the-commits] -================================================================ -Якщо ви задоволені змінами в коді та комітами, вам потрібно відправити їх на GitHub. +Опис запиту на витягування .[#toc-pull-request-description] +=========================================================== -```shell -git push origin new_branch_name -``` +При створенні pull request інтерфейс GitHub дозволить вам ввести заголовок і опис. Надайте лаконічний заголовок і включіть якомога більше інформації в опис про причини вашої зміни. -Зміни присутні у відкритому доступі, однак, ви повинні запропонувати свої зміни для інтеграції в основну гілку Nette. Для цього створіть [pull request |https://help.github.com/articles/creating-a-pull-request]. -Кожен pull request має назву та опис. Будь ласка, надайте якийсь описовий заголовок. Часто вона схожа на назву гілки, наприклад, "Захист сигналів від CSRF-атаки". +Також вкажіть у заголовку, чи це нова функція, чи виправлення помилки, і чи може це спричинити проблеми зворотної сумісності (BC break). Якщо є пов'язана проблема, зробіть посилання на неї, щоб вона була закрита після схвалення запиту на вилучення. -В описі запиту слід вказати більш конкретну інформацію про зміни, які ви вносите до коду: ``` -- bug fix? yes/no <!-- #issue numbers, if any --> -- new feature? yes/no +- bug fix / new feature? <!-- #issue numbers, if any --> - BC break? yes/no -- doc PR: nette/docs#??? <!-- highly welcome, see https://nette.org/en/writing --> -``` - -Будь ласка, змініть інформаційну таблицю відповідно до вашого pull-запиту. Коментарі до кожного пункту списку: -- Зазначає, чи запит додає **можливість**, чи це **виправлення помилки**. -- Вказує на **пов'язану проблему**, яка буде закрита після об'єднання запиту. -- Показує, чи потребує запит **зміни документації**, якщо так, надайте посилання на відповідні запити. Вам не обов'язково надавати зміни до документації негайно, однак, якщо вони потрібні, запит не буде об'єднано. Зміна документації повинна бути підготовлена для англійської документації, інші мовні мутації не є обов'язковими. -- Каже, якщо pull request створює **розрив BC**. Будь ласка, розглядайте все, що змінює публічний інтерфейс, як розрив BC. - -Фінальна таблиця може виглядати так: +- doc PR: nette/docs#? <!-- highly welcome, see https://nette.org/en/writing --> ``` -- bug fix? no -- new feature? yes issue #123 -- BC break? no -``` - - -Переробляючи свої зміни .[#toc-reworking-your-changes] -====================================================== -Дуже часто ми отримуємо коментарі до змін у коді. Будь ласка, намагайтеся слідувати запропонованим змінам і переробляти свої коміти для цього. Ви можете зафіксувати запропоновані зміни як нові коміти, а потім витіснити їх до попередніх. Дивіться розділ [Інтерактивне |https://help.github.com/en/github/using-git/about-git-rebase] відновлення на GitHub. Після ребазирования виконайте примусове перенесення змін у віддалений форк, і все буде автоматично поширено в пул-запит. {{priority: -1}} diff --git a/contributing/uk/coding-standard.texy b/contributing/uk/coding-standard.texy index 059161cb33..6657393295 100644 --- a/contributing/uk/coding-standard.texy +++ b/contributing/uk/coding-standard.texy @@ -38,7 +38,7 @@ Nette Coding Standard відповідає PSR-12 (або PER Coding Style), у - стрілочні функції записуються без пробілу перед дужкою, тобто `fn($a) => $b`. - між різними типами операторів імпорту `use` не потрібен порожній рядок -- тип функції/методу, що повертається, і дужка, що відкриває, повинні розташовуватися на окремих рядках для кращої читабельності: +- тип повернення функції/методу та відкриваюча фігурна дужка завжди знаходяться в різних рядках: ```php public function find( @@ -50,6 +50,10 @@ Nette Coding Standard відповідає PSR-12 (або PER Coding Style), у } ``` +Фігурна дужка, що відкривається окремим рядком, важлива для візуального відокремлення підпису функції/методу від основного тексту. Якщо підпис розміщено в одному рядку, поділ чіткий (зображення ліворуч), якщо в кількох рядках, то в PSR підписи і тіло зливаються разом (посередині), тоді як у стандарті Nette вони залишаються відокремленими (праворуч): + +[* new-line-after.webp *] + Блоки документації (phpDoc) .[#toc-documentation-blocks-phpdoc] =============================================================== diff --git a/contributing/uk/documentation.texy b/contributing/uk/documentation.texy index 40763297ff..f70e43a7c8 100644 --- a/contributing/uk/documentation.texy +++ b/contributing/uk/documentation.texy @@ -1,53 +1,69 @@ -Написання документації -********************** +Долучайтеся до створення документації +************************************* .[perex] -Внесок у документацію - це один з багатьох способів допомогти Nette. Це також один з найбільш корисних видів діяльності, оскільки ви допомагаєте іншим зрозуміти фреймворк. +Внесок у документацію є одним з найцінніших видів діяльності, оскільки він допомагає іншим зрозуміти структуру. Як писати? .[#toc-how-to-write] ------------------------------- -Документація в першу чергу призначена для людей, які тільки знайомляться з темою. Тому вона має відповідати кільком важливим пунктам: +Документація в першу чергу призначена для людей, які є новачками в темі. Тому вона повинна відповідати декільком важливим пунктам: -- **При написанні документації починайте з простого і загального, а в кінці переходьте до більш складних тем.**. -- Надавайте лише ту інформацію, яку користувачеві дійсно потрібно знати про тему. -- Переконайтеся, що ваша інформація дійсно правдива. Перш ніж наводити приклад, спочатку протестуйте його. -- Будьте лаконічними - скоротіть те, що ви пишете, наполовину. А потім не соромтеся повторити це ще раз. -- Намагайтеся пояснити питання якомога краще. Наприклад, спробуйте спочатку пояснити тему колезі. +- Починайте з простих і загальних тем. Переходьте до більш складних тем наприкінці +- Намагайтеся пояснювати тему якомога зрозуміліше. Наприклад, спробуйте спочатку пояснити тему колезі +- Надавайте лише ту інформацію, яку користувачеві дійсно потрібно знати з даної теми +- Переконайтеся, що ваша інформація точна. Тестуйте кожен код +- Будьте лаконічними - скорочуйте те, що пишете, вдвічі. А потім не соромтеся робити це ще раз +- Економно використовуйте виділення, від напівжирного шрифту до рамок, таких як `.[note]` +- Дотримуйтесь [стандарту кодування |Coding Standard] в коді -Пам'ятайте про ці моменти протягом усього процесу написання. Документація написана мовою [Texy! |https://texy.info], тому вивчіть її [синтаксис |syntax]. Ви можете скористатися редактором документації на https://editor.nette.org/, щоб переглянути статтю під час її написання. +Також вивчайте [синтаксис |syntax]. Для попереднього перегляду статті під час написання ви можете скористатися [редактором попереднього перегляду |https://editor.nette.org/]. -Серед загальних правил написання документації, перерахованих вище, будь ласка, дотримуйтесь наступних: -- Ваш код повинен відповідати [Стандарту кодування |Coding Standard]. -- Пишіть назви змінних, класів і методів англійською мовою. -- Простори імен мають бути згадані лише при першій згадці. -- Намагайтеся форматувати код так, щоб не відображалися смуги прокрутки. -- Відмовтеся від усіх видів виділень, від напівжирного шрифту до `.[note]` рамок. -- З документації посилайтеся тільки на документацію або `www`. +Мовні мутації .[#toc-language-mutations] +---------------------------------------- +Англійська є основною мовою, тому ваші зміни повинні бути англійською. Якщо англійська не є вашою сильною стороною, скористайтеся [DeepL Перекладачем |https://www.deepl.com/translator], і інші перевірять ваш текст. -Структура документації .[#toc-documentation-structure] ------------------------------------------------------- +Переклад на інші мови буде зроблено автоматично після затвердження та уточнення вашої правки. + + +Тривіальні правки .[#toc-trivial-edits] +--------------------------------------- + +Щоб зробити свій внесок у документацію, вам потрібно мати обліковий запис на [GitHub |https://github.com]. -Повна документація розміщена на GitHub у репозиторії [nette/docs |https://github.com/nette/docs]. Це сховище розділене на гілки відповідно до версії документації, наприклад, гілка `doc-3.1` містить документацію для версії 3.1. А ще є гілка `nette.org`, яка містить вміст інших субдоменів nette.org. +Найпростіший спосіб внести невелику зміну в документацію - скористатися посиланнями в кінці кожної сторінки: -Кожна гілка поділяється на кілька папок: +- *Показати на GitHub* відкриває вихідну версію сторінки на GitHub. Потім просто натисніть кнопку `E` і ви можете почати редагування (ви повинні бути зареєстровані на GitHub) +- *Відкрити попередній перегляд* відкриває редактор, де ви можете одразу побачити остаточний візуальний вигляд -* `cs` і `en`: містять файли документації для кожної мовної версії -* `files`: зображення, які можна вставити на сторінки документації +Оскільки [редактор поперед |https://editor.nette.org/] нього перегляду не має можливості зберігати зміни безпосередньо на GitHub, вам потрібно скопіювати вихідний текст в буфер обміну (за допомогою кнопки *Копіювати в буфер обміну*), а потім вставити його в редактор на GitHub. +Під полем для редагування знаходиться форма для відправки. Тут не забудьте коротко підсумувати і пояснити причину вашого редагування. Після відправки створюється так званий pull request (PR), який можна надалі редагувати. -Шлях до файлу без розширення відповідає URL-адресі сторінки в документації. Таким чином, файл `en/quickstart/single-post.texy` матиме URL `doc.nette.org/en/quickstart/single-post`. +Більші редагування .[#toc-larger-edits] +--------------------------------------- -Дописувач .[#toc-contributing] ------------------------------- +Доцільніше бути знайомим з основами роботи з системою контролю версій Git, ніж покладатися виключно на інтерфейс GitHub. Якщо ви не знайомі з Git'ом, ви можете звернутися до [git - простий |https://rogerdudler.github.io/git-guide/] посібник і розглянути можливість використання одного з багатьох доступних [графічних клієнтів |https://git-scm.com/downloads/guis]. -Щоб зробити внесок у документацію, ви повинні мати обліковий запис на [GitHub |https://github.com] і знати основи Git'у. Якщо ви не знайомі з Git'ом, ви можете ознайомитися з коротким керівництвом: [git - простий |https://rogerdudler.github.io/git-guide/] посібник, або скористатися одним з багатьох графічних інструментів: [GIT - графічні клієнти |https://git-scm.com/downloads/guis]. +Редагуйте документацію наступним чином: -Ви можете вносити прості зміни безпосередньо в інтерфейсі GitHub. Однак, зручніше створити форк репозиторію [nette/docs |https://github.com/nette/docs] і клонувати його на свій комп'ютер. Потім внести зміни у відповідній гілці, зафіксувати зміни, перенести на свій GitHub і відправити pull request до оригінального репозиторію `nette/docs`. +1) на GitHub створіть [форк |https://help.github.com/en/github/getting-started-with-github/fork-a-repo] сховища [nette/docs |https://github.com/nette/docs] +2) [клонуйте |https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository] цей репозиторій на свій комп'ютер +3) потім внесіть зміни у [відповідній гілці |#Documentation Structure] +4) перевірте наявність зайвих пробілів у тексті за допомогою інструменту [Code-Checker |code-checker:] +5) збережіть (зафіксуйте) зміни +6) якщо зміни вас влаштовують, перенесіть їх на GitHub у свій форк +7) звідти відправте їх до репозиторію `nette/docs`, створивши [pull request|https://help.github.com/articles/creating-a-pull-request] (PR) + +Зазвичай ви отримуєте коментарі з пропозиціями. Відстежуйте запропоновані зміни та вносьте їх. Додайте запропоновані зміни як нові коміти і повторно надішліть їх на GitHub. Ніколи не створюйте новий пул-запит лише для того, щоб змінити існуючий. + + +Структура документації .[#toc-documentation-structure] +------------------------------------------------------ -Перед кожним pull-запитом бажано запускати [Code-Checker |code-checker:], щоб перевірити наявність зайвих пробілів у тексті. +Вся документація знаходиться на GitHub в репозиторії [nette/docs |https://github.com/nette/docs]. Поточна версія знаходиться в головній гілці, тоді як старіші версії знаходяться в гілках `doc-3.x`, `doc-2.x`. -{{priority: -1}} +Вміст кожної гілки розділено на основні папки, що представляють окремі області документації. Наприклад, `application/` відповідає https://doc.nette.org/en/application, `latte/` відповідає https://latte.nette.org і т.д. Кожна з цих папок містить підпапки, що представляють мовні мутації (`cs`, `en`, ...) і додатково підпапку `files` з зображеннями, які можна вставляти на сторінки документації. diff --git a/database/cs/explorer.texy b/database/cs/explorer.texy index 9a3acc8ea0..1ed2cf95a5 100644 --- a/database/cs/explorer.texy +++ b/database/cs/explorer.texy @@ -463,7 +463,7 @@ if ($book->translator) { } ``` -Pokud chceme získat autora více knih, použijeme stejný přístup. Nette Database Explorer za nás z databáze záznamy autorů a překladatelů pro všechny knihy najednou. +Pokud chceme získat autora více knih, použijeme stejný přístup. Nette Database Explorer vybere z databáze záznamy autorů a překladatelů pro všechny knihy najednou. ```php $books = $explorer->table('book'); diff --git a/database/en/explorer.texy b/database/en/explorer.texy index fb1820797e..77e8f5a91c 100644 --- a/database/en/explorer.texy +++ b/database/en/explorer.texy @@ -345,7 +345,7 @@ Fetching Data Insert, Update & Delete ======================= -Method `insert()` accepts array of Traversable objects (for example [ArrayHash |utils:arrays#ArrayHash] which returns [forms|forms:]): +Method `insert()` accepts array or Traversable objects (for example [ArrayHash |utils:arrays#ArrayHash] which returns [forms|forms:]): ```php $row = $explorer->table('users')->insert([ diff --git a/dependency-injection/bg/@home.texy b/dependency-injection/bg/@home.texy index 4dec44c7dc..5db2ff419d 100644 --- a/dependency-injection/bg/@home.texy +++ b/dependency-injection/bg/@home.texy @@ -5,8 +5,10 @@ Dependency Injection е шаблон за проектиране, който ще промени из основи начина, по който гледате на кода и разработката. Той открива пътя към свят на чисто проектирани и устойчиви приложения. - [Какво е вкарване на зависимости? |introduction] -- [Какво е DI контейнер? |container] +- [Глобално състояние и единични елементи |global-state] - [Предаване на зависимости |passing-dependencies] +- [Какво е DI контейнер? |container] +- [Често задавани въпроси |faq] Nette DI diff --git a/dependency-injection/bg/@left-menu.texy b/dependency-injection/bg/@left-menu.texy index e2dceb8d8b..4d8328ab57 100644 --- a/dependency-injection/bg/@left-menu.texy +++ b/dependency-injection/bg/@left-menu.texy @@ -1,8 +1,10 @@ Изпълнение на зависимостта ************************** - [Какво е прилагане на зависимостта? |introduction] -- [Какво е контейнер DI? |container] +- [Глобално състояние и единични елементи |global-state] - [Прехвърляне на зависимостта |passing-dependencies] +- [Какво е контейнер DI? |container] +- [Често задавани въпроси |faq] Nette DI diff --git a/dependency-injection/bg/faq.texy b/dependency-injection/bg/faq.texy new file mode 100644 index 0000000000..392c71c995 --- /dev/null +++ b/dependency-injection/bg/faq.texy @@ -0,0 +1,112 @@ +Често задавани въпроси за DI (FAQ) +********************************** + + +DI ли е другото име на IoC? .[#toc-is-di-another-name-for-ioc] +-------------------------------------------------------------- + +*Инверсия на контрола* (IoC) е принцип, насочен към начина, по който се изпълнява кодът - дали вашият код инициира външен код или вашият код е интегриран във външен код, който след това го извиква. +IoC е широка концепция, която включва [събития |nette:glossary#Events], така наречения [холивудски принцип |application:components#Hollywood style] и други аспекти. +Фабриките, които са част от [Правило № 3: Нека фабриката да се справи с него |introduction#Rule #3: Let the Factory Handle It], и представляват инверсия за оператора `new`, също са компоненти на тази концепция. + +*Dependency Injection* (DI) е за това как един обект знае за друг обект, т.е. за зависимостта. Това е шаблон за проектиране, който изисква изрично предаване на зависимости между обектите. + +Следователно може да се каже, че DI е специфична форма на IoC. Не всички форми на IoC обаче са подходящи от гледна точка на чистотата на кода. Например сред антимоделите включваме всички техники, които работят с [глобално състояние |global state] или така наречения [Service Locator |#What is a Service Locator]. + + +Какво представлява Service Locator? .[#toc-what-is-a-service-locator] +--------------------------------------------------------------------- + +Service Locator е алтернатива на Dependency Injection. Той работи чрез създаване на централно хранилище, в което се регистрират всички налични услуги или зависимости. Когато даден обект се нуждае от зависимост, той я заявява от Service Locator. + +Въпреки това, в сравнение с Dependency Injection, той губи прозрачност: зависимостите не се предават директно на обектите и следователно не са лесно разпознаваеми, което изисква разглеждане на кода, за да се открият и разберат всички връзки. Тестването също така е по-сложно, тъй като не можем просто да предаваме подигравателни обекти на тестваните обекти, а трябва да преминем през Service Locator. Освен това Service Locator нарушава дизайна на кода, тъй като отделните обекти трябва да знаят за неговото съществуване, което се различава от Dependency Injection, при която обектите не знаят за контейнера DI. + + +Кога е по-добре да не се използва DI? .[#toc-when-is-it-better-not-to-use-di] +----------------------------------------------------------------------------- + +Не са известни трудности, свързани с използването на шаблона за проектиране Dependency Injection. Напротив, получаването на зависимости от глобално достъпни места води до [редица усложнения, |global-state] както и използването на Service Locator. +Затова е препоръчително винаги да използвате DI. Това не е догматичен подход, но просто не е намерена по-добра алтернатива. + +Въпреки това има някои ситуации, в които не предаваме обекти един на друг и ги получаваме от глобалното пространство. Например при отстраняване на грешки в кода, когато е необходимо да се изведе стойност на променлива в определен момент от програмата, да се измери продължителността на определена част от програмата или да се регистрира съобщение. +В такива случаи, когато става въпрос за временни действия, които по-късно ще бъдат премахнати от кода, е закономерно да се използва глобално достъпен дъмпер, хронометър или логер. В крайна сметка тези инструменти не принадлежат към дизайна на кода. + + +Има ли използването на DI своите недостатъци? .[#toc-does-using-di-have-its-drawbacks] +-------------------------------------------------------------------------------------- + +Използването на Dependency Injection има ли някакви недостатъци, като например по-голяма сложност при писането на код или по-лоша производителност? Какво губим, когато започнем да пишем код в съответствие с DI? + +DI не оказва влияние върху производителността на приложението или изискванията за памет. Производителността на DI контейнера може да играе роля, но в случая на [Nette DI | nette-container] контейнерът е компилиран в чист PHP, така че натоварването му по време на изпълнение на приложението е по същество нулево. + +При писане на код е необходимо да се създадат конструктори, които приемат зависимости. В миналото това можеше да отнеме много време, но благодарение на съвременните IDE и [промотирането на свойствата на конструкторите |https://blog.nette.org/bg/php-8-0-p-len-pregled-na-novostite#toc-constructor-property-promotion] сега е въпрос на няколко секунди. Конструкторите могат да се генерират лесно с помощта на Nette DI и приставка за PhpStorm само с няколко кликвания. +От друга страна, не е необходимо да се пишат синглетони и статични точки за достъп. + +Може да се заключи, че едно правилно проектирано приложение, използващо DI, не е нито по-кратко, нито по-дълго в сравнение с приложение, използващо синглетони. Частите от кода, работещи със зависимости, просто се извличат от отделните класове и се преместват на нови места, т.е. в контейнера и фабриките на DI. + + +Как да пренапишем наследено приложение към DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] +------------------------------------------------------------------------------------------------ + +Мигрирането от наследено приложение към Dependency Injection може да бъде труден процес, особено за големи и сложни приложения. Важно е към този процес да се подходи систематично. + +- При преминаването към Dependency Injection е важно всички членове на екипа да разбират принципите и практиките, които се използват. +- Първо, направете анализ на съществуващото приложение, за да идентифицирате ключовите компоненти и техните зависимости. Създайте план за това кои части ще бъдат префактурирани и в какъв ред. +- Реализирайте контейнер за DI или, още по-добре, използвайте съществуваща библиотека като Nette DI. +- Постепенно преработете всяка част от приложението, за да използвате впръскване на зависимости. Това може да включва модифициране на конструктори или методи, за да приемат зависимости като параметри. +- Променете местата в кода, където се създават обекти на зависимости, така че вместо това зависимостите да се инжектират от контейнера. Това може да включва използването на фабрики. + +Не забравяйте, че преминаването към инжектиране на зависимости е инвестиция в качеството на кода и дългосрочната устойчивост на приложението. Въпреки че може да е предизвикателство да се направят тези промени, резултатът трябва да бъде по-чист, по-модулен и лесно тестваем код, който е готов за бъдещи разширения и поддръжка. + + +Защо композицията е за предпочитане пред наследяването? .[#toc-why-composition-is-preferred-over-inheritance] +------------------------------------------------------------------------------------------------------------- +За предпочитане е да се използва композиция, а не наследяване, тъй като тя служи за целите на повторната употреба на кода, без да е необходимо да се притеснявате за ефекта от промяната. По този начин се осигурява по-свободно свързване, при което не е необходимо да се притесняваме, че промяната на някакъв код ще доведе до промяна на друг зависим код. Типичен пример за това е ситуацията, наречена [ад на конструкторите |passing-dependencies#Constructor hell]. + + +Може ли Nette DI Container да се използва извън Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] +-------------------------------------------------------------------------------------------------------------- + +Абсолютно. Nette DI Container е част от Nette, но е проектирана като самостоятелна библиотека, която може да се използва независимо от други части на рамката. Просто я инсталирайте с помощта на Composer, създайте конфигурационен файл, определящ вашите услуги, и след това използвайте няколко реда PHP код, за да създадете DI контейнера. +И веднага можете да започнете да се възползвате от предимствата на Dependency Injection във вашите проекти. + +Главата [Nette DI Container |nette-container] описва как изглежда конкретен случай на употреба, включително кода. + + +Защо конфигурацията е във файлове NEON? .[#toc-why-is-the-configuration-in-neon-files] +-------------------------------------------------------------------------------------- + +NEON е прост и лесен за четене език за конфигуриране, разработен в Nette, за настройка на приложения, услуги и техните зависимости. В сравнение с JSON или YAML той предлага много по-интуитивни и гъвкави възможности за тази цел. В NEON можете по естествен начин да описвате връзки, които не биха били възможни да се напишат в Symfony & YAML или изобщо, или само чрез сложно описание. + + +Обработката на NEON файловете забавя ли приложението? .[#toc-does-parsing-neon-files-slow-down-the-application] +--------------------------------------------------------------------------------------------------------------- + +Въпреки че файловете NEON се анализират много бързо, този аспект няма особено значение. Причината е, че парсирането на файловете се извършва само веднъж при първото стартиране на приложението. След това кодът на DI контейнера се генерира, съхранява се на диска и се изпълнява за всяка следваща заявка, без да е необходимо допълнително разбор. + +Това е начинът, по който се работи в производствена среда. По време на разработката NEON файловете се анализират всеки път, когато съдържанието им се промени, което гарантира, че разработчикът винаги разполага с актуален DI контейнер. Както беше споменато по-рано, действителното парсване е въпрос на един миг. + + +Как да получа достъп до параметрите от конфигурационния файл в моя клас? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +------------------------------------------------------------------------------------------------------------------------------------------------------- + +Имайте предвид [Правило № 1: Позволете да ви бъде предадено |introduction#Rule #1: Let It Be Passed to You]. Ако даден клас изисква информация от конфигурационен файл, не е необходимо да измисляме как да получим достъп до тази информация; вместо това просто я изискваме - например чрез конструктора на класа. И извършваме предаването в конфигурационния файл. + +В този пример `%myParameter%` е заместител на стойността на параметъра `myParameter`, който ще бъде предаден на конструктора `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Ако искате да предадете няколко параметъра или да използвате автоматично свързване, е полезно да [обвиете параметрите в обект |best-practices:passing-settings-to-presenters]. + + +Поддържа ли Nette интерфейса PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] +---------------------------------------------------------------------------------------------------- + +Nette DI Container не поддържа PSR-11 директно. Въпреки това, ако се нуждаете от оперативна съвместимост между Nette DI Container и библиотеки или рамки, които очакват PSR-11 Container Interface, можете да създадете [прост адаптер |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], който да служи като мост между Nette DI Container и PSR-11. diff --git a/dependency-injection/bg/global-state.texy b/dependency-injection/bg/global-state.texy new file mode 100644 index 0000000000..c3c95d1be7 --- /dev/null +++ b/dependency-injection/bg/global-state.texy @@ -0,0 +1,312 @@ +Глобално състояние и единични числа +*********************************** + +.[perex] +Предупреждение: Следните конструкции са симптоми на лош дизайн на кода: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` или `static::$var` + +Среща ли се някоя от тези конструкции във вашия код? Тогава имате възможност да се подобрите. Може би си мислите, че това са често срещани конструкции, които виждаме в примерни решения на различни библиотеки и фреймуърки. +За съжаление, те все още са ясен индикатор за лош дизайн. Те имат една обща черта: използването на глобално състояние. + +Сега със сигурност не става дума за някаква академична чистота. Използването на глобално състояние и синглетони има разрушителен ефект върху качеството на кода. Поведението му става непредсказуемо, намалява производителността на разработчиците и принуждава интерфейсите на класовете да лъжат за истинските си зависимости. Което обърква програмистите. + +В тази глава ще покажем как това е възможно. + + +Глобално свързване .[#toc-global-interlinking] +---------------------------------------------- + +Основният проблем на глобалната държава е, че тя е глобално достъпна. Това дава възможност да се записва в базата данни чрез глобалния (статичен) метод `DB::insert()`. +В един идеален свят даден обект трябва да може да комуникира само с други обекти, които са му били [директно предадени |passing-dependencies]. +Ако създам два обекта `A` и `B` и никога не предам референция от `A` на `B`, тогава нито `A`, нито `B` могат да получат достъп до другия обект или да променят състоянието му. +Това е много желана характеристика на кода. Подобно е на това да имате батерия и електрическа крушка; крушката няма да светне, докато не ги свържете. + +Това не е вярно за глобалните (статични) променливи или единичните променливи. Обектът `A` би могъл да получи *безжичен* достъп до обекта `C` и да го модифицира, без да предава никаква референция, като извика `C::changeSomething()`. +Ако обектът `B` грабне и глобалната променлива `C`, тогава `A` и `B` могат да взаимодействат помежду си чрез `C`. + +Използването на глобални променливи въвежда нова форма на *безжично* свързване в системата, която не е видима отвън. +Тя създава димна завеса, която усложнява разбирането и използването на кода. +Разработчиците трябва да прочетат всеки ред от изходния код, за да разберат наистина зависимостите. Вместо просто да се запознаят с интерфейса на класовете. +Освен това това е напълно ненужно свързване. + +.[note] +По отношение на поведението няма разлика между глобална и статична променлива. Те са еднакво вредни. + + +Призрачно действие на разстояние (Spooky Action at a Distance) .[#toc-the-spooky-action-at-a-distance] +------------------------------------------------------------------------------------------------------ + +"Призрачно действие на разстояние" - така Алберт Айнщайн нарича едно явление в квантовата физика, което през 1935 г. му докарало ужас. +Става дума за квантовото заплитане, чиято особеност е, че когато измервате информация за една частица, веднага въздействате върху друга частица, дори ако те са на милиони светлинни години една от друга. +което привидно нарушава фундаменталния закон на Вселената, според който нищо не може да се движи по-бързо от светлината. + +В света на софтуера можем да наречем "spooky action at a distance" ситуация, при която стартираме процес, който смятаме за изолиран (защото не сме му предали никакви референции), но в отдалечени места на системата се случват неочаквани взаимодействия и промени в състоянието, за които не сме казали на обекта. Това може да се случи само чрез глобалното състояние. + +Представете си, че се присъедините към екип за разработване на проект, който има голяма, зряла база от кодове. Новият ви ръководител ви моли да реализирате нова функция и като добър разработчик започвате с написването на тест. Но тъй като сте нов в проекта, правите много проучвателни тестове от типа "какво ще стане, ако извикам този метод". И се опитвате да напишете следния тест: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // номера на картата ви. + $cc->charge(100); +} +``` + +Изпълнявате кода, може би няколко пъти, и след известно време забелязвате на телефона си известия от банката, че всеки път, когато го изпълнявате, от кредитната ви карта са били изтеглени 100 долара 🤦‍♂️ + +Как, за Бога, тестът би могъл да предизвика действително таксуване? Не е лесно да се оперира с кредитна карта. Трябва да взаимодействате с уеб услуга на трета страна, трябва да знаете URL адреса на тази уеб услуга, трябва да влезете в системата и т.н. +Нито една от тези информации не е включена в теста. Още по-лошо, дори не знаете къде присъства тази информация и следователно как да издекламирате външните зависимости, така че всяко изпълнение да не води до повторно таксуване на 100 USD. А като нов разработчик откъде трябваше да знаете, че това, което ще направите, ще доведе до обедняването ви със 100 долара? + +Това е призрачно действие от разстояние! + +Не ви остава нищо друго, освен да се ровите в много изходен код, да питате по-възрастни и по-опитни колеги, докато разберете как работят връзките в проекта. +Това се дължи на факта, че когато разглеждате интерфейса на класа `CreditCard`, не можете да определите глобалното състояние, което трябва да бъде инициализирано. Дори разглеждането на изходния код на класа няма да ви подскаже кой метод за инициализация да извикате. В най-добрия случай можете да намерите глобалната променлива, до която се осъществява достъп, и да се опитате да предположите как да я инициализирате от нея. + +Класовете в такъв проект са патологични лъжци. Платежната карта се преструва, че можете просто да я инстанцирате и да извикате метода `charge()`. Тя обаче тайно взаимодейства с друг клас, `PaymentGateway`. Дори интерфейсът му казва, че може да бъде инициализиран самостоятелно, но в действителност той тегли идентификационни данни от някакъв конфигурационен файл и така нататък. +За разработчиците, които са написали този код, е ясно, че `CreditCard` се нуждае от `PaymentGateway`. Те са написали кода по този начин. Но за всеки, който е нов в проекта, това е пълна загадка и пречи на обучението. + +Как да поправим ситуацията? Лесно. **Дайте възможност на API да декларира зависимостите.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Забележете как връзките в кода изведнъж стават очевидни. Като декларирате, че методът `charge()` се нуждае от `PaymentGateway`, не е нужно да питате никого как кодът е взаимозависим. Знаете, че трябва да създадете негова инстанция, а когато се опитате да го направите, се сблъсквате с факта, че трябва да предоставите параметри за достъп. Без тях кодът дори не би могъл да се изпълни. + +И най-важното, сега можете да издекламирате шлюза за плащане, така че да не ви таксуват по 100 долара всеки път, когато стартирате тест. + +Глобалното състояние кара обектите ви да могат тайно да получават достъп до неща, които не са декларирани в техните API, и в резултат на това прави API-тата ви патологични лъжци. + +Може и да не сте го мислили по този начин преди, но винаги когато използвате глобално състояние, създавате тайни безжични канали за комуникация. Страховитото отдалечено действие принуждава разработчиците да четат всеки ред код, за да разберат потенциалните взаимодействия, намалява производителността на разработчиците и обърква новите членове на екипа. +Ако вие сте този, който е създал кода, знаете истинските зависимости, но всеки, който идва след вас, е безпомощен. + +Не пишете код, който използва глобално състояние, а предпочитайте да предавате зависимости. Това е инжектиране на зависимости. + + +Крехкостта на глобалната държава .[#toc-brittleness-of-the-global-state] +------------------------------------------------------------------------ + +В код, който използва глобално състояние и единични елементи, никога не е сигурно кога и от кого е променено това състояние. Този риск е налице още при инициализацията. Следващият код трябва да създаде връзка с база данни и да инициализира шлюза за плащания, но продължава да хвърля изключение и намирането на причината е изключително трудоемко: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Трябва да прегледате подробно кода, за да откриете, че обектът `PaymentGateway` осъществява безжичен достъп до други обекти, някои от които изискват връзка с база данни. По този начин трябва да инициализирате базата данни, преди `PaymentGateway`. Димната завеса на глобалното състояние обаче скрива това от вас. Колко време бихте спестили, ако API на всеки клас не лъже и не декларира своите зависимости? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Подобен проблем възниква, когато използвате глобален достъп до връзка с база данни: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Когато се извиква методът `save()`, не е сигурно дали вече е създадена връзка към базата данни и кой е отговорен за създаването ѝ. Например, ако искаме да променим връзката с базата данни в движение, може би за целите на тестването, вероятно ще трябва да създадем допълнителни методи като `DB::reconnect(...)` или `DB::reconnectForTest()`. + +Разгледайте един пример: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Откъде можем да сме сигурни, че тестовата база данни наистина се използва при извикването на `$article->save()`? Какво ще стане, ако методът `Foo::doSomething()` промени глобалната връзка към базата данни? За да разберем това, ще трябва да разгледаме изходния код на класа `Foo` и вероятно на много други класове. Този подход обаче би дал само краткосрочен отговор, тъй като ситуацията може да се промени в бъдеще. + +Какво ще стане, ако преместим връзката с базата данни в статична променлива вътре в класа `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Това изобщо не променя нищо. Проблемът е глобално състояние и няма значение в кой клас се крие. В този случай, както и в предишния, нямаме представа в коя база данни се записва, когато се извика методът `$article->save()`. Всеки, който се намира в далечния край на приложението, може да промени базата данни по всяко време, като използва `Article::setDb()`. Под нашите ръце. + +Глобалното състояние прави нашето приложение **изключително крехко**. + +Има обаче прост начин да се справим с този проблем. Просто накарайте API да декларира зависимости, за да се осигури правилна функционалност. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Този подход елиминира притеснението от скрити и неочаквани промени във връзките с бази данни. Сега сме сигурни къде се съхранява статията и никакви модификации на кода вътре в друг несвързан клас вече не могат да променят ситуацията. Кодът вече не е крехък, а стабилен. + +Не пишете код, който използва глобално състояние, а предпочитайте да предавате зависимости. По този начин се въвежда инжектиране на зависимости. + + +Singleton .[#toc-singleton] +--------------------------- + +Singleton е шаблон за проектиране, който по [дефиниция от |https://en.wikipedia.org/wiki/Singleton_pattern] известната публикация на Gang of Four ограничава класа до един екземпляр и предлага глобален достъп до него. Реализацията на този шаблон обикновено прилича на следния код: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // и други методи, които изпълняват функциите на класа. +} +``` + +За съжаление сингълтонът въвежда глобално състояние в приложението. А както показахме по-горе, глобалното състояние е нежелателно. Ето защо сингълтонът се счита за антимодел. + +Не използвайте сингълтони в кода си и ги заменете с други механизми. Наистина не се нуждаете от синглетони. Ако обаче трябва да гарантирате съществуването на една единствена инстанция на даден клас за цялото приложение, оставете това на [DI контейнера |container]. +По този начин създайте синглетон на приложението или услуга. Това ще попречи на класа да осигури собствената си уникалност (т.е. той няма да има `getInstance()` метод и статична променлива) и ще изпълнява само своите функции. По този начин той ще спре да нарушава принципа на единичната отговорност. + + +Глобално състояние срещу тестове .[#toc-global-state-versus-tests] +------------------------------------------------------------------ + +Когато пишем тестове, приемаме, че всеки тест е изолирана единица и че в него не влиза никакво външно състояние. И никое състояние не напуска тестовете. Когато тестът завърши, всяко състояние, свързано с него, трябва да бъде премахнато автоматично от garbage collector. Това прави тестовете изолирани. Следователно можем да изпълняваме тестовете в произволен ред. + +Ако обаче са налице глобални състояния/синглетони, всички тези хубави предположения се развалят. Едно състояние може да влиза и излиза от тест. Изведнъж редът на тестовете може да има значение. + +За да могат изобщо да тестват единични състояния, разработчиците често трябва да смекчат техните свойства, може би като позволят даден екземпляр да бъде заменен с друг. Такива решения в най-добрия случай са хакове, които създават код, който е труден за поддържане и разбиране. Всеки тест или метод `tearDown()`, който засяга някое глобално състояние, трябва да отмени тези промени. + +Глобалното състояние е най-голямото главоболие при тестването на единици! + +Как да поправим ситуацията? Лесно. Не пишете код, който използва singletons, а предпочитайте да предавате зависимости. Това е инжектиране на зависимости. + + +Глобални константи .[#toc-global-constants] +------------------------------------------- + +Глобалното състояние не се ограничава само до използването на единични и статични променливи, но може да се прилага и за глобални константи. + +Константи, чиято стойност не ни предоставя никаква нова (`M_PI`) или полезна (`PREG_BACKTRACK_LIMIT_ERROR`) информация, са очевидно ОК. +Обратно, константи, които служат като начин за *безжично* предаване на информация вътре в кода, не са нищо повече от скрита зависимост. Като `LOG_FILE` в следния пример. +Използването на константата `FILE_APPEND` е напълно правилно. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +В този случай трябва да декларираме параметъра в конструктора на класа `Foo`, за да го направим част от API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Сега можем да подаваме информация за пътя до файла за регистриране и лесно да го променяме при необходимост, което улеснява тестването и поддръжката на кода. + + +Глобални функции и статични методи .[#toc-global-functions-and-static-methods] +------------------------------------------------------------------------------ + +Искаме да подчертаем, че използването на статични методи и глобални функции само по себе си не е проблематично. Обяснихме неуместността на използването на `DB::insert()` и други подобни методи, но винаги е ставало въпрос за глобално състояние, съхранявано в статична променлива. Методът `DB::insert()` изисква съществуването на статична променлива, защото съхранява връзката с базата данни. Без тази променлива би било невъзможно да се реализира методът. + +Използването на детерминистични статични методи и функции, като например `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` и много други, е напълно съвместимо с инжектирането на зависимости. Тези функции винаги връщат едни и същи резултати от едни и същи входни параметри и следователно са предвидими. Те не използват никакво глобално състояние. + +В PHP обаче има функции, които не са детерминирани. Сред тях е например функцията `htmlspecialchars()`. Нейният трети параметър, `$encoding`, ако не е посочен, по подразбиране е стойността на конфигурационната опция `ini_get('default_charset')`. Ето защо се препоръчва винаги да посочвате този параметър, за да избегнете евентуално непредсказуемо поведение на функцията. Nette последователно прави това. + +Някои функции, като например `strtolower()`, `strtoupper()` и други подобни, имаха недетерминирано поведение в близкото минало и зависеха от настройката `setlocale()`. Това предизвикваше много усложнения, най-често при работа с турския език. +Това е така, защото турският език прави разлика между големи и малки букви `I` с и без точка. Така `strtolower('I')` връщаше символа `ı`, а `strtoupper('i')` - символа `İ`, което водеше до това, че приложенията предизвикваха редица мистериозни грешки. +Този проблем обаче беше отстранен във версия 8.2 на PHP и функциите вече не зависят от локалите. + +Това е хубав пример за това как глобалното състояние е измъчвало хиляди разработчици по целия свят. Решението беше да го заменим с инжектиране на зависимости. + + +Кога е възможно да се използва глобално състояние? .[#toc-when-is-it-possible-to-use-global-state] +-------------------------------------------------------------------------------------------------- + +Има някои специфични ситуации, в които е възможно да се използва глобално състояние. Например, когато отстранявате грешки в кода и трябва да изхвърлите стойността на променлива или да измерите продължителността на определена част от програмата. В такива случаи, които се отнасят до временни действия, които по-късно ще бъдат премахнати от кода, е закономерно да се използва глобално достъпен дъмпер или хронометър. Тези инструменти не са част от дизайна на кода. + +Друг пример са функциите за работа с регулярни изрази `preg_*`, които вътрешно съхраняват компилирани регулярни изрази в статичен кеш в паметта. Когато извиквате един и същ регулярен израз няколко пъти в различни части на кода, той се компилира само веднъж. Кешът спестява производителност, а освен това е напълно невидим за потребителя, така че такава употреба може да се счита за легитимна. + + +Обобщение .[#toc-summary] +------------------------- + +Показахме защо има смисъл + +1) Премахнете всички статични променливи от кода +2) Декларирайте зависимостите +3) И използвайте инжектиране на зависимости + +Когато обмисляте дизайна на кода, имайте предвид, че всеки `static $foo` представлява проблем. За да може кодът ви да бъде среда, уважаваща DI, е необходимо напълно да изкорените глобалното състояние и да го замените с инжектиране на зависимости. + +По време на този процес може да откриете, че трябва да разделите даден клас, защото той има повече от една отговорност. Не се притеснявайте за това; стремете се към принципа на една отговорност. + +*Искам да благодаря на Мишко Хевери, чиито статии като [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] са в основата на тази глава.* diff --git a/dependency-injection/bg/introduction.texy b/dependency-injection/bg/introduction.texy index ca7eeb23ed..b4c40679f1 100644 --- a/dependency-injection/bg/introduction.texy +++ b/dependency-injection/bg/introduction.texy @@ -2,17 +2,17 @@ ************************************ .[perex] -Тази глава ви запознава с основните практики за програмиране, които трябва да следвате при писането на всяко приложение. Това са основите, необходими за писане на чист, разбираем и поддържан код. +Тази глава ще ви запознае с основните практики за програмиране, които трябва да спазвате при писането на всяко приложение. Това са основите, необходими за писането на чист, разбираем и поддържан код. -Ако научите и следвате тези правила, Nette ще бъде до вас на всяка крачка. Тя ще се справя с рутинните задачи вместо вас и ще ви осигури възможно най-голямо удобство, за да можете да се съсредоточите върху самата логика. +Ако научите и следвате тези правила, Nette ще бъде до вас на всяка крачка. Тя ще се справи с рутинните задачи вместо вас и ще ви осигури максимален комфорт, така че да можете да се съсредоточите върху самата логика. -Принципите, които ще покажем тук, са съвсем прости. Няма за какво да се притеснявате. +Принципите, които ще покажем тук, са съвсем прости. Не е нужно да се притеснявате за нищо. Помните ли първата си програма? .[#toc-remember-your-first-program] ------------------------------------------------------------------- -Нямаме представа на какъв език сте я написали, но ако е била на PHP, вероятно щеше да изглежда по следния начин: +Не знаем на какъв език сте го написали, но ако е PHP, може да изглежда по следния начин: ```php function addition(float $a, float $b): float @@ -25,11 +25,11 @@ echo addition(23, 1); // извежда 24 Няколко тривиални реда код, но в тях са скрити толкова много ключови концепции. Че има променливи. Че кодът е разделен на по-малки единици, които са функции, например. Че им подаваме входни аргументи и те връщат резултати. Липсват само условия и цикли. -Фактът, че подаваме входни аргументи на функция и тя връща резултат, е напълно разбираема концепция, която се използва и в други области, например в математиката. +Фактът, че функцията приема входни данни и връща резултат, е напълно разбираемо понятие, което се използва и в други области, например в математиката. -Една функция има сигнатура, която се състои от нейното име, списък на параметрите и техните типове, и накрая типа на връщаната стойност. Като потребители ние се интересуваме от сигнатурата; обикновено не е необходимо да знаем нищо за вътрешната реализация. +Една функция има сигнатура, която се състои от нейното име, списък с параметри и техните типове и накрая типа на връщаната стойност. Като потребители ние се интересуваме от сигнатурата и обикновено не е необходимо да знаем нищо за вътрешната реализация. -Сега си представете, че сигнатурата на една функция изглежда така: +Сега си представете, че сигнатурата на функцията изглежда по следния начин: ```php function addition(float $x): float @@ -41,15 +41,15 @@ function addition(float $x): float function addition(): float ``` -Това е наистина странно, нали? Как мислите, че се използва тази функция? +Това е наистина странно, нали? Как се използва функцията? ```php echo addition(); // какво се показва тук? ``` -Като гледаме такъв код, сме объркани. Не само начинаещ не би го разбрал, дори опитен програмист не би разбрал такъв код. +Ако погледнем такъв код, ще се объркаме. Не само начинаещ програмист не би го разбрал, но дори и опитен програмист не би разбрал такъв код. -Чудите ли се как всъщност би изглеждала една такава функция вътре? Откъде би взела суматорите? Вероятно би ги получила *по някакъв начин* сама, например по следния начин: +Чудите ли се как всъщност би изглеждала една такава функция вътре? Откъде би получила сумарните стойности? Вероятно *по някакъв начин* би ги получила сама, може би по следния начин: ```php function addition(): float @@ -66,13 +66,13 @@ function addition(): float Не по този начин! .[#toc-not-this-way] -------------------------------------- -Дизайнът, който току-що ни беше показан, е същността на много отрицателни характеристики: +Дизайнът, който току-що показахме, е същността на много отрицателни характеристики: -- сигнатурата на функцията се преструва, че не се нуждае от добавки, което ни обърква +- сигнатурата на функцията се преструва, че не се нуждае от сумарните стойности, което ни обърква - нямаме представа как да накараме функцията да изчислява с две други числа -- трябваше да се вгледаме в кода, за да видим откъде взема адентите -- открихме скрити връзки -- за да разберем напълно, трябва да изследваме и тези връзки +- трябваше да разгледаме кода, за да разберем откъде идват сумарните стойности +- открихме скрити зависимости +- пълното разбиране изисква да се разгледат и тези зависимости А дали изобщо задачата на функцията за добавяне е да набавя входове? Разбира се, че не е. Нейната отговорност е само да добавя. @@ -93,20 +93,20 @@ function addition(float $a, float $b): float Най-важното правило е: **всички данни, от които се нуждаят функциите или класовете, трябва да им бъдат предадени**. -Вместо да измисляте скрити механизми, които да им помогнат по някакъв начин сами да стигнат до тях, просто предайте параметрите. Ще спестите времето, което е необходимо за измисляне на скрит начин, който определено няма да подобри кода ви. +Вместо да измисляте скрити начини за самостоятелен достъп до данните, просто им предайте параметрите. Така ще спестите време, което бихте прекарали в измисляне на скрити пътища, които със сигурност няма да подобрят кода ви. -Ако спазвате това правило винаги и навсякъде, сте на път към код без скрити обвързвания. Към код, който е разбираем не само за автора, но и за всеки, който го прочете след това. Където всичко се разбира от сигнатурите на функциите и класовете и няма нужда да търсите скрити тайни в имплементацията. +Ако винаги и навсякъде спазвате това правило, сте на път към код без скрити зависимости. Към код, който е разбираем не само за автора, но и за всеки, който го прочете след това. Където всичко се разбира от сигнатурите на функциите и класовете и няма нужда да търсите скрити тайни в имплементацията. -Тази техника експертно се нарича **инжектиране на зависимости**. А данните се наричат **зависимости.** Но това е просто предаване на параметри, нищо повече. +Тази техника професионално се нарича **инжектиране на зависимости**. А тези данни се наричат **зависимости**. Това е просто обикновено предаване на параметри, нищо повече. .[note] -Моля, не бъркайте инжектирането на зависимости, което е шаблон за проектиране, с "контейнер за инжектиране на зависимости", който е инструмент, нещо съвсем различно. Контейнерите ще обсъдим по-късно. +Моля, не бъркайте инжектирането на зависимости, което е шаблон за проектиране, с "контейнер за инжектиране на зависимости", който е инструмент, нещо диаметрално различно. Контейнерите ще разгледаме по-късно. От функции към класове .[#toc-from-functions-to-classes] -------------------------------------------------------- -А как се отнасят класовете към това? Класът е по-сложна единица от простата функция, но правило № 1 важи и тук. Просто има [повече начини за предаване на аргументи |passing-dependencies]. Например, доста подобно на случая с функцията: +И как са свързани класовете? Класът е по-сложна единица от простата функция, но правило № 1 важи изцяло и тук. Просто има [повече начини за предаване на аргументи |passing-dependencies]. Например, доста подобно на случая с функцията: ```php class Math @@ -121,7 +121,7 @@ $math = new Math; echo $math->addition(23, 1); // 24 ``` -Или чрез използване на други методи, или директно чрез конструктора: +Или чрез други методи, или директно чрез конструктора: ```php class Addition @@ -149,9 +149,9 @@ echo $addition->calculate(); // 24 Примери от реалния живот .[#toc-real-life-examples] --------------------------------------------------- -В реалния свят няма да напишете класове за събиране на числа. Нека преминем към примери от реалния свят. +В реалния свят няма да пишете класове за събиране на числа. Нека преминем към практически примери. -Нека имаме клас `Article`, представляващ статия в блог: +Нека имаме клас `Article`, който представлява публикация в блог: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Методът `save()` запазва статията в таблица в базата данни. Реализацията му с помощта на [Nette Database |database:] щеше да е лесна работа, ако не беше една засечка: откъде `Article` трябва да получи връзката с базата данни, т.е. обекта на класа `Nette\Database\Connection`? +Методът `save()` ще запази статията в таблица в базата данни. Изпълнението му с помощта на [Nette Database |database:] ще бъде направо лесна работа, ако не беше една засечка: откъде `Article` получава връзката с базата данни, т.е. обект от клас `Nette\Database\Connection`? -Изглежда, че имаме много възможности. Той може да я вземе от някъде в статична променлива. Или да я наследи от клас, който ще предостави връзката с базата данни. Или да се възползвате от [един-единствен клас |global-state#Singleton]. Или така наречените фасади, които се използват в Laravel: +Изглежда, че имаме много възможности. Той може да я вземе от статична променлива някъде. Или да наследи от клас, който предоставя връзка към база данни. Или да се възползва от [един-единствен клас |global-state#Singleton]. Или да използваме така наречените фасади, които се използват в Laravel: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ class Article Или сме го направили? -Нека си припомним [правило № 1: Нека да ти бъде предадено |#rule #1: Let It Be Passed to You]: всички зависимости, от които се нуждае класът, трябва да му бъдат предадени. Защото ако не го направим и нарушим правилото, сме тръгнали по пътя към мръсен код, пълен със скрити връзки, неразбираемост, а резултатът ще бъде приложение, което е мъчително за поддържане и разработване. +Нека да си припомним [правило №1: Нека ви бъде предадено |#rule #1: Let It Be Passed to You]: всички зависимости, от които се нуждае класът, трябва да му бъдат предадени. Защото ако нарушим правилото, сме поели по пътя към мръсен код, пълен със скрити зависимости, неразбираемост, а резултатът ще бъде приложение, което ще бъде болезнено за поддържане и разработване. -Потребителят на класа `Article` няма представа къде методът `save()` съхранява статията. В таблица от базата данни? В коя от тях - в производствената или в тази за разработка? И как това може да бъде променено? +Потребителят на класа `Article` няма представа къде методът `save()` съхранява статията. В таблица от базата данни? В коя - в производствената или в тестовата? И как може да бъде променена? -Потребителят трябва да погледне как е реализиран методът `save()`, за да открие употребата на метода `DB::insert()`. Така че той трябва да търси по-нататък, за да открие как този метод осигурява връзка с база данни. А скритите връзки могат да образуват доста дълга верига. +Потребителят трябва да погледне как е реализиран методът `save()` и да открие използването на метода `DB::insert()`. И така, той трябва да търси по-нататък, за да открие как този метод получава връзка с база данни. А скритите зависимости могат да образуват доста дълга верига. -Скритите връзки, фасадите на Laravel или статичните променливи никога не присъстват в чист, добре проектиран код. В чистия и добре проектиран код се предават аргументи: +В чистия и добре проектиран код никога няма скрити зависимости, фасади на Laravel или статични променливи. В чистия и добре проектиран код се предават аргументи: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Още по-практично, както ще видим по-нататък, е да се използва конструктор: +Още по-практичен подход, както ще видим по-късно, е чрез конструктора: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Ако сте опитен програмист, може би си мислите, че `Article` изобщо не трябва да има метод `save()`, трябва да бъде чист компонент за данни, а за съхранението трябва да се грижи отделно хранилище. Това е логично. Но това би ни отвело далеч отвъд темата, която е инжектиране на зависимости, и се опитваме да дадем прости примери. +Ако сте опитен програмист, може би ще си помислите, че `Article` изобщо не трябва да има метод `save()`; той трябва да представлява чисто информационен компонент, а за съхранението трябва да се грижи отделно хранилище. Това е логично. Но това би ни изкарало далеч извън обхвата на темата, която е инжектиране на зависимости, и усилията да предоставим прости примери. -Ако ще пишете клас, който изисква база данни, за да работи, например, не измисляйте откъде да я вземете, а да ви бъде предадена. Може би като параметър на конструктор или друг метод. Декларирайте зависимости. Разкрийте ги в API на класа си. Ще получите разбираем и предсказуем код. +Ако пишете клас, който изисква например база данни за работата си, не измисляйте откъде да я вземете, а нека тя бъде предадена. Или като параметър на конструктора, или като друг метод. Признайте зависимостите. Допускайте ги в API на класа си. Така ще получите разбираем и предвидим код. -Какво ще кажете за този клас, който регистрира съобщения за грешки: +А какво да кажем за този клас, който регистрира съобщения за грешки: ```php class Logger @@ -266,7 +266,7 @@ class Logger Не спазихме. -Ключовата информация, директорията на лог файла, е *получена* от класа от константата. +Ключовата информация, т.е. директорията с лог файла, се *получава* от самия клас от константата. Вижте примера за използване: @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Можете ли да отговорите на въпроса къде се записват съобщенията, без да познавате имплементацията? Може ли да се предположи, че съществуването на константата LOG_DIR е необходимо, за да работи? И бихте ли могли да създадете втора инстанция, която да записва на друго място? Със сигурност не. +Можете ли да отговорите на въпроса къде се записват съобщенията, без да познавате имплементацията? Бихте ли предположили, че съществуването на константата `LOG_DIR` е необходимо за нейното функциониране? А бихте ли могли да създадете втора инстанция, която да записва на друго място? Със сигурност не. Нека поправим класа: @@ -295,7 +295,7 @@ class Logger } ``` -Сега класът е много по-ясен, по-конфигурируем и следователно по-полезен. +Сега класът е много по-разбираем, конфигурируем и следователно по-полезен. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Но на мен не ми пука! .[#toc-but-i-don-t-care] ---------------------------------------------- -*"Когато създавам обект Article и извиквам save(), не искам да се занимавам с базата данни, а само да я запазя в тази, която съм задал в конфигурацията. "* +*"Когато създавам обект Article и извиквам save(), не искам да се занимавам с базата данни, а само да го запазя в тази, която съм задал в конфигурацията."* -*"Когато използвам Logger, искам просто съобщението да бъде записано и не искам да се занимавам с това къде. Нека се използват глобалните настройки. "* +*"Когато използвам Logger, искам просто съобщението да бъде записано и не искам да се занимавам с това къде. Нека се използват глобалните настройки."* -Това са правилни коментари. +Това са валидни забележки. -Като пример, нека вземем клас, който изпраща бюлетини и регистрира как е протекло това: +Като пример, нека разгледаме клас, който изпраща бюлетини и записва как е преминал: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -Усъвършенстваният `Logger`, който вече не използва константата `LOG_DIR`, изисква път до файла в конструктора. Как да се реши този проблем? Класът `NewsletterDistributor` не се интересува от това къде се записват съобщенията, той просто иска да ги запише. +Подобреният `Logger`, който вече не използва константата `LOG_DIR`, изисква посочване на пътя до файла в конструктора. Как да се реши този проблем? Класът `NewsletterDistributor` не се интересува от това къде са записани съобщенията; той просто иска да ги запише. -Решението отново е в [правило № 1: Нека да ти бъде предадено |#rule #1: Let It Be Passed to You]: предай всички данни, от които се нуждае класът, на него. +Решението отново е в [правило № 1: Нека ви бъде предадено |#rule #1: Let It Be Passed to You]: предайте всички данни, от които класът се нуждае. -Така че предаваме пътя до дневника на конструктора, който след това използваме, за да създадем обекта `Logger`? +Значи ли това, че през конструктора предаваме пътя до дневника, който след това използваме при създаването на обекта `Logger`? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Не е така! Защото пътят **не принадлежи** към данните, от които се нуждае класът `NewsletterDistributor`; той се нуждае от `Logger`. Класът се нуждае от самия логер. И точно това ще предадем: +Не, не по този начин! Пътят не принадлежи към данните, от които се нуждае класът `NewsletterDistributor`; всъщност от него се нуждае класът `Logger`. Виждате ли разликата? Класът `NewsletterDistributor` се нуждае от самия логер. Така че това е, което ще предадем: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Сега от сигнатурите на класа `NewsletterDistributor` става ясно, че регистрирането е част от неговата функционалност. И задачата да се замени логерът с друг, може би с цел тестване, е съвсем тривиална. -Нещо повече, ако конструкторът на класа `Logger` бъде променен, това няма да се отрази на нашия клас. +Сега от сигнатурите на класа `NewsletterDistributor` става ясно, че регистрирането на данни също е част от неговата функционалност. А задачата да се смени логерът с друг, може би за тестване, е напълно тривиална. +Освен това, ако конструкторът на класа `Logger` се промени, това няма да се отрази на нашия клас. -Правило № 2: Вземете това, което е ваше .[#toc-rule-2-take-what-is-yours] -------------------------------------------------------------------------- +Правило № 2: Вземете това, което е ваше .[#toc-rule-2-take-what-s-yours] +------------------------------------------------------------------------ -Не се подвеждайте и не позволявайте параметрите на вашите зависимости да ви бъдат предавани. Предавайте директно параметрите на зависимостите. +Не се подвеждайте и не си позволявайте да минавате покрай зависимостите на вашите зависимости. Просто предавайте собствените си зависимости. -Това ще направи кода, използващ други обекти, напълно независим от промените в техните конструктори. Неговият API ще бъде по-верен. И най-важното, ще бъде тривиално да се сменят тези зависимости с други. +Благодарение на това кодът, използващ други обекти, ще бъде напълно независим от промените в техните конструктори. Неговият API ще бъде по-истински. И най-вече ще бъде тривиално да замените тези зависимости с други. -Нов член на семейството .[#toc-a-new-member-of-the-family] ----------------------------------------------------------- +Нов член на семейството .[#toc-new-family-member] +------------------------------------------------- -Екипът на разработчиците реши да създаде втори регистратор, който да записва в базата данни. Затова създаваме клас `DatabaseLogger`. И така, имаме два класа, `Logger` и `DatabaseLogger`, единият записва във файл, а другият - в база данни ... не ви ли се струва, че има нещо странно в това име? -Няма ли да е по-добре да преименуваме `Logger` на `FileLogger`? Разбира се, че би било. +Екипът на разработчиците реши да създаде втори регистратор, който да записва в базата данни. Затова създаваме клас `DatabaseLogger`. И така, имаме два класа, `Logger` и `DatabaseLogger`, единият записва във файл, а другият в база данни ... не ви ли се струва странно наименованието? +Няма ли да е по-добре да преименуваме `Logger` на `FileLogger`? Определено да. -Но нека да го направим умно. Ще създадем интерфейс под оригиналното име: +Но нека да го направим умно. Създаваме интерфейс под оригиналното име: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...който и двата логера ще имплементират: +... което ще бъде изпълнено и от двата регистратора: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -По този начин няма да е необходимо да се променя нищо в останалата част от кода, където се използва логерът. Например конструкторът на класа `NewsletterDistributor` все още ще бъде доволен от изискването на `Logger` като параметър. А от нас ще зависи коя инстанция ще му подадем. +Поради това няма да е необходимо да променяте нищо в останалата част от кода, където се използва логерът. Например конструкторът на класа `NewsletterDistributor` все още ще се задоволява с изискването на `Logger` като параметър. И от нас ще зависи коя инстанция ще подадем. -**Това е причината, поради която никога не даваме на имената на интерфейсите суфикс `Interface` или префикс `I`.** В противен случай би било невъзможно да се разработи толкова хубав код. +**Поради това никога не добавяме суфикс `Interface` или префикс `I` към имената на интерфейсите.** В противен случай нямаше да е възможно да се разработи толкова хубав код. Хюстън, имаме проблем .[#toc-houston-we-have-a-problem] ------------------------------------------------------- -Докато в цялото приложение можем да се задоволим с една единствена инстанция на логер, независимо дали е файлов или на база данни, и просто да го предаваме навсякъде, където нещо се регистрира, то в случая с класа `Article` е съвсем различно. Всъщност ние създаваме негови инстанции според нуждите, вероятно многократно. Как да се справим с обвързването с базата данни в неговия конструктор? +Въпреки че можем да се справим с една единствена инстанция на логера, независимо дали е базиран на файл или на база данни, в цялото приложение и просто да го предаваме навсякъде, където нещо се регистрира, при класа `Article` е съвсем различно. Създаваме негови екземпляри, когато е необходимо, дори няколко пъти. Как да се справим със зависимостта от базата данни в неговия конструктор? -Като пример можем да използваме контролер, който трябва да запише статия в базата данни след изпращане на формуляр: +Пример за това може да бъде контролер, който трябва да запише статия в базата данни след изпращане на формуляр: ```php class EditController extends Controller @@ -437,17 +437,17 @@ class EditController extends Controller } ``` -Възможно решение се предлага директно: обектът на базата данни да бъде предаден от конструктора на `EditController` и да се използва `$article = new Article($this->db)`. +Възможното решение е очевидно: предайте обекта на базата данни на конструктора `EditController` и използвайте `$article = new Article($this->db)`. -Както и в предишния случай с `Logger` и пътя до файла, това не е правилният подход. Базата данни не е зависима от `EditController`, а от `Article`. Така че предаването на базата данни противоречи на [правило № 2: вземи това, което е твое |#rule #2: take what is yours]. Когато конструкторът на класа `Article` се промени (добави се нов параметър), кодът на всички места, където се създават екземпляри, също ще трябва да се промени. Ufff. +Точно както в предишния случай с `Logger` и пътя до файла, това не е правилният подход. Базата данни не е зависима от `EditController`, а от `Article`. Предаването на базата данни противоречи на [правило № 2: вземи това, което е твое |#rule #2: take what's yours]. Ако конструкторът на класа `Article` се промени (добави се нов параметър), ще трябва да промените кода навсякъде, където се създават екземпляри. Ufff. -Хюстън, какво предлагаш? +Хюстън, какво предлагате? Правило № 3: Оставете фабриката да се справи с това .[#toc-rule-3-let-the-factory-handle-it] -------------------------------------------------------------------------------------------- -Като премахнем скритите връзки и предадем всички зависимости като аргументи, получаваме по-конфигурируеми и гъвкави класове. И по този начин се нуждаем от нещо друго, за да създаваме и конфигурираме тези по-гъвкави класове. Ще го наречем фабрики. +Чрез премахването на скритите зависимости и предаването на всички зависимости като аргументи получихме по-конфигурируеми и гъвкави класове. И затова се нуждаем от нещо друго, което да създава и конфигурира тези по-гъвкави класове за нас. Ще го наречем фабрики. Правилото е: ако даден клас има зависимости, оставете създаването на техните инстанции на фабриката. @@ -460,7 +460,7 @@ class EditController extends Controller Фабрика .[#toc-factory] ----------------------- -Фабриката е метод или клас, който произвежда и конфигурира обекти. Наричаме `Article` произвеждащ клас `ArticleFactory` и той може да изглежда така: +Фабриката е метод или клас, който създава и конфигурира обекти. Ще назовем класа, произвеждащ `Article`, като `ArticleFactory`, и той може да изглежда по следния начин: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Използването му в контролера би било следното: +Използването му в контролера ще бъде следното: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -В този момент, когато сигнатурата на конструктора на класа `Article` се промени, единствената част от кода, която трябва да реагира, е самата фабрика `ArticleFactory`. Всеки друг код, който работи с обекти `Article`, като например `EditController`, няма да бъде засегнат. +В този момент, ако сигнатурата на конструктора на класа `Article` се промени, единствената част от кода, която трябва да реагира, е самият `ArticleFactory`. Всички останали кодове, работещи с обекти `Article`, като например `EditController`, няма да бъдат засегнати. -Може би сега се чукате по челото и се чудите дали изобщо сме си помогнали. Обемът на кода нарасна и цялото нещо започва да изглежда подозрително сложно. +Може би се чудите дали всъщност сме подобрили нещата. Обемът на кода се е увеличил и всичко започва да изглежда подозрително сложно. -Не се притеснявайте, скоро ще стигнем до контейнера Nette DI. А той има редица асове в ръкава си, които ще направят изграждането на приложения, използващи инжектиране на зависимости, изключително лесно. Например, вместо класа `ArticleFactory` ще е достатъчно да [напишете прост интерфейс |factory]: +Не се притеснявайте, скоро ще стигнем до контейнера Nette DI. А той има няколко трика в ръкава си, които значително ще опростят изграждането на приложения, използващи инжектиране на зависимости. Например, вместо класа `ArticleFactory` ще трябва да [напишете |factory] само [прост интерфейс |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Но ние изпреварваме, почакайте :-) +Но ние изпреварваме себе си; моля, бъдете търпеливи :-) Резюме .[#toc-summary] ---------------------- -В началото на тази глава обещахме да ви покажем начин за проектиране на чист код. Просто дайте класовете +В началото на тази глава обещахме да ви покажем процес за проектиране на чист код. Всичко, което е необходимо, е класовете да: -- [зависимостите, от които се нуждаят |#Rule #1: Let It Be Passed to You] -- [а не това, от което нямат пряка нужда |#Rule #2: Take What Is Yours] -- [и че обектите със зависимости е най-добре да се правят във фабрики |#Rule #3: Let the Factory Handle it] +- [да предават зависимостите, от които се нуждаят |#Rule #1: Let It Be Passed to You] +- [обратно, да не предават това, което не им е пряко необходимо |#Rule #2: Take What's Yours] +- [и че обектите със зависимости е най-добре да се създават във фабрики |#Rule #3: Let the Factory Handle it] -На пръв поглед може да не изглежда така, но тези три правила имат далечни последици. Те водят до коренно различен поглед върху проектирането на кода. Струва ли си? Програмистите, които са изхвърлили старите навици и са започнали последователно да използват инжектиране на зависимости, смятат това за ключов момент в професионалния си живот. Той им е отворил свят на ясни и устойчиви приложения. +На пръв поглед тези три правила може да не изглеждат с далечни последици, но те водят до коренно различна перспектива за проектирането на кода. Струва ли си? Разработчиците, които са изоставили старите навици и са започнали последователно да използват инжектиране на зависимости, смятат тази стъпка за решаващ момент в професионалния си живот. Тя е отворила за тях света на ясните и поддържани приложения. -Но какво става, ако кодът не използва последователно инжектиране на зависимости? Какво става, ако е изграден върху статични методи или единични елементи? Това носи ли някакви проблеми? [Да, създава, и то много съществени |global-state]. +Но какво става, ако в кода не се използва последователно инжектиране на зависимости? Какво става, ако той разчита на статични методи или единични методи? Това води ли до някакви проблеми? [Да, създава, и то много съществени |global-state]. diff --git a/dependency-injection/bg/passing-dependencies.texy b/dependency-injection/bg/passing-dependencies.texy index c8e47b65c0..079f493c12 100644 --- a/dependency-injection/bg/passing-dependencies.texy +++ b/dependency-injection/bg/passing-dependencies.texy @@ -12,7 +12,7 @@ </div> -Първите три метода са приложими във всички обектно-ориентирани езици, а четвъртият е специфичен за презентаторите на Nette, затова е разгледан в [отделна глава |best-practices:inject-method-attribute]. Сега ще разгледаме по-подробно всяка от тези възможности и ще ги покажем с конкретни примери. +Сега ще илюстрираме различните варианти с конкретни примери. Внедряване чрез конструктор .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Зависимостите се предават като аргументи на конструктора при създаването на обекта: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Тази форма е полезна за задължителни зависимости, които са абсолютно необходими за функционирането на класа, тъй като без тях не може да се създаде инстанция. @@ -40,10 +40,10 @@ $service = new MyService($cache); ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ class MyService ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService Контейнерът DI предава зависимостите на конструктора автоматично, като използва [автоматично свързване |autowiring]. Аргументите, които не могат да бъдат предадени по този начин (напр. низове, числа, булеви стойности), [се записват в конфигурацията |services#Arguments]. +Адът на конструкторите .[#toc-constructor-hell] +----------------------------------------------- + +Терминът *ад на конструкторите* се отнася до ситуация, при която наследник наследява родителски клас, чийто конструктор изисква зависимости, и наследникът също изисква зависимости. То също трябва да поеме и предаде зависимостите на родителя: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Проблемът възниква, когато искаме да променим конструктора на класа `BaseClass`, например когато се добави нова зависимост. Тогава трябва да променим и всички конструктори на децата. Което превръща подобна модификация в ад. + +Как да предотвратим това? Решението е да се даде **приоритет на композицията пред наследяването**. + +Така че нека да проектираме кода по различен начин. Ще избягваме абстрактните класове `Base*`. Вместо `MyClass` да получава някаква функционалност, наследявайки я от `BaseClass`, тя ще има тази функционалност, предадена като зависимост: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Зависимости чрез задаващи елементи .[#toc-setter-injection] =========================================================== -Зависимостите се предават чрез извикване на метод, който ги съхранява в частно свойство. Обичайната конвенция за именуване на тези методи е `set*()`, затова те се наричат setters. +Зависимостите се предават чрез извикване на метод, който ги съхранява в частни свойства. Обичайната конвенция за именуване на тези методи е от вида `set*()`, поради което те се наричат сетъри, но, разбира се, могат да се наричат и по друг начин. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Този метод е полезен за незадължителни зависимости, които не са необходими за функционирането на класа, тъй като не е гарантирано, че обектът действително ще ги получи (т.е. че потребителят ще извика метода). @@ -90,16 +150,16 @@ $service->setCache($cache); В същото време този метод позволява многократно извикване на setter за промяна на зависимостта. Ако това не е желателно, добавете проверка към метода или, от версия PHP 8.1, маркирайте свойството `$cache` с флага `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ class MyService ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ services: Зависимостите се предават директно на свойството: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Този метод се счита за неприемлив, тъй като свойството трябва да бъде декларирано като `public`. Следователно нямаме контрол върху това дали предадената зависимост действително има зададения тип (това беше вярно преди PHP 7.4) и губим възможността да реагираме на новоназначената зависимост със собствен код, например за да предотвратим последващи промени. В същото време свойството става част от публичния интерфейс на класа, което може да е нежелателно. @@ -137,12 +197,18 @@ $service->cache = $cache; ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Инжектиране .[#toc-inject] +========================== + +Докато предишните три метода са общовалидни във всички обектно-ориентирани езици, инжектирането чрез метод, анотация или атрибут *inject* е специфично за презентаторите на Nette. Те са разгледани в [отделна глава |best-practices:inject-method-attribute]. + + Кой път да избера? .[#toc-which-way-to-choose] ============================================== diff --git a/dependency-injection/bg/services.texy b/dependency-injection/bg/services.texy index e70bb0356a..44bb7797e9 100644 --- a/dependency-injection/bg/services.texy +++ b/dependency-injection/bg/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Режим на изпълнение .[#toc-inject-mode] ======================================= -Флагът `inject: true` се използва за активиране на прехвърлянето на зависимости чрез публични променливи с помощта на анотацията [inject |best-practices:inject-method-attribute#Inject Annotations] и методите [inject*() |best-practices:inject-method-attribute#inject Methods]. +Флагът `inject: true` се използва за активиране на прехвърлянето на зависимости чрез публични променливи с помощта на анотацията [inject |best-practices:inject-method-attribute#Inject Attributes] и методите [inject*() |best-practices:inject-method-attribute#inject Methods]. ```neon services: diff --git a/dependency-injection/cs/@home.texy b/dependency-injection/cs/@home.texy index 3e66ae0454..15748c0a9d 100644 --- a/dependency-injection/cs/@home.texy +++ b/dependency-injection/cs/@home.texy @@ -5,8 +5,10 @@ Dependency Injection Dependency Injection je návrhový vzor, který zásadně změní váš pohled na kód a vývoj. Otevře vám cestu do světa čistě navržených a udržitelných aplikací. - [Co je Dependency Injection? |introduction] -- [Co je DI kontejner? |container] +- [Globální stav a singletony |global-state] - [Předávání závislostí |passing-dependencies] +- [Co je DI kontejner? |container] +- [Často kladené otázky|faq] Nette DI diff --git a/dependency-injection/cs/@left-menu.texy b/dependency-injection/cs/@left-menu.texy index f4bade0b57..ed7e4452b3 100644 --- a/dependency-injection/cs/@left-menu.texy +++ b/dependency-injection/cs/@left-menu.texy @@ -1,8 +1,10 @@ Dependency Injection ******************** - [Co je DI? |introduction] -- [Co je DI kontejner? |container] +- [Globální stav a singletony |global-state] - [Předávání závislostí |passing-dependencies] +- [Co je DI kontejner? |container] +- [Často kladené otázky|faq] Nette DI diff --git a/dependency-injection/cs/container.texy b/dependency-injection/cs/container.texy index 477e4be064..3583a27dad 100644 --- a/dependency-injection/cs/container.texy +++ b/dependency-injection/cs/container.texy @@ -4,7 +4,7 @@ Co je DI kontejner? .[perex] Dependency injection kontejner (DIC) je třída, která umí instancovat a konfigurovat objekty. -Možná vás to překvapí, ale v mnoha případech nepotřebujete dependency injection kontejner, abyste mohli využívat výhod dependency injection (krátce DI). Vždyť i v [předchozí kapitole|introduction] jsme si na konkrétních příkladech DI ukázali a žádný kontejner nebyl potřeba. +Možná vás to překvapí, ale v mnoha případech nepotřebujete dependency injection kontejner, abyste mohli využívat výhod dependency injection (krátce DI). Vždyť i v [úvodní kapitole|introduction] jsme si na konkrétních příkladech DI ukázali a žádný kontejner nebyl potřeba. Pokud však potřebujete spravovat velké množství různých objektů s mnoha závislostmi, bude dependency injection container opravdu užitečný. Což je třeba případ webových aplikací postavených na frameworku. diff --git a/dependency-injection/cs/faq.texy b/dependency-injection/cs/faq.texy new file mode 100644 index 0000000000..31f26903b0 --- /dev/null +++ b/dependency-injection/cs/faq.texy @@ -0,0 +1,112 @@ +Často kladené otázky o DI (FAQ) +******************************* + + +Je DI jiným názvem pro IoC? +--------------------------- + +*Inversion of Control* (IoC) je princip zaměřený na způsob, jakým je kód spouštěn - zda váš kód spouští cizí nebo je váš kód integrován do cizího, který jej následně volá. +IoC je široký pojem zahrnující [události|nette:glossary#Události], takzvaný [Hollywoodský princip |application:components#Hollywood style] a další aspekty. +Součástí tohoto konceptu jsou i továrny, o kterých hovoří [Pravidlo č. 3: nech to na továrně |introduction#Pravidlo č. 3: nech to na továrně], a které představují inverzi pro operátor `new`. + +*Dependency Injection* (DI) se zaměřuje na způsob, jakým se jeden objekt dozví o jiném objektu, tedy o jeho závislosti. Jde o návrhový vzor, který požaduje explicitní předávání závislostí mezi objekty. + +Lze tedy říci, že DI je specifickou formou IoC. Nicméně ne všechny formy IoC jsou vhodné z hlediska čistoty kódu. Například mezi antivzory patří techniky, které pracují s [globálním stavem |global-state] nebo takzvaný [Service Locator |#Co je to Service Locator]. + + +Co je to Service Locator? +------------------------- + +Jde o alternativu k Dependency Injection. Funguje tak, že vytvoří centrální úložiště, kde jsou registrovány všechny dostupné služby nebo závislosti. Když objekt potřebuje závislost, požádá o ni Service Locator. + +Oproti Dependency Injection však ztrácí na transparentnosti: závislosti nejsou objektům předávány přímo a nejsou tak snadno identifikovatelné, což vyžaduje prozkoumání kódu, aby byly všechny vazby odhaleny a pochopeny. Testování je také složitější, protože nemůžeme jednoduše předávat mock objekty testovaným objektům, ale musíme na to jít přes Service Locator. Navíc, Service Locator narušuje návrh kódu, jelikož jednotlivé objekty musí o jeho existenci vědět, což se liší od Dependency Injection, kde objekty nemají povědomí o DI kontejneru. + + +Kdy je lepší DI nepoužít? +------------------------- + +Nejsou známy žádné obtíže spojené s použitím návrhového vzoru Dependency Injection. Naopak získávání závislostí z globálně dostupných míst vede k [celé řadě komplikací |global-state], stejně tak používání Service Locatoru. +Proto je vhodné využívat DI vždy. To není dogmatický přístup, ale jednoduše nebyla nalezena lepší alternativa. + +Přesto existují určité situace, kdy si objeky nepředáváme a získáme je z globálního prostoru. Například při ladění kódu, kdy potřebujete v konkrétním bodě programu vypsat hodnotu proměnné, změřit dobu trvání určité části programu nebo zaznamenat zprávu. +V takových případech, kdy jde o dočasné úkony, které budou později z kódu odstraněny, je legitimní využít globálně dostupný dumper, stopky nebo logger. Tyto nástroje totiž nepatří k návrhu kódu. + + +Má používání DI své stinné stránky? +----------------------------------- + +Obnáší použití Dependency Injection nějaké nevýhody, jako například zvýšenou náročnost na psaní kódu nebo zhoršený výkon? Co ztrácíme, když začneme psát kód v souladu s DI? + +DI nemá na výkon nebo paměťové nároky aplikace vliv. Určitou roli může hrát výkon DI Containeru, avšak v případě [Nette DI |nette-container] je kontejner kompilován do čistého PHP, takže jeho režie při běhu aplikace je v podstatě nulová. + +Při psaní kódu bývá nutné vytvářet konstruktory přijímající závislosti. Dříve to mohlo být zdlouhavé, avšak díky moderním IDE a [constructor property promotion |https://blog.nette.org/cs/php-8-0-kompletni-prehled-novinek#toc-constructor-property-promotion] je to nyní otázkou několika sekund. Továrny lze snadno generovat pomocí Nette DI a pluginu pro PhpStorm kliknutím myší. +Na druhou stranu odpadá potřeba psát singletony a statické přístupové body. + +Lze konstatovat, že správně navržená aplikace využívající DI není v porovnání s aplikací využívající singletony ani kratší ani delší. Části kódu pracující se závislostmi jsou pouze vyňaty z jednotlivých tříd a přesunuty na nová místa, tedy do DI kontejneru a továren. + + +Jak legacy aplikaci přepsat na DI? +---------------------------------- + +Přechod z legacy aplikace na Dependency Injection může být náročný proces, zejména u velkých a komplexních aplikací. Je důležité přistupovat k tomuto procesu systematicky. + +- Při přechodu na Dependency Injection je důležité, aby všichni členové týmu rozuměli principům a postupům, které se používají. +- Nejprve proveďte analýzu stávající aplikace a identifikujete klíčové komponenty a jejich závislosti. Vytvořte plán, které části budou refaktorovány a v jakém pořadí. +- Implementujte DI kontejner nebo ještě lépe použijte existující knihovnu, například Nette DI. +- Postupně refaktorujte jednotlivé části aplikace, aby používaly Dependency Injection. To může zahrnovat úpravy konstruktorů nebo metod tak, aby přijímaly závislosti jako parametry. +- Upravte místa v kódu, kde se vytvářejí objekty se závislostmi, aby místo toho byly závislosti injektovány kontejnerem. To může zahrnovat použití továren. + +Pamatujte, že přechod na Dependency Injection je investice do kvality kódu a dlouhodobé udržitelnosti aplikace. Ačkoli může být náročné provést tyto změny, výsledkem by měl být čistší, modulárnější a snadno testovatelný kód, který je připraven pro budoucí rozšíření a údržbu. + + +Proč se upřednostňuje kompozice před dědičností? +------------------------------------------------ +Je vhodnější používat kompozici místo dědičnosti, protože slouží k opětovnému použití kódu, aniž bychom se museli starat o důsledky změn. Poskytuje tedy volnější vazbu, kdy nemusíme mít obavy, že změna nějakého kódu způsobí potřebu změny jiného závislého kódu. Typickým příkladem je situace označovaná jako [constructor hell |passing-dependencies#Constructor hell]. + + +Lze použít Nette DI Container mimo Nette? +----------------------------------------- + +Rozhodně. Nette DI Container je součástí Nette, ale je navržen jako samostatná knihovna, která může být použita nezávisle na ostatních částech frameworku. Stačí ji nainstalovat pomocí Composeru, vytvořit konfigurační soubor s definicí vašich služeb a poté pomocí několika řádků PHP kódu vytvořit DI kontejner. +A ihned můžte začít využívat výhody Dependency Injection ve svých projektech. + +Jak vypadá konkrétní použití včetně kódů popisuje kapitola [Nette DI Container |nette-container]. + + +Proč je konfigurace v NEON souborech? +------------------------------------- + +NEON je jednoduchý a snadno čitelný konfigurační jazyk, který byl vyvinut v rámci Nette pro nastavení aplikací, služeb a jejich závislostí. Ve srovnání s JSONem nebo YAMLem nabízí pro tento účel mnohem intuitivnější a flexibilnější možnosti. V NEONu lze přirozeně popsat vazby, které by v Symfony & YAMLu nebylo možné zapsat buď vůbec, nebo jen prostřednictvím složitého opisu. + + +Nezpomaluje aplikaci parsování NEON souborů? +-------------------------------------------- + +Byť se soubory NEON parsují velmi rychle, na tomto hledisku vůbec nezáleží. Důvodem je, že parsování souborů proběhne pouze jednou při prvním spuštění aplikace. Poté se vygeneruje kód DI kontejneru, uloží se na disk a spustí se při každém dalším požadavku, aniž by bylo nutné provádět další parsování. + +Takto to funguje v produkčním prostředí. Během vývoje se NEON soubory parsují pokaždé, když dojde ke změně jejich obsahu, aby vývojář měl vždy aktuální DI kontejner. Samotná parsování je, jak bylo řečeno, otázkou okamžiku. + + +Jak se dostanu ze své třídy k parametrům v konfiguračním souboru? +----------------------------------------------------------------- + +Mějme na paměti [Pravidlo č. 1: nech si to předat |introduction#Pravidlo č. 1: nech si to předat]. Pokud třída vyžaduje informace z konfiguračního souboru, nemusíme přemýšlet, jak se k těm informacím dostat, místo toho si o ně jednoduše požádáme - například prostřednictvím konstruktoru třídy. A předání uskutečníme v konfiguračním souboru. + +V této ukázce je `%myParameter%` zástupný symbol pro hodnotu parametru `myParameter`, který se předá do konstruktoru třídy `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Chcete-li předávat více parametrů nebo využít autowiring, je vhodné [parametry zabalit do objektu |best-practices:passing-settings-to-presenters]. + + +Podporuje Nette PSR-11: Container interface? +-------------------------------------------- + +Nette DI Container nepodporuje PSR-11 přímo. Nicméně, pokud potřebujete interoperabilitu mezi Nette DI Containerem a knihovnami nebo frameworky, které očekávají PSR-11 Container Interface, můžete vytvořit [jednoduchý adaptér |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], který bude sloužit jako most mezi Nette DI Containerem a PSR-11. diff --git a/dependency-injection/cs/global-state.texy b/dependency-injection/cs/global-state.texy new file mode 100644 index 0000000000..09193a246b --- /dev/null +++ b/dependency-injection/cs/global-state.texy @@ -0,0 +1,312 @@ +Globální stav a singletony +************************** + +.[perex] +Varování: Následující konstrukce jsou příznakem špatného návrhu kódu: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` nebo `static::$var` + +Vyskytují se některé z těchto konstrukcí ve vašem kódu? Pak máte příležitost k zlepšení. Možná si říkáte, že jde o běžné konstrukce, které vídáme i v ukázkových řešeních různých knihoven a frameworků. +Bohužel, i přesto jsou jasným indikátorem špatného návrhu. Spojuje je jedno: používání globálního stavu. + +Nyní rozhodně nemluvíme o jakési akademické čistotě. Používání globálního stavu a singletonů má destruktivní dopady na kvalitu kódu. Jeho chování se stává nepředvídatelné, snižuje produktivitu vývojářů a nutí rozhraní tříd lhát o svých skutečných závislostech. Což mate programátory. + +V této kapitole si ukážeme, jak je to možné. + + +Globální provázání +------------------ + +Základní problém globálního stavu spočívá v tom, že je globálně přístupný. Díky tomu je třeba možné zapsat do databáze přes globální (statickou) metodu `DB::insert()`. +V ideálním světě by měl být objekt schopen komunikovat pouze s jinými objekty, které mu byly [přímo předány |passing-dependencies]. +Pokud vytvořím dva objekty `A` a `B` a nikdy nepředám referenci z `A` na `B`, pak se ani `A`, ani `B` nemohou dostat k druhému objektu nebo změnit jeho stav. +To je velmi žádoucí vlastnost kódu. Je to podobné, jako když máte baterii a žárovku; žárovka nebude svítit, dokud je nepropojíte drátem. + +To ale neplatí u globálních (statických) proměnných nebo singletonů. Objekt `A` by se mohl *bezdrátově* dostat k objektu `C` a modifikovat jej bez jakéhokoliv předání reference, tím, že zavolá `C::changeSomething()`. +Pokud se objekt `B` také chopí globálního `C`, pak se `A` a `B` mohou navzájem ovlivňovat prostřednictvím `C`. + +Použití globálních proměnných do systému vnáší novou formu *bezdrátové* provázanosti, která není zvenčí vidět. +Vytváří kouřovou clonu komplikující pochopení a používání kódu. +Aby vývojáři závislostem skutečně porozuměli, musí přečíst každý řádek zdrojového kódu. Místo pouhého seznámení se s rozhraním tříd. +Jde navíc o provázanost zcela zbytečnou. + +.[note] +Z hlediska chování není rozdíl mezi globální a statickou proměnnou. Jsou stejně škodlivé. + + +Strašidelné působení na dálku +----------------------------- + +"Strašidelné působení na dálku" - tak slavně nazval roku 1935 Albert Einstein jev v kvantové fyzice, který mu naháněl husí kůži. +Jedná se o kvantové propojení, jehož zvláštností je, že když změříte informaci o jedné částici, okamžitě tím ovlivníte částici druhou, i když jsou od sebe vzdáleny miliony světelných let. +Což zdánlivě porušuje základní zákon vesmíru, že nic se nemůže šířit rychleji než světlo. + +V softwarovém světě můžeme "strašidelným působení na dálku" nazvat situaci, kdy spustíme nějaký proces, o kterém se domníváme, že je izolovaný (protože jsme mu nepředali žádné reference), ale ve vzdálených místech systému dojde k neočekávaným interakcím a změnám stavu, o kterých jsme neměli tušení. K tomu může dojít pouze prostřednictvím globálního stavu. + +Představte si, že se připojíte k týmu vývojářů projektu, který má rozsáhlou vyspělou kódovou základnu. Váš nový vedoucí vás požádá o implementaci nové funkce a vy jako správný vývojář začnete psaním testu. Protože jste ale v projektu noví, děláte spoustu průzkumných testů typu "co se stane, když zavolám tuto metodu". A zkusíte napsat následující test: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // číslo vaší karty + $cc->charge(100); +} +``` + +Spustíte kód, třeba několikrát, a po nějaké době si všimnete na mobilu notifikací z banky, že při každém spuštění se strhlo 100 dolarů z vaší platební karty 🤦‍♂️ + +Jak proboha mohl test způsobit skutečné stržení peněz? Operovat s platební kartou není snadné. Musíte komunikovat s webovou službou třetí strany, musíte znát URL této webové služby, musíte se přihlásit a tak dále. +Žádná z těchto informací není v testu obsažena. Ba co hůř, ani nevíte, kde jsou tyto informace přítomny, a tedy ani jak mockovat externí závislosti, aby každé spuštění nevedlo k tomu, že se znovu strhne 100 dolarů. A jak jste měl jako nový vývojář vědět, že to, co se chystáte udělat, povede k tomu, že budete o 100 dolarů chudší? + +To je strašidelné působení na dálku! + +Nezbývá vám, než se dlouze hrabat ve spoustě zdrojových kódů, ptát se starších a zkušenějších kolegů, než pochopíte, jak vazby v projektu fungují. +To je způsobeno tím, že při pohledu na rozhraní třídy `CreditCard` nelze zjistit globální stav, který je třeba inicializovat. Dokonce ani pohled do zdrojového kódu třídy vám neprozradí, kterou inicializační metodu máte zavolat. V nejlepším případě můžete najít globální proměnnou, ke které se přistupuje, a z ní se pokusit odhadnout, jak ji inicializovat. + +Třídy v takovém projektu jsou patologickými lháři. Platební karta předstírá, že ji stačí instancovat a zavolat metodu `charge()`. Ve skrytu však spolupracuje s jinou třídou `PaymentGateway`, která představuje platební bránu. I její rozhraní říká, že ji lze inicializovat samostatně, ale ve skutečnosti si vytáhne credentials z nějakého konfiguračního souboru a tak dále. +Vývojářům, kteří tento kód napsali, je jasné, že `CreditCard` potřebuje `PaymentGateway`. Napsali kód tímto způsobem. Ale pro každého, kdo je v projektu nový, je to naprostá záhada a brání to učení. + +Jak situaci opravit? Snadno. **Nechte API deklarovat závislosti.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Všimněte si, jak jsou najednou provázanosti uvnitř kódu zřejmé. Tím, že metoda `charge()` deklaruje, že potřebuje `PaymentGateway`, nemusíte se na to, jak je kód provázaný, nikoho ptát. Víte, že musíte vytvořit její instanci, a když se o to pokusíte, narazíte na to, že musíte dodat přístupové parametry. Bez nich by kód nešel ani spustit. + +A hlavně nyní můžete platební bránu mockovat, takže se vám při každém spuštění testu nebude účtovat 100 dolarů. + +Globální stav způsobuje, že se vaše objekty mohou tajně dostat k věcem, které nejsou deklarovány v jejich API, a v důsledku toho dělají z vašich API patologické lháře. + +Možná jste o tom dříve takto nepřemýšleli, ale kdykoli používáte globální stav, vytváříte tajné bezdrátové komunikační kanály. Strašidelná akce na dálku nutí vývojáře číst každý řádek kódu, aby pochopili potenciální interakce, snižuje produktivitu vývojářů a mate nové členy týmu. +Pokud jste vy ten, kdo kód vytvořil, znáte skutečné závislosti, ale každý, kdo přijde po vás, je bezradný. + +Nepište kód, který využívá globální stav, dejte přednost předávání závislostí. Tedy dependency injection. + + +Křehkost globálního stavu +------------------------- + +V kódu, který používá globální stav a singletony, není nikdy jisté, kdy a kdo tento stav změnil. Toto riziko se objevuje již při inicializaci. Následující kód má vytvořit databázové spojení a inicializovat platební bránu, avšak neustále vyhazuje výjimku a hledání příčiny je nesmírně zdlouhavé: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Musíte podrobně procházet kód, abyste zjistili, že objekt `PaymentGateway` přistupuje bezdrátově k dalším objektům, z nichž některé vyžadují databázové připojení. Tedy je nutné inicializovat databázi dříve než `PaymentGateway`. Nicméně kouřová clona globálního stavu toto před vámi skrývá. Kolik času byste ušetřili, kdyby API jednotlivých tříd neklamalo a deklarovalo své závislosti? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Podobný problém se objevuje i při použití globálního přístupu k databázovému spojení: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Při volání metody `save()` není jisté, zda bylo již vytvořeno připojení k databázi a kdo nese odpovědnost za jeho vytvoření. Pokud chceme například měnit databázové připojení za běhu, třeba kvůli testům, museli bychom nejspíš vytvořit další metody jako například `DB::reconnect(...)` nebo `DB::reconnectForTest()`. + +Zvažme příklad: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Kde máme jistotu, že při volání `$article->save()` se opravdu používá testovací databáze? Co když metoda `Foo::doSomething()` změnila globální databázové připojení? Pro zjištění bychom museli prozkoumat zdrojový kód třídy `Foo` a pravděpodobně i mnoha dalších tříd. Tento přístup by však přinesl pouze krátkodobou odpověď, jelikož se situace může v budoucnu změnit. + +A co když připojení k databázi přesuneme do statické proměnné uvnitř třídy `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Tím se vůbec nic nezměnilo. Problémem je globální stav a je úplně jedno, ve které třídě se skrývá. V tomto případě, stejně jako v předchozím, nemáme při volání metody `$article->save()` žádné vodítko k tomu, do jaké databáze se zapíše. Kdokoliv na druhém konci aplikace mohl kdykoliv pomocí `Article::setDb()` databázi změnit. Nám pod rukama. + +Globálnímu stav činní naši aplikaci **nesmírně křehkou**. + +Existuje však jednoduchý způsob, jak s tímto problémem naložit. Stačí nechat API deklarovat závislosti, čímž se zajistí správná funkčnost. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Díky tomuto přístupu odpadá obava o skryté a neočekávané změny připojení k databázi. Nyní máme jistotu, kam se článek ukládá a žádné úpravy kódu uvnitř jiné nesouvisející třídy již nemohou situaci změnit. Kód už není křehký, ale stabilní. + +Nepište kód, který využívá globální stav, dejte přednost předávání závislostí. Tedy dependency injection. + + +Singleton +--------- + +Singleton je návrhový vzor, který podle "definice":https://en.wikipedia.org/wiki/Singleton_pattern ze známé publikace Gang of Four omezuje třídu na jedinou instanci a nabízí k ní globální přístup. Implementace tohoto vzoru se obvykle podobá následujícímu kódu: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // a další metody plnící funkce dané třídy +} +``` + +Bohužel, singleton zavádí do aplikace globální stav. A jak jsme si ukázali výše, globální stav je nežádoucí. Proto je singleton považován za antipattern. + +Nepoužívejte ve svém kódu singletony a nahraďte je jinými mechanismy. Singletony opravdu nepotřebujete. Pokud však potřebujete zaručit existenci jediné instance třídy pro celou aplikaci, nechte to na [DI kontejneru |container]. +Vytvořte tak aplikační singleton, neboli službu. Tím se třída přestane věnovat zajištění své vlastní jedinečnosti (tj. nebude mít metodu `getInstance()` a statickou proměnnou) a bude plnit pouze své funkce. Tak přestane porušovat princip jediné odpovědnosti. + + +Globální stav versus testy +-------------------------- + +Při psaní testů předpokládáme, že každý test je izolovanou jednotkou a že do něj nevstupuje žádný externí stav. A žádný stav testy neopouští. Po dokončení testu by měl být veškerý související stav s testem odstraněn automaticky garbage collectorem. Díky tomu jsou testy izolované. Proto můžeme testy spouštět v libovolném pořadí. + +Pokud jsou však přítomny globální stavy/singletony, všechny tyto příjemné předpoklady se rozpadají. Stav může do testu vstupovat a vystupovat z něj. Najednou může záležet na pořadí testů. + +Abychom vůbec mohli testovat singletony, vývojáři často musí rozvolnit jejich vlastnosti, třeba tím, že dovolí instanci nahradit jinou. Taková řešení jsou v nejlepším případě hackem, který vytváří obtížně udržovatelný a srozumitelný kód. Každý test nebo metoda `tearDown()`, která ovlivní jakýkoli globální stav, musí tyto změny vrátit zpět. + +Globální stav je největší bolestí hlavy při unit testování! + +Jak situaci opravit? Snadno. Nepište kód, který využívá singletony, dejte přednost předávání závislostí. Tedy dependency injection. + + +Globální konstanty +------------------ + +Globální stav se neomezuje pouze na používání singletonů a statických proměnných, ale může se týkat také globálních konstant. + +Konstanty, jejichž hodnota nám nepřináší žádnou novou (`M_PI`) nebo užitečnou (`PREG_BACKTRACK_LIMIT_ERROR`) informaci, jsou jednoznačně v pořádku. +Naopak konstanty, které slouží jako způsob, jak *bezdrátově* předat informaci dovnitř kódu, nejsou ničím jiným než skrytou závislostí. Jako třeba `LOG_FILE` v následujícím příkladu. +Použití konstanty `FILE_APPEND` je zcela korektní. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +V tomto případě bychom měli deklarovat parametr v konstruktoru třídy `Foo`, aby se stal součástí API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Nyní můžeme předat informaci o cestě k souboru pro logování a snadno ji měnit podle potřeby, což usnadňuje testování a údržbu kódu. + + +Globální funkce a statické metody +--------------------------------- + +Chceme zdůranit, že samotné používání statických metod a globálních funkcí není problematické. Vysvětlovali jsme, v čem spočívá nevhodnost použití `DB::insert()` a podobných metod, ale vždy se jednalo pouze o záležitost globálního stavu, který je uložen v nějaké statické proměnné. Metoda `DB::insert()` vyžaduje existenci statické proměnné, protože v ní je uloženo připojení k databázi. Bez této proměnné by bylo nemožné metodu implementovat. + +Používání deterministických statických metod a funkcí, jako například `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` a mnoha dalších, je v naprostém souladu s dependency injection. Tyto funkce vždy vracejí stejné výsledky ze stejných vstupních parametrů a jsou tedy předvídatelné. Nepoužívají žádný globální stav. + +Existují ovšem i funkce v PHP, které nejsou deterministické. K nim patří například funkce `htmlspecialchars()`. Její třetí parametr `$encoding`, pokud není uveden, jako výchozí hodnotu má hodnotu konfigurační volby `ini_get('default_charset')`. Proto se doporučuje tento parametr vždy uvádět a předejít tak případnému nepředvídatelnému chování funkce. Nette to důsledně dělá. + +Některé funkce, jako například `strtolower()`, `strtoupper()` a podobné, se v nedávné minulosti nedeterministicky chovaly a byly závislé na nastavení `setlocale()`. To způsobovalo mnoho komplikací, nejčastěji při práci s tureckým jazykem. +Ten totiž rozlišuje malé i velké písmeno `I` s tečkou i bez tečky. Takže `strtolower('I')` vracelo znak `ı` a `strtoupper('i')` znak `İ`, což vedlo k tomu, že aplikace začaly způsobovat řadu záhadných chyb. +Tento problém byl však odstraněn v PHP verze 8.2 a funkce již nejsou závislé na locale. + +Jde o pěkný příklad, jak globální stav potrápil tisíce vývojářů na celém světě. Řešením bylo nahradit jej za dependency injection. + + +Kdy je možné použít globální stav? +---------------------------------- + +Existují určité specifické situace, kdy je možné využít globální stav. Například při ladění kódu, když potřebujete vypsat hodnotu proměnné nebo změřit dobu trvání určité části programu. V takových případech, které se týkají dočasných akcí, jež budou později odstraněny z kódu, je možné legitimně využít globálně dostupný dumper nebo stopky. Tyto nástroje totiž nejsou součástí návrhu kódu. + +Dalším příkladem jsou funkce pro práci s regulárními výrazy `preg_*`, které interně ukládají zkompilované regulární výrazy do statické cache v paměti. Když tedy voláte stejný regulární výraz vícekrát na různých místech kódu, zkompiluje se pouze jednou. Cache šetří výkon a zároveň je pro uživatele zcela neviditelná, proto lze takové využití považovat za legitimní. + + +Shrnutí +------- + +Probrali jsme si, proč má smysl: + +1) Odstranit veškeré statické proměnné z kódu +2) Deklarovat závislosti +3) A používat dependency injection + +Když promýšlíte návrh kódu, myslete na to, že každé `static $foo` představuje problém. Aby váš kód byl prostředím respektujícím DI, je nezbytné úplně vymýtit globální stav a nahradit ho pomocí dependency injection. + +Během tohoto procesu možná zjistíte, že je třeba třídu rozdělit, protože má více než jednu odpovědnost. Nebojte se toho; usilujte o princip jedné odpovědnosti. + +*Rád bych poděkoval Miškovi Heverymu, jehož články, jako je [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], jsou základem této kapitoly.* diff --git a/dependency-injection/cs/passing-dependencies.texy b/dependency-injection/cs/passing-dependencies.texy index 719f45baa1..433f999eb0 100644 --- a/dependency-injection/cs/passing-dependencies.texy +++ b/dependency-injection/cs/passing-dependencies.texy @@ -12,7 +12,7 @@ Argumenty, nebo v terminologii DI „závislosti“, lze do tříd předávat t </div> -První tři způsoby platí obecně ve všech objektově orientovaných jazycích, čtvrtý je specifický pro presentery v Nette, takže o něm pojednává [samostatná kapitola |best-practices:inject-method-attribute]. Nyní si jednotlivé možnosti přiblížíme a ukážeme na konkrétních případech. +Nyní si jednotlivé varianty ukážeme na konkrétních příkladech. Předávání konstruktorem @@ -21,17 +21,17 @@ Předávání konstruktorem Závislosti jsou předávány v okamžiku vytváření objektu jako argumenty konstruktoru: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Tato forma je vhodná pro povinné závislosti, které třída nezbytně potřebuje ke své funkci, neboť bez nich nepůjde instanci vytvořit. @@ -40,10 +40,10 @@ Od PHP 8.0 můžeme použít kratší formu zápisu ([constructor property promo ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ Od PHP 8.1 lze proměnnou označit příznakem `readonly`, který deklaruje, že ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,51 +65,111 @@ class MyService DI kontejner předá konstruktoru závislosti automaticky pomocí [autowiringu |autowiring]. Argumenty, které takto předat nelze (např. řetězce, čísla, booleany) [zapíšeme v konfiguraci |services#Argumenty]. +Constructor hell +---------------- + +Termín *constructor hell* označuje situaci, když potomek dědí od rodičovské třídy, jejíž konstruktor vyžaduje závislosti, a zároveň potomek vyžaduje závislosti. Přitom musí převzít a předat i ty rodičovské: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Problém nastane v okamžiku, kdy budeme chtít změnit kontruktor třídy `BaseClass`, třeba když přibude nová závislost. Pak je totiž nutné upravit také všechny konstruktory potomků. Což z takové úpravy dělá peklo. + +Jak tomu předcházet? Řešením je **dávat přednost kompozici před dědičností.** + +Tedy navrhneme kód jinak. Budeme se vyhýbat abstraktním `Base*` třídám. Místo toho, aby `MyClass` získávala určitou funkčnost tím, že dědí od `BaseClass`, si tuto funkčnost nechá předat jako závislost: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Předávání setterem ================== -Závislosti jsou předávány voláním metody, která je uloží do privátní proměnné. Obvyklou konvencí pojmenování těchto metod je tvar `set*()`, proto se jim říká settery. +Závislosti jsou předávány voláním metody, která je uloží do privátní proměnné. Obvyklou konvencí pojmenování těchto metod je tvar `set*()`, proto se jim říká settery, ale mohou se samozřejmě jmenovat jakkoliv jinak. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Tento způsob je vhodný pro nepovinné závislosti, které nejsou pro funkci třídy nezbytné, neboť není garantováno, že objekt závislost skutečně dostane (tj. že uživatel metodu zavolá). -Zároveň tento způsob připouští volat setter opakovaně a závislost tak měnit. Pokud to není žádoucí, přidáme do metody kontrolu, nebo od PHP 8.1 označíme proměnnou `$cache` příznakem `readonly`. +Zároveň tento způsob připouští volat setter opakovaně a závislost tak měnit. Pokud to není žádoucí, přidáme do metody kontrolu, nebo od PHP 8.1 označíme property `$cache` příznakem `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` -Volání setteru definujeme v konfiraci DI kontejneru v [sekci setup |services#Setup]. I tady se využívá automatického předávání závislostí pomocí autowiringu: +Volání setteru definujeme v konfiguraci DI kontejneru v [klíči setup |services#Setup]. I tady se využívá automatického předávání závislostí pomocí autowiringu: ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Nastavením proměnné Závislosti jsou předávány zapsáním přímo do členské proměnné: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Tento způsob se považuje za nevhodný, protože členská proměnná musí být deklarována jako `public`. A tudíž nemáme kontrolu nad tím, že předaná závislost bude skutečně daného typu (platilo před PHP 7.4) a přicházíme o možnost reagovat na nově přiřazenou závislost vlastním kódem, například zabránit následné změně. Zároveň se proměnná stává součástí veřejného rozhraní třídy, což nemusí být žádoucí. @@ -137,12 +197,18 @@ Nastavení proměnné definujeme v konfiraci DI kontejneru v [sekci setup |servi ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Inject +====== + +Zatímco předchozí tři způsoby platí obecně ve všech objektově orientovaných jazycích, injektování metodou, anotací či atributem *inject* je specifické čistě pro presentery v Nette. Pojednává o nich [samostatná kapitola |best-practices:inject-method-attribute]. + + Jaký způsob zvolit? =================== diff --git a/dependency-injection/cs/services.texy b/dependency-injection/cs/services.texy index 01f8bcef4c..52e3a357ea 100644 --- a/dependency-injection/cs/services.texy +++ b/dependency-injection/cs/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Režim Inject ============ -Pomocí příznaku `inject: true` se aktivuje předávání závislostí přes veřejné proměnné s anotací [inject |best-practices:inject-method-attribute#Anotace inject] a metody [inject*() |best-practices:inject-method-attribute#metody inject]. +Pomocí příznaku `inject: true` se aktivuje předávání závislostí přes veřejné proměnné s anotací [inject |best-practices:inject-method-attribute#Atributy Inject] a metody [inject*() |best-practices:inject-method-attribute#metody inject]. ```neon services: diff --git a/dependency-injection/de/@home.texy b/dependency-injection/de/@home.texy index 83da7cef4c..da4652cddc 100644 --- a/dependency-injection/de/@home.texy +++ b/dependency-injection/de/@home.texy @@ -5,8 +5,10 @@ Injektion von Abhängigkeiten Dependency Injection ist ein Entwurfsmuster, das die Art und Weise, wie Sie Code und Entwicklung betrachten, grundlegend verändern wird. Es öffnet den Weg in eine Welt der sauber gestalteten und nachhaltigen Anwendungen. - [Was ist Dependency Injection? |introduction] -- [Was ist ein DI-Container? |container] +- [Globaler Zustand & Singletons |global-state] - [Übergabe von Abhängigkeiten |passing-dependencies] +- [Was ist ein DI-Container? |container] +- [Häufig gestellte Fragen |faq] Nette DI diff --git a/dependency-injection/de/@left-menu.texy b/dependency-injection/de/@left-menu.texy index be8495e1df..404d36326d 100644 --- a/dependency-injection/de/@left-menu.texy +++ b/dependency-injection/de/@left-menu.texy @@ -1,8 +1,10 @@ Injektion von Abhängigkeiten **************************** - [Was ist DI? |introduction] -- [Was ist ein DI-Container? |container] +- [Globaler Zustand & Singletons |global-state] - [Übergabe von Abhängigkeiten |passing-dependencies] +- [Was ist ein DI-Container? |container] +- [Häufig gestellte Fragen |faq] Nette DI diff --git a/dependency-injection/de/faq.texy b/dependency-injection/de/faq.texy new file mode 100644 index 0000000000..0a4279f61e --- /dev/null +++ b/dependency-injection/de/faq.texy @@ -0,0 +1,112 @@ +Häufig gestellte Fragen zu DI (FAQ) +*********************************** + + +Ist DI ein anderer Name für IoC? .[#toc-is-di-another-name-for-ioc] +------------------------------------------------------------------- + +Die *Inversion of Control* (IoC) ist ein Prinzip, das sich auf die Art und Weise konzentriert, wie Code ausgeführt wird - ob Ihr Code externen Code initiiert oder Ihr Code in externen Code integriert ist, der ihn dann aufruft. +IoC ist ein umfassendes Konzept, das [Ereignisse |nette:glossary#Events], das so genannte [Hollywood-Prinzip |application:components#Hollywood style] und andere Aspekte einschließt. +Fabriken, die Teil von [Regel Nr. 3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It] sind und eine Umkehrung des `new` -Operators darstellen, sind ebenfalls Bestandteil dieses Konzepts. + +Bei der *Dependency Injection* (DI) geht es darum, wie ein Objekt über ein anderes Objekt Bescheid weiß, d.h. um die Abhängigkeit. Es handelt sich um ein Entwurfsmuster, das die explizite Weitergabe von Abhängigkeiten zwischen Objekten erfordert. + +Somit kann DI als eine spezielle Form von IoC bezeichnet werden. Allerdings sind nicht alle Formen von IoC im Hinblick auf die Reinheit des Codes geeignet. Zu den Anti-Patterns gehören zum Beispiel alle Techniken, die mit einem [globalen Zustand |global state] oder dem sogenannten [Service Locator |#What is a Service Locator] arbeiten. + + +Was ist ein Service Locator? .[#toc-what-is-a-service-locator] +-------------------------------------------------------------- + +Ein Service Locator ist eine Alternative zur Dependency Injection. Dabei wird ein zentraler Speicher angelegt, in dem alle verfügbaren Dienste oder Abhängigkeiten registriert werden. Wenn ein Objekt eine Abhängigkeit benötigt, fordert es diese beim Service Locator an. + +Im Vergleich zu Dependency Injection verliert es jedoch an Transparenz: Abhängigkeiten werden nicht direkt an Objekte weitergegeben und sind daher nicht leicht zu erkennen, was eine Untersuchung des Codes erfordert, um alle Verbindungen aufzudecken und zu verstehen. Auch das Testen ist komplizierter, da wir nicht einfach Mock-Objekte an die getesteten Objekte übergeben können, sondern den Weg über den Service Locator gehen müssen. Außerdem stört der Service Locator das Design des Codes, da die einzelnen Objekte von seiner Existenz wissen müssen, was sich von der Dependency Injection unterscheidet, bei der die Objekte keine Kenntnis vom DI-Container haben. + + +Wann ist es besser, DI nicht zu verwenden? .[#toc-when-is-it-better-not-to-use-di] +---------------------------------------------------------------------------------- + +Es sind keine Schwierigkeiten bekannt, die mit der Verwendung des Dependency Injection Design Pattern verbunden sind. Im Gegenteil, die Beschaffung von Abhängigkeiten von global zugänglichen Stellen führt zu einer [Reihe von Komplikationen |global-state], ebenso wie die Verwendung eines Service Locators. +Daher ist es ratsam, immer DI zu verwenden. Dies ist kein dogmatischer Ansatz, aber es wurde einfach keine bessere Alternative gefunden. + +Es gibt jedoch bestimmte Situationen, in denen wir Objekte nicht aneinander übergeben und sie aus dem globalen Raum beziehen. Zum Beispiel beim Debuggen von Code, wenn man einen Variablenwert an einem bestimmten Punkt im Programm ausgeben, die Dauer eines bestimmten Teils des Programms messen oder eine Meldung protokollieren muss. +In solchen Fällen, in denen es sich um temporäre Aktionen handelt, die später aus dem Code entfernt werden, ist es legitim, einen global zugänglichen Dumper, eine Stoppuhr oder einen Logger zu verwenden. Diese Werkzeuge gehören schließlich nicht zum Design des Codes. + + +Hat die Verwendung von DI auch Nachteile? .[#toc-does-using-di-have-its-drawbacks] +---------------------------------------------------------------------------------- + +Bringt die Verwendung von Dependency Injection irgendwelche Nachteile mit sich, wie z. B. eine höhere Komplexität beim Schreiben von Code oder eine schlechtere Leistung? Was verlieren wir, wenn wir anfangen, Code in Übereinstimmung mit DI zu schreiben? + +DI hat keinen Einfluss auf die Anwendungsleistung oder den Speicherbedarf. Die Leistung des DI-Containers kann eine Rolle spielen, aber im Fall von [Nette DI | nette-container] wird der Container in reines PHP kompiliert, so dass sein Overhead während der Laufzeit der Anwendung im Wesentlichen gleich Null ist. + +Beim Schreiben von Code ist es notwendig, Konstruktoren zu erstellen, die Abhängigkeiten akzeptieren. In der Vergangenheit konnte dies zeitaufwändig sein, aber dank moderner IDEs und der [Förderung von Konstruktoreigenschaften |https://blog.nette.org/de/php-8-0-vollstaendiger-ueberblick-ueber-die-neuigkeiten#toc-constructor-property-promotion] ist dies nun eine Sache von wenigen Sekunden. Factories lassen sich mit Nette DI und einem PhpStorm-Plugin mit wenigen Klicks erzeugen. +Andererseits besteht keine Notwendigkeit, Singletons und statische Zugriffspunkte zu schreiben. + +Daraus lässt sich schließen, dass eine richtig konzipierte Anwendung mit DI im Vergleich zu einer Anwendung mit Singletons weder kürzer noch länger ist. Teile des Codes, die mit Abhängigkeiten arbeiten, werden einfach aus den einzelnen Klassen extrahiert und an neue Stellen verschoben, d.h. in den DI-Container und die Fabriken. + + +Wie schreibt man eine Legacy-Anwendung auf DI um? .[#toc-how-to-rewrite-a-legacy-application-to-di] +--------------------------------------------------------------------------------------------------- + +Die Umstellung einer Legacy-Anwendung auf Dependency Injection kann ein schwieriger Prozess sein, insbesondere bei großen und komplexen Anwendungen. Es ist wichtig, diesen Prozess systematisch anzugehen. + +- Bei der Umstellung auf Dependency Injection ist es wichtig, dass alle Teammitglieder die Prinzipien und Praktiken verstehen, die verwendet werden. +- Führen Sie zunächst eine Analyse der bestehenden Anwendung durch, um die wichtigsten Komponenten und ihre Abhängigkeiten zu ermitteln. Erstellen Sie einen Plan, welche Teile in welcher Reihenfolge refaktorisiert werden sollen. +- Implementieren Sie einen DI-Container oder, noch besser, verwenden Sie eine vorhandene Bibliothek wie Nette DI. +- Schrittweise Umstrukturierung jedes Teils der Anwendung, um Dependency Injection zu verwenden. Dies kann die Änderung von Konstruktoren oder Methoden beinhalten, um Abhängigkeiten als Parameter zu akzeptieren. +- Ändern Sie die Stellen im Code, an denen Abhängigkeitsobjekte erstellt werden, so dass die Abhängigkeiten stattdessen vom Container injiziert werden. Dies kann die Verwendung von Fabriken beinhalten. + +Denken Sie daran, dass der Wechsel zu Dependency Injection eine Investition in die Codequalität und die langfristige Nachhaltigkeit der Anwendung ist. Auch wenn es eine Herausforderung sein mag, diese Änderungen vorzunehmen, sollte das Ergebnis ein sauberer, modularer und leicht testbarer Code sein, der für zukünftige Erweiterungen und Wartung bereit ist. + + +Warum ist Komposition der Vererbung vorzuziehen? .[#toc-why-composition-is-preferred-over-inheritance] +------------------------------------------------------------------------------------------------------ +Die Komposition ist der Vererbung vorzuziehen, da sie die Wiederverwendbarkeit des Codes ermöglicht, ohne dass man sich um die Auswirkungen von Änderungen sorgen muss. Dadurch wird eine lockerere Kopplung erreicht, bei der wir uns keine Sorgen machen müssen, dass die Änderung eines Codes dazu führt, dass ein anderer abhängiger Code geändert werden muss. Ein typisches Beispiel ist die Situation, die als [Konstruktorhölle |passing-dependencies#Constructor hell] bezeichnet wird. + + +Kann Nette DI Container auch außerhalb von Nette verwendet werden? .[#toc-can-nette-di-container-be-used-outside-of-nette] +-------------------------------------------------------------------------------------------------------------------------- + +Auf jeden Fall. Der Nette DI Container ist Teil von Nette, aber er ist als eigenständige Bibliothek konzipiert, die unabhängig von anderen Teilen des Frameworks verwendet werden kann. Installieren Sie ihn einfach mit Composer, erstellen Sie eine Konfigurationsdatei, die Ihre Dienste definiert, und verwenden Sie dann ein paar Zeilen PHP-Code, um den DI-Container zu erstellen. +Und schon können Sie damit beginnen, die Vorteile der Dependency Injection in Ihren Projekten zu nutzen. + +Das Kapitel [Nette DI Container |nette-container] beschreibt, wie ein spezifischer Anwendungsfall aussieht, einschließlich des Codes. + + +Warum ist die Konfiguration in NEON-Dateien? .[#toc-why-is-the-configuration-in-neon-files] +------------------------------------------------------------------------------------------- + +NEON ist eine einfache und leicht lesbare Konfigurationssprache, die innerhalb von Nette entwickelt wurde, um Anwendungen, Dienste und deren Abhängigkeiten einzurichten. Im Vergleich zu JSON oder YAML bietet sie für diesen Zweck wesentlich intuitivere und flexiblere Möglichkeiten. In NEON lassen sich auf natürliche Weise Bindungen beschreiben, die in Symfony & YAML entweder gar nicht oder nur durch eine komplexe Beschreibung möglich wären. + + +Verlangsamt das Parsen von NEON-Dateien die Anwendung? .[#toc-does-parsing-neon-files-slow-down-the-application] +---------------------------------------------------------------------------------------------------------------- + +Obwohl NEON-Dateien sehr schnell geparst werden, ist dieser Aspekt nicht wirklich von Bedeutung. Der Grund dafür ist, dass das Parsen von Dateien nur einmal beim ersten Start der Anwendung erfolgt. Danach wird der DI-Container-Code generiert, auf der Festplatte gespeichert und bei jeder nachfolgenden Anforderung ausgeführt, ohne dass ein weiteres Parsen erforderlich ist. + +So funktioniert es auch in einer Produktionsumgebung. Während der Entwicklung werden die NEON-Dateien jedes Mal geparst, wenn sich ihr Inhalt ändert, so dass der Entwickler immer über einen aktuellen DI-Container verfügt. Wie bereits erwähnt, ist das eigentliche Parsen eine Sache von einem Augenblick. + + +Wie greife ich in meiner Klasse auf die Parameter aus der Konfigurationsdatei zu? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +---------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Denken Sie an [Regel Nr. 1: Lass es dir übergeben |introduction#Rule #1: Let It Be Passed to You]. Wenn eine Klasse Informationen aus einer Konfigurationsdatei benötigt, brauchen wir nicht herauszufinden, wie wir auf diese Informationen zugreifen können; stattdessen fragen wir sie einfach ab - zum Beispiel über den Klassenkonstruktor. Und wir führen die Übergabe in der Konfigurationsdatei durch. + +In diesem Beispiel ist `%myParameter%` ein Platzhalter für den Wert des Parameters `myParameter`, der an den Konstruktor `MyClass` übergeben wird: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Wenn Sie mehrere Parameter übergeben oder Autowiring verwenden wollen, ist es sinnvoll, [die Parameter in ein Objekt zu verpacken |best-practices:passing-settings-to-presenters]. + + +Unterstützt Nette die PSR-11 Container-Schnittstelle? .[#toc-does-nette-support-psr-11-container-interface] +----------------------------------------------------------------------------------------------------------- + +Nette DI Container unterstützt PSR-11 nicht direkt. Wenn Sie jedoch Interoperabilität zwischen dem Nette DI Container und Bibliotheken oder Frameworks benötigen, die das PSR-11 Container Interface erwarten, können Sie einen [einfachen Adapter |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] erstellen, der als Brücke zwischen dem Nette DI Container und PSR-11 dient. diff --git a/dependency-injection/de/global-state.texy b/dependency-injection/de/global-state.texy new file mode 100644 index 0000000000..9b3d9d680b --- /dev/null +++ b/dependency-injection/de/global-state.texy @@ -0,0 +1,312 @@ +Globaler Zustand und Singletons +******************************* + +.[perex] +Warnung: Die folgenden Konstrukte sind Symptome für schlechtes Code-Design: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` oder `static::$var` + +Kommt eines dieser Konstrukte in Ihrem Code vor? Dann haben Sie die Möglichkeit, sich zu verbessern. Sie denken vielleicht, dass es sich um gängige Konstrukte handelt, die wir in Musterlösungen verschiedener Bibliotheken und Frameworks sehen. +Leider sind sie dennoch ein klarer Indikator für schlechtes Design. Sie haben eines gemeinsam: die Verwendung eines globalen Zustands. + +Wir sprechen hier sicherlich nicht von einer akademischen Reinheit. Die Verwendung von globalen Zuständen und Singletons hat zerstörerische Auswirkungen auf die Codequalität. Ihr Verhalten wird unvorhersehbar, verringert die Produktivität der Entwickler und zwingt Klassenschnittstellen dazu, über ihre wahren Abhängigkeiten zu lügen. Das verwirrt die Programmierer. + +In diesem Kapitel werden wir zeigen, wie dies möglich ist. + + +Globale Verkettung .[#toc-global-interlinking] +---------------------------------------------- + +Das grundsätzliche Problem mit dem globalen Zustand ist, dass er global zugänglich ist. Dadurch ist es möglich, über die globale (statische) Methode `DB::insert()` in die Datenbank zu schreiben. +In einer idealen Welt sollte ein Objekt nur mit anderen Objekten kommunizieren können, die [ihm direkt übergeben |passing-dependencies] wurden. +Wenn ich zwei Objekte `A` und `B` anlege und niemals eine Referenz von `A` an `B` übergebe, dann können weder `A` noch `B` auf das andere Objekt zugreifen oder dessen Zustand ändern. +Dies ist eine sehr wünschenswerte Eigenschaft des Codes. Es ist vergleichbar mit einer Batterie und einer Glühbirne; die Glühbirne leuchtet erst, wenn man sie miteinander verbindet. + +Dies gilt nicht für globale (statische) Variablen oder Singletons. Das Objekt `A` kann *drahtlos* auf das Objekt `C` zugreifen und es ändern, ohne eine Referenz zu übergeben, indem es `C::changeSomething()` aufruft. +Wenn das Objekt `B` auch auf das globale `C` zugreift, dann können `A` und `B` über `C` miteinander interagieren. + +Die Verwendung von globalen Variablen führt eine neue Form der *drahtlosen* Kopplung in das System ein, die von außen nicht sichtbar ist. +Sie schafft einen Nebelschleier, der das Verständnis und die Verwendung des Codes erschwert. +Die Entwickler müssen jede Zeile des Quellcodes lesen, um die Abhängigkeiten wirklich zu verstehen. Anstatt sich nur mit der Schnittstelle der Klassen vertraut zu machen. +Außerdem handelt es sich um eine völlig unnötige Kopplung. + +.[note] +Was das Verhalten angeht, gibt es keinen Unterschied zwischen einer globalen und einer statischen Variable. Sie sind gleichermaßen schädlich. + + +Die spukhafte Wirkung in der Ferne .[#toc-the-spooky-action-at-a-distance] +-------------------------------------------------------------------------- + +"Spukhafte Fernwirkung" - so nannte Albert Einstein 1935 ein Phänomen der Quantenphysik, das ihm eine Gänsehaut bereitete. +Es handelt sich dabei um die Quantenverschränkung, deren Besonderheit darin besteht, dass die Messung von Informationen über ein Teilchen sofort Auswirkungen auf ein anderes Teilchen hat, selbst wenn sie Millionen von Lichtjahren voneinander entfernt sind. +Dies verstößt scheinbar gegen das grundlegende Gesetz des Universums, dass sich nichts schneller als das Licht bewegen kann. + +In der Software-Welt können wir von einer "spukhaften Fernwirkung" sprechen, wenn wir einen Prozess laufen lassen, von dem wir glauben, dass er isoliert ist (weil wir ihm keine Referenzen übergeben haben), aber unerwartete Wechselwirkungen und Zustandsänderungen an entfernten Stellen des Systems auftreten, von denen wir dem Objekt nichts gesagt haben. Dies kann nur über den globalen Zustand geschehen. + +Stellen Sie sich vor, Sie treten in ein Projektentwicklungsteam ein, das über eine große, ausgereifte Codebasis verfügt. Ihr neuer Leiter bittet Sie, eine neue Funktion zu implementieren, und wie ein guter Entwickler beginnen Sie damit, einen Test zu schreiben. Da Sie aber neu im Projekt sind, machen Sie viele Tests vom Typ "was passiert, wenn ich diese Methode aufrufe". Und Sie versuchen, den folgenden Test zu schreiben: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // Ihre Kartennummer + $cc->charge(100); +} +``` + +Sie führen den Code aus, vielleicht mehrere Male, und nach einer Weile bemerken Sie auf Ihrem Telefon Benachrichtigungen von der Bank, dass jedes Mal, wenn Sie ihn ausführen, Ihre Kreditkarte mit 100 Dollar belastet wurde 🤦‍♂️ + +Wie um alles in der Welt konnte der Test eine tatsächliche Belastung verursachen? Es ist nicht einfach, mit einer Kreditkarte zu arbeiten. Sie müssen mit einem Webdienst eines Drittanbieters interagieren, Sie müssen die URL dieses Webdienstes kennen, Sie müssen sich anmelden und so weiter. +Keine dieser Informationen ist in dem Test enthalten. Noch schlimmer ist, dass Sie nicht einmal wissen, wo diese Informationen vorhanden sind und wie Sie externe Abhängigkeiten simulieren können, damit nicht bei jedem Durchlauf erneut 100 Dollar fällig werden. Und woher sollten Sie als neuer Entwickler wissen, dass das, was Sie gerade tun wollten, Sie um 100 Dollar ärmer machen würde? + +Das ist eine gespenstische Aktion aus der Ferne! + +Es bleibt Ihnen nichts anderes übrig, als sich durch eine Menge Quellcode zu wühlen und ältere und erfahrenere Kollegen zu fragen, bis Sie verstehen, wie die Zusammenhänge im Projekt funktionieren. +Das liegt daran, dass man bei einem Blick auf die Schnittstelle der Klasse `CreditCard` den globalen Zustand, der initialisiert werden muss, nicht feststellen kann. Selbst ein Blick in den Quellcode der Klasse verrät Ihnen nicht, welche Initialisierungsmethode Sie aufrufen müssen. Bestenfalls können Sie die globale Variable finden, auf die zugegriffen wird, und versuchen, daraus zu erraten, wie sie zu initialisieren ist. + +Die Klassen in einem solchen Projekt sind pathologische Lügner. Die Zahlungskarte gibt vor, dass man sie einfach instanziieren und die Methode `charge()` aufrufen kann. Insgeheim interagiert sie jedoch mit einer anderen Klasse, `PaymentGateway`. Sogar ihre Schnittstelle sagt, dass sie unabhängig initialisiert werden kann, aber in Wirklichkeit bezieht sie Anmeldedaten aus einer Konfigurationsdatei usw. +Den Entwicklern, die diesen Code geschrieben haben, ist klar, dass `CreditCard` `PaymentGateway` benötigt. Sie haben den Code auf diese Weise geschrieben. Aber für jeden, der neu in das Projekt einsteigt, ist dies ein völliges Rätsel und behindert das Lernen. + +Wie kann man das Problem lösen? Ganz einfach. **Lassen Sie die API Abhängigkeiten deklarieren. + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Beachten Sie, dass die Beziehungen innerhalb des Codes plötzlich offensichtlich sind. Indem Sie erklären, dass die Methode `charge()` `PaymentGateway` benötigt, müssen Sie niemanden fragen, wie der Code voneinander abhängig ist. Sie wissen, dass Sie eine Instanz der Methode erstellen müssen, und wenn Sie dies versuchen, stoßen Sie auf die Tatsache, dass Sie Zugriffsparameter bereitstellen müssen. Ohne sie würde der Code nicht einmal laufen. + +Und das Wichtigste ist, dass Sie jetzt das Zahlungs-Gateway simulieren können, damit Sie nicht jedes Mal, wenn Sie einen Test durchführen, 100 Dollar bezahlen müssen. + +Der globale Status bewirkt, dass Ihre Objekte heimlich auf Dinge zugreifen können, die nicht in ihren APIs deklariert sind, und macht Ihre APIs damit zu pathologischen Lügnern. + +Sie haben vielleicht noch nie darüber nachgedacht, aber immer wenn Sie einen globalen Zustand verwenden, schaffen Sie geheime drahtlose Kommunikationskanäle. Unheimliche Remote-Aktionen zwingen Entwickler dazu, jede Codezeile zu lesen, um mögliche Interaktionen zu verstehen, verringern die Produktivität der Entwickler und verwirren neue Teammitglieder. +Wenn Sie derjenige sind, der den Code erstellt hat, kennen Sie die tatsächlichen Abhängigkeiten, aber jeder, der nach Ihnen kommt, ist ahnungslos. + +Schreiben Sie keinen Code, der globale Zustände verwendet, sondern übergeben Sie lieber Abhängigkeiten. Das heißt, Dependency Injection. + + +Die Zerbrechlichkeit des globalen Staates .[#toc-brittleness-of-the-global-state] +--------------------------------------------------------------------------------- + +Bei Code, der globale Zustände und Singletons verwendet, ist es nie sicher, wann und von wem dieser Zustand geändert wurde. Dieses Risiko ist bereits bei der Initialisierung gegeben. Der folgende Code soll eine Datenbankverbindung erstellen und das Zahlungs-Gateway initialisieren, aber er löst immer wieder eine Ausnahme aus, und die Suche nach der Ursache ist extrem mühsam: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Sie müssen den Code im Detail durchgehen, um herauszufinden, dass das Objekt `PaymentGateway` drahtlos auf andere Objekte zugreift, von denen einige eine Datenbankverbindung benötigen. Sie müssen also die Datenbank vor `PaymentGateway` initialisieren. Der Nebel des globalen Zustands verbirgt dies jedoch vor Ihnen. Wie viel Zeit würden Sie sparen, wenn die API der einzelnen Klassen nicht lügen und ihre Abhängigkeiten deklarieren würde? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Ein ähnliches Problem ergibt sich bei der Verwendung des globalen Zugriffs auf eine Datenbankverbindung: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Beim Aufruf der Methode `save()` ist nicht sicher, ob bereits eine Datenbankverbindung erstellt wurde und wer für deren Erstellung verantwortlich ist. Wenn wir zum Beispiel die Datenbankverbindung spontan ändern wollten, vielleicht zu Testzwecken, müssten wir wahrscheinlich zusätzliche Methoden wie `DB::reconnect(...)` oder `DB::reconnectForTest()` erstellen. + +Betrachten wir ein Beispiel: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Wo können wir sicher sein, dass beim Aufruf von `$article->save()` wirklich die Testdatenbank verwendet wird? Was wäre, wenn die Methode `Foo::doSomething()` die globale Datenbankverbindung ändern würde? Um das herauszufinden, müssten wir den Quellcode der Klasse `Foo` und wahrscheinlich vieler anderer Klassen untersuchen. Dieser Ansatz würde jedoch nur eine kurzfristige Antwort liefern, da sich die Situation in der Zukunft ändern kann. + +Was wäre, wenn wir die Datenbankverbindung in eine statische Variable innerhalb der Klasse `Article` verschieben? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Das ändert überhaupt nichts. Das Problem ist ein globaler Zustand und es spielt keine Rolle, in welcher Klasse es sich versteckt. In diesem Fall, wie auch im vorherigen, haben wir keine Ahnung, in welche Datenbank geschrieben wird, wenn die Methode `$article->save()` aufgerufen wird. Jeder am entfernten Ende der Anwendung könnte die Datenbank jederzeit mit `Article::setDb()` ändern. Unter unseren Händen. + +Der globale Zustand macht unsere Anwendung **extrem anfällig**. + +Es gibt jedoch eine einfache Möglichkeit, mit diesem Problem umzugehen. Lassen Sie die API einfach Abhängigkeiten deklarieren, um die ordnungsgemäße Funktionalität zu gewährleisten. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Auf diese Weise wird die Sorge vor versteckten und unerwarteten Änderungen an Datenbankverbindungen beseitigt. Jetzt wissen wir genau, wo der Artikel gespeichert ist, und keine Code-Änderungen in einer anderen, nicht verwandten Klasse können die Situation mehr verändern. Der Code ist nicht mehr anfällig, sondern stabil. + +Schreiben Sie keinen Code, der globale Zustände verwendet, sondern übergeben Sie lieber Abhängigkeiten. Daher Dependency Injection. + + +Singleton .[#toc-singleton] +--------------------------- + +Singleton ist ein Entwurfsmuster, das gemäß der [Definition |https://en.wikipedia.org/wiki/Singleton_pattern] aus der berühmten Gang of Four-Publikation eine Klasse auf eine einzige Instanz beschränkt und globalen Zugriff auf diese bietet. Die Implementierung dieses Musters ähnelt normalerweise dem folgenden Code: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // und andere Methoden, die die Funktionen der Klasse ausführen +} +``` + +Leider führt das Singleton einen globalen Zustand in die Anwendung ein. Und wie wir oben gezeigt haben, ist ein globaler Zustand unerwünscht. Deshalb wird das Singleton als Antipattern betrachtet. + +Verwenden Sie keine Singletons in Ihrem Code und ersetzen Sie sie durch andere Mechanismen. Sie brauchen Singletons wirklich nicht. Wenn Sie jedoch die Existenz einer einzigen Instanz einer Klasse für die gesamte Anwendung garantieren müssen, überlassen Sie dies dem [DI-Container |container]. +Erstellen Sie also ein Anwendungssingleton oder einen Dienst. Dadurch wird die Klasse nicht mehr für ihre eigene Einzigartigkeit sorgen (d. h. sie wird keine `getInstance()` -Methode und keine statische Variable haben) und nur ihre Funktionen ausführen. Damit wird das Prinzip der einzigen Verantwortung nicht mehr verletzt. + + +Globaler Zustand vs. Tests .[#toc-global-state-versus-tests] +------------------------------------------------------------ + +Beim Schreiben von Tests gehen wir davon aus, dass jeder Test eine isolierte Einheit ist und dass kein externer Zustand in ihn eintritt. Und kein Zustand verlässt die Tests. Wenn ein Test abgeschlossen ist, sollte jeder mit dem Test verbundene Zustand automatisch vom Garbage Collector entfernt werden. Dadurch werden die Tests isoliert. Daher können wir die Tests in beliebiger Reihenfolge ausführen. + +Wenn jedoch globale Zustände/Singletons vorhanden sind, sind alle diese schönen Annahmen hinfällig. Ein Zustand kann einen Test betreten und verlassen. Plötzlich kann die Reihenfolge der Tests eine Rolle spielen. + +Um Singletons überhaupt testen zu können, müssen Entwickler oft ihre Eigenschaften lockern, indem sie beispielsweise zulassen, dass eine Instanz durch eine andere ersetzt wird. Solche Lösungen sind bestenfalls Hacks, die schwer zu wartenden und schwer zu verstehenden Code produzieren. Jeder Test oder jede Methode `tearDown()`, die einen globalen Zustand beeinflusst, muss diese Änderungen rückgängig machen. + +Der globale Zustand ist das größte Problem bei Unit-Tests! + +Wie kann man das Problem lösen? Ganz einfach. Schreiben Sie keinen Code, der Singletons verwendet, sondern ziehen Sie es vor, Abhängigkeiten zu übergeben. Das heißt, dependency injection. + + +Globale Konstanten .[#toc-global-constants] +------------------------------------------- + +Der globale Status ist nicht auf die Verwendung von Singletons und statischen Variablen beschränkt, sondern kann auch für globale Konstanten gelten. + +Konstanten, deren Wert uns keine neuen (`M_PI`) oder nützlichen (`PREG_BACKTRACK_LIMIT_ERROR`) Informationen liefert, sind eindeutig in Ordnung. +Umgekehrt sind Konstanten, die dazu dienen, Informationen innerhalb des Codes *drahtlos* weiterzugeben, nichts anderes als eine versteckte Abhängigkeit. Wie `LOG_FILE` im folgenden Beispiel. +Die Verwendung der Konstante `FILE_APPEND` ist völlig korrekt. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +In diesem Fall sollten wir den Parameter im Konstruktor der Klasse `Foo` deklarieren, um ihn zum Bestandteil der API zu machen: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Jetzt können wir Informationen über den Pfad zur Protokollierungsdatei übergeben und ihn bei Bedarf leicht ändern, was das Testen und Warten des Codes erleichtert. + + +Globale Funktionen und statische Methoden .[#toc-global-functions-and-static-methods] +------------------------------------------------------------------------------------- + +Wir möchten betonen, dass die Verwendung von statischen Methoden und globalen Funktionen an sich nicht problematisch ist. Wir haben die Unangemessenheit der Verwendung von `DB::insert()` und ähnlichen Methoden erläutert, aber es ging immer um den globalen Zustand, der in einer statischen Variablen gespeichert wird. Die Methode `DB::insert()` erfordert das Vorhandensein einer statischen Variablen, weil sie die Datenbankverbindung speichert. Ohne diese Variable wäre es unmöglich, die Methode zu implementieren. + +Die Verwendung von deterministischen statischen Methoden und Funktionen, wie `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` und viele andere, ist mit der Dependency Injection vollkommen vereinbar. Diese Funktionen liefern immer die gleichen Ergebnisse für die gleichen Eingabeparameter und sind daher vorhersehbar. Sie verwenden keinen globalen Zustand. + +Allerdings gibt es in PHP Funktionen, die nicht deterministisch sind. Dazu gehört z.B. die Funktion `htmlspecialchars()`. Ihr dritter Parameter, `$encoding`, wird, wenn er nicht angegeben wird, standardmäßig mit dem Wert der Konfigurationsoption `ini_get('default_charset')` belegt. Es wird daher empfohlen, diesen Parameter immer anzugeben, um ein unvorhersehbares Verhalten der Funktion zu vermeiden. Nette tut dies konsequent. + +Einige Funktionen, wie `strtolower()`, `strtoupper()` und ähnliche, haben sich in der jüngsten Vergangenheit nicht deterministisch verhalten und waren von der Einstellung `setlocale()` abhängig. Dies führte zu zahlreichen Komplikationen, vor allem bei der Arbeit mit der türkischen Sprache. +Das liegt daran, dass die türkische Sprache zwischen Groß- und Kleinschreibung `I` mit und ohne Punkt unterscheidet. So gab `strtolower('I')` das Zeichen `ı` und `strtoupper('i')` das Zeichen `İ` zurück, was dazu führte, dass Anwendungen eine Reihe von mysteriösen Fehlern verursachten. +Dieses Problem wurde jedoch in der PHP-Version 8.2 behoben, und die Funktionen sind nun nicht mehr vom Gebietsschema abhängig. + +Dies ist ein schönes Beispiel dafür, wie der globale Zustand Tausende von Entwicklern auf der ganzen Welt geplagt hat. Die Lösung bestand darin, ihn durch Dependency Injection zu ersetzen. + + +Wann ist es möglich, einen globalen Status zu verwenden? .[#toc-when-is-it-possible-to-use-global-state] +-------------------------------------------------------------------------------------------------------- + +Es gibt bestimmte Situationen, in denen es möglich ist, globale Zustände zu verwenden. Zum Beispiel beim Debuggen von Code, wenn Sie den Wert einer Variablen ausgeben oder die Dauer eines bestimmten Programmteils messen müssen. In solchen Fällen, die temporäre Aktionen betreffen, die später aus dem Code entfernt werden, ist es legitim, einen global verfügbaren Dumper oder eine Stoppuhr zu verwenden. Diese Werkzeuge sind nicht Teil des Codeentwurfs. + +Ein weiteres Beispiel sind die Funktionen für die Arbeit mit regulären Ausdrücken `preg_*`, die intern kompilierte reguläre Ausdrücke in einem statischen Cache im Speicher ablegen. Wenn Sie denselben regulären Ausdruck mehrmals in verschiedenen Teilen des Codes aufrufen, wird er nur einmal kompiliert. Der Cache spart Leistung und ist außerdem für den Benutzer völlig unsichtbar, so dass eine solche Verwendung als legitim angesehen werden kann. + + +Zusammenfassung .[#toc-summary] +------------------------------- + +Wir haben gezeigt, warum es Sinn macht + +1) Entfernen Sie alle statischen Variablen aus dem Code +2) Deklarieren Sie Abhängigkeiten +3) Und verwenden Sie Dependency Injection + +Wenn Sie über den Entwurf von Code nachdenken, sollten Sie bedenken, dass jedes `static $foo` ein Problem darstellt. Damit Ihr Code eine DI-konforme Umgebung wird, ist es unerlässlich, den globalen Zustand vollständig zu beseitigen und durch Dependency Injection zu ersetzen. + +Während dieses Prozesses kann es vorkommen, dass Sie eine Klasse aufteilen müssen, weil sie mehr als eine Verantwortung hat. Machen Sie sich keine Gedanken darüber; streben Sie das Prinzip der einen Verantwortung an. + +*Ich möchte Miško Hevery danken, dessen Artikel wie [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] die Grundlage für dieses Kapitel bilden.* diff --git a/dependency-injection/de/introduction.texy b/dependency-injection/de/introduction.texy index 9661522999..74bb6c1d35 100644 --- a/dependency-injection/de/introduction.texy +++ b/dependency-injection/de/introduction.texy @@ -2,17 +2,17 @@ Was ist Dependency Injection? ***************************** .[perex] -In diesem Kapitel werden Sie mit den grundlegenden Programmierpraktiken vertraut gemacht, die Sie beim Schreiben jeder Anwendung befolgen sollten. Dies sind die Grundlagen, die erforderlich sind, um sauberen, verständlichen und wartbaren Code zu schreiben. +In diesem Kapitel werden Sie mit den grundlegenden Programmierpraktiken vertraut gemacht, die Sie beim Schreiben jeder Anwendung befolgen sollten. Dies sind die Grundlagen, die zum Schreiben von sauberem, verständlichem und wartbarem Code erforderlich sind. -Wenn Sie diese Regeln lernen und befolgen, wird Nette Ihnen bei jedem Schritt zur Seite stehen. Es wird Routineaufgaben für Sie erledigen und es Ihnen so bequem wie möglich machen, damit Sie sich auf die eigentliche Logik konzentrieren können. +Wenn Sie diese Regeln lernen und befolgen, wird Nette Ihnen bei jedem Schritt zur Seite stehen. Es wird Routineaufgaben für Sie erledigen und Ihnen maximalen Komfort bieten, so dass Sie sich auf die eigentliche Logik konzentrieren können. -Die Prinzipien, die wir hier zeigen, sind ganz einfach. Sie müssen sich um nichts kümmern. +Die Prinzipien, die wir hier zeigen, sind ganz einfach. Sie brauchen sich um nichts zu kümmern. Erinnern Sie sich an Ihr erstes Programm? .[#toc-remember-your-first-program] ----------------------------------------------------------------------------- -Wir haben keine Ahnung, in welcher Sprache Sie es geschrieben haben, aber wenn es PHP war, würde es wahrscheinlich ungefähr so aussehen: +Wir wissen nicht, in welcher Sprache Sie es geschrieben haben, aber wenn es PHP war, könnte es etwa so aussehen: ```php function summe(float $a, float $b): float @@ -25,31 +25,31 @@ echo summe(23, 1); // gibt 24 aus Ein paar triviale Codezeilen, aber so viele wichtige Konzepte, die darin versteckt sind. Dass es Variablen gibt. Dass der Code in kleinere Einheiten unterteilt ist, die zum Beispiel Funktionen sind. Dass wir ihnen Eingabeargumente übergeben und sie Ergebnisse zurückgeben. Alles, was fehlt, sind Bedingungen und Schleifen. -Die Tatsache, dass wir einer Funktion eine Eingabe übergeben und sie ein Ergebnis zurückliefert, ist ein vollkommen verständliches Konzept, das auch in anderen Bereichen, wie der Mathematik, verwendet wird. +Die Tatsache, dass eine Funktion Eingabedaten entgegennimmt und ein Ergebnis zurückliefert, ist ein durchaus verständliches Konzept, das auch in anderen Bereichen, z. B. der Mathematik, verwendet wird. -Eine Funktion hat eine Signatur, die aus ihrem Namen, einer Liste von Parametern und deren Typen und schließlich dem Typ des Rückgabewerts besteht. Als Benutzer sind wir an der Signatur interessiert; über die interne Implementierung brauchen wir normalerweise nichts zu wissen. +Eine Funktion hat ihre Signatur, die aus ihrem Namen, einer Liste von Parametern und deren Typen und schließlich dem Typ des Rückgabewerts besteht. Als Benutzer sind wir an der Signatur interessiert und müssen normalerweise nichts über die interne Implementierung wissen. -Stellen Sie sich nun vor, dass die Signatur einer Funktion wie folgt aussieht: +Stellen Sie sich nun vor, die Funktionssignatur sähe wie folgt aus: ```php function summe(float $x): float ``` -Eine Addition mit einem Parameter? Das ist seltsam... Wie wäre es hiermit? +Ein Zusatz mit einem Parameter? Das ist seltsam... Und was ist damit? ```php function summe(): float ``` -Das ist wirklich seltsam, nicht wahr? Was glaubst du, wie die Funktion verwendet wird? +Das ist doch wirklich seltsam, oder? Wie wird die Funktion verwendet? ```php echo summe(); // was wird gedruckt? ``` -Wenn wir uns einen solchen Code ansehen, sind wir verwirrt. Nicht nur ein Anfänger würde ihn nicht verstehen, auch ein erfahrener Programmierer würde einen solchen Code nicht verstehen. +Wenn wir uns einen solchen Code ansehen, wären wir verwirrt. Nicht nur ein Anfänger würde ihn nicht verstehen, sondern auch ein erfahrener Programmierer würde einen solchen Code nicht verstehen. -Fragen Sie sich, wie eine solche Funktion eigentlich aussehen würde? Woher würde sie die Addierer nehmen? Wahrscheinlich würde sie sie sich *irgendwie* selbst beschaffen, etwa so: +Fragen Sie sich, wie eine solche Funktion eigentlich aussehen würde? Woher würde sie die Summanden bekommen? Sie würde sie wahrscheinlich *irgendwie* selbst beschaffen, vielleicht so: ```php function summe(): float @@ -66,13 +66,13 @@ Es stellt sich heraus, dass es versteckte Bindungen zu anderen Funktionen (oder Nicht hier entlang! .[#toc-not-this-way] ---------------------------------------- -Das Design, das uns gerade gezeigt wurde, ist die Essenz vieler negativer Merkmale: +Das eben gezeigte Design ist die Essenz vieler negativer Merkmale: -- die Funktionssignatur gibt vor, dass sie keine Summanden braucht, was uns verwirrt +- die Funktionssignatur gibt vor, dass sie die Summanden nicht braucht, was uns verwirrt - wir haben keine Ahnung, wie wir die Funktion mit zwei anderen Zahlen rechnen lassen können -- wir mussten in den Code schauen, um zu sehen, woher die Summanden kommen -- wir haben versteckte Bindungen entdeckt -- um alles zu verstehen, müssen wir auch diese Bindungen untersuchen +- wir mussten uns den Code ansehen, um herauszufinden, woher die Summanden kamen +- wir haben versteckte Abhängigkeiten gefunden +- ein vollständiges Verständnis erfordert auch die Untersuchung dieser Abhängigkeiten Und ist es überhaupt die Aufgabe der Additionsfunktion, Eingaben zu beschaffen? Nein, natürlich nicht. Ihre Aufgabe ist es nur, zu addieren. @@ -93,20 +93,20 @@ Regel Nr. 1: Lass es dir übergeben .[#toc-rule-1-let-it-be-passed-to-you] Die wichtigste Regel lautet: **alle Daten, die Funktionen oder Klassen benötigen, müssen an sie übergeben werden**. -Anstatt versteckte Mechanismen zu erfinden, die ihnen helfen, irgendwie selbst an die Daten zu kommen, übergeben Sie einfach die Parameter. So sparen Sie sich die Zeit, die Sie brauchen, um sich versteckte Wege auszudenken, die Ihren Code definitiv nicht verbessern. +Anstatt versteckte Wege für den Zugriff auf die Daten selbst zu erfinden, übergeben Sie einfach die Parameter. So sparen Sie Zeit, die Sie sonst für das Erfinden versteckter Pfade aufwenden müssten, die Ihren Code sicherlich nicht verbessern würden. -Wenn Sie diese Regel immer und überall befolgen, sind Sie auf dem Weg zu Code ohne versteckte Bindungen. Auf dem Weg zu Code, der nicht nur für den Autor, sondern auch für jeden, der ihn später liest, verständlich ist. Wo alles aus den Signaturen von Funktionen und Klassen verständlich ist und man nicht nach versteckten Geheimnissen in der Implementierung suchen muss. +Wenn Sie diese Regel immer und überall befolgen, sind Sie auf dem Weg zu einem Code ohne versteckte Abhängigkeiten. Zu einem Code, der nicht nur für den Autor, sondern auch für jeden, der ihn später liest, verständlich ist. Wo alles aus den Signaturen von Funktionen und Klassen verständlich ist und man nicht nach versteckten Geheimnissen in der Implementierung suchen muss. -Diese Technik wird fachmännisch **dependency injection** genannt. Und die Daten werden **Abhängigkeiten** genannt. Aber es ist eine einfache Parameterübergabe, nichts weiter. +Diese Technik wird in der Fachsprache **dependency injection** genannt. Und diese Daten werden **Abhängigkeiten** genannt. Es ist nur eine gewöhnliche Parameterübergabe, nichts weiter. .[note] -Bitte verwechseln Sie nicht die Dependency Injection, die ein Entwurfsmuster ist, mit dem "Dependency Injection Container", der ein Werkzeug ist, etwas völlig anderes. Wir werden später über Container sprechen. +Verwechseln Sie bitte nicht Dependency Injection, die ein Entwurfsmuster ist, mit einem "Dependency Injection Container", der ein Werkzeug ist, etwas diametral anderes. Wir werden uns später mit Containern beschäftigen. Von Funktionen zu Klassen .[#toc-from-functions-to-classes] ----------------------------------------------------------- -Und was haben Klassen damit zu tun? Eine Klasse ist ein komplexeres Gebilde als eine einfache Funktion, aber auch hier gilt Regel Nr. 1. Es gibt einfach [mehr Möglichkeiten, Argumente |passing-dependencies] zu übergeben. Zum Beispiel, ganz ähnlich wie im Fall einer Funktion: +Und wie hängen die Klassen zusammen? Eine Klasse ist eine komplexere Einheit als eine einfache Funktion, aber auch hier gilt Regel Nr. 1 uneingeschränkt. Es gibt einfach [mehr Möglichkeiten, Argumente |passing-dependencies] zu übergeben. Zum Beispiel, ganz ähnlich wie im Fall einer Funktion: ```php class Mathematik @@ -121,7 +121,7 @@ $math = new Mathematik; echo $math->summe(23, 1); // 24 ``` -Oder durch die Verwendung anderer Methoden oder direkt durch den Konstruktor: +Oder durch andere Methoden, oder direkt durch den Konstruktor: ```php class Summe @@ -149,9 +149,9 @@ Beide Beispiele stehen vollständig im Einklang mit Dependency Injection. Beispiele aus der Praxis .[#toc-real-life-examples] --------------------------------------------------- -In der realen Welt werden Sie keine Klassen für die Addition von Zahlen schreiben. Kommen wir nun zu den Beispielen aus der realen Welt. +In der realen Welt werden Sie keine Klassen für die Addition von Zahlen schreiben. Kommen wir nun zu den praktischen Beispielen. -Nehmen wir eine Klasse `Article`, die einen Blog-Artikel darstellt: +Nehmen wir eine Klasse `Article`, die einen Blogbeitrag darstellt: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Die Methode `save()` speichert den Artikel in einer Datenbanktabelle. Die Implementierung mit [Nette Database |database:] wäre ein Kinderspiel, wenn es nicht einen Haken gäbe: Woher soll `Article` die Datenbankverbindung, d.h. das Objekt der Klasse `Nette\Database\Connection` bekommen? +Die Methode `save()` speichert den Artikel in einer Datenbanktabelle. Die Implementierung mit [Nette Database |database:] ist ein Kinderspiel, wenn es nicht ein Problem gäbe: Woher bekommt `Article` die Datenbankverbindung, d.h. ein Objekt der Klasse `Nette\Database\Connection`? -Es scheint, dass wir eine Menge Möglichkeiten haben. Es kann sie von irgendwoher aus einer statischen Variable beziehen. Oder sie von einer Klasse erben, die die Datenbankverbindung bereitstellt. Oder die Vorteile eines [Singletons |global-state#Singleton] nutzen. Oder die sogenannten Fassaden, die in Laravel verwendet werden: +Es scheint, dass wir viele Möglichkeiten haben. Es kann die Verbindung von einer statischen Variable irgendwoher nehmen. Oder von einer Klasse erben, die eine Datenbankverbindung bereitstellt. Oder die Vorteile eines [Singletons |global-state#Singleton] nutzen. Oder sogenannte Fassaden verwenden, die in Laravel verwendet werden: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ Toll, wir haben das Problem gelöst. Oder haben wir das? -Erinnern wir uns an [Regel Nr. 1: Lass es dir übergeben |#rule #1: Let It Be Passed to You]: Alle Abhängigkeiten, die die Klasse benötigt, müssen an sie weitergegeben werden. Denn wenn wir das nicht tun und gegen die Regel verstoßen, haben wir den Weg zu schmutzigem Code voller versteckter Bindungen und Unverständlichkeit eingeschlagen, und das Ergebnis wird eine Anwendung sein, die nur schwer zu warten und zu entwickeln ist. +Erinnern wir uns an [Regel Nr. 1: "Let It Be Passed to You |#rule #1: Let It Be Passed to You]": Alle Abhängigkeiten, die die Klasse benötigt, müssen an sie weitergegeben werden. Denn wenn wir diese Regel brechen, haben wir uns auf einen Weg zu schmutzigem Code voller versteckter Abhängigkeiten und Unverständlichkeit begeben, und das Ergebnis wird eine Anwendung sein, die mühsam zu warten und zu entwickeln sein wird. -Der Benutzer der Klasse `Article` hat keine Ahnung, wo die Methode `save()` den Artikel speichert. In einer Datenbanktabelle? In welcher, der Produktions- oder der Entwicklungstabelle? Und wie kann dies geändert werden? +Der Benutzer der Klasse `Article` hat keine Ahnung, wo die Methode `save()` den Artikel speichert. In einer Datenbanktabelle? In welcher, der Produktions- oder der Testtabelle? Und wie kann sie geändert werden? -Der Benutzer muss sich ansehen, wie die Methode `save()` implementiert ist, um die Verwendung der Methode `DB::insert()` zu finden. Er muss also weiter suchen, um herauszufinden, wie diese Methode eine Datenbankverbindung herstellt. Und versteckte Bindungen können eine ziemlich lange Kette bilden. +Der Benutzer muss sich ansehen, wie die Methode `save()` implementiert ist, und findet die Verwendung der Methode `DB::insert()`. Er muss also weiter suchen, um herauszufinden, wie diese Methode eine Datenbankverbindung herstellt. Und versteckte Abhängigkeiten können eine ziemlich lange Kette bilden. -Versteckte Bindungen, Laravel-Fassaden oder statische Variablen sind in sauberem, gut durchdachtem Code nie vorhanden. In sauberem und gut durchdachtem Code werden Argumente übergeben: +In sauberem und gut durchdachtem Code gibt es niemals versteckte Abhängigkeiten, Laravel-Fassaden oder statische Variablen. In sauberem und gut durchdachtem Code werden Argumente übergeben: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Noch praktischer ist es, wie wir gleich sehen werden, einen Konstruktor zu verwenden: +Ein noch praktischerer Ansatz ist, wie wir später sehen werden, die Verwendung des Konstruktors: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Wenn Sie ein erfahrener Programmierer sind, denken Sie vielleicht, dass `Article` überhaupt keine `save()` -Methode haben sollte, sondern eine reine Datenkomponente sein sollte, und dass ein separates Repository sich um die Speicherung kümmern sollte. Das macht auch Sinn. Aber das würde weit über das Thema hinausgehen, bei dem es um Dependency Injection geht, und wir würden versuchen, einfache Beispiele zu geben. +Wenn Sie ein erfahrener Programmierer sind, denken Sie vielleicht, dass `Article` überhaupt keine Methode `save()` haben sollte; es sollte eine reine Datenkomponente darstellen, und ein separates Repository sollte sich um das Speichern kümmern. Das macht Sinn. Aber das würde weit über den Rahmen dieses Themas hinausgehen, das sich mit der Injektion von Abhängigkeiten befasst, und den Versuch, einfache Beispiele zu liefern. -Wenn Sie eine Klasse schreiben, die zum Beispiel eine Datenbank benötigt, um zu funktionieren, sollten Sie nicht herausfinden, woher Sie sie bekommen, sondern sie an sich selbst übergeben. Vielleicht als Parameter in einem Konstruktor oder einer anderen Methode. Deklarieren Sie Abhängigkeiten. Legen Sie sie in der API Ihrer Klasse offen. So erhalten Sie verständlichen und vorhersehbaren Code. +Wenn Sie eine Klasse schreiben, die zum Beispiel eine Datenbank für ihren Betrieb benötigt, erfinden Sie nicht, woher Sie diese bekommen, sondern lassen Sie sie übergeben. Entweder als Parameter des Konstruktors oder einer anderen Methode. Geben Sie Abhängigkeiten zu. Geben Sie sie in der API Ihrer Klasse an. Sie werden verständlichen und vorhersehbaren Code erhalten. -Wie wäre es mit dieser Klasse, die Fehlermeldungen protokolliert: +Und was ist mit dieser Klasse, die Fehlermeldungen protokolliert? ```php class Logger @@ -266,9 +266,9 @@ Was meinen Sie, haben wir die [Regel Nr. 1: Lass es dir übergeben |#rule #1: Le Wir haben es nicht getan. -Die Schlüsselinformation, das Verzeichnis der Protokolldatei, wird von der Klasse aus der Konstante *erhalten*. +Die Schlüsselinformation, d.h. das Verzeichnis mit der Protokolldatei, wird von der Klasse selbst aus der Konstante *erhalten*. -Siehe das Verwendungsbeispiel: +Sehen Sie sich das Beispiel für die Verwendung an: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Könnten Sie, ohne die Implementierung zu kennen, die Frage beantworten, wo die Nachrichten geschrieben werden? Würden Sie vermuten, dass das Vorhandensein der Konstante LOG_DIR notwendig ist, damit es funktioniert? Und wären Sie in der Lage, eine zweite Instanz zu erstellen, die an einen anderen Ort schreibt? Sicherlich nicht. +Können Sie, ohne die Implementierung zu kennen, die Frage beantworten, wo die Nachrichten geschrieben werden? Würden Sie vermuten, dass das Vorhandensein der Konstante `LOG_DIR` für das Funktionieren des Programms notwendig ist? Und könnten Sie eine zweite Instanz erstellen, die an einen anderen Ort schreibt? Sicherlich nicht. Lassen Sie uns die Klasse korrigieren: @@ -295,7 +295,7 @@ class Logger } ``` -Die Klasse ist jetzt viel übersichtlicher, besser konfigurierbar und daher nützlicher. +Die Klasse ist jetzt viel verständlicher, konfigurierbar und daher nützlicher. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Aber das ist mir egal! .[#toc-but-i-don-t-care] ----------------------------------------------- -*"Wenn ich ein Artikelobjekt erstelle und save() aufrufe, möchte ich mich nicht mit der Datenbank befassen, ich möchte nur, dass es in der Datenbank gespeichert wird, die ich in der Konfiguration eingestellt habe. "* +*"Wenn ich ein Artikel-Objekt erstelle und save() aufrufe, möchte ich mich nicht mit der Datenbank befassen; ich möchte nur, dass es in der Datenbank gespeichert wird, die ich in der Konfiguration eingestellt habe."* -*"Wenn ich Logger verwende, möchte ich nur, dass die Nachricht geschrieben wird, und ich möchte mich nicht darum kümmern, wo. Es sollen die globalen Einstellungen verwendet werden. "* +*"Wenn ich Logger verwende, möchte ich nur, dass die Nachricht geschrieben wird, und ich möchte mich nicht darum kümmern, wo. Es sollen die globalen Einstellungen verwendet werden."* -Dies sind korrekte Kommentare. +Dies sind berechtigte Einwände. -Nehmen wir als Beispiel eine Klasse, die Newsletter verschickt und protokolliert, wie das gelaufen ist: +Betrachten wir als Beispiel eine Klasse, die Newsletter versendet und protokolliert, wie es gelaufen ist: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -Die verbesserte `Logger`, die nicht mehr die Konstante `LOG_DIR` verwendet, erfordert einen Dateipfad im Konstruktor. Wie lässt sich das Problem lösen? Der Klasse `NewsletterDistributor` ist es egal, wohin die Nachrichten geschrieben werden, sie will sie nur schreiben. +Bei der verbesserten Version `Logger`, die nicht mehr die Konstante `LOG_DIR` verwendet, muss der Dateipfad im Konstruktor angegeben werden. Wie lässt sich das Problem lösen? Der Klasse `NewsletterDistributor` ist es egal, wohin die Nachrichten geschrieben werden; sie will sie einfach nur schreiben. -Die Lösung ist wieder [Regel Nr. 1: Lass es dir übergeben |#rule #1: Let It Be Passed to You]: Gib der Klasse alle Daten, die sie braucht. +Die Lösung ist wieder [Regel Nr. 1: Lass sie dir übergeben |#rule #1: Let It Be Passed to You]: Übergeben Sie alle Daten, die die Klasse benötigt. -Wir übergeben also den Pfad zum Protokoll an den Konstruktor, mit dem wir dann das Objekt `Logger` erstellen ? +Heißt das also, dass wir den Pfad zum Protokoll über den Konstruktor übergeben, den wir dann bei der Erstellung des `Logger` Objekts verwenden? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -So nicht! Denn der Pfad **gehört** nicht zu den Daten, die die Klasse `NewsletterDistributor` braucht; sie braucht `Logger`. Die Klasse braucht den Logger selbst. Und den werden wir weitergeben: +Nein, nicht auf diese Weise! Der Pfad gehört nicht zu den Daten, die die Klasse `NewsletterDistributor` braucht, sondern die Klasse `Logger` braucht ihn. Verstehen Sie den Unterschied? Die Klasse `NewsletterDistributor` braucht den Logger selbst. Das ist es also, was wir übergeben: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Aus den Signaturen der Klasse `NewsletterDistributor` geht hervor, dass die Protokollierung Teil ihrer Funktionalität ist. Und die Aufgabe, den Logger durch einen anderen zu ersetzen, vielleicht zu Testzwecken, ist recht trivial. -Wenn der Konstruktor der Klasse `Logger` geändert wird, hat dies keine Auswirkungen auf unsere Klasse. +Nun geht aus den Signaturen der Klasse `NewsletterDistributor` hervor, dass auch die Protokollierung zu ihrer Funktionalität gehört. Und die Aufgabe, den Logger gegen einen anderen auszutauschen, etwa zu Testzwecken, ist völlig trivial. +Wenn sich außerdem der Konstruktor der Klasse `Logger` ändert, hat dies keine Auswirkungen auf unsere Klasse. -Regel Nr. 2: Nimm, was dir gehört .[#toc-rule-2-take-what-is-yours] -------------------------------------------------------------------- +Regel Nr. 2: Nimm, was dir gehört .[#toc-rule-2-take-what-s-yours] +------------------------------------------------------------------ -Lassen Sie sich nicht in die Irre führen und lassen Sie sich nicht die Parameter Ihrer Abhängigkeiten übergeben. Geben Sie die Abhängigkeiten direkt weiter. +Lassen Sie sich nicht in die Irre führen und lassen Sie sich nicht die Abhängigkeiten von Ihren Abhängigen geben. Übergeben Sie nur Ihre eigenen Abhängigkeiten. -Dadurch wird Code, der andere Objekte verwendet, völlig unabhängig von Änderungen an deren Konstruktoren. Seine API wird wahrheitsgetreuer sein. Und das Wichtigste: Es wird trivial sein, diese Abhängigkeiten gegen andere auszutauschen. +Dadurch wird der Code, der andere Objekte verwendet, völlig unabhängig von Änderungen in deren Konstruktoren. Seine API wird wahrheitsgetreuer sein. Und vor allem wird es trivial sein, diese Abhängigkeiten durch andere zu ersetzen. -Ein neues Mitglied der Familie .[#toc-a-new-member-of-the-family] ------------------------------------------------------------------ +Neues Familienmitglied .[#toc-new-family-member] +------------------------------------------------ -Das Entwicklungsteam beschloss, einen zweiten Logger zu erstellen, der in die Datenbank schreibt. Wir erstellen also eine Klasse `DatabaseLogger`. Wir haben also zwei Klassen, `Logger` und `DatabaseLogger`, die eine schreibt in eine Datei, die andere in eine Datenbank ... finden Sie nicht auch, dass dieser Name etwas seltsam ist? -Wäre es nicht besser, `Logger` in `FileLogger` umzubenennen? Sicher wäre es das. +Das Entwicklungsteam beschloss, einen zweiten Logger zu erstellen, der in die Datenbank schreibt. Also erstellen wir eine `DatabaseLogger` Klasse. Wir haben also zwei Klassen, `Logger` und `DatabaseLogger`, eine schreibt in eine Datei, die andere in eine Datenbank ... kommt Ihnen die Namensgebung nicht seltsam vor? +Wäre es nicht besser, `Logger` in `FileLogger` umzubenennen? Eindeutig ja. -Aber lassen Sie uns das klug anstellen. Wir werden eine Schnittstelle unter dem ursprünglichen Namen erstellen: +Aber lassen Sie uns das auf intelligente Weise tun. Wir erstellen eine Schnittstelle unter dem ursprünglichen Namen: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...die beide Logger implementieren werden: +... die beide Logger implementieren werden: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -Auf diese Weise muss im restlichen Code, in dem der Logger verwendet wird, nichts geändert werden. Zum Beispiel wird der Konstruktor der Klasse `NewsletterDistributor` immer noch damit zufrieden sein, `Logger` als Parameter zu benötigen. Und es bleibt uns überlassen, welche Instanz wir an ihn übergeben. +Daher muss im restlichen Code, in dem der Logger verwendet wird, nichts geändert werden. Zum Beispiel wird der Konstruktor der Klasse `NewsletterDistributor` immer noch damit zufrieden sein, `Logger` als Parameter zu benötigen. Und es bleibt uns überlassen, welche Instanz wir übergeben. -**Das ist der Grund, warum wir Schnittstellennamen niemals das Suffix `Interface` oder das Präfix `I` geben.** Andernfalls wäre es unmöglich, Code so schön zu entwickeln. +**Deshalb fügen wir den Schnittstellennamen niemals das Suffix `Interface` oder das Präfix `I` hinzu.** Sonst wäre es nicht möglich, den Code so schön zu entwickeln. Houston, wir haben ein Problem .[#toc-houston-we-have-a-problem] ---------------------------------------------------------------- -Während wir uns in der gesamten Anwendung mit einer einzigen Instanz eines Loggers, ob Datei oder Datenbank, begnügen und diese einfach überall dort übergeben können, wo etwas protokolliert wird, sieht es im Fall der Klasse `Article` ganz anders aus. Wir erstellen nämlich Instanzen davon nach Bedarf, möglicherweise mehrfach. Wie geht man mit der Datenbankanbindung in ihrem Konstruktor um? +Während wir mit einer einzigen Instanz des Loggers, egal ob datei- oder datenbankbasiert, in der gesamten Anwendung auskommen und sie einfach überall dort übergeben können, wo etwas protokolliert wird, verhält es sich bei der Klasse `Article` ganz anders. Wir erzeugen ihre Instanzen je nach Bedarf, sogar mehrfach. Wie geht man mit der Datenbankabhängigkeit in ihrem Konstruktor um? -Als Beispiel können wir einen Controller verwenden, der nach dem Absenden eines Formulars einen Artikel in der Datenbank speichern soll: +Ein Beispiel kann ein Controller sein, der nach dem Absenden eines Formulars einen Artikel in der Datenbank speichern soll: ```php class EditController extends Controller @@ -437,17 +437,17 @@ class EditController extends Controller } ``` -Eine mögliche Lösung bietet sich direkt an: Lassen Sie das Datenbankobjekt vom Konstruktor an `EditController` übergeben und verwenden Sie `$article = new Article($this->db)`. +Eine mögliche Lösung liegt auf der Hand: Übergeben Sie das Datenbankobjekt an den `EditController` Konstruktor und verwenden Sie `$article = new Article($this->db)`. -Wie im vorherigen Fall mit `Logger` und dem Dateipfad ist dies nicht der richtige Ansatz. Die Datenbank ist keine Abhängigkeit von `EditController`, sondern von `Article`. Die Übergabe der Datenbank verstößt also gegen [Regel #2: Nimm, was dir gehört |#rule #2: take what is yours]. Wenn der Konstruktor der Klasse `Article` geändert wird (ein neuer Parameter wird hinzugefügt), muss der Code an allen Stellen, an denen Instanzen erstellt werden, ebenfalls geändert werden. Ufff. +Genau wie im vorherigen Fall mit `Logger` und dem Dateipfad ist dies nicht der richtige Ansatz. Die Datenbank ist keine Abhängigkeit von `EditController`, sondern von `Article`. Die Übergabe der Datenbank verstößt gegen [Regel #2: Nimm, was dir gehört |#rule #2: take what's yours]. Wenn sich der Konstruktor der Klasse `Article` ändert (ein neuer Parameter wird hinzugefügt), müssen Sie den Code überall dort ändern, wo Instanzen erzeugt werden. Ufff. -Houston, was schlägst du vor? +Houston, was schlagen Sie vor? Regel Nr. 3: Überlassen Sie die Abwicklung der Fabrik .[#toc-rule-3-let-the-factory-handle-it] ---------------------------------------------------------------------------------------------- -Wenn wir die versteckten Bindungen entfernen und alle Abhängigkeiten als Argumente übergeben, erhalten wir flexiblere und besser konfigurierbare Klassen. Und deshalb brauchen wir etwas anderes, um diese flexibleren Klassen zu erstellen und zu konfigurieren. Nennen wir es Fabriken. +Durch die Beseitigung versteckter Abhängigkeiten und die Übergabe aller Abhängigkeiten als Argumente haben wir mehr konfigurierbare und flexible Klassen erhalten. Und deshalb brauchen wir etwas anderes, um diese flexibleren Klassen für uns zu erstellen und zu konfigurieren. Wir werden es Fabriken nennen. Die Faustregel lautet: Wenn eine Klasse Abhängigkeiten hat, überlassen Sie die Erstellung ihrer Instanzen der Fabrik. @@ -460,7 +460,7 @@ Nicht zu verwechseln mit dem Entwurfsmuster *Fabrikmethode*, das eine spezielle Fabrik .[#toc-factory] ---------------------- -Eine Fabrik ist eine Methode oder Klasse, die Objekte erzeugt und konfiguriert. Wir nennen die produzierende Klasse `Article` `ArticleFactory` und sie könnte wie folgt aussehen: +Eine Fabrik ist eine Methode oder Klasse, die Objekte erstellt und konfiguriert. Wir werden die Klasse, die `Article` erzeugt, `ArticleFactory` nennen, und sie könnte wie folgt aussehen: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Ihre Verwendung im Controller würde folgendermaßen aussehen: +Die Verwendung im Controller sieht folgendermaßen aus: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -Wenn sich die Signatur des Konstruktors der Klasse `Article` ändert, ist der einzige Teil des Codes, der darauf reagieren muss, die `ArticleFactory` factory selbst. Jeder andere Code, der mit `Article` Objekten arbeitet, wie z. B. `EditController`, ist davon nicht betroffen. +Wenn sich nun die Signatur des Konstruktors der Klasse `Article` ändert, ist der einzige Teil des Codes, der darauf reagieren muss, der `ArticleFactory` selbst. Alle anderen Codes, die mit `Article` Objekten arbeiten, wie z.B. `EditController`, sind davon nicht betroffen. -Vielleicht tippen Sie sich jetzt an die Stirn und fragen sich, ob wir uns überhaupt geholfen haben. Die Menge des Codes ist gewachsen und das Ganze sieht langsam verdächtig kompliziert aus. +Sie werden sich vielleicht fragen, ob wir die Dinge tatsächlich besser gemacht haben. Die Menge des Codes hat zugenommen, und das Ganze sieht verdächtig kompliziert aus. -Keine Sorge, wir werden bald zum Nette-DI-Container kommen. Und der hat eine Reihe von Trümpfen im Ärmel, die das Erstellen von Anwendungen mit Dependency Injection extrem vereinfachen. Zum Beispiel genügt es, statt der Klasse `ArticleFactory` [eine einfache Schnittstelle zu schreiben |factory]: +Keine Sorge, bald werden wir zum Nette-DI-Container kommen. Und der hat einige Tricks in petto, die das Erstellen von Anwendungen mit Dependency Injection erheblich vereinfachen werden. Zum Beispiel müssen Sie anstelle der Klasse `ArticleFactory` nur [eine einfache Schnittstelle schreiben |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Aber wir kommen der Sache zuvor, warten Sie ab :-) +Aber wir greifen uns selbst vor; bitte haben Sie noch etwas Geduld :-) Zusammenfassung .[#toc-summary] ------------------------------- -Zu Beginn dieses Kapitels haben wir versprochen, Ihnen einen Weg zu zeigen, wie Sie sauberen Code entwerfen können. Geben Sie einfach den Klassen +Zu Beginn dieses Kapitels haben wir versprochen, Ihnen einen Prozess zur Entwicklung von sauberem Code zu zeigen. Alles, was es braucht, ist, dass die Klassen: -- [die Abhängigkeiten, die sie brauchen |#Rule #1: Let It Be Passed to You] -- [und nicht das, was sie nicht direkt brauchen |#Rule #2: Take What Is Yours] -- [und dass Objekte mit Abhängigkeiten am besten in Fabriken erstellt |#Rule #3: Let the Factory Handle it]werden +- [die Abhängigkeiten zu übergeben, die sie benötigen |#Rule #1: Let It Be Passed to You] +- [umgekehrt nicht übergeben, was sie nicht direkt brauchen |#Rule #2: Take What's Yours] +- [und dass Objekte mit Abhängigkeiten am besten in Fabriken erstellt werden |#Rule #3: Let the Factory Handle it] -Es mag auf den ersten Blick nicht so aussehen, aber diese drei Regeln haben weitreichende Auswirkungen. Sie führen zu einer radikal anderen Sichtweise des Codeentwurfs. Ist es das wert? Programmierer, die alte Gewohnheiten über Bord geworfen haben und mit der konsequenten Anwendung von Dependency Injection begonnen haben, betrachten dies als einen Schlüsselmoment in ihrem Berufsleben. Es eröffnete ihnen eine Welt klarer und nachhaltiger Anwendungen. +Auf den ersten Blick scheinen diese drei Regeln keine weitreichenden Konsequenzen zu haben, aber sie führen zu einer radikal anderen Sichtweise des Codeentwurfs. Ist es das wert? Entwickler, die alte Gewohnheiten aufgegeben und mit der konsequenten Nutzung von Dependency Injection begonnen haben, betrachten diesen Schritt als einen entscheidenden Moment in ihrem Berufsleben. Er hat ihnen die Welt der klaren und wartbaren Anwendungen eröffnet. -Aber was ist, wenn der Code nicht konsequent mit Dependency Injection arbeitet? Was ist, wenn er auf statischen Methoden oder Singletons aufbaut? Bringt das irgendwelche Probleme mit sich? [Das tut es, und es ist sehr bedeutsam |global-state]. +Was aber, wenn der Code nicht konsequent Dependency Injection verwendet? Was ist, wenn er sich auf statische Methoden oder Singletons stützt? Verursacht das Probleme? [Ja, das tut es, und zwar ganz grundlegende |global-state]. diff --git a/dependency-injection/de/passing-dependencies.texy b/dependency-injection/de/passing-dependencies.texy index f9174c7420..c5111a38be 100644 --- a/dependency-injection/de/passing-dependencies.texy +++ b/dependency-injection/de/passing-dependencies.texy @@ -12,7 +12,7 @@ Argumente, oder "Abhängigkeiten" in der DI-Terminologie, können auf die folgen </div> -Die ersten drei Methoden gelten allgemein in allen objektorientierten Sprachen, die vierte ist spezifisch für Nette-Präsentatoren und wird daher in einem [eigenen Kapitel |best-practices:inject-method-attribute] behandelt. Wir werden uns nun jede dieser Möglichkeiten genauer ansehen und anhand konkreter Beispiele erläutern. +Wir werden nun die verschiedenen Varianten mit konkreten Beispielen illustrieren. Konstruktor-Injektion .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Konstruktor-Injektion .[#toc-constructor-injection] Abhängigkeiten werden als Argumente an den Konstruktor übergeben, wenn das Objekt erstellt wird: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Diese Form ist nützlich für obligatorische Abhängigkeiten, die die Klasse unbedingt benötigt, um zu funktionieren, da ohne sie die Instanz nicht erstellt werden kann. @@ -40,10 +40,10 @@ Seit PHP 8.0 können wir eine kürzere Form der Notation verwenden ([constructor ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ Seit PHP 8.1 kann eine Eigenschaft mit einem Flag `readonly` markiert werden, da ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService Der DI-Container übergibt Abhängigkeiten automatisch an den Konstruktor mittels [Autowiring |autowiring]. Argumente, die nicht auf diese Weise übergeben werden können (z.B. Strings, Zahlen, Booleans), [werden in die Konfiguration geschrieben |services#Arguments]. +Konstrukteur-Hölle .[#toc-constructor-hell] +------------------------------------------- + +Der Begriff *Konstruktorhölle* bezieht sich auf eine Situation, in der ein Kind von einer Elternklasse erbt, deren Konstruktor Abhängigkeiten benötigt, und das Kind benötigt ebenfalls Abhängigkeiten. Es muss auch die Abhängigkeiten der Elternklasse übernehmen und weitergeben: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Das Problem tritt auf, wenn wir den Konstruktor der Klasse `BaseClass` ändern wollen, zum Beispiel wenn eine neue Abhängigkeit hinzugefügt wird. Dann müssen wir auch alle Konstruktoren der Kinder ändern. Das macht eine solche Änderung zur Hölle. + +Wie lässt sich das verhindern? Die Lösung besteht darin, **Komposition gegenüber Vererbung** zu bevorzugen. + +Lassen Sie uns also den Code anders gestalten. Wir werden abstrakte `Base*` Klassen vermeiden. Anstatt dass `MyClass` eine bestimmte Funktionalität durch Vererbung von `BaseClass` erhält, wird diese Funktionalität als Abhängigkeit übergeben: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Setter-Injektion .[#toc-setter-injection] ========================================= -Abhängigkeiten werden durch den Aufruf einer Methode übergeben, die sie in einer privaten Eigenschaft speichert. Die übliche Namenskonvention für diese Methoden ist die Form `set*()`, weshalb sie auch Setter genannt werden. +Abhängigkeiten werden durch den Aufruf einer Methode übergeben, die sie in einer privaten Eigenschaft speichert. Die übliche Namenskonvention für diese Methoden ist die Form `set*()`, weshalb sie auch Setter genannt werden, aber natürlich können sie auch anders heißen. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Diese Methode ist nützlich für optionale Abhängigkeiten, die für die Funktion der Klasse nicht notwendig sind, da nicht garantiert ist, dass das Objekt sie tatsächlich erhält (d. h. dass der Benutzer die Methode aufruft). @@ -90,16 +150,16 @@ Diese Methode ist nützlich für optionale Abhängigkeiten, die für die Funktio Gleichzeitig ermöglicht diese Methode, dass der Setter wiederholt aufgerufen werden kann, um die Abhängigkeit zu ändern. Wenn dies nicht erwünscht ist, fügen Sie der Methode ein Häkchen hinzu, oder markieren Sie ab PHP 8.1 die Eigenschaft `$cache` mit dem Flag `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ Der Setter-Aufruf wird in der DI-Container-Konfiguration im [Abschnitt setup |se ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Property Injection .[#toc-property-injection] Abhängigkeiten werden direkt an die Eigenschaft übergeben: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Diese Methode wird als ungeeignet angesehen, da die Eigenschaft als `public` deklariert werden muss. Daher haben wir keine Kontrolle darüber, ob die übergebene Abhängigkeit tatsächlich vom angegebenen Typ ist (dies war vor PHP 7.4 der Fall), und wir verlieren die Möglichkeit, auf die neu zugewiesene Abhängigkeit mit unserem eigenen Code zu reagieren, um zum Beispiel nachträgliche Änderungen zu verhindern. Gleichzeitig wird die Eigenschaft Teil der öffentlichen Schnittstelle der Klasse, was möglicherweise nicht wünschenswert ist. @@ -137,12 +197,18 @@ Die Einstellung der Variablen wird in der Konfiguration des DI-Containers im [Ab ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Einspritzen .[#toc-inject] +========================== + +Während die drei vorangegangenen Methoden allgemein in allen objektorientierten Sprachen gültig sind, ist das Injizieren per Methode, Annotation oder *inject*-Attribut spezifisch für Nette-Präsentatoren. Sie werden in einem [separaten Kapitel |best-practices:inject-method-attribute] behandelt. + + Welcher Weg soll gewählt werden? .[#toc-which-way-to-choose] ============================================================ diff --git a/dependency-injection/de/services.texy b/dependency-injection/de/services.texy index 85b1927fee..f4659b138d 100644 --- a/dependency-injection/de/services.texy +++ b/dependency-injection/de/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Injektionsmodus .[#toc-inject-mode] =================================== -Das Flag `inject: true` wird verwendet, um die Übergabe von Abhängigkeiten über öffentliche Variablen mit der [inject-Annotation |best-practices:inject-method-attribute#Inject Annotations] und den [inject*() |best-practices:inject-method-attribute#inject Methods] -Methoden zu aktivieren. +Das Flag `inject: true` wird verwendet, um die Übergabe von Abhängigkeiten über öffentliche Variablen mit der [inject-Annotation |best-practices:inject-method-attribute#Inject Attributes] und den [inject*() |best-practices:inject-method-attribute#inject Methods] -Methoden zu aktivieren. ```neon services: diff --git a/dependency-injection/el/@home.texy b/dependency-injection/el/@home.texy index aa5da71fe8..e93142a244 100644 --- a/dependency-injection/el/@home.texy +++ b/dependency-injection/el/@home.texy @@ -5,8 +5,10 @@ Το Dependency Injection είναι ένα πρότυπο σχεδίασης που θα αλλάξει ριζικά τον τρόπο με τον οποίο βλέπετε τον κώδικα και την ανάπτυξη. Ανοίγει το δρόμο για έναν κόσμο καθαρά σχεδιασμένων και βιώσιμων εφαρμογών. - [Τι είναι το Dependency Injection; |introduction] -- [Τι είναι το DI Container; |container] +- [Παγκόσμια κατάσταση & Singletons |global-state] - [Πέρασμα εξαρτήσεων |passing-dependencies] +- [Τι είναι το DI Container; |container] +- [Συχνές ερωτήσεις |faq] Nette DI diff --git a/dependency-injection/el/@left-menu.texy b/dependency-injection/el/@left-menu.texy index 86bd4cabbd..25ec08ae37 100644 --- a/dependency-injection/el/@left-menu.texy +++ b/dependency-injection/el/@left-menu.texy @@ -1,8 +1,10 @@ Εγχώνευση εξάρτησης ******************* - [Τι είναι η DI; |introduction] -- [Τι είναι το DI Container; |container] +- [Παγκόσμια κατάσταση & Singletons |global-state] - [Πέρασμα εξαρτήσεων |passing-dependencies] +- [Τι είναι το DI Container; |container] +- [Συχνές ερωτήσεις |faq] Nette DI diff --git a/dependency-injection/el/faq.texy b/dependency-injection/el/faq.texy new file mode 100644 index 0000000000..0ee71b6c73 --- /dev/null +++ b/dependency-injection/el/faq.texy @@ -0,0 +1,112 @@ +Συχνές ερωτήσεις σχετικά με το DI (FAQ) +*************************************** + + +Είναι το DI ένα άλλο όνομα για το IoC; .[#toc-is-di-another-name-for-ioc] +------------------------------------------------------------------------- + +Η *Inversion of Control* (IoC) είναι μια αρχή που επικεντρώνεται στον τρόπο εκτέλεσης του κώδικα - αν ο κώδικάς σας εκκινεί εξωτερικό κώδικα ή αν ο κώδικάς σας ενσωματώνεται σε εξωτερικό κώδικα, ο οποίος στη συνέχεια τον καλεί. +Η IoC είναι μια ευρεία έννοια που περιλαμβάνει [γεγονότα |nette:glossary#Events], τη λεγόμενη [αρχή του Χόλιγουντ |application:components#Hollywood style] και άλλες πτυχές. +Τα εργοστάσια, τα οποία αποτελούν μέρος του [κανόνα #3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], και αντιπροσωπεύουν την αντιστροφή για τον τελεστή `new`, είναι επίσης συστατικά αυτής της έννοιας. + +Το *Dependency Injection* (DI) αφορά το πώς ένα αντικείμενο γνωρίζει για ένα άλλο αντικείμενο, δηλαδή την εξάρτηση. Πρόκειται για ένα πρότυπο σχεδίασης που απαιτεί ρητή μεταβίβαση εξαρτήσεων μεταξύ αντικειμένων. + +Έτσι, το DI μπορεί να ειπωθεί ότι είναι μια ειδική μορφή του IoC. Ωστόσο, δεν είναι όλες οι μορφές IoC κατάλληλες όσον αφορά την καθαρότητα του κώδικα. Για παράδειγμα, στα αντι-πρότυπα περιλαμβάνουμε όλες τις τεχνικές που λειτουργούν με την [παγκόσμια κατάσταση |global state] ή το λεγόμενο [Service Locator |#What is a Service Locator]. + + +Τι είναι ο εντοπιστής υπηρεσιών; .[#toc-what-is-a-service-locator] +------------------------------------------------------------------ + +Ο εντοπιστής υπηρεσιών είναι μια εναλλακτική λύση για την ένθεση εξαρτήσεων (Dependency Injection). Λειτουργεί με τη δημιουργία ενός κεντρικού αποθηκευτικού χώρου όπου καταχωρούνται όλες οι διαθέσιμες υπηρεσίες ή εξαρτήσεις. Όταν ένα αντικείμενο χρειάζεται μια εξάρτηση, τη ζητάει από το Service Locator. + +Ωστόσο, σε σύγκριση με το Dependency Injection, χάνει σε διαφάνεια: οι εξαρτήσεις δεν μεταβιβάζονται απευθείας σε αντικείμενα και επομένως δεν είναι εύκολα αναγνωρίσιμες, γεγονός που απαιτεί την εξέταση του κώδικα για να αποκαλυφθούν και να κατανοηθούν όλες οι συνδέσεις. Η δοκιμή είναι επίσης πιο περίπλοκη, καθώς δεν μπορούμε απλά να περάσουμε αντικείμενα προσομοίωσης στα δοκιμαζόμενα αντικείμενα, αλλά πρέπει να περάσουμε μέσω του Service Locator. Επιπλέον, ο Service Locator διαταράσσει τη σχεδίαση του κώδικα, καθώς τα μεμονωμένα αντικείμενα πρέπει να γνωρίζουν την ύπαρξή του, κάτι που διαφέρει από το Dependency Injection, όπου τα αντικείμενα δεν έχουν καμία γνώση του DI container. + + +Πότε είναι καλύτερο να μην χρησιμοποιείται το DI; .[#toc-when-is-it-better-not-to-use-di] +----------------------------------------------------------------------------------------- + +Δεν υπάρχουν γνωστές δυσκολίες που σχετίζονται με τη χρήση του προτύπου σχεδίασης Dependency Injection. Αντιθέτως, η απόκτηση εξαρτήσεων από παγκοσμίως προσβάσιμες θέσεις οδηγεί σε [αρκετές επιπλοκές |global-state], όπως και η χρήση ενός Service Locator. +Επομένως, είναι σκόπιμο να χρησιμοποιείτε πάντα το DI. Αυτή δεν είναι μια δογματική προσέγγιση, αλλά απλά δεν έχει βρεθεί καλύτερη εναλλακτική λύση. + +Ωστόσο, υπάρχουν ορισμένες καταστάσεις όπου δεν περνάμε αντικείμενα μεταξύ μας και τα λαμβάνουμε από τον παγκόσμιο χώρο. Για παράδειγμα, όταν κάνουμε αποσφαλμάτωση κώδικα και χρειάζεται να απορρίψουμε την τιμή μιας μεταβλητής σε ένα συγκεκριμένο σημείο του προγράμματος, να μετρήσουμε τη διάρκεια ενός συγκεκριμένου τμήματος του προγράμματος ή να καταγράψουμε ένα μήνυμα. +Σε τέτοιες περιπτώσεις, όπου πρόκειται για προσωρινές ενέργειες που θα αφαιρεθούν αργότερα από τον κώδικα, είναι θεμιτό να χρησιμοποιείται ένας dumper, ένα χρονόμετρο ή ένας καταγραφέας με παγκόσμια πρόσβαση. Αυτά τα εργαλεία, άλλωστε, δεν ανήκουν στον σχεδιασμό του κώδικα. + + +Η χρήση DI έχει τα μειονεκτήματά της; .[#toc-does-using-di-have-its-drawbacks] +------------------------------------------------------------------------------ + +Η χρήση του Dependency Injection συνεπάγεται μειονεκτήματα, όπως αυξημένη πολυπλοκότητα στη συγγραφή κώδικα ή χειρότερες επιδόσεις; Τι χάνουμε όταν αρχίζουμε να γράφουμε κώδικα σύμφωνα με το DI; + +Η DI δεν έχει καμία επίπτωση στην απόδοση της εφαρμογής ή στις απαιτήσεις μνήμης. Η απόδοση του DI Container μπορεί να παίζει κάποιο ρόλο, αλλά στην περίπτωση του [Nette DI | nette-container], το container μεταγλωττίζεται σε καθαρή PHP, οπότε η επιβάρυνσή του κατά την εκτέλεση της εφαρμογής είναι ουσιαστικά μηδενική. + +Κατά τη συγγραφή κώδικα, είναι απαραίτητο να δημιουργηθούν κατασκευαστές που δέχονται εξαρτήσεις. Στο παρελθόν, αυτό μπορούσε να είναι χρονοβόρο, αλλά χάρη στα σύγχρονα IDE και την [προώθηση ιδιοτήτων των κατασκευαστών |https://blog.nette.org/el/php-8-0-pleres-episkopese-ton-neon#toc-constructor-property-promotion], είναι πλέον θέμα λίγων δευτερολέπτων. Οι κατασκευαστές μπορούν εύκολα να δημιουργηθούν χρησιμοποιώντας το Nette DI και ένα πρόσθετο PhpStorm με λίγα μόνο κλικ. +Από την άλλη πλευρά, δεν χρειάζεται να γράψετε singletons και στατικά σημεία πρόσβασης. + +Μπορούμε να συμπεράνουμε ότι μια σωστά σχεδιασμένη εφαρμογή που χρησιμοποιεί DI δεν είναι ούτε μικρότερη ούτε μεγαλύτερη σε σύγκριση με μια εφαρμογή που χρησιμοποιεί singletons. Τμήματα του κώδικα που εργάζονται με εξαρτήσεις απλώς εξάγονται από μεμονωμένες κλάσεις και μεταφέρονται σε νέες θέσεις, δηλαδή στον περιέκτη DI και στα εργοστάσια. + + +Πώς να ξαναγράψετε μια παλαιά εφαρμογή σε DI; .[#toc-how-to-rewrite-a-legacy-application-to-di] +----------------------------------------------------------------------------------------------- + +Η μετάβαση από μια παλαιά εφαρμογή σε Dependency Injection μπορεί να είναι μια δύσκολη διαδικασία, ειδικά για μεγάλες και πολύπλοκες εφαρμογές. Είναι σημαντικό να προσεγγίσετε αυτή τη διαδικασία συστηματικά. + +- Κατά τη μετάβαση σε Dependency Injection, είναι σημαντικό όλα τα μέλη της ομάδας να κατανοήσουν τις αρχές και τις πρακτικές που χρησιμοποιούνται. +- Αρχικά, πραγματοποιήστε μια ανάλυση της υπάρχουσας εφαρμογής για να εντοπίσετε τα βασικά στοιχεία και τις εξαρτήσεις τους. Δημιουργήστε ένα σχέδιο για το ποια μέρη θα αναδιαμορφωθούν και με ποια σειρά. +- Υλοποιήστε έναν περιέκτη DI ή, ακόμα καλύτερα, χρησιμοποιήστε μια υπάρχουσα βιβλιοθήκη όπως η Nette DI. +- Αναδιαμορφώστε σταδιακά κάθε τμήμα της εφαρμογής ώστε να χρησιμοποιεί Dependency Injection. Αυτό μπορεί να περιλαμβάνει την τροποποίηση κατασκευαστών ή μεθόδων ώστε να δέχονται εξαρτήσεις ως παραμέτρους. +- Τροποποιήστε τα σημεία του κώδικα όπου δημιουργούνται αντικείμενα εξάρτησης έτσι ώστε οι εξαρτήσεις να εγχέονται από τον περιέκτη. Αυτό μπορεί να περιλαμβάνει τη χρήση εργοστασίων. + +Να θυμάστε ότι η μετάβαση σε Dependency Injection είναι μια επένδυση στην ποιότητα του κώδικα και στη μακροπρόθεσμη βιωσιμότητα της εφαρμογής. Αν και μπορεί να είναι πρόκληση να γίνουν αυτές οι αλλαγές, το αποτέλεσμα θα πρέπει να είναι καθαρότερος, πιο αρθρωτός και εύκολα ελέγξιμος κώδικας που είναι έτοιμος για μελλοντικές επεκτάσεις και συντήρηση. + + +Γιατί η σύνθεση προτιμάται από την κληρονομικότητα; .[#toc-why-composition-is-preferred-over-inheritance] +--------------------------------------------------------------------------------------------------------- +Είναι προτιμότερο να χρησιμοποιείται η σύνθεση παρά η κληρονομικότητα, καθώς εξυπηρετεί τον σκοπό της δυνατότητας επαναχρησιμοποίησης του κώδικα χωρίς να χρειάζεται να ανησυχείτε για την επίδραση της αλλαγής. Έτσι, παρέχει πιο χαλαρή σύζευξη όπου δεν χρειάζεται να ανησυχούμε για το αν η αλλαγή κάποιου κώδικα προκαλεί την αλλαγή κάποιου άλλου εξαρτημένου κώδικα που απαιτεί αλλαγή. Ένα τυπικό παράδειγμα είναι η κατάσταση που προσδιορίζεται ως [κόλαση των κατασκευαστών |passing-dependencies#Constructor hell]. + + +Μπορεί το Nette DI Container να χρησιμοποιηθεί εκτός της Nette; .[#toc-can-nette-di-container-be-used-outside-of-nette] +----------------------------------------------------------------------------------------------------------------------- + +Απολύτως. Το Nette DI Container είναι μέρος του Nette, αλλά έχει σχεδιαστεί ως αυτόνομη βιβλιοθήκη που μπορεί να χρησιμοποιηθεί ανεξάρτητα από άλλα μέρη του πλαισίου. Απλά εγκαταστήστε το χρησιμοποιώντας το Composer, δημιουργήστε ένα αρχείο ρυθμίσεων που ορίζει τις υπηρεσίες σας και στη συνέχεια χρησιμοποιήστε μερικές γραμμές κώδικα PHP για να δημιουργήσετε το DI container. +Και μπορείτε αμέσως να αρχίσετε να εκμεταλλεύεστε το Dependency Injection στα έργα σας. + +Το κεφάλαιο [Nette DI Container |nette-container] περιγράφει πώς μοιάζει μια συγκεκριμένη περίπτωση χρήσης, συμπεριλαμβανομένου του κώδικα. + + +Γιατί η διαμόρφωση βρίσκεται σε αρχεία NEON; .[#toc-why-is-the-configuration-in-neon-files] +------------------------------------------------------------------------------------------- + +Το NEON είναι μια απλή και ευανάγνωστη γλώσσα ρυθμίσεων που αναπτύχθηκε στο πλαίσιο της Nette για τη ρύθμιση εφαρμογών, υπηρεσιών και των εξαρτήσεών τους. Σε σύγκριση με το JSON ή το YAML, προσφέρει πολύ πιο διαισθητικές και ευέλικτες επιλογές για το σκοπό αυτό. Στη NEON, μπορείτε φυσικά να περιγράψετε δεσμεύσεις που δεν θα ήταν δυνατόν να γραφτούν σε Symfony & YAML είτε καθόλου είτε μόνο μέσω μιας πολύπλοκης περιγραφής. + + +Επιβραδύνει η ανάλυση των αρχείων NEON την εφαρμογή; .[#toc-does-parsing-neon-files-slow-down-the-application] +-------------------------------------------------------------------------------------------------------------- + +Παρόλο που τα αρχεία NEON αναλύονται πολύ γρήγορα, αυτή η πτυχή δεν έχει πραγματικά σημασία. Ο λόγος είναι ότι η ανάλυση των αρχείων γίνεται μόνο μία φορά κατά την πρώτη εκκίνηση της εφαρμογής. Μετά από αυτό, ο κώδικας δοχείου DI δημιουργείται, αποθηκεύεται στο δίσκο και εκτελείται για κάθε επόμενη αίτηση χωρίς να χρειάζεται περαιτέρω ανάλυση. + +Έτσι λειτουργεί σε ένα περιβάλλον παραγωγής. Κατά τη διάρκεια της ανάπτυξης, τα αρχεία NEON αναλύονται κάθε φορά που αλλάζει το περιεχόμενό τους, διασφαλίζοντας ότι ο προγραμματιστής έχει πάντα ένα ενημερωμένο DI container. Όπως αναφέρθηκε προηγουμένως, η πραγματική ανάλυση είναι θέμα μιας στιγμής. + + +Πώς μπορώ να προσπελάσω τις παραμέτρους από το αρχείο διαμόρφωσης στην κλάση μου; .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +---------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Λάβετε υπόψη σας τον [Κανόνας #1: Αφήστε να περάσει σε σας |introduction#Rule #1: Let It Be Passed to You]. Εάν μια κλάση απαιτεί πληροφορίες από ένα αρχείο ρυθμίσεων, δεν χρειάζεται να βρούμε πώς να αποκτήσουμε πρόσβαση σε αυτές τις πληροφορίες- αντίθετα, απλά τις ζητάμε - για παράδειγμα, μέσω του κατασκευαστή της κλάσης. Και εκτελούμε την πάσα στο αρχείο διαμόρφωσης. + +Σε αυτό το παράδειγμα, το `%myParameter%` είναι ένας χώρος τοποθέτησης για την τιμή της παραμέτρου `myParameter`, η οποία θα μεταβιβαστεί στον κατασκευαστή `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Εάν θέλετε να περάσετε πολλαπλές παραμέτρους ή να χρησιμοποιήσετε αυτόματη σύνδεση, είναι χρήσιμο να [τυλίξετε τις παραμέτρους σε ένα αντικείμενο |best-practices:passing-settings-to-presenters]. + + +Υποστηρίζει η Nette τη διεπαφή PSR-11 Container; .[#toc-does-nette-support-psr-11-container-interface] +------------------------------------------------------------------------------------------------------ + +Το Nette DI Container δεν υποστηρίζει άμεσα το PSR-11. Ωστόσο, αν χρειάζεστε διαλειτουργικότητα μεταξύ του Nette DI Container και βιβλιοθηκών ή πλαισίων που αναμένουν τη διεπαφή PSR-11 Container, μπορείτε να δημιουργήσετε έναν [απλό προσαρμογέα |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] που θα χρησιμεύσει ως γέφυρα μεταξύ του Nette DI Container και του PSR-11. diff --git a/dependency-injection/el/global-state.texy b/dependency-injection/el/global-state.texy new file mode 100644 index 0000000000..5c6cbcecdf --- /dev/null +++ b/dependency-injection/el/global-state.texy @@ -0,0 +1,312 @@ +Παγκόσμια κατάσταση και singletons +********************************** + +.[perex] +Προειδοποίηση: οι ακόλουθες δομές είναι συμπτώματα κακού σχεδιασμού κώδικα: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` ή `static::$var` + +Εμφανίζεται κάποια από αυτές τις δομές στον κώδικά σας; Τότε έχετε την ευκαιρία να βελτιωθείτε. Μπορεί να σκέφτεστε ότι πρόκειται για κοινές κατασκευές που βλέπουμε σε δείγματα λύσεων διαφόρων βιβλιοθηκών και πλαισίων. +Δυστυχώς, εξακολουθούν να αποτελούν σαφή ένδειξη κακού σχεδιασμού. Έχουν ένα κοινό χαρακτηριστικό: τη χρήση της παγκόσμιας κατάστασης. + +Τώρα, σίγουρα δεν μιλάμε για κάποιο είδος ακαδημαϊκής καθαρότητας. Η χρήση global state και singletons έχει καταστροφικά αποτελέσματα στην ποιότητα του κώδικα. Η συμπεριφορά της γίνεται απρόβλεπτη, μειώνει την παραγωγικότητα των προγραμματιστών και αναγκάζει τις διεπαφές κλάσεων να λένε ψέματα για τις πραγματικές εξαρτήσεις τους. Το οποίο προκαλεί σύγχυση στους προγραμματιστές. + +Σε αυτό το κεφάλαιο, θα δείξουμε πώς αυτό είναι εφικτό. + + +Παγκόσμια διασύνδεση .[#toc-global-interlinking] +------------------------------------------------ + +Το θεμελιώδες πρόβλημα με το παγκόσμιο κράτος είναι ότι είναι παγκοσμίως προσβάσιμο. Αυτό καθιστά δυνατή την εγγραφή στη βάση δεδομένων μέσω της παγκόσμιας (στατικής) μεθόδου `DB::insert()`. +Σε έναν ιδανικό κόσμο, ένα αντικείμενο θα πρέπει να μπορεί να επικοινωνεί μόνο με άλλα αντικείμενα που έχουν [περάσει απευθείας σε |passing-dependencies] αυτό. +Αν δημιουργήσω δύο αντικείμενα `A` και `B` και δεν περάσω ποτέ μια αναφορά από το `A` στο `B`, τότε ούτε το `A`, ούτε το `B` μπορούν να έχουν πρόσβαση στο άλλο αντικείμενο ή να αλλάξουν την κατάστασή του. +Αυτό είναι ένα πολύ επιθυμητό χαρακτηριστικό του κώδικα. Είναι παρόμοιο με το να έχετε μια μπαταρία και μια λάμπα- η λάμπα δεν θα ανάψει μέχρι να τα συνδέσετε μεταξύ τους. + +Αυτό δεν ισχύει για παγκόσμιες (στατικές) μεταβλητές ή singletons. Το αντικείμενο `A` θα μπορούσε να προσπελάσει *ασύρματα* το αντικείμενο `C` και να το τροποποιήσει χωρίς να περάσει καμία αναφορά, καλώντας το `C::changeSomething()`. +Εάν το αντικείμενο `B` αρπάζει επίσης το παγκόσμιο `C`, τότε τα `A` και `B` μπορούν να αλληλεπιδράσουν μεταξύ τους μέσω του `C`. + +Η χρήση των παγκόσμιων μεταβλητών εισάγει μια νέα μορφή *ασύρματης* σύζευξης στο σύστημα που δεν είναι ορατή από έξω. +Δημιουργεί ένα προπέτασμα καπνού που περιπλέκει την κατανόηση και τη χρήση του κώδικα. +Οι προγραμματιστές πρέπει να διαβάσουν κάθε γραμμή του πηγαίου κώδικα για να κατανοήσουν πραγματικά τις εξαρτήσεις. Αντί να εξοικειώνονται απλώς με τη διεπαφή των κλάσεων. +Επιπλέον, πρόκειται για μια εντελώς περιττή σύζευξη. + +.[note] +Όσον αφορά τη συμπεριφορά, δεν υπάρχει καμία διαφορά μεταξύ μιας παγκόσμιας και μιας στατικής μεταβλητής. Είναι εξίσου επιβλαβείς. + + +Η τρομακτική δράση από απόσταση .[#toc-the-spooky-action-at-a-distance] +----------------------------------------------------------------------- + +"Φαινομενική δράση από απόσταση" - έτσι ονόμασε ο Άλμπερτ Αϊνστάιν ένα φαινόμενο της κβαντικής φυσικής που τον ανατρίχιασε το 1935. +Πρόκειται για την κβαντική διεμπλοκή, η ιδιαιτερότητα της οποίας είναι ότι όταν μετράτε πληροφορίες για ένα σωματίδιο, επηρεάζετε αμέσως ένα άλλο σωματίδιο, ακόμη και αν αυτά απέχουν εκατομμύρια έτη φωτός. +γεγονός που φαινομενικά παραβιάζει τον θεμελιώδη νόμο του σύμπαντος ότι τίποτα δεν μπορεί να ταξιδέψει γρηγορότερα από το φως. + +Στον κόσμο του λογισμικού, μπορούμε να ονομάσουμε "spooky action at a distance" μια κατάσταση κατά την οποία εκτελούμε μια διαδικασία που νομίζουμε ότι είναι απομονωμένη (επειδή δεν της έχουμε περάσει καμία αναφορά), αλλά απροσδόκητες αλληλεπιδράσεις και αλλαγές κατάστασης συμβαίνουν σε απομακρυσμένες θέσεις του συστήματος για τις οποίες δεν ενημερώσαμε το αντικείμενο. Αυτό μπορεί να συμβεί μόνο μέσω της παγκόσμιας κατάστασης. + +Φανταστείτε να ενταχθείτε σε μια ομάδα ανάπτυξης έργου που έχει μια μεγάλη, ώριμη βάση κώδικα. Ο νέος σας επικεφαλής σας ζητά να υλοποιήσετε ένα νέο χαρακτηριστικό και, σαν καλός προγραμματιστής, ξεκινάτε γράφοντας μια δοκιμή. Αλλά επειδή είστε νέος στο έργο, κάνετε πολλές διερευνητικές δοκιμές τύπου "τι συμβαίνει αν καλέσω αυτή τη μέθοδο". Και προσπαθείτε να γράψετε την ακόλουθη δοκιμή: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // τον αριθμό της κάρτας σας + $cc->charge(100); +} +``` + +Εκτελείτε τον κώδικα, ίσως αρκετές φορές, και μετά από λίγο παρατηρείτε ειδοποιήσεις στο τηλέφωνό σας από την τράπεζα ότι κάθε φορά που τον εκτελείτε, χρεώνονται 100 δολάρια στην πιστωτική σας κάρτα 🤦‍♂️ + +Πώς στο καλό θα μπορούσε το τεστ να προκαλέσει πραγματική χρέωση; Δεν είναι εύκολο να λειτουργήσει με πιστωτική κάρτα. Πρέπει να αλληλεπιδράσετε με μια διαδικτυακή υπηρεσία τρίτου μέρους, πρέπει να γνωρίζετε τη διεύθυνση URL αυτής της διαδικτυακής υπηρεσίας, πρέπει να συνδεθείτε και ούτω καθεξής. +Καμία από αυτές τις πληροφορίες δεν περιλαμβάνεται στη δοκιμή. Ακόμα χειρότερα, δεν γνωρίζετε καν πού υπάρχουν αυτές οι πληροφορίες και, επομένως, πώς να παριστάνετε τις εξωτερικές εξαρτήσεις, ώστε κάθε εκτέλεση να μην οδηγεί σε νέα χρέωση 100 δολαρίων. Και ως νέος προγραμματιστής, πώς υποτίθεται ότι θα γνωρίζατε ότι αυτό που θα κάνατε θα σας οδηγούσε στο να γίνετε κατά 100 δολάρια φτωχότεροι; + +Αυτή είναι μια τρομακτική δράση από απόσταση! + +Δεν έχετε άλλη επιλογή από το να ψάξετε πολύ πηγαίο κώδικα, ρωτώντας παλαιότερους και πιο έμπειρους συναδέλφους, μέχρι να καταλάβετε πώς λειτουργούν οι συνδέσεις στο έργο. +Αυτό οφείλεται στο γεγονός ότι, όταν εξετάζετε τη διεπαφή της κλάσης `CreditCard`, δεν μπορείτε να προσδιορίσετε την παγκόσμια κατάσταση που πρέπει να αρχικοποιηθεί. Ακόμα και αν κοιτάξετε τον πηγαίο κώδικα της κλάσης δεν θα σας πει ποια μέθοδος αρχικοποίησης πρέπει να καλέσετε. Στην καλύτερη περίπτωση, μπορείτε να βρείτε την παγκόσμια μεταβλητή στην οποία γίνεται πρόσβαση και να προσπαθήσετε να μαντέψετε πώς να την αρχικοποιήσετε από αυτήν. + +Οι κλάσεις σε ένα τέτοιο έργο είναι παθολογικοί ψεύτες. Η κάρτα πληρωμών προσποιείται ότι μπορείτε απλώς να την ενσαρκώσετε και να καλέσετε τη μέθοδο `charge()`. Ωστόσο, κρυφά αλληλεπιδρά με μια άλλη κλάση, την `PaymentGateway`. Ακόμη και η διεπαφή της λέει ότι μπορεί να αρχικοποιηθεί ανεξάρτητα, αλλά στην πραγματικότητα αντλεί διαπιστευτήρια από κάποιο αρχείο ρυθμίσεων κ.ο.κ. +Είναι σαφές στους προγραμματιστές που έγραψαν αυτόν τον κώδικα ότι το `CreditCard` χρειάζεται το `PaymentGateway`. Έγραψαν τον κώδικα με αυτόν τον τρόπο. Αλλά για οποιονδήποτε νέο στο έργο, αυτό είναι ένα πλήρες μυστήριο και εμποδίζει την εκμάθηση. + +Πώς να διορθώσετε την κατάσταση; Εύκολα. **Αφήστε το API να δηλώσει εξαρτήσεις.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Παρατηρήστε πώς οι σχέσεις μέσα στον κώδικα είναι ξαφνικά προφανείς. Δηλώνοντας ότι η μέθοδος `charge()` χρειάζεται τη διεύθυνση `PaymentGateway`, δεν χρειάζεται να ρωτήσετε κανέναν πώς ο κώδικας είναι αλληλοεξαρτώμενος. Ξέρετε ότι πρέπει να δημιουργήσετε μια παρουσία της, και όταν προσπαθείτε να το κάνετε αυτό, πέφτετε πάνω στο γεγονός ότι πρέπει να παρέχετε παραμέτρους πρόσβασης. Χωρίς αυτές, ο κώδικας δεν θα μπορούσε καν να εκτελεστεί. + +Και το πιο σημαντικό, μπορείτε τώρα να μιμηθείτε την πύλη πληρωμών, ώστε να μην χρεώνεστε 100 δολάρια κάθε φορά που εκτελείτε μια δοκιμή. + +Η παγκόσμια κατάσταση προκαλεί στα αντικείμενά σας τη δυνατότητα να έχουν κρυφή πρόσβαση σε πράγματα που δεν έχουν δηλωθεί στα API τους, και ως αποτέλεσμα καθιστά τα API σας παθολογικά ψεύτικα. + +Μπορεί να μην το είχατε σκεφτεί με αυτόν τον τρόπο πριν, αλλά κάθε φορά που χρησιμοποιείτε global state, δημιουργείτε μυστικά ασύρματα κανάλια επικοινωνίας. Η ανατριχιαστική απομακρυσμένη δράση αναγκάζει τους προγραμματιστές να διαβάσουν κάθε γραμμή κώδικα για να κατανοήσουν τις πιθανές αλληλεπιδράσεις, μειώνει την παραγωγικότητα των προγραμματιστών και μπερδεύει τα νέα μέλη της ομάδας. +Αν είστε αυτός που δημιούργησε τον κώδικα, γνωρίζετε τις πραγματικές εξαρτήσεις, αλλά όποιος έρχεται μετά από εσάς είναι άσχετος. + +Μη γράφετε κώδικα που χρησιμοποιεί παγκόσμια κατάσταση, προτιμήστε να μεταβιβάζετε εξαρτήσεις. Δηλαδή, την έγχυση εξαρτήσεων (dependency injection). + + +Η ευθραυστότητα του παγκόσμιου κράτους .[#toc-brittleness-of-the-global-state] +------------------------------------------------------------------------------ + +Σε κώδικα που χρησιμοποιεί παγκόσμια κατάσταση και singletons, δεν είναι ποτέ βέβαιο πότε και από ποιον έχει αλλάξει αυτή η κατάσταση. Αυτός ο κίνδυνος υπάρχει ήδη κατά την αρχικοποίηση. Ο παρακάτω κώδικας υποτίθεται ότι δημιουργεί μια σύνδεση με βάση δεδομένων και αρχικοποιεί την πύλη πληρωμών, αλλά συνεχίζει να πετάει μια εξαίρεση και η εύρεση της αιτίας είναι εξαιρετικά κουραστική: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Πρέπει να ψάξετε λεπτομερώς τον κώδικα για να διαπιστώσετε ότι το αντικείμενο `PaymentGateway` αποκτά ασύρματη πρόσβαση σε άλλα αντικείμενα, ορισμένα από τα οποία απαιτούν σύνδεση με βάση δεδομένων. Έτσι, πρέπει να αρχικοποιήσετε τη βάση δεδομένων πριν από το `PaymentGateway`. Ωστόσο, το προπέτασμα καπνού της παγκόσμιας κατάστασης το κρύβει αυτό από εσάς. Πόσο χρόνο θα γλιτώνατε αν το API κάθε κλάσης δεν έλεγε ψέματα και δεν δήλωνε τις εξαρτήσεις του; + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Ένα παρόμοιο πρόβλημα προκύπτει όταν χρησιμοποιείτε παγκόσμια πρόσβαση σε μια σύνδεση βάσης δεδομένων: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Κατά την κλήση της μεθόδου `save()`, δεν είναι βέβαιο αν έχει ήδη δημιουργηθεί μια σύνδεση βάσης δεδομένων και ποιος είναι υπεύθυνος για τη δημιουργία της. Για παράδειγμα, αν θέλαμε να αλλάξουμε τη σύνδεση της βάσης δεδομένων εν κινήσει, ίσως για λόγους δοκιμών, θα έπρεπε πιθανώς να δημιουργήσουμε πρόσθετες μεθόδους όπως οι `DB::reconnect(...)` ή `DB::reconnectForTest()`. + +Σκεφτείτε ένα παράδειγμα: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Πού μπορούμε να είμαστε σίγουροι ότι η βάση δεδομένων δοκιμής χρησιμοποιείται πραγματικά όταν καλούμε το `$article->save()`; Τι γίνεται αν η μέθοδος `Foo::doSomething()` αλλάξει την παγκόσμια σύνδεση της βάσης δεδομένων; Για να το μάθουμε, θα πρέπει να εξετάσουμε τον πηγαίο κώδικα της κλάσης `Foo` και πιθανώς πολλών άλλων κλάσεων. Ωστόσο, αυτή η προσέγγιση θα έδινε μόνο μια βραχυπρόθεσμη απάντηση, καθώς η κατάσταση μπορεί να αλλάξει στο μέλλον. + +Τι θα γινόταν αν μετακινούσαμε τη σύνδεση με τη βάση δεδομένων σε μια στατική μεταβλητή μέσα στην κλάση `Article`; + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Αυτό δεν αλλάζει τίποτα απολύτως. Το πρόβλημα είναι μια παγκόσμια κατάσταση και δεν έχει σημασία σε ποια κλάση κρύβεται. Σε αυτή την περίπτωση, όπως και στην προηγούμενη, δεν έχουμε καμία ένδειξη για το σε ποια βάση δεδομένων γράφεται όταν καλείται η μέθοδος `$article->save()`. Οποιοσδήποτε στο μακρινό άκρο της εφαρμογής θα μπορούσε να αλλάξει τη βάση δεδομένων ανά πάσα στιγμή χρησιμοποιώντας τη μέθοδο `Article::setDb()`. Κάτω από τα χέρια μας. + +Η παγκόσμια κατάσταση καθιστά την εφαρμογή μας **εξαιρετικά εύθραυστη**. + +Ωστόσο, υπάρχει ένας απλός τρόπος να αντιμετωπίσουμε αυτό το πρόβλημα. Απλά βάλτε το API να δηλώσει εξαρτήσεις για να διασφαλιστεί η σωστή λειτουργικότητα. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Αυτή η προσέγγιση εξαλείφει την ανησυχία για κρυφές και απροσδόκητες αλλαγές στις συνδέσεις βάσης δεδομένων. Τώρα είμαστε σίγουροι για το πού αποθηκεύεται το άρθρο και καμία τροποποίηση κώδικα μέσα σε μια άλλη άσχετη κλάση δεν μπορεί πλέον να αλλάξει την κατάσταση. Ο κώδικας δεν είναι πλέον εύθραυστος, αλλά σταθερός. + +Μη γράφετε κώδικα που χρησιμοποιεί παγκόσμια κατάσταση, προτιμήστε να περνάτε εξαρτήσεις. Έτσι, η έγχυση εξαρτήσεων (dependency injection). + + +Singleton .[#toc-singleton] +--------------------------- + +Το Singleton είναι ένα μοτίβο σχεδίασης που, σύμφωνα με τον [ορισμό |https://en.wikipedia.org/wiki/Singleton_pattern] από τη διάσημη δημοσίευση της Gang of Four, περιορίζει μια κλάση σε μια μοναδική περίπτωση και προσφέρει παγκόσμια πρόσβαση σε αυτήν. Η υλοποίηση αυτού του προτύπου μοιάζει συνήθως με τον ακόλουθο κώδικα: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // και άλλες μεθόδους που εκτελούν τις λειτουργίες της κλάσης +} +``` + +Δυστυχώς, το singleton εισάγει παγκόσμια κατάσταση στην εφαρμογή. Και όπως δείξαμε παραπάνω, η παγκόσμια κατάσταση είναι ανεπιθύμητη. Αυτός είναι ο λόγος για τον οποίο το singleton θεωρείται αντιπρότυπο. + +Μην χρησιμοποιείτε singletons στον κώδικά σας και αντικαταστήστε τα με άλλους μηχανισμούς. Πραγματικά δεν χρειάζεστε singletons. Ωστόσο, αν πρέπει να εγγυηθείτε την ύπαρξη μιας και μόνο περίπτωσης μιας κλάσης για ολόκληρη την εφαρμογή, αφήστε το στο [DI |container] container. +Έτσι, δημιουργήστε ένα singleton της εφαρμογής ή μια υπηρεσία. Αυτό θα σταματήσει την κλάση από το να παρέχει τη δική της μοναδικότητα (δηλαδή, δεν θα έχει μια μέθοδο `getInstance()` και μια στατική μεταβλητή) και θα εκτελεί μόνο τις λειτουργίες της. Έτσι, θα σταματήσει να παραβιάζει την αρχή της ενιαίας ευθύνης. + + +Παγκόσμια κατάσταση έναντι δοκιμών .[#toc-global-state-versus-tests] +-------------------------------------------------------------------- + +Όταν γράφουμε δοκιμές, υποθέτουμε ότι κάθε δοκιμή είναι μια απομονωμένη μονάδα και ότι δεν εισέρχεται σε αυτήν καμία εξωτερική κατάσταση. Και καμία κατάσταση δεν φεύγει από τις δοκιμές. Όταν μια δοκιμή ολοκληρώνεται, κάθε κατάσταση που σχετίζεται με τη δοκιμή θα πρέπει να αφαιρείται αυτόματα από τον garbage collector. Αυτό καθιστά τις δοκιμές απομονωμένες. Επομένως, μπορούμε να εκτελέσουμε τις δοκιμές με οποιαδήποτε σειρά. + +Ωστόσο, αν υπάρχουν καθολικές καταστάσεις/συνθήκες, όλες αυτές οι ωραίες υποθέσεις καταρρέουν. Μια κατάσταση μπορεί να εισέλθει και να εξέλθει από μια δοκιμή. Ξαφνικά, η σειρά των δοκιμών μπορεί να έχει σημασία. + +Για να δοκιμάσουν καθόλου singletons, οι προγραμματιστές πρέπει συχνά να χαλαρώσουν τις ιδιότητές τους, ίσως επιτρέποντας την αντικατάσταση μιας περίπτωσης από μια άλλη. Τέτοιες λύσεις είναι, στην καλύτερη περίπτωση, χάκερς που παράγουν κώδικα που είναι δύσκολο να συντηρηθεί και να κατανοηθεί. Κάθε δοκιμή ή μέθοδος `tearDown()` που επηρεάζει οποιαδήποτε παγκόσμια κατάσταση πρέπει να αναιρεί αυτές τις αλλαγές. + +Η παγκόσμια κατάσταση είναι ο μεγαλύτερος πονοκέφαλος στον έλεγχο μονάδων! + +Πώς να διορθώσετε την κατάσταση; Εύκολα. Μη γράφετε κώδικα που χρησιμοποιεί singletons, προτιμήστε να περνάτε εξαρτήσεις. Δηλαδή, με την έγχυση εξαρτήσεων (dependency injection). + + +Παγκόσμιες σταθερές .[#toc-global-constants] +-------------------------------------------- + +Η παγκόσμια κατάσταση δεν περιορίζεται στη χρήση των singletons και των στατικών μεταβλητών, αλλά μπορεί να εφαρμοστεί και στις παγκόσμιες σταθερές. + +Οι σταθερές των οποίων η τιμή δεν μας παρέχει καμία νέα (`M_PI`) ή χρήσιμη (`PREG_BACKTRACK_LIMIT_ERROR`) πληροφορία είναι σαφώς ΟΚ. +Αντίθετα, οι σταθερές που χρησιμεύουν ως ένας τρόπος για την *ασύρματη* μετάδοση πληροφοριών μέσα στον κώδικα δεν είναι τίποτα περισσότερο από μια κρυφή εξάρτηση. Όπως το `LOG_FILE` στο ακόλουθο παράδειγμα. +Η χρήση της σταθεράς `FILE_APPEND` είναι απολύτως σωστή. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Σε αυτή την περίπτωση, θα πρέπει να δηλώσουμε την παράμετρο στον κατασκευαστή της κλάσης `Foo` για να γίνει μέρος του API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Τώρα μπορούμε να περνάμε πληροφορίες σχετικά με τη διαδρομή του αρχείου καταγραφής και να την αλλάζουμε εύκολα ανάλογα με τις ανάγκες, διευκολύνοντας έτσι τον έλεγχο και τη συντήρηση του κώδικα. + + +Παγκόσμιες συναρτήσεις και στατικές μέθοδοι .[#toc-global-functions-and-static-methods] +--------------------------------------------------------------------------------------- + +Θέλουμε να τονίσουμε ότι η χρήση στατικών μεθόδων και παγκόσμιων συναρτήσεων δεν είναι από μόνη της προβληματική. Έχουμε εξηγήσει την ακαταλληλότητα της χρήσης του `DB::insert()` και παρόμοιων μεθόδων, αλλά πρόκειται πάντα για την παγκόσμια κατάσταση που αποθηκεύεται σε μια στατική μεταβλητή. Η μέθοδος `DB::insert()` απαιτεί την ύπαρξη μιας στατικής μεταβλητής επειδή αποθηκεύει τη σύνδεση με τη βάση δεδομένων. Χωρίς αυτή τη μεταβλητή, θα ήταν αδύνατη η υλοποίηση της μεθόδου. + +Η χρήση ντετερμινιστικών στατικών μεθόδων και συναρτήσεων, όπως οι `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` και πολλές άλλες, είναι απόλυτα συνεπής με την έγχυση εξάρτησης. Αυτές οι συναρτήσεις επιστρέφουν πάντα τα ίδια αποτελέσματα από τις ίδιες παραμέτρους εισόδου και επομένως είναι προβλέψιμες. Δεν χρησιμοποιούν καμία παγκόσμια κατάσταση. + +Ωστόσο, υπάρχουν συναρτήσεις στην PHP που δεν είναι ντετερμινιστικές. Σε αυτές περιλαμβάνεται, για παράδειγμα, η συνάρτηση `htmlspecialchars()`. Η τρίτη της παράμετρος, `$encoding`, αν δεν καθοριστεί, έχει ως προεπιλογή την τιμή της επιλογής διαμόρφωσης `ini_get('default_charset')`. Επομένως, συνιστάται να προσδιορίζετε πάντα αυτή την παράμετρο για να αποφύγετε πιθανή απρόβλεπτη συμπεριφορά της συνάρτησης. Η Nette το πράττει αυτό με συνέπεια. + +Ορισμένες συναρτήσεις, όπως η `strtolower()`, η `strtoupper()`, και οι παρόμοιες, είχαν μη ντετερμινιστική συμπεριφορά στο πρόσφατο παρελθόν και εξαρτιόνταν από τη ρύθμιση `setlocale()`. Αυτό προκάλεσε πολλές επιπλοκές, τις περισσότερες φορές όταν εργάζονταν με την τουρκική γλώσσα. +Αυτό οφείλεται στο γεγονός ότι η τουρκική γλώσσα κάνει διάκριση μεταξύ κεφαλαίων και πεζών `I` με και χωρίς τελεία. Έτσι το `strtolower('I')` επέστρεφε τον χαρακτήρα `ı` και το `strtoupper('i')` επέστρεφε τον χαρακτήρα `İ`, γεγονός που οδηγούσε τις εφαρμογές να προκαλούν μια σειρά από μυστηριώδη σφάλματα. +Ωστόσο, το πρόβλημα αυτό διορθώθηκε στην έκδοση 8.2 της PHP και οι λειτουργίες δεν εξαρτώνται πλέον από την τοπική γλώσσα. + +Αυτό είναι ένα ωραίο παράδειγμα του πώς η παγκόσμια κατάσταση έχει ταλαιπωρήσει χιλιάδες προγραμματιστές σε όλο τον κόσμο. Η λύση ήταν η αντικατάστασή του με την έγχυση εξάρτησης. + + +Πότε είναι δυνατή η χρήση του Global State; .[#toc-when-is-it-possible-to-use-global-state] +------------------------------------------------------------------------------------------- + +Υπάρχουν ορισμένες συγκεκριμένες καταστάσεις στις οποίες είναι δυνατή η χρήση καθολικής κατάστασης. Για παράδειγμα, όταν κάνετε αποσφαλμάτωση κώδικα και πρέπει να απορρίψετε την τιμή μιας μεταβλητής ή να μετρήσετε τη διάρκεια ενός συγκεκριμένου τμήματος του προγράμματος. Σε τέτοιες περιπτώσεις, οι οποίες αφορούν προσωρινές ενέργειες που θα αφαιρεθούν αργότερα από τον κώδικα, είναι θεμιτό να χρησιμοποιήσετε έναν σφαιρικά διαθέσιμο ντάμπερ ή ένα χρονόμετρο. Τα εργαλεία αυτά δεν αποτελούν μέρος του σχεδιασμού του κώδικα. + +Ένα άλλο παράδειγμα είναι οι συναρτήσεις για την εργασία με κανονικές εκφράσεις `preg_*`, οι οποίες αποθηκεύουν εσωτερικά τις μεταγλωττισμένες κανονικές εκφράσεις σε μια στατική κρυφή μνήμη στη μνήμη. Όταν καλείτε την ίδια κανονική έκφραση πολλές φορές σε διαφορετικά μέρη του κώδικα, αυτή μεταγλωττίζεται μόνο μία φορά. Η κρυφή μνήμη εξοικονομεί απόδοση και είναι επίσης εντελώς αόρατη στον χρήστη, οπότε μια τέτοια χρήση μπορεί να θεωρηθεί νόμιμη. + + +Περίληψη .[#toc-summary] +------------------------ + +Δείξαμε γιατί έχει νόημα + +1) Αφαιρέστε όλες τις στατικές μεταβλητές από τον κώδικα +2) Δηλώστε εξαρτήσεις +3) Και χρησιμοποιήστε την έγχυση εξαρτήσεων + +Όταν μελετάτε το σχεδιασμό κώδικα, να έχετε κατά νου ότι κάθε `static $foo` αντιπροσωπεύει ένα πρόβλημα. Προκειμένου ο κώδικάς σας να είναι ένα περιβάλλον που σέβεται το DI, είναι απαραίτητο να εξαλείψετε εντελώς την παγκόσμια κατάσταση και να την αντικαταστήσετε με έγχυση εξαρτήσεων. + +Κατά τη διάρκεια αυτής της διαδικασίας, μπορεί να διαπιστώσετε ότι πρέπει να χωρίσετε μια κλάση επειδή έχει περισσότερες από μία αρμοδιότητες. Μην ανησυχείτε γι' αυτό- επιδιώξτε την αρχή της μίας ευθύνης. + +*Θα ήθελα να ευχαριστήσω τον Miško Hevery, του οποίου άρθρα όπως το [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] αποτελούν τη βάση αυτού του κεφαλαίου.* diff --git a/dependency-injection/el/introduction.texy b/dependency-injection/el/introduction.texy index 78f1239580..f64ff0169a 100644 --- a/dependency-injection/el/introduction.texy +++ b/dependency-injection/el/introduction.texy @@ -2,9 +2,9 @@ ********************************* .[perex] -Αυτό το κεφάλαιο σας εισάγει στις βασικές πρακτικές προγραμματισμού που πρέπει να ακολουθείτε κατά τη συγγραφή οποιασδήποτε εφαρμογής. Αυτά είναι τα βασικά που απαιτούνται για τη συγγραφή καθαρού, κατανοητού και συντηρήσιμου κώδικα. +Αυτό το κεφάλαιο θα σας εισαγάγει στις βασικές πρακτικές προγραμματισμού που πρέπει να ακολουθείτε κατά τη συγγραφή οποιασδήποτε εφαρμογής. Αυτές είναι οι βασικές αρχές που απαιτούνται για τη συγγραφή καθαρού, κατανοητού και συντηρήσιμου κώδικα. -Αν μάθετε και ακολουθήσετε αυτούς τους κανόνες, η Nette θα είναι δίπλα σας σε κάθε σας βήμα. Θα χειρίζεται τις εργασίες ρουτίνας για εσάς και θα σας κάνει να αισθάνεστε όσο το δυνατόν πιο άνετα, ώστε να μπορείτε να επικεντρωθείτε στην ίδια τη λογική. +Αν μάθετε και ακολουθήσετε αυτούς τους κανόνες, η Nette θα είναι δίπλα σας σε κάθε σας βήμα. Θα χειρίζεται εργασίες ρουτίνας για εσάς και θα σας παρέχει τη μέγιστη δυνατή άνεση, ώστε να μπορείτε να επικεντρωθείτε στην ίδια τη λογική. Οι αρχές που θα παρουσιάσουμε εδώ είναι αρκετά απλές. Δεν χρειάζεται να ανησυχείτε για τίποτα. @@ -12,7 +12,7 @@ Θυμάστε το πρώτο σας πρόγραμμα; .[#toc-remember-your-first-program] ------------------------------------------------------------------- -Δεν έχουμε ιδέα σε ποια γλώσσα το γράψατε, αλλά αν ήταν PHP, πιθανόν να έμοιαζε κάπως έτσι: +Δεν ξέρουμε σε ποια γλώσσα το γράψατε, αλλά αν ήταν PHP, μπορεί να έμοιαζε κάπως έτσι: ```php function addition(float $a, float $b): float @@ -25,31 +25,31 @@ echo addition(23, 1); // εκτυπώσεις 24 Λίγες ασήμαντες γραμμές κώδικα, αλλά τόσες πολλές βασικές έννοιες κρυμμένες σε αυτές. Ότι υπάρχουν μεταβλητές. Ότι ο κώδικας αναλύεται σε μικρότερες μονάδες, οι οποίες είναι συναρτήσεις, για παράδειγμα. Ότι τους δίνουμε ορίσματα εισόδου και επιστρέφουν αποτελέσματα. Το μόνο που λείπει είναι οι συνθήκες και οι βρόχοι. -Το γεγονός ότι δίνουμε είσοδο σε μια συνάρτηση και αυτή επιστρέφει ένα αποτέλεσμα είναι μια απολύτως κατανοητή έννοια που χρησιμοποιείται και σε άλλα πεδία, όπως τα μαθηματικά. +Το γεγονός ότι μια συνάρτηση λαμβάνει δεδομένα εισόδου και επιστρέφει ένα αποτέλεσμα είναι μια απολύτως κατανοητή έννοια, η οποία χρησιμοποιείται και σε άλλους τομείς, όπως τα μαθηματικά. -Μια συνάρτηση έχει μια υπογραφή, η οποία αποτελείται από το όνομά της, μια λίστα παραμέτρων και τους τύπους τους και, τέλος, τον τύπο της τιμής επιστροφής. Ως χρήστες, μας ενδιαφέρει η υπογραφή- συνήθως δεν χρειάζεται να γνωρίζουμε τίποτα για την εσωτερική υλοποίηση. +Μια συνάρτηση έχει την υπογραφή της, η οποία αποτελείται από το όνομά της, έναν κατάλογο παραμέτρων και τους τύπους τους και, τέλος, τον τύπο της τιμής επιστροφής. Ως χρήστες, μας ενδιαφέρει η υπογραφή και συνήθως δεν χρειάζεται να γνωρίζουμε τίποτα για την εσωτερική υλοποίηση. -Φανταστείτε τώρα ότι η υπογραφή μιας συνάρτησης μοιάζει ως εξής: +Φανταστείτε τώρα ότι η υπογραφή της συνάρτησης έμοιαζε ως εξής: ```php function addition(float $x): float ``` -Μια πρόσθεση με μία παράμετρο; Αυτό είναι παράξενο... Τι λέτε για αυτό; +Μια προσθήκη με μία παράμετρο; Αυτό είναι παράξενο... Τι λέτε γι' αυτό; ```php function addition(): float ``` -Αυτό είναι πραγματικά περίεργο, έτσι δεν είναι; Πώς νομίζεις ότι χρησιμοποιείται η λειτουργία; +Τώρα αυτό είναι πραγματικά περίεργο, σωστά; Πώς χρησιμοποιείται η συνάρτηση; ```php echo addition(); // τι εκτυπώνει; ``` -Κοιτάζοντας έναν τέτοιο κώδικα, είμαστε μπερδεμένοι. Όχι μόνο ένας αρχάριος δεν θα τον καταλάβαινε, ακόμη και ένας εξειδικευμένος προγραμματιστής δεν θα καταλάβαινε έναν τέτοιο κώδικα. +Κοιτάζοντας έναν τέτοιο κώδικα, θα ήμασταν μπερδεμένοι. Όχι μόνο ένας αρχάριος δεν θα τον καταλάβαινε, αλλά ακόμη και ένας έμπειρος προγραμματιστής δεν θα καταλάβαινε έναν τέτοιο κώδικα. -Αναρωτιέστε πώς θα έμοιαζε στην πραγματικότητα μια τέτοια συνάρτηση στο εσωτερικό της; Πού θα έβρισκε τους αθροιστές; Πιθανότατα θα τους έβρισκε *με κάποιο τρόπο* από μόνη της, κάπως έτσι: +Αναρωτιέστε πώς θα έμοιαζε στην πραγματικότητα μια τέτοια συνάρτηση στο εσωτερικό της; Από πού θα έπαιρνε τα αθροίσματα; Πιθανότατα θα τις έπαιρνε *με κάποιο τρόπο* από μόνη της, ίσως κάπως έτσι: ```php function addition(): float @@ -66,13 +66,13 @@ function addition(): float Όχι με αυτόν τον τρόπο! .[#toc-not-this-way] -------------------------------------------- -Το σχέδιο που μόλις μας παρουσιάστηκε είναι η ουσία πολλών αρνητικών χαρακτηριστικών: +Ο σχεδιασμός που μόλις δείξαμε είναι η ουσία πολλών αρνητικών χαρακτηριστικών: -- Η υπογραφή της συνάρτησης προσποιούνταν ότι δεν χρειαζόταν προσθετέα, γεγονός που μας μπέρδευε +- Η υπογραφή της συνάρτησης προσποιούνταν ότι δεν χρειαζόταν τα αθροίσματα, γεγονός που μας μπέρδευε. - δεν έχουμε ιδέα πώς να κάνουμε τη συνάρτηση να υπολογίζει με δύο άλλους αριθμούς -- έπρεπε να κοιτάξουμε τον κώδικα για να δούμε πού παίρνει τους προσθετέους -- ανακαλύψαμε κρυφές δεσμεύσεις -- για να κατανοήσουμε πλήρως, πρέπει να εξερευνήσουμε και αυτές τις δεσμεύσεις +- έπρεπε να κοιτάξουμε τον κώδικα για να βρούμε από πού προέρχονταν τα αθροίσματα +- Βρήκαμε κρυφές εξαρτήσεις. +- η πλήρης κατανόηση απαιτεί την εξέταση και αυτών των εξαρτήσεων Και μήπως είναι καν δουλειά της συνάρτησης πρόσθεσης να προμηθεύεται εισόδους; Φυσικά και δεν είναι. Η ευθύνη της είναι μόνο να προσθέτει. @@ -93,20 +93,20 @@ function addition(float $a, float $b): float Ο πιο σημαντικός κανόνας είναι: **Όλα τα δεδομένα που χρειάζονται οι συναρτήσεις ή οι κλάσεις πρέπει να τους μεταβιβάζονται**. -Αντί να εφευρίσκετε κρυφούς μηχανισμούς για να τις βοηθήσετε να τα φτάσουν με κάποιο τρόπο μόνες τους, απλά περάστε τις παραμέτρους. Θα γλιτώσετε το χρόνο που χρειάζεται για να επινοήσετε κρυφό τρόπο, ο οποίος σίγουρα δεν θα βελτιώσει τον κώδικά σας. +Αντί να εφευρίσκονται κρυφοί τρόποι για να έχουν πρόσβαση στα δεδομένα, απλώς μεταβιβάστε τις παραμέτρους. Θα εξοικονομήσετε χρόνο που θα ξοδευόταν στην εφεύρεση κρυφών μονοπατιών που σίγουρα δεν θα βελτιώσουν τον κώδικά σας. -Αν ακολουθείτε αυτόν τον κανόνα πάντα και παντού, βρίσκεστε στο δρόμο για κώδικα χωρίς κρυφές δεσμεύσεις. Προς κώδικα που είναι κατανοητός όχι μόνο για τον συγγραφέα, αλλά και για οποιονδήποτε τον διαβάσει στη συνέχεια. Όπου τα πάντα είναι κατανοητά από τις υπογραφές των συναρτήσεων και των κλάσεων και δεν χρειάζεται να ψάχνετε για κρυμμένα μυστικά στην υλοποίηση. +Αν ακολουθείτε πάντα και παντού αυτόν τον κανόνα, βρίσκεστε στο δρόμο για κώδικα χωρίς κρυφές εξαρτήσεις. Σε κώδικα που είναι κατανοητός όχι μόνο για τον συγγραφέα αλλά και για οποιονδήποτε τον διαβάσει στη συνέχεια. Όπου τα πάντα είναι κατανοητά από τις υπογραφές των συναρτήσεων και των κλάσεων και δεν χρειάζεται να ψάχνετε για κρυμμένα μυστικά στην υλοποίηση. -Αυτή η τεχνική ονομάζεται επιδέξια **έγχυση εξάρτησης**. Και τα δεδομένα ονομάζονται **εξαρτήσεις.** Αλλά πρόκειται για μια απλή μεταβίβαση παραμέτρων, τίποτα περισσότερο. +Αυτή η τεχνική ονομάζεται επαγγελματικά **έγχυση εξάρτησης**. Και αυτά τα δεδομένα ονομάζονται **εξαρτήσεις**. Είναι απλά μια συνηθισμένη μεταβίβαση παραμέτρων, τίποτα περισσότερο. .[note] -Παρακαλώ μην συγχέετε την έγχυση εξαρτήσεων, που είναι ένα πρότυπο σχεδίασης, με το "dependency injection container", που είναι ένα εργαλείο, κάτι εντελώς διαφορετικό. Θα συζητήσουμε τα containers αργότερα. +Παρακαλώ μην συγχέετε την έγχυση εξαρτήσεων, η οποία είναι ένα πρότυπο σχεδίασης, με ένα "dependency injection container", το οποίο είναι ένα εργαλείο, κάτι διαμετρικά διαφορετικό. Θα ασχοληθούμε με τους περιέκτες αργότερα. Από τις συναρτήσεις στις κλάσεις .[#toc-from-functions-to-classes] ------------------------------------------------------------------ -Και πώς σχετίζονται οι κλάσεις με αυτό; Μια κλάση είναι μια πιο σύνθετη οντότητα από μια απλή συνάρτηση, αλλά ο κανόνας #1 ισχύει και εδώ. Απλά υπάρχουν [περισσότεροι τρόποι για να περάσετε ορίσματα |passing-dependencies]. Για παράδειγμα, αρκετά παρόμοια με την περίπτωση μιας συνάρτησης: +Και πώς συνδέονται οι τάξεις; Μια κλάση είναι μια πιο σύνθετη μονάδα από μια απλή συνάρτηση, αλλά ο κανόνας #1 ισχύει πλήρως και εδώ. Απλά υπάρχουν [περισσότεροι τρόποι για να περάσετε ορίσματα |passing-dependencies]. Για παράδειγμα, αρκετά παρόμοια με την περίπτωση μιας συνάρτησης: ```php class Math @@ -121,7 +121,7 @@ $math = new Math; echo $math->addition(23, 1); // 24 ``` -Ή με τη χρήση άλλων μεθόδων, ή απευθείας από τον κατασκευαστή: +Ή μέσω άλλων μεθόδων ή απευθείας μέσω του κατασκευαστή: ```php class Addition @@ -149,9 +149,9 @@ echo $addition->calculate(); // 24 Παραδείγματα πραγματικής ζωής .[#toc-real-life-examples] -------------------------------------------------------- -Στον πραγματικό κόσμο, δεν θα γράψετε κλάσεις για την πρόσθεση αριθμών. Ας προχωρήσουμε σε παραδείγματα του πραγματικού κόσμου. +Στον πραγματικό κόσμο, δεν θα γράφετε μαθήματα για την πρόσθεση αριθμών. Ας προχωρήσουμε σε πρακτικά παραδείγματα. -Ας έχουμε μια κλάση `Article` που αναπαριστά ένα άρθρο ιστολογίου: +Ας έχουμε μια κλάση `Article` που αναπαριστά μια δημοσίευση σε ιστολόγιο: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Η μέθοδος `save()` αποθηκεύει το άρθρο σε έναν πίνακα της βάσης δεδομένων. Η υλοποίησή της με τη χρήση [της Nette Database |database:] θα ήταν πανεύκολη, αν δεν υπήρχε ένα πρόβλημα: από πού θα πρέπει να πάρει το `Article` τη σύνδεση με τη βάση δεδομένων, δηλαδή το αντικείμενο της κλάσης `Nette\Database\Connection`; +Η μέθοδος `save()` θα αποθηκεύσει το άρθρο σε έναν πίνακα της βάσης δεδομένων. Η υλοποίησή της με τη χρήση [της Nette Database |database:] θα είναι πανεύκολη, αν δεν υπήρχε ένα πρόβλημα: από πού παίρνει η `Article` τη σύνδεση με τη βάση δεδομένων, δηλαδή ένα αντικείμενο της κλάσης `Nette\Database\Connection`; -Φαίνεται ότι έχουμε πολλές επιλογές. Μπορεί να την πάρει από κάπου σε μια στατική μεταβλητή. Ή να την κληρονομήσει από μια κλάση που θα παρέχει τη σύνδεση με τη βάση δεδομένων. Ή να εκμεταλλευτεί ένα [singleton |global-state#Singleton]. Ή τα λεγόμενα facades που χρησιμοποιούνται στο Laravel: +Φαίνεται ότι έχουμε πολλές επιλογές. Μπορεί να την πάρει από μια στατική μεταβλητή κάπου. Ή να κληρονομήσει από μια κλάση που παρέχει μια σύνδεση με τη βάση δεδομένων. Ή να επωφεληθεί από ένα [singleton |global-state#Singleton]. Ή να χρησιμοποιήσει τα λεγόμενα facades, τα οποία χρησιμοποιούνται στο Laravel: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ class Article Ή μήπως όχι; -Ας θυμηθούμε τον [κανόνα #1: Αφήστε να περάσει σε σας |#rule #1: Let It Be Passed to You] You: όλες οι εξαρτήσεις που χρειάζεται η κλάση πρέπει να περάσουν σε αυτήν. Γιατί αν δεν το κάνουμε και παραβιάσουμε τον κανόνα, έχουμε ξεκινήσει το δρόμο για βρώμικο κώδικα γεμάτο κρυφές δεσμεύσεις, ακατανόητο και το αποτέλεσμα θα είναι μια εφαρμογή που θα είναι μπελάς στη συντήρηση και την ανάπτυξη. +Ας θυμηθούμε τον [κανόνα #1: Let It Be Passed to |#rule #1: Let It Be Passed to You] You: όλες οι εξαρτήσεις που χρειάζεται η κλάση πρέπει να περάσουν σε αυτήν. Διότι αν παραβιάσουμε τον κανόνα, έχουμε ξεκινήσει μια πορεία προς βρώμικο κώδικα γεμάτο κρυφές εξαρτήσεις, ακατανόητο και το αποτέλεσμα θα είναι μια εφαρμογή που θα είναι οδυνηρή στη συντήρηση και την ανάπτυξη. -Ο χρήστης της κλάσης `Article` δεν έχει ιδέα πού αποθηκεύει το άρθρο η μέθοδος `save()`. Σε έναν πίνακα της βάσης δεδομένων; Σε ποιον, στην παραγωγή ή στην ανάπτυξη; Και πώς μπορεί να αλλάξει αυτό; +Ο χρήστης της κλάσης `Article` δεν έχει ιδέα πού αποθηκεύει το άρθρο η μέθοδος `save()`. Σε έναν πίνακα της βάσης δεδομένων; Σε ποιον, στην παραγωγή ή στις δοκιμές; Και πώς μπορεί να αλλάξει; -Ο χρήστης πρέπει να εξετάσει τον τρόπο υλοποίησης της μεθόδου `save()` για να βρει τη χρήση της μεθόδου `DB::insert()`. Έτσι, πρέπει να ψάξει περαιτέρω για να βρει πώς αυτή η μέθοδος προμηθεύεται μια σύνδεση με τη βάση δεδομένων. Και οι κρυφές συνδέσεις μπορούν να σχηματίσουν μια αρκετά μεγάλη αλυσίδα. +Ο χρήστης πρέπει να κοιτάξει πώς υλοποιείται η μέθοδος `save()` και να βρει τη χρήση της μεθόδου `DB::insert()`. Έτσι, πρέπει να ψάξει περαιτέρω για να βρει πώς αυτή η μέθοδος αποκτά μια σύνδεση με τη βάση δεδομένων. Και οι κρυφές εξαρτήσεις μπορεί να σχηματίσουν μια αρκετά μεγάλη αλυσίδα. -Οι κρυφές δεσμεύσεις, οι προσόψεις του Laravel ή οι στατικές μεταβλητές δεν υπάρχουν ποτέ σε καθαρό, καλά σχεδιασμένο κώδικα. Στον καθαρό και καλά σχεδιασμένο κώδικα, τα ορίσματα μεταβιβάζονται: +Σε καθαρό και καλά σχεδιασμένο κώδικα, δεν υπάρχουν ποτέ κρυφές εξαρτήσεις, προσόψεις του Laravel ή στατικές μεταβλητές. Στον καθαρό και καλά σχεδιασμένο κώδικα, τα ορίσματα μεταβιβάζονται: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Ακόμα πιο πρακτικό, όπως θα δούμε στη συνέχεια, είναι η χρήση ενός κατασκευαστή: +Μια ακόμα πιο πρακτική προσέγγιση, όπως θα δούμε αργότερα, θα είναι μέσω του κατασκευαστή: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Αν είστε έμπειρος προγραμματιστής, ίσως σκέφτεστε ότι το `Article` δεν θα έπρεπε να έχει καθόλου μέθοδο `save()`, θα έπρεπε να είναι ένα καθαρό στοιχείο δεδομένων και ένα ξεχωριστό αποθετήριο θα έπρεπε να φροντίζει για την αποθήκευση. Αυτό είναι λογικό. Αλλά αυτό θα μας πήγαινε πολύ πέρα από το θέμα, που είναι η έγχυση εξαρτήσεων, και θα προσπαθούσαμε να δώσουμε απλά παραδείγματα. +Αν είστε έμπειρος προγραμματιστής, μπορεί να σκεφτείτε ότι το `Article` δεν θα έπρεπε να έχει καθόλου τη μέθοδο `save()` - θα έπρεπε να αντιπροσωπεύει ένα καθαρά στοιχείο δεδομένων και ένα ξεχωριστό αποθετήριο θα έπρεπε να φροντίζει για την αποθήκευση. Αυτό είναι λογικό. Αλλά αυτό θα μας πήγαινε πολύ πέρα από το αντικείμενο του θέματος, που είναι η έγχυση εξαρτήσεων, και την προσπάθεια να δώσουμε απλά παραδείγματα. -Αν πρόκειται να γράψετε μια κλάση που απαιτεί μια βάση δεδομένων για να λειτουργήσει, για παράδειγμα, μην βρείτε από πού θα την πάρετε, αλλά να σας την περάσετε. Ίσως ως παράμετρος σε έναν κατασκευαστή ή σε μια άλλη μέθοδο. Δηλώστε εξαρτήσεις. Εκθέστε τις στο API της κλάσης σας. Θα έχετε κατανοητό και προβλέψιμο κώδικα. +Αν γράψετε μια κλάση που απαιτεί, για παράδειγμα, μια βάση δεδομένων για τη λειτουργία της, μην επινοήσετε από πού θα την πάρετε, αλλά να την έχετε περάσει. Είτε ως παράμετρο του κατασκευαστή είτε ως κάποια άλλη μέθοδο. Παραδεχτείτε εξαρτήσεις. Παραδεχτείτε τις στο API της κλάσης σας. Θα έχετε κατανοητό και προβλέψιμο κώδικα. -Τι θα λέγατε για αυτή την κλάση που καταγράφει μηνύματα σφάλματος: +Και τι γίνεται με αυτή την κλάση, η οποία καταγράφει μηνύματα σφάλματος: ```php class Logger @@ -266,9 +266,9 @@ class Logger Δεν το κάναμε. -Η βασική πληροφορία, ο κατάλογος του αρχείου καταγραφής, *αποκτάται* από την κλάση από τη σταθερά. +Η βασική πληροφορία, δηλαδή ο κατάλογος με το αρχείο καταγραφής, *αποκτάται* από την ίδια την κλάση από τη σταθερά. -Δείτε το παράδειγμα χρήσης: +Κοιτάξτε το παράδειγμα χρήσης: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Χωρίς να γνωρίζετε την εφαρμογή, μπορείτε να απαντήσετε στην ερώτηση πού γράφονται τα μηνύματα; Θα σας έδειχνε ότι η ύπαρξη της σταθεράς LOG_DIR είναι απαραίτητη για να λειτουργήσει; Και θα μπορούσατε να δημιουργήσετε μια δεύτερη περίπτωση που να γράφει σε διαφορετική θέση; Σίγουρα όχι. +Χωρίς να γνωρίζετε την υλοποίηση, μπορείτε να απαντήσετε στο ερώτημα πού γράφονται τα μηνύματα; Θα μπορούσατε να υποθέσετε ότι η ύπαρξη της σταθεράς `LOG_DIR` είναι απαραίτητη για τη λειτουργία του; Και θα μπορούσατε να δημιουργήσετε μια δεύτερη περίπτωση που θα έγραφε σε διαφορετική θέση; Σίγουρα όχι. Ας διορθώσουμε την κλάση: @@ -295,7 +295,7 @@ class Logger } ``` -Η κλάση είναι τώρα πολύ πιο σαφής, πιο παραμετροποιήσιμη και επομένως πιο χρήσιμη. +Η κλάση είναι τώρα πολύ πιο κατανοητή, παραμετροποιήσιμη και επομένως πιο χρήσιμη. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Αλλά δεν με νοιάζει! .[#toc-but-i-don-t-care] --------------------------------------------- -*"Όταν δημιουργώ ένα αντικείμενο Article και καλώ την save(), δεν θέλω να ασχοληθώ με τη βάση δεδομένων, θέλω απλώς να αποθηκευτεί σε αυτήν που έχω ορίσει στη διαμόρφωση. "* +*"Όταν δημιουργώ ένα αντικείμενο Article και καλώ την save(), δεν θέλω να ασχοληθώ με τη βάση δεδομένων- θέλω απλώς να αποθηκευτεί σε αυτήν που έχω ορίσει στη ρύθμιση παραμέτρων."* -*"Όταν χρησιμοποιώ το Logger, θέλω απλώς να γράφεται το μήνυμα και δεν θέλω να ασχοληθώ με το πού. Αφήστε να χρησιμοποιηθούν οι παγκόσμιες ρυθμίσεις. "* +*"Όταν χρησιμοποιώ το Logger, θέλω απλώς να γράφεται το μήνυμα και δεν θέλω να ασχοληθώ με το πού. Αφήστε να χρησιμοποιηθούν οι παγκόσμιες ρυθμίσεις."* -Αυτά είναι σωστά σχόλια. +Αυτά είναι βάσιμα σημεία. -Ως παράδειγμα, ας πάρουμε μια τάξη που στέλνει ενημερωτικά δελτία και καταγράφει πώς πήγε αυτό: +Ως παράδειγμα, ας δούμε μια κλάση που στέλνει ενημερωτικά δελτία και καταγράφει πώς πήγε: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -Το βελτιωμένο `Logger`, το οποίο δεν χρησιμοποιεί πλέον τη σταθερά `LOG_DIR`, απαιτεί μια διαδρομή αρχείου στον κατασκευαστή. Πώς να το λύσετε αυτό; Η κλάση `NewsletterDistributor` δεν ενδιαφέρεται για το πού γράφονται τα μηνύματα, απλά θέλει να τα γράψει. +Το βελτιωμένο `Logger`, το οποίο δεν χρησιμοποιεί πλέον τη σταθερά `LOG_DIR`, απαιτεί τον προσδιορισμό της διαδρομής του αρχείου στον κατασκευαστή. Πώς να το λύσετε αυτό; Η κλάση `NewsletterDistributor` δεν ενδιαφέρεται για το πού γράφονται τα μηνύματα- θέλει απλώς να τα γράψει. -Η λύση είναι και πάλι ο [κανόνας #1: Αφήστε να περάσει σε σας |#rule #1: Let It Be Passed to You]: περάστε όλα τα δεδομένα που χρειάζεται η κλάση σε αυτήν. +Η λύση είναι και πάλι ο [κανόνας #1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: περάστε όλα τα δεδομένα που χρειάζεται η κλάση. -Έτσι, περνάμε τη διαδρομή προς το αρχείο καταγραφής στον κατασκευαστή, τον οποίο στη συνέχεια χρησιμοποιούμε για να δημιουργήσουμε το αντικείμενο `Logger`? +Αυτό λοιπόν σημαίνει ότι περνάμε τη διαδρομή προς το αρχείο καταγραφής μέσω του κατασκευαστή, την οποία στη συνέχεια χρησιμοποιούμε κατά τη δημιουργία του αντικειμένου `Logger`; ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Όχι έτσι! Επειδή η διαδρομή **δεν** ανήκει στα δεδομένα που χρειάζεται η κλάση `NewsletterDistributor` - χρειάζεται το `Logger`. Η κλάση χρειάζεται τον ίδιο τον καταγραφέα. Και αυτό είναι που θα μεταβιβάσουμε: +Όχι, όχι έτσι! Το μονοπάτι δεν ανήκει στα δεδομένα που χρειάζεται η κλάση `NewsletterDistributor` - στην πραγματικότητα, το `Logger` το χρειάζεται. Βλέπετε τη διαφορά; Η κλάση `NewsletterDistributor` χρειάζεται τον ίδιο τον καταγραφέα. Έτσι, αυτό είναι που θα περάσουμε: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Τώρα είναι σαφές από τις υπογραφές της κλάσης `NewsletterDistributor` ότι η καταγραφή αποτελεί μέρος της λειτουργικότητάς της. Και το έργο της αντικατάστασης του καταγραφέα με έναν άλλο, ίσως για σκοπούς δοκιμής, είναι αρκετά ασήμαντο. -Επιπλέον, αν αλλάξει ο κατασκευαστής της κλάσης `Logger`, αυτό δεν θα έχει καμία επίδραση στην κλάση μας. +Τώρα είναι σαφές από τις υπογραφές της κλάσης `NewsletterDistributor` ότι η καταγραφή αποτελεί επίσης μέρος της λειτουργικότητάς της. Και το έργο της ανταλλαγής του καταγραφέα με έναν άλλο, ίσως για δοκιμές, είναι εντελώς ασήμαντο. +Επιπλέον, αν αλλάξει ο κατασκευαστής της κλάσης `Logger`, αυτό δεν θα επηρεάσει την κλάση μας. -Κανόνας #2: Πάρτε αυτό που σας ανήκει .[#toc-rule-2-take-what-is-yours] ------------------------------------------------------------------------ +Κανόνας #2: Πάρτε ό,τι σας ανήκει .[#toc-rule-2-take-what-s-yours] +------------------------------------------------------------------ -Μην παραπλανηθείτε και μην αφήνετε τις παραμέτρους των εξαρτήσεών σας να σας μεταβιβάζονται. Περάστε τις εξαρτήσεις απευθείας. +Μην παρασύρεστε και μην αφήνετε τον εαυτό σας να περάσει τις εξαρτήσεις των εξαρτημένων σας. Περάστε μόνο τις δικές σας εξαρτήσεις. -Αυτό θα κάνει τον κώδικα που χρησιμοποιεί άλλα αντικείμενα εντελώς ανεξάρτητο από τις αλλαγές στους κατασκευαστές τους. Το API του θα είναι πιο αληθινό. Και το πιο σημαντικό, θα είναι τετριμμένο να ανταλλάξετε αυτές τις εξαρτήσεις με άλλες. +Χάρη σε αυτό, ο κώδικας που χρησιμοποιεί άλλα αντικείμενα θα είναι εντελώς ανεξάρτητος από τις αλλαγές στους κατασκευαστές τους. Το API του θα είναι πιο ειλικρινές. Και πάνω απ' όλα, θα είναι τετριμμένο να αντικαταστήσετε αυτές τις εξαρτήσεις με άλλες. -Ένα νέο μέλος της οικογένειας .[#toc-a-new-member-of-the-family] ----------------------------------------------------------------- +Νέο μέλος της οικογένειας .[#toc-new-family-member] +--------------------------------------------------- -Η ομάδα ανάπτυξης αποφάσισε να δημιουργήσει έναν δεύτερο καταγραφέα που γράφει στη βάση δεδομένων. Έτσι δημιουργούμε μια κλάση `DatabaseLogger`. Έτσι έχουμε δύο κλάσεις, `Logger` και `DatabaseLogger`, η μία γράφει σε ένα αρχείο, η άλλη γράφει σε μια βάση δεδομένων ... δεν νομίζετε ότι υπάρχει κάτι περίεργο σε αυτό το όνομα; -Δεν θα ήταν καλύτερα να μετονομάσουμε την `Logger` σε `FileLogger`; Σίγουρα θα ήταν καλύτερα. +Η ομάδα ανάπτυξης αποφάσισε να δημιουργήσει έναν δεύτερο καταγραφέα που γράφει στη βάση δεδομένων. Έτσι δημιουργούμε μια κλάση `DatabaseLogger`. Έτσι έχουμε δύο κλάσεις, `Logger` και `DatabaseLogger`, η μία γράφει σε ένα αρχείο, η άλλη σε μια βάση δεδομένων ... δεν σας φαίνεται περίεργη η ονομασία; +Δεν θα ήταν καλύτερα να μετονομάσετε την `Logger` σε `FileLogger`; Σίγουρα ναι. -Αλλά ας το κάνουμε έξυπνα. Θα δημιουργήσουμε μια διεπαφή με το αρχικό όνομα: +Αλλά ας το κάνουμε έξυπνα. Δημιουργούμε μια διεπαφή με το αρχικό όνομα: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...την οποία θα υλοποιούν και οι δύο καταγραφείς: +... το οποίο θα εφαρμόσουν και οι δύο καταγραφείς: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -Και με αυτόν τον τρόπο, τίποτα δεν θα χρειαστεί να αλλάξει στον υπόλοιπο κώδικα όπου χρησιμοποιείται ο καταγραφέας. Για παράδειγμα, ο κατασκευαστής της κλάσης `NewsletterDistributor` θα εξακολουθεί να είναι ευχαριστημένος με την απαίτηση του `Logger` ως παραμέτρου. Και θα εξαρτάται από εμάς ποια περίπτωση θα του δώσουμε. +Και εξαιτίας αυτού, δεν θα χρειαστεί να αλλάξετε τίποτα στον υπόλοιπο κώδικα όπου χρησιμοποιείται ο καταγραφέας. Για παράδειγμα, ο κατασκευαστής της κλάσης `NewsletterDistributor` θα εξακολουθεί να αρκείται στην απαίτηση του `Logger` ως παραμέτρου. Και θα εξαρτάται από εμάς ποια περίπτωση θα περάσουμε. -**Αυτός είναι ο λόγος για τον οποίο δεν δίνουμε ποτέ στα ονόματα διεπαφών την κατάληξη `Interface` ή το πρόθεμα `I`.** Διαφορετικά, θα ήταν αδύνατο να αναπτύξουμε κώδικα τόσο όμορφα. +**Αυτός είναι ο λόγος για τον οποίο δεν προσθέτουμε ποτέ το επίθημα `Interface` ή το πρόθεμα `I` στα ονόματα των διεπαφών.** Διαφορετικά, δεν θα ήταν δυνατή η ανάπτυξη του κώδικα τόσο όμορφα. Χιούστον, έχουμε ένα πρόβλημα .[#toc-houston-we-have-a-problem] --------------------------------------------------------------- -Ενώ στο σύνολο της εφαρμογής μπορούμε να είμαστε ευχαριστημένοι με μια μόνο περίπτωση ενός καταγραφέα, είτε πρόκειται για αρχείο είτε για βάση δεδομένων, και απλά να την περνάμε όπου καταγράφεται κάτι, τα πράγματα είναι εντελώς διαφορετικά στην περίπτωση της κλάσης `Article`. Για την ακρίβεια, δημιουργούμε περιπτώσεις της ανάλογα με τις ανάγκες μας, ενδεχομένως πολλές φορές. Πώς θα αντιμετωπίσουμε τη δέσμευση της βάσης δεδομένων στον κατασκευαστή της; +Ενώ μπορούμε να τα βγάλουμε πέρα με μια μοναδική περίπτωση του καταγραφέα, είτε βασίζεται σε αρχείο είτε σε βάση δεδομένων, σε ολόκληρη την εφαρμογή και απλά να την περνάμε οπουδήποτε καταγράφεται κάτι, τα πράγματα είναι εντελώς διαφορετικά για την κλάση `Article`. Δημιουργούμε τις περιπτώσεις της όπως χρειάζεται, ακόμη και πολλές φορές. Πώς θα αντιμετωπίσουμε την εξάρτηση από τη βάση δεδομένων στον κατασκευαστή της; -Ως παράδειγμα, μπορούμε να χρησιμοποιήσουμε έναν ελεγκτή που θα πρέπει να αποθηκεύσει ένα άρθρο στη βάση δεδομένων μετά την υποβολή μιας φόρμας: +Ένα παράδειγμα μπορεί να είναι ένας ελεγκτής που θα πρέπει να αποθηκεύσει ένα άρθρο στη βάση δεδομένων μετά την υποβολή μιας φόρμας: ```php class EditController extends Controller @@ -437,9 +437,9 @@ class EditController extends Controller } ``` -Μια πιθανή λύση προσφέρεται άμεσα: να περάσει το αντικείμενο της βάσης δεδομένων από τον κατασκευαστή στο `EditController` και να χρησιμοποιηθεί το `$article = new Article($this->db)`. +Μια πιθανή λύση είναι προφανής: περάστε το αντικείμενο της βάσης δεδομένων στον κατασκευαστή `EditController` και χρησιμοποιήστε το `$article = new Article($this->db)`. -Όπως και στην προηγούμενη περίπτωση με το `Logger` και τη διαδρομή του αρχείου, αυτή δεν είναι η σωστή προσέγγιση. Η βάση δεδομένων δεν αποτελεί εξάρτηση του `EditController`, αλλά του `Article`. Έτσι, η μεταβίβαση της βάσης δεδομένων αντιβαίνει στον [κανόνα #2: πάρτε ό,τι σας ανήκει |#rule #2: take what is yours]. Όταν ο κατασκευαστής της κλάσης `Article` τροποποιείται (προστίθεται μια νέα παράμετρος), θα πρέπει να τροποποιηθεί και ο κώδικας σε όλα τα σημεία όπου δημιουργούνται περιπτώσεις. Ufff. +Όπως και στην προηγούμενη περίπτωση με το `Logger` και τη διαδρομή του αρχείου, αυτή δεν είναι η σωστή προσέγγιση. Η βάση δεδομένων δεν αποτελεί εξάρτηση του `EditController`, αλλά του `Article`. Η μεταβίβαση της βάσης δεδομένων αντιβαίνει στον [κανόνα #2: πάρτε ό,τι σας ανήκει |#rule #2: take what's yours]. Αν ο κατασκευαστής της κλάσης `Article` αλλάξει (προστίθεται μια νέα παράμετρος), θα πρέπει να τροποποιήσετε τον κώδικα όπου δημιουργούνται περιπτώσεις. Ufff. Χιούστον, τι προτείνεις; @@ -447,11 +447,11 @@ class EditController extends Controller Κανόνας #3: Αφήστε το εργοστάσιο να το χειριστεί .[#toc-rule-3-let-the-factory-handle-it] ----------------------------------------------------------------------------------------- -Αφαιρώντας τις κρυφές δεσμεύσεις και περνώντας όλες τις εξαρτήσεις ως ορίσματα, παίρνουμε πιο παραμετροποιήσιμες και ευέλικτες κλάσεις. Και έτσι χρειαζόμαστε κάτι άλλο για να δημιουργήσουμε και να διαμορφώσουμε αυτές τις πιο ευέλικτες κλάσεις. Θα το ονομάσουμε εργοστάσια. +Εξαλείφοντας τις κρυφές εξαρτήσεις και περνώντας όλες τις εξαρτήσεις ως ορίσματα, έχουμε αποκτήσει πιο παραμετροποιήσιμες και ευέλικτες κλάσεις. Και επομένως, χρειαζόμαστε κάτι άλλο για να δημιουργήσουμε και να διαμορφώσουμε αυτές τις πιο ευέλικτες κλάσεις για εμάς. Θα το ονομάσουμε εργοστάσια. Ο γενικός κανόνας είναι: αν μια κλάση έχει εξαρτήσεις, αφήστε τη δημιουργία των περιπτώσεων τους στο εργοστάσιο. -Τα εργοστάσια είναι μια πιο έξυπνη αντικατάσταση του τελεστή `new` στον κόσμο του dependency injection. +Τα εργοστάσια είναι μια πιο έξυπνη αντικατάσταση του τελεστή `new` στον κόσμο της έγχυσης εξαρτήσεων. .[note] Μην συγχέετε με το πρότυπο σχεδίασης *factory method*, το οποίο περιγράφει έναν συγκεκριμένο τρόπο χρήσης των εργοστασίων και δεν σχετίζεται με αυτό το θέμα. @@ -460,7 +460,7 @@ class EditController extends Controller Εργοστάσιο .[#toc-factory] -------------------------- -Ένα εργοστάσιο είναι μια μέθοδος ή μια κλάση που παράγει και διαμορφώνει αντικείμενα. Ονομάζουμε `Article` που παράγει την κλάση `ArticleFactory` και θα μπορούσε να μοιάζει ως εξής: +Ένα εργοστάσιο είναι μια μέθοδος ή μια κλάση που δημιουργεί και ρυθμίζει αντικείμενα. Θα ονομάσουμε την κλάση που παράγει το `Article` ως `ArticleFactory`, και θα μπορούσε να μοιάζει ως εξής: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Η χρήση του στον ελεγκτή θα ήταν η εξής: +Η χρήση του στον ελεγκτή θα έχει ως εξής: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -Σε αυτό το σημείο, όταν η υπογραφή του κατασκευαστή της κλάσης `Article` αλλάζει, το μόνο μέρος του κώδικα που χρειάζεται να ανταποκριθεί είναι το ίδιο το εργοστάσιο `ArticleFactory`. Οποιοσδήποτε άλλος κώδικας που εργάζεται με αντικείμενα `Article`, όπως το `EditController`, δεν θα επηρεαστεί. +Σε αυτό το σημείο, αν αλλάξει η υπογραφή του κατασκευαστή της κλάσης `Article`, το μόνο μέρος του κώδικα που χρειάζεται να αντιδράσει είναι το ίδιο το `ArticleFactory`. Όλος ο υπόλοιπος κώδικας που εργάζεται με αντικείμενα `Article`, όπως το `EditController`, δεν θα επηρεαστεί. -Μπορεί να χτυπάτε το μέτωπό σας αυτή τη στιγμή και να αναρωτιέστε αν βοηθηθήκαμε καθόλου. Η ποσότητα του κώδικα έχει αυξηθεί και το όλο θέμα αρχίζει να φαίνεται ύποπτα περίπλοκο. +Μπορεί να αναρωτιέστε αν έχουμε κάνει τα πράγματα πραγματικά καλύτερα. Η ποσότητα του κώδικα έχει αυξηθεί και όλα αρχίζουν να φαίνονται ύποπτα περίπλοκα. -Μην ανησυχείτε, σύντομα θα φτάσουμε στο δοχείο Nette DI. Και έχει αρκετούς άσσους στο μανίκι του που θα κάνουν τη δημιουργία εφαρμογών με χρήση dependency injection εξαιρετικά απλή. Για παράδειγμα, αντί για την κλάση `ArticleFactory`, θα αρκεί να [γράψετε μια απλή διεπαφή |factory]: +Μην ανησυχείτε, σύντομα θα φτάσουμε στο δοχείο Nette DI. Και έχει αρκετά κόλπα στο μανίκι του, τα οποία θα απλοποιήσουν σημαντικά τη δημιουργία εφαρμογών που χρησιμοποιούν dependency injection. Για παράδειγμα, αντί για την κλάση `ArticleFactory`, θα χρειαστεί να [γράψετε |factory] μόνο [μια απλή διεπαφή |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Αλλά προχωράμε μπροστά, περιμένετε :-) +Αλλά βρισκόμαστε μπροστά από τους εαυτούς μας- σας παρακαλούμε να είστε υπομονετικοί :-) Περίληψη .[#toc-summary] ------------------------ -Στην αρχή αυτού του κεφαλαίου, υποσχεθήκαμε να σας δείξουμε έναν τρόπο σχεδίασης καθαρού κώδικα. Απλά δώστε στις κλάσεις +Στην αρχή αυτού του κεφαλαίου, υποσχεθήκαμε να σας δείξουμε μια διαδικασία για τη σχεδίαση καθαρού κώδικα. Το μόνο που χρειάζεται είναι οι κλάσεις να: -- [τις εξαρτήσεις που χρειάζονται |#Rule #1: Let It Be Passed to You] -- [και όχι αυτά που δεν χρειάζονται άμεσα |#Rule #2: Take What Is Yours] -- [και ότι τα αντικείμενα με εξαρτήσεις είναι καλύτερο να γίνονται σε εργοστάσια |#Rule #3: Let the Factory Handle it] +- [να περνούν τις εξαρτήσεις που χρειάζονται |#Rule #1: Let It Be Passed to You] +- [αντίστροφα, να μην περνούν ό,τι δεν χρειάζονται άμεσα |#Rule #2: Take What's Yours] +- [και ότι τα αντικείμενα με εξαρτήσεις είναι καλύτερο να δημιουργούνται σε εργοστάσια |#Rule #3: Let the Factory Handle it] -Μπορεί να μη φαίνεται έτσι με την πρώτη ματιά, αλλά αυτοί οι τρεις κανόνες έχουν εκτεταμένες επιπτώσεις. Οδηγούν σε μια ριζικά διαφορετική θεώρηση του σχεδιασμού κώδικα. Αξίζει τον κόπο; Οι προγραμματιστές που έχουν απορρίψει τις παλιές συνήθειες και έχουν αρχίσει να χρησιμοποιούν με συνέπεια την έγχυση εξαρτήσεων θεωρούν ότι πρόκειται για μια κομβική στιγμή στην επαγγελματική τους ζωή. Άνοιξε έναν κόσμο ξεκάθαρων και βιώσιμων εφαρμογών. +Με μια πρώτη ματιά, αυτοί οι τρεις κανόνες μπορεί να μην φαίνεται να έχουν εκτεταμένες συνέπειες, αλλά οδηγούν σε μια ριζικά διαφορετική οπτική για τη σχεδίαση κώδικα. Αξίζει τον κόπο; Οι προγραμματιστές που εγκατέλειψαν τις παλιές συνήθειες και άρχισαν να χρησιμοποιούν με συνέπεια την έγχυση εξαρτήσεων θεωρούν αυτό το βήμα μια κρίσιμη στιγμή στην επαγγελματική τους ζωή. Τους άνοιξε τον κόσμο των σαφών και συντηρήσιμων εφαρμογών. -Τι γίνεται όμως αν ο κώδικας δεν χρησιμοποιεί με συνέπεια την έγχυση εξαρτήσεων; Τι γίνεται αν είναι βασισμένος σε στατικές μεθόδους ή singletons; Μήπως αυτό επιφέρει προβλήματα; [Ναι, και είναι πολύ σημαντικά |global-state]. +Τι γίνεται όμως αν ο κώδικας δεν χρησιμοποιεί με συνέπεια την έγχυση εξαρτήσεων; Τι γίνεται αν βασίζεται σε στατικές μεθόδους ή singletons; Προκαλεί αυτό προβλήματα; [Ναι, δημιουργεί, και μάλιστα πολύ θεμελιώδη |global-state]. diff --git a/dependency-injection/el/passing-dependencies.texy b/dependency-injection/el/passing-dependencies.texy index d2a41e9073..01698c8e6a 100644 --- a/dependency-injection/el/passing-dependencies.texy +++ b/dependency-injection/el/passing-dependencies.texy @@ -12,7 +12,7 @@ </div> -Οι τρεις πρώτες μέθοδοι εφαρμόζονται γενικά σε όλες τις αντικειμενοστραφείς γλώσσες, η τέταρτη είναι ειδική για τους παρουσιαστές της Nette, γι' αυτό και εξετάζεται σε [ξεχωριστό κεφάλαιο |best-practices:inject-method-attribute]. Θα εξετάσουμε τώρα πιο προσεκτικά καθεμία από αυτές τις επιλογές και θα τις παρουσιάσουμε με συγκεκριμένα παραδείγματα. +Θα παρουσιάσουμε τώρα τις διάφορες παραλλαγές με συγκεκριμένα παραδείγματα. Έγχυση κατασκευαστή .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Οι εξαρτήσεις περνούν ως ορίσματα στον κατασκευαστή κατά τη δημιουργία του αντικειμένου: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Αυτή η μορφή είναι χρήσιμη για τις υποχρεωτικές εξαρτήσεις που η κλάση χρειάζεται οπωσδήποτε για να λειτουργήσει, καθώς χωρίς αυτές δεν μπορεί να δημιουργηθεί η περίπτωση. @@ -40,10 +40,10 @@ $service = new MyService($cache); ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ class MyService ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService Το DI container περνάει τις εξαρτήσεις στον κατασκευαστή αυτόματα χρησιμοποιώντας [την αυτόματη σύνδεση (autowiring |autowiring]). Τα επιχειρήματα που δεν μπορούν να περάσουν με αυτόν τον τρόπο (π.χ. συμβολοσειρές, αριθμοί, booleans) [γράφονται στη διαμόρφωση |services#Arguments]. +Κόλαση του κατασκευαστή .[#toc-constructor-hell] +------------------------------------------------ + +Ο όρος *κόλαση κατασκευαστών* αναφέρεται σε μια κατάσταση όπου ένα παιδί κληρονομεί από μια γονική κλάση της οποίας ο κατασκευαστής απαιτεί εξαρτήσεις και το παιδί απαιτεί επίσης εξαρτήσεις. Πρέπει επίσης να αναλάβει και να μεταβιβάσει τις εξαρτήσεις του γονέα: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Το πρόβλημα εμφανίζεται όταν θέλουμε να αλλάξουμε τον κατασκευαστή της κλάσης `BaseClass`, για παράδειγμα όταν προστίθεται μια νέα εξάρτηση. Τότε πρέπει να τροποποιήσουμε και όλους τους κατασκευαστές των παιδιών. Το οποίο κάνει μια τέτοια τροποποίηση κόλαση. + +Πώς μπορεί να αποφευχθεί αυτό; Η λύση είναι η **προτεραιότητα της σύνθεσης έναντι της κληρονομικότητας**. + +Ας σχεδιάσουμε λοιπόν τον κώδικα με διαφορετικό τρόπο. Θα αποφύγουμε τις αφηρημένες κλάσεις `Base*`. Αντί το `MyClass` να παίρνει κάποια λειτουργικότητα κληρονομώντας από το `BaseClass`, θα έχει αυτή τη λειτουργικότητα περασμένη ως εξάρτηση: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Έγχυση ρυθμιστή .[#toc-setter-injection] ======================================== -Οι εξαρτήσεις μεταβιβάζονται με την κλήση μιας μεθόδου που τις αποθηκεύει σε μια ιδιωτική ιδιότητα. Η συνήθης σύμβαση ονοματοδοσίας για αυτές τις μεθόδους είναι της μορφής `set*()`, γι' αυτό και ονομάζονται setters. +Οι εξαρτήσεις μεταφέρονται με την κλήση μιας μεθόδου που τις αποθηκεύει σε μια ιδιωτική ιδιότητα. Η συνήθης σύμβαση ονοματοδοσίας για αυτές τις μεθόδους είναι της μορφής `set*()`, γι' αυτό και ονομάζονται setters, αλλά φυσικά μπορούν να ονομαστούν και αλλιώς. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Η μέθοδος αυτή είναι χρήσιμη για προαιρετικές εξαρτήσεις που δεν είναι απαραίτητες για τη λειτουργία της κλάσης, καθώς δεν είναι εγγυημένο ότι το αντικείμενο θα τις λάβει πραγματικά (δηλαδή ότι ο χρήστης θα καλέσει τη μέθοδο). @@ -90,16 +150,16 @@ $service->setCache($cache); Ταυτόχρονα, η μέθοδος αυτή επιτρέπει την επανειλημμένη κλήση του setter για την αλλαγή της εξάρτησης. Αν αυτό δεν είναι επιθυμητό, προσθέστε έναν έλεγχο στη μέθοδο ή, από την PHP 8.1, σημειώστε την ιδιότητα `$cache` με τη σημαία `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ class MyService ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Property Injection .[#toc-property-injection] Οι εξαρτήσεις περνούν απευθείας στην ιδιότητα: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` `public`Ως εκ τούτου, δεν έχουμε κανέναν έλεγχο για το αν η μεταβιβαζόμενη εξάρτηση θα είναι πράγματι του καθορισμένου τύπου (αυτό ίσχυε πριν από την PHP 7.4) και χάνουμε τη δυνατότητα να αντιδράσουμε στη νέα ανατεθείσα εξάρτηση με το δικό μας κώδικα, για παράδειγμα για να αποτρέψουμε μεταγενέστερες αλλαγές. Ταυτόχρονα, η ιδιότητα γίνεται μέρος της δημόσιας διεπαφής της κλάσης, κάτι που μπορεί να μην είναι επιθυμητό. @@ -137,12 +197,18 @@ $service->cache = $cache; ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Ένεση .[#toc-inject] +==================== + +Ενώ οι τρεις προηγούμενες μέθοδοι ισχύουν γενικά σε όλες τις αντικειμενοστραφείς γλώσσες, η έγχυση μέσω μεθόδου, σχολίου ή χαρακτηριστικού *inject* είναι ειδική για τους παρουσιαστές Nette. Συζητούνται σε [ξεχωριστό |best-practices:inject-method-attribute] κεφάλαιο. + + Ποιον τρόπο να επιλέξω; .[#toc-which-way-to-choose] =================================================== diff --git a/dependency-injection/el/services.texy b/dependency-injection/el/services.texy index bc0fca2828..f264fd21f6 100644 --- a/dependency-injection/el/services.texy +++ b/dependency-injection/el/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Inject Mode .[#toc-inject-mode] =============================== -Η σημαία `inject: true` χρησιμοποιείται για την ενεργοποίηση του περάσματος εξαρτήσεων μέσω δημόσιων μεταβλητών με τον σχολιασμό [inject |best-practices:inject-method-attribute#Inject Annotations] και τις μεθόδους [inject*() |best-practices:inject-method-attribute#inject Methods]. +Η σημαία `inject: true` χρησιμοποιείται για την ενεργοποίηση του περάσματος εξαρτήσεων μέσω δημόσιων μεταβλητών με τον σχολιασμό [inject |best-practices:inject-method-attribute#Inject Attributes] και τις μεθόδους [inject*() |best-practices:inject-method-attribute#inject Methods]. ```neon services: diff --git a/dependency-injection/en/@home.texy b/dependency-injection/en/@home.texy index 99c96616bb..8ea471e277 100644 --- a/dependency-injection/en/@home.texy +++ b/dependency-injection/en/@home.texy @@ -5,8 +5,10 @@ Dependency Injection Dependency Injection is a design pattern that will fundamentally change the way you look at code and development. It opens the way to a world of cleanly designed and sustainable applications. - [What is Dependency Injection? |introduction] -- [What is DI Container? |container] +- [Global State & Singletons |global-state] - [Passing Dependencies |passing-dependencies] +- [What is DI Container? |container] +- [Frequently Asked Questions |faq] Nette DI diff --git a/dependency-injection/en/@left-menu.texy b/dependency-injection/en/@left-menu.texy index 30092630f6..3ddd017bd2 100644 --- a/dependency-injection/en/@left-menu.texy +++ b/dependency-injection/en/@left-menu.texy @@ -1,8 +1,10 @@ Dependency Injection ******************** - [What is DI? |introduction] -- [What is DI Container? |container] +- [Global State & Singletons |global-state] - [Passing Dependencies |passing-dependencies] +- [What is DI Container? |container] +- [Frequently Asked Questions |faq] Nette DI diff --git a/dependency-injection/en/container.texy b/dependency-injection/en/container.texy index 0cdcc31316..2f5e18d979 100644 --- a/dependency-injection/en/container.texy +++ b/dependency-injection/en/container.texy @@ -4,7 +4,7 @@ What Is DI Container? .[perex] Dependency injection container (DIC) is a class that can instantiate and configure objects. -It may surprise you, but in many cases you don't need a dependency injection container to take advantage of dependency injection (DI for short). After all, even in [previous chapter|introduction] we showed specific examples of DI and no container was needed. +It may surprise you, but in many cases you don't need a dependency injection container to take advantage of dependency injection (DI for short). After all, even in [introductory chapter|introduction] we showed specific examples of DI and no container was needed. However, if you need to manage a large number of different objects with many dependencies, a dependency injection container will be really useful. Which is perhaps the case for web applications built on a framework. diff --git a/dependency-injection/en/faq.texy b/dependency-injection/en/faq.texy new file mode 100644 index 0000000000..ecf0ee0df0 --- /dev/null +++ b/dependency-injection/en/faq.texy @@ -0,0 +1,112 @@ +DI Frequently Asked Questions (FAQ) +*********************************** + + +Is DI another name for IoC? +--------------------------- + +The *Inversion of Control* (IoC) is a principle focused on the way code is executed - whether your code initiates external code or your code is integrated into external code, which then calls it. +IoC is a broad concept that includes [events |nette:glossary#Events], the so-called [Hollywood Principle |application:components#Hollywood style], and other aspects. +Factories, which are part of [Rule #3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], and represent inversion for the `new` operator, are also components of this concept. + +The *Dependency Injection* (DI) is about how one object knows about another object, i.e., dependency. It is a design pattern that requires explicit passing of dependencies between objects. + +Thus, DI can be said to be a specific form of IoC. However, not all forms of IoC are suitable in terms of code purity. For example, among anti-patterns, we include all techniques that work with [global state] or the so-called [Service Locator |#What is a Service Locator]. + + +What is a Service Locator? +-------------------------- + +A Service Locator is an alternative to Dependency Injection. It works by creating a central storage where all available services or dependencies are registered. When an object needs a dependency, it requests it from the Service Locator. + +However, compared to Dependency Injection, it loses transparency: dependencies are not directly passed to objects and are therefore not easily identifiable, which requires examining the code to uncover and understand all connections. Testing is also more complicated, as we cannot simply pass mock objects to the tested objects, but have to go through the Service Locator. Furthermore, the Service Locator disrupts the design of the code, as individual objects must be aware of its existence, which differs from Dependency Injection, where objects have no knowledge of the DI container. + + +When is it better not to use DI? +-------------------------------- + +There are no known difficulties associated with using the Dependency Injection design pattern. On the contrary, obtaining dependencies from globally accessible locations leads to [a number of complications |global-state], as does using a Service Locator. +Therefore, it is advisable to always use DI. This is not a dogmatic approach, but simply no better alternative has been found. + +However, there are certain situations where we do not pass objects to each other and obtain them from the global space. For example, when debugging code and needing to dump a variable value at a specific point in the program, measure the duration of a certain part of the program, or log a message. +In such cases, where it is about temporary actions that will be later removed from the code, it is legitimate to use a globally accessible dumper, stopwatch, or logger. These tools, after all, do not belong to the design of the code. + + +Does using DI have its drawbacks? +--------------------------------- + +Does using Dependency Injection involve any disadvantages, such as increased code writing complexity or worse performance? What do we lose when we start writing code in accordance with DI? + +DI has no impact on application performance or memory requirements. The performance of the DI Container may play a role, but in the case of [Nette DI | nette-container], the container is compiled into pure PHP, so its overhead during application runtime is essentially zero. + +When writing code, it is necessary to create constructors that accept dependencies. In the past, this could be time-consuming, but thanks to modern IDEs and [constructor property promotion |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion], it is now a matter of a few seconds. Factories can be easily generated using Nette DI and a PhpStorm plugin with just a few clicks. +On the other hand, there is no need to write singletons and static access points. + +It can be concluded that a properly designed application using DI is neither shorter nor longer compared to an application using singletons. Parts of the code working with dependencies are simply extracted from individual classes and moved to new locations, i.e., the DI container and factories. + + +How to rewrite a legacy application to DI? +------------------------------------------ + +Migrating from a legacy application to Dependency Injection can be a challenging process, especially for large and complex applications. It is important to approach this process systematically. + +- When moving to Dependency Injection, it is important that all team members understand the principles and practices being used. +- First, perform an analysis of the existing application to identify key components and their dependencies. Create a plan for which parts will be refactored and in what order. +- Implement a DI container or, better yet, use an existing library such as Nette DI. +- Gradually refactor each part of the application to use Dependency Injection. This may involve modifying constructors or methods to accept dependencies as parameters. +- Modify places in the code where dependency objects are created so that dependencies are instead injected by the container. This may include the use of factories. + +Remember that moving to Dependency Injection is an investment in code quality and the long-term sustainability of the application. While it may be challenging to make these changes, the result should be cleaner, more modular, and easily testable code that is ready for future extensions and maintenance. + + +Why composition is preferred over inheritance? +---------------------------------------------- +It is preferable to use composition rather then inheritance as it serves the purpose of code reuse-ability without having the need to worry about the trickle down effect of change. Thus it provides more loosely coupling where we do not have to worry about changing some code causing some other dependent code requiring change. A typical example is the situation identified as [constructor hell |passing-dependencies#Constructor hell]. + + +Can Nette DI Container be used outside of Nette? +------------------------------------------------ + +Absolutely. The Nette DI Container is part of Nette, but is designed as a standalone library that can be used independently of other parts of the framework. Just install it using Composer, create a configuration file defining your services, and then use a few lines of PHP code to create the DI container. +And you can immediately start taking advantage of Dependency Injection in your projects. + +The [Nette DI Container |nette-container] chapter describes what a specific use case looks like, including the code. + + +Why is the configuration in NEON files? +--------------------------------------- + +NEON is a simple and easily readable configuration language developed within Nette for setting up applications, services, and their dependencies. Compared to JSON or YAML, it offers much more intuitive and flexible options for this purpose. In NEON, you can naturally describe bindings that would not be possible to write in Symfony & YAML either at all or only through a complex description. + + +Does parsing NEON files slow down the application? +-------------------------------------------------- + +Although NEON files are parsed very quickly, this aspect doesn't really matter. The reason is that parsing files occurs only once during the first launch of the application. After that, the DI container code is generated, stored on the disk, and executed for each subsequent request without the need for further parsing. + +This is how it works in a production environment. During development, NEON files are parsed every time their content changes, ensuring that the developer always has an up-to-date DI container. As mentioned earlier, the actual parsing is a matter of an instant. + + +How do I access the parameters from the configuration file in my class? +----------------------------------------------------------------------- + +Keep in mind [Rule #1: Let It Be Passed to You |introduction#Rule #1: Let It Be Passed to You]. If a class requires information from a configuration file, we don't need to figure out how to access that information; instead, we simply ask for it - for example, through the class constructor. And we perform the passing in the configuration file. + +In this example, `%myParameter%` is a placeholder for the value of the `myParameter` parameter, which will be passed to the `MyClass` constructor: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +If you want to pass multiple parameters or use autowiring, it is useful to [wrap the parameters in an object |best-practices:passing-settings-to-presenters]. + + +Does Nette support PSR-11 Container interface? +---------------------------------------------- + +Nette DI Container does not support PSR-11 directly. However, if you need interoperability between the Nette DI Container and libraries or frameworks that expect the PSR-11 Container Interface, you can create a [simple adapter |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] to serve as a bridge between the Nette DI Container and PSR-11. diff --git a/dependency-injection/en/global-state.texy b/dependency-injection/en/global-state.texy new file mode 100644 index 0000000000..8a73af584c --- /dev/null +++ b/dependency-injection/en/global-state.texy @@ -0,0 +1,312 @@ +Global State and Singletons +*************************** + +.[perex] +Warning: the following constructs are symptoms of poor code design: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` or `static::$var` + +Do any of these constructs occur in your code? Then you have an opportunity to improve. You may be thinking that these are common constructs that we see in sample solutions of various libraries and frameworks. +Unfortunately, they are still a clear indicator of poor design. They have one thing in common: the use of global state. + +Now, we're certainly not talking about some kind of academic purity. The use of global state and singletons has destructive effects on code quality. Its behavior becomes unpredictable, reduces developer productivity, and forces class interfaces to lie about their true dependencies. Which confuses programmers. + +In this chapter, we will show how this is possible. + + +Global Interlinking +------------------- + +The fundamental problem with the global state is that it is globally accessible. This makes it possible to write to the database via the global (static) method `DB::insert()`. +In an ideal world, an object should only be able to communicate with other objects that have been [directly passed to |passing-dependencies] it. +If I create two objects `A` and `B` and never pass a reference from `A` to `B`, then neither `A`, nor `B` can access the other object or change its state. +This is a very desirable feature of the code. It's similar to having a battery and a light bulb; the bulb won't light until you wire them together. + +This is not true for global (static) variables or singletons. The `A` object could *wirelessly* access the `C` object and modify it without passing any reference, by calling `C::changeSomething()`. +If the `B` object also grabs the global `C`, then `A` and `B` can interact with each other via `C`. + +The use of global variables introduces a new form of *wireless* coupling into the system that is not visible from the outside. +It creates a smokescreen complicating the understanding and use of the code. +Developers must read every line of source code to truly understand the dependencies. Instead of just familiarizing themselves with the interface of the classes. +Moreover, it is a completely unnecessary coupling. + +.[note] +In terms of behavior, there is no difference between a global and a static variable. They are equally harmful. + + +The Spooky Action at a Distance +------------------------------- + +"Spooky action at a distance" - that's what Albert Einstein famously called a phenomenon in quantum physics that gave him the creeps in 1935. +It is quantum entanglement, the peculiarity of which is that when you measure information about one particle, you immediately affect another particle, even if they are millions of light years apart. +which seemingly violates the fundamental law of the universe that nothing can travel faster than light. + +In the software world, we can call a "spooky action at a distance" a situation where we run a process that we think is isolated (because we haven't passed it any references), but unexpected interactions and state changes happen in distant locations of the system which we did not tell the object about. This can only happen through the global state. + +Imagine joining a project development team that has a large, mature code base. Your new lead asks you to implement a new feature and, like a good developer, you start by writing a test. But because you're new to the project, you do a lot of exploratory "what happens if I call this method" type tests. And you try to write the following test: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // your card number + $cc->charge(100); +} +``` + +You run the code, maybe several times, and after a while you notice notifications on your phone from the bank that each time you run it, $100 was charged to your credit card 🤦‍♂️ + +How on earth could the test cause an actual charge? It's not easy to operate with credit card. You have to interact with a third party web service, you have to know the URL of that web service, you have to log in, and so on. +None of this information is included in the test. Even worse, you don't even know where this information is present, and therefore how to mock external dependencies so that each run doesn't result in $100 being charged again. And as a new developer, how were you supposed to know that what you were about to do would lead to you being $100 poorer? + +That's a spooky action at a distance! + +You have no choice but to dig through a lot of source code, asking older and more experienced colleagues, until you understand how the connections in the project work. +This is due to the fact that when looking at the interface of the `CreditCard` class, you cannot determine the global state that needs to be initialized. Even looking at the source code of the class won't tell you which initialization method to call. At best, you can find the global variable being accessed and try to guess how to initialize it from that. + +The classes in such a project are pathological liars. The payment card pretends that you can just instantiate it and call the `charge()` method. However, it secretly interacts with another class, `PaymentGateway`. Even its interface says it can be initialized independently, but in reality it pulls credentials from some configuration file and so on. +It is clear to the developers who wrote this code that `CreditCard` needs `PaymentGateway`. They wrote the code this way. But for anyone new to the project, this is a complete mystery and hinders learning. + +How to fix the situation? Easy. **Let the API declare dependencies.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Notice how the relationships within the code are suddenly obvious. By declaring that the `charge()` method needs `PaymentGateway`, you don't have to ask anyone how the code is interdependent. You know you have to create an instance of it, and when you try to do so, you run into the fact that you have to supply access parameters. Without them, the code wouldn't even run. + +And most importantly, you can now mock the payment gateway so you won't be charged $100 every time you run a test. + +The global state causes your objects to be able to secretly access things that aren't declared in their APIs, and as a result makes your APIs pathological liars. + +You may not have thought of it this way before, but whenever you use global state, you're creating secret wireless communication channels. Creepy remote action forces developers to read every line of code to understand potential interactions, reduces developer productivity, and confuses new team members. +If you're the one who created the code, you know the real dependencies, but anyone who comes after you is clueless. + +Don't write code that uses global state, prefer to pass dependencies. That is, dependency injection. + + +Brittleness of the Global State +------------------------------- + +In code that uses global state and singletons, it is never certain when and by whom that state has changed. This risk is already present at initialization. The following code is supposed to create a database connection and initialize the payment gateway, but it keeps throwing an exception and finding the cause is extremely tedious: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +You have to go through the code in detail to find that the `PaymentGateway` object accesses other objects wirelessly, some of which require a database connection. Thus, you must initialize the database before `PaymentGateway`. However, the smokescreen of global state hides this from you. How much time would you save if the API of each class did not lie and declare its dependencies? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +A similar problem arises when using global access to a database connection: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +When calling the `save()` method, it is not certain whether a database connection has already been created and who is responsible for creating it. For example, if we wanted to change the database connection on the fly, perhaps for testing purposes, we would probably have to create additional methods such as `DB::reconnect(...)` or `DB::reconnectForTest()`. + +Consider an example: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Where can we be sure that the test database is really being used when calling `$article->save()`? What if the `Foo::doSomething()` method changed the global database connection? To find out, we would have to examine the source code of the `Foo` class and probably many other classes. However, this approach would provide only a short-term answer, as the situation may change in the future. + +What if we move the database connection to a static variable inside the `Article` class? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +This doesn't change anything at all. The problem is a global state and it doesn't matter which class it hides in. In this case, as in the previous one, we have no clue as to what database is being written to when the `$article->save()` method is called. Anyone on the distant end of the application could change the database at any time using `Article::setDb()`. Under our hands. + +The global state makes our application **extremely fragile**. + +However, there is a simple way to deal with this problem. Just have the API declare dependencies to ensure proper functionality. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +This approach eliminates the worry of hidden and unexpected changes to database connections. Now we are sure where the article is stored and no code modifications inside another unrelated class can change the situation anymore. The code is no longer fragile, but stable. + +Don't write code that uses global state, prefer to pass dependencies. Thus, dependency injection. + + +Singleton +--------- + +Singleton is a design pattern that, by [definition |https://en.wikipedia.org/wiki/Singleton_pattern] from the famous Gang of Four publication, restricts a class to a single instance and offers global access to it. The implementation of this pattern usually resembles the following code: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // and other methods that perform the functions of the class +} +``` + +Unfortunately, the singleton introduces global state into the application. And as we have shown above, global state is undesirable. That's why the singleton is considered an antipattern. + +Don't use singletons in your code and replace them with other mechanisms. You really don't need singletons. However, if you need to guarantee the existence of a single instance of a class for the entire application, leave it to the [DI container |container]. +Thus, create an application singleton, or service. This will stop the class from providing its own uniqueness (i.e., it won't have a `getInstance()` method and a static variable) and will only perform its functions. Thus, it will stop violating the single responsibility principle. + + +Global State Versus Tests +------------------------- + +When writing tests, we assume that each test is an isolated unit and that no external state enters it. And no state leaves the tests. When a test completes, any state associated with the test should be removed automatically by the garbage collector. This makes the tests isolated. Therefore, we can run the tests in any order. + +However, if global states/singletons are present, all these nice assumptions break down. A state can enter and exit a test. Suddenly, the order of the tests may matter. + +To test singletons at all, developers often have to relax their properties, perhaps by allowing an instance to be replaced by another. Such solutions are, at best, hacks that produce code that is difficult to maintain and understand. Any test or method `tearDown()` that affects any global state must undo those changes. + +Global state is the biggest headache in unit testing! + +How to fix the situation? Easy. Don't write code that uses singletons, prefer to pass dependencies. That is, dependency injection. + + +Global Constants +---------------- + +Global state is not limited to the use of singletons and static variables, but can also apply to global constants. + +Constants whose value does not provide us with any new (`M_PI`) or useful (`PREG_BACKTRACK_LIMIT_ERROR`) information are clearly OK. +Conversely, constants that serve as a way to *wirelessly* pass information inside the code are nothing more than a hidden dependency. Like `LOG_FILE` in the following example. +Using the `FILE_APPEND` constant is perfectly correct. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +In this case, we should declare the parameter in the constructor of the `Foo` class to make it part of the API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Now we can pass information about the path to the logging file and easily change it as needed, making it easier to test and maintain the code. + + +Global Functions and Static Methods +----------------------------------- + +We want to emphasize that the use of static methods and global functions is not in itself problematic. We have explained the inappropriateness of using `DB::insert()` and similar methods, but it has always been a matter of global state stored in a static variable. The `DB::insert()` method requires the existence of a static variable because it stores the database connection. Without this variable, it would be impossible to implement the method. + +The use of deterministic static methods and functions, such as `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` and many others, is perfectly consistent with dependency injection. These functions always return the same results from the same input parameters and are therefore predictable. They do not use any global state. + +However, there are functions in PHP that are not deterministic. These include, for example, the `htmlspecialchars()` function. Its third parameter, `$encoding`, if not specified, defaults to the value of the configuration option `ini_get('default_charset')`. Therefore, it is recommended to always specify this parameter to avoid possible unpredictable behavior of the function. Nette consistently does this. + +Some functions, such as `strtolower()`, `strtoupper()`, and the like, have had non-deterministic behavior in the recent past and have depended on the `setlocale()` setting. This caused many complications, most often when working with the Turkish language. +This is because the Turkish language distinguishes between upper and lower case `I` with and without a dot. So `strtolower('I')` returned the `ı` character and `strtoupper('i')` returned the `İ` character , which led to applications causing a number of mysterious errors. +However, this problem was fixed in PHP version 8.2 and the functions are no longer locale dependent. + +This is a nice example of how global state has plagued thousands of developers around the world. The solution was to replace it with dependency injection. + + +When Is It Possible to Use Global State? +---------------------------------------- + +There are certain specific situations where it is possible to use global state. For example, when debugging code and you need to dump the value of a variable or measure the duration of a specific part of the program. In such cases, which concern temporary actions that will be later removed from the code, it is legitimate to use a globally available dumper or stopwatch. These tools are not part of the code design. + +Another example is the functions for working with regular expressions `preg_*`, which internally store compiled regular expressions in a static cache in memory. When you call the same regular expression multiple times in different parts of the code, it is compiled only once. The cache saves performance and is also completely invisible to the user, so such usage can be considered legitimate. + + +Summary +------- + +We've shown why it makes sense + +1) Remove all static variables from the code +2) Declare dependencies +3) And use dependency injection + +When contemplating code design, keep in mind that each `static $foo` represents a problem. In order for your code to be a DI-respecting environment, it is essential to completely eradicate global state and replace it with dependency injection. + +During this process, you may find that you need to split a class because it has more than one responsibility. Don't worry about it; strive for the principle of one responsibility. + +*I would like to thank Miško Hevery, whose articles such as [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] form the basis of this chapter.* diff --git a/dependency-injection/en/introduction.texy b/dependency-injection/en/introduction.texy index d697ede4b2..3179e5fdd2 100644 --- a/dependency-injection/en/introduction.texy +++ b/dependency-injection/en/introduction.texy @@ -2,17 +2,17 @@ What is Dependency Injection? ***************************** .[perex] -This chapter introduces you to the basic programming practices you should follow when writing any application. These are the basics required to write clean, understandable, and maintainable code. +This chapter will introduce you to the basic programming practices that you should follow when writing any application. These are the fundamentals needed for writing clean, understandable, and maintainable code. -If you learn and follow these rules, Nette will be there for you every step of the way. It will handle routine tasks for you and make you as comfortable as possible so you can focus on the logic itself. +If you learn and follow these rules, Nette will be there for you every step of the way. It will handle routine tasks for you and provide maximum comfort, so you can focus on the logic itself. -The principles we will show here are quite simple. You have nothing to worry about. +The principles we will show here are quite simple. You don't have to worry about anything. Remember Your First Program? ---------------------------- -We have no idea what language you wrote it in, but if it was PHP, it would probably look something like this: +We don't know what language you wrote it in, but if it was PHP, it might have looked something like this: ```php function addition(float $a, float $b): float @@ -25,31 +25,31 @@ echo addition(23, 1); // prints 24 A few trivial lines of code, but so many key concepts hidden in them. That there are variables. That code is broken down into smaller units, which are functions, for example. That we pass them input arguments and they return results. All that's missing are conditions and loops. -The fact that we pass input to a function and it returns a result is a perfectly understandable concept that is used in other fields, such as mathematics. +The fact that a function takes input data and returns a result is a perfectly understandable concept, which is also used in other fields, such as mathematics. -A function has a signature, which consists of its name, a list of parameters and their types, and finally the type of return value. As users, we are interested in the signature; we usually don't need to know anything about the internal implementation. +A function has its signature, which consists of its name, a list of parameters and their types, and finally the type of the return value. As users, we are interested in the signature, and we usually don't need to know anything about the internal implementation. -Now imagine that the signature of a function looks like this: +Now imagine that the function signature looked like this: ```php function addition(float $x): float ``` -An addition with one parameter? That's weird... How about this? +An addition with one parameter? That's strange... What about this? ```php function addition(): float ``` -That's really weird, isn't it? How do you think the function is used? +Now that's really weird, right? How is the function used? ```php echo addition(); // what does it prints? ``` -Looking at such code, we are confused. Not only a beginner would not understand it, even a skilled programmer would not understand such code. +Looking at such code, we would be confused. Not only would a beginner not understand it, but even an experienced programmer would not understand such code. -Do you wonder what such a function would actually look like inside? Where would it get the adders? It would probably get them *somehow* on its own, like this: +Are you wondering what such a function would actually look like inside? Where would it get the summands? It would probably *somehow* get them by itself, perhaps like this: ```php function addition(): float @@ -66,13 +66,13 @@ It turns out that there are hidden bindings to other functions (or static method Not This Way! ------------- -The design we have just been shown is the essence of many negative features: +The design we just showed is the essence of many negative features: -- the function signature pretended that it didn't need addends, which confused us +- the function signature pretended that it didn't need the summands, which confused us - we have no idea how to make the function calculate with two other numbers -- we had to look into the code to see where it takes the addends -- we discovered hidden bindings -- to fully understand, we need to explore these bindings as well +- we had to look at the code to find out where the summands came from +- we found hidden dependencies +- a full understanding requires examining these dependencies as well And is it even the job of the addition function to procure inputs? Of course it isn't. Its responsibility is only to add. @@ -93,20 +93,20 @@ Rule #1: Let It Be Passed to You The most important rule is: **all data that functions or classes need must be passed to them**. -Instead of inventing hidden mechanisms to help them somehow get to it themselves, simply pass the parameters in. You'll save the time it takes to come up with hidden way, which definitely won't improve your code. +Instead of inventing hidden ways for them to access the data themselves, simply pass the parameters. You will save time that would be spent inventing hidden paths that certainly won't improve your code. -If you follow this rule always and everywhere, you are on your way to code without hidden bindings. Towards code that is understandable not only to the author, but also to anyone who reads it afterwards. Where everything is understandable from the signatures of functions and classes and there is no need to search for hidden secrets in the implementation. +If you always and everywhere follow this rule, you are on your way to code without hidden dependencies. To code that is understandable not only to the author but also to anyone who reads it afterward. Where everything is understandable from the signatures of functions and classes, and there is no need to search for hidden secrets in the implementation. -This technique is expertly called **dependency injection**. And the data is called **dependencies.** But it's a simple parameter passing, nothing more. +This technique is professionally called **dependency injection**. And those data are called **dependencies**. It's just ordinary parameter passing, nothing more. .[note] -Please don't confuse dependency injection, which is a design pattern, with "dependency injection container", which is a tool, something completely different. We will discuss containers later. +Please do not confuse dependency injection, which is a design pattern, with a "dependency injection container", which is a tool, something diametrically different. We will deal with containers later. From Functions to Classes ------------------------- -And how do classes relate to this? A class is a more complex entity than a simple function, but rule #1 applies here as well. There are just [more ways to pass arguments |passing-dependencies]. For example, quite similar to the case of a function: +And how are classes related? A class is a more complex unit than a simple function, but rule #1 applies entirely here as well. There are just [more ways to pass arguments |passing-dependencies]. For example, quite similar to the case of a function: ```php class Math @@ -121,7 +121,7 @@ $math = new Math; echo $math->addition(23, 1); // 24 ``` -Or by using other methods, or directly by the constructor: +Or through other methods, or directly through the constructor: ```php class Addition @@ -149,9 +149,9 @@ Both examples are completely in compliance with dependency injection. Real-Life Examples ------------------ -In the real world, you won't write classes for adding numbers. Let's move on to real-world examples. +In the real world, you won't be writing classes for adding numbers. Let's move on to practical examples. -Let's have a class `Article` representing a blog article: +Let's have a `Article` class representing a blog post: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -The `save()` method saves the article in a database table. Implementing it using [Nette Database |database:] would be a piece of cake, if it weren't for one hitch: where should `Article` get the database connection, i.e. the `Nette\Database\Connection` class object ? +The `save()` method will save the article to a database table. Implementing it using [Nette Database |database:] will be a piece of cake, were it not for one hitch: where does `Article` get the database connection, i.e., an object of class `Nette\Database\Connection`? -It seems we have a lot of options. It can take it from somewhere in a static variable. Or inherit it from a class that will provide the database connection. Or take advantage of a [singleton |global-state#Singleton]. Or the so-called facades that are used in Laravel: +It seems we have plenty of options. It can take it from a static variable somewhere. Or inherit from a class that provides a database connection. Or take advantage of a [singleton |global-state#Singleton]. Or use so-called facades, which are used in Laravel: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ Great, we've solved the problem. Or have we? -Let's recall [#rule #1: Let It Be Passed to You]: all the dependencies the class needs must be passed to it. Because if we don't, and we break the rule, we've started down the path to dirty code full of hidden bindings, incomprehensibility, and the result will be an application that's a pain to maintain and develop. +Let's recall [#rule #1: Let It Be Passed to You]: all the dependencies the class needs must be passed to it. Because if we break the rule, we have embarked on a path to dirty code full of hidden dependencies, incomprehensibility, and the result will be an application that will be painful to maintain and develop. -The user of class `Article` has no idea where method `save()` stores the article. In a database table? In which one, production or development? And how can this be changed? +The user of the `Article` class has no idea where the `save()` method stores the article. In a database table? Which one, production or testing? And how can it be changed? -The user has to look at how the method `save()` is implemented to find the use of the method `DB::insert()`. So he has to search further to find out how this method procures a database connection. And hidden bindings can form quite a long chain. +The user has to look at how the `save()` method is implemented, and finds the use of the `DB::insert()` method. So, he has to search further to find out how this method obtains a database connection. And hidden dependencies can form quite a long chain. -Hidden bindings, Laravel facades, or static variables are never present in clean, well-designed code. In clean and well-designed code, arguments are passed: +In clean and well-designed code, there are never any hidden dependencies, Laravel facades, or static variables. In clean and well-designed code, arguments are passed: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Even more practical, as we'll see next, is to use a constructor: +An even more practical approach, as we will see later, will be through the constructor: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -If you're an experienced programmer, you might be thinking that `Article` shouldn't have a `save()` method at all, it should be a pure data component, and a separate repository should take care of storage. This makes sense. But that would take us well beyond the topic, which is dependency injection, and trying to give simple examples. +If you are an experienced programmer, you might think that `Article` should not have a `save()` method at all; it should represent a purely data component, and a separate repository should take care of saving. That makes sense. But that would take us far beyond the scope of the topic, which is dependency injection, and the effort to provide simple examples. -If you're going to write a class that requires a database to operate, for example, don't figure out where to get it from, but have it passed to you. Perhaps as a parameter to a constructor or other method. Declare dependencies. Expose them in the API of your class. You'll get understandable and predictable code. +If you write a class that requires, for example, a database for its operation, don't invent where to get it from, but have it passed. Either as a parameter of the constructor or another method. Admit dependencies. Admit them in the API of your class. You will get understandable and predictable code. -How about this class that logs error messages: +And what about this class, which logs error messages: ```php class Logger @@ -266,9 +266,9 @@ What do you think, did we follow [#rule #1: Let It Be Passed to You]? We didn't. -The key information, the log file directory, is *obtained* by the class from the constant. +The key information, i.e., the directory with the log file, is *obtained* by the class itself from the constant. -See the example usage: +Look at the example of usage: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Without knowing the implementation, could you answer the question where the messages are written? Would it suggest to you that the existence of the LOG_DIR constant is necessary for it to work? And would you be able to create a second instance that writes to a different location? Certainly not. +Without knowing the implementation, could you answer the question of where the messages are written? Would you guess that the existence of the `LOG_DIR` constant is necessary for its functioning? And could you create a second instance that would write to a different location? Certainly not. Let's fix the class: @@ -295,7 +295,7 @@ class Logger } ``` -The class is now much clearer, more configurable and therefore more useful. +The class is now much more understandable, configurable, and therefore more useful. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); But I Don’t Care! ----------------- -*"When I create an Article object and call save(), I don't want to deal with the database, I just want it to be saved to the one I have set in the configuration. "* +*"When I create an Article object and call save(), I don't want to deal with the database; I just want it to be saved in the one I have set in the configuration."* -*"When I use Logger, I just want the message to be written, and I don't want to deal with where. Let the global settings be used. "* +*"When I use Logger, I just want the message to be written, and I don't want to deal with where. Let the global settings be used."* -These are correct comments. +These are valid points. -As an example, let's take a class that sends out newsletters and logs how that went: +As an example, let's look at a class that sends newsletters and logs how it went: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -The enhanced `Logger`, which no longer uses the constant `LOG_DIR`, requires a file path in the constructor. How to solve this? The `NewsletterDistributor` class doesn't care where the messages are written, it just wants to write them. +The improved `Logger`, which no longer uses the `LOG_DIR` constant, requires specifying the file path in the constructor. How to solve this? The `NewsletterDistributor` class doesn't care where the messages are written; it just wants to write them. -The solution is again [#rule #1: Let It Be Passed to You]: pass all the data the class needs to it. +The solution is again [#rule #1: Let It Be Passed to You]: pass all the data that the class needs. -So we pass the path to the log to the constructor, which we then use to create the `Logger` object ? +So does that mean we pass the path to the log through the constructor, which we then use when creating the `Logger` object? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Not like that! Because the path **doesn't** belong to the data that the `NewsletterDistributor` class needs; it needs `Logger`. The class needs the logger itself. And that's what we'll pass on: +No, not like this! The path doesn't belong among the data that the `NewsletterDistributor` class needs; in fact, the `Logger` needs it. Do you see the difference? The `NewsletterDistributor` class needs the logger itself. So that's what we'll pass: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Now it is clear from the signatures of the `NewsletterDistributor` class that logging is part of its functionality. And the task of replacing the logger with another one, perhaps for testing purposes, is quite trivial. -Moreover, if the constructor of the `Logger` class is changed, it will have no effect on our class. +Now it is clear from the signatures of the `NewsletterDistributor` class that logging is also part of its functionality. And the task of exchanging the logger for another, perhaps for testing, is completely trivial. +Moreover, if the constructor of the `Logger` class changes, it will not affect our class. -Rule #2: Take What Is Yours ---------------------------- +Rule #2: Take What's Yours +-------------------------- -Don't be misled and don't let the parameters of your dependencies be passed to you. Pass on the dependencies directly. +Don't be misled and don't let yourself pass the dependencies of your dependencies. Just pass your own dependencies. -This will make code using other objects completely independent of changes to their constructors. Its API will be truer. And most importantly, it will be trivial to swap those dependencies for others. +Thanks to this, the code using other objects will be completely independent of changes in their constructors. Its API will be more truthful. And above all, it will be trivial to replace these dependencies with others. -A New Member of the Family --------------------------- +New Family Member +----------------- -The development team decided to create a second logger that writes to the database. So we create a class `DatabaseLogger`. So we have two classes, `Logger` and `DatabaseLogger`, one writes to a file, the other writes to a database ... don't you think there's something strange about that name? -Wouldn't it be better to rename `Logger` to `FileLogger`? Sure it would. +The development team decided to create a second logger that writes to the database. So we create a `DatabaseLogger` class. So we have two classes, `Logger` and `DatabaseLogger`, one writes to a file, the other to a database ... doesn't the naming seem strange to you? +Wouldn't it be better to rename `Logger` to `FileLogger`? Definitely yes. -But let's do it smart. We'll create an interface under the original name: +But let's do it smartly. We create an interface under the original name: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...that both loggers will implement: +... which both loggers will implement: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -And this way, nothing will need to be changed in the rest of the code where the logger is used. For example, the constructor of the `NewsletterDistributor` class will still be happy with requiring `Logger` as a parameter. And it will be up to us which instance we pass to it. +And because of this, there will be no need to change anything in the rest of the code where the logger is used. For example, the constructor of the `NewsletterDistributor` class will still be satisfied with requiring `Logger` as a parameter. And it will be up to us which instance we pass. -**This is why we never give interface names the suffix `Interface` or the `I` prefix.** Otherwise, it would be impossible to develop code this nicely. +**That's why we never add the `Interface` suffix or `I` prefix to interface names.** Otherwise, it would not be possible to develop the code so nicely. Houston, We Have a Problem -------------------------- -While in the whole application we can be happy with a single instance of a logger, whether file or database, and simply pass it wherever something is logged, it is quite different in the case of the `Article` class. In fact, we create instances of it as needed, possibly multiple times. How to deal with the database binding in its constructor? +While we can get by with a single instance of the logger, whether file-based or database-based, throughout the entire application and simply pass it wherever something is logged, it's quite different for the `Article` class. We create its instances as needed, even multiple times. How to deal with the database dependency in its constructor? -As an example, we can use a controller that should save an article to the database after submitting a form: +An example can be a controller that should save an article to the database after submitting a form: ```php class EditController extends Controller @@ -437,21 +437,21 @@ class EditController extends Controller } ``` -A possible solution is directly offered: have the database object passed by the constructor to `EditController` and use `$article = new Article($this->db)`. +A possible solution is obvious: pass the database object to the `EditController` constructor and use `$article = new Article($this->db)`. -As in the previous case with `Logger` and the file path, this is not the correct approach. The database is not a dependency of `EditController`, but of `Article`. So passing the database goes against [#rule #2: take what is yours]. When the constructor of the `Article` class is changed (a new parameter is added), the code in all the places where instances are created will also need to be modified. Ufff. +Just as in the previous case with `Logger` and the file path, this is not the right approach. The database is not a dependency of the `EditController`, but of the `Article`. Passing the database goes against [#rule #2: take what's yours]. If the `Article` class constructor changes (a new parameter is added), you will need to modify the code wherever instances are created. Ufff. -Houston, what are you suggesting? +Houston, what do you suggest? Rule #3: Let the Factory Handle It ---------------------------------- -By removing the hidden bindings and passing all dependencies as arguments, we get more configurable and flexible classes. And thus we need something else to create and configure those more flexible classes. We'll call it factories. +By eliminating hidden dependencies and passing all dependencies as arguments, we have gained more configurable and flexible classes. And therefore, we need something else to create and configure those more flexible classes for us. We will call it factories. The rule of thumb is: if a class has dependencies, leave the creation of their instances to the factory. -Factories are a smarter replacement for the `new` operator in the dependency injection world. +Factories are a smarter replacement for the `new` operator in the world of dependency injection. .[note] Please do not confuse with the *factory method* design pattern, which describes a specific way of using factories and is not related to this topic. @@ -460,7 +460,7 @@ Please do not confuse with the *factory method* design pattern, which describes Factory ------- -A factory is a method or class that produces and configures objects. We call `Article` producing class `ArticleFactory` and it could look like this: +A factory is a method or class that creates and configures objects. We will name the class producing `Article` as `ArticleFactory`, and it could look like this: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Its use in the controller would be as follows: +Its usage in the controller will be as follows: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -At this point, when the signature of the `Article` class constructor changes, the only part of the code that needs to respond is the `ArticleFactory` factory itself. Any other code that works with `Article` objects, such as `EditController`, will not be affected. +At this point, if the signature of the `Article` class constructor changes, the only part of the code that needs to react is the `ArticleFactory` itself. All other code working with `Article` objects, such as the `EditController`, will not be affected. -You may be tapping your forehead right now wondering if we helped ourselves at all. The amount of code has grown and the whole thing is starting to look suspiciously complicated. +You might be wondering if we have actually made things better. The amount of code has increased, and it all starts to look suspiciously complicated. -Don't worry, we will soon get to the Nette DI container. And it has a number of aces up its sleeve that will make building applications using dependency injection extremely simple. For example, instead of the `ArticleFactory` class, it will be enough to [write a simple interface |factory]: +Don't worry, soon we will get to the Nette DI container. And it has several tricks up its sleeve, which will greatly simplify building applications using dependency injection. For example, instead of the `ArticleFactory` class, you will only need to [write a simple interface |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -But we're getting ahead of ourselves, hang on :-) +But we're getting ahead of ourselves; please be patient :-) Summary ------- -At the beginning of this chapter, we promised to show you a way for designing clean code. Just give the classes +At the beginning of this chapter, we promised to show you a process for designing clean code. All it takes is for classes to: -- [the dependencies they need |#Rule #1: Let It Be Passed to You] -- [and not what they don't directly need |#Rule #2: Take What Is Yours] -- [and that objects with dependencies are best made in factories |#Rule #3: Let the Factory Handle it] +- [pass the dependencies they need |#Rule #1: Let It Be Passed to You] +- [conversely, not pass what they don't directly need |#Rule #2: Take What's Yours] +- [and that objects with dependencies are best created in factories |#Rule #3: Let the Factory Handle it] -It may not seem so at first glance, but these three rules have far-reaching implications. They lead to a radically different view of code design. Is it worth it? Programmers who have thrown out old habits and started consistently using dependency injection consider this a pivotal moment in their professional lives. It opened up a world of clear and sustainable applications. +At first glance, these three rules may not seem to have far-reaching consequences, but they lead to a radically different perspective on code design. Is it worth it? Developers who have abandoned old habits and started consistently using dependency injection consider this step a crucial moment in their professional lives. It has opened the world of clear and maintainable applications for them. -But what if the code doesn't consistently use dependency injection? What if it's built on static methods or singletons? Does it bring any problems? [It does, and it's very significant |global-state]. +But what if the code does not consistently use dependency injection? What if it relies on static methods or singletons? Does that cause any problems? [Yes, it does, and very fundamental ones |global-state]. diff --git a/dependency-injection/en/passing-dependencies.texy b/dependency-injection/en/passing-dependencies.texy index 2a168f2390..43912611d4 100644 --- a/dependency-injection/en/passing-dependencies.texy +++ b/dependency-injection/en/passing-dependencies.texy @@ -12,7 +12,7 @@ Arguments, or "dependencies" in DI terminology, can be passed to classes in the </div> -The first three methods apply in general in all object-oriented languages, the fourth is specific to Nette presenters, so it is discussed in [separate chapter |best-practices:inject-method-attribute]. We will now take a closer look at each of these options and show them with specific examples. +We will now illustrate the different variants with concrete examples. Constructor Injection @@ -21,17 +21,17 @@ Constructor Injection Dependencies are passed as arguments to the constructor when the object is created: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` This form is useful for mandatory dependencies that the class absolutely needs to function, as without them the instance cannot be created. @@ -40,10 +40,10 @@ Since PHP 8.0, we can use a shorter form of notation ([constructor property prom ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ As of PHP 8.1, a property can be marked with a flag `readonly` that declares tha ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService DI container passes dependencies to the constructor automatically using [autowiring]. Arguments that cannot be passed in this way (e.g. strings, numbers, booleans) [write in configuration |services#Arguments]. +Constructor Hell +---------------- + +The term *constructor hell* refers to a situation where a child inherits from a parent class whose constructor requires dependencies, and the child requires dependencies too. It must also take over and pass on the parent's dependencies: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +The problem occurs when we want to change the constructor of the `BaseClass` class, for example when a new dependency is added. Then we have to modify all the constructors of the children as well. Which makes such a modification hell. + +How to prevent this? The solution is to **prioritize composition over inheritance**. + +So let's design the code differently. We'll avoid abstract `Base*` classes. Instead of `MyClass` getting some functionality by inheriting from `BaseClass`, it will have that functionality passed as a dependency: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Setter Injection ================ -Dependencies are passed by calling a method that stores them in a private properties. The usual naming convention for these methods is of the form `set*()`, which is why they are called setters. +Dependencies are passed by calling a method that stores them in a private properties. The usual naming convention for these methods is of the form `set*()`, which is why they are called setters, but of course they can be called anything else. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` This method is useful for optional dependencies that are not necessary for the class function, since it is not guaranteed that the object will actually receive them (i.e., that the user will call the method). @@ -90,16 +150,16 @@ This method is useful for optional dependencies that are not necessary for the c At the same time, this method allows the setter to be called repeatedly to change the dependency. If this is not desirable, add a check to the method, or as of PHP 8.1, mark the property `$cache` with the `readonly` flag. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ The setter call is defined in the DI container configuration in [section setup | ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Property Injection Dependencies are passed directly to the property: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` This method is considered inappropriate because the property must be declared as `public`. Hence, we have no control over whether the passed dependency will actually be of the specified type (this was true before PHP 7.4) and we lose the ability to react to the newly assigned dependency with our own code, for example to prevent subsequent changes. At the same time, the property becomes part of the public interface of the class, which may not be desirable. @@ -137,12 +197,18 @@ The setting of the variable is defined in the DI container configuration in [sec ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Inject +====== + +While the previous three methods are generally valid in all object-oriented languages, injecting by method, annotation or *inject* attribute is specific to Nette presenters. They are discussed in [a separate chapter |best-practices:inject-method-attribute]. + + Which Way to Choose? ==================== diff --git a/dependency-injection/en/services.texy b/dependency-injection/en/services.texy index ff52ab02d2..b0ba6e9eaa 100644 --- a/dependency-injection/en/services.texy +++ b/dependency-injection/en/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Inject Mode =========== -The `inject: true` flag is used to activate the passing of dependencies via public variables with the [inject |best-practices:inject-method-attribute#Inject Annotations] annotation and the [inject*() |best-practices:inject-method-attribute#inject Methods] methods. +The `inject: true` flag is used to activate the passing of dependencies via public variables with the [inject |best-practices:inject-method-attribute#Inject Attributes] annotation and the [inject*() |best-practices:inject-method-attribute#inject Methods] methods. ```neon services: diff --git a/dependency-injection/es/@home.texy b/dependency-injection/es/@home.texy index 7c4366df5e..5a2577eb9d 100644 --- a/dependency-injection/es/@home.texy +++ b/dependency-injection/es/@home.texy @@ -5,8 +5,10 @@ Inyección de dependencia La inyección de dependencias es un patrón de diseño que cambiará radicalmente tu forma de ver el código y el desarrollo. Abre el camino a un mundo de aplicaciones sostenibles y de diseño limpio. - [¿Qué es la inyección de dependencia? |introduction] -- [¿Qué es un Contenedor DI? |container] +- [Estado Global y Singletons |global-state] - [Pasar Dependencias |passing-dependencies] +- [¿Qué es un Contenedor DI? |container] +- [Preguntas frecuentes|faq] Nette DI diff --git a/dependency-injection/es/@left-menu.texy b/dependency-injection/es/@left-menu.texy index 48d164c2f3..16a88609ad 100644 --- a/dependency-injection/es/@left-menu.texy +++ b/dependency-injection/es/@left-menu.texy @@ -1,8 +1,10 @@ Inyección de dependencia ************************ - [Qué es el DI? |introduction] -- [Qué es el Contenedor DI? |container] +- [Estado Global y Singletons |global-state] - [Pasar dependencias |passing-dependencies] +- [Qué es el Contenedor DI? |container] +- [Preguntas frecuentes|faq] Nette DI diff --git a/dependency-injection/es/faq.texy b/dependency-injection/es/faq.texy new file mode 100644 index 0000000000..ae21869cfa --- /dev/null +++ b/dependency-injection/es/faq.texy @@ -0,0 +1,112 @@ +Preguntas frecuentes sobre DI (FAQ) +*********************************** + + +¿Es DI otro nombre para IoC? .[#toc-is-di-another-name-for-ioc] +--------------------------------------------------------------- + +La *Inversion of Control* (IoC) es un principio centrado en la forma en que se ejecuta el código: si tu código inicia código externo o si tu código se integra en código externo, que luego lo llama. +IoC es un concepto amplio que incluye [eventos |nette:glossary#Events], el llamado [Principio de Hollywood |application:components#Hollywood style] y otros aspectos. +Las fábricas, que forman parte de [la Regla #3: Deja que la Fábrica se encargue |introduction#Rule #3: Let the Factory Handle It], y representan la inversión para el operador `new`, también son componentes de este concepto. + +La *Dependency Injection* (DI) trata sobre cómo un objeto sabe de otro objeto, es decir, la dependencia. Es un patrón de diseño que requiere el paso explícito de dependencias entre objetos. + +Por lo tanto, se puede decir que DI es una forma específica de IoC. Sin embargo, no todas las formas de IoC son adecuadas en términos de pureza del código. Por ejemplo, entre los anti-patrones, incluimos todas las técnicas que trabajan con [estado global |global state] o el llamado [Service Locator |#What is a Service Locator]. + + +¿Qué es un Service Locator? .[#toc-what-is-a-service-locator] +------------------------------------------------------------- + +Un Localizador de Servicios es una alternativa a la Inyección de Dependencias. Funciona creando un almacén central donde se registran todos los servicios o dependencias disponibles. Cuando un objeto necesita una dependencia, la solicita al Localizador de Servicios. + +Sin embargo, en comparación con la Inyección de Dependencias, pierde transparencia: las dependencias no se pasan directamente a los objetos y, por tanto, no son fácilmente identificables, lo que requiere examinar el código para descubrir y comprender todas las conexiones. Las pruebas también son más complicadas, ya que no podemos simplemente pasar objetos simulados a los objetos probados, sino que tenemos que pasar por el Localizador de Servicios. Además, el Localizador de Servicios altera el diseño del código, ya que los objetos individuales deben ser conscientes de su existencia, lo que difiere de la Inyección de Dependencias, donde los objetos no tienen conocimiento del contenedor DI. + + +¿Cuándo es mejor no utilizar DI? .[#toc-when-is-it-better-not-to-use-di] +------------------------------------------------------------------------ + +No se conocen dificultades asociadas al uso del patrón de diseño Inyección de Dependencias. Por el contrario, la obtención de dependencias desde ubicaciones globalmente accesibles conlleva una [serie de complicaciones |global-state], al igual que el uso de un Localizador de Servicios. +Por lo tanto, es aconsejable utilizar siempre DI. No se trata de un enfoque dogmático, sino que simplemente no se ha encontrado una alternativa mejor. + +Sin embargo, hay ciertas situaciones en las que no nos pasamos objetos y los obtenemos del espacio global. Por ejemplo, al depurar código y necesitar volcar el valor de una variable en un punto concreto del programa, medir la duración de cierta parte del programa o registrar un mensaje. +En estos casos, en los que se trata de acciones temporales que más tarde se eliminarán del código, es legítimo utilizar un dumper, cronómetro o logger accesible globalmente. Al fin y al cabo, estas herramientas no pertenecen al diseño del código. + + +¿Tiene sus inconvenientes el uso de DI? .[#toc-does-using-di-have-its-drawbacks] +-------------------------------------------------------------------------------- + +¿El uso de la inyección de dependencias implica alguna desventaja, como una mayor complejidad en la escritura de código o un peor rendimiento? ¿Qué perdemos cuando empezamos a escribir código de acuerdo con DI? + +DI no tiene ningún impacto en el rendimiento de la aplicación ni en los requisitos de memoria. El rendimiento del contenedor DI puede influir, pero en el caso de [Nette DI | nette-container], el contenedor se compila en PHP puro, por lo que su sobrecarga durante el tiempo de ejecución de la aplicación es esencialmente nula. + +Al escribir código, es necesario crear constructores que acepten dependencias. En el pasado, esto podía llevar mucho tiempo, pero gracias a los IDEs modernos y a la [promoción de propiedades de los |https://blog.nette.org/es/php-8-0-vision-completa-de-las-novedades#toc-constructor-property-promotion] constructores, ahora es cuestión de unos segundos. Las fábricas se pueden generar fácilmente utilizando Nette DI y un plugin de PhpStorm con sólo unos clics. +Por otra parte, no hay necesidad de escribir singletons y puntos de acceso estáticos. + +Se puede concluir que una aplicación correctamente diseñada usando DI no es ni más corta ni más larga comparada con una aplicación usando singletons. Las partes del código que trabajan con dependencias simplemente se extraen de las clases individuales y se trasladan a nuevas ubicaciones, es decir, el contenedor DI y las fábricas. + + +¿Cómo reescribir una aplicación heredada a DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] +------------------------------------------------------------------------------------------------ + +La migración de una aplicación heredada a Inyección de Dependencias puede ser un proceso difícil, especialmente para aplicaciones grandes y complejas. Es importante abordar este proceso de forma sistemática. + +- Al pasar a la inyección de dependencias, es importante que todos los miembros del equipo comprendan los principios y prácticas que se están utilizando. +- En primer lugar, realice un análisis de la aplicación existente para identificar los componentes clave y sus dependencias. Cree un plan sobre qué partes se refactorizarán y en qué orden. +- Implemente un contenedor DI o, mejor aún, utilice una biblioteca existente como Nette DI. +- Refactorizar gradualmente cada parte de la aplicación para utilizar la inyección de dependencias. Esto puede implicar modificar constructores o métodos para que acepten dependencias como parámetros. +- Modificar los lugares del código donde se crean los objetos de dependencia para que las dependencias sean inyectadas por el contenedor. Esto puede incluir el uso de fábricas. + +Recuerde que pasar a la inyección de dependencias es una inversión en la calidad del código y la sostenibilidad a largo plazo de la aplicación. Aunque puede ser difícil hacer estos cambios, el resultado debería ser un código más limpio, más modular y fácil de probar que esté listo para futuras extensiones y mantenimiento. + + +¿Por qué se prefiere la composición a la herencia? .[#toc-why-composition-is-preferred-over-inheritance] +-------------------------------------------------------------------------------------------------------- +Es preferible utilizar la composición en lugar de la herencia, ya que sirve al propósito de la reutilización del código sin tener que preocuparse por el efecto de goteo del cambio. Por lo tanto, proporciona un acoplamiento más laxo en el que no tenemos que preocuparnos de que el cambio de algún código provoque que otro código dependiente requiera un cambio. Un ejemplo típico es la situación identificada como [el infierno de los constructores |passing-dependencies#Constructor hell]. + + +¿Se puede utilizar Nette DI Container fuera de Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] +------------------------------------------------------------------------------------------------------------- + +Por supuesto. El Nette DI Container es parte de Nette, pero está diseñado como una librería independiente que puede ser usada independientemente de otras partes del framework. Simplemente instálelo usando Composer, cree un archivo de configuración definiendo sus servicios, y luego use unas pocas líneas de código PHP para crear el contenedor DI. +E inmediatamente puedes empezar a aprovechar las ventajas de la Inyección de Dependencias en tus proyectos. + +El capítulo [Nette DI Container |nette-container] describe cómo es un caso de uso específico, incluyendo el código. + + +¿Por qué la configuración está en archivos NEON? .[#toc-why-is-the-configuration-in-neon-files] +----------------------------------------------------------------------------------------------- + +NEON es un lenguaje de configuración sencillo y fácilmente legible desarrollado dentro de Nette para configurar aplicaciones, servicios y sus dependencias. Comparado con JSON o YAML, ofrece opciones mucho más intuitivas y flexibles para este propósito. En NEON, puedes describir de forma natural enlaces que no sería posible escribir en Symfony y YAML en absoluto o sólo a través de una descripción compleja. + + +¿El análisis de los archivos NEON ralentiza la aplicación? .[#toc-does-parsing-neon-files-slow-down-the-application] +-------------------------------------------------------------------------------------------------------------------- + +Aunque los archivos NEON se analizan muy rápidamente, este aspecto no importa realmente. La razón es que el análisis sintáctico de los archivos se produce sólo una vez durante el primer lanzamiento de la aplicación. Después de eso, el código contenedor DI se genera, se almacena en el disco, y se ejecuta para cada solicitud posterior sin necesidad de más análisis. + +Así es como funciona en un entorno de producción. Durante el desarrollo, los archivos NEON se analizan cada vez que cambia su contenido, lo que garantiza que el desarrollador siempre disponga de un contenedor DI actualizado. Como se mencionó anteriormente, el análisis sintáctico real es cuestión de un instante. + + +¿Cómo puedo acceder a los parámetros del archivo de configuración en mi clase? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Ten en cuenta la [Regla nº 1: Déjalo que te lo pasen |introduction#Rule #1: Let It Be Passed to You]. Si una clase requiere información de un fichero de configuración, no necesitamos averiguar cómo acceder a esa información; en su lugar, simplemente la pedimos - por ejemplo, a través del constructor de la clase. Y realizamos el paso en el archivo de configuración. + +En este ejemplo, `%myParameter%` es un marcador de posición para el valor del parámetro `myParameter`, que se pasará al constructor `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Si quieres pasar múltiples parámetros o usar autocableado, es útil [envolver los parámetros en un objeto |best-practices:passing-settings-to-presenters]. + + +¿Admite Nette la interfaz PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] +------------------------------------------------------------------------------------------------- + +Nette DI Container no soporta PSR-11 directamente. Sin embargo, si necesita interoperabilidad entre el Nette DI Container y las librerías o frameworks que esperan el PSR-11 Container Interface, puede crear un [adaptador simple |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] que sirva de puente entre el Nette DI Container y el PSR-11. diff --git a/dependency-injection/es/global-state.texy b/dependency-injection/es/global-state.texy new file mode 100644 index 0000000000..96c56d55fd --- /dev/null +++ b/dependency-injection/es/global-state.texy @@ -0,0 +1,312 @@ +Estado global y Singletons +************************** + +.[perex] +Advertencia: las siguientes construcciones son síntomas de un mal diseño de código: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` o `static::$var` + +¿Alguna de estas construcciones aparece en su código? Entonces tienes una oportunidad para mejorar. Puede que pienses que son construcciones comunes que vemos en soluciones de ejemplo de varias bibliotecas y frameworks. +Por desgracia, siguen siendo un claro indicador de un diseño deficiente. Tienen una cosa en común: el uso de estado global. + +Ahora, ciertamente no estamos hablando de algún tipo de pureza académica. El uso de estado global y singletons tiene efectos destructivos en la calidad del código. Su comportamiento se vuelve impredecible, reduce la productividad de los desarrolladores y obliga a las interfaces de las clases a mentir sobre sus verdaderas dependencias. Lo que confunde a los programadores. + +En este capítulo, mostraremos cómo esto es posible. + + +Interconexión global .[#toc-global-interlinking] +------------------------------------------------ + +El problema fundamental del estado global es que es accesible globalmente. Esto hace posible escribir en la base de datos a través del método global (estático) `DB::insert()`. +En un mundo ideal, un objeto sólo debería poder comunicarse con otros objetos que [le hayan sido pasados directamente |passing-dependencies]. +Si creo dos objetos `A` y `B` y nunca paso una referencia de `A` a `B`, entonces ni `A`, ni `B` pueden acceder al otro objeto o cambiar su estado. +Esta es una característica muy deseable del código. Es similar a tener una pila y una bombilla; la bombilla no se encenderá hasta que las conectes. + +Esto no es cierto para variables globales (estáticas) o singletons. El objeto `A` podría acceder *inalámbricamente* al objeto `C` y modificarlo sin pasar ninguna referencia, llamando a `C::changeSomething()`. +Si el objeto `B` también toma el objeto global `C`, entonces `A` y `B` pueden interactuar entre sí a través de `C`. + +El uso de variables globales introduce una nueva forma de acoplamiento *inalámbrico* en el sistema que no es visible desde el exterior. +Crea una cortina de humo que complica la comprensión y el uso del código. +Los desarrolladores deben leer cada línea del código fuente para comprender realmente las dependencias. En lugar de limitarse a familiarizarse con la interfaz de las clases. +Además, es un acoplamiento completamente innecesario. + +.[note] +En términos de comportamiento, no hay diferencia entre una variable global y una estática. Son igualmente perjudiciales. + + +La espeluznante acción a distancia .[#toc-the-spooky-action-at-a-distance] +-------------------------------------------------------------------------- + +"Espeluznante acción a distancia": así llamó Albert Einstein en 1935 a un fenómeno de la física cuántica que le puso los pelos de punta. +Se trata del entrelazamiento cuántico, cuya peculiaridad es que cuando se mide información sobre una partícula, afecta inmediatamente a otra, aunque estén a millones de años luz de distancia. +Lo que aparentemente viola la ley fundamental del universo de que nada puede viajar más rápido que la luz. + +En el mundo del software, podemos llamar "espeluznante acción a distancia" a una situación en la que ejecutamos un proceso que creemos aislado (porque no le hemos pasado ninguna referencia), pero se producen interacciones inesperadas y cambios de estado en lugares distantes del sistema de los que no hemos informado al objeto. Esto sólo puede ocurrir a través del estado global. + +Imagina que te unes a un equipo de desarrollo de un proyecto que tiene una base de código grande y madura. Tu nuevo jefe te pide que implementes una nueva función y, como buen desarrollador, empiezas escribiendo una prueba. Pero como eres nuevo en el proyecto, haces muchas pruebas exploratorias del tipo "qué pasa si llamo a este método". Y tratas de escribir la siguiente prueba: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // su número de tarjeta + $cc->charge(100); +} +``` + +Ejecutas el código, tal vez varias veces, y después de un tiempo notas notificaciones en tu teléfono del banco que cada vez que lo ejecutas, $100 fueron cargados a tu tarjeta de crédito 🤦‍♂️ + +¿Cómo diablos pudo la prueba causar un cargo real? No es fácil operar con tarjeta de crédito. Tienes que interactuar con un servicio web de terceros, tienes que conocer la URL de ese servicio web, tienes que iniciar sesión, etc. +Ninguna de estas informaciones se incluye en la prueba. Peor aún, ni siquiera sabes dónde está presente esta información y, por lo tanto, cómo simular las dependencias externas para que cada ejecución no suponga un nuevo cargo de 100 dólares. Y como nuevo desarrollador, ¿cómo ibas a saber que lo que estabas a punto de hacer te llevaría a ser 100 dólares más pobre? + +¡Eso es una acción espeluznante a distancia! + +No te queda más remedio que escarbar en un montón de código fuente, preguntando a colegas más veteranos y experimentados, hasta que entiendes cómo funcionan las conexiones en el proyecto. +Esto se debe al hecho de que al mirar la interfaz de la clase `CreditCard`, no puedes determinar el estado global que necesita ser inicializado. Incluso mirando el código fuente de la clase no le dirá qué método de inicialización para llamar. Como mucho, puedes encontrar la variable global a la que se accede e intentar adivinar cómo inicializarla a partir de ahí. + +Las clases de un proyecto así son mentirosas patológicas. La tarjeta de pago finge que puedes simplemente instanciarla y llamar al método `charge()`. Sin embargo, secretamente interactúa con otra clase, `PaymentGateway`. Incluso su interfaz dice que se puede inicializar de forma independiente, pero en realidad extrae credenciales de algún archivo de configuración y demás. +Está claro para los desarrolladores que escribieron este código que `CreditCard` necesita a `PaymentGateway`. Ellos escribieron el código de esta manera. Pero para cualquiera que sea nuevo en el proyecto, esto es un completo misterio y dificulta el aprendizaje. + +¿Cómo arreglar la situación? Fácil. **Deja que la API declare las dependencias.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Observa cómo las relaciones dentro del código son repentinamente obvias. Al declarar que el método `charge()` necesita `PaymentGateway`, no tienes que preguntar a nadie cómo el código es interdependiente. Sabes que tienes que crear una instancia del mismo, y cuando intentas hacerlo, te encuentras con el hecho de que tienes que suministrar parámetros de acceso. Sin ellos, el código ni siquiera se ejecutaría. + +Y lo más importante, ahora puedes simular la pasarela de pago para que no te cobren 100 dólares cada vez que ejecutes una prueba. + +El estado global hace que tus objetos puedan acceder secretamente a cosas que no están declaradas en sus APIs, y como resultado hace que tus APIs sean mentirosas patológicas. + +Puede que no lo hayas pensado así antes, pero siempre que usas estado global, estás creando canales secretos de comunicación inalámbrica. La espeluznante acción remota obliga a los desarrolladores a leer cada línea de código para entender las posibles interacciones, reduce la productividad de los desarrolladores y confunde a los nuevos miembros del equipo. +Si eres tú quien ha creado el código, conoces las dependencias reales, pero cualquiera que venga después no tiene ni idea. + +No escribas código que utilice estado global, prefiere pasar dependencias. Es decir, inyección de dependencias. + + +La fragilidad del Estado mundial .[#toc-brittleness-of-the-global-state] +------------------------------------------------------------------------ + +En código que utiliza estado global y singletons, nunca se sabe con certeza cuándo y por quién ha cambiado ese estado. Este riesgo ya está presente en la inicialización. El siguiente código se supone que debe crear una conexión a la base de datos e inicializar la pasarela de pago, pero sigue lanzando una excepción y encontrar la causa es extremadamente tedioso: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Hay que revisar el código en detalle para descubrir que el objeto `PaymentGateway` accede a otros objetos de forma inalámbrica, algunos de los cuales requieren una conexión a la base de datos. Así, debe inicializar la base de datos antes de `PaymentGateway`. Sin embargo, la cortina de humo del estado global te lo oculta. ¿Cuánto tiempo ahorrarías si la API de cada clase no mintiera y declarara sus dependencias? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Un problema similar surge cuando se utiliza el acceso global a una conexión de base de datos: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Cuando se llama al método `save()`, no se sabe con certeza si ya se ha creado una conexión a la base de datos y quién es el responsable de crearla. Por ejemplo, si quisiéramos cambiar la conexión a la base de datos sobre la marcha, quizás con fines de prueba, probablemente tendríamos que crear métodos adicionales como `DB::reconnect(...)` o `DB::reconnectForTest()`. + +Veamos un ejemplo: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +¿Dónde podemos estar seguros de que se está utilizando realmente la base de datos de prueba cuando se llama a `$article->save()`? ¿Qué pasaría si el método `Foo::doSomething()` cambiara la conexión global a la base de datos? Para averiguarlo, tendríamos que examinar el código fuente de la clase `Foo` y probablemente de muchas otras clases. Sin embargo, este enfoque sólo proporcionaría una respuesta a corto plazo, ya que la situación podría cambiar en el futuro. + +¿Y si trasladamos la conexión a la base de datos a una variable estática dentro de la clase `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Esto no cambia nada en absoluto. El problema es un estado global y no importa en qué clase se esconda. En este caso, como en el anterior, no tenemos ni idea de en qué base de datos se está escribiendo cuando se llama al método `$article->save()`. Cualquiera en el extremo distante de la aplicación podría cambiar la base de datos en cualquier momento usando `Article::setDb()`. Bajo nuestras manos. + +El estado global hace que nuestra aplicación sea **extremadamente frágil**. + +Sin embargo, hay una forma sencilla de lidiar con este problema. Basta con hacer que la API declare dependencias para garantizar una funcionalidad adecuada. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Este enfoque elimina la preocupación de cambios ocultos e inesperados en las conexiones a la base de datos. Ahora estamos seguros de dónde se almacena el artículo y ninguna modificación de código dentro de otra clase no relacionada puede cambiar la situación nunca más. El código ya no es frágil, sino estable. + +No escribas código que use estado global, prefiere pasar dependencias. Por lo tanto, la inyección de dependencia. + + +Singleton .[#toc-singleton] +--------------------------- + +Singleton es un patrón de diseño que, por [definición |https://en.wikipedia.org/wiki/Singleton_pattern] de la famosa publicación Gang of Four, restringe una clase a una única instancia y ofrece acceso global a la misma. La implementación de este patrón suele parecerse al siguiente código: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // y otros métodos que realizan las funciones de la clase +} +``` + +Desafortunadamente, el singleton introduce estado global en la aplicación. Y como hemos demostrado anteriormente, el estado global no es deseable. Por eso el singleton se considera un antipatrón. + +No utilices singletons en tu código y sustitúyelos por otros mecanismos. Realmente no necesitas singletons. Sin embargo, si necesitas garantizar la existencia de una única instancia de una clase para toda la aplicación, déjalo en manos del [contenedor DI |container]. +Por lo tanto, cree un singleton de aplicación, o servicio. Esto evitará que la clase proporcione su propia unicidad (es decir, no tendrá un método `getInstance()` y una variable estática) y sólo realizará sus funciones. Así, dejará de violar el principio de responsabilidad única. + + +Estado global frente a pruebas .[#toc-global-state-versus-tests] +---------------------------------------------------------------- + +Cuando escribimos pruebas, asumimos que cada prueba es una unidad aislada y que ningún estado externo entra en ella. Y ningún estado sale de las pruebas. Cuando una prueba se completa, cualquier estado asociado con la prueba debe ser eliminado automáticamente por el recolector de basura. Esto hace que las pruebas estén aisladas. Por lo tanto, podemos ejecutar las pruebas en cualquier orden. + +Sin embargo, si hay estados/singletons globales, todas estas suposiciones se vienen abajo. Un estado puede entrar y salir de una prueba. De repente, el orden de las pruebas puede ser importante. + +Para probar los singletons, los desarrolladores a menudo tienen que relajar sus propiedades, tal vez permitiendo que una instancia sea sustituida por otra. Estas soluciones son, en el mejor de los casos, trucos que producen un código difícil de mantener y comprender. Cualquier prueba o método `tearDown()` que afecte a cualquier estado global debe deshacer esos cambios. + +El estado global es el mayor dolor de cabeza en las pruebas unitarias. + +¿Cómo arreglar la situación? Fácil. No escribas código que utilice singletons, prefiere pasar dependencias. Es decir, inyección de dependencias. + + +Constantes globales .[#toc-global-constants] +-------------------------------------------- + +El estado global no se limita al uso de singletons y variables estáticas, sino que también puede aplicarse a las constantes globales. + +Las constantes cuyo valor no nos proporciona ninguna información nueva (`M_PI`) o útil (`PREG_BACKTRACK_LIMIT_ERROR`) están claramente bien. +Por el contrario, las constantes que sirven para pasar información de forma *inalámbrica* dentro del código no son más que una dependencia oculta. Como `LOG_FILE` en el siguiente ejemplo. +Utilizar la constante `FILE_APPEND` es perfectamente correcto. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +En este caso, debemos declarar el parámetro en el constructor de la clase `Foo` para que forme parte de la API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Ahora podemos pasar información sobre la ruta al archivo de registro y cambiarla fácilmente según sea necesario, lo que facilita las pruebas y el mantenimiento del código. + + +Funciones globales y métodos estáticos .[#toc-global-functions-and-static-methods] +---------------------------------------------------------------------------------- + +Queremos enfatizar que el uso de métodos estáticos y funciones globales no es problemático en sí mismo. Hemos explicado lo inapropiado de usar `DB::insert()` y métodos similares, pero siempre ha sido una cuestión de estado global almacenado en una variable estática. El método `DB::insert()` requiere la existencia de una variable estática porque almacena la conexión a la base de datos. Sin esta variable, sería imposible implementar el método. + +El uso de métodos y funciones estáticas deterministas, como `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` y muchos otros, es perfectamente coherente con la inyección de dependencias. Estas funciones siempre devuelven los mismos resultados a partir de los mismos parámetros de entrada y, por lo tanto, son predecibles. No utilizan ningún estado global. + +Sin embargo, hay funciones en PHP que no son deterministas. Estas incluyen, por ejemplo, la función `htmlspecialchars()`. Su tercer parámetro, `$encoding`, si no se especifica, toma por defecto el valor de la opción de configuración `ini_get('default_charset')`. Por lo tanto, se recomienda especificar siempre este parámetro para evitar un posible comportamiento impredecible de la función. Nette lo hace sistemáticamente. + +Algunas funciones, como `strtolower()`, `strtoupper()`, y similares, han tenido un comportamiento no determinista en el pasado reciente y han dependido del parámetro `setlocale()`. Esto causaba muchas complicaciones, sobre todo cuando se trabajaba con el idioma turco. +Esto se debe a que el idioma turco distingue entre mayúsculas y minúsculas `I` con y sin punto. Así que `strtolower('I')` devolvía el carácter `ı` y `strtoupper('i')` devolvía el carácter `İ`, lo que provocaba en las aplicaciones una serie de misteriosos errores. +Sin embargo, este problema se solucionó en la versión 8.2 de PHP y las funciones ya no dependen de la configuración regional. + +Este es un buen ejemplo de cómo el estado global ha plagado a miles de desarrolladores en todo el mundo. La solución fue reemplazarlo con inyección de dependencia. + + +¿Cuándo es posible utilizar el Estado Global? .[#toc-when-is-it-possible-to-use-global-state] +--------------------------------------------------------------------------------------------- + +Hay ciertas situaciones específicas en las que es posible utilizar el estado global. Por ejemplo, cuando se depura código y se necesita volcar el valor de una variable o medir la duración de una parte concreta del programa. En estos casos, que se refieren a acciones temporales que más tarde se eliminarán del código, es legítimo utilizar un dumper o un cronómetro disponibles globalmente. Estas herramientas no forman parte del diseño del código. + +Otro ejemplo son las funciones para trabajar con expresiones regulares `preg_*`, que almacenan internamente expresiones regulares compiladas en una caché estática en memoria. Cuando se llama a la misma expresión regular varias veces en distintas partes del código, sólo se compila una vez. La caché ahorra rendimiento y además es completamente invisible para el usuario, por lo que este uso puede considerarse legítimo. + + +Resumen .[#toc-summary] +----------------------- + +Hemos demostrado por qué tiene sentido + +1) Eliminar todas las variables estáticas del código +2) Declarar dependencias +3) Y utilizar la inyección de dependencias + +Cuando contemples el diseño del código, ten en cuenta que cada `static $foo` representa un problema. Para que tu código sea un entorno respetuoso con DI, es esencial erradicar por completo el estado global y sustituirlo por la inyección de dependencias. + +Durante este proceso, puede que descubras que necesitas dividir una clase porque tiene más de una responsabilidad. No te preocupes por ello; esfuérzate por el principio de una sola responsabilidad. + +*Me gustaría dar las gracias a Miško Hevery, cuyos artículos como [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] forman la base de este capítulo.* diff --git a/dependency-injection/es/introduction.texy b/dependency-injection/es/introduction.texy index c6448c383b..4531425f04 100644 --- a/dependency-injection/es/introduction.texy +++ b/dependency-injection/es/introduction.texy @@ -100,7 +100,7 @@ Si sigues esta regla siempre y en todas partes, estarás en camino hacia un cód Esta técnica se denomina de forma experta **inyección de dependencias**. Y los datos se llaman **dependencias.** Pero es un simple paso de parámetros, nada más. .[note] -Por favor, no confundas la inyección de dependencias, que es un patrón de diseño, con el "contenedor de inyección de dependencias", que es una herramienta, algo completamente diferente. Hablaremos de contenedores más adelante. +Por favor, no confundas la inyección de dependencias, que es un patrón de diseño, con un "contenedor de inyección de dependencias", que es una herramienta, algo diametralmente distinto. Nos ocuparemos de los contenedores más adelante. De las funciones a las clases .[#toc-from-functions-to-classes] @@ -245,7 +245,7 @@ class Article ``` .[note] -Si eres un programador experimentado, podrías estar pensando que `Article` no debería tener un método `save()` en absoluto, debería ser un componente de datos puro, y un repositorio separado debería encargarse del almacenamiento. Esto tiene sentido. Pero eso nos llevaría mucho más allá del tema, que es la inyección de dependencias, e intentar dar ejemplos sencillos. +Si usted es un programador experimentado, podría pensar que `Article` no debería tener un método `save()` en absoluto; debería representar un componente puramente de datos, y un repositorio separado debería encargarse de guardar. Eso tiene sentido. Pero eso nos llevaría mucho más allá del alcance del tema, que es la inyección de dependencias, y del esfuerzo por proporcionar ejemplos sencillos. Si vas a escribir una clase que requiere una base de datos para funcionar, por ejemplo, no te imagines de dónde obtenerla, sino que te la pasen. Quizá como parámetro de un constructor u otro método. Declara las dependencias. Exponlas en la API de tu clase. Conseguirás un código comprensible y predecible. @@ -306,9 +306,9 @@ $logger->log('The temperature is 15 °C'); Pero no me importa! .[#toc-but-i-don-t-care] -------------------------------------------- -*"Cuando creo un objeto Article y llamo a save(), no quiero tratar con la base de datos, sólo quiero que se guarde en la que he establecido en la configuración. "* +*"Cuando creo un objeto Article y llamo a save(), no quiero tratar con la base de datos, sólo quiero que se guarde en la que he establecido en la configuración."* -*"Cuando uso Logger, sólo quiero que se escriba el mensaje, y no quiero ocuparme de dónde. Que se use la configuración global. "* +*"Cuando uso Logger, sólo quiero que se escriba el mensaje, y no quiero ocuparme de dónde. Que se use la configuración global."* Estos comentarios son correctos. @@ -379,21 +379,21 @@ Ahora está claro por las firmas de la clase `NewsletterDistributor` que el regi Además, si se cambia el constructor de la clase `Logger`, no tendrá ningún efecto en nuestra clase. -Regla nº 2: Toma lo que es tuyo .[#toc-rule-2-take-what-is-yours] ------------------------------------------------------------------ +Regla nº 2: Toma lo que es tuyo .[#toc-rule-2-take-what-s-yours] +---------------------------------------------------------------- -No te dejes engañar y no dejes que te pasen los parámetros de tus dependencias. Pasa las dependencias directamente. +No te dejes engañar y no te dejes pasar las dependencias de tus dependencias. Sólo pasa tus propias dependencias. Esto hará que el código que utilice otros objetos sea completamente independiente de los cambios en sus constructores. Su API será más verdadera. Y lo más importante, será trivial cambiar esas dependencias por otras. -Un nuevo miembro de la familia .[#toc-a-new-member-of-the-family] ------------------------------------------------------------------ +Nuevo miembro de la familia .[#toc-new-family-member] +----------------------------------------------------- -El equipo de desarrollo decidió crear un segundo registrador que escribe en la base de datos. Así que creamos una clase `DatabaseLogger`. Así que tenemos dos clases, `Logger` y `DatabaseLogger`, una escribe en un archivo, la otra escribe en una base de datos ... ¿no crees que hay algo extraño en ese nombre? -¿No sería mejor renombrar `Logger` a `FileLogger`? Claro que sí. +El equipo de desarrollo decidió crear un segundo registrador que escribe en la base de datos. Así que creamos una clase `DatabaseLogger`. Así que tenemos dos clases, `Logger` y `DatabaseLogger`, una escribe en un archivo, la otra en una base de datos ... ¿no te parece extraña la nomenclatura? +¿No sería mejor renombrar `Logger` a `FileLogger`? Desde luego que sí. -Pero hagámoslo de forma inteligente. Crearemos una interfaz con el nombre original: +Pero hagámoslo de forma inteligente. Creamos una interfaz con el nombre original: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...que ambos registradores implementarán: +... que ambos registradores implementarán: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -Y de esta forma, no será necesario cambiar nada en el resto del código donde se utilice el logger. Por ejemplo, el constructor de la clase `NewsletterDistributor` seguirá contentándose con requerir `Logger` como parámetro. Y dependerá de nosotros qué instancia le pasemos. +Y debido a esto, no habrá necesidad de cambiar nada en el resto del código donde se utilice el logger. Por ejemplo, el constructor de la clase `NewsletterDistributor` seguirá conformándose con requerir `Logger` como parámetro. Y dependerá de nosotros qué instancia le pasemos. -**Esta es la razón por la que nunca damos a los nombres de interfaz el sufijo `Interface` o el prefijo `I`.** De lo contrario, sería imposible desarrollar código de esta forma tan agradable. +**Por eso nunca añadimos el sufijo `Interface` o el prefijo `I` a los nombres de las interfaces.** De lo contrario, no sería posible desarrollar el código de forma tan agradable. Houston, tenemos un problema .[#toc-houston-we-have-a-problem] -------------------------------------------------------------- -Mientras que en toda la aplicación podemos estar contentos con una única instancia de un logger, ya sea de fichero o de base de datos, y simplemente pasarlo allí donde se registre algo, es bastante diferente en el caso de la clase `Article`. De hecho, creamos instancias de ella según sea necesario, posiblemente varias veces. ¿Cómo tratar el enlace a la base de datos en su constructor? +Mientras que podemos arreglárnoslas con una única instancia del registrador, ya sea basado en archivos o en bases de datos, a lo largo de toda la aplicación y simplemente pasarlo allí donde se registre algo, es bastante diferente para la clase `Article`. Creamos sus instancias según sea necesario, incluso varias veces. ¿Cómo tratar la dependencia de la base de datos en su constructor? -Como ejemplo, podemos utilizar un controlador que debe guardar un artículo en la base de datos después de enviar un formulario: +Un ejemplo puede ser un controlador que debe guardar un artículo en la base de datos después de enviar un formulario: ```php class EditController extends Controller @@ -437,9 +437,9 @@ class EditController extends Controller } ``` -Se ofrece directamente una posible solución: hacer que el objeto de base de datos sea pasado por el constructor a `EditController` y utilizar `$article = new Article($this->db)`. +Una posible solución es obvia: pasar el objeto de base de datos al constructor `EditController` y utilizar `$article = new Article($this->db)`. -Como en el caso anterior con `Logger` y la ruta del archivo, este no es el enfoque correcto. La base de datos no es una dependencia de `EditController`, sino de `Article`. Así que pasar la base de datos va en contra de [la regla nº 2: toma lo que es tuyo |#rule #2: take what is yours]. Cuando se cambia el constructor de la clase `Article` (se añade un nuevo parámetro), también habrá que modificar el código en todos los lugares donde se crean instancias. Ufff. +Al igual que en el caso anterior con `Logger` y la ruta del archivo, este no es el enfoque correcto. La base de datos no es una dependencia de `EditController`, sino de `Article`. Pasar la base de datos va en contra de [la regla #2: toma lo que es tuyo |#rule #2: take what's yours]. Si el constructor de la clase `Article` cambia (se añade un nuevo parámetro), tendrás que modificar el código allí donde se creen instancias. Ufff. Houston, ¿qué sugieres? @@ -447,7 +447,7 @@ Houston, ¿qué sugieres? Regla nº 3: Deje que se encargue la fábrica .[#toc-rule-3-let-the-factory-handle-it] ------------------------------------------------------------------------------------ -Al eliminar los enlaces ocultos y pasar todas las dependencias como argumentos, obtenemos clases más configurables y flexibles. Y por lo tanto necesitamos algo más para crear y configurar esas clases más flexibles. Lo llamaremos fábricas. +Al eliminar las dependencias ocultas y pasar todas las dependencias como argumentos, hemos conseguido clases más configurables y flexibles. Y por lo tanto, necesitamos algo más para crear y configurar esas clases más flexibles para nosotros. Lo llamaremos fábricas. La regla general es: si una clase tiene dependencias, deja la creación de sus instancias a la fábrica. @@ -519,10 +519,10 @@ Resumen .[#toc-summary] Al principio de este capítulo, prometimos mostrarte una forma de diseñar código limpio. Basta con dar a las clases -- [las dependencias que necesitan |#Rule #1: Let It Be Passed to You] -- [y no lo que no necesitan directamente |#Rule #2: Take What Is Yours] -- [y que los objetos con dependencias se hacen mejor en fábricas |#Rule #3: Let the Factory Handle it] +- [pasen las dependencias que necesitan|#Rule #1: Let It Be Passed to You] +- [a la inversa, no pasen lo que no necesitan directamente |#Rule #2: Take What's Yours] +- [y que los objetos con dependencias se creen mejor en fábricas |#Rule #3: Let the Factory Handle it] -Puede no parecerlo a primera vista, pero estas tres reglas tienen implicaciones de gran alcance. Conducen a una visión radicalmente distinta del diseño de código. ¿Merece la pena? Los programadores que han desechado viejos hábitos y han empezado a utilizar sistemáticamente la inyección de dependencias lo consideran un momento crucial en su vida profesional. Les ha abierto un mundo de aplicaciones claras y sostenibles. +A primera vista, puede que estas tres reglas no parezcan tener consecuencias de gran alcance, pero conducen a una perspectiva radicalmente distinta del diseño de código. ¿Merece la pena? Los desarrolladores que han abandonado viejos hábitos y han empezado a utilizar sistemáticamente la inyección de dependencias consideran este paso un momento crucial en su vida profesional. Les ha abierto el mundo de las aplicaciones claras y mantenibles. -Pero, ¿qué ocurre si el código no utiliza sistemáticamente la inyección de dependencias? ¿Y si se basa en métodos estáticos o singletons? ¿Supone algún problema? [Lo hace, y es muy |global-state] significativo. +Pero, ¿qué ocurre si el código no utiliza sistemáticamente la inyección de dependencias? ¿Y si se basa en métodos estáticos o singletons? ¿Causa problemas? [Sí, los hay, y muy fundamentales |global-state]. diff --git a/dependency-injection/es/passing-dependencies.texy b/dependency-injection/es/passing-dependencies.texy index 8e57560170..05588ddfdc 100644 --- a/dependency-injection/es/passing-dependencies.texy +++ b/dependency-injection/es/passing-dependencies.texy @@ -12,7 +12,7 @@ Los argumentos, o "dependencias" en la terminología DI, se pueden pasar a las c </div> -Los tres primeros métodos se aplican en general en todos los lenguajes orientados a objetos, el cuarto es específico de los presentadores Nette, por lo que se trata en [capítulo aparte |best-practices:inject-method-attribute]. A continuación veremos más detenidamente cada una de estas opciones y las mostraremos con ejemplos concretos. +A continuación ilustraremos las distintas variantes con ejemplos concretos. Inyección de constructor .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Inyección de constructor .[#toc-constructor-injection] Las dependencias se pasan como argumentos al constructor cuando se crea el objeto: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Esta forma es útil para dependencias obligatorias que la clase necesita absolutamente para funcionar, ya que sin ellas la instancia no puede ser creada. @@ -40,10 +40,10 @@ Desde PHP 8.0, podemos usar una forma más corta de notación que es funcionalme ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ A partir de PHP 8.1, una propiedad puede ser marcada con una bandera `readonly` ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService El contenedor DI pasa dependencias al constructor automáticamente usando [autowiring]. Argumentos que no se pueden pasar de esta forma (por ejemplo cadenas, números, booleanos) [escribir en configuración |services#Arguments]. +Infierno constructor .[#toc-constructor-hell] +--------------------------------------------- + +El término *infierno de constructores* se refiere a una situación en la que un hijo hereda de una clase padre cuyo constructor requiere dependencias, y el hijo también requiere dependencias. También debe asumir y pasar las dependencias del padre: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +El problema surge cuando queremos cambiar el constructor de la clase `BaseClass`, por ejemplo cuando se añade una nueva dependencia. Entonces tenemos que modificar también todos los constructores de los hijos. Lo que convierte tal modificación en un infierno. + +¿Cómo evitarlo? La solución es **priorizar la composición sobre la herencia**. + +Así que diseñemos el código de otra manera. Evitaremos las clases abstractas `Base*`. En lugar de que `MyClass` obtenga alguna funcionalidad heredando de `BaseClass`, tendrá esa funcionalidad pasada como una dependencia: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Inyección de Setter .[#toc-setter-injection] ============================================ -Las dependencias se pasan llamando a un método que las almacena en una propiedad privada. La convención de nomenclatura habitual para estos métodos es de la forma `set*()`, por lo que se denominan setters. +Las dependencias se pasan llamando a un método que las almacena en una propiedad privada. La convención de nomenclatura habitual para estos métodos es de la forma `set*()`, que es por lo que se llaman setters, pero por supuesto pueden llamarse de cualquier otra forma. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Este método es útil para dependencias opcionales que no son necesarias para la función de la clase, ya que no se garantiza que el objeto las reciba realmente (es decir, que el usuario llame al método). @@ -90,16 +150,16 @@ Este método es útil para dependencias opcionales que no son necesarias para la Al mismo tiempo, este método permite llamar al setter repetidamente para cambiar la dependencia. Si esto no es deseable, añada una comprobación al método, o a partir de PHP 8.1, marque la propiedad `$cache` con la bandera `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('La dependencia ya se ha establecido'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ La llamada al setter se define en la configuración del contenedor DI en [secci ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Inyección de propiedades .[#toc-property-injection] Las dependencias se pasan directamente a la propiedad: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Este método se considera inapropiado porque la propiedad debe declararse como `public`. Por lo tanto, no tenemos control sobre si la dependencia pasada será realmente del tipo especificado (esto era cierto antes de PHP 7.4) y perdemos la capacidad de reaccionar a la dependencia recién asignada con nuestro propio código, por ejemplo para evitar cambios posteriores. Al mismo tiempo, la propiedad pasa a formar parte de la interfaz pública de la clase, lo que puede no ser deseable. @@ -137,12 +197,18 @@ La configuración de la variable se define en la configuración del contenedor D ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Inyectar .[#toc-inject] +======================= + +Mientras que los tres métodos anteriores son generalmente válidos en todos los lenguajes orientados a objetos, la inyección por método, anotación o atributo *inject* es específica de los presentadores Nette. Se tratan en [un capítulo aparte |best-practices:inject-method-attribute]. + + ¿Qué camino elegir? .[#toc-which-way-to-choose] =============================================== diff --git a/dependency-injection/es/services.texy b/dependency-injection/es/services.texy index 4052578b5c..2f6af40b80 100644 --- a/dependency-injection/es/services.texy +++ b/dependency-injection/es/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Modo de inyección .[#toc-inject-mode] ===================================== -El indicador `inject: true` se utiliza para activar el paso de dependencias a través de variables públicas con la anotación [inject |best-practices:inject-method-attribute#Inject Annotations] y los métodos [inject*() |best-practices:inject-method-attribute#inject Methods]. +El indicador `inject: true` se utiliza para activar el paso de dependencias a través de variables públicas con la anotación [inject |best-practices:inject-method-attribute#Inject Attributes] y los métodos [inject*() |best-practices:inject-method-attribute#inject Methods]. ```neon services: diff --git a/dependency-injection/fr/@home.texy b/dependency-injection/fr/@home.texy index 492b692599..a5a7bfffec 100644 --- a/dependency-injection/fr/@home.texy +++ b/dependency-injection/fr/@home.texy @@ -5,8 +5,10 @@ Injection de dépendances L'injection de dépendances est un modèle de conception qui va fondamentalement changer votre façon de voir le code et le développement. Il ouvre la voie à un monde d'applications propres et durables. - [Qu'est-ce que l'injection de dépendances ? |introduction] -- [Qu'est-ce qu'un conteneur DI ? |container] +- [État global et singletons |global-state] - [Passage des dépendances |passing-dependencies] +- [Qu'est-ce qu'un conteneur DI ? |container] +- [Questions Fréquemment Posées |faq] Nette DI diff --git a/dependency-injection/fr/@left-menu.texy b/dependency-injection/fr/@left-menu.texy index 00b687e6b6..a140c8dbc4 100644 --- a/dependency-injection/fr/@left-menu.texy +++ b/dependency-injection/fr/@left-menu.texy @@ -1,8 +1,10 @@ Injection de dépendances ************************ - [Qu'est-ce que DI ? |introduction] -- [Qu'est-ce que DI Container ? |container] +- [Etat global et singletons |global-state] - [Passer les dépendances |passing-dependencies] +- [Qu'est-ce que DI Container ? |container] +- [Questions fréquemment posées |faq] Nette DI diff --git a/dependency-injection/fr/faq.texy b/dependency-injection/fr/faq.texy new file mode 100644 index 0000000000..6a16f99f8a --- /dev/null +++ b/dependency-injection/fr/faq.texy @@ -0,0 +1,112 @@ +Questions fréquemment posées sur DI (FAQ) +***************************************** + + +DI est-il un autre nom pour IoC ? .[#toc-is-di-another-name-for-ioc] +-------------------------------------------------------------------- + +*Inversion of Control* (IoC) est un principe axé sur la manière dont le code est exécuté - que votre code initie un code externe ou que votre code soit intégré dans un code externe, qui l'appelle ensuite. +L'inversion de contrôle est un concept large qui inclut les [événements |nette:glossary#Events], le [principe |application:components#Hollywood style] dit [d'Hollywood |application:components#Hollywood style] et d'autres aspects. +Les usines, qui font partie de la [règle n° 3 : laisser l'usine s'en charger |introduction#Rule #3: Let the Factory Handle It], et représentent l'inversion de l'opérateur `new`, sont également des composantes de ce concept. + +*Dependency Injection* (DI) concerne la manière dont un objet connaît un autre objet, c'est-à-dire la dépendance. Il s'agit d'un modèle de conception qui exige le passage explicite des dépendances entre les objets. + +On peut donc dire que l'injection de dépendances est une forme spécifique d'IoC. Cependant, toutes les formes de contrôle interne ne conviennent pas en termes de pureté du code. Par exemple, parmi les anti-modèles, nous incluons toutes les techniques qui travaillent avec l'[état global |global state] ou ce que l'on appelle le [Service Locator |#What is a Service Locator]. + + +Qu'est-ce qu'un Service Locator ? .[#toc-what-is-a-service-locator] +------------------------------------------------------------------- + +Un localisateur de services est une alternative à l'injection de dépendances. Il fonctionne en créant un stockage central où tous les services ou dépendances disponibles sont enregistrés. Lorsqu'un objet a besoin d'une dépendance, il la demande au localisateur de services. + +Cependant, par rapport à l'injection de dépendances, elle perd en transparence : les dépendances ne sont pas directement transmises aux objets et ne sont donc pas facilement identifiables, ce qui nécessite d'examiner le code pour découvrir et comprendre toutes les connexions. Les tests sont également plus compliqués, car nous ne pouvons pas simplement passer des objets fictifs aux objets testés, mais nous devons passer par le Service Locator. En outre, le Service Locator perturbe la conception du code, car les objets individuels doivent être conscients de son existence, ce qui diffère de l'injection de dépendances, où les objets n'ont aucune connaissance du conteneur DI. + + +Quand est-il préférable de ne pas utiliser l'injection de dépendance ? .[#toc-when-is-it-better-not-to-use-di] +-------------------------------------------------------------------------------------------------------------- + +Il n'y a pas de difficultés connues liées à l'utilisation du modèle de conception de l'injection de dépendances. Au contraire, l'obtention de dépendances à partir d'emplacements accessibles globalement entraîne un [certain nombre de complications |global-state], tout comme l'utilisation d'un localisateur de services. +Il est donc conseillé de toujours utiliser l'injection de dépendances. Il ne s'agit pas d'une approche dogmatique, mais simplement de l'absence d'une meilleure alternative. + +Cependant, dans certaines situations, il n'est pas nécessaire de se transmettre des objets et de les obtenir dans l'espace global. Par exemple, lors du débogage d'un code, il est nécessaire de vidanger la valeur d'une variable à un moment précis du programme, de mesurer la durée d'une certaine partie du programme ou d'enregistrer un message. +Dans de tels cas, lorsqu'il s'agit d'actions temporaires qui seront ultérieurement supprimées du code, il est légitime d'utiliser un dumper, un chronomètre ou un logger accessible globalement. Ces outils, après tout, ne font pas partie de la conception du code. + + +L'utilisation de l'ID présente-t-elle des inconvénients ? .[#toc-does-using-di-have-its-drawbacks] +-------------------------------------------------------------------------------------------------- + +L'utilisation de l'injection de dépendances présente-t-elle des inconvénients, tels qu'une complexité accrue de l'écriture du code ou une baisse des performances ? Que perdons-nous lorsque nous commençons à écrire du code conformément à l'injection de dépendances ? + +L'injection de dépendances n'a aucun impact sur les performances de l'application ou sur les besoins en mémoire. La performance du conteneur DI peut jouer un rôle, mais dans le cas de [Nette DI | nette-container], le conteneur est compilé en PHP pur, de sorte que sa surcharge pendant l'exécution de l'application est pratiquement nulle. + +Lors de l'écriture du code, il est nécessaire de créer des constructeurs qui acceptent les dépendances. Dans le passé, cela pouvait prendre du temps, mais grâce aux IDE modernes et à la [promotion des propriétés des constructeurs |https://blog.nette.org/fr/php-8-0-apercu-complet-des-nouveautes#toc-constructor-property-promotion], c'est maintenant une question de quelques secondes. Les usines peuvent être facilement générées à l'aide de Nette DI et d'un plugin PhpStorm en quelques clics. +D'autre part, il n'est pas nécessaire d'écrire des singletons et des points d'accès statiques. + +On peut conclure qu'une application bien conçue utilisant DI n'est ni plus courte ni plus longue qu'une application utilisant des singletons. Les parties du code travaillant avec les dépendances sont simplement extraites des classes individuelles et déplacées vers de nouveaux emplacements, c'est-à-dire le conteneur DI et les usines. + + +Comment réécrire une application existante en DI ? .[#toc-how-to-rewrite-a-legacy-application-to-di] +---------------------------------------------------------------------------------------------------- + +La migration d'une application existante vers l'injection de dépendances peut être un processus difficile, en particulier pour les applications complexes et de grande taille. Il est important d'aborder ce processus de manière systématique. + +- Lors du passage à l'injection de dépendances, il est important que tous les membres de l'équipe comprennent les principes et les pratiques utilisés. +- Tout d'abord, effectuez une analyse de l'application existante afin d'identifier les composants clés et leurs dépendances. Créez un plan pour savoir quelles parties seront remaniées et dans quel ordre. +- Implémenter un conteneur DI ou, mieux encore, utiliser une bibliothèque existante telle que Nette DI. +- Refondre progressivement chaque partie de l'application pour utiliser l'injection de dépendances. Cela peut impliquer de modifier les constructeurs ou les méthodes pour qu'ils acceptent les dépendances en tant que paramètres. +- Modifier les endroits du code où les objets de dépendance sont créés afin que les dépendances soient injectées par le conteneur. Cela peut inclure l'utilisation d'usines. + +N'oubliez pas que le passage à l'injection de dépendances est un investissement dans la qualité du code et la viabilité à long terme de l'application. Bien qu'il puisse être difficile d'effectuer ces changements, le résultat devrait être un code plus propre, plus modulaire et plus facilement testable, prêt pour les extensions et la maintenance futures. + + +Pourquoi la composition est-elle préférable à l'héritage ? .[#toc-why-composition-is-preferred-over-inheritance] +---------------------------------------------------------------------------------------------------------------- +Il est préférable d'utiliser la composition plutôt que l'héritage car elle permet de réutiliser le code sans avoir à s'inquiéter de l'effet de ruissellement des changements. Elle permet donc un couplage plus lâche, sans avoir à se soucier du fait que la modification d'un code entraîne la modification d'un autre code dépendant. Un exemple typique est la situation identifiée comme l'[enfer des constructeurs |passing-dependencies#Constructor hell]. + + +Nette DI Container peut-il être utilisé en dehors de Nette ? .[#toc-can-nette-di-container-be-used-outside-of-nette] +-------------------------------------------------------------------------------------------------------------------- + +Absolument. Le Nette DI Container fait partie de Nette, mais il est conçu comme une bibliothèque autonome qui peut être utilisée indépendamment des autres parties du framework. Il suffit de l'installer à l'aide de Composer, de créer un fichier de configuration définissant vos services, puis d'utiliser quelques lignes de code PHP pour créer le conteneur DI. +Et vous pouvez immédiatement commencer à tirer parti de l'injection de dépendances dans vos projets. + +Le chapitre sur le [conteneur DI de Nette |nette-container] décrit un cas d'utilisation spécifique, y compris le code. + + +Pourquoi la configuration se fait-elle dans des fichiers NEON ? .[#toc-why-is-the-configuration-in-neon-files] +-------------------------------------------------------------------------------------------------------------- + +NEON est un langage de configuration simple et facilement lisible développé au sein de Nette pour configurer les applications, les services et leurs dépendances. Comparé à JSON ou YAML, il offre des options beaucoup plus intuitives et flexibles à cet effet. En NEON, vous pouvez naturellement décrire des bindings qu'il ne serait pas possible d'écrire en Symfony & YAML, soit du tout, soit seulement au travers d'une description complexe. + + +L'analyse des fichiers NEON ralentit-elle l'application ? .[#toc-does-parsing-neon-files-slow-down-the-application] +------------------------------------------------------------------------------------------------------------------- + +Bien que les fichiers NEON soient analysés très rapidement, cet aspect n'a pas vraiment d'importance. En effet, l'analyse des fichiers n'a lieu qu'une seule fois lors du premier lancement de l'application. Ensuite, le code du conteneur DI est généré, stocké sur le disque et exécuté pour chaque demande ultérieure sans qu'il soit nécessaire de procéder à une nouvelle analyse. + +C'est ainsi que cela fonctionne dans un environnement de production. Pendant le développement, les fichiers NEON sont analysés chaque fois que leur contenu est modifié, ce qui garantit que le développeur dispose toujours d'un conteneur DI à jour. Comme nous l'avons déjà mentionné, l'analyse proprement dite se fait en un instant. + + +Comment puis-je accéder aux paramètres du fichier de configuration dans ma classe ? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +Gardez à l'esprit la [Règle n° 1 : Laissez-le vous être transmis |introduction#Rule #1: Let It Be Passed to You]. Si une classe nécessite des informations provenant d'un fichier de configuration, il n'est pas nécessaire de déterminer comment accéder à ces informations ; au lieu de cela, nous les demandons simplement - par exemple, par l'intermédiaire du constructeur de la classe. Et nous effectuons le transfert dans le fichier de configuration. + +Dans cet exemple, `%myParameter%` est un espace réservé pour la valeur du paramètre `myParameter`, qui sera transmis au constructeur `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Si vous souhaitez passer plusieurs paramètres ou utiliser le câblage automatique, il est utile d'[envelopper les paramètres dans un objet |best-practices:passing-settings-to-presenters]. + + +Nette prend-il en charge l'interface PSR-11 Container ? .[#toc-does-nette-support-psr-11-container-interface] +------------------------------------------------------------------------------------------------------------- + +Nette DI Container ne supporte pas directement PSR-11. Cependant, si vous avez besoin d'interopérabilité entre le Nette DI Container et des bibliothèques ou des frameworks qui attendent l'interface de conteneur PSR-11, vous pouvez créer un [simple adaptateur |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] pour servir de pont entre le Nette DI Container et PSR-11. diff --git a/dependency-injection/fr/global-state.texy b/dependency-injection/fr/global-state.texy new file mode 100644 index 0000000000..4adc68cf63 --- /dev/null +++ b/dependency-injection/fr/global-state.texy @@ -0,0 +1,312 @@ +État global et singletons +************************* + +.[perex] +Avertissement : les constructions suivantes sont des symptômes d'une mauvaise conception du code : + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` ou `static::$var` + +L'une de ces constructions apparaît-elle dans votre code ? Alors vous avez la possibilité de vous améliorer. Vous pensez peut-être qu'il s'agit de constructions courantes que nous voyons dans des exemples de solutions de diverses bibliothèques et de divers frameworks. +Malheureusement, elles sont toujours un indicateur clair d'une mauvaise conception. Elles ont une chose en commun : l'utilisation d'un état global. + +Il ne s'agit certainement pas d'une sorte de pureté académique. L'utilisation de l'état global et des singletons a des effets destructeurs sur la qualité du code. Son comportement devient imprévisible, réduit la productivité des développeurs et oblige les interfaces de classe à mentir sur leurs véritables dépendances. Ce qui désoriente les programmeurs. + +Dans ce chapitre, nous allons montrer comment cela est possible. + + +Interconnexion globale .[#toc-global-interlinking] +-------------------------------------------------- + +Le problème fondamental de l'état global est qu'il est accessible globalement. Il est donc possible d'écrire dans la base de données via la méthode globale (statique) `DB::insert()`. +Dans un monde idéal, un objet ne devrait pouvoir communiquer qu'avec les autres objets qui lui ont été [directement transmis |passing-dependencies]. +Si je crée deux objets `A` et `B` et que je ne passe jamais une référence de `A` à `B`, ni `A`, ni `B` ne peuvent accéder à l'autre objet ou modifier son état. +Il s'agit d'une caractéristique très souhaitable du code. C'est comme si vous aviez une batterie et une ampoule ; l'ampoule ne s'allume pas tant que vous ne les connectez pas ensemble. + +Ce n'est pas vrai pour les variables globales (statiques) ou les singletons. L'objet `A` pourrait accéder *sans fil* à l'objet `C` et le modifier sans passer de référence, en appelant `C::changeSomething()`. +Si l'objet `B` s'empare également de la variable globale `C`, alors `A` et `B` peuvent interagir entre eux via `C`. + +L'utilisation de variables globales introduit dans le système une nouvelle forme de couplage *sans fil* qui n'est pas visible de l'extérieur. +Elle crée un écran de fumée qui complique la compréhension et l'utilisation du code. +Les développeurs doivent lire chaque ligne du code source pour vraiment comprendre les dépendances. Au lieu de se contenter de se familiariser avec l'interface des classes. +De plus, il s'agit d'un couplage totalement inutile. + +.[note] +En termes de comportement, il n'y a pas de différence entre une variable globale et une variable statique. Elles sont tout aussi nuisibles l'une que l'autre. + + +L'action sinistre à distance .[#toc-the-spooky-action-at-a-distance] +-------------------------------------------------------------------- + +"L'action sinistre à distance" : c'est ainsi qu'Albert Einstein a appelé un phénomène de la physique quantique qui lui a donné la chair de poule en 1935. +Il s'agit de l'intrication quantique, dont la particularité est que lorsque vous mesurez une information sur une particule, vous affectez immédiatement une autre particule, même si elles sont distantes de millions d'années-lumière. +Ce qui semble violer la loi fondamentale de l'univers selon laquelle rien ne peut voyager plus vite que la lumière. + +Dans le monde des logiciels, nous pouvons parler d'une "action étrange à distance", une situation dans laquelle nous exécutons un processus que nous pensons isolé (parce que nous ne lui avons transmis aucune référence), mais des interactions inattendues et des changements d'état se produisent dans des endroits éloignés du système dont nous n'avons pas parlé à l'objet. Cela ne peut se produire qu'à travers l'état global. + +Imaginez que vous rejoignez une équipe de développement de projet qui dispose d'une base de code importante et mature. Votre nouveau chef vous demande d'implémenter une nouvelle fonctionnalité et, comme tout bon développeur, vous commencez par écrire un test. Mais comme vous êtes nouveau dans le projet, vous faites beaucoup de tests exploratoires du type "que se passe-t-il si j'appelle cette méthode". Et vous essayez d'écrire le test suivant : + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // votre numéro de carte + $cc->charge(100); +} +``` + +Vous exécutez le code, peut-être plusieurs fois, et après un certain temps, vous remarquez sur votre téléphone des notifications de la banque indiquant qu'à chaque fois que vous l'exécutez, 100 $ ont été débités sur votre carte de crédit 🤦‍♂️. + +Comment diable le test a-t-il pu provoquer un débit réel ? Il n'est pas facile d'opérer avec une carte de crédit. Vous devez interagir avec un service web tiers, vous devez connaître l'URL de ce service web, vous devez vous connecter, et ainsi de suite. +Aucune de ces informations n'est incluse dans le test. Pire encore, vous ne savez même pas où ces informations sont présentes, et donc comment simuler les dépendances externes pour que chaque exécution n'entraîne pas une nouvelle facturation de 100 dollars. Et en tant que nouveau développeur, comment étiez-vous censé savoir que ce que vous étiez sur le point de faire vous ferait perdre 100 dollars ? + +C'est une action effrayante à distance ! + +Vous n'avez pas d'autre choix que de fouiller dans une grande quantité de code source, en demandant à des collègues plus anciens et plus expérimentés, jusqu'à ce que vous compreniez comment fonctionnent les connexions dans le projet. +Cela est dû au fait qu'en regardant l'interface de la classe `CreditCard`, vous ne pouvez pas déterminer l'état global qui doit être initialisé. Même en regardant le code source de la classe, vous ne pourrez pas savoir quelle méthode d'initialisation appeler. Au mieux, vous pouvez trouver la variable globale à laquelle on accède et essayer de deviner comment l'initialiser à partir de là. + +Les classes d'un tel projet sont des menteurs pathologiques. La carte de paiement prétend que vous pouvez simplement l'instancier et appeler la méthode `charge()`. Cependant, elle interagit secrètement avec une autre classe, `PaymentGateway`. Même son interface indique qu'elle peut être initialisée de manière indépendante, mais en réalité, elle tire les informations d'identification d'un fichier de configuration et ainsi de suite. +Il est clair pour les développeurs qui ont écrit ce code que `CreditCard` a besoin de `PaymentGateway`. Ils ont écrit le code de cette façon. Mais pour toute personne nouvelle dans le projet, c'est un mystère complet et cela entrave l'apprentissage. + +Comment remédier à cette situation ? Facile. **Laissez l'API déclarer les dépendances.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Remarquez comment les relations au sein du code deviennent soudainement évidentes. En déclarant que la méthode `charge()` a besoin de `PaymentGateway`, vous n'avez pas besoin de demander à qui que ce soit comment le code est interdépendant. Vous savez que vous devez créer une instance de cette méthode, et lorsque vous essayez de le faire, vous vous heurtez au fait que vous devez fournir des paramètres d'accès. Sans eux, le code ne fonctionnerait même pas. + +Et surtout, vous pouvez maintenant simuler la passerelle de paiement afin de ne pas être facturé 100 $ à chaque fois que vous exécutez un test. + +L'état global permet à vos objets d'accéder secrètement à des éléments qui ne sont pas déclarés dans leurs API et, par conséquent, fait de vos API des menteurs pathologiques. + +Vous n'y avez peut-être jamais pensé de cette façon, mais chaque fois que vous utilisez l'état global, vous créez des canaux de communication sans fil secrets. Les actions à distance effrayantes obligent les développeurs à lire chaque ligne de code pour comprendre les interactions potentielles, réduisent la productivité des développeurs et désorientent les nouveaux membres de l'équipe. +Si vous êtes celui qui a créé le code, vous connaissez les véritables dépendances, mais tous ceux qui viennent après vous ne savent rien. + +N'écrivez pas de code qui utilise l'état global, préférez passer les dépendances. C'est l'injection de dépendances. + + +La fragilité de l'État mondial .[#toc-brittleness-of-the-global-state] +---------------------------------------------------------------------- + +Dans le code qui utilise un état global et des singletons, on ne sait jamais avec certitude quand et par qui cet état a été modifié. Ce risque est déjà présent à l'initialisation. Le code suivant est censé créer une connexion à une base de données et initialiser la passerelle de paiement, mais il continue à lancer une exception et il est extrêmement fastidieux d'en trouver la cause : + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Vous devez parcourir le code en détail pour découvrir que l'objet `PaymentGateway` accède à d'autres objets sans fil, dont certains nécessitent une connexion à une base de données. Ainsi, vous devez initialiser la base de données avant `PaymentGateway`. Cependant, l'écran de fumée de l'état global vous cache cela. Combien de temps gagneriez-vous si l'API de chaque classe ne mentait pas et ne déclarait pas ses dépendances ? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Un problème similaire se pose lors de l'utilisation de l'accès global à une connexion de base de données : + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Lorsque l'on appelle la méthode `save()`, on ne sait pas si une connexion à la base de données a déjà été créée et qui est responsable de sa création. Par exemple, si nous voulions modifier la connexion à la base de données à la volée, peut-être à des fins de test, nous devrions probablement créer des méthodes supplémentaires telles que `DB::reconnect(...)` ou `DB::reconnectForTest()`. + +Prenons un exemple : + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Comment pouvons-nous être sûrs que la base de données de test est réellement utilisée lors de l'appel à `$article->save()`? Et si la méthode `Foo::doSomething()` modifiait la connexion globale à la base de données ? Pour le savoir, nous devrions examiner le code source de la classe `Foo` et probablement de nombreuses autres classes. Toutefois, cette approche ne fournirait qu'une réponse à court terme, car la situation pourrait changer à l'avenir. + +Et si nous déplacions la connexion à la base de données vers une variable statique à l'intérieur de la classe `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Cela ne change rien du tout. Le problème est un état global et la classe dans laquelle il se cache n'a pas d'importance. Dans ce cas, comme dans le précédent, nous n'avons aucune idée de la base de données qui est écrite lorsque la méthode `$article->save()` est appelée. N'importe qui à l'extrémité distante de l'application pourrait changer la base de données à tout moment en utilisant `Article::setDb()`. Sous nos yeux. + +L'état global rend notre application **extrêmement fragile**. + +Cependant, il existe un moyen simple de résoudre ce problème. Il suffit de faire en sorte que l'API déclare des dépendances pour garantir une bonne fonctionnalité. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Cette approche élimine le souci de modifications cachées et inattendues des connexions à la base de données. Maintenant, nous sommes sûrs de l'endroit où l'article est stocké et aucune modification du code dans une autre classe sans rapport ne peut plus changer la situation. Le code n'est plus fragile, mais stable. + +N'écrivez pas de code qui utilise l'état global, préférez le passage des dépendances. Ainsi, l'injection de dépendances. + + +Singleton .[#toc-singleton] +--------------------------- + +Le singleton est un modèle de conception qui, selon la [définition de |https://en.wikipedia.org/wiki/Singleton_pattern] la célèbre publication Gang of Four, limite une classe à une seule instance et lui offre un accès global. L'implémentation de ce patron ressemble généralement au code suivant : + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // et d'autres méthodes qui exécutent les fonctions de la classe +} +``` + +Malheureusement, le singleton introduit un état global dans l'application. Et comme nous l'avons montré ci-dessus, l'état global n'est pas souhaitable. C'est pourquoi le singleton est considéré comme un anti-modèle. + +N'utilisez pas les singletons dans votre code et remplacez-les par d'autres mécanismes. Vous n'avez vraiment pas besoin de singletons. Toutefois, si vous devez garantir l'existence d'une seule instance d'une classe pour l'ensemble de l'application, confiez cette tâche au [conteneur DI |container]. +Ainsi, créez un singleton d'application, ou service. Cela empêchera la classe de fournir sa propre unicité (c'est-à-dire qu'elle n'aura pas de méthode `getInstance()` et de variable statique) et n'exécutera que ses fonctions. Ainsi, elle cessera de violer le principe de responsabilité unique. + + +État global versus tests .[#toc-global-state-versus-tests] +---------------------------------------------------------- + +Lorsque nous écrivons des tests, nous supposons que chaque test est une unité isolée et qu'aucun état externe n'y entre. Et aucun état ne quitte les tests. Lorsqu'un test se termine, tout état associé au test devrait être supprimé automatiquement par le ramasse-miettes. Cela rend les tests isolés. Par conséquent, nous pouvons exécuter les tests dans n'importe quel ordre. + +Cependant, si des états/singletons globaux sont présents, toutes ces belles hypothèses s'effondrent. Un état peut entrer et sortir d'un test. Soudainement, l'ordre des tests peut avoir de l'importance. + +Pour tester les singletons, les développeurs doivent souvent assouplir leurs propriétés, peut-être en permettant à une instance d'être remplacée par une autre. De telles solutions sont, au mieux, des bidouillages qui produisent un code difficile à maintenir et à comprendre. Tout test ou méthode `tearDown()` qui affecte un état global doit annuler ces changements. + +L'état global est le plus gros casse-tête des tests unitaires ! + +Comment remédier à cette situation ? Facile. N'écrivez pas de code qui utilise des singletons, préférez passer des dépendances. C'est-à-dire l'injection de dépendances. + + +Constantes globales .[#toc-global-constants] +-------------------------------------------- + +L'état global ne se limite pas à l'utilisation des singletons et des variables statiques, mais peut également s'appliquer aux constantes globales. + +Les constantes dont la valeur ne nous fournit aucune information nouvelle (`M_PI`) ou utile (`PREG_BACKTRACK_LIMIT_ERROR`) sont clairement OK. +À l'inverse, les constantes qui servent à faire passer des informations *sans fil* à l'intérieur du code ne sont rien d'autre qu'une dépendance cachée. Comme `LOG_FILE` dans l'exemple suivant. +L'utilisation de la constante `FILE_APPEND` est parfaitement correcte. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Dans ce cas, nous devons déclarer le paramètre dans le constructeur de la classe `Foo` pour qu'il fasse partie de l'API : + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Nous pouvons maintenant transmettre des informations sur le chemin d'accès au fichier de journalisation et le modifier facilement si nécessaire, ce qui facilite les tests et la maintenance du code. + + +Fonctions globales et méthodes statiques .[#toc-global-functions-and-static-methods] +------------------------------------------------------------------------------------ + +Nous tenons à souligner que l'utilisation de méthodes statiques et de fonctions globales n'est pas problématique en soi. Nous avons expliqué le caractère inapproprié de l'utilisation de `DB::insert()` et de méthodes similaires, mais il s'agissait toujours d'un état global stocké dans une variable statique. La méthode `DB::insert()` nécessite l'existence d'une variable statique car elle stocke la connexion à la base de données. Sans cette variable, il serait impossible de mettre en œuvre la méthode. + +L'utilisation de méthodes et de fonctions statiques déterministes, telles que `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` et bien d'autres, est parfaitement compatible avec l'injection de dépendances. Ces fonctions renvoient toujours les mêmes résultats à partir des mêmes paramètres d'entrée et sont donc prévisibles. Elles n'utilisent pas d'état global. + +Cependant, il existe des fonctions en PHP qui ne sont pas déterministes. C'est le cas, par exemple, de la fonction `htmlspecialchars()`. Son troisième paramètre, `$encoding`, s'il n'est pas spécifié, prend par défaut la valeur de l'option de configuration `ini_get('default_charset')`. Il est donc recommandé de toujours spécifier ce paramètre pour éviter un éventuel comportement imprévisible de la fonction. C'est ce que fait systématiquement Nette. + +Certaines fonctions, telles que `strtolower()`, `strtoupper()`, et autres, ont eu un comportement non déterministe dans un passé récent et ont dépendu du paramètre `setlocale()`. Cela a causé de nombreuses complications, le plus souvent en travaillant avec la langue turque. +En effet, la langue turque fait la distinction entre les majuscules et les minuscules `I` avec et sans point. Ainsi, `strtolower('I')` renvoyait le caractère `ı` et `strtoupper('i')` renvoyait le caractère `İ`, ce qui conduisait les applications à provoquer un certain nombre d'erreurs mystérieuses. +Toutefois, ce problème a été corrigé dans la version 8.2 de PHP et les fonctions ne dépendent plus de la locale. + +C'est un bel exemple de la manière dont l'état global a tourmenté des milliers de développeurs dans le monde. La solution a été de le remplacer par l'injection de dépendances. + + +Quand est-il possible d'utiliser l'état global ? .[#toc-when-is-it-possible-to-use-global-state] +------------------------------------------------------------------------------------------------ + +Dans certaines situations spécifiques, il est possible d'utiliser l'état global. Par exemple, lorsque vous déboguez un code et que vous avez besoin d'extraire la valeur d'une variable ou de mesurer la durée d'une partie spécifique du programme. Dans de tels cas, qui concernent des actions temporaires qui seront ultérieurement supprimées du code, il est légitime d'utiliser un dumper ou un chronomètre disponible globalement. Ces outils ne font pas partie de la conception du code. + +Un autre exemple est celui des fonctions permettant de travailler avec des expressions régulières `preg_*`, qui stockent en interne les expressions régulières compilées dans un cache statique en mémoire. Lorsque vous appelez la même expression régulière plusieurs fois dans différentes parties du code, elle n'est compilée qu'une seule fois. Le cache permet de gagner en performance et est totalement invisible pour l'utilisateur, de sorte qu'une telle utilisation peut être considérée comme légitime. + + +Résumé .[#toc-summary] +---------------------- + +Nous avons montré pourquoi il est logique + +1) Supprimer toutes les variables statiques du code +2) Déclarer les dépendances +3) Et utiliser l'injection de dépendances + +Lorsque vous envisagez la conception d'un code, gardez à l'esprit que chaque `static $foo` représente un problème. Pour que votre code soit un environnement respectueux de l'injection de dépendances, il est essentiel d'éradiquer complètement l'état global et de le remplacer par l'injection de dépendances. + +Au cours de ce processus, vous constaterez peut-être que vous devez diviser une classe parce qu'elle a plus d'une responsabilité. Ne vous en préoccupez pas ; efforcez-vous de respecter le principe de la responsabilité unique. + +*Je tiens à remercier Miško Hevery, dont les articles tels que [Flaw : Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] constituent la base de ce chapitre*. diff --git a/dependency-injection/fr/introduction.texy b/dependency-injection/fr/introduction.texy index e197b4783e..03adbd78c3 100644 --- a/dependency-injection/fr/introduction.texy +++ b/dependency-injection/fr/introduction.texy @@ -2,17 +2,17 @@ Qu'est-ce que l'injection de dépendances ? ****************************************** .[perex] -Ce chapitre vous présente les pratiques de programmation de base que vous devez suivre lors de l'écriture de toute application. Ce sont les bases nécessaires pour écrire un code propre, compréhensible et maintenable. +Ce chapitre vous présente les pratiques de programmation de base que vous devez suivre lorsque vous écrivez une application. Il s'agit des principes fondamentaux nécessaires à l'écriture d'un code propre, compréhensible et facile à maintenir. -Si vous apprenez et suivez ces règles, Nette sera là pour vous à chaque étape du processus. Elle s'occupera des tâches de routine pour vous et vous rendra aussi confortable que possible afin que vous puissiez vous concentrer sur la logique elle-même. +Si vous apprenez et suivez ces règles, Nette sera là pour vous à chaque étape. Il s'occupera des tâches routinières à votre place et vous offrira un maximum de confort, afin que vous puissiez vous concentrer sur la logique elle-même. -Les principes que nous allons présenter ici sont assez simples. Vous n'avez aucun souci à vous faire. +Les principes que nous allons exposer ici sont très simples. Vous n'avez pas à vous soucier de quoi que ce soit. Vous vous souvenez de votre premier programme ? .[#toc-remember-your-first-program] ----------------------------------------------------------------------------------- -Nous n'avons aucune idée du langage dans lequel vous l'avez écrit, mais si c'était du PHP, il ressemblerait probablement à quelque chose comme ceci : +Nous ne savons pas dans quel langage vous l'avez écrit, mais s'il s'agit de PHP, il aurait pu ressembler à ceci : ```php function addition(float $a, float $b): float @@ -25,31 +25,31 @@ echo addition(23, 1); // imprime 24 Quelques lignes de code triviales, mais tellement de concepts clés cachés en elles. Qu'il y a des variables. Que le code est décomposé en unités plus petites, qui sont des fonctions, par exemple. Qu'on leur passe des arguments d'entrée et qu'elles renvoient des résultats. Il ne manque que les conditions et les boucles. -Le fait de passer des arguments à une fonction et qu'elle renvoie un résultat est un concept parfaitement compréhensible et utilisé dans d'autres domaines, comme les mathématiques. +Le fait qu'une fonction prenne des données en entrée et renvoie un résultat est un concept parfaitement compréhensible, qui est également utilisé dans d'autres domaines, tels que les mathématiques. -Une fonction a une signature, qui se compose de son nom, d'une liste de paramètres et de leurs types, et enfin du type de valeur de retour. En tant qu'utilisateurs, nous sommes intéressés par la signature ; nous n'avons généralement pas besoin de savoir quoi que ce soit sur l'implémentation interne. +Une fonction a sa signature, qui se compose de son nom, d'une liste de paramètres et de leurs types, et enfin du type de la valeur de retour. En tant qu'utilisateurs, nous sommes intéressés par la signature, et nous n'avons généralement pas besoin de connaître l'implémentation interne. -Imaginons maintenant que la signature d'une fonction ressemble à ceci : +Imaginons maintenant que la signature de la fonction ressemble à ceci : ```php function addition(float $x): float ``` -Une addition avec un seul paramètre ? C'est bizarre... Que penses-tu de ça ? +Une addition avec un seul paramètre ? C'est étrange... Qu'en est-il de ceci ? ```php function addition(): float ``` -C'est vraiment bizarre, n'est-ce pas ? Comment pensez-vous que la fonction est utilisée ? +C'est vraiment bizarre, non ? Comment la fonction est-elle utilisée ? ```php echo addition(); // qu'est-ce que ça imprime ? ``` -En regardant un tel code, nous sommes confus. Non seulement un débutant ne le comprendrait pas, mais même un programmeur compétent ne comprendrait pas un tel code. +En regardant un tel code, nous serions confus. Non seulement un débutant ne le comprendrait pas, mais même un programmeur expérimenté ne le comprendrait pas. -Vous vous demandez à quoi ressemblerait une telle fonction à l'intérieur ? Où trouverait-elle les additionneurs ? Elle les obtiendrait probablement d'une manière ou d'une autre par elle-même, comme ceci : +Vous demandez-vous à quoi ressemblerait une telle fonction à l'intérieur ? Où obtiendrait-elle les sommets ? Elle les obtiendrait probablement d'elle-même, d'une manière ou d'une autre, par exemple de la manière suivante : ```php function addition(): float @@ -66,13 +66,13 @@ Il s'avère qu'il y a des liens cachés vers d'autres fonctions (ou méthodes st Pas par là ! .[#toc-not-this-way] --------------------------------- -La conception que l'on vient de nous montrer est l'essence même de nombreuses caractéristiques négatives : +La conception que nous venons de montrer est l'essence même de nombreuses caractéristiques négatives : -- la signature de la fonction prétendait qu'elle n'avait pas besoin d'addition, ce qui nous a déconcertés -- nous n'avons aucune idée de la façon de faire calculer la fonction avec deux autres nombres -- nous avons dû regarder dans le code pour voir où il prend les additions -- nous avons découvert des liaisons cachées -- pour bien comprendre, nous devons également explorer ces liaisons. +- la signature de la fonction prétend qu'elle n'a pas besoin des sommets, ce qui nous rend perplexes +- nous n'avons aucune idée de la manière dont la fonction pourrait être calculée avec deux autres nombres +- nous avons dû consulter le code pour savoir d'où venaient les sommations +- nous avons découvert des dépendances cachées +- pour bien comprendre, il faut aussi examiner ces dépendances Et est-ce même le rôle de la fonction d'addition de se procurer des entrées ? Bien sûr que non. Sa responsabilité est uniquement d'ajouter. @@ -93,20 +93,20 @@ Règle n° 1 : Laissez-le vous être transmis .[#toc-rule-1-let-it-be-passed-to- La règle la plus importante est la suivante : **toutes les données dont les fonctions ou les classes ont besoin doivent leur être transmises**. -Au lieu d'inventer des mécanismes cachés pour les aider à les obtenir eux-mêmes, passez simplement les paramètres. Vous économiserez le temps nécessaire à l'invention de mécanismes cachés, qui n'amélioreront certainement pas votre code. +Au lieu d'inventer des moyens cachés pour qu'ils accèdent eux-mêmes aux données, passez simplement les paramètres. Vous gagnerez du temps que vous auriez passé à inventer des chemins cachés qui n'amélioreront certainement pas votre code. -Si vous suivez cette règle toujours et partout, vous êtes sur la voie d'un code sans liaisons cachées. Vers un code compréhensible non seulement pour l'auteur, mais aussi pour toute personne qui le lira par la suite. Où tout est compréhensible à partir des signatures des fonctions et des classes et où il n'est pas nécessaire de chercher des secrets cachés dans l'implémentation. +Si vous suivez toujours et partout cette règle, vous êtes sur la voie d'un code sans dépendances cachées. Un code compréhensible non seulement pour l'auteur mais aussi pour tous ceux qui le liront par la suite. Où tout est compréhensible à partir des signatures des fonctions et des classes, et où il n'est pas nécessaire de chercher des secrets cachés dans l'implémentation. -Cette technique est expertement appelée **injection de dépendance**. Et les données sont appelées **dépendances**. Mais il s'agit d'un simple passage de paramètres, rien de plus. +Cette technique est appelée professionnellement **injection de dépendance**. Et ces données sont appelées **dépendances**. Il s'agit d'un simple passage de paramètres, rien de plus. .[note] -Ne confondez pas l'injection de dépendances, qui est un modèle de conception, avec le "conteneur d'injection de dépendances", qui est un outil, quelque chose de complètement différent. Nous parlerons des conteneurs plus tard. +Ne confondez pas l'injection de dépendances, qui est un modèle de conception, avec un "conteneur d'injection de dépendances", qui est un outil, quelque chose de diamétralement différent. Nous traiterons des conteneurs plus tard. Des fonctions aux classes .[#toc-from-functions-to-classes] ----------------------------------------------------------- -Et quel est le rapport avec les classes ? Une classe est une entité plus complexe qu'une simple fonction, mais la règle n°1 s'applique ici aussi. Il y a simplement [plus de façons de passer des arguments |passing-dependencies]. Par exemple, tout comme dans le cas d'une fonction : +Et quel est le lien entre les classes ? Une classe est une unité plus complexe qu'une simple fonction, mais la règle n° 1 s'applique entièrement ici aussi. Il y a simplement [plus de façons de passer des arguments |passing-dependencies]. Par exemple, comme dans le cas d'une fonction : ```php class Math @@ -121,7 +121,7 @@ $math = new Math; echo $math->addition(23, 1); // 24 ``` -Ou en utilisant d'autres méthodes, ou directement par le constructeur : +Ou par d'autres méthodes, ou directement par le constructeur : ```php class Addition @@ -149,9 +149,9 @@ Ces deux exemples sont tout à fait conformes à l'injection de dépendances. Exemples concrets .[#toc-real-life-examples] -------------------------------------------- -Dans le monde réel, vous n'écrirez pas de classes pour l'addition de nombres. Passons maintenant aux exemples du monde réel. +Dans le monde réel, vous n'écrirez pas de cours sur l'addition de nombres. Passons maintenant aux exemples pratiques. -Ayons une classe `Article` représentant un article de blog : +Prenons une classe `Article` représentant un article de blog : ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -La méthode `save()` enregistre l'article dans une table de la base de données. L'implémenter en utilisant [Nette Database |database:] serait un jeu d'enfant, s'il n'y avait pas un problème : où `Article` doit-il trouver la connexion à la base de données, c'est-à-dire l'objet de classe `Nette\Database\Connection`? +La méthode `save()` enregistre l'article dans une table de la base de données. L'implémentation de cette méthode à l'aide de [Nette Database |database:] serait un jeu d'enfant, si ce n'était d'un problème : où `Article` obtient-il la connexion à la base de données, c'est-à-dire un objet de la classe `Nette\Database\Connection`? -Il semble que nous ayons beaucoup d'options. On peut la prendre quelque part dans une variable statique. Ou hériter d'une classe qui fournira la connexion à la base de données. Ou tirer parti d'un [singleton |global-state#Singleton]. Ou encore les "façades" qui sont utilisées dans Laravel : +Il semble que nous ayons beaucoup d'options. Il peut l'obtenir à partir d'une variable statique quelque part. Ou hériter d'une classe qui fournit une connexion à la base de données. Ou tirer parti d'un [singleton |global-state#Singleton]. Ou utiliser ce que l'on appelle les façades, qui sont utilisées dans Laravel : ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ Super, nous avons résolu le problème. Ou l'avons-nous fait ? -Rappelons la [règle n°1 : laissez-le vous être transmis |#rule #1: Let It Be Passed to You]: toutes les dépendances dont la classe a besoin doivent lui être transmises. Parce que si nous ne le faisons pas, et que nous enfreignons la règle, nous nous engageons sur la voie d'un code sale, plein de liaisons cachées, incompréhensible, et le résultat sera une application pénible à maintenir et à développer. +Rappelons la [règle n°1 : Let It Be Passed to You |#rule #1: Let It Be Passed to You]: toutes les dépendances dont la classe a besoin doivent lui être transmises. Car si nous enfreignons cette règle, nous nous engageons sur la voie d'un code sale, plein de dépendances cachées, incompréhensible, et le résultat sera une application pénible à maintenir et à développer. -L'utilisateur de la classe `Article` n'a aucune idée de l'endroit où la méthode `save()` stocke l'article. Dans une table de la base de données ? Dans laquelle, celle de production ou celle de développement ? Et comment cela peut-il être modifié ? +L'utilisateur de la classe `Article` n'a aucune idée de l'endroit où la méthode `save()` stocke l'article. Dans une table de la base de données ? Laquelle, celle de production ou celle de test ? Et comment la modifier ? -L'utilisateur doit regarder comment la méthode `save()` est mise en œuvre pour trouver l'utilisation de la méthode `DB::insert()`. Il doit donc chercher plus loin pour savoir comment cette méthode permet d'obtenir une connexion à la base de données. Et les liaisons cachées peuvent former une chaîne assez longue. +L'utilisateur doit regarder comment la méthode `save()` est implémentée et trouve l'utilisation de la méthode `DB::insert()`. Il doit donc poursuivre ses recherches pour découvrir comment cette méthode obtient une connexion à la base de données. Les dépendances cachées peuvent former une longue chaîne. -Les liaisons cachées, les façades Laravel ou les variables statiques ne sont jamais présentes dans un code propre et bien conçu. Dans un code propre et bien conçu, les arguments sont passés : +Dans un code propre et bien conçu, il n'y a jamais de dépendances cachées, de façades Laravel ou de variables statiques. Dans un code propre et bien conçu, les arguments sont transmis : ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Encore plus pratique, comme nous le verrons ensuite, est d'utiliser un constructeur : +Une approche encore plus pratique, comme nous le verrons plus loin, consistera à utiliser le constructeur : ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Si vous êtes un programmeur expérimenté, vous pensez peut-être que `Article` ne devrait pas du tout avoir de méthode `save()`, qu'il devrait être un pur composant de données et qu'un référentiel séparé devrait s'occuper du stockage. C'est tout à fait logique. Mais cela nous amènerait bien au-delà du sujet, qui est l'injection de dépendances, et à essayer de donner des exemples simples. +Si vous êtes un programmeur expérimenté, vous pouvez penser que `Article` ne devrait pas avoir de méthode `save()` du tout ; il devrait représenter un composant purement de données, et un référentiel séparé devrait s'occuper de la sauvegarde. C'est logique. Mais cela nous mènerait bien au-delà du sujet, qui est l'injection de dépendances, et de l'effort pour fournir des exemples simples. -Si vous écrivez une classe qui a besoin d'une base de données pour fonctionner, par exemple, ne cherchez pas à savoir où la trouver, mais faites-vous la passer. Peut-être en tant que paramètre d'un constructeur ou d'une autre méthode. Déclarez les dépendances. Exposez-les dans l'API de votre classe. Vous obtiendrez un code compréhensible et prévisible. +Si vous écrivez une classe qui a besoin, par exemple, d'une base de données pour fonctionner, n'inventez pas où aller la chercher, mais faites-la passer. Soit en tant que paramètre du constructeur, soit en tant que paramètre d'une autre méthode. Admettez les dépendances. Admettez-les dans l'API de votre classe. Vous obtiendrez un code compréhensible et prévisible. -Que pensez-vous de cette classe qui enregistre les messages d'erreur ? +Et que dire de cette classe qui enregistre les messages d'erreur : ```php class Logger @@ -266,9 +266,9 @@ Qu'en pensez-vous, avons-nous respecté la [règle n°1 : laissez-le vous être On ne l'a pas fait. -L'information clé, le répertoire du fichier journal, est *obtenu* par la classe à partir de la constante. +L'information clé, c'est-à-dire le répertoire contenant le fichier journal, est *obtenue* par la classe elle-même à partir de la constante. -Voir l'exemple d'utilisation : +Regardez l'exemple d'utilisation : ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Sans connaître l'implémentation, pourriez-vous répondre à la question de savoir où sont écrits les messages ? Cela vous suggérerait-il que l'existence de la constante LOG_DIR est nécessaire pour que cela fonctionne ? Et seriez-vous en mesure de créer une seconde instance qui écrirait à un autre endroit ? Certainement pas. +Sans connaître la mise en œuvre, pourriez-vous répondre à la question de savoir où les messages sont écrits ? Devinez-vous que l'existence de la constante `LOG_DIR` est nécessaire à son fonctionnement ? Et pourriez-vous créer une deuxième instance qui écrirait à un autre endroit ? Certainement pas. Réparons la classe : @@ -295,7 +295,7 @@ class Logger } ``` -La classe est maintenant beaucoup plus claire, plus configurable et donc plus utile. +La classe est désormais beaucoup plus compréhensible, configurable et donc plus utile. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Mais je m'en fiche ! .[#toc-but-i-don-t-care] --------------------------------------------- -*"Lorsque je crée un objet Article et que j'appelle save(), je ne veux pas m'occuper de la base de données, je veux juste qu'il soit enregistré dans celle que j'ai définie dans la configuration. "* +*"Lorsque je crée un objet Article et que j'appelle save(), je ne veux pas m'occuper de la base de données ; je veux juste qu'il soit enregistré dans celle que j'ai définie dans la configuration."* -*"Quand j'utilise Logger, je veux juste que le message soit écrit, et je ne veux pas m'occuper de l'endroit. Laissez les paramètres globaux être utilisés. "* +*"Lorsque j'utilise Logger, je veux juste que le message soit écrit, et je ne veux pas m'occuper de l'endroit. Laissez les paramètres globaux être utilisés."* -Ces commentaires sont corrects. +Ces points sont valables. -À titre d'exemple, prenons une classe qui envoie des bulletins d'information et enregistre comment cela s'est passé : +Prenons l'exemple d'une classe qui envoie des lettres d'information et enregistre leur déroulement : ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -La version améliorée de `Logger`, qui n'utilise plus la constante `LOG_DIR`, requiert un chemin d'accès au fichier dans le constructeur. Comment résoudre ce problème ? La classe `NewsletterDistributor` ne se soucie pas de l'endroit où les messages sont écrits, elle veut simplement les écrire. +La version améliorée de `Logger`, qui n'utilise plus la constante `LOG_DIR`, nécessite de spécifier le chemin d'accès au fichier dans le constructeur. Comment résoudre ce problème ? La classe `NewsletterDistributor` ne se préoccupe pas de l'endroit où les messages sont écrits ; elle veut simplement les écrire. -La solution réside à nouveau dans la [règle n° 1 : laissez-le vous être transmis |#rule #1: Let It Be Passed to You]: passez-lui toutes les données dont la classe a besoin. +La solution est encore une fois la [règle n° 1 : "Let It Be Passed to You |#rule #1: Let It Be Passed to You]" : transmettez toutes les données dont la classe a besoin. -Nous passons donc le chemin d'accès au journal au constructeur, que nous utilisons ensuite pour créer l'objet `Logger`? +Cela signifie-t-il que nous transmettons le chemin d'accès au journal par l'intermédiaire du constructeur, que nous utilisons ensuite lors de la création de l'objet `Logger`? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Pas comme ça ! Parce que le chemin **n'appartient pas** aux données dont la classe `NewsletterDistributor` a besoin ; elle a besoin de `Logger`. La classe a besoin du logger lui-même. Et c'est ce que nous allons transmettre : +Non, pas comme ça ! Le chemin d'accès ne fait pas partie des données dont la classe `NewsletterDistributor` a besoin ; en fait, c'est la classe `Logger` qui en a besoin. Voyez-vous la différence ? La classe `NewsletterDistributor` a besoin du logger lui-même. C'est donc ce que nous allons passer : ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Il est maintenant clair, d'après les signatures de la classe `NewsletterDistributor`, que la journalisation fait partie de ses fonctionnalités. Et la tâche de remplacer le logger par un autre, peut-être à des fins de test, est assez triviale. -De plus, si le constructeur de la classe `Logger` est modifié, cela n'aura aucun effet sur notre classe. +Les signatures de la classe `NewsletterDistributor` montrent clairement que la journalisation fait également partie de ses fonctionnalités. Et la tâche consistant à remplacer le logger par un autre, par exemple pour des tests, est tout à fait triviale. +De plus, si le constructeur de la classe `Logger` change, cela n'affectera pas notre classe. -Règle n° 2 : prenez ce qui vous appartient .[#toc-rule-2-take-what-is-yours] +Règle n° 2 : Prendre ce qui vous appartient .[#toc-rule-2-take-what-s-yours] ---------------------------------------------------------------------------- -Ne vous laissez pas abuser et ne laissez pas les paramètres de vos dépendances vous être transmis. Transmettez directement les dépendances. +Ne vous laissez pas induire en erreur et ne vous laissez pas passer les dépendances de vos dépendances. Contentez-vous de passer vos propres dépendances. -Cela rendra le code utilisant d'autres objets complètement indépendant des modifications apportées à leurs constructeurs. Son API sera plus vraie. Et surtout, il sera trivial d'échanger ces dépendances contre d'autres. +Grâce à cela, le code utilisant d'autres objets sera complètement indépendant des changements dans leurs constructeurs. Son API sera plus véridique. Et surtout, il sera trivial de remplacer ces dépendances par d'autres. -Un nouveau membre de la famille .[#toc-a-new-member-of-the-family] ------------------------------------------------------------------- +Nouveau membre de la famille .[#toc-new-family-member] +------------------------------------------------------ -L'équipe de développement a décidé de créer un deuxième enregistreur qui écrit dans la base de données. Nous avons donc créé une classe `DatabaseLogger`. Nous avons donc deux classes, `Logger` et `DatabaseLogger`, l'une écrit dans un fichier, l'autre dans une base de données ... Ne pensez-vous pas que ce nom est étrange ? -Ne serait-il pas préférable de renommer `Logger` en `FileLogger`? Bien sûr que oui. +L'équipe de développement a décidé de créer un deuxième enregistreur qui écrit dans la base de données. Nous avons donc créé une classe `DatabaseLogger`. Nous avons donc deux classes, `Logger` et `DatabaseLogger`, l'une écrit dans un fichier, l'autre dans une base de données ... le nommage ne vous semble pas étrange ? +Ne serait-il pas préférable de renommer `Logger` en `FileLogger`? Tout à fait. -Mais faisons-le intelligemment. Nous allons créer une interface sous le nom original : +Mais faisons-le intelligemment. Nous créons une interface sous le nom original : ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...que les deux loggers implémenteront : +... que les deux bûcherons mettront en œuvre : ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -Et de cette façon, rien ne devra être modifié dans le reste du code où le logger est utilisé. Par exemple, le constructeur de la classe `NewsletterDistributor` se contentera toujours de demander `Logger` comme paramètre. Et ce sera à nous de choisir l'instance que nous lui passerons. +De ce fait, il ne sera pas nécessaire de changer quoi que ce soit dans le reste du code où le logger est utilisé. Par exemple, le constructeur de la classe `NewsletterDistributor` se contentera toujours d'exiger `Logger` comme paramètre. Et il nous appartiendra de choisir l'instance que nous lui transmettrons. -**C'est pourquoi nous ne donnons jamais aux noms d'interface le suffixe `Interface` ou le préfixe `I`.** Sinon, il serait impossible de développer du code aussi bien. +**C'est pourquoi nous n'ajoutons jamais le suffixe `Interface` ou le préfixe `I` aux noms d'interface,** sans quoi il ne serait pas possible de développer le code de manière aussi agréable. Houston, nous avons un problème .[#toc-houston-we-have-a-problem] ----------------------------------------------------------------- -Alors que dans l'ensemble de l'application, nous pouvons nous contenter d'une seule instance d'un enregistreur, qu'il s'agisse d'un fichier ou d'une base de données, et le passer simplement partout où quelque chose est enregistré, il en va tout autrement dans le cas de la classe `Article`. En fait, nous créons des instances de cette classe selon les besoins, voire plusieurs fois. Comment gérer la liaison avec la base de données dans son constructeur ? +Alors que nous pouvons nous contenter d'une seule instance de l'enregistreur, qu'il soit basé sur un fichier ou une base de données, dans l'ensemble de l'application et qu'il suffit de le passer à chaque fois que quelque chose est enregistré, il en va tout autrement pour la classe `Article`. Nous créons ses instances en fonction des besoins, même plusieurs fois. Comment gérer la dépendance de la base de données dans son constructeur ? -À titre d'exemple, nous pouvons utiliser un contrôleur qui doit enregistrer un article dans la base de données après avoir soumis un formulaire : +Un exemple peut être un contrôleur qui doit enregistrer un article dans la base de données après avoir soumis un formulaire : ```php class EditController extends Controller @@ -437,9 +437,9 @@ class EditController extends Controller } ``` -Une solution possible est directement proposée : faire passer l'objet base de données par le constructeur à `EditController` et utiliser `$article = new Article($this->db)`. +Une solution possible est évidente : passer l'objet base de données au constructeur `EditController` et utiliser `$article = new Article($this->db)`. -Comme dans le cas précédent avec `Logger` et le chemin du fichier, ce n'est pas l'approche correcte. La base de données n'est pas une dépendance de `EditController`, mais de `Article`. Ainsi, passer la base de données va à l'encontre de la [règle n°2 : prenez ce qui vous appartient |#rule #2: take what is yours]. Lorsque le constructeur de la classe `Article` est modifié (un nouveau paramètre est ajouté), le code à tous les endroits où des instances sont créées devra également être modifié. Ufff. +Tout comme dans le cas précédent avec `Logger` et le chemin d'accès au fichier, ce n'est pas la bonne approche. La base de données n'est pas une dépendance de `EditController`, mais de `Article`. Passer la base de données va à l'encontre de la [règle #2 : prenez ce qui vous appartient |#rule #2: take what's yours]. Si le constructeur de la classe `Article` change (un nouveau paramètre est ajouté), vous devrez modifier le code partout où des instances sont créées. Ufff. Houston, que suggérez-vous ? @@ -447,11 +447,11 @@ Houston, que suggérez-vous ? Règle n° 3 : Laissez l'usine s'en occuper .[#toc-rule-3-let-the-factory-handle-it] ---------------------------------------------------------------------------------- -En supprimant les liens cachés et en passant toutes les dépendances comme arguments, nous obtenons des classes plus configurables et plus flexibles. Et donc nous avons besoin de quelque chose d'autre pour créer et configurer ces classes plus flexibles. Nous l'appellerons "usine". +En éliminant les dépendances cachées et en passant toutes les dépendances en tant qu'arguments, nous avons obtenu des classes plus configurables et plus flexibles. Par conséquent, nous avons besoin de quelque chose d'autre pour créer et configurer ces classes plus flexibles pour nous. Nous l'appellerons "usine". La règle de base est la suivante : si une classe a des dépendances, laissez la création de leurs instances à la fabrique. -Les fabriques sont un remplacement plus intelligent de l'opérateur `new` dans le monde de l'injection de dépendances. +Les usines sont un remplacement plus intelligent de l'opérateur `new` dans le monde de l'injection de dépendances. .[note] Ne pas confondre avec le modèle de conception *factory method*, qui décrit une manière spécifique d'utiliser les usines et n'est pas lié à ce sujet. @@ -460,7 +460,7 @@ Ne pas confondre avec le modèle de conception *factory method*, qui décrit une Usine .[#toc-factory] --------------------- -Une fabrique est une méthode ou une classe qui produit et configure des objets. Nous appelons `Article` la classe de production `ArticleFactory` et elle pourrait ressembler à ceci : +Une fabrique est une méthode ou une classe qui crée et configure des objets. Nous nommerons la classe produisant `Article` `ArticleFactory` , et elle pourrait ressembler à ceci : ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Son utilisation dans le contrôleur serait la suivante : +Son utilisation dans le contrôleur sera la suivante : ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -À ce stade, lorsque la signature du constructeur de la classe `Article` change, la seule partie du code qui doit réagir est la fabrique `ArticleFactory` elle-même. Tout autre code qui travaille avec les objets `Article`, comme `EditController`, ne sera pas affecté. +À ce stade, si la signature du constructeur de la classe `Article` change, la seule partie du code qui doit réagir est le `ArticleFactory` lui-même. Tout autre code travaillant avec des objets `Article`, comme `EditController`, ne sera pas affecté. -Vous vous tapez peut-être le front en ce moment en vous demandant si nous nous sommes bien aidés. La quantité de code a augmenté et l'ensemble commence à avoir l'air suspicieusement compliqué. +Vous vous demandez peut-être si nous avons vraiment amélioré les choses. La quantité de code a augmenté et tout cela commence à sembler étrangement compliqué. -Ne vous inquiétez pas, nous allons bientôt arriver au conteneur Nette DI. Et il a un certain nombre d'atouts dans sa manche qui rendront la construction d'applications utilisant l'injection de dépendances extrêmement simple. Par exemple, au lieu de la classe `ArticleFactory`, il suffira d'[écrire une simple interface |factory]: +Ne vous inquiétez pas, nous allons bientôt aborder le conteneur Nette DI. Il a plus d'un tour dans son sac, ce qui simplifiera grandement la construction d'applications utilisant l'injection de dépendances. Par exemple, au lieu de la classe `ArticleFactory`, vous n'aurez qu'à [écrire une simple interface |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Mais nous prenons de l'avance, attendez :-) +Mais nous prenons de l'avance ; soyez patients :-) Résumé .[#toc-summary] ---------------------- -Au début de ce chapitre, nous vous avons promis de vous montrer une méthode pour concevoir du code propre. Il suffit de donner aux classes +Au début de ce chapitre, nous avons promis de vous montrer un processus de conception de code propre. Tout ce qu'il faut, c'est que les classes.. : -- [les dépendances dont elles ont besoin |#Rule #1: Let It Be Passed to You] -- [et pas ce dont elles n'ont pas directement besoin |#Rule #2: Take What Is Yours] -- [et que les objets avec des dépendances sont mieux fabriqués dans des usines |#Rule #3: Let the Factory Handle it]. +- [passer les dépendances dont elles ont besoin |#Rule #1: Let It Be Passed to You] +- [à l'inverse, ne pas transmettre ce dont elles n'ont pas directement besoin |#Rule #2: Take What's Yours] +- [et que les objets avec des dépendances soient mieux créés dans des usines |#Rule #3: Let the Factory Handle it] -Cela ne semble peut-être pas être le cas à première vue, mais ces trois règles ont des implications considérables. Elles conduisent à une vision radicalement différente de la conception du code. Cela en vaut-il la peine ? Les programmeurs qui se sont débarrassés de leurs vieilles habitudes et ont commencé à utiliser systématiquement l'injection de dépendances considèrent qu'il s'agit d'un moment décisif dans leur vie professionnelle. Cela leur a ouvert un monde d'applications claires et durables. +À première vue, ces trois règles ne semblent pas avoir de conséquences importantes, mais elles conduisent à une perspective radicalement différente de la conception du code. Le jeu en vaut-il la chandelle ? Les développeurs qui ont abandonné leurs vieilles habitudes et commencé à utiliser systématiquement l'injection de dépendances considèrent cette étape comme un moment crucial de leur vie professionnelle. Elle leur a ouvert le monde des applications claires et faciles à maintenir. -Mais que se passe-t-il si le code n'utilise pas systématiquement l'injection de dépendances ? Et s'il est construit sur des méthodes statiques ou des singletons ? Cela pose-t-il des problèmes ? [Oui, et c'est très important |global-state]. +Mais que se passe-t-il si le code n'utilise pas systématiquement l'injection de dépendances ? Que se passe-t-il s'il s'appuie sur des méthodes statiques ou des singletons ? Cela pose-t-il des problèmes ? [Oui, cela pose des problèmes, et des problèmes fondamentaux |global-state]. diff --git a/dependency-injection/fr/passing-dependencies.texy b/dependency-injection/fr/passing-dependencies.texy index 30156ce5a1..67f2ea3138 100644 --- a/dependency-injection/fr/passing-dependencies.texy +++ b/dependency-injection/fr/passing-dependencies.texy @@ -12,7 +12,7 @@ Les arguments, ou "dépendances" dans la terminologie DI, peuvent être transmis </div> -Les trois premières méthodes s'appliquent en général dans tous les langages orientés objet, la quatrième est spécifique aux présentateurs Nette, elle est donc traitée dans un [chapitre séparé |best-practices:inject-method-attribute]. Nous allons maintenant examiner de plus près chacune de ces options et les montrer à l'aide d'exemples spécifiques. +Nous allons maintenant illustrer les différentes variantes par des exemples concrets. Injection de constructeur .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Injection de constructeur .[#toc-constructor-injection] Les dépendances sont transmises comme arguments au constructeur lors de la création de l'objet : ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Cette forme est utile pour les dépendances obligatoires dont la classe a absolument besoin pour fonctionner, car sans elles, l'instance ne peut être créée. @@ -40,10 +40,10 @@ Depuis PHP 8.0, nous pouvons utiliser une forme plus courte de notation qui est ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ Depuis PHP 8.1, une propriété peut être marquée avec un drapeau `readonly` q ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService Le conteneur DI passe les dépendances au constructeur automatiquement en utilisant le [câblage automatique |autowiring]. Les arguments qui ne peuvent pas être passés de cette façon (par exemple, les chaînes de caractères, les nombres, les booléens) [s'écrivent dans la configuration |services#Arguments]. +L'enfer des constructeurs .[#toc-constructor-hell] +-------------------------------------------------- + +Le terme *constructor hell* fait référence à une situation où un enfant hérite d'une classe parentale dont le constructeur nécessite des dépendances, et où l'enfant nécessite également des dépendances. Il doit également reprendre et transmettre les dépendances du parent : + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Le problème se pose lorsque nous voulons modifier le constructeur de la classe `BaseClass`, par exemple lorsqu'une nouvelle dépendance est ajoutée. Il faut alors modifier tous les constructeurs des enfants. Ce qui rend une telle modification infernale. + +Comment éviter cela ? La solution est de **prioriser la composition sur l'héritage**. + +Concevons donc le code différemment. Nous éviterons les classes abstraites `Base*`. Au lieu que `MyClass` obtienne une fonctionnalité en héritant de `BaseClass`, cette fonctionnalité lui sera transmise en tant que dépendance : + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Injection de setter .[#toc-setter-injection] ============================================ -Les dépendances sont transmises en appelant une méthode qui les stocke dans une propriété privée. La convention de dénomination habituelle de ces méthodes est de la forme `set*()`, c'est pourquoi elles sont appelées setters. +Les dépendances sont transmises en appelant une méthode qui les stocke dans une propriété privée. La convention de dénomination habituelle pour ces méthodes est de la forme `set*()`, c'est pourquoi elles sont appelées "setters", mais elles peuvent bien sûr être appelées autrement. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Cette méthode est utile pour les dépendances facultatives qui ne sont pas nécessaires au fonctionnement de la classe, car il n'est pas garanti que l'objet les recevra effectivement (c'est-à-dire que l'utilisateur appellera la méthode). @@ -90,16 +150,16 @@ Cette méthode est utile pour les dépendances facultatives qui ne sont pas néc En même temps, cette méthode permet d'appeler le setter à plusieurs reprises pour modifier la dépendance. Si cela n'est pas souhaitable, ajoutez une vérification à la méthode, ou à partir de PHP 8.1, marquez la propriété `$cache` avec le drapeau `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ L'appel au setter est défini dans la configuration du conteneur DI dans la [sec ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Injection de propriétés .[#toc-property-injection] Les dépendances sont transmises directement à la propriété : ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Cette méthode est considérée comme inappropriée car la propriété doit être déclarée comme `public`. Par conséquent, nous n'avons aucun contrôle sur le fait que la dépendance passée sera effectivement du type spécifié (c'était vrai avant PHP 7.4) et nous perdons la possibilité de réagir à la dépendance nouvellement assignée avec notre propre code, par exemple pour empêcher des changements ultérieurs. En même temps, la propriété devient une partie de l'interface publique de la classe, ce qui n'est pas forcément souhaitable. @@ -137,12 +197,18 @@ Le paramétrage de la variable est défini dans la configuration du conteneur DI ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Injecter .[#toc-inject] +======================= + +Alors que les trois méthodes précédentes sont généralement valables dans tous les langages orientés objet, l'injection par méthode, annotation ou attribut *inject* est spécifique aux présentateurs Nette. Elles font l'objet d'un [chapitre distinct |best-practices:inject-method-attribute]. + + Quelle voie choisir ? .[#toc-which-way-to-choose] ================================================= diff --git a/dependency-injection/fr/services.texy b/dependency-injection/fr/services.texy index 177c5825ae..95f152a65b 100644 --- a/dependency-injection/fr/services.texy +++ b/dependency-injection/fr/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Mode d'injection .[#toc-inject-mode] ==================================== -L'indicateur `inject: true` est utilisé pour activer le passage des dépendances via des variables publiques avec l'annotation [inject |best-practices:inject-method-attribute#Inject Annotations] et les méthodes [inject*() |best-practices:inject-method-attribute#inject Methods]. +L'indicateur `inject: true` est utilisé pour activer le passage des dépendances via des variables publiques avec l'annotation [inject |best-practices:inject-method-attribute#Inject Attributes] et les méthodes [inject*() |best-practices:inject-method-attribute#inject Methods]. ```neon services: diff --git a/dependency-injection/hu/@home.texy b/dependency-injection/hu/@home.texy index 6bb246f739..d53d1b944a 100644 --- a/dependency-injection/hu/@home.texy +++ b/dependency-injection/hu/@home.texy @@ -5,8 +5,10 @@ Függőségi injekció A Dependency Injection egy olyan tervezési minta, amely alapvetően megváltoztatja a kód és a fejlesztés szemléletét. Megnyitja az utat a tisztán megtervezett és fenntartható alkalmazások világa felé. - [Mi az a Dependency Injection? |introduction] -- [Mi az a DI Container? |container] +- [Globális állapot és singletonok |global-state] - [Függőségek átadása |passing-dependencies] +- [Mi az a DI Container? |container] +- [Gyakran ismételt kérdések |faq] Nette DI diff --git a/dependency-injection/hu/@left-menu.texy b/dependency-injection/hu/@left-menu.texy index b1dcdbae58..4247a766ec 100644 --- a/dependency-injection/hu/@left-menu.texy +++ b/dependency-injection/hu/@left-menu.texy @@ -1,8 +1,10 @@ Függőségi injekció ****************** - [Mi az a DI? |introduction] -- [Mi az a DI Container? |container] +- [Globális állapot és singletonok |global-state] - [Függőségek átadása |passing-dependencies] +- [Mi az a DI Container? |container] +- [Gyakran ismételt kérdések |faq] Nette DI diff --git a/dependency-injection/hu/faq.texy b/dependency-injection/hu/faq.texy new file mode 100644 index 0000000000..7bb9fe4437 --- /dev/null +++ b/dependency-injection/hu/faq.texy @@ -0,0 +1,112 @@ +Gyakran ismételt kérdések a DI-ről (FAQ) +**************************************** + + +A DI az IoC másik neve? .[#toc-is-di-another-name-for-ioc] +---------------------------------------------------------- + +Az *Inversion of Control* (IoC) egy olyan elv, amely a kód végrehajtásának módjára összpontosít - arra, hogy a kódod külső kódot kezdeményez, vagy a kódod integrálódik külső kódba, amely aztán meghívja azt. +Az IoC egy tág fogalom, amely magában foglalja az [eseményeket |nette:glossary#Events], az úgynevezett [hollywoodi elvet |application:components#Hollywood style] és más szempontokat. +A gyárak, amelyek a [3. szabály: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It] részét képezik, és a `new` operátor inverzióját jelentik, szintén e koncepció összetevői. + +A *Dependency Injection* (DI) arról szól, hogy egy objektum hogyan tud egy másik objektumról, azaz függőségről. Ez egy olyan tervezési minta, amely megköveteli a függőségek explicit átadását az objektumok között. + +Így a DI az IoC egy sajátos formájának mondható. Az IoC nem minden formája alkalmas azonban a kódtisztaság szempontjából. Például az anti-minták közé soroljuk az összes olyan technikát, amely [globális állapottal |global state] vagy az úgynevezett [Service Locatorral |#What is a Service Locator] dolgozik. + + +Mi az a Service Locator? .[#toc-what-is-a-service-locator] +---------------------------------------------------------- + +A Service Locator a Dependency Injection alternatívája. Úgy működik, hogy létrehoz egy központi tárolót, ahol az összes elérhető szolgáltatás vagy függőség regisztrálva van. Amikor egy objektumnak szüksége van egy függőségre, akkor azt a Service Locatorból kéri. + +A Dependency Injectionhoz képest azonban veszít az átláthatóságból: a függőségek nem kerülnek közvetlenül az objektumokhoz, ezért nem könnyen azonosíthatók, ami a kód vizsgálatát igényli az összes kapcsolat feltárásához és megértéséhez. A tesztelés is bonyolultabb, mivel nem adhatunk át egyszerűen mock objektumokat a tesztelt objektumoknak, hanem a Service Locatoron keresztül kell haladnunk. Továbbá a Service Locator megzavarja a kód tervezését, mivel az egyes objektumoknak tisztában kell lenniük a létezésével, ami eltér a Dependency Injectiontől, ahol az objektumok nem tudnak a DI konténerről. + + +Mikor jobb, ha nem használjuk a DI-t? .[#toc-when-is-it-better-not-to-use-di] +----------------------------------------------------------------------------- + +A Dependency Injection tervezési minta használatával kapcsolatban nincsenek ismert nehézségek. Ellenkezőleg, a függőségek globálisan elérhető helyekről való megszerzése [számos bonyodalomhoz |global-state] vezet, akárcsak a Service Locator használata. +Ezért tanácsos mindig a DI használata. Ez nem dogmatikus megközelítés, de egyszerűen nem találtunk jobb alternatívát. + +Vannak azonban olyan helyzetek, amikor nem adunk át objektumokat egymásnak, és a globális térből szerezzük meg őket. Például, amikor kódot hibakeresünk, és a program egy adott pontján ki kell dobnunk egy változó értékét, meg kell mérnünk a program egy bizonyos részének időtartamát, vagy naplóznunk kell egy üzenetet. +Ilyen esetekben, amikor olyan ideiglenes műveletekről van szó, amelyek később eltávolításra kerülnek a kódból, jogos egy globálisan elérhető dumper, stopperóra vagy logger használata. Ezek az eszközök végül is nem tartoznak a kód tervezéséhez. + + +Vannak-e hátrányai a DI használatának? .[#toc-does-using-di-have-its-drawbacks] +------------------------------------------------------------------------------- + +A Dependency Injection használata jár-e hátrányokkal, például a kódírás bonyolultabbá válásával vagy rosszabb teljesítménnyel? Mit veszítünk, ha a DI-vel összhangban kezdünk kódot írni? + +A DI nincs hatással az alkalmazás teljesítményére vagy memóriaigényére. A DI konténer teljesítménye szerepet játszhat, de a [Nette DI | nette-container] esetében a konténer tiszta PHP-be van fordítva, így az alkalmazás futási ideje alatt a többletköltsége lényegében nulla. + +A kód írásakor olyan konstruktorokat kell létrehozni, amelyek elfogadják a függőségeket. Régebben ez időigényes lehetett, de a modern IDE-knek és a [konstruktorok tulajdonságainak promóciójának |https://blog.nette.org/hu/php-8-0-teljes-attekintes-az-ujdonsagokrol#toc-constructor-property-promotion] köszönhetően ez ma már néhány másodperc kérdése. A Nette DI és egy PhpStorm plugin segítségével néhány kattintással könnyedén létrehozhatók a konstruktorok. +Másrészt nincs szükség singletonok és statikus hozzáférési pontok írására. + +Megállapítható, hogy egy megfelelően megtervezett, DI-t használó alkalmazás nem rövidebb és nem hosszabb a singletonokat használó alkalmazáshoz képest. A függőségekkel dolgozó kódrészek egyszerűen kivonásra kerülnek az egyes osztályokból, és új helyekre, azaz a DI konténerbe és a gyárakba kerülnek. + + +Hogyan írjunk át egy régi alkalmazást DI-re? .[#toc-how-to-rewrite-a-legacy-application-to-di] +---------------------------------------------------------------------------------------------- + +A régebbi alkalmazásról a Dependency Injectionra való áttérés kihívást jelentő folyamat lehet, különösen a nagy és összetett alkalmazások esetében. Fontos, hogy szisztematikusan közelítsük meg ezt a folyamatot. + +- A Dependency Injectionra való áttérés során fontos, hogy a csapat minden tagja megértse az alkalmazott elveket és gyakorlatokat. +- Először is végezze el a meglévő alkalmazás elemzését a kulcsfontosságú összetevők és függőségük azonosítása érdekében. Készítsen tervet arra vonatkozóan, hogy mely részeket és milyen sorrendben fogja refaktorálni. +- Implementáljon egy DI-konténert, vagy még jobb, ha egy meglévő könyvtárat használ, például a Nette DI-t. +- Fokozatosan alakítsa át az alkalmazás minden egyes részét a Dependency Injection használatára. Ez magában foglalhatja a konstruktorok vagy metódusok módosítását, hogy paraméterként elfogadják a függőségeket. +- Módosítsa a kód azon helyeit, ahol függőségi objektumok jönnek létre, hogy a függőségeket a konténer injektálja. Ez magában foglalhatja a gyárak használatát. + +Ne feledje, hogy a függőségi injektálásra való áttérés a kód minőségébe és az alkalmazás hosszú távú fenntarthatóságába való befektetés. Bár kihívást jelenthet ezeknek a változtatásoknak a végrehajtása, az eredménynek tisztább, modulárisabb és könnyen tesztelhető kódnak kell lennie, amely készen áll a jövőbeli bővítésekre és karbantartásra. + + +Miért előnyösebb a kompozíció az örökléssel szemben? .[#toc-why-composition-is-preferred-over-inheritance] +---------------------------------------------------------------------------------------------------------- +Az öröklés helyett előnyösebb a kompozíciót használni, mivel ez a kód újrafelhasználhatóságát szolgálja anélkül, hogy aggódnunk kellene a változtatások átcsapó hatása miatt. Így lazább csatolást biztosít, ahol nem kell aggódnunk amiatt, hogy egy kód megváltoztatása más függő kódok megváltoztatását eredményezi. Tipikus példa erre a [konstruktorpokolként |passing-dependencies#Constructor hell] azonosított helyzet. + + +Használható-e a Nette DI Container a Nette rendszeren kívül is? .[#toc-can-nette-di-container-be-used-outside-of-nette] +----------------------------------------------------------------------------------------------------------------------- + +Természetesen. A Nette DI Container része a Nette-nek, de önálló könyvtárként van kialakítva, amely a keretrendszer más részeitől függetlenül használható. Csak telepítse a Composer segítségével, hozzon létre egy konfigurációs fájlt, amely meghatározza a szolgáltatásait, majd néhány sor PHP kóddal hozza létre a DI konténert. +És máris elkezdheti kihasználni a Dependency Injection előnyeit a projektjeiben. + +A [Nette DI Container |nette-container] fejezetben leírja, hogyan néz ki egy konkrét felhasználási eset, a kóddal együtt. + + +Miért van a konfiguráció NEON fájlokban? .[#toc-why-is-the-configuration-in-neon-files] +--------------------------------------------------------------------------------------- + +A NEON egy egyszerű és könnyen olvasható konfigurációs nyelv, amelyet a Nette-en belül fejlesztettek ki az alkalmazások, szolgáltatások és függőségeik beállítására. A JSON-hoz vagy a YAML-hez képest sokkal intuitívabb és rugalmasabb lehetőségeket kínál erre a célra. A NEON-ban természetesen leírhatók olyan kötöttségek, amelyeket Symfony & YAML-ben egyáltalán nem vagy csak bonyolult leírással lehetne megírni. + + +Lassítja a NEON fájlok elemzése az alkalmazást? .[#toc-does-parsing-neon-files-slow-down-the-application] +--------------------------------------------------------------------------------------------------------- + +Bár a NEON fájlok elemzése nagyon gyorsan történik, ez a szempont nem igazán számít. Ennek oka az, hogy a fájlok elemzése csak egyszer történik meg az alkalmazás első indítása során. Ezt követően a DI-konténer kódja generálódik, a lemezen tárolódik, és minden további kérésnél további elemzés nélkül végrehajtódik. + +Ez így működik a termelési környezetben. A fejlesztés során a NEON-fájlok minden alkalommal elemzése megtörténik, amikor tartalmuk megváltozik, így biztosítva, hogy a fejlesztő mindig naprakész DI-konténerrel rendelkezzen. Mint korábban említettük, a tényleges elemzés egy pillanat alatt megtörténik. + + +Hogyan férhetek hozzá a konfigurációs fájl paramétereihez az osztályomban? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +--------------------------------------------------------------------------------------------------------------------------------------------------------- + +Tartsd szem előtt az [1. szabályt: Hagyd, hogy átadják n |introduction#Rule #1: Let It Be Passed to You]eked. Ha egy osztálynak szüksége van egy konfigurációs fájlból származó információra, nem kell kitalálnunk, hogyan érhetjük el az információt, hanem egyszerűen kérdezzük meg - például az osztály konstruktorán keresztül. Az átadást pedig a konfigurációs fájlban végezzük el. + +Ebben a példában a `%myParameter%` a `myParameter` paraméter értékének helyőrzője, amelyet a `MyClass` konstruktornak adunk át: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Ha több paramétert akar átadni, vagy automatikus kapcsolást szeretne használni, hasznos, ha [a paramétereket egy objektumba csomagolja |best-practices:passing-settings-to-presenters]. + + +Támogatja a Nette a PSR-11 konténer interfészt? .[#toc-does-nette-support-psr-11-container-interface] +----------------------------------------------------------------------------------------------------- + +A Nette DI Container nem támogatja közvetlenül a PSR-11-et. Ha azonban interoperabilitásra van szüksége a Nette DI Container és a PSR-11 Container interfészt elváró könyvtárak vagy keretrendszerek között, létrehozhat egy [egyszerű adaptert |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], amely hídként szolgál a Nette DI Container és a PSR-11 között. diff --git a/dependency-injection/hu/global-state.texy b/dependency-injection/hu/global-state.texy new file mode 100644 index 0000000000..efcae52950 --- /dev/null +++ b/dependency-injection/hu/global-state.texy @@ -0,0 +1,312 @@ +Globális állapot és singletonok +******************************* + +.[perex] +Figyelmeztetés: A következő konstrukciók a rossz kódtervezés tünetei: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` vagy `static::$var` + +Előfordulnak-e ezek a konstrukciók az Ön kódjában? Akkor lehetősége van a javításra. Talán arra gondolsz, hogy ezek olyan gyakori konstrukciók, amelyeket különböző könyvtárak és keretrendszerek mintamegoldásaiban látunk. +Sajnos ezek még mindig a rossz tervezés egyértelmű jelei. Egy dolog közös bennük: a globális állapot használata. + +Most biztosan nem valamiféle akadémiai tisztaságról beszélünk. A globális állapot és a singletonok használata romboló hatással van a kód minőségére. A viselkedés kiszámíthatatlanná válik, csökkenti a fejlesztők termelékenységét, és arra kényszeríti az osztályinterfészeket, hogy hazudjanak a valódi függőségükről. Ami összezavarja a programozókat. + +Ebben a fejezetben megmutatjuk, hogyan lehetséges ez. + + +Globális összekapcsolás .[#toc-global-interlinking] +--------------------------------------------------- + +A globális állammal az az alapvető probléma, hogy globálisan hozzáférhető. Ez lehetővé teszi, hogy a `DB::insert()` globális (statikus) metóduson keresztül írjunk az adatbázisba. +Egy ideális világban egy objektumnak csak olyan más objektumokkal kellene tudnia kommunikálni, amelyeket [közvetlenül átadtak neki |passing-dependencies]. +Ha létrehozok két objektumot `A` és `B`, és soha nem adok át hivatkozást a `A` objektumról a `B` objektumra, akkor sem a `A`, sem a `B` nem tud hozzáférni a másik objektumhoz, és nem tudja megváltoztatni annak állapotát. +Ez egy nagyon kívánatos tulajdonsága a kódnak. Hasonló ahhoz, mintha lenne egy elem és egy izzó; az izzó nem fog világítani, amíg össze nem kapcsoljuk őket. + +Ez nem igaz a globális (statikus) változókra vagy a szingletonokra. A `A` objektum *vezeték nélkül* hozzáférhet a `C` objektumhoz, és módosíthatja azt hivatkozás átadása nélkül, a `C::changeSomething()` meghívásával. +Ha a `B` objektum a globális `C` objektumot is megragadja, akkor a `A` és a `B` objektum a `C` objektumon keresztül kölcsönhatásba léphet egymással. + +A globális változók használata a *vezeték nélküli* csatolás egy új, kívülről nem látható formáját vezeti be a rendszerbe. +Ez egy füstfüggönyt hoz létre, amely megnehezíti a kód megértését és használatát. +A fejlesztőknek a forráskód minden sorát el kell olvasniuk ahhoz, hogy valóban megértsék a függőségeket. Ahelyett, hogy csak az osztályok interfészével ismerkednének. +Ráadásul ez egy teljesen felesleges csatolás. + +.[note] +A viselkedés szempontjából nincs különbség egy globális és egy statikus változó között. Egyformán károsak. + + +A kísérteties cselekvés távolról .[#toc-the-spooky-action-at-a-distance] +------------------------------------------------------------------------ + +"Kísérteties hatás a távolban" - így nevezte Albert Einstein 1935-ben a kvantumfizika egyik jelenségét, amelytől kirázta a hideg. +Ez a kvantum összefonódás, amelynek sajátossága, hogy amikor egy részecskéről információt mérünk, azonnal hatással vagyunk egy másik részecskére, még akkor is, ha azok több millió fényévre vannak egymástól. +ami látszólag sérti a világegyetem alapvető törvényét, miszerint semmi sem haladhat gyorsabban a fénynél. + +A szoftverek világában "spooky action at a distance"-nek nevezhetjük azt a helyzetet, amikor lefuttatunk egy folyamatot, amelyről azt gondoljuk, hogy elszigetelt (mert nem adtunk át neki semmilyen hivatkozást), de váratlan kölcsönhatások és állapotváltozások történnek a rendszer távoli pontjain, amelyekről nem szóltunk az objektumnak. Ez csak a globális állapoton keresztül történhet. + +Képzeljük el, hogy csatlakozunk egy olyan projektfejlesztő csapathoz, amely nagy, kiforrott kódbázissal rendelkezik. Az új vezetőd megkér egy új funkció megvalósítására, és jó fejlesztőhöz méltóan egy teszt megírásával kezded. De mivel új vagy a projektben, sok feltáró "mi történik, ha meghívom ezt a metódust" típusú tesztet csinálsz. És megpróbálod megírni a következő tesztet: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // az Ön kártyaszámát + $cc->charge(100); +} +``` + +Egy idő után észreveszed, hogy a bank értesítést küld a telefonodon, hogy minden egyes futtatáskor 100 dollárral terhelték meg a hitelkártyádat. 🤦‍♂️ + +Hogy a fenébe okozhatott a teszt tényleges terhelést? Nem könnyű a hitelkártyával operálni. Egy harmadik fél webes szolgáltatásával kell kapcsolatba lépnie, ismernie kell a webes szolgáltatás URL-jét, be kell jelentkeznie, és így tovább. +Ezek közül az információk közül egyik sem szerepel a tesztben. Még rosszabb, hogy azt sem tudod, hol vannak ezek az információk, és ezért nem tudod, hogyan kell a külső függőségeket leutánozni, hogy minden egyes futtatásnál ne kelljen újra 100 dollárt fizetni. És új fejlesztőként honnan kellett volna tudnod, hogy amit most fogsz csinálni, az 100 dollárral szegényebbé tesz téged? + +Ez egy kísérteties akció a távolból! + +Nincs más választásod, mint rengeteg forráskódban turkálni, megkérdezni idősebb és tapasztaltabb kollégákat, amíg meg nem érted, hogyan működnek az összefüggések a projektben. +Ennek oka, hogy a `CreditCard` osztály interfészét megnézve nem tudod meghatározni az inicializálandó globális állapotot. Még az osztály forráskódjának megnézése sem árulja el, hogy melyik inicializálási metódust kell meghívni. A legjobb esetben megkereshetjük a globális változót, amelyhez hozzáférünk, és ebből próbálhatjuk kitalálni, hogyan kell inicializálni. + +Egy ilyen projektben az osztályok beteges hazudozók. A fizetési kártya úgy tesz, mintha egyszerűen csak instanciáznád, és meghívnád a `charge()` metódust. Titokban azonban kölcsönhatásba lép egy másik osztállyal, a `PaymentGateway`. Még az interfésze is azt mondja, hogy önállóan inicializálható, de a valóságban valamilyen konfigurációs fájlból húzza a hitelesítő adatokat, és így tovább. +A kódot író fejlesztők számára egyértelmű, hogy a `CreditCard` -nak szüksége van a `PaymentGateway`. Így írták meg a kódot. De bárki számára, aki új a projektben, ez teljes rejtély, és akadályozza a tanulást. + +Hogyan lehet kijavítani a helyzetet? Egyszerűen. **Hagyjuk, hogy az API deklarálja a függőségeket.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Figyeljük meg, hogy a kódon belüli kapcsolatok hirtelen nyilvánvalóvá válnak. Azzal, hogy deklaráljuk, hogy a `charge()` metódusnak szüksége van a `PaymentGateway` címre, senkitől sem kell megkérdeznünk, hogy a kód hogyan függ egymástól. Tudod, hogy egy példányt kell létrehoznod belőle, és amikor megpróbálod ezt megtenni, belefutsz abba, hogy hozzáférési paramétereket kell megadnod. Ezek nélkül a kód nem is futna. + +És ami a legfontosabb, most már le tudja mockolni a fizetési átjárót, hogy ne kelljen 100 dollárt fizetnie minden egyes teszt futtatásakor. + +A globális állapot miatt az objektumaid titokban hozzáférhetnek olyan dolgokhoz, amelyek nincsenek deklarálva az API-jukban, és ennek eredményeképpen az API-id kóros hazudozókká válnak. + +Lehet, hogy eddig nem gondoltál rá így, de valahányszor globális állapotot használsz, titkos vezeték nélküli kommunikációs csatornákat hozol létre. A hátborzongató távoli működés arra kényszeríti a fejlesztőket, hogy minden egyes kódsort elolvassanak a lehetséges interakciók megértéséhez, csökkenti a fejlesztők termelékenységét, és összezavarja az új csapattagokat. +Ha te vagy az, aki a kódot létrehozta, akkor ismered a valódi függőségeket, de bárki, aki utánad jön, tanácstalan. + +Ne írjon olyan kódot, amely globális állapotot használ, inkább adja át a függőségeket. Vagyis a függőségi injektálás. + + +A globális állam törékenysége .[#toc-brittleness-of-the-global-state] +--------------------------------------------------------------------- + +A globális állapotot és singletonokat használó kódban sosem lehetünk biztosak abban, hogy az állapotot mikor és ki változtatta meg. Ez a kockázat már az inicializáláskor fennáll. A következő kódnak egy adatbázis-kapcsolatot kellene létrehoznia és inicializálnia a fizetési átjárót, de folyamatosan kivételt dob, és az okának megtalálása rendkívül fárasztó: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Részletesen át kell nézni a kódot, hogy kiderüljön, hogy a `PaymentGateway` objektum vezeték nélkül más objektumokhoz is hozzáfér, amelyek közül néhányhoz adatbázis-kapcsolat szükséges. Így a `PaymentGateway` előtt inicializálni kell az adatbázist. A globális állapot füstfüggönye azonban ezt elrejti Ön elől. Mennyi időt spórolna meg, ha az egyes osztályok API-ja nem hazudna és nem jelentené be függőségeit? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Hasonló probléma merül fel, amikor globális hozzáférést használunk egy adatbázis-kapcsolathoz: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +A `save()` metódus meghívásakor nem biztos, hogy az adatbázis-kapcsolat már létrejött-e, és ki a felelős a létrehozásáért. Ha például menet közben szeretnénk megváltoztatni az adatbázis-kapcsolatot, esetleg tesztelési céllal, akkor valószínűleg további metódusokat kellene létrehoznunk, például a `DB::reconnect(...)` vagy a `DB::reconnectForTest()` metódusokat. + +Vegyünk egy példát: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Hol lehetünk biztosak abban, hogy a `$article->save()` meghívásakor valóban a tesztadatbázist használjuk ? Mi van, ha a `Foo::doSomething()` módszer megváltoztatta a globális adatbázis-kapcsolatot? Ahhoz, hogy ezt megtudjuk, meg kellene vizsgálnunk a `Foo` osztály és valószínűleg sok más osztály forráskódját. Ez a megközelítés azonban csak rövid távú választ adna, mivel a jövőben változhat a helyzet. + +Mi lenne, ha az adatbázis-kapcsolatot egy statikus változóba helyeznénk át a `Article` osztályon belül? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Ez egyáltalán nem változtat semmit. A probléma egy globális állapot, és nem számít, hogy melyik osztályban rejtőzik. Ebben az esetben, ahogy az előző esetben is, fogalmunk sincs arról, hogy a `$article->save()` metódus meghívásakor milyen adatbázisba íródik. Bárki az alkalmazás távoli végén bármikor megváltoztathatja az adatbázist a `Article::setDb()` segítségével. A mi kezünk alatt. + +A globális állapot miatt az alkalmazásunk **rendkívül törékennyé** válik. + +Van azonban egy egyszerű módja ennek a problémának a kezelésére. Csak az API-nak kell deklarálnia a függőségeket a megfelelő funkcionalitás biztosítása érdekében. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Ez a megközelítés kiküszöböli az adatbázis-kapcsolatok rejtett és váratlan változásai miatti aggodalmat. Most már biztosak vagyunk abban, hogy a cikket hol tároljuk, és semmilyen kódmódosítás egy másik, nem kapcsolódó osztályon belül nem változtathatja meg többé a helyzetet. A kód többé nem törékeny, hanem stabil. + +Ne írjunk olyan kódot, amely globális állapotot használ, inkább adjuk át a függőségeket. Így a függőségi injektálás. + + +Singleton .[#toc-singleton] +--------------------------- + +A singleton egy olyan tervezési minta, amely a híres Gang of Four kiadvány [definíciója |https://en.wikipedia.org/wiki/Singleton_pattern] szerint egy osztályt egyetlen példányra korlátoz, és globális hozzáférést biztosít hozzá. Ennek a mintának a megvalósítása általában a következő kódhoz hasonlít: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // és más metódusok, amelyek az osztály funkcióit hajtják végre. +} +``` + +Sajnos a singleton globális állapotot vezet be az alkalmazásba. És mint fentebb megmutattuk, a globális állapot nem kívánatos. Ezért tekinthető a singleton antipatternnek. + +Ne használjon singletont a kódjában, és helyettesítse más mechanizmusokkal. Tényleg nincs szükséged szingletonokra. Ha azonban garantálnod kell egy osztály egyetlen példányának létezését az egész alkalmazás számára, akkor hagyd ezt a [DI konténerre |container]. +Így hozzon létre egy alkalmazás szingletont, vagy szolgáltatást. Ezáltal az osztály nem fogja biztosítani a saját egyediségét (azaz nem lesz `getInstance()` metódusa és statikus változója), és csak a funkcióit fogja végrehajtani. Így megszűnik az egyetlen felelősség elvének megsértése. + + +Globális állapot a tesztek ellenében .[#toc-global-state-versus-tests] +---------------------------------------------------------------------- + +A tesztek írása során feltételezzük, hogy minden teszt egy izolált egység, és nem kerül bele külső állapot. És semmilyen állapot nem hagyja el a teszteket. Amikor egy teszt befejeződik, a teszthez kapcsolódó állapotot a szemétgyűjtőnek automatikusan el kell távolítania. Ez teszi a teszteket izolálttá. Ezért a teszteket tetszőleges sorrendben futtathatjuk. + +Ha azonban globális állapotok/singletonok vannak jelen, akkor mindezek a szép feltételezések összeomlanak. Egy állapot beléphet és kiléphet egy tesztből. Hirtelen a tesztek sorrendje számíthat. + +Ahhoz, hogy a szingletonokat egyáltalán tesztelni lehessen, a fejlesztőknek gyakran lazítaniuk kell a tulajdonságaikon, például úgy, hogy megengedik, hogy egy példányt egy másikra cseréljenek. Az ilyen megoldások a legjobb esetben is hackek, amelyek nehezen karbantartható és nehezen érthető kódot eredményeznek. Minden olyan tesztnek vagy metódusnak `tearDown()`, amely bármilyen globális állapotot érint, vissza kell vonnia ezeket a változásokat. + +A globális állapot a legnagyobb fejfájás az egységtesztelésben! + +Hogyan lehet megoldani a helyzetet? Egyszerűen. Ne írj olyan kódot, amely singletonokat használ, inkább add át a függőségeket. Vagyis függőségi injektálással. + + +Globális konstansok .[#toc-global-constants] +-------------------------------------------- + +A globális állapot nem korlátozódik a szingletonok és statikus változók használatára, hanem a globális konstansokra is vonatkozhat. + +Azok a konstansok, amelyek értéke nem szolgáltat számunkra új (`M_PI`) vagy hasznos (`PREG_BACKTRACK_LIMIT_ERROR`) információt, egyértelműen rendben vannak. +Ezzel szemben azok a konstansok, amelyek arra szolgálnak, hogy *vezeték nélkül* információt adjunk át a kódon belül, nem többek, mint rejtett függőség. Mint a `LOG_FILE` a következő példában. +A `FILE_APPEND` konstans használata teljesen helyes. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Ebben az esetben a paramétert a `Foo` osztály konstruktorában kell deklarálnunk, hogy az API részévé váljon: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Most már átadhatjuk a naplófájl elérési útvonalára vonatkozó információt, és szükség esetén könnyen módosíthatjuk azt, így könnyebben tesztelhetjük és karbantarthatjuk a kódot. + + +Globális függvények és statikus metódusok .[#toc-global-functions-and-static-methods] +------------------------------------------------------------------------------------- + +Szeretnénk hangsúlyozni, hogy a statikus metódusok és globális függvények használata önmagában nem problémás. A `DB::insert()` és hasonló módszerek használatának helytelenségét már elmagyaráztuk, de mindig is a statikus változóban tárolt globális állapotról volt szó. A `DB::insert()` metódus megköveteli egy statikus változó meglétét, mivel az adatbázis-kapcsolatot tárolja. E változó nélkül lehetetlen lenne a módszer végrehajtása. + +A determinisztikus statikus módszerek és függvények, mint például a `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` és sok más, használata tökéletesen összhangban van a függőségi injektálással. Ezek a függvények mindig ugyanazokat az eredményeket adják vissza ugyanazokból a bemeneti paraméterekből, ezért kiszámíthatóak. Nem használnak semmilyen globális állapotot. + +Vannak azonban a PHP-ben olyan függvények, amelyek nem determinisztikusak. Ezek közé tartozik például a `htmlspecialchars()` függvény. Harmadik paramétere, a `$encoding`, ha nincs megadva, alapértelmezés szerint a `ini_get('default_charset')` konfigurációs opció értékét veszi fel. Ezért ajánlott mindig megadni ezt a paramétert, hogy elkerüljük a függvény esetleges kiszámíthatatlan viselkedését. A Nette következetesen ezt teszi. + +Egyes függvények, mint például a `strtolower()`, `strtoupper()` és hasonlók, a közelmúltban nem determinisztikus viselkedést mutattak, és a `setlocale()` beállításától függtek. Ez sok bonyodalmat okozott, leggyakrabban a török nyelvvel való munka során. +Ennek oka, hogy a török nyelv különbséget tesz a kis- és nagybetűs `I` között ponttal és pont nélkül. Így a `strtolower('I')` a `ı` karaktert, a `strtoupper('i')` pedig a `İ` karaktert adta vissza , ami az alkalmazásokban számos rejtélyes hibát okozott. +Ezt a problémát azonban a PHP 8.2-es verziójában kijavították, és a függvények többé nem függnek a nyelvjárástól. + +Ez egy szép példa arra, hogy a globális állapot fejlesztők ezreit sújtotta világszerte. A megoldás az volt, hogy függőségi injektálással helyettesítették. + + +Mikor lehetséges a globális állapot használata? .[#toc-when-is-it-possible-to-use-global-state] +----------------------------------------------------------------------------------------------- + +Vannak bizonyos speciális helyzetek, amikor lehetséges a globális állapot használata. Például kód hibakereséskor, amikor ki kell dobni egy változó értékét, vagy meg kell mérni a program egy adott részének időtartamát. Ilyen esetekben, amelyek olyan ideiglenes műveletekre vonatkoznak, amelyeket később eltávolítunk a kódból, jogos egy globálisan elérhető dumper vagy stopperóra használata. Ezek az eszközök nem részei a kódtervezésnek. + +Egy másik példa a `preg_*`, a reguláris kifejezésekkel való munkavégzésre szolgáló függvények, amelyek a lefordított reguláris kifejezéseket belsőleg egy statikus gyorsítótárban tárolják a memóriában. Ha ugyanazt a reguláris kifejezést a kód különböző részeiben többször hívja meg, akkor csak egyszer fordítja le. A gyorsítótár teljesítményt takarít meg, és a felhasználó számára is teljesen láthatatlan, így az ilyen használat jogosnak tekinthető. + + +Összefoglaló .[#toc-summary] +---------------------------- + +Megmutattuk, miért van értelme + +1) Távolítsunk el minden statikus változót a kódból. +2) Deklaráljuk a függőségeket +3) És használjuk a függőségi injektálást + +Amikor a kódtervezésről gondolkodik, tartsa szem előtt, hogy minden egyes `static $foo` egy problémát jelent. Ahhoz, hogy a kódod DI-tisztelő környezet legyen, elengedhetetlen a globális állapot teljes kiirtása és függőségi injektálással való helyettesítése. + +E folyamat során előfordulhat, hogy egy osztályt fel kell osztanod, mert egynél több felelőssége van. Ne aggódjon emiatt; törekedjen az egy felelősség elvére. + +*Köszönöm Miško Hevery-nek, akinek olyan cikkei, mint a [Flaw: Brittle Global State & Singletons (Hiba: Törékeny globális állapot és szingletonok |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] ) képezik e fejezet alapját.* diff --git a/dependency-injection/hu/introduction.texy b/dependency-injection/hu/introduction.texy index 72bc88aff3..f46cc6195d 100644 --- a/dependency-injection/hu/introduction.texy +++ b/dependency-injection/hu/introduction.texy @@ -2,17 +2,17 @@ Mi az a Dependency Injection? ***************************** .[perex] -Ez a fejezet bemutatja azokat az alapvető programozási gyakorlatokat, amelyeket bármilyen alkalmazás írása során követni kell. Ezek az alapok szükségesek ahhoz, hogy tiszta, érthető és karbantartható kódot írhassunk. +Ez a fejezet megismerteti Önt azokkal az alapvető programozási gyakorlatokkal, amelyeket bármilyen alkalmazás írása során követnie kell. Ezek az alapok szükségesek a tiszta, érthető és karbantartható kód írásához. -Ha megtanulod és betartod ezeket a szabályokat, a Nette minden lépésnél a segítségedre lesz. El fogja végezni helyetted a rutinfeladatokat, és a lehető legkényelmesebbé teszi, hogy magára a logikára koncentrálhass. +Ha megtanulod és betartod ezeket a szabályokat, a Nette minden lépésnél a segítségedre lesz. El fogja végezni helyetted a rutinfeladatokat, és maximális kényelmet biztosít, így magára a logikára koncentrálhatsz. -Az elvek, amelyeket itt bemutatunk, meglehetősen egyszerűek. Nincs miért aggódnia. +Az elvek, amelyeket itt bemutatunk, meglehetősen egyszerűek. Nem kell aggódnia semmi miatt. Emlékszel az első programodra? .[#toc-remember-your-first-program] ------------------------------------------------------------------ -Fogalmunk sincs, milyen nyelven írtad, de ha PHP volt, akkor valószínűleg valahogy így nézhetett ki: +Nem tudjuk, hogy milyen nyelven írta, de ha PHP volt, akkor valahogy így nézhetett ki: ```php function osszeg(float $a, float $b): float @@ -25,31 +25,31 @@ echo osszeg(23, 1); // 24-et ír ki. Néhány triviális kódsor, de nagyon sok kulcsfogalom rejtőzik benne. Hogy vannak változók. Hogy a kódot kisebb egységekre bontják, amelyek például függvények. Hogy bemeneti argumentumokat adunk át nekik, és eredményt adnak vissza. Csak a feltételek és a ciklusok hiányoznak. -Az, hogy bemeneti adatokat adunk át egy függvénynek, és az visszaad egy eredményt, egy tökéletesen érthető fogalom, amelyet más területeken, például a matematikában is használnak. +Az a tény, hogy egy függvény bemeneti adatokat fogad el, és egy eredményt ad vissza, tökéletesen érthető fogalom, amelyet más területeken, például a matematikában is használnak. -Egy függvénynek van egy szignatúrája, amely a nevéből, a paraméterek listájából és azok típusából, végül pedig a visszatérési érték típusából áll. Felhasználóként minket a szignatúra érdekel, a belső megvalósításról általában semmit sem kell tudnunk. +Egy függvénynek van egy szignatúrája, amely a nevéből, a paraméterek listájából és azok típusából, végül pedig a visszatérési érték típusából áll. Felhasználóként minket a szignatúra érdekel, és általában nem kell tudnunk semmit a belső megvalósításról. -Most képzeljük el, hogy egy függvény szignója így néz ki: +Most képzeljük el, hogy a függvény aláírás így néz ki: ```php function osszeg(float $x): float ``` -Egy összeadás egy paraméterrel? Ez furcsa... Mit szólsz ehhez? +Egyetlen paraméteres kiegészítés? Ez furcsa... Mi a helyzet ezzel? ```php function osszeg(): float ``` -Ez tényleg furcsa, nem? Mit gondolsz, hogyan használják a funkciót? +Ez tényleg furcsa, nem? Hogyan használják a funkciót? ```php echo osszeg(); // mit ír ki? ``` -Ha ilyen kódot nézünk, összezavarodunk. Nem csak egy kezdő nem értené, még egy gyakorlott programozó sem értené az ilyen kódot. +Ha ilyen kódot néznénk, összezavarodnánk. Nemcsak egy kezdő nem értené, de még egy tapasztalt programozó sem értené az ilyen kódot. -Vajon hogyan nézne ki egy ilyen függvény valójában belülről? Honnan szerezné meg az összeadókat? Valószínűleg *valahogyan* magától szerezné meg őket, például így: +Kíváncsi vagy, hogy egy ilyen függvény valójában hogyan nézne ki belülről? Honnan szerezné meg az összegzőket? Valószínűleg *valahogyan* magától szerezné meg őket, talán így: ```php function osszeg(): float @@ -66,13 +66,13 @@ Kiderült, hogy a függvény testében rejtett kötések vannak más függvénye Ne erre! .[#toc-not-this-way] ----------------------------- -Az imént bemutatott dizájn számos negatív tulajdonság lényege: +Az imént bemutatott dizájn sok negatív tulajdonság lényege: -- a függvény aláírás úgy tett, mintha nem lenne szüksége addendumokra, ami összezavart minket. -- fogalmunk sincs, hogyan lehetne a függvényt két másik számmal kiszámítani -- bele kellett néznünk a kódba, hogy lássuk, hova veszi az addendeket -- rejtett kötéseket fedeztünk fel -- a teljes megértéshez ezeket a kötéseket is fel kell tárnunk. +- A függvény aláírás úgy tett, mintha nem lenne szüksége az összegzőkre, ami összezavart minket. +- fogalmunk sincs, hogyan lehetne a függvényt két másik számmal kiszámítani. +- meg kellett néznünk a kódot, hogy rájöjjünk, honnan származnak az összegzők +- rejtett függőségeket találtunk +- a teljes megértéshez ezeket a függőségeket is meg kell vizsgálni És egyáltalán az összeadási függvény feladata a bemenetek beszerzése? Természetesen nem az. Az ő feladata csak az összeadás. @@ -93,20 +93,20 @@ function osszeg(float $a, float $b): float A legfontosabb szabály: **minden adatot, amelyre a függvényeknek vagy osztályoknak szükségük van, át kell adni nekik**. -Ahelyett, hogy rejtett mechanizmusokat találnánk ki, hogy valahogyan maguk jussanak hozzá, egyszerűen adjuk át a paramétereket. Ezzel megspórolod a rejtett módok kitalálásához szükséges időt, ami biztosan nem javítja a kódodat. +Ahelyett, hogy rejtett módokat találnának ki, hogy maguk férjenek hozzá az adatokhoz, egyszerűen adja át a paramétereket. Ezzel időt takarít meg, amelyet rejtett utak kitalálásával töltene, amelyek biztosan nem javítanának a kódján. -Ha ezt a szabályt mindig és mindenhol betartod, akkor már úton vagy a rejtett kötések nélküli kód felé. Olyan kód felé, amely nemcsak a szerző számára érthető, hanem mindenki számára, aki utólag elolvassa. Ahol minden érthető a függvények és osztályok aláírásából, és nem kell rejtett titkokat keresni az implementációban. +Ha mindig és mindenhol követi ezt a szabályt, akkor máris úton van a rejtett függőségek nélküli kód felé. Olyan kódhoz, amely nemcsak a szerző, hanem mindenki számára érthető, aki utólag elolvassa. Ahol minden érthető a függvények és osztályok szignatúrájából, és nem kell rejtett titkokat keresni az implementációban. -Ezt a technikát szakszerűen **függőségi injektálásnak** nevezik. Az adatokat pedig **függőségeknek hívják.** De ez egy egyszerű paraméterátadás, semmi több. +Ezt a technikát szaknyelven **függőségi injektálásnak** nevezik. Ezeket az adatokat pedig **függőségeknek** nevezik. Ez csak közönséges paraméterátadás, semmi több. .[note] -Kérlek, ne keverd össze a függőségi injektálást, ami egy tervezési minta, a "függőségi injektáló konténerrel", ami egy eszköz, valami teljesen más. A konténerekről később fogunk beszélni. +Kérlek, ne keverd össze a függőségi injektálást, ami egy tervezési minta, a "függőségi injektálási konténerrel", ami egy eszköz, valami szögesen más. A konténerekkel később fogunk foglalkozni. A függvényektől az osztályokig .[#toc-from-functions-to-classes] ---------------------------------------------------------------- -És hogyan kapcsolódnak ehhez az osztályok? Egy osztály összetettebb entitás, mint egy egyszerű függvény, de az 1. szabály itt is érvényes. Csak [több módja van az argumentumok átadásának |passing-dependencies]. Például egészen hasonlóan, mint egy függvény esetében: +És hogyan kapcsolódnak az osztályok? Egy osztály összetettebb egység, mint egy egyszerű függvény, de az 1. szabály itt is teljes mértékben érvényes. Csak [több módja van az argumentumok átadásának |passing-dependencies]. Például egészen hasonlóan, mint egy függvény esetében: ```php class Matematika @@ -121,7 +121,7 @@ $math = new Matematika; echo $math->osszeg(23, 1); // 24 ``` -Vagy más metódusok használatával, vagy közvetlenül a konstruktor által: +Vagy más metódusokon keresztül, vagy közvetlenül a konstruktoron keresztül: ```php class Osszeg @@ -149,9 +149,9 @@ Mindkét példa teljesen megfelel a függőségi injektálásnak. Valós életbeli példák .[#toc-real-life-examples] ------------------------------------------------ -A való világban nem fogsz osztályokat írni számok összeadására. Térjünk át a valós világbeli példákra. +A való világban nem fogsz számok összeadására szolgáló osztályokat írni. Térjünk át a gyakorlati példákra. -Legyen egy `Article` osztályunk, amely egy blogcikket reprezentál: +Legyen egy `Article` osztályunk, amely egy blogbejegyzést reprezentál: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -A `save()` módszer elmenti a cikket egy adatbázis-táblába. A megvalósítás a [Nette Database |database:] segítségével gyerekjáték lenne, ha nem lenne egy bökkenő: honnan szerezze meg a `Article` az adatbázis-kapcsolatot, azaz a `Nette\Database\Connection` osztály objektumát ? +A `save()` módszer elmenti a cikket egy adatbázis-táblába. Ennek megvalósítása a [Nette Database |database:] segítségével gyerekjáték lesz, ha nem lenne egy bökkenő: honnan szerzi meg a `Article` az adatbázis-kapcsolatot, azaz egy `Nette\Database\Connection` osztályú objektumot ? -Úgy tűnik, sok lehetőségünk van. Veheti valahonnan egy statikus változóból. Vagy örökli egy olyan osztályból, amelyik biztosítja az adatbázis-kapcsolatot. Vagy kihasználja egy [singleton |global-state#Singleton] előnyeit. Vagy az úgynevezett facades-t, amit a Laravelben használnak: +Úgy tűnik, rengeteg lehetőségünk van. Valahonnan egy statikus változóból is veheti. Vagy egy olyan osztályból örökli, amely adatbázis-kapcsolatot biztosít. Vagy kihasználja egy [singleton |global-state#Singleton] előnyeit. Vagy használhatunk úgynevezett facades-t, amit a Laravelben használnak: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ Nagyszerű, megoldottuk a problémát. Vagy mégis? -Emlékezzünk vissza az [1. szabályra: hadd adassák át neked |#rule #1: Let It Be Passed to You]: minden függőséget, amire az osztálynak szüksége van, át kell adni neki. Mert ha nem tesszük, és megszegjük a szabályt, akkor elindultunk a rejtett kötöttségekkel teli, piszkos kódhoz vezető úton, amely tele van rejtett kötöttségekkel, érthetetlenséggel, és az eredmény egy olyan alkalmazás lesz, amelynek a karbantartása és a fejlesztése csak kínszenvedés. +Emlékezzünk vissza az [1. szabályra: Legyen átadva |#rule #1: Let It Be Passed to You]: minden függőséget, amire az osztálynak szüksége van, át kell adni neki. Mert ha megszegjük ezt a szabályt, akkor elindultunk a rejtett függőségekkel teli, piszkos kódhoz vezető úton, amely tele van rejtett függőségekkel, érthetetlenséggel, és az eredmény egy olyan alkalmazás lesz, amelyet fájdalmas lesz karbantartani és fejleszteni. -A `Article` osztály felhasználójának fogalma sincs, hogy a `save()` metódus hol tárolja a cikket. Egy adatbázis táblában? Melyikben, a termelési vagy a fejlesztési? És hogyan lehet ezt megváltoztatni? +A `Article` osztály felhasználójának fogalma sincs arról, hogy a `save()` metódus hol tárolja a cikket. Egy adatbázis táblában? Melyikben, a termelési vagy a tesztelésben? És hogyan lehet ezt megváltoztatni? -A felhasználónak meg kell néznie, hogyan van implementálva a `save()` metódus, hogy megtalálja a `DB::insert()` metódus használatát. Tehát tovább kell keresnie, hogy megtudja, hogyan szerez ez a módszer adatbázis-kapcsolatot. A rejtett kötések pedig elég hosszú láncot alkothatnak. +A felhasználónak meg kell néznie, hogyan van implementálva a `save()` metódus, és meg kell találnia a `DB::insert()` metódus használatát. Tehát tovább kell keresnie, hogy megtudja, hogyan szerez ez a módszer adatbázis-kapcsolatot. A rejtett függőségek pedig elég hosszú láncot alkothatnak. -A rejtett kötések, Laravel fakciók vagy statikus változók soha nincsenek jelen a tiszta, jól megtervezett kódban. A tiszta és jól megtervezett kódban az argumentumok átadásra kerülnek: +A tiszta és jól megtervezett kódban soha nincsenek rejtett függőségek, Laravel-facadek vagy statikus változók. A tiszta és jól megtervezett kódban az argumentumok átadásra kerülnek: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Még praktikusabb, ahogy azt a következőkben látni fogjuk, ha konstruktort használunk: +Egy még praktikusabb megközelítés, mint később látni fogjuk, a konstruktoron keresztül történik: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Ha tapasztalt programozó vagy, talán arra gondolsz, hogy a `Article` egyáltalán nem kellene, hogy legyen `save()` metódus, hanem egy tiszta adatkomponensnek kellene lennie, és egy külön tárolónak kellene gondoskodnia a tárolásról. Ennek van értelme. De ez jóval túlmutatna a témán, ami a függőségi injektálás, és megpróbálnánk egyszerű példákat adni. +Ha tapasztalt programozó vagy, azt gondolhatod, hogy a `Article` egyáltalán nem kellene, hogy rendelkezzen `save()` metódussal; tisztán adatkomponenst kellene képviselnie, és egy külön tárolónak kellene gondoskodnia a mentésről. Ennek van is értelme. De ez messze túlmutatna a téma keretein, ami a függőségi injektálás, és az egyszerű példák bemutatására tett erőfeszítésen. -Ha például olyan osztályt írsz, aminek a működéséhez adatbázisra van szükséged, akkor ne azt találd ki, hogy honnan szerezd meg, hanem azt add át magadnak. Esetleg egy konstruktor vagy más metódus paramétereként. Deklaráld a függőségeket. Mutassa ki őket az osztálya API-jában. Érthető és kiszámítható kódot kapsz. +Ha olyan osztályt írsz, aminek a működéséhez például adatbázisra van szüksége, ne találd ki, honnan szerzed be, hanem legyen átadva. Vagy a konstruktor paramétereként, vagy egy másik metódus paramétereként. Ismerd el a függőségeket. Ismerd el őket az osztályod API-jában. Érthető és kiszámítható kódot fogsz kapni. -Mit szólsz ehhez az osztályhoz, amely hibaüzeneteket naplóz: +És mi a helyzet ezzel az osztállyal, amely hibaüzeneteket naplóz: ```php class Logger @@ -266,9 +266,9 @@ Mit gondolsz, betartottuk az [1. számú szabályt: hadd adassák át neked |#ru Nem tettük meg. -A kulcsinformációt, a naplófájl könyvtárát az osztály a konstansból *kapja*. +A kulcsinformációt, azaz a naplófájlt tartalmazó könyvtárat maga az osztály *kapja* a konstansból. -Lásd a példahasználatot: +Nézd meg a használati példát: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -A megvalósítás ismerete nélkül tudna válaszolni arra a kérdésre, hogy hová íródnak az üzenetek? Ez azt sugallná, hogy a LOG_DIR konstans létezése szükséges a működéshez? És tudnál-e létrehozni egy második példányt, ami más helyre ír? Természetesen nem. +A megvalósítás ismerete nélkül tudna válaszolni arra a kérdésre, hogy hová íródnak az üzenetek? Gondolnád, hogy a `LOG_DIR` állandó létezése szükséges a működéséhez? És tudnál-e létrehozni egy második példányt, amely más helyre írna? Biztosan nem. Javítsuk meg az osztályt: @@ -295,7 +295,7 @@ class Logger } ``` -Az osztály most már sokkal világosabb, jobban konfigurálható és ezért hasznosabb. +Az osztály most már sokkal érthetőbb, konfigurálhatóbb és ezáltal hasznosabb. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); De nem érdekel! .[#toc-but-i-don-t-care] ---------------------------------------- -*"Amikor létrehozok egy cikkobjektumot és meghívom a save() parancsot, nem akarok foglalkozni az adatbázissal, csak azt akarom, hogy a konfigurációban beállított adatbázisba mentődjön. "* +*"Amikor létrehozok egy cikkobjektumot, és meghívom a save() parancsot, nem akarok foglalkozni az adatbázissal; csak azt akarom, hogy abba az adatbázisba kerüljön elmentésre, amelyet a konfigurációban beállítottam."* -*"Amikor a Logger-t használom, csak azt akarom, hogy az üzenet kiírásra kerüljön, és nem akarok foglalkozni azzal, hogy hova. Legyenek a globális beállítások használva. "* +*"Amikor a Logger-t használom, csak azt akarom, hogy az üzenet kiírásra kerüljön, és nem akarok foglalkozni azzal, hogy hova. Legyen a globális beállítások használata."* -Ezek helyes megjegyzések. +Ezek jogos észrevételek. -Példaként vegyünk egy olyan osztályt, amely hírleveleket küld ki, és naplózzuk, hogyan ment ez: +Példaként nézzünk meg egy olyan osztályt, amely hírleveleket küld és naplózza, hogyan ment: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -A továbbfejlesztett `Logger`, amely már nem használja a `LOG_DIR` állandót, megköveteli a fájl elérési útvonalát a konstruktorban. Hogyan lehet ezt megoldani? A `NewsletterDistributor` osztályt nem érdekli, hogy hova íródnak az üzenetek, csak írni akarja őket. +A továbbfejlesztett `Logger`, amely már nem használja a `LOG_DIR` konstanst, megköveteli a fájl elérési útvonalának megadását a konstruktorban. Hogyan lehet ezt megoldani? A `NewsletterDistributor` osztályt nem érdekli, hogy az üzenetek hova íródnak, csak írni akarja őket. -A megoldás ismét az [1. szabály: hadd adassák át neked |#rule #1: Let It Be Passed to You]: adj át neki minden adatot, amire az osztálynak szüksége van. +A megoldás ismét az [1. szabály: Hagyd, hogy átadják n |#rule #1: Let It Be Passed to You]eked: adj át minden adatot, amire az osztálynak szüksége van. -Tehát átadjuk a napló elérési útvonalát a konstruktornak, amivel aztán létrehozzuk a `Logger` objektumot ? +Ez tehát azt jelenti, hogy a konstruktoron keresztül átadjuk a napló elérési útvonalát, amit aztán a `Logger` objektum létrehozásakor használunk? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Nem így! Mert az elérési út **nem** tartozik azokhoz az adatokhoz, amelyekre a `NewsletterDistributor` osztálynak szüksége van, hanem a `Logger`. Az osztálynak magára a loggerre van szüksége. És ezt fogjuk továbbadni: +Nem, nem így! Az útvonal nem tartozik a `NewsletterDistributor` osztály számára szükséges adatok közé, sőt, a `Logger` osztálynak van rá szüksége. Látod a különbséget? A `NewsletterDistributor` osztálynak magára a naplózóra van szüksége. Tehát ezt fogjuk átadni: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Most már a `NewsletterDistributor` osztály aláírásából egyértelmű, hogy a naplózás része a funkcionalitásnak. És az a feladat, hogy a naplózót egy másikkal helyettesítsük, esetleg tesztelési céllal, elég triviális. -Ráadásul, ha a `Logger` osztály konstruktorát megváltoztatjuk, az nem lesz hatással a mi osztályunkra. +A `NewsletterDistributor` osztály aláírásaiból egyértelmű, hogy a naplózás is része a funkcióinak. És a logger cseréje egy másikra, esetleg tesztelés céljából, teljesen triviális feladat. +Ráadásul, ha a `Logger` osztály konstruktora megváltozik, az nem befolyásolja a mi osztályunkat. -2. szabály: Vedd el, ami a tiéd .[#toc-rule-2-take-what-is-yours] ------------------------------------------------------------------ +2. szabály: Vedd el, ami a tiéd .[#toc-rule-2-take-what-s-yours] +---------------------------------------------------------------- -Ne hagyd magad félrevezetni, és ne hagyd, hogy a függőségek paramétereit átadják neked. Adja át közvetlenül a függőségeket. +Ne hagyd magad félrevezetni, és ne engedd át magad a függőségeid függőségein. Csak a saját függőségeidet add át. -Ezáltal a más objektumokat használó kód teljesen független lesz a konstruktoraik módosításaitól. Az API-ja igazabb lesz. És ami a legfontosabb, triviális lesz ezeket a függőségeket másokra cserélni. +Ennek köszönhetően a más objektumokat használó kód teljesen független lesz a konstruktoraikban bekövetkező változásoktól. Az API-ja sokkal igazabb lesz. És mindenekelőtt triviális lesz ezeket a függőségeket másokkal helyettesíteni. -A család új tagja .[#toc-a-new-member-of-the-family] ----------------------------------------------------- +Új családtag .[#toc-new-family-member] +-------------------------------------- -A fejlesztőcsapat úgy döntött, hogy létrehoz egy második loggert, amely az adatbázisba ír. Létrehozunk tehát egy `DatabaseLogger` osztályt. Tehát két osztályunk van, a `Logger` és a `DatabaseLogger`, az egyik egy fájlba ír, a másik az adatbázisba ... nem gondolod, hogy van valami furcsa ebben a névben? -Nem lenne jobb, ha átneveznénk a `Logger` -t `FileLogger`-re ? Dehogynem. +A fejlesztőcsapat úgy döntött, hogy létrehoz egy második loggert, amely az adatbázisba ír. Ezért létrehozunk egy `DatabaseLogger` osztályt. Tehát két osztályunk van, `Logger` és `DatabaseLogger`, az egyik egy fájlba ír, a másik az adatbázisba ... nem tűnik furcsának az elnevezés? +Nem lenne jobb átnevezni a `Logger` -t `FileLogger`-re ? Határozottan igen. -De csináljuk okosan. Létrehozunk egy interfészt az eredeti név alatt: +De tegyük ezt okosan. Létrehozunk egy interfészt az eredeti név alatt: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...amit mindkét naplózó implementál: +... amelyet mindkét logger végrehajt: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -És így semmit sem kell változtatni a kód többi részén, ahol a logger használatban van. Például a `NewsletterDistributor` osztály konstruktora továbbra is elégedett lesz azzal, hogy paraméterként a `Logger` címet kéri. És rajtunk múlik majd, hogy melyik példányt adjuk át neki. +És emiatt nem kell semmit sem változtatni a kód többi részén, ahol a naplózót használjuk. Például a `NewsletterDistributor` osztály konstruktora továbbra is megelégszik azzal, hogy paraméterként a `Logger` címet kéri. Az pedig csak rajtunk múlik majd, hogy melyik példányt adjuk át. -**Ez az oka annak, hogy soha nem adunk interfész neveknek `Interface` utótagot vagy `I` előtagot.** Máskülönben lehetetlen lenne ilyen szépen kódot fejleszteni. +**Ez az oka annak, hogy soha nem adjuk hozzá a `Interface` utótagot vagy a `I` előtagot az interfésznevekhez.** Máskülönben nem lehetne ilyen szépen fejleszteni a kódot. Houston, van egy problémánk .[#toc-houston-we-have-a-problem] ------------------------------------------------------------- -Míg az egész alkalmazásban megelégedhetünk egyetlen loggerpéldánnyal, legyen az fájl vagy adatbázis, és egyszerűen átadhatjuk azt bárhol, ahol valamit naplózni kell, addig a `Article` osztály esetében ez egészen másképp van. Valójában szükség szerint hozunk létre példányokat belőle, esetleg többször is. Hogyan kezeljük az adatbázis-kötést a konstruktorában? +Míg a logger egyetlen példányával - legyen az fájl- vagy adatbázis-alapú - az egész alkalmazásban boldogulhatunk, és egyszerűen átadhatjuk azt mindenhol, ahol valamit naplózni kell, addig a `Article` osztály esetében ez teljesen másképp van. Annak példányait szükség szerint hozzuk létre, akár többször is. Hogyan kezeljük az adatbázis-függőséget a konstruktorában? -Példaként használhatunk egy olyan vezérlőt, amelynek egy űrlap elküldése után egy cikket kell elmentenie az adatbázisba: +Egy példa lehet egy olyan vezérlő, amelynek egy űrlap elküldése után egy cikket kell elmentenie az adatbázisba: ```php class EditController extends Controller @@ -437,9 +437,9 @@ class EditController extends Controller } ``` -Egy lehetséges megoldás közvetlenül kínálkozik: az adatbázis-objektumot a konstruktor adja át a `EditController` címre, és használja a `$article = new Article($this->db)` címet. +Egy lehetséges megoldás kézenfekvő: adjuk át az adatbázis-objektumot a `EditController` konstruktornak, és használjuk a `$article = new Article($this->db)` címet. -Az előző esethez hasonlóan a `Logger` és a fájl elérési útvonalával kapcsolatban ez nem a helyes megközelítés. Az adatbázis nem a `EditController`, hanem a `Article` függvénye. Az adatbázis átadása tehát ellentétes a [2. szabállyal: vedd el, ami a tiéd |#rule #2: take what is yours]. Ha a `Article` osztály konstruktora megváltozik (új paramétert adunk hozzá), akkor a kódot is módosítani kell minden olyan helyen, ahol példányok jönnek létre. Ufff. +Csakúgy, mint az előző esetben a `Logger` és a fájl elérési útvonalával, ez nem a helyes megközelítés. Az adatbázis nem a `EditController`, hanem a `Article` függvénye. Az adatbázis átadása ellentétes a [2. szabállyal: vedd el, ami a tiéd |#rule #2: take what's yours]. Ha a `Article` osztály konstruktora megváltozik (új paramétert adunk hozzá), akkor a kódot módosítani kell, ahol a példányok létrehozására sor kerül. Ufff. Houston, mit javasolsz? @@ -447,7 +447,7 @@ Houston, mit javasolsz? 3. szabály: Hagyd, hogy a gyár kezelje a dolgot .[#toc-rule-3-let-the-factory-handle-it] ---------------------------------------------------------------------------------------- -A rejtett kötések eltávolításával és az összes függőség argumentumként való átadásával sokkal konfigurálhatóbb és rugalmasabb osztályokat kapunk. És így valami másra van szükségünk, hogy létrehozzuk és konfiguráljuk ezeket a rugalmasabb osztályokat. Ezt nevezzük gyáraknak. +A rejtett függőségek kiküszöbölésével és az összes függőség argumentumként való átadásával sokkal konfigurálhatóbb és rugalmasabb osztályokat kaptunk. És ezért szükségünk van valami másra, ami ezeket a rugalmasabb osztályokat létrehozza és konfigurálja helyettünk. Ezt nevezzük gyáraknak. Az ökölszabály a következő: ha egy osztály függőségekkel rendelkezik, a példányaik létrehozását bízzuk a gyárra. @@ -460,7 +460,7 @@ Kérjük, ne keverjük össze a *factory method* tervezési mintával, amely a g Gyár .[#toc-factory] -------------------- -A gyár egy olyan metódus vagy osztály, amely objektumokat állít elő és konfigurál. A `Article` termelő osztályt `ArticleFactory` nevezzük, és ez így nézhet ki: +A gyár egy olyan metódus vagy osztály, amely objektumokat hoz létre és konfigurál. A `Article` -t előállító osztályt `ArticleFactory`-nek fogjuk nevezni, és így nézhet ki: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -A vezérlőben való használata a következő lenne: +A vezérlőben való használata a következő: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -Ekkor, amikor a `Article` osztály konstruktorának aláírása megváltozik, a kód egyetlen része, amelynek reagálnia kell, maga a `ArticleFactory` gyár. Minden más, a `Article` objektumokkal dolgozó kódot, például a `EditController`, ez nem érinti. +Ezen a ponton, ha a `Article` osztály konstruktorának aláírása megváltozik, a kód egyetlen része, amelyre reagálnia kell, maga a `ArticleFactory`. A `Article` objektumokkal dolgozó minden más kódot, például a `EditController`, ez nem érinti. -Lehet, hogy most a homlokodat kopogtatod, hogy vajon segítettünk-e egyáltalán magunkon. A kód mennyisége megnőtt, és az egész dolog kezd gyanúsan bonyolultnak tűnni. +Talán elgondolkodik azon, hogy valóban jobbá tettük-e a dolgokat. A kód mennyisége megnőtt, és az egész kezd gyanúsan bonyolultnak tűnni. -Ne aggódj, hamarosan rátérünk a Nette DI konténerre. És számos olyan ász van a tarsolyában, amelyek rendkívül egyszerűvé teszik a függőségi injektálást használó alkalmazások építését. Például a `ArticleFactory` osztály helyett elég lesz [egy egyszerű interfészt írni |factory]: +Ne aggódjon, hamarosan eljutunk a Nette DI konténerhez. És ez számos trükköt tartogat a tarsolyában, ami nagyban leegyszerűsíti a függőségi injektálást használó alkalmazások építését. Például a `ArticleFactory` osztály helyett csak [egy egyszerű interfészt |factory] kell [írni |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -De előreszaladtunk, várjunk csak :-) +De most már elébe megyünk a dolgoknak, kérlek, legyetek türelemmel :-) Összefoglaló .[#toc-summary] ---------------------------- -A fejezet elején azt ígértük, hogy megmutatjuk, hogyan tervezhetünk tiszta kódot. Csak adjuk meg az osztályokat +A fejezet elején azt ígértük, hogy bemutatjuk a tiszta kód tervezésének folyamatát. Mindössze annyit kell tennünk, hogy az osztályok: -- [a szükséges függőségeket |#Rule #1: Let It Be Passed to You] -- [és ne azt, amire nincs közvetlen szükségük |#Rule #2: Take What Is Yours] -- [és hogy a függőségekkel rendelkező objektumok a legjobb, ha gyárakban készülnek |#Rule #3: Let the Factory Handle it]. +- [átadják a szükséges függőségeket |#Rule #1: Let It Be Passed to You] +- [fordítva, ne adják át azt, amire nincs közvetlen szükségük |#Rule #2: Take What's Yours] +- [és hogy a függőségekkel rendelkező objektumokat a legjobb gyárakban létrehozni |#Rule #3: Let the Factory Handle it] -Első pillantásra talán nem így tűnik, de ennek a három szabálynak messzemenő következményei vannak. A kódtervezés gyökeresen eltérő szemléletéhez vezetnek. Megéri ez? Azok a programozók, akik kidobták a régi szokásokat, és elkezdték következetesen használni a függőségi injektálást, ezt szakmai életük sorsfordító pillanatának tekintik. A tiszta és fenntartható alkalmazások világát nyitotta meg. +Első pillantásra úgy tűnhet, hogy ennek a három szabálynak nincsenek messzemenő következményei, de a kódtervezés gyökeresen más szemléletéhez vezetnek. Megéri ez? Azok a fejlesztők, akik felhagytak a régi szokásokkal, és következetesen használni kezdték a függőségi injektálást, ezt a lépést szakmai életük döntő pillanatának tekintik. Megnyitotta előttük a letisztult és karbantartható alkalmazások világát. -De mi van akkor, ha a kód nem következetesen használja a függőségi injektálást? Mi van, ha statikus metódusokra vagy singletonokra épül? Hozhat ez problémákat? [Igen, és ez nagyon jelentős |global-state]. +De mi van akkor, ha a kód nem használja következetesen a függőségi injektálást? Mi van, ha statikus metódusokra vagy singletonokra támaszkodik? Okoz ez problémát? [Igen, és nagyon alapvető problémákat okoz |global-state]. diff --git a/dependency-injection/hu/passing-dependencies.texy b/dependency-injection/hu/passing-dependencies.texy index ebbdf7a32e..a426543d32 100644 --- a/dependency-injection/hu/passing-dependencies.texy +++ b/dependency-injection/hu/passing-dependencies.texy @@ -12,7 +12,7 @@ Az argumentumok, vagy DI terminológiában "függőségek", a következő főbb </div> -Az első három módszer általánosságban minden objektumorientált nyelvben alkalmazható, a negyedik a Nette prezenterekre jellemző, ezért [külön fejezetben |best-practices:inject-method-attribute] tárgyaljuk. A következőkben mindegyik lehetőséget közelebbről megvizsgáljuk és konkrét példákon keresztül bemutatjuk. +Most konkrét példákkal illusztráljuk a különböző változatokat. Konstruktor-befecskendezés .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Konstruktor-befecskendezés .[#toc-constructor-injection] A függőségek az objektum létrehozásakor argumentumként kerülnek átadásra a konstruktornak: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Ez a forma olyan kötelező függőségek esetében hasznos, amelyekre az osztálynak feltétlenül szüksége van a működéshez, mivel nélkülük a példány nem hozható létre. @@ -40,10 +40,10 @@ A PHP 8.0 óta használhatunk egy rövidebb jelölési formát, amely funkcioná ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ A PHP 8.1 óta egy tulajdonságot megjelölhetünk egy `readonly` jelzővel, ame ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService A DI konténer automatikusan átadja a függőségeket a konstruktornak az [autowiring |autowiring] segítségével. Az ilyen módon nem átadható argumentumok (pl. stringek, számok, booleans) [a konfigurátorban íródnak |services#Arguments]. +Constructor Hell .[#toc-constructor-hell] +----------------------------------------- + +A *construktor hell* kifejezés arra a helyzetre utal, amikor egy gyermek egy olyan szülő osztálytól örököl, amelynek konstruktora függőségeket igényel, és a gyermek is függőségeket igényel. A szülő függőségeit is át kell vennie és tovább kell adnia: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +A probléma akkor jelentkezik, amikor a `BaseClass` osztály konstruktorát meg akarjuk változtatni, például egy új függőség hozzáadásakor. Ekkor a gyerekek összes konstruktorát is módosítani kell. Ami pokollá teszi az ilyen módosítást. + +Hogyan lehet ezt megelőzni? A megoldás az, hogy **prioritást adunk a kompozíciónak az örökléssel szemben**. + +Tehát tervezzük meg a kódot másképp. Kerüljük az absztrakt `Base*` osztályokat. Ahelyett, hogy a `MyClass` a `BaseClass` örökölése révén kapna bizonyos funkciókat, ahelyett, hogy a függőségként kapná meg ezeket a funkciókat: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Setter injektálás .[#toc-setter-injection] ========================================== -A függőségek átadása egy olyan metódus meghívásával történik, amely egy privát tulajdonságban tárolja őket. Ezeknek a metódusoknak a szokásos elnevezési konvenciója a `set*()`, ezért hívják őket settereknek. +A függőségek átadása egy olyan metódus meghívásával történik, amely egy privát tulajdonságban tárolja őket. Ezeknek a metódusoknak a szokásos elnevezési konvenciója a `set*()`, ezért hívják őket settereknek, de természetesen hívhatók máshogy is. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Ez a metódus olyan opcionális függőségek esetében hasznos, amelyek nem szükségesek az osztály működéséhez, mivel nem garantált, hogy az objektum valóban megkapja őket (azaz a felhasználó meghívja a metódust). @@ -90,16 +150,16 @@ Ez a metódus olyan opcionális függőségek esetében hasznos, amelyek nem sz Ugyanakkor ez a metódus lehetővé teszi a setter ismételt meghívását a függőség megváltoztatására. Ha ez nem kívánatos, adjunk hozzá egy ellenőrzést a metódushoz, vagy a PHP 8.1-től kezdve jelöljük meg a `$cache` tulajdonságot a `readonly` flaggel. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ A setter hívás a DI konténer konfigurációjában a [setup szakaszban |servic ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Property Injection .[#toc-property-injection] A függőségek közvetlenül a tulajdonsághoz kerülnek átadásra: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Ez a módszer nem tekinthető megfelelőnek, mivel a tulajdonságot a `public` címen kell deklarálni. Így nincs befolyásunk arra, hogy az átadott függőség valóban a megadott típusú lesz-e (ez a PHP 7.4 előtt volt igaz), és elveszítjük a lehetőséget, hogy saját kódunkkal reagáljunk az újonnan hozzárendelt függőségre, például a későbbi változások megakadályozására. Ugyanakkor a tulajdonság az osztály nyilvános interfészének részévé válik, ami nem feltétlenül kívánatos. @@ -137,12 +197,18 @@ A változó beállítását a DI konténer konfigurációjában, a [setup szakas ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Injektálás .[#toc-inject] +========================= + +Míg az előző három módszer általában minden objektumorientált nyelvben érvényes, a metódus, annotáció vagy *inject* attribútum általi injektálás a Nette prezenterekre jellemző. Ezeket [külön fejezetben |best-practices:inject-method-attribute] tárgyaljuk. + + Melyik utat válasszuk? .[#toc-which-way-to-choose] ================================================== diff --git a/dependency-injection/hu/services.texy b/dependency-injection/hu/services.texy index 9310be2752..d1630dc19a 100644 --- a/dependency-injection/hu/services.texy +++ b/dependency-injection/hu/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Inject Mode .[#toc-inject-mode] =============================== -A `inject: true` flag a függőségek nyilvános változókon keresztüli átadásának aktiválására szolgál az [inject |best-practices:inject-method-attribute#Inject Annotations] annotációval és az [inject*() |best-practices:inject-method-attribute#inject Methods] metódusokkal. +A `inject: true` flag a függőségek nyilvános változókon keresztüli átadásának aktiválására szolgál az [inject |best-practices:inject-method-attribute#Inject Attributes] annotációval és az [inject*() |best-practices:inject-method-attribute#inject Methods] metódusokkal. ```neon services: diff --git a/dependency-injection/it/@home.texy b/dependency-injection/it/@home.texy index 8a6e1181bb..76ccee5828 100644 --- a/dependency-injection/it/@home.texy +++ b/dependency-injection/it/@home.texy @@ -5,8 +5,10 @@ Iniezione di dipendenza La Dependency Injection è un modello di progettazione che cambierà radicalmente il modo di vedere il codice e lo sviluppo. Apre la strada a un mondo di applicazioni pulite e sostenibili. - [Che cos'è la Dependency Injection? |introduction] +- [Stato globale e singleton |global-state] +- [Passare le dipendenze |passing-dependencies] - [Cos'è il contenitore DI? |container] -- [Passaggio delle dipendenze |passing-dependencies] +- [Domande frequenti |faq] Nette DI diff --git a/dependency-injection/it/@left-menu.texy b/dependency-injection/it/@left-menu.texy index fd8cbdee19..31552b7412 100644 --- a/dependency-injection/it/@left-menu.texy +++ b/dependency-injection/it/@left-menu.texy @@ -1,8 +1,10 @@ Iniezione di dipendenza *********************** - [Che cos'è DI? |introduction] +- [Stato globale e singleton |global-state] +- [Passare le dipendenze |passing-dependencies] - [Che cos'è il contenitore DI? |container] -- [Passaggio di dipendenze |passing-dependencies] +- [Domande frequenti |faq] Nette DI diff --git a/dependency-injection/it/faq.texy b/dependency-injection/it/faq.texy new file mode 100644 index 0000000000..4dfa2d5e1a --- /dev/null +++ b/dependency-injection/it/faq.texy @@ -0,0 +1,112 @@ +Domande frequenti su DI (FAQ) +***************************** + + +DI è un altro nome per IoC? .[#toc-is-di-another-name-for-ioc] +-------------------------------------------------------------- + +*Inversion of Control* (IoC) è un principio che si concentra sul modo in cui il codice viene eseguito: sia che il codice inizi il codice esterno, sia che il codice sia integrato nel codice esterno, che poi lo chiama. +L'IoC è un concetto ampio che include gli [eventi |nette:glossary#Events], il cosiddetto [principio di Hollywood |application:components#Hollywood style] e altri aspetti. +Anche le fabbriche, che fanno parte della [regola #3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], e che rappresentano l'inversione dell'operatore `new`, sono componenti di questo concetto. + +La *Dependency Injection* (DI) riguarda il modo in cui un oggetto conosce un altro oggetto, cioè la dipendenza. È un modello di progettazione che richiede il passaggio esplicito delle dipendenze tra gli oggetti. + +Pertanto, si può dire che DI sia una forma specifica di IoC. Tuttavia, non tutte le forme di IoC sono adatte in termini di purezza del codice. Per esempio, tra gli anti-pattern, includiamo tutte le tecniche che lavorano con lo [stato globale |global state] o il cosiddetto [Service Locator |#What is a Service Locator]. + + +Che cos'è un Service Locator? .[#toc-what-is-a-service-locator] +--------------------------------------------------------------- + +Un localizzatore di servizi è un'alternativa alla Dependency Injection. Funziona creando un archivio centrale in cui sono registrati tutti i servizi o le dipendenze disponibili. Quando un oggetto ha bisogno di una dipendenza, la richiede al Service Locator. + +Tuttavia, rispetto alla Dependency Injection, perde in trasparenza: le dipendenze non vengono passate direttamente agli oggetti e non sono quindi facilmente identificabili, il che richiede l'esame del codice per scoprire e comprendere tutte le connessioni. Anche i test sono più complicati, perché non si possono semplicemente passare gli oggetti mock agli oggetti testati, ma bisogna passare attraverso il Service Locator. Inoltre, il Service Locator sconvolge la progettazione del codice, poiché i singoli oggetti devono essere a conoscenza della sua esistenza, a differenza della Dependency Injection, in cui gli oggetti non sono a conoscenza del contenitore DI. + + +Quando è meglio non usare DI? .[#toc-when-is-it-better-not-to-use-di] +--------------------------------------------------------------------- + +Non ci sono difficoltà note associate all'uso del design pattern Dependency Injection. Al contrario, ottenere le dipendenze da posizioni accessibili a livello globale comporta [una serie di complicazioni |global-state], così come l'uso di un Service Locator. +Pertanto, è consigliabile utilizzare sempre la DI. Non si tratta di un approccio dogmatico, ma semplicemente non è stata trovata un'alternativa migliore. + +Tuttavia, ci sono alcune situazioni in cui non è possibile passare gli oggetti tra loro e ottenerli dallo spazio globale. Ad esempio, quando si esegue il debug del codice e si ha bisogno di scaricare il valore di una variabile in un punto specifico del programma, di misurare la durata di una certa parte del programma o di registrare un messaggio. +In questi casi, quando si tratta di azioni temporanee che verranno successivamente rimosse dal codice, è legittimo utilizzare un dumper, un cronometro o un logger accessibile a livello globale. Questi strumenti, dopo tutto, non appartengono alla progettazione del codice. + + +L'uso di DI ha degli svantaggi? .[#toc-does-using-di-have-its-drawbacks] +------------------------------------------------------------------------ + +L'uso della Dependency Injection comporta qualche svantaggio, come una maggiore complessità di scrittura del codice o prestazioni peggiori? Che cosa perdiamo quando iniziamo a scrivere codice secondo la DI? + +DI non ha alcun impatto sulle prestazioni dell'applicazione o sui requisiti di memoria. Le prestazioni del contenitore DI possono avere un ruolo, ma nel caso di [Nette DI | nette-container], il contenitore è compilato in PHP puro, quindi il suo overhead durante l'esecuzione dell'applicazione è sostanzialmente nullo. + +Quando si scrive il codice, è necessario creare costruttori che accettino le dipendenze. In passato, questo poteva richiedere molto tempo, ma grazie ai moderni IDE e alla [promozione delle proprietà dei costruttori |https://blog.nette.org/it/php-8-0-panoramica-completa-delle-novita#toc-constructor-property-promotion], ora è una questione di pochi secondi. Le fabbriche possono essere facilmente generate utilizzando Nette DI e un plugin di PhpStorm con pochi clic. +D'altra parte, non è necessario scrivere singleton e punti di accesso statici. + +Si può concludere che un'applicazione progettata correttamente utilizzando DI non è né più corta né più lunga rispetto a un'applicazione che utilizza singleton. Le parti di codice che lavorano con le dipendenze vengono semplicemente estratte dalle singole classi e spostate in nuove posizioni, cioè nel contenitore DI e nelle fabbriche. + + +Come riscrivere un'applicazione legacy in DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] +----------------------------------------------------------------------------------------------- + +La migrazione da un'applicazione legacy alla Dependency Injection può essere un processo impegnativo, soprattutto per applicazioni grandi e complesse. È importante affrontare questo processo in modo sistematico. + +- Quando si passa alla Dependency Injection, è importante che tutti i membri del team comprendano i principi e le pratiche da utilizzare. +- In primo luogo, è necessario eseguire un'analisi dell'applicazione esistente per identificare i componenti chiave e le loro dipendenze. Creare un piano per le parti da rifattorizzare e in quale ordine. +- Implementare un contenitore DI o, meglio ancora, utilizzare una libreria esistente come Nette DI. +- Rifattorizzare gradualmente ogni parte dell'applicazione per utilizzare la Dependency Injection. Ciò può comportare la modifica di costruttori o metodi per accettare le dipendenze come parametri. +- Modificare i punti del codice in cui vengono creati gli oggetti di dipendenza, in modo che le dipendenze siano invece iniettate dal contenitore. Questo può includere l'uso di fabbriche. + +Ricordate che il passaggio alla Dependency Injection è un investimento nella qualità del codice e nella sostenibilità a lungo termine dell'applicazione. Sebbene possa essere impegnativo apportare queste modifiche, il risultato dovrebbe essere un codice più pulito, modulare e facilmente testabile, pronto per estensioni e manutenzioni future. + + +Perché la composizione è preferibile all'ereditarietà? .[#toc-why-composition-is-preferred-over-inheritance] +------------------------------------------------------------------------------------------------------------ +È preferibile usare la composizione piuttosto che l'ereditarietà, perché serve a riutilizzare il codice senza doversi preoccupare dell'effetto a cascata delle modifiche. In questo modo si ottiene un accoppiamento più lasco, in cui non ci si deve preoccupare che la modifica di un codice provochi la modifica di un altro codice dipendente. Un esempio tipico è la situazione definita [inferno dei costruttori |passing-dependencies#Constructor hell]. + + +Nette DI Container può essere utilizzato al di fuori di Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] +---------------------------------------------------------------------------------------------------------------------- + +Assolutamente sì. Nette DI Container fa parte di Nette, ma è stato progettato come una libreria autonoma che può essere utilizzata indipendentemente da altre parti del framework. È sufficiente installarla con Composer, creare un file di configurazione che definisca i servizi e poi utilizzare poche righe di codice PHP per creare il contenitore DI. +In questo modo si può iniziare a sfruttare immediatamente la Dependency Injection nei propri progetti. + +Il capitolo [Nette DI Container |nette-container] descrive un caso d'uso specifico, compreso il codice. + + +Perché la configurazione è nei file NEON? .[#toc-why-is-the-configuration-in-neon-files] +---------------------------------------------------------------------------------------- + +NEON è un linguaggio di configurazione semplice e facilmente leggibile sviluppato all'interno di Nette per la configurazione di applicazioni, servizi e relative dipendenze. Rispetto a JSON o YAML, offre opzioni molto più intuitive e flessibili. In NEON, è possibile descrivere in modo naturale legami che non sarebbe possibile scrivere in Symfony e YAML o solo attraverso una descrizione complessa. + + +L'analisi dei file NEON rallenta l'applicazione? .[#toc-does-parsing-neon-files-slow-down-the-application] +---------------------------------------------------------------------------------------------------------- + +Sebbene l'analisi dei file NEON sia molto rapida, questo aspetto non ha molta importanza. Il motivo è che l'analisi dei file avviene solo una volta durante il primo avvio dell'applicazione. In seguito, il codice del contenitore DI viene generato, memorizzato sul disco ed eseguito per ogni richiesta successiva, senza bisogno di ulteriori analisi. + +Questo è il modo in cui funziona in un ambiente di produzione. Durante lo sviluppo, i file NEON vengono analizzati ogni volta che il loro contenuto cambia, assicurando che lo sviluppatore abbia sempre un contenitore DI aggiornato. Come già detto, il parsing vero e proprio è questione di un istante. + + +Come si accede ai parametri del file di configurazione nella propria classe? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +----------------------------------------------------------------------------------------------------------------------------------------------------------- + +Tenete a mente la [regola n. 1: lasciate che vi venga passato |introduction#Rule #1: Let It Be Passed to You]. Se una classe richiede informazioni da un file di configurazione, non è necessario capire come accedere a tali informazioni, ma è sufficiente richiederle, ad esempio attraverso il costruttore della classe. Ed eseguiamo il passaggio nel file di configurazione. + +In questo esempio, `%myParameter%` è un segnaposto per il valore del parametro `myParameter`, che sarà passato al costruttore `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Se si desidera passare più parametri o utilizzare il cablaggio automatico, è utile [avvolgere i parametri in un oggetto |best-practices:passing-settings-to-presenters]. + + +Nette supporta l'interfaccia PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] +---------------------------------------------------------------------------------------------------- + +Nette DI Container non supporta direttamente PSR-11. Tuttavia, se è necessaria l'interoperabilità tra Nette DI Container e librerie o framework che si aspettano l'interfaccia PSR-11 Container, è possibile creare un [semplice adattatore |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] che funga da ponte tra Nette DI Container e PSR-11. diff --git a/dependency-injection/it/global-state.texy b/dependency-injection/it/global-state.texy new file mode 100644 index 0000000000..f48ba87e7f --- /dev/null +++ b/dependency-injection/it/global-state.texy @@ -0,0 +1,312 @@ +Stato globale e singoletti +************************** + +.[perex] +Attenzione: i seguenti costrutti sono sintomi di una cattiva progettazione del codice: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` o `static::$var` + +Qualcuno di questi costrutti è presente nel vostro codice? Allora avete l'opportunità di migliorare. Potreste pensare che si tratta di costrutti comuni che vediamo nelle soluzioni di esempio di varie librerie e framework. +Sfortunatamente, sono comunque un chiaro indicatore di cattiva progettazione. Hanno una cosa in comune: l'uso dello stato globale. + +Ora, non stiamo certo parlando di una sorta di purezza accademica. L'uso dello stato globale e dei singleton ha effetti distruttivi sulla qualità del codice. Il suo comportamento diventa imprevedibile, riduce la produttività degli sviluppatori e costringe le interfacce delle classi a mentire sulle loro vere dipendenze. Il che confonde i programmatori. + +In questo capitolo mostreremo come ciò sia possibile. + + +Interconnessione globale .[#toc-global-interlinking] +---------------------------------------------------- + +Il problema fondamentale dello stato globale è che è accessibile a livello globale. Questo rende possibile scrivere sul database tramite il metodo globale (statico) `DB::insert()`. +In un mondo ideale, un oggetto dovrebbe essere in grado di comunicare solo con altri oggetti che gli sono stati [passati direttamente |passing-dependencies]. +Se creo due oggetti `A` e `B` e non passo mai un riferimento da `A` a `B`, né `A`, né `B` possono accedere all'altro oggetto o modificarne lo stato. +Questa è una caratteristica molto desiderabile del codice. È come avere una batteria e una lampadina; la lampadina non si accende finché non si collegano i due oggetti. + +Questo non vale per le variabili globali (statiche) o per i singleton. L'oggetto `A` potrebbe accedere *senza fili* all'oggetto `C` e modificarlo senza passare alcun riferimento, chiamando `C::changeSomething()`. +Se l'oggetto `B` prende anche la variabile globale `C`, allora `A` e `B` possono interagire tra loro tramite `C`. + +L'uso di variabili globali introduce una nuova forma di accoppiamento *wireless* nel sistema, non visibile dall'esterno. +Crea una cortina di fumo che complica la comprensione e l'uso del codice. +Gli sviluppatori devono leggere ogni riga del codice sorgente per capire veramente le dipendenze. Invece di limitarsi a familiarizzare con l'interfaccia delle classi. +Inoltre, si tratta di un accoppiamento del tutto inutile. + +.[note] +In termini di comportamento, non c'è differenza tra una variabile globale e una statica. Sono ugualmente dannose. + + +L'azione spettrale a distanza .[#toc-the-spooky-action-at-a-distance] +--------------------------------------------------------------------- + +"Azione spettrale a distanza": così Albert Einstein definì nel 1935 un fenomeno della fisica quantistica che gli fece venire i brividi. +Si tratta dell'entanglement quantistico, la cui peculiarità è che quando si misura un'informazione su una particella, si influisce immediatamente su un'altra particella, anche se si trovano a milioni di anni luce di distanza. +Il che sembra violare la legge fondamentale dell'universo secondo cui nulla può viaggiare più veloce della luce. + +Nel mondo del software, possiamo chiamare "azione spettrale a distanza" una situazione in cui eseguiamo un processo che pensiamo sia isolato (perché non gli abbiamo passato alcun riferimento), ma interazioni inaspettate e cambiamenti di stato avvengono in posizioni distanti del sistema di cui non abbiamo parlato all'oggetto. Questo può avvenire solo attraverso lo stato globale. + +Immaginate di entrare in un team di sviluppo di un progetto che ha una base di codice ampia e matura. Il vostro nuovo capo vi chiede di implementare una nuova funzionalità e, da bravi sviluppatori, iniziate scrivendo un test. Ma poiché siete nuovi nel progetto, fate molti test esplorativi del tipo "cosa succede se chiamo questo metodo". E provate a scrivere il seguente test: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // il numero della carta + $cc->charge(100); +} +``` + +Eseguite il codice, magari più volte, e dopo un po' notate sul vostro telefono le notifiche della banca che ogni volta che lo eseguite, 100 dollari sono stati addebitati sulla vostra carta di credito 🤦‍♂️ + +Come può il test causare un addebito effettivo? Non è facile operare con la carta di credito. Bisogna interagire con un servizio web di terze parti, conoscere l'URL di tale servizio web, effettuare il login e così via. +Nessuna di queste informazioni è inclusa nel test. Ancora peggio, non si sa nemmeno dove siano presenti queste informazioni e quindi come prendere in giro le dipendenze esterne in modo che ogni esecuzione non comporti un nuovo addebito di 100 dollari. E come nuovo sviluppatore, come potevate sapere che quello che stavate per fare vi avrebbe fatto perdere 100 dollari? + +È un'azione spettrale a distanza! + +Non avete altra scelta se non quella di scavare nel codice sorgente, chiedendo ai colleghi più anziani e più esperti, fino a capire come funzionano le connessioni nel progetto. +Ciò è dovuto al fatto che, guardando l'interfaccia della classe `CreditCard`, non è possibile determinare lo stato globale che deve essere inizializzato. Anche guardando il codice sorgente della classe non si può sapere quale metodo di inizializzazione chiamare. Al massimo, si può trovare la variabile globale a cui si accede e cercare di indovinare come inizializzarla a partire da essa. + +Le classi in un progetto di questo tipo sono bugiarde patologiche. La carta di pagamento finge di poter essere semplicemente istanziata e di poter chiamare il metodo `charge()`. Tuttavia, interagisce segretamente con un'altra classe, `PaymentGateway`. Anche la sua interfaccia dice che può essere inizializzata in modo indipendente, ma in realtà preleva le credenziali da qualche file di configurazione e così via. +Per gli sviluppatori che hanno scritto questo codice è chiaro che `CreditCard` ha bisogno di `PaymentGateway`. Hanno scritto il codice in questo modo. Ma per chiunque sia nuovo al progetto, questo è un completo mistero e ostacola l'apprendimento. + +Come risolvere la situazione? Semplice. **Lasciare che l'API dichiari le dipendenze.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Si noti come le relazioni all'interno del codice siano improvvisamente evidenti. Dichiarando che il metodo `charge()` ha bisogno di `PaymentGateway`, non è necessario chiedere a nessuno come il codice sia interdipendente. Si sa che bisogna crearne un'istanza e, quando si cerca di farlo, ci si imbatte nel fatto che bisogna fornire dei parametri di accesso. Senza di essi, il codice non potrebbe nemmeno funzionare. + +E soprattutto, ora potete prendere in giro il gateway di pagamento, così non vi verranno addebitati 100 dollari ogni volta che eseguite un test. + +Lo stato globale fa sì che i vostri oggetti possano accedere segretamente a cose che non sono dichiarate nelle loro API e, di conseguenza, rende le vostre API dei bugiardi patologici. + +Forse non ci avete mai pensato prima, ma ogni volta che usate lo stato globale, state creando canali di comunicazione wireless segreti. Le inquietanti azioni remote costringono gli sviluppatori a leggere ogni riga di codice per capire le potenziali interazioni, riducono la produttività degli sviluppatori e confondono i nuovi membri del team. +Se siete voi a creare il codice, conoscete le vere dipendenze, ma chi viene dopo di voi non sa nulla. + +Non scrivete codice che utilizza lo stato globale, ma preferite passare le dipendenze. Ovvero, la dependency injection. + + +La fragilità dello Stato globale .[#toc-brittleness-of-the-global-state] +------------------------------------------------------------------------ + +Nel codice che utilizza lo stato globale e i singleton, non è mai certo quando e da chi lo stato è stato modificato. Questo rischio è già presente al momento dell'inizializzazione. Il codice seguente dovrebbe creare una connessione al database e inizializzare il gateway di pagamento, ma continua a lanciare un'eccezione e trovare la causa è estremamente noioso: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +È necessario esaminare il codice in dettaglio per scoprire che l'oggetto `PaymentGateway` accede ad altri oggetti in modalità wireless, alcuni dei quali richiedono una connessione al database. Pertanto, è necessario inizializzare il database prima di `PaymentGateway`. Tuttavia, la cortina di fumo dello stato globale nasconde questo aspetto. Quanto tempo si risparmierebbe se l'API di ogni classe non mentisse e non dichiarasse le sue dipendenze? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Un problema simile si presenta quando si utilizza l'accesso globale a una connessione di database: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Quando si chiama il metodo `save()`, non si sa se è già stata creata una connessione al database e chi è responsabile della sua creazione. Ad esempio, se si volesse cambiare al volo la connessione al database, magari a scopo di test, probabilmente si dovrebbero creare metodi aggiuntivi come `DB::reconnect(...)` o `DB::reconnectForTest()`. + +Consideriamo un esempio: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Come possiamo essere sicuri che il database di prova sia davvero utilizzato quando chiamiamo `$article->save()`? E se il metodo `Foo::doSomething()` cambiasse la connessione globale al database? Per scoprirlo, dovremmo esaminare il codice sorgente della classe `Foo` e probabilmente di molte altre classi. Tuttavia, questo approccio fornirebbe solo una risposta a breve termine, poiché la situazione potrebbe cambiare in futuro. + +E se spostassimo la connessione al database in una variabile statica all'interno della classe `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Questo non cambia assolutamente nulla. Il problema è uno stato globale e non importa in quale classe si nasconda. In questo caso, come in quello precedente, non abbiamo alcun indizio su quale database viene scritto quando viene chiamato il metodo `$article->save()`. Chiunque, all'estremità distante dell'applicazione, potrebbe cambiare il database in qualsiasi momento usando `Article::setDb()`. Sotto le nostre mani. + +Lo stato globale rende la nostra applicazione **estremamente fragile**. + +Tuttavia, esiste un modo semplice per affrontare questo problema. Basta che l'API dichiari le dipendenze per garantire la corretta funzionalità. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Questo approccio elimina la preoccupazione di modifiche nascoste e inaspettate alle connessioni al database. Ora siamo sicuri di dove è memorizzato l'articolo e nessuna modifica del codice all'interno di un'altra classe non correlata può più cambiare la situazione. Il codice non è più fragile, ma stabile. + +Non scrivete codice che utilizza lo stato globale, ma preferite passare le dipendenze. Quindi, l'iniezione di dipendenza. + + +Singleton .[#toc-singleton] +--------------------------- + +Singleton è un pattern di progettazione che, secondo [la definizione |https://en.wikipedia.org/wiki/Singleton_pattern] della famosa pubblicazione Gang of Four, limita una classe a una singola istanza e offre un accesso globale a essa. L'implementazione di questo pattern di solito assomiglia al codice seguente: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // e altri metodi che svolgono le funzioni della classe +} +``` + +Purtroppo, il singleton introduce lo stato globale nell'applicazione. E come abbiamo mostrato in precedenza, lo stato globale è indesiderabile. Ecco perché il singleton è considerato un antipattern. + +Non utilizzate i singleton nel vostro codice e sostituiteli con altri meccanismi. I singleton non sono necessari. Tuttavia, se è necessario garantire l'esistenza di una singola istanza di una classe per l'intera applicazione, bisogna lasciarla al [contenitore DI |container]. +Quindi, creare un singleton dell'applicazione, o un servizio. Questo impedirà alla classe di fornire la propria unicità (cioè, non avrà un metodo `getInstance()` e una variabile statica) ed eseguirà solo le sue funzioni. In questo modo, non violerà più il principio della responsabilità unica. + + +Stato globale e test .[#toc-global-state-versus-tests] +------------------------------------------------------ + +Quando si scrivono i test, si assume che ogni test sia un'unità isolata e che nessuno stato esterno vi entri. E nessuno stato lascia i test. Quando un test viene completato, qualsiasi stato associato al test dovrebbe essere rimosso automaticamente dal garbage collector. Questo rende i test isolati. Pertanto, possiamo eseguire i test in qualsiasi ordine. + +Tuttavia, se sono presenti stati/singleton globali, tutte queste belle assunzioni vengono meno. Uno stato può entrare e uscire da un test. Improvvisamente, l'ordine dei test può essere importante. + +Per testare i singleton, gli sviluppatori devono spesso rilassare le loro proprietà, magari permettendo a un'istanza di essere sostituita da un'altra. Queste soluzioni sono, nella migliore delle ipotesi, dei trucchi che producono codice difficile da mantenere e da capire. Qualsiasi test o metodo `tearDown()` che influisca su uno stato globale deve annullare tali modifiche. + +Lo stato globale è il più grande grattacapo dei test unitari! + +Come risolvere la situazione? Semplice. Non scrivete codice che utilizza singleton, ma preferite passare le dipendenze. Ovvero, la dependency injection. + + +Costanti globali .[#toc-global-constants] +----------------------------------------- + +Lo stato globale non è limitato all'uso di singleton e variabili statiche, ma può essere applicato anche alle costanti globali. + +Le costanti il cui valore non ci fornisce alcuna informazione nuova (`M_PI`) o utile (`PREG_BACKTRACK_LIMIT_ERROR`) sono chiaramente OK. +Al contrario, le costanti che servono a passare *senza fili* informazioni all'interno del codice non sono altro che una dipendenza nascosta. Come `LOG_FILE` nell'esempio seguente. +L'uso della costante `FILE_APPEND` è perfettamente corretto. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +In questo caso, dovremmo dichiarare il parametro nel costruttore della classe `Foo` per renderlo parte dell'API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Ora possiamo passare le informazioni sul percorso del file di registrazione e modificarle facilmente in base alle necessità, rendendo più facili i test e la manutenzione del codice. + + +Funzioni globali e metodi statici .[#toc-global-functions-and-static-methods] +----------------------------------------------------------------------------- + +Vogliamo sottolineare che l'uso di metodi statici e funzioni globali non è di per sé problematico. Abbiamo spiegato l'inadeguatezza dell'uso di `DB::insert()` e di metodi simili, ma si è sempre trattato di uno stato globale memorizzato in una variabile statica. Il metodo `DB::insert()` richiede l'esistenza di una variabile statica perché memorizza la connessione al database. Senza questa variabile, sarebbe impossibile implementare il metodo. + +L'uso di metodi e funzioni statiche deterministiche, come `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` e molti altri, è perfettamente coerente con la dependency injection. Queste funzioni restituiscono sempre gli stessi risultati dagli stessi parametri di ingresso e sono quindi prevedibili. Non utilizzano alcuno stato globale. + +Tuttavia, esistono funzioni in PHP che non sono deterministiche. Tra queste c'è, per esempio, la funzione `htmlspecialchars()`. Il suo terzo parametro, `$encoding`, se non specificato, assume per default il valore dell'opzione di configurazione `ini_get('default_charset')`. Pertanto, si raccomanda di specificare sempre questo parametro per evitare un comportamento imprevedibile della funzione. Nette fa sempre così. + +Alcune funzioni, come `strtolower()`, `strtoupper()`, e simili, nel recente passato hanno avuto un comportamento non deterministico e sono dipese dall'impostazione `setlocale()`. Questo ha causato molte complicazioni, soprattutto quando si lavorava con la lingua turca. +Questo perché la lingua turca distingue tra maiuscole e minuscole `I` con e senza punto. Quindi `strtolower('I')` restituiva il carattere `ı` e `strtoupper('i')` restituiva il carattere `İ`, il che portava le applicazioni a causare una serie di errori misteriosi. +Tuttavia, questo problema è stato risolto nella versione 8.2 di PHP e le funzioni non dipendono più dal locale. + +Questo è un bell'esempio di come lo stato globale abbia afflitto migliaia di sviluppatori in tutto il mondo. La soluzione è stata quella di sostituirlo con la dependency injection. + + +Quando è possibile utilizzare lo Stato globale? .[#toc-when-is-it-possible-to-use-global-state] +----------------------------------------------------------------------------------------------- + +Ci sono alcune situazioni specifiche in cui è possibile utilizzare lo stato globale. Ad esempio, quando si esegue il debug del codice ed è necessario scaricare il valore di una variabile o misurare la durata di una parte specifica del programma. In questi casi, che riguardano azioni temporanee che saranno successivamente rimosse dal codice, è legittimo utilizzare un dumper o un cronometro disponibile a livello globale. Questi strumenti non fanno parte della progettazione del codice. + +Un altro esempio sono le funzioni per lavorare con le espressioni regolari `preg_*`, che memorizzano internamente le espressioni regolari compilate in una cache statica in memoria. Quando si richiama la stessa espressione regolare più volte in diverse parti del codice, essa viene compilata una sola volta. La cache consente di risparmiare prestazioni ed è completamente invisibile all'utente, per cui tale utilizzo può essere considerato legittimo. + + +Sintesi .[#toc-summary] +----------------------- + +Abbiamo mostrato perché ha senso + +1) Rimuovere tutte le variabili statiche dal codice +2) Dichiarare le dipendenze +3) E utilizzare l'iniezione delle dipendenze + +Quando si progetta il codice, bisogna tenere presente che ogni `static $foo` rappresenta un problema. Affinché il codice sia un ambiente che rispetta la DI, è essenziale sradicare completamente lo stato globale e sostituirlo con la dependency injection. + +Durante questo processo, potreste scoprire che è necessario dividere una classe perché ha più di una responsabilità. Non preoccupatevi di questo; cercate di mantenere il principio di una sola responsabilità. + +*Vorrei ringraziare Miško Hevery, i cui articoli, come [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], costituiscono la base di questo capitolo.* diff --git a/dependency-injection/it/introduction.texy b/dependency-injection/it/introduction.texy index ad2efec467..e526eef1e8 100644 --- a/dependency-injection/it/introduction.texy +++ b/dependency-injection/it/introduction.texy @@ -2,17 +2,17 @@ Cos'è l'iniezione di dipendenza? ******************************** .[perex] -Questo capitolo introduce le pratiche di programmazione di base da seguire quando si scrive un'applicazione. Si tratta delle basi necessarie per scrivere codice pulito, comprensibile e manutenibile. +Questo capitolo introduce le pratiche di programmazione di base da seguire nella scrittura di qualsiasi applicazione. Si tratta dei fondamenti necessari per scrivere codice pulito, comprensibile e manutenibile. -Se imparate e seguite queste regole, Nette vi assisterà in ogni momento. Gestirà per voi le attività di routine e vi metterà il più possibile a vostro agio, in modo che possiate concentrarvi sulla logica. +Se imparate e seguite queste regole, Nette vi assisterà in ogni momento. Gestirà per voi le attività di routine e vi offrirà il massimo comfort, in modo che possiate concentrarvi sulla logica. -I principi che vi illustreremo sono piuttosto semplici. Non dovete preoccuparvi di nulla. +I principi che illustreremo qui sono piuttosto semplici. Non dovete preoccuparvi di nulla. Ricordate il vostro primo programma? .[#toc-remember-your-first-program] ------------------------------------------------------------------------ -Non abbiamo idea in quale linguaggio l'abbiate scritto, ma se fosse stato PHP, probabilmente avrebbe avuto un aspetto simile a questo: +Non sappiamo in quale linguaggio sia stato scritto, ma se si tratta di PHP, potrebbe avere un aspetto simile a questo: ```php function addition(float $a, float $b): float @@ -25,31 +25,31 @@ echo addition(23, 1); // francobollo 24 Poche righe di codice banali, ma con tanti concetti chiave nascosti. Che ci sono variabili. Che il codice è suddiviso in unità più piccole, come ad esempio le funzioni. Che si passano loro degli argomenti in ingresso e che restituiscono dei risultati. Mancano solo le condizioni e i cicli. -Il fatto che si passi un input a una funzione e che questa restituisca un risultato è un concetto perfettamente comprensibile, utilizzato anche in altri campi, come la matematica. +Il fatto che una funzione prenda dei dati in ingresso e restituisca un risultato è un concetto perfettamente comprensibile, utilizzato anche in altri campi, come la matematica. -Una funzione ha una firma, che consiste nel suo nome, in un elenco di parametri e dei loro tipi e infine nel tipo di valore di ritorno. Come utenti, siamo interessati alla firma; di solito non abbiamo bisogno di sapere nulla dell'implementazione interna. +Una funzione ha una firma, che consiste nel suo nome, in un elenco di parametri e dei loro tipi e, infine, nel tipo del valore di ritorno. Come utenti, siamo interessati alla firma e di solito non abbiamo bisogno di sapere nulla dell'implementazione interna. -Immaginiamo ora che la firma di una funzione abbia il seguente aspetto: +Immaginiamo ora che la firma della funzione abbia questo aspetto: ```php function addition(float $x): float ``` -Un'addizione con un solo parametro? È strano... Che ne dite di questo? +Un'aggiunta con un solo parametro? È strano... Che ne dite di questo? ```php function addition(): float ``` -È davvero strano, vero? Come pensate che venga utilizzata la funzione? +È davvero strano, vero? Come viene utilizzata la funzione? ```php echo addition(); // cosa stampa? ``` -Guardando questo codice, siamo confusi. Non solo un principiante non lo capirebbe, anche un programmatore esperto non capirebbe questo codice. +Guardando questo codice, saremmo confusi. Non solo un principiante non lo capirebbe, ma anche un programmatore esperto non lo capirebbe. -Vi chiedete come sarebbe in realtà una funzione del genere? Dove troverebbe gli addendi? Probabilmente li otterrebbe *in qualche modo* da sola, come in questo caso: +Vi state chiedendo che aspetto avrebbe una funzione del genere? Dove troverebbe i sommatori? Probabilmente li otterrebbe *in qualche modo* da sola, forse in questo modo: ```php function addition(): float @@ -66,13 +66,13 @@ Si scopre che nel corpo della funzione ci sono legami nascosti con altre funzion Non in questo modo! .[#toc-not-this-way] ---------------------------------------- -Il disegno che ci è stato appena mostrato è l'essenza di molte caratteristiche negative: +Il design appena mostrato è l'essenza di molte caratteristiche negative: -- la firma della funzione fingeva di non aver bisogno di addendi, il che ci confondeva +- la firma della funzione fingeva di non aver bisogno dei sommatori, il che ci confondeva - non abbiamo idea di come far calcolare la funzione con altri due numeri -- abbiamo dovuto guardare nel codice per vedere dove prende gli addendi -- abbiamo scoperto legami nascosti -- per comprendere appieno, dobbiamo esplorare anche questi binding +- abbiamo dovuto esaminare il codice per scoprire da dove provenissero i sommatori +- abbiamo trovato dipendenze nascoste +- una comprensione completa richiede l'esame anche di queste dipendenze E il compito della funzione di addizione è anche quello di procurarsi gli input? Ovviamente no. La sua responsabilità è solo quella di aggiungere. @@ -93,20 +93,20 @@ Regola n. 1: Lascia che ti venga passato .[#toc-rule-1-let-it-be-passed-to-you] La regola più importante è: **tutti i dati di cui hanno bisogno le funzioni o le classi devono essere passati a loro**. -Invece di inventare meccanismi nascosti per aiutarle a raggiungerli da sole, basta passare i parametri. Si risparmierà il tempo necessario per inventare meccanismi nascosti, che sicuramente non miglioreranno il codice. +Invece di inventare modi nascosti per accedere ai dati, basta passare i parametri. Si risparmierà tempo che sarebbe stato speso per inventare percorsi nascosti che certamente non miglioreranno il codice. -Se seguite sempre e ovunque questa regola, sarete sulla buona strada verso un codice senza vincoli nascosti. Verso un codice comprensibile non solo per l'autore, ma anche per chiunque lo legga in seguito. Dove tutto è comprensibile dalle firme delle funzioni e delle classi e non c'è bisogno di cercare segreti nascosti nell'implementazione. +Se si segue sempre e ovunque questa regola, si è sulla strada per un codice senza dipendenze nascoste. Un codice comprensibile non solo per l'autore, ma anche per chiunque lo legga in seguito. Dove tutto è comprensibile dalle firme delle funzioni e delle classi e non c'è bisogno di cercare segreti nascosti nell'implementazione. -Questa tecnica è sapientemente chiamata **dependency injection**. E i dati si chiamano **dipendenze**, ma si tratta di un semplice passaggio di parametri, niente di più. +Questa tecnica è chiamata professionalmente **dependency injection**. E questi dati sono chiamati **dipendenze**. È solo un normale passaggio di parametri, niente di più. .[note] -Non confondere l'iniezione di dipendenze, che è un modello di progettazione, con il "contenitore di iniezione di dipendenze", che è uno strumento, qualcosa di completamente diverso. Parleremo dei contenitori più avanti. +Non bisogna confondere l'iniezione di dipendenze, che è un modello di progettazione, con un "contenitore di iniezione di dipendenze", che è uno strumento, qualcosa di diametralmente diverso. Ci occuperemo dei contenitori più avanti. Dalle funzioni alle classi .[#toc-from-functions-to-classes] ------------------------------------------------------------ -E come si relazionano le classi? Una classe è un'entità più complessa di una semplice funzione, ma la regola n. 1 si applica anche in questo caso. Ci sono solo [più modi per passare gli argomenti |passing-dependencies]. Per esempio, in modo del tutto simile al caso di una funzione: +E come sono collegate le classi? Una classe è un'unità più complessa di una semplice funzione, ma la regola n. 1 si applica interamente anche in questo caso. Ci sono solo [più modi per passare gli argomenti |passing-dependencies]. Per esempio, in modo del tutto simile al caso di una funzione: ```php class Math @@ -121,7 +121,7 @@ $math = new Math; echo $math->addition(23, 1); // 24 ``` -Oppure utilizzando altri metodi o direttamente il costruttore: +Oppure attraverso altri metodi o direttamente attraverso il costruttore: ```php class Addition @@ -149,9 +149,9 @@ Entrambi gli esempi sono completamente conformi alla dependency injection. Esempi reali .[#toc-real-life-examples] --------------------------------------- -Nel mondo reale non si scrivono classi per l'addizione di numeri. Passiamo agli esempi del mondo reale. +Nel mondo reale, non scriverete classi per l'aggiunta di numeri. Passiamo agli esempi pratici. -Abbiamo una classe `Article` che rappresenta un articolo di blog: +Abbiamo una classe `Article` che rappresenta un post del blog: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Il metodo `save()` salva l'articolo in una tabella del database. Implementarlo utilizzando [Nette Database |database:] sarebbe un gioco da ragazzi, se non fosse per un inconveniente: dove `Article` dovrebbe ottenere la connessione al database, cioè l'oggetto della classe `Nette\Database\Connection`? +Il metodo `save()` salva l'articolo in una tabella del database. Implementarlo utilizzando [Nette Database |database:] sarà un gioco da ragazzi, se non fosse per un problema: dove `Article` ottiene la connessione al database, cioè un oggetto della classe `Nette\Database\Connection`? -Sembra che ci siano molte opzioni. Può prenderla da qualche parte in una variabile statica. Oppure ereditarla da una classe che fornisca la connessione al database. Oppure sfruttare un [singleton |global-state#Singleton]. Oppure le cosiddette facciate utilizzate in Laravel: +Sembra che ci siano molte opzioni. Può prenderla da una variabile statica da qualche parte. Oppure ereditare da una classe che fornisce una connessione al database. Oppure sfruttare un [singleton |global-state#Singleton]. Oppure utilizzare le cosiddette facciate, utilizzate in Laravel: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ Bene, abbiamo risolto il problema. O forse sì? -Ricordiamo la [regola numero 1: lascia che ti venga passato |#rule #1: Let It Be Passed to You]: tutte le dipendenze di cui la classe ha bisogno devono essere passate ad essa. Perché se non lo facciamo e infrangiamo la regola, abbiamo iniziato a percorrere la strada verso un codice sporco, pieno di vincoli nascosti, incomprensibile e il risultato sarà un'applicazione che è una sofferenza da mantenere e sviluppare. +Ricordiamo la [regola numero 1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: tutte le dipendenze di cui la classe ha bisogno devono essere passate ad essa. Perché se infrangiamo questa regola, abbiamo intrapreso la strada del codice sporco, pieno di dipendenze nascoste, incomprensibile e il risultato sarà un'applicazione dolorosa da mantenere e sviluppare. -L'utente della classe `Article` non ha idea di dove il metodo `save()` memorizzi l'articolo. In una tabella del database? In quale, in produzione o in sviluppo? E come si può cambiare? +L'utente della classe `Article` non ha idea di dove il metodo `save()` memorizzi l'articolo. In una tabella del database? Quale, quella di produzione o quella di test? E come può essere modificata? -L'utente deve guardare come è implementato il metodo `save()` per trovare l'uso del metodo `DB::insert()`. Quindi deve cercare ulteriormente per scoprire come questo metodo si procura una connessione al database. E le connessioni nascoste possono formare una catena piuttosto lunga. +L'utente deve guardare a come è implementato il metodo `save()` e trova l'uso del metodo `DB::insert()`. Quindi, deve cercare ulteriormente per scoprire come questo metodo ottiene una connessione al database. E le dipendenze nascoste possono formare una catena piuttosto lunga. -I binding nascosti, le facciate di Laravel o le variabili statiche non sono mai presenti in un codice pulito e ben progettato. Nel codice pulito e ben progettato, gli argomenti vengono passati: +In un codice pulito e ben progettato, non ci sono mai dipendenze nascoste, facciate di Laravel o variabili statiche. Nel codice pulito e ben progettato, gli argomenti vengono passati: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Ancora più pratico, come vedremo in seguito, è utilizzare un costruttore: +Un approccio ancora più pratico, come vedremo in seguito, sarà quello del costruttore: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Se siete programmatori esperti, potreste pensare che `Article` non dovrebbe avere un metodo `save()`, dovrebbe essere un puro componente di dati e un repository separato dovrebbe occuparsi della memorizzazione. Questo ha senso. Ma questo ci porterebbe ben oltre l'argomento, che è l'iniezione di dipendenza, e cercheremmo di fornire semplici esempi. +Se siete programmatori esperti, potreste pensare che `Article` non dovrebbe avere un metodo `save()`; dovrebbe rappresentare un componente puramente di dati e un repository separato dovrebbe occuparsi del salvataggio. Questo ha senso. Ma questo ci porterebbe ben oltre lo scopo dell'argomento, che è l'iniezione di dipendenza, e lo sforzo di fornire semplici esempi. -Se si sta per scrivere una classe che richiede un database per funzionare, per esempio, non si deve capire da dove prenderlo, ma farselo passare. Magari come parametro di un costruttore o di un altro metodo. Dichiarare le dipendenze. Esponetele nell'API della vostra classe. Otterrete un codice comprensibile e prevedibile. +Se scrivete una classe che richiede, per esempio, un database per il suo funzionamento, non inventate dove prenderlo, ma fatelo passare. Come parametro del costruttore o di un altro metodo. Ammettete le dipendenze. Ammettetele nell'API della vostra classe. Otterrete un codice comprensibile e prevedibile. -Che ne dite di questa classe che registra i messaggi di errore? +E che dire di questa classe, che registra i messaggi di errore: ```php class Logger @@ -266,9 +266,9 @@ Cosa ne pensate, abbiamo rispettato la [regola n. 1: lascia che ti venga passato Non l'abbiamo fatto. -L'informazione chiave, la directory del file di log, è *ottenuta* dalla classe dalla costante. +L'informazione chiave, cioè la directory con il file di log, viene *ottenuta* dalla classe stessa dalla costante. -Si veda l'esempio di utilizzo: +Guardate l'esempio di utilizzo: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Senza conoscere l'implementazione, si può rispondere alla domanda dove vengono scritti i messaggi? Si potrebbe pensare che l'esistenza della costante LOG_DIR sia necessaria per il suo funzionamento? E si potrebbe creare una seconda istanza che scriva in una posizione diversa? Certamente no. +Senza conoscere l'implementazione, potreste rispondere alla domanda su dove vengono scritti i messaggi? Si potrebbe ipotizzare che l'esistenza della costante `LOG_DIR` sia necessaria per il suo funzionamento? E si potrebbe creare una seconda istanza che scriva in una posizione diversa? Certamente no. Correggiamo la classe: @@ -295,7 +295,7 @@ class Logger } ``` -La classe è ora molto più chiara, più configurabile e quindi più utile. +La classe è ora molto più comprensibile, configurabile e quindi più utile. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Ma non mi interessa! .[#toc-but-i-don-t-care] --------------------------------------------- -*Quando creo un oggetto Article e chiamo save(), non voglio avere a che fare con il database, voglio solo che venga salvato in quello che ho impostato nella configurazione. "* +*"Quando creo un oggetto Articolo e chiamo save(), non voglio avere a che fare con il database; voglio solo che sia salvato in quello che ho impostato nella configurazione."* -*"Quando uso Logger, voglio solo che il messaggio venga scritto e non voglio occuparmi di dove. Lasciare che vengano utilizzate le impostazioni globali. "* +*"Quando uso Logger, voglio solo che il messaggio venga scritto e non voglio occuparmi di dove. Lasciate che vengano utilizzate le impostazioni globali."* -Questi sono commenti corretti. +Questi sono punti validi. -Come esempio, prendiamo una classe che invia newsletter e registriamo come è andata: +A titolo di esempio, analizziamo una classe che invia newsletter e registra come è andata: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -La versione migliorata di `Logger`, che non utilizza più la costante `LOG_DIR`, richiede un percorso di file nel costruttore. Come risolvere questo problema? Alla classe `NewsletterDistributor` non interessa dove vengono scritti i messaggi, vuole solo scriverli. +La versione migliorata di `Logger`, che non utilizza più la costante `LOG_DIR`, richiede di specificare il percorso del file nel costruttore. Come risolvere questo problema? Alla classe `NewsletterDistributor` non interessa dove vengono scritti i messaggi, vuole solo scriverli. -La soluzione è ancora una volta la [regola n. 1: lascia che ti venga passato |#rule #1: Let It Be Passed to You]: passare alla classe tutti i dati di cui ha bisogno. +La soluzione è ancora una volta la [regola n. 1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: passare tutti i dati di cui la classe ha bisogno. -Quindi passiamo il percorso del log al costruttore, che poi usiamo per creare l'oggetto `Logger`? +Questo significa che passiamo il percorso del log attraverso il costruttore, che poi usiamo quando creiamo l'oggetto `Logger`? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Non è così! Perché il percorso non fa parte dei dati di cui ha bisogno la classe `NewsletterDistributor`, ma ha bisogno di `Logger`. La classe ha bisogno del logger stesso. Ed è questo che passeremo: +No, non così! Il percorso non fa parte dei dati di cui ha bisogno la classe `NewsletterDistributor`; infatti, la classe `Logger` ne ha bisogno. Vedete la differenza? La classe `NewsletterDistributor` ha bisogno del logger stesso. Quindi è questo che passeremo: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Ora è chiaro dalle firme della classe `NewsletterDistributor` che il logging fa parte delle sue funzionalità. E il compito di sostituire il logger con un altro, magari a scopo di test, è abbastanza banale. -Inoltre, se il costruttore della classe `Logger` viene modificato, non avrà alcun effetto sulla nostra classe. +Ora è chiaro dalle firme della classe `NewsletterDistributor` che anche il logging fa parte delle sue funzionalità. E il compito di scambiare il logger con un altro, magari per i test, è del tutto banale. +Inoltre, se il costruttore della classe `Logger` cambia, questo non influisce sulla nostra classe. -Regola n. 2: prendere ciò che è vostro .[#toc-rule-2-take-what-is-yours] ------------------------------------------------------------------------- +Regola n. 2: prendere ciò che è vostro .[#toc-rule-2-take-what-s-yours] +----------------------------------------------------------------------- -Non lasciarsi ingannare e non farsi passare i parametri delle dipendenze. Passare direttamente le dipendenze. +Non fatevi ingannare e non lasciate passare le dipendenze delle vostre dipendenze. Passate solo le vostre dipendenze. -Questo renderà il codice che utilizza altri oggetti completamente indipendente dalle modifiche ai loro costruttori. La sua API sarà più vera. E soprattutto, sarà banale scambiare queste dipendenze con altre. +In questo modo, il codice che utilizza altri oggetti sarà completamente indipendente dalle modifiche ai loro costruttori. La sua API sarà più veritiera. E soprattutto, sarà banale sostituire queste dipendenze con altre. -Un nuovo membro della famiglia .[#toc-a-new-member-of-the-family] ------------------------------------------------------------------ +Un nuovo membro della famiglia .[#toc-new-family-member] +-------------------------------------------------------- -Il team di sviluppo ha deciso di creare un secondo logger che scrive sul database. Viene quindi creata la classe `DatabaseLogger`. Abbiamo quindi due classi, `Logger` e `DatabaseLogger`, una scrive su un file, l'altra scrive su un database... non vi sembra che questo nome abbia qualcosa di strano? -Non sarebbe meglio rinominare `Logger` in `FileLogger`? Certo che sì. +Il team di sviluppo ha deciso di creare un secondo logger che scrive sul database. Abbiamo quindi creato una classe `DatabaseLogger`. Abbiamo quindi due classi, `Logger` e `DatabaseLogger`, una scrive su un file, l'altra su un database... non vi sembra strano come nome? +Non sarebbe meglio rinominare `Logger` in `FileLogger`? Decisamente sì. -Ma facciamolo in modo intelligente. Creeremo un'interfaccia con il nome originale: +Ma facciamolo in modo intelligente. Creiamo un'interfaccia con il nome originale: ```php interface Logger @@ -406,23 +406,23 @@ interface Logger ```php class FileLogger implements Logger -//... +// ... class DatabaseLogger implements Logger -//... +// ... ``` -In questo modo, non sarà necessario modificare nulla nel resto del codice in cui il logger viene utilizzato. Per esempio, il costruttore della classe `NewsletterDistributor` si accontenterà di richiedere `Logger` come parametro. E dipenderà da noi quale istanza passargli. +Per questo motivo, non sarà necessario modificare nulla nel resto del codice in cui viene utilizzato il logger. Per esempio, il costruttore della classe `NewsletterDistributor` si accontenterà di richiedere `Logger` come parametro. E dipenderà da noi quale istanza passare. -**Questo è il motivo per cui non diamo mai ai nomi delle interfacce il suffisso `Interface` o il prefisso `I` ** Altrimenti, sarebbe impossibile sviluppare codice in modo così gradevole. +**Ecco perché non aggiungiamo mai il suffisso `Interface` o il prefisso `I` ai nomi delle interfacce.** Altrimenti, non sarebbe possibile sviluppare il codice in modo così gradevole. Houston, abbiamo un problema .[#toc-houston-we-have-a-problem] -------------------------------------------------------------- -Mentre in tutta l'applicazione possiamo accontentarci di una singola istanza di un logger, sia esso di file o di database, e passarlo semplicemente ovunque si debba registrare qualcosa, nel caso della classe `Article` è molto diverso. Infatti, ne creiamo istanze a seconda delle necessità, eventualmente più volte. Come gestire il legame con il database nel suo costruttore? +Mentre per l'intera applicazione si può utilizzare una singola istanza del logger, sia essa basata su file o su database, e passarla semplicemente ogni volta che si deve registrare qualcosa, per la classe `Article` è molto diverso. Creiamo le sue istanze secondo le necessità, anche più volte. Come gestire la dipendenza dal database nel suo costruttore? -Come esempio, possiamo usare un controllore che deve salvare un articolo nel database dopo aver inviato un modulo: +Un esempio può essere un controllore che deve salvare un articolo nel database dopo aver inviato un modulo: ```php class EditController extends Controller @@ -437,17 +437,17 @@ class EditController extends Controller } ``` -Viene offerta direttamente una possibile soluzione: far passare l'oggetto database dal costruttore a `EditController` e usare `$article = new Article($this->db)`. +Una possibile soluzione è ovvia: passare l'oggetto database al costruttore `EditController` e utilizzare `$article = new Article($this->db)`. -Come nel caso precedente con `Logger` e il percorso del file, questo non è l'approccio corretto. Il database non è una dipendenza di `EditController`, ma di `Article`. Quindi passare il database va contro la [regola #2: prendi ciò che è tuo |#rule #2: take what is yours]. Quando il costruttore della classe `Article` viene modificato (viene aggiunto un nuovo parametro), anche il codice in tutti i punti in cui vengono create le istanze dovrà essere modificato. Ufff. +Come nel caso precedente con `Logger` e il percorso del file, questo non è l'approccio giusto. Il database non è una dipendenza di `EditController`, ma di `Article`. Passare il database va contro la [regola n. 2: prendi ciò che è tuo |#rule #2: take what's yours]. Se il costruttore della classe `Article` cambia (viene aggiunto un nuovo parametro), sarà necessario modificare il codice ovunque vengano create le istanze. Ufff. -Houston, cosa stai suggerendo? +Houston, cosa suggerisci? Regola n. 3: lasciare che se ne occupi la fabbrica .[#toc-rule-3-let-the-factory-handle-it] ------------------------------------------------------------------------------------------- -Rimuovendo i vincoli nascosti e passando tutte le dipendenze come argomenti, si ottengono classi più configurabili e flessibili. Quindi abbiamo bisogno di qualcos'altro per creare e configurare queste classi più flessibili. Le chiameremo fabbriche. +Eliminando le dipendenze nascoste e passando tutte le dipendenze come argomenti, abbiamo ottenuto classi più configurabili e flessibili. Pertanto, abbiamo bisogno di qualcos'altro per creare e configurare queste classi più flessibili. Lo chiameremo "fabbriche". La regola generale è: se una classe ha delle dipendenze, lasciare la creazione delle sue istanze al factory. @@ -460,7 +460,7 @@ Non bisogna confondersi con il design pattern *factory method*, che descrive un Fabbrica .[#toc-factory] ------------------------ -Un factory è un metodo o una classe che produce e configura oggetti. Chiamiamo `Article` la classe produttrice `ArticleFactory` e potrebbe assomigliare a questa: +Un factory è un metodo o una classe che crea e configura oggetti. Chiameremo la classe che produce `Article` come `ArticleFactory`, e potrebbe avere questo aspetto: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Il suo uso nel controllore sarebbe il seguente: +Il suo utilizzo nel controllore sarà il seguente: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -A questo punto, quando la firma del costruttore della classe `Article` cambia, l'unica parte del codice che deve rispondere è il factory `ArticleFactory` stesso. Qualsiasi altro codice che lavora con gli oggetti `Article`, come ad esempio `EditController`, non sarà influenzato. +A questo punto, se la firma del costruttore della classe `Article` cambia, l'unica parte del codice che deve reagire è la stessa `ArticleFactory`. Tutto il resto del codice che lavora con gli oggetti `Article`, come ad esempio `EditController`, non ne risentirà. -Forse in questo momento vi starete battendo la fronte, chiedendoci se siamo stati d'aiuto. La quantità di codice è cresciuta e il tutto comincia a sembrare sospettosamente complicato. +Ci si potrebbe chiedere se abbiamo effettivamente migliorato le cose. La quantità di codice è aumentata e tutto inizia a sembrare sospettosamente complicato. -Non preoccupatevi, presto arriveremo al contenitore Nette DI. Questo contenitore ha una serie di assi nella manica che renderanno estremamente semplice la costruzione di applicazioni che utilizzano l'iniezione di dipendenze. Per esempio, al posto della classe `ArticleFactory`, sarà sufficiente [scrivere una semplice interfaccia |factory]: +Non preoccupatevi, presto arriveremo al contenitore Nette DI. Questo contenitore ha diversi assi nella manica, che semplificheranno notevolmente la costruzione di applicazioni che utilizzano l'iniezione di dipendenze. Per esempio, invece della classe `ArticleFactory`, sarà sufficiente [scrivere una semplice interfaccia |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Ma stiamo andando avanti, aspettate :-) +Ma stiamo correndo troppo; abbiate pazienza :-) Riassunto .[#toc-summary] ------------------------- -All'inizio di questo capitolo abbiamo promesso di mostrare un metodo per progettare codice pulito. Basta dare alle classi +All'inizio di questo capitolo abbiamo promesso di mostrare un processo per progettare codice pulito. Tutto ciò che serve è che le classi: -- [le dipendenze di cui hanno bisogno |#Rule #1: Let It Be Passed to You] -- [e non quelle di cui non hanno direttamente bisogno |#Rule #2: Take What Is Yours] -- [e che gli oggetti con dipendenze sono meglio realizzati in factory |#Rule #3: Let the Factory Handle it] +- [passino le dipendenze di cui hanno bisogno |#Rule #1: Let It Be Passed to You] +- [viceversa, non passino ciò di cui non hanno direttamente bisogno |#Rule #2: Take What's Yours] +- [e che gli oggetti con dipendenze siano meglio creati nei factory |#Rule #3: Let the Factory Handle it] -A prima vista può non sembrare, ma queste tre regole hanno implicazioni di vasta portata. Portano a una visione radicalmente diversa della progettazione del codice. Ne vale la pena? I programmatori che hanno abbandonato le vecchie abitudini e hanno iniziato a usare l'iniezione di dipendenza considerano questo momento cruciale della loro vita professionale. Ha aperto un mondo di applicazioni chiare e sostenibili. +A prima vista, queste tre regole non sembrano avere conseguenze di vasta portata, ma portano a una prospettiva radicalmente diversa sulla progettazione del codice. Ne vale la pena? Gli sviluppatori che hanno abbandonato le vecchie abitudini e hanno iniziato a usare coerentemente la dependency injection considerano questo passo un momento cruciale della loro vita professionale. Ha aperto loro il mondo delle applicazioni chiare e manutenibili. -Ma cosa succede se il codice non utilizza costantemente l'iniezione di dipendenza? E se è costruito con metodi statici o singleton? Questo comporta dei problemi? Sì, [ed è molto significativo |global-state]. +Ma cosa succede se il codice non utilizza costantemente l'iniezione di dipendenza? E se si basa su metodi statici o singleton? Questo causa qualche problema? [Sì, e sono molto importanti. |global-state] diff --git a/dependency-injection/it/passing-dependencies.texy b/dependency-injection/it/passing-dependencies.texy index df527c39c2..bfd95ad60a 100644 --- a/dependency-injection/it/passing-dependencies.texy +++ b/dependency-injection/it/passing-dependencies.texy @@ -12,7 +12,7 @@ Gli argomenti, o "dipendenze" nella terminologia DI, possono essere passati alle </div> -I primi tre metodi si applicano in generale a tutti i linguaggi orientati agli oggetti, mentre il quarto è specifico per i presentatori Nette, quindi viene discusso in un [capitolo a parte |best-practices:inject-method-attribute]. Daremo ora un'occhiata più da vicino a ciascuna di queste opzioni e le illustreremo con esempi specifici. +Illustriamo ora le diverse varianti con esempi concreti. Iniezione del costruttore .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Iniezione del costruttore .[#toc-constructor-injection] Le dipendenze vengono passate come argomenti al costruttore quando l'oggetto viene creato: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Questa forma è utile per le dipendenze obbligatorie di cui la classe ha assolutamente bisogno per funzionare, poiché senza di esse l'istanza non può essere creata. @@ -40,10 +40,10 @@ Da PHP 8.0, è possibile utilizzare una forma di notazione più breve ([construc ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ A partire da PHP 8.1, una proprietà può essere contrassegnata da un flag `read ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService Il contenitore DI passa le dipendenze al costruttore automaticamente, utilizzando l'[autowiring |autowiring]. Gli argomenti che non possono essere passati in questo modo (per esempio stringhe, numeri, booleani) vengono [scritti nella configurazione |services#Arguments]. +L'inferno dei costruttori .[#toc-constructor-hell] +-------------------------------------------------- + +Il termine *inferno dei costruttori* si riferisce a una situazione in cui un figlio eredita da una classe genitore il cui costruttore richiede delle dipendenze, e anche il figlio richiede delle dipendenze. Deve anche assumere e trasmettere le dipendenze del genitore: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Il problema si presenta quando si vuole modificare il costruttore della classe `BaseClass`, ad esempio quando viene aggiunta una nuova dipendenza. Allora dobbiamo modificare anche tutti i costruttori dei figli. Il che rende tale modifica un inferno. + +Come evitarlo? La soluzione è quella di **privilegiare la composizione rispetto all'ereditarietà**. + +Quindi progettiamo il codice in modo diverso. Eviteremo le classi astratte di `Base*`. Invece di ottenere una funzionalità da `MyClass` ereditando da `BaseClass`, avrà quella funzionalità passata come dipendenza: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Iniezione di setter .[#toc-setter-injection] ============================================ -Le dipendenze vengono passate chiamando un metodo che le memorizza in una proprietà privata. La convenzione di denominazione usuale per questi metodi è la forma `set*()`, motivo per cui vengono chiamati setter. +Le dipendenze vengono passate chiamando un metodo che le memorizza in una proprietà privata. La convenzione di denominazione usuale per questi metodi è la forma `set*()`, che è il motivo per cui sono chiamati setter, ma naturalmente possono essere chiamati in qualsiasi altro modo. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Questo metodo è utile per le dipendenze opzionali che non sono necessarie per il funzionamento della classe, poiché non è garantito che l'oggetto le riceva effettivamente (cioè che l'utente chiami il metodo). @@ -90,16 +150,16 @@ Questo metodo è utile per le dipendenze opzionali che non sono necessarie per i Allo stesso tempo, questo metodo consente di richiamare ripetutamente il setter per modificare la dipendenza. Se ciò non è auspicabile, si può aggiungere un controllo al metodo o, a partire da PHP 8.1, contrassegnare la proprietà `$cache` con il flag `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ La chiamata al setter è definita nella configurazione del contenitore DI, nella ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Iniezione di proprietà .[#toc-property-injection] Le dipendenze vengono passate direttamente alla proprietà: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Questo metodo è considerato inappropriato, perché la proprietà deve essere dichiarata come `public`. Quindi, non si ha alcun controllo sul fatto che la dipendenza passata sia effettivamente del tipo specificato (questo era vero prima di PHP 7.4) e si perde la possibilità di reagire alla nuova dipendenza assegnata con il proprio codice, ad esempio per prevenire modifiche successive. Allo stesso tempo, la proprietà diventa parte dell'interfaccia pubblica della classe, il che potrebbe non essere auspicabile. @@ -137,12 +197,18 @@ L'impostazione della variabile è definita nella configurazione del contenitore ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Iniettare .[#toc-inject] +======================== + +Mentre i tre metodi precedenti sono generalmente validi in tutti i linguaggi orientati agli oggetti, l'iniezione tramite metodo, annotazione o attributo *inject* è specifica dei presentatori Nette. Sono trattati in [un capitolo a parte |best-practices:inject-method-attribute]. + + Quale strada scegliere? .[#toc-which-way-to-choose] =================================================== diff --git a/dependency-injection/it/services.texy b/dependency-injection/it/services.texy index a19588dd04..d3b690fe82 100644 --- a/dependency-injection/it/services.texy +++ b/dependency-injection/it/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Modalità Inject .[#toc-inject-mode] =================================== -Il flag `inject: true` è usato per attivare il passaggio di dipendenze tramite variabili pubbliche con l'annotazione [inject |best-practices:inject-method-attribute#Inject Annotations] e i metodi [inject*() |best-practices:inject-method-attribute#inject Methods]. +Il flag `inject: true` è usato per attivare il passaggio di dipendenze tramite variabili pubbliche con l'annotazione [inject |best-practices:inject-method-attribute#Inject Attributes] e i metodi [inject*() |best-practices:inject-method-attribute#inject Methods]. ```neon services: diff --git a/dependency-injection/ja/@home.texy b/dependency-injection/ja/@home.texy index 276a697e30..bf7357f0ab 100644 --- a/dependency-injection/ja/@home.texy +++ b/dependency-injection/ja/@home.texy @@ -5,8 +5,10 @@ 依存性注入は、コードや開発に対する見方を根本的に変えるデザインパターンです。クリーンな設計で持続可能なアプリケーションの世界への道を開くものです。 - [Dependency Injectionとは? |introduction] -- [DIコンテナとは? |container] +- [グローバルステートとシングルトン |global-state] - [依存性の受け渡し |passing-dependencies] +- [DIコンテナとは? |container] +- [よくある質問 |faq] Nette DI diff --git a/dependency-injection/ja/@left-menu.texy b/dependency-injection/ja/@left-menu.texy index dac78fb0e6..0f99c6dd14 100644 --- a/dependency-injection/ja/@left-menu.texy +++ b/dependency-injection/ja/@left-menu.texy @@ -1,8 +1,10 @@ 依存性注入 ***** - [DIとは? |introduction] -- [DI コンテナとは?|container] +- [グローバルステートとシングルトン |global-state] - [依存関係の受け渡し |passing-dependencies] +- [DI コンテナとは?|container] +- [よくある質問 |faq] Nette DI diff --git a/dependency-injection/ja/faq.texy b/dependency-injection/ja/faq.texy new file mode 100644 index 0000000000..f8656dc7a5 --- /dev/null +++ b/dependency-injection/ja/faq.texy @@ -0,0 +1,112 @@ +DIに関するFAQ +********* + + +DIはIoCの別名なのか? .[#toc-is-di-another-name-for-ioc] +------------------------------------------------ + +IoC(Inversion of Control)とは、コードの実行方法に着目した原則で、自分のコードが外部コードを起動するのか、自分のコードが外部コードに統合され、その外部コードがコードを呼び出すのか、ということです。 +IoCは、[イベント |nette:en:glossary#Events]、いわゆる[ハリウッドの原則などを |application:en:components#Hollywood style]含む広い概念である。 +[ルール3「ファクトリーに |introduction#Rule #3: Let the Factory Handle It]任せる」の一部であり、`new` 演算子の反転を表すファクトリーもこのコンセプトの構成要素です。 + +依存性注入*(DI)とは、あるオブジェクトが他のオブジェクトについてどのように知っているか、つまり依存関係についてです。これは、オブジェクト間で依存関係を明示的に受け渡すことを要求するデザインパターンです。 + +したがって、DIはIoCの特定の形態であると言える。しかし、IoCのすべての形態がコードピュリティの点で適しているわけではありません。例えば、アンチパターンの中には、[グローバルステートを |global state]扱う技法や、いわゆる[サービスロケーターも |#What is a Service Locator]全て含まれます。 + + +サービスロケーターとは? .[#toc-what-is-a-service-locator] +---------------------------------------------- + +サービスロケーターは、依存性注入の代替となるものです。利用可能なサービスや依存関係がすべて登録されている中央ストレージを作成することで機能します。オブジェクトが依存関係を必要とするとき、サービスロケータに要求します。 + +依存関係はオブジェクトに直接渡されるわけではないので、簡単に識別することができず、すべての接続を明らかにして理解するためにコードを調査する必要があります。また、モックオブジェクトをテスト対象オブジェクトに単純に渡すことができず、Service Locatorを経由する必要があるため、テストがより複雑になります。さらに、Service Locatorは、個々のオブジェクトがその存在を認識する必要があるため、コードの設計を混乱させます。これは、オブジェクトがDIコンテナを知らないDependency Injectionとは異なります。 + + +DIを使わない方が良い場合とは? .[#toc-when-is-it-better-not-to-use-di] +-------------------------------------------------------- + +Dependency Injectionデザインパターンを使用することに関して、既知の困難はありません。逆に、グローバルにアクセス可能な場所から依存関係を取得することは、Service Locatorを使用する場合と同様に、[多くの複雑さを |global-state]もたらします。 +そのため、常にDIを使用することが望ましいとされています。これは独断的なアプローチではなく、単に他に良い代替案が見つかっていないだけなのです。 + +しかし、オブジェクトを相互に受け渡しせず、グローバル空間から取得する場面もある。たとえば、コードをデバッグするときに、プログラムの特定の時点で変数値をダンプしたり、プログラムのある部分の継続時間を測定したり、メッセージをログに記録したりする必要がある場合です。 +このような場合、後でコードから削除される一時的な動作に関するものであれば、グローバルにアクセス可能なダンパ、ストップウォッチ、ロガーを使用することは正当です。これらのツールは、結局のところ、コードの設計に属するものではありません。 + + +DIを使うと欠点があるのでしょうか? .[#toc-does-using-di-have-its-drawbacks] +----------------------------------------------------------- + +Dependency Injectionを使うと、コードの書き方が複雑になったり、パフォーマンスが低下したりするなどのデメリットはあるのでしょうか?DIに従ってコードを書き始めると、何を失うのでしょうか? + +DIは、アプリケーションのパフォーマンスやメモリ要件に影響を与えることはありません。DIコンテナの性能が影響する場合もありますが、[ネットDIの | nette-container]場合、コンテナは純粋なPHPにコンパイルされているため、アプリケーション実行時のオーバーヘッドは実質ゼロです。 + +コードを書くとき、依存関係を受け入れるコンストラクタを作成する必要があります。以前は、この作業に時間がかかることもありましたが、最新のIDEと[コンストラクタのプロパティプロモーションの |https://blog.nette.org/en/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]おかげで、今では数秒の問題で済むようになりました。Nette DIとPhpStormプラグインを使えば、数クリックで簡単にファクトリーを生成することができる。 +一方、シングルトンや静的アクセスポイントを書く必要はありません。 + +DIを使用して適切に設計されたアプリケーションは、シングルトンを使用したアプリケーションと比較して、短くも長くもないという結論に達することができる。依存関係を扱うコードの一部は、単に個々のクラスから抽出され、新しい場所、すなわちDIコンテナやファクトリーに移動されます。 + + +レガシーアプリケーションをDIに書き換えるには? .[#toc-how-to-rewrite-a-legacy-application-to-di] +-------------------------------------------------------------------------- + +レガシーアプリケーションからDependency Injectionへの移行は、特に大規模で複雑なアプリケーションの場合、困難なプロセスになることがあります。このプロセスには、体系的にアプローチすることが重要です。 + +- Dependency Injectionに移行する場合、チームメンバー全員が使用する原則と実践方法を理解することが重要である。 +- まず、既存のアプリケーションを分析し、主要なコンポーネントとその依存関係を特定します。どの部分をどのような順序でリファクタリングするか、計画を作成する。 +- DIコンテナを実装するか、Nette DIなどの既存のライブラリを使用する。 +- Dependency Injectionを使用するために、アプリケーションの各部を徐々にリファクタリングする。これは、コンストラクタやメソッドを修正して、依存関係をパラメータとして受け取るようにすることを含むかもしれません。 +- 依存性オブジェクトが作成されるコードの場所を変更し、コンテナによって依存性が注入されるようにする。これには、ファクトリーの使用が含まれる場合があります。 + +Dependency Injectionへの移行は、コードの品質とアプリケーションの長期的な持続可能性への投資であることを忘れないでください。これらの変更を行うのは難しいかもしれませんが、その結果、よりクリーンで、よりモジュール化され、将来の拡張やメンテナンスに対応できる、テストしやすいコードができるはずです。 + + +なぜコンポジションが継承より好まれるのか? .[#toc-why-composition-is-preferred-over-inheritance] +--------------------------------------------------------------------------- +継承ではなく、コンポジションを使用することが望ましい。あるコードを変更すると、他の依存するコードも変更しなければならなくなるという心配がないため、より緩やかな結合が可能になります。典型的な例として、「[コンストラクタ地獄 |passing-dependencies#Constructor hell]」と呼ばれる状況があります。 + + +ネッテDIコンテナは、ネッテ以外でも使えるのですか? .[#toc-can-nette-di-container-be-used-outside-of-nette] +---------------------------------------------------------------------------------- + +もちろんです。Nette DIコンテナはNetteの一部ですが、フレームワークの他の部分から独立して使用できるスタンドアロンライブラリとして設計されています。Composerを使ってインストールし、サービスを定義する設定ファイルを作成し、数行のPHPコードでDIコンテナを作成するだけです。 +そして、すぐにあなたのプロジェクトで依存性注入を活用し始めることができます。 + +[ネットDIコンテナ |nette-container]編では、具体的な使用例がどのようなものか、コードも含めて解説しています。 + + +なぜNEONファイルに設定があるのですか? .[#toc-why-is-the-configuration-in-neon-files] +-------------------------------------------------------------------- + +NEONは、アプリケーション、サービス、およびそれらの依存関係を設定するために、Nette内で開発されたシンプルで読みやすい設定言語です。JSONやYAMLと比較して、より直感的で柔軟なオプションを提供することができます。NEONでは、SymfonyやYAMLではまったく、あるいは複雑な記述でしか書けないようなバインディングを自然に記述することができます。 + + +NEONファイルの解析はアプリケーションの速度を低下させますか? .[#toc-does-parsing-neon-files-slow-down-the-application] +------------------------------------------------------------------------------------------ + +NEONファイルは非常に高速に解析されますが、この点はあまり重要ではありません。なぜなら、ファイルの解析はアプリケーションの最初の起動時に1回だけ行われるからです。その後、DIコンテナコードが生成され、ディスクに保存され、その後のリクエストごとに実行されるため、さらなる解析は必要ありません。 + +本番環境ではこのように動作します。開発中は、NEONファイルの内容が変わるたびに解析され、開発者は常に最新のDIコンテナを手に入れることができます。前述したように、実際のパースは一瞬で終わります。 + + +クラス内で設定ファイルからパラメータにアクセスするには? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +----------------------------------------------------------------------------------------------------------- + +[ルールその1: 渡されるように |introduction#Rule #1: Let It Be Passed to You]する」を覚えておきましょう。クラスが設定ファイルからの情報を必要とする場合、その情報にアクセスする方法を考える必要はなく、代わりに、例えばクラスのコンストラクタを通して、単に情報を求めます。そして、設定ファイルの中で受け渡しを行います。 + +この例では、`%myParameter%` は`myParameter` パラメータの値のプレースホルダで、`MyClass` コンストラクタに渡される。 + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +複数のパラメータを渡したり、自動配線を使用する場合は、[パラメータをオブジェクトで囲むと |best-practices:en:passing-settings-to-presenters]便利です。 + + +ネッテはPSR-11コンテナインターフェースに対応していますか? .[#toc-does-nette-support-psr-11-container-interface] +-------------------------------------------------------------------------------------- + +Nette DI Containerは、PSR-11を直接サポートしていません。しかし、Nette DI ContainerとPSR-11 Container Interfaceを期待するライブラリやフレームワークとの相互運用性が必要な場合は、Nette DI ContainerとPSR-11の橋渡しとなる[簡易アダプタを |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f]作成することができます。 diff --git a/dependency-injection/ja/global-state.texy b/dependency-injection/ja/global-state.texy new file mode 100644 index 0000000000..507095089c --- /dev/null +++ b/dependency-injection/ja/global-state.texy @@ -0,0 +1,312 @@ +グローバルステートとシングルトン +**************** + +.[perex] +警告:以下の構成要素は、コード設計が不十分な場合の症状です。 + +-`Foo::getInstance()` +-`DB::insert(...)` +-`Article::setDb($db)` +-`ClassName::$var` または`static::$var` + +あなたのコードに、これらの構成要素はありませんか?それなら、改善するチャンスがあります。これらは、さまざまなライブラリやフレームワークのサンプルソリューションでよく見かける構成だとお考えかもしれません。 +しかし、残念ながら、これらは設計が不十分であることを示す明確な指標となります。これらの共通点は、グローバルステートを使用していることです。 + +さて、私たちは確かに、ある種の学問的な純度の話をしているわけではありません。グローバルステートとシングルトンの使用は、コードの品質に破壊的な影響を及ぼします。その挙動は予測不可能になり、開発者の生産性を低下させ、クラス・インターフェースに真の依存関係を偽らせることになる。そして、プログラマーを混乱させます。 + +この章では、その方法を紹介します。 + + +グローバルインターリンキング .[#toc-global-interlinking] +------------------------------------------ + +グローバル状態の根本的な問題は、グローバルにアクセス可能であることです。そのため、グローバル(静的)メソッド`DB::insert()` を使ってデータベースへの書き込みが可能になってしまいます。 +理想的な世界では、オブジェクトは、[直接渡さ |passing-dependencies]れた他のオブジェクトとしか通信できないはずです。 +`A` と`B` の2つのオブジェクトを作成し、`A` から`B` に決して参照を渡さないとすると、`A` も`B` も、もう一方のオブジェクトにアクセスしたり、その状態を変更したりすることはできません。 +これは、コードの非常に望ましい機能です。これは、電池と電球があるのと似ていて、それらを一緒に配線しないと電球は点灯しないのです。 + +これは、グローバル(静的)変数やシングルトンには当てはまりません。`A` オブジェクトは、`C::changeSomething()` を呼び出すことで、*ワイヤレスで*`C` オブジェクトにアクセスし、参照を渡すことなく変更することができます。 +`B` オブジェクトがグローバルな`C` も把握している場合、`A` と`B` は`C` を介して互いに対話することができます。 + +グローバル変数の使用は、外からは見えない新しい形の*ワイヤレス*カップリングをシステムに導入します。 +これは、コードの理解や使用を複雑にする煙幕を作り出します。 +開発者は、依存関係を本当に理解するために、ソースコードのすべての行を読まなければなりません。単にクラスのインターフェイスに精通するのではなく、です。 +しかも、まったく不要なカップリングです。 + +.[note] +動作の面では、グローバル変数と静的変数の間に違いはありません。どちらも同じように有害です。 + + +遠距離の不気味な作用 .[#toc-the-spooky-action-at-a-distance] +-------------------------------------------------- + +1935年、アルベルト・アインシュタインは、量子物理学のある現象を「Spooky action at a distance」(距離による不気味な作用)と名付けた。 +量子もつれとは、ある粒子に関する情報を測定すると、たとえそれが何百万光年も離れていても、すぐに別の粒子に影響を与えるという特殊性のことである。 +これは、「光より速く移動するものはない」という宇宙の基本法則を一見破っているように見える。 + +ソフトウェアの世界では、(参照を渡していないので)孤立していると思われるプロセスを実行しても、オブジェクトに伝えていないシステムの遠い場所で予期せぬ相互作用や状態変化が起こる状況を「spooky action at a distance」と呼ぶことができます。これはグローバルな状態を通してのみ起こりうることです。 + +大規模で成熟したコードベースを持つプロジェクト開発チームに参加することを想像してください。新しいリーダーから新機能の実装を依頼されたあなたは、優秀な開発者らしく、テストを書くことから始めます。しかし、あなたはプロジェクトに参加したばかりなので、「このメソッドを呼び出したらどうなるか」という探索的なテストをたくさん行います。そして、次のようなテストを書こうとします。 + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // your card number + $cc->charge(100); +} +``` + +あなたはコードを実行し、おそらく数回実行しました。しばらくして、銀行からあなたの携帯電話に、実行するたびに100ドルがあなたのクレジットカードに請求されたという通知に気づきます 🤦‍♂️ + +一体どうやってテストで実際の請求が発生するのでしょうか?クレジットカードで操作するのは簡単ではありません。サードパーティのウェブサービスとやりとりしなければならない、そのウェブサービスのURLを知っていなければならない、ログインしなければならない、などなど。 +これらの情報は、テストには一切含まれていません。さらに悪いことに、この情報がどこに存在するのか、したがって、実行のたびに100ドルが再び請求されることがないように、外部の依存関係をどのように模擬すればよいのかさえもわからないのです。そして新米開発者であるあなたは、これからやろうとしていることが100ドル貧乏になることにつながると、どうやって知ることになるのでしょうか? + +遠目で見ると不気味な動作ですね!? + +プロジェクト内の接続の仕組みが理解できるまで、先輩や経験者に聞きながら、たくさんのソースコードを掘り下げるしかないのです。 +これは、`CreditCard` クラスのインターフェイスを見ても、初期化が必要なグローバル状態を判断できないことに起因しています。クラスのソースコードを見ても、どの初期化メソッドを呼び出せばいいのかがわからないのです。せいぜい、アクセスされているグローバル変数を見つけ、そこから初期化方法を推測するくらいです。 + +このようなプロジェクトのクラスは病的な嘘つきである。ペイメントカードは、インスタンス化して`charge()` メソッドを呼び出すだけでよいように装っています。しかし、それは密かに別のクラス、`PaymentGateway` と相互作用している。そのインターフェースでさえ、独立して初期化できると言っているが、実際には、ある設定ファイルからクレデンシャルを引き出したりするのである。 +このコードを書いた開発者には、`CreditCard` が`PaymentGateway` を必要とすることは明らかです。彼らはこのようにコードを書きました。しかし、このプロジェクトに初めて参加する人にとっては、これは完全な謎であり、学習の妨げになります。 + +どうすればこの状況を解決できるのか?簡単です。**Let the API declare dependencies.**(APIに依存関係を宣言させる)。 + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +コード内の関係が突然明らかになったことに注目してください。`charge()` メソッドが`PaymentGateway` を必要とすると宣言することで、このコードがどのように相互依存しているのか、誰かに尋ねる必要はありません。あなたは、このメソッドのインスタンスを作成しなければならないことを知っていて、それを実行しようとすると、アクセス・パラメータを提供しなければならないという事実にぶつかります。アクセス・パラメータがなければ、コードは実行すらできないのです。 + +そして最も重要なのは、決済ゲートウェイをモックにすることで、テストを実行するたびに100ドル請求されることがないようにしたことです。 + +グローバルな状態は、オブジェクトがAPIで宣言されていないものに密かにアクセスできるようになり、結果としてAPIを病的な嘘つきにしてしまいます。 + +あなたは今までこのように考えていなかったかもしれませんが、グローバルステートを使うときはいつも、秘密の無線通信チャンネルを作っているのです。不気味な遠隔操作によって、開発者は潜在的な相互作用を理解するためにコードのすべての行を読まなければならず、開発者の生産性を低下させ、新しいチームメンバーを混乱させる。 +あなたがコードを作成した人であれば、本当の依存関係を知っていますが、あなたの後に来る人は何も知りません。 + +グローバルな状態を使うようなコードを書かず、依存関係を渡すことを優先する。つまり、依存性注入です。 + + +グローバル国家の脆さ .[#toc-brittleness-of-the-global-state] +-------------------------------------------------- + +グローバルステートとシングルトンを使用するコードでは、そのステートがいつ、誰によって変更されたのか、決して確実ではありません。このリスクは、初期化時にすでに存在している。次のコードは、データベース接続を作成し、ペイメントゲートウェイを初期化することになっていますが、例外を投げ続け、その原因を見つけるのは非常に面倒です。 + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +`PaymentGateway` オブジェクトが他のオブジェクトに無線でアクセスし、その中にはデータベース接続を必要とするものがあることは、コードを詳しく見てみなければわかりません。したがって、`PaymentGateway` の前にデータベースを初期化する必要があります。しかし、グローバルステートという煙幕が、このことを隠しています。もし各クラスのAPIが嘘をつかず、依存関係を宣言していたら、どれだけの時間を節約できるでしょうか? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +データベース接続にグローバルアクセスを使用する場合にも、同様の問題が発生します。 + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +`save()` メソッドを呼び出す際、データベース接続がすでに作成されているかどうか、また、誰がその作成に責任を持つのかが不明確である。たとえば、テスト目的でデータベース接続をその場で変更したい場合、`DB::reconnect(...)` や`DB::reconnectForTest()` などの追加のメソッドを作成する必要があるでしょう。 + +一例を考えてみましょう。 + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +`$article->save()` を呼び出す際に、テストデータベースが本当に使用されていることをどこで確認できるのでしょうか?もし、`Foo::doSomething()` メソッドがグローバルデータベース接続を変更したとしたらどうでしょうか?それを知るためには、`Foo` クラスのソースコードと、おそらく他の多くのクラスのソースコードを調査する必要があります。しかし、この方法は短期的な答えしか得られません。なぜなら、将来的に状況が変わる可能性があるからです。 + +データベース接続を`Article` クラス内の静的変数に移したらどうでしょう。 + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +これでは全く何も変わりません。問題はグローバルな状態であり、どのクラスに潜んでいるかは関係ないのです。この場合、前のものと同様に、`$article->save()` メソッドが呼ばれたときに、どのデータベースに書き込まれているのかについては、全くわかりません。アプリケーションの遠くの端にいる誰もが、`Article::setDb()` を使っていつでもデータベースを変更することができます。私たちの手の中で + +グローバルな状態は、私たちのアプリケーションを**極めて壊れやすい**ものにしています。 + +しかし、この問題に対処する簡単な方法があります。APIに依存関係を宣言させるだけで、適切な機能を確保することができるのです。 + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +このアプローチにより、データベース接続の隠れた予期せぬ変更の心配がなくなります。今、私たちは記事がどこに保存されているかを確信しており、別の無関係なクラス内のコードを修正しても、もう状況を変えることはできません。コードはもはや壊れやすくなく、安定しているのです。 + +グローバルな状態を使うようなコードは書かないで、依存関係を渡す方がいい。したがって、依存性注入。 + + +シングルトン .[#toc-singleton] +------------------------ + +シングルトンは、有名なGang of Fourの出版物からの[定義により |https://en.wikipedia.org/wiki/Singleton_pattern]、クラスを単一のインスタンスに制限し、それに対してグローバルなアクセスを提供するデザインパターンである。このパターンの実装は、通常、次のようなコードに似ています。 + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // and other methods that perform the functions of the class +} +``` + +残念ながら、シングルトンはアプリケーションにグローバルな状態を導入することになります。そして、上で示したように、グローバルな状態は望ましくありません。これが、シングルトンがアンチパターンと言われる所以です。 + +コードにシングルトンを使わず、他のメカニズムに置き換えてください。シングルトンは本当に必要ない。しかし、アプリケーション全体に対して、あるクラスの単一のインスタンスの存在を保証する必要がある場合は、[DIコンテナに |container]任せます。 +したがって、アプリケーションシングルトン(サービス)を作成します。これにより、クラスは独自のユニークさを持たなくなり(つまり、`getInstance()` メソッドや静的変数を持たなくなり)、その機能のみを実行するようになります。したがって、単一責任の原則に違反することはなくなる。 + + +グローバル・ステート・バーズ・テスト .[#toc-global-state-versus-tests] +---------------------------------------------------- + +テストを書くとき、各テストは孤立したユニットであり、外部の状態が入り込むことはないと仮定します。また、テストから離れる状態もない。テストが完了すると、テストに関連する状態は、ガベージコレクタによって自動的に削除されるはずです。これにより、テストは孤立したものになります。したがって、テストを任意の順序で実行することができます。 + +しかし、グローバルな状態やシングルトンが存在する場合、これらの素敵な仮定はすべて崩れてしまいます。状態はテストに入り、テストから出ることができる。突然、テストの順番が問題になることがある。 + +シングルトンをテストするために、開発者はしばしば、インスタンスを別のものに置き換えるなどして、その特性を緩和しなければなりません。このような解決策は、せいぜいハック程度で、維持と理解が困難なコードを生成します。グローバルな状態に影響を与えるテストやメソッド(`tearDown()` )は、それらの変更を元に戻さなければなりません。 + +グローバルステートは、ユニットテストにおける最大の頭痛の種です + +どうすればこの状況を解決できるのか?簡単です。シングルトンを使うようなコードを書かず、依存関係を渡すことを優先する。つまり、依存性注入です。 + + +グローバル定数 .[#toc-global-constants] +-------------------------------- + +グローバルステートは、シングルトンや静的変数の使用に限らず、グローバル定数にも適用可能です。 + +定数の値が、新しい情報(`M_PI` )や有用な情報(`PREG_BACKTRACK_LIMIT_ERROR` )を提供しない定数は、明らかにOKです。 +逆に、コード内部で情報を*ワイヤレス*で受け渡す方法として機能する定数は、隠れた依存関係以外の何物でもありません。次の例の`LOG_FILE` のようなものです。 +`FILE_APPEND` 定数を使用することは完全に正しいです。 + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +この場合、`Foo` クラスのコンストラクタでパラメータを宣言し、API の一部とする必要があります。 + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +これで、ロギングファイルのパスに関する情報を渡して、必要に応じて簡単に変更できるようになり、コードのテストやメンテナンスがしやすくなりました。 + + +グローバルファンクションとスタティックメソッド .[#toc-global-functions-and-static-methods] +------------------------------------------------------------------- + +静的メソッドやグローバル関数の使用自体が問題ではないことを強調したい。`DB::insert()` や同様のメソッドの使用が不適切であることを説明してきましたが、それは常に静的変数に格納されるグローバルな状態の問題でした。`DB::insert()` メソッドは、データベース接続を格納するため、静的変数の存在を必要とします。この変数がなければ、このメソッドを実装することは不可能です。 + +`DateTime::createFromFormat()`,`Closure::fromCallable`,`strlen()` などの決定論的な静的メソッドや関数の使用は、依存性注入と完全に一致します。これらの関数は、常に同じ入力パラメータから同じ結果を返すので、予測可能です。また、グローバルな状態を使用することもありません。 + +しかし、PHPには決定論的でない関数があります。例えば、`htmlspecialchars()` 関数がそうです。その第3パラメータである`$encoding` は、指定しない場合、デフォルトで設定オプション`ini_get('default_charset')` の値になります。したがって、関数の予測不可能な動作を避けるために、このパラメータを常に指定することが推奨されます。Netteでは一貫してこれを採用しています。 + +`strtolower()`,`strtoupper()` などの一部の関数は、最近になって非決定的な動作をするようになり、`setlocale()` の設定に依存するようになりました。このため、多くの複雑な問題が発生し、その多くはトルコ語を扱うときに発生しました。 +というのも、トルコ語はドットのある大文字と小文字`I` を区別しているからです。そのため、`strtolower('I')` は`ı` の文字を返し、`strtoupper('i')` は`İ` の文字を返します。このため、アプリケーションは多くの謎のエラーを引き起こすことになりました。 +しかし、この問題はPHPバージョン8.2で修正され、関数はロケールに依存しなくなりました。 + +これは、グローバルステートが世界中の何千人もの開発者を悩ませてきたことを示すいい例です。その解決策は、依存性注入に置き換えることでした。 + + +グローバルステートの使用はどのような場合に可能か? .[#toc-when-is-it-possible-to-use-global-state] +------------------------------------------------------------------------- + +グローバルステートを使用することが可能な特定の状況があります。例えば、コードをデバッグする際に、変数の値をダンプしたり、プログラムの特定の部分の時間を測定したりする必要がある場合です。このような場合、後でコードから削除される一時的な動作に関するものであれば、グローバルに利用可能なダンパやストップウォッチを使用することが正当です。これらのツールは、コード設計の一部ではありません。 + +もう一つの例は、正規表現を扱うための関数`preg_*` で、コンパイルされた正規表現を内部的にメモリ上の静的キャッシュに保存します。コードの異なる部分で同じ正規表現を複数回呼び出しても、コンパイルされるのは1回だけです。キャッシュは性能を節約し、またユーザーには全く見えないので、このような使い方は正当なものだと考えることができます。 + + +概要 .[#toc-summary] +------------------ + +なぜそれが理にかなっているのかを示しました + +1) コードからすべての静的変数を削除する +2) 依存関係を宣言する +3) そして依存性注入を使う + +コード設計を考えるとき、`static $foo` のそれぞれが問題を表していることに留意してください。あなたのコードがDIを尊重する環境になるためには、グローバルステートを完全に根絶し、依存性注入に置き換えることが必要不可欠です。 + +この過程で、クラスが複数の責任を持つため、クラスを分割する必要があることがわかるかもしれません。そのようなことは気にせず、1つの責任という原則を貫くようにしましょう。 + +*本章は、Miško Hevery氏の「[Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/]」等の論文に基づくものです。 diff --git a/dependency-injection/ja/introduction.texy b/dependency-injection/ja/introduction.texy index f965f5555a..fb13b55c6c 100644 --- a/dependency-injection/ja/introduction.texy +++ b/dependency-injection/ja/introduction.texy @@ -2,17 +2,17 @@ ****************************** .[perex] -この章では、アプリケーションを書くときに守るべき基本的なプログラミングの実践方法を紹介します。これらは、クリーンで理解しやすく、保守性の高いコードを書くために必要な基本です。 +この章では、アプリケーションを書くときに守るべき基本的なプログラミングの実践について紹介します。これらは、きれいで理解しやすく、保守しやすいコードを書くために必要な基礎知識です。 -このルールさえ守れば、ネッテはずっとあなたのそばにいてくれます。ロジックに集中できるよう、ルーティンワークを処理し、できるだけ快適に過ごせるようにします。 +このルールに従えば、ネッテはどんなときでもあなたの味方になってくれます。ロジックに集中できるよう、ルーティンワークをこなし、最高の快適さを提供します。 -ここでご紹介する原理は、とてもシンプルです。何も心配することはありません。 +ここで紹介する原理は、とてもシンプルです。何も心配する必要はありません。 -初めてのプログラムを覚えていますか?.[#toc-remember-your-first-program] ------------------------------------------------------ +初めてのプログラムを覚えていますか? .[#toc-remember-your-first-program] +------------------------------------------------------ -どのような言語で書かれたかは不明ですが、PHPであれば、おそらく次のような形になるかと思います。 +どのような言語で書かれたかはわかりませんが、PHPであれば、次のような感じだったかもしれません: ```php function addition(float $a, float $b): float @@ -25,31 +25,31 @@ echo addition(23, 1); // prints 24 ほんの数行の些細なコードですが、そこには多くの重要な概念が隠されています。変数があること。コードはより小さな単位に分解され、例えば関数となる。入力引数を渡すと、結果を返してくれる。足りないのは、条件とループだけだ。 -関数に入力を渡すと結果が返ってくるというのは、数学など他の分野でも使われているよくわかる概念です。 +関数が入力データを受け取り、結果を返すというのは、数学など他の分野でも使われている、完全に理解できる概念である。 -関数にはシグネチャがあり、その名前、パラメータのリストとその型、そして最後に戻り値の型から構成されています。ユーザーとしては、シグネチャに興味があるのであって、通常、内部実装については何も知る必要はない。 +関数にはシグネチャがあり、その名前、パラメータのリストとその型、そして最後に戻り値の型から構成されています。ユーザーとしては、シグネチャに興味があり、通常、内部実装については何も知る必要はない。 -さて、ある関数のシグネチャが次のようなものだと想像してください。 +ここで、関数シグネチャが次のようなものだったと想像してみてください: ```php function addition(float $x): float ``` -パラメータが1つの足し算?それは変だ...。こんなのはどうでしょう? +パラメータが1つの足し算?それはおかしい...。これならどうだろう? ```php function addition(): float ``` -本当に不思議ですよね。その機能はどのように使われているのでしょうか? +今のは本当に変ですよね?その機能はどのように使われているのでしょうか? ```php echo addition(); // what does it prints? ``` -このようなコードを見て、私たちは混乱します。初心者だけでなく、熟練したプログラマーでさえ、このようなコードは理解できないでしょう。 +このようなコードを見ると、私たちは混乱してしまうでしょう。初心者が理解できないだけでなく、経験豊富なプログラマーでさえ、このようなコードは理解できないでしょう。 -このような関数の内部はどうなっているのだろうかと思うことはないだろうか。どこで加算器を手に入れるのだろう?おそらく、このように、自分でどうにかして手に入れるのでしょう。 +このような関数が実際に内部でどのように見えるか気になりませんか?その関数はどこで和集合を得るのだろう?おそらく、次のような形で、自分自身で取得するのでしょう: ```php function addition(): float @@ -63,16 +63,16 @@ function addition(): float その結果、関数本体には他の関数(または静的メソッド)へのバインディングが隠されていることが判明し、実際にアドエンドの由来を知るには、さらに掘り下げる必要がある。 -このままではダメだ!.[#toc-not-this-way] ------------------------------- +このままではダメだ! .[#toc-not-this-way] +------------------------------- -今、見せていただいたデザインは、多くのマイナス要素を含んだエッセンスです。 +先ほどお見せしたデザインは、多くのマイナス点を解消するためのエッセンスです: -- 関数のシグネチャがアドエンドの必要がないように見せかけ、私たちを混乱させました。 +- 関数のシグネチャが和集合を必要としないように見せかけるので、混乱しました。 - 他の2つの数値で計算する関数を作る方法がわからない -- アドエンスをどこに持っていくか、コードを調べる必要がありました。 -- 私たちは、隠された束縛を発見した。 -- を完全に理解するためには、これらのバインディングも探求する必要があります。 +- 和算がどこから来たのか、コードを見なければわかりませんでしたが +- 隠れた依存関係を発見しました +- 完全な理解には、これらの依存関係も調べる必要があります。 そして、インプットを調達するのも加算機能の仕事なのだろうか?もちろん、そんなことはありません。 足し算の仕事だけです。 @@ -88,25 +88,25 @@ function addition(float $a, float $b): float ``` -ルールその1:渡されるようにする.[#toc-rule-1-let-it-be-passed-to-you] ------------------------------------------------------- +ルールその1:渡されるようにする .[#toc-rule-1-let-it-be-passed-to-you] +------------------------------------------------------- 最も重要なルールは**関数やクラスが必要とするすべてのデータは、関数やクラスに渡さなければならない**ということです。 -どうにかして自分でたどり着けるように、隠れた仕組みを考案するのではなく、単にパラメータを渡すだけでいいのです。隠された方法を考え出すのにかかる時間を節約でき、間違いなくあなたのコードは改善されないでしょう。 +データへのアクセス方法を工夫する代わりに、パラメータを渡すだけでよいのです。コードを改善することのない隠し通路を考案する時間を節約することができます。 -このルールにいつも、どこでも従っていれば、隠れたバインディングのないコードへの道を歩むことができます。作者だけでなく、その後に読む人にも理解できるコードへ。関数やクラスのシグネチャからすべてが理解でき、実装の中に隠された秘密を探す必要がないところです。 +もしあなたがいつもどこでもこのルールに従うなら、あなたは隠れた依存関係のないコードへの道を歩んでいることになります。作者だけでなく、その後に読む人にも理解できるコードへ。関数やクラスのシグネチャからすべてが理解でき、実装の中に隠された秘密を探す必要がないところ。 -このテクニックは専門的には**依存性注入**と呼ばれています。そしてそのデータは **dependencies.** と呼ばれています しかしこれは単純なパラメータ渡しであり それ以上のものではありません。 +この手法は専門的には**依存性注入**と呼ばれています。そして、それらのデータは**依存性**と呼ばれています。これは単なるパラメータの受け渡しであり、それ以上のものではありません。 .[note] -デザインパターンである依存性注入と、ツールである「依存性注入コンテナ」を混同しないように、全く別のものにしてください。コンテナについては後ほど説明します。 +デザインパターンである依存性注入と、ツールである「依存性注入コンテナ」は正反対のものなので、混同しないようにしてください。コンテナについては後述します。 -関数からクラスへ.[#toc-from-functions-to-classes] ------------------------------------------ +関数からクラスへ .[#toc-from-functions-to-classes] +------------------------------------------ -そして、クラスはこれとどう関係するのでしょうか?クラスは単純な関数よりも複雑な存在ですが、ルール1がここでも適用されます。[引数を |passing-dependencies]渡す方法が増えただけです。例えば、関数の場合とかなり似ています。 +また、クラスはどのように関係しているのでしょうか?クラスは単純な関数よりも複雑な単位ですが、ここでもルールその1が完全に適用されます。[引数を |passing-dependencies]渡す方法が増えただけです。例えば、関数の場合とかなり似ています: ```php class Math @@ -121,7 +121,7 @@ $math = new Math; echo $math->addition(23, 1); // 24 ``` -または他のメソッドを使用するか、コンストラクタによって直接行う。 +あるいは他のメソッドを通じて、あるいはコンストラクターを通じて直接: ```php class Addition @@ -146,12 +146,12 @@ echo $addition->calculate(); // 24 どちらの例も、依存性注入に完全に準拠している。 -実際の事例.[#toc-real-life-examples] -------------------------------- +実際の事例 .[#toc-real-life-examples] +-------------------------------- -現実世界では、数字の足し算のクラスは書きません。では、実例の紹介に移ります。 +実社会では、数字の足し算のクラスを書くことはないでしょう。では、実践的な例題に移りましょう。 -ブログの記事を表すクラス`Article` を用意しましょう。 +ブログの記事を表す`Article` クラスを用意しよう: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -`save()` メソッドは、データベースのテーブルに記事を保存します。[ |database:] `Article` `Nette\Database\Connection` Nette Databaseを使って[ |database:]このメソッドを実装するのは簡単ですが、1つだけ問題があります。 +`save()` メソッドは、記事をデータベースのテーブルに保存します。[Nette Databaseを使って |database:en]これを実装するのは簡単ですが、1つだけ難点があります。`Article` は、データベース接続、つまりクラス`Nette\Database\Connection` のオブジェクトをどこで取得するのか? -いろいろな選択肢があるようですね。静的変数でどこかから取得することもできます。あるいは、データベース接続を提供するクラスから継承する。あるいは、[シングルトンを |global-state#Singleton]利用する。あるいは、Laravelで使用されているいわゆるファサード。 +選択肢はたくさんあるようです。どこかの静的変数から取得することができます。あるいは、データベース接続を提供するクラスから継承する。あるいは、[シングルトンを |global-state#Singleton]利用する。あるいは、Laravelで使われている、いわゆるファサードを利用する: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ class Article あるいは、そうしてきたのだろうか。 -[ルールその1「渡さ |#rule #1: Let It Be Passed to You]れる」:クラスが必要とする依存関係は、すべて渡されなければならない。もしそうせず、ルールを破ってしまったら、隠れたバインディングに満ちた汚いコードへの道を歩み始め、理解不能になり、その結果、保守や開発に手間のかかるアプリケーションになってしまうからです。 +[ルールその1「渡されるようにする |#rule #1: Let It Be Passed to You]」:クラスが必要とする依存関係はすべて渡されなければならない、ということを思い出してみましょう。なぜなら、このルールを破ると、隠れた依存関係でいっぱいの汚いコード、理解不能なコードへの道に乗り出したことになり、その結果、保守や開発に苦痛を伴うアプリケーションになってしまうからです。 -クラス`Article` のユーザーは、メソッド`save()` がどこに記事を保存しているのか分からない。データベースのテーブルの中か?本番と開発、どちらで?そして、これはどのように変更することができるのでしょうか? +`Article` クラスのユーザーは、`save()` メソッドが記事をどこに保存するのか分からない。データベースのテーブルの中?本番とテスト、どちらでしょうか?そして、それはどのように変更できるのでしょうか? -ユーザは、`save()` のメソッドがどのように実装されているかを見て、`DB::insert()` のメソッドの用途を見つけなければならない。そのため、このメソッドがどのようにデータベース接続を調達しているのかを知るために、さらに検索しなければならないのです。そして、隠されたバインディングは非常に長い鎖を形成することができます。 +ユーザーは、`save()` メソッドがどのように実装されているかを見て、`DB::insert()` メソッドの使用を見つけなければなりません。そこで、このメソッドがどのようにデータベース接続を取得するのか、さらに調べなければならない。そして、隠された依存関係は非常に長い鎖を形成することができます。 -隠しバインディング、Laravelファサード、静的変数は、クリーンでよく設計されたコードには決して存在しません。きれいでよく設計されたコードでは、引数は渡されます。 +きれいに設計されたコードでは、隠れた依存関係、Laravelファサード、静的変数が存在することはありません。きれいでよくできたコードでは、引数は渡されます: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -さらに実用的なのは、次に見るように、コンストラクタを使うことです。 +後述するように、さらに実用的なアプローチは、コンストラクタを利用することになります: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -経験豊富なプログラマーであれば、`Article` は`save()` メソッドを一切持たず、純粋なデータコンポーネントとし、ストレージは別のリポジトリが担当するべきだと考えているかもしれませんね。これは理にかなっています。しかし、それでは依存性注入というテーマから大きく外れてしまいますし、簡単な例を挙げようとすると、そのようなことになります。 +経験豊富なプログラマーであれば、`Article` は`save()` メソッドを持つべきでないと考えるかもしれません。純粋にデータコンポーネントを表し、保存は別のリポジトリが行うべきでしょう。それはそれで理にかなっている。しかし、それでは、依存性注入という今回のテーマの範囲をはるかに超えてしまいますし、簡単な例を提供する努力も必要です。 -例えば、データベースを必要とするクラスを書く場合、どこからデータベースを取得するかは考えず、渡されたデータベースを使用するようにします。コンストラクタや他のメソッドのパラメータとして渡すとよいでしょう。依存関係を宣言する。クラスのAPIでそれらを公開する。そうすれば、理解しやすく、予測可能なコードを得ることができます。 +例えば、操作のためにデータベースを必要とするクラスを書く場合、そのデータベースをどこから取得するかは考えず、渡すようにします。コンストラクタのパラメータとして、あるいは別のメソッドとして。依存関係を認める。クラスのAPIで依存関係を認めてください。そうすれば、理解しやすく、予測可能なコードを得ることができます。 -エラーメッセージを記録するこのクラスはいかがでしょう。 +そして、エラーメッセージを記録するこのクラスはどうでしょう: ```php class Logger @@ -266,9 +266,9 @@ class Logger していないんです。 -重要な情報であるログファイルのディレクトリは、クラスが定数から*取得*します。 +重要な情報、すなわちログファイルのあるディレクトリは、クラス自身が定数から*取得*します。 -使用例をご覧ください。 +使用例を見てください: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -実装を知らないで、メッセージがどこに書かれているかという質問に答えられますか?LOG_DIR定数の存在が動作に必要であることを示唆しているのでしょうか?また、別の場所に書き込む2番目のインスタンスを作成することができるでしょうか?確かにそうですね。 +実装を知らなくても、どこにメッセージが書かれているかという質問に答えられるだろうか?`LOG_DIR` 定数の存在がその機能に必要であると推測できますか?そして、別の場所に書き込む2番目のインスタンスを作ることができるでしょうか?もちろん無理でしょう。 クラスを固定しよう。 @@ -295,7 +295,7 @@ class Logger } ``` -クラスがより明確になり、より設定しやすくなったので、より便利になりました。 +このクラスは、より理解しやすく、設定可能で、したがって、より便利なものになりました。 ```php $logger = new Logger('/path/to/log.txt'); @@ -303,16 +303,16 @@ $logger->log('The temperature is 15 °C'); ``` -But I Don't Care!.[#toc-but-i-don-t-care] ------------------------------------------ +But I Don't Care! .[#toc-but-i-don-t-care] +------------------------------------------ -*"Articleオブジェクトを作成してsave()を呼び出すと、データベースを扱わず、設定で設定したものに保存されるようにしたいのです。"* +*記事オブジェクトを作成してsave()を呼び出すと、データベースを扱うのではなく、設定で設定したものに保存されるだけでいいのです。 -*Loggerを使うときは、メッセージを書いてほしいだけで、どこをどうするかは考えたくないんです。グローバル設定を使わせてください。"* +*Loggerを使うときは、メッセージを書いてほしいだけで、どこをどうするかは考えたくないんです。グローバル設定を使わせてください」*。 -これらは正しいコメントです。 +これらは有効な指摘です。 -例として、ニュースレターを配信するクラスを取り上げ、それがどうだったかを記録してみましょう。 +例として、ニュースレターを送信し、その様子をログに残すクラスを見てみましょう: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -定数`LOG_DIR` を使用しなくなった拡張版`Logger` では、コンストラクタにファイルパスが必要です。これを解決するにはどうしたらいいのでしょうか?`NewsletterDistributor` クラスは、メッセージがどこに書き込まれるかは気にしません。 +`LOG_DIR` 定数を使用しなくなった改良版`Logger` では、コンストラクタでファイルパスを指定する必要があります。これを解決するにはどうしたらいいのでしょうか?`NewsletterDistributor` クラスは、メッセージがどこに書き込まれるかは気にしません。 -解決策は、やはり[ルールその1「渡されるようにする |#rule #1: Let It Be Passed to You]」:クラスが必要とするデータをすべて渡すことです。 +解決策は、やはり[ルールその1「渡されるように |#rule #1: Let It Be Passed to You]する」:クラスが必要とするデータをすべて渡すことです。 -そこで、コンストラクタにログへのパスを渡し、そのパスを使って`Logger` オブジェクトを作成するのです? +つまり、コンストラクタでログへのパスを渡し、`Logger` オブジェクトを作成する際にそれを使用するということでしょうか。 ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -そのようなことはありません!なぜなら、パスは`NewsletterDistributor` クラスが必要とするデータには属さず、`Logger` が必要だからです。このクラスが必要とするのは、ロガーそのものです。そして、それを渡すことになるのです。 +いいえ、このようなことはありません!パスは、`NewsletterDistributor` クラスが必要とするデータの中には含まれていません。実際には、`Logger` が必要とします。この違いがお分かりになりますか?`NewsletterDistributor` クラスは、ロガーそのものを必要としているのです。だから、それを渡すのです: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -`NewsletterDistributor` クラスのシグネチャから、ロギングがその機能の一部であることは明らかです。そして、ロガーを別のものに置き換えるという作業は、おそらくテスト目的であれば、非常に些細なことです。 -さらに、`Logger` クラスのコンストラクタが変更されても、私たちのクラスには何の影響もない。 +`NewsletterDistributor` クラスの署名から、ロギングもその機能の一部であることは明らかです。そして、ロガーを別のものと交換する作業(おそらくテストのため)は、完全に些細なことです。 +さらに、`Logger` クラスのコンストラクタが変更されても、私たちのクラスには影響しません。 -ルールその2:自分のものは自分で取る.[#toc-rule-2-take-what-is-yours] +ルールその2:自分のものは自分で取る .[#toc-rule-2-take-what-s-yours] --------------------------------------------------- -惑わされずに、依存関係のパラメータを渡さないようにしましょう。依存関係を直接渡す。 +惑わされずに、自分の依存関係を通さないようにしましょう。自分の依存関係を通すだけです。 -これにより、他のオブジェクトを使用するコードは、そのコンストラクタの変更に完全に依存しなくなります。そのAPIはより真実に近いものになるでしょう。そして最も重要なことは、これらの依存関係を他のものと交換することが簡単にできるようになることです。 +このおかげで、他のオブジェクトを使用するコードは、そのコンストラクタの変更に完全に依存しなくなります。そのAPIは、より真実に近いものになります。そして何よりも、これらの依存関係を他のものに置き換えるのは簡単なことなのです。 -新しい家族の一員.[#toc-a-new-member-of-the-family] ------------------------------------------- +新しい家族 .[#toc-new-family-member] +------------------------------- -開発チームは、データベースに書き込む2つ目のロガーを作成することにしました。そこで、`DatabaseLogger` というクラスを作成しました。つまり、`Logger` と`DatabaseLogger` という2つのクラスがあり、1つはファイルに書き込み、もう1つはデータベースに書き込む...この名前、何か変だと思いませんか? -`Logger` を`FileLogger` に改名したほうがいいのでは?そうですね。 +開発チームは、データベースに書き込む2つ目のロガーを作ることにしました。そこで、`DatabaseLogger` クラスを作成しました。つまり、`Logger` と`DatabaseLogger` の2つのクラスがあり、1つはファイルに書き込み、もう1つはデータベースに書き込みます...このネーミングは奇妙だと思いませんか? +`Logger` を`FileLogger` に改名した方が良いのではないでしょうか?間違いなくそうです。 -でも、スマートにやりましょう。元の名前でインターフェイスを作ることにします。 +でも、スマートにやってしまいましょう。元の名前でインターフェイスを作るのです: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...両ロガーが実施すること。 +... 両方のロガーが実装することになります: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -そして、この方法では、ロガーが使用される残りのコードでは何も変更する必要がありません。例えば、`NewsletterDistributor` クラスのコンストラクタは、`Logger` をパラメータとして要求することで、満足することができます。そして、どのインスタンスを渡すかは、私たち次第です。 +このため、ロガーが使用される他のコードでは、何も変更する必要がありません。例えば、`NewsletterDistributor` クラスのコンストラクタは、`Logger` をパラメータとして要求することで、満足することができます。そして、どのインスタンスを渡すかは、私たち次第です。 -**インターフェイス名に接尾辞`Interface` や接頭辞 `I` をつけないのはこのためです。** さもなければ、このようにきれいにコードを開発することは不可能です。 +**インターフェース名に`Interface` や`I` という接頭辞をつけないのはそのためです。** そうでなければ、こんなにきれいに開発することはできません。 -ヒューストン、問題が発生した.[#toc-houston-we-have-a-problem] ------------------------------------------------ +ヒューストン、問題が発生した .[#toc-houston-we-have-a-problem] +------------------------------------------------ -アプリケーション全体では、ファイルであれデータベースであれ、ロガーのインスタンスが1つあれば満足で、何かが記録される場所にはそれを渡すだけですが、`Article` クラスの場合は全く違います。実際、必要に応じてインスタンスを作成し、場合によっては複数回作成します。コンストラクタでデータベースのバインディングをどのように扱うか? +ファイルベースであれデータベースベースであれ、アプリケーション全体を通してロガーのインスタンスを1つ用意し、何かが記録される場所に渡すだけで、何とかなるものですが、`Article` クラスの場合は全く違います。必要に応じてインスタンスを作成し、複数回作成することもあります。コンストラクタでデータベースの依存関係をどのように扱うか? -例として、フォームを送信した後に記事をデータベースに保存するコントローラを使用することができます。 +例えば、フォームを送信した後、記事をデータベースに保存するコントローラが考えられます: ```php class EditController extends Controller @@ -437,30 +437,30 @@ class EditController extends Controller } ``` -コンストラクタで渡されるデータベースオブジェクトを`EditController` 、`$article = new Article($this->db)` を使用する、という解決策が直接提示されます。 +解決策としては、`EditController` のコンストラクタにデータベースオブジェクトを渡し、`$article = new Article($this->db)` を使用する方法が考えられます。 -先ほどの`Logger` とファイルパスの場合と同様に、これは正しいアプローチではありません。データベースは`EditController` の依存関係ではなく、`Article` の依存関係です。したがって、データベースを渡すことは[ルール2「自分のものを取る |#rule #2: take what is yours]」に反します。`Article` クラスのコンストラクタを変更する(新しいパラメータを追加する)と、インスタンスが生成されるすべての場所のコードも変更する必要があります。ウフフ。 +先ほどの`Logger` とファイルパスの場合と同様に、これは正しいアプローチではありません。データベースは`EditController` の依存関係ではなく、`Article` の依存関係です。データベースを渡すと、[ルール2「自分のものを取る |#rule #2: take what's yours]」に反します。`Article` クラスのコンストラクタが変更された場合(新しいパラメータが追加された場合)、インスタンスが作成される場所のコードを修正する必要があります。ウフフ。 -ヒューストン、何を言いたいんだ? +ヒューストン、どうする? -ルールその3:工場に任せる.[#toc-rule-3-let-the-factory-handle-it] ------------------------------------------------------ +ルールその3:工場に任せる .[#toc-rule-3-let-the-factory-handle-it] +------------------------------------------------------ -隠しバインディングを取り除き、すべての依存関係を引数として渡すことで、より設定可能で柔軟なクラスを手に入れることができます。そこで、より柔軟なクラスを作成・設定するために、別のものが必要になります。それをファクトリーと呼ぶことにします。 +隠れた依存関係を排除し、すべての依存関係を引数として渡すことで、私たちはより設定可能で柔軟なクラスを得ることができました。そのため、より柔軟なクラスを作成し、構成してくれるものが必要です。それをファクトリーと呼ぶことにします。 経験則では、クラスに依存性がある場合、そのインスタンスの作成はファクトリーに任せます。 -ファクトリーは、依存性注入の世界では、`new` 演算子に代わる、よりスマートな存在です。 +ファクトリーは、依存性注入の世界では、`new` 演算子に代わるスマートな存在です。 .[note] ファクトリーメソッド*デザインパターンと混同しないようにご注意ください。 -工場.[#toc-factory] ------------------ +工場 .[#toc-factory] +------------------ -ファクトリーとは、オブジェクトを生産し設定するメソッドやクラスのことです。`Article` プロデュースクラス`ArticleFactory` と呼びますが、次のようになる可能性があります。 +ファクトリーとは、オブジェクトを生成したり設定したりするメソッドやクラスのことです。ここでは、`Article` を生成するクラスを`ArticleFactory` と名付け、以下のような形にします: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -コントローラーでの使い方は次のようになります。 +コントローラーでの使い方は以下のようになります: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -この時点で、`Article` クラスのコンストラクタのシグネチャが変更された場合、対応する必要があるのは`ArticleFactory` ファクトリ自身だけです。`EditController` など、`Article` オブジェクトを扱う他のコードは影響を受けません。 +この時点で、`Article` クラスのコンストラクタのシグネチャが変更された場合、対応する必要があるのは、`ArticleFactory` 自身だけです。`EditController` のような`Article` オブジェクトを扱う他のすべてのコードは影響を受けません。 -今、あなたは、自分たちが役に立ったのだろうかと、額を叩いているかもしれません。コードの量は増え、全体が怪しく複雑に見えてきました。 +本当に良くなったのだろうかと疑問に思われるかもしれません。コードの量が増え、すべてが怪しく複雑に見えるようになりました。 -心配しないでください、すぐにNette DIコンテナを紹介します。このコンテナには、依存性注入を使ったアプリケーションを非常に簡単に構築するためのエースがいくつも用意されています。例えば、`ArticleFactory` クラスの代わりに、[シンプルなインターフェイスを書 |factory]けば十分です。 +心配しないでください、すぐにNette DIコンテナにたどり着きます。そして、このコンテナにはいくつかのトリックがあり、依存性注入を使ったアプリケーションの構築を大幅に簡略化することができます。例えば、`ArticleFactory` クラスの代わりに、[シンプルなインターフェイスを書くだけ |factory]でよいのです: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -でも、先走りすぎです、ちょっと待ってください :-) +でも、先を急ぐので、どうかご容赦ください :-) -概要.[#toc-summary] ------------------ +概要 .[#toc-summary] +------------------ -この章の冒頭で、きれいなコードを設計するための方法を紹介すると約束しました。ただ、クラス +この章の冒頭で、きれいなコードを設計するためのプロセスを紹介することを約束しました。必要なのは、クラスが -[- 必要とする依存関係 |#Rule #1: Let It Be Passed to You] -[- 直接的に必要なものでない |#Rule #2: Take What Is Yours]ものは -[- と、依存関係のあるオブジェクトは工場で作るのがベスト |#Rule #3: Let the Factory Handle it]であること +[- 必要な依存関係を渡す |#Rule #1: Let It Be Passed to You] +[- 逆に言えば、直接的に必要でないものは通さない |#Rule #2: Take What's Yours] +[- また、依存関係のあるオブジェクトはファクトリーで作成するのがベスト |#Rule #3: Let the Factory Handle it]であること -一見するとそう見えないかもしれませんが、この3つのルールは広範囲に影響を及ぼします。コード設計を根本から見直すきっかけになるのです。その価値はあるのか?古い習慣を捨て、依存性注入を一貫して使い始めたプログラマは、これを職業生活における極めて重要な瞬間とみなしています。それは、明確で持続可能なアプリケーションの世界を切り開いたのです。 +一見すると、この3つのルールは遠大な結果をもたらすようには見えないかもしれませんが、コード設計の視点を根本から変えることにつながるのです。その価値はあるのでしょうか?古い習慣を捨て、依存性注入を一貫して使い始めた開発者は、このステップが彼らの職業生活における決定的な瞬間であると考えます。依存性注入によって、明快で保守性の高いアプリケーションの世界が開かれたのです。 -しかし、そのコードが一貫して依存性注入を使用していない場合はどうでしょうか?静的メソッドやシングルトンで構築されていたらどうでしょう?それは何か問題をもたらすのでしょうか?それは[非常に重要な |global-state]ことです。 +しかし、そのコードが一貫して依存性注入を使用していない場合はどうでしょうか?静的メソッドやシングルトンに依存していたらどうでしょう?それは何か問題を引き起こすのでしょうか?そうです、[非常に基本的な |global-state]問題です。 diff --git a/dependency-injection/ja/passing-dependencies.texy b/dependency-injection/ja/passing-dependencies.texy index 86cf594ed3..22d913aed6 100644 --- a/dependency-injection/ja/passing-dependencies.texy +++ b/dependency-injection/ja/passing-dependencies.texy @@ -12,7 +12,7 @@ </div> -最初の3つの方法は、すべてのオブジェクト指向言語で一般的に適用されます。4番目の方法は、Nette プレゼンターに特有の方法なので、[別の |best-practices:en:inject-method-attribute]章で説明します。これから、これらのオプションのそれぞれを詳しく見て、具体的な例で紹介します。 +ここでは、さまざまなバリエーションを具体的な例で説明します。 コンストラクタ・インジェクション .[#toc-constructor-injection] @@ -21,17 +21,17 @@ 依存関係は、オブジェクトが作成されるときにコンストラクタに引数として渡されます。 ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` この形式は、クラスが機能するために絶対に必要な必須の依存関係を指定するのに便利です。なぜなら、その依存関係がなければインスタンスを作成することができないからです。 @@ -40,10 +40,10 @@ PHP 8.0 以降、機能的に同等な短い形式の記法を使用すること ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ PHP 8.1 以降では、プロパティにフラグ`readonly` を指定して、 ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService DI コンテナは、[自動配線によって |autowiring]依存関係をコンストラクタに渡します。この方法で渡すことができない引数(文字列、数値、ブール値など)は、[設定に書きます |services#Arguments]。 +コンストラクター地獄 .[#toc-constructor-hell] +----------------------------------- + +コンストラクタ地獄とは、コンストラクタが依存性を要求する親クラスを子が継承し、その子も依存性を要求する状況を指す言葉である。また、親クラスの依存関係を引き継がなければなりません。 + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +問題は、新しい依存関係が追加されたときなど、`BaseClass` クラスのコンストラクタを変更したいときに発生します。そうすると、子クラスのコンストラクタもすべて変更しなければならない。そのため、このような修正は地獄のようなものです。 + +これを防ぐにはどうしたらいいのでしょうか?解決策は、**継承よりも構成を優先させる**ことです。 + +そこで、コードを違った形で設計してみましょう。抽象的な`Base*` クラスは避けることにします。`MyClass` が`BaseClass` を継承して機能を得る代わりに、その機能は依存関係として渡されるようにします。 + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + セッター・インジェクション .[#toc-setter-injection] ====================================== -依存関係は、プライベートプロパティに格納するメソッドを呼び出すことで渡されます。これらのメソッドの通常の命名規則は、`set*()` という形式であり、これがセッターと呼ばれる所以です。 +依存関係は、プライベート・プロパティに格納するメソッドを呼び出すことで渡されます。これらのメソッドの通常の命名規則は、`set*()` という形式です。そのため、セッターと呼ばれていますが、もちろん、他の名前で呼ぶこともできます。 ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` このメソッドは、オブジェクトが実際にそれを受け取る(つまり、ユーザーがメソッドを呼び出す)ことが保証されていないため、クラス機能に必要のないオプションの依存関係に対して便利です。 @@ -90,16 +150,16 @@ $service->setCache($cache); 同時に、このメソッドによって、依存関係を変更するためにセッターを繰り返し呼び出すことができます。これが望ましくない場合は、メソッドにチェックを加えるか、 PHP 8.1 以降は`readonly` フラグでプロパティ`$cache` をマークします。 ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ setter の呼び出しは、DI コンテナの設定[セクション setup |serv ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ services: 依存関係は直接プロパティに渡されます。 ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` なぜなら、プロパティは`public` として宣言されなければならないからです。 したがって、渡された依存関係が実際に指定された型になるかどうかを制御することはできませんし (これは PHP 7.4 以前も同様でした)、新しく割り当てられた依存関係に独自のコードで対応する能力、 たとえばその後の変更を防止する能力も失われています。同時に、このプロパティはクラスのパブリックインターフェイスの一部となり、 望ましくないかもしれません。 @@ -137,12 +197,18 @@ $service->cache = $cache; ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +インジェクト .[#toc-inject] +===================== + +前の3つの方法は、一般的にすべてのオブジェクト指向言語で有効ですが、メソッド、アノテーション、*inject*属性による注入は、Netteプレゼンターに特有の方法です。これらは[別章で |best-practices:en:inject-method-attribute]説明します。 + + どの方法を選択するか? .[#toc-which-way-to-choose] ======================================= diff --git a/dependency-injection/ja/services.texy b/dependency-injection/ja/services.texy index c75f3603f7..f4b2054bee 100644 --- a/dependency-injection/ja/services.texy +++ b/dependency-injection/ja/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); インジェクトモード .[#toc-inject-mode] ============================= -`inject: true` フラグは、[inject |best-practices:en:inject-method-attribute#Inject Annotations]アノテーションと[inject*() |best-practices:en:inject-method-attribute#inject Methods]メソッドによるパブリック変数経由の依存関係の受け渡しを有効にするために使用されます。 +`inject: true` フラグは、[inject |best-practices:en:inject-method-attribute#Inject Attributes]アノテーションと[inject*() |best-practices:en:inject-method-attribute#inject Methods]メソッドによるパブリック変数経由の依存関係の受け渡しを有効にするために使用されます。 ```neon services: diff --git a/dependency-injection/pl/@home.texy b/dependency-injection/pl/@home.texy index d47b7d35c3..37148286cc 100644 --- a/dependency-injection/pl/@home.texy +++ b/dependency-injection/pl/@home.texy @@ -5,8 +5,10 @@ Dependency Injection Dependency Injection jest wzorcem projektowym, który fundamentalnie zmieni sposób w jaki patrzysz na kod i rozwój. Otwiera on drogę do świata czysto zaprojektowanych i trwałych aplikacji. - [Co to jest Wstrzykiwanie Zależności? |introduction] -- [Co to jest kontener DI? |container] +- [Stan globalny i singletony |global-state] - [Przekazywanie zależności|passing-dependencies] +- [Co to jest kontener DI? |container] +- [Najczęściej zadawane pytania |faq] Nette DI diff --git a/dependency-injection/pl/@left-menu.texy b/dependency-injection/pl/@left-menu.texy index 68cc779c43..58f1a5f291 100644 --- a/dependency-injection/pl/@left-menu.texy +++ b/dependency-injection/pl/@left-menu.texy @@ -1,8 +1,10 @@ Wtrysk zależności ***************** - [Co to jest DI? |introduction] -- [Co to jest kontener DI? |container] +- [Stan globalny i singletony |global-state] - [Przekazywanie zależności |passing-dependencies] +- [Co to jest kontener DI? |container] +- [Najczęściej zadawane pytania |faq] Nette DI diff --git a/dependency-injection/pl/faq.texy b/dependency-injection/pl/faq.texy new file mode 100644 index 0000000000..46b1bd8cc7 --- /dev/null +++ b/dependency-injection/pl/faq.texy @@ -0,0 +1,112 @@ +Najczęściej zadawane pytania dotyczące DI (FAQ) +*********************************************** + + +Czy DI to kolejna nazwa dla IoC? .[#toc-is-di-another-name-for-ioc] +------------------------------------------------------------------- + +*Inversion of Control* (IoC) to zasada skupiająca się na sposobie wykonywania kodu - czy twój kod inicjuje kod zewnętrzny, czy też twój kod jest zintegrowany z kodem zewnętrznym, który następnie go wywołuje. +IoC jest szeroką koncepcją, która obejmuje [zdarzenia |nette:glossary#Events], tzw. zasadę [Hollywood |application:components#Hollywood style] i inne aspekty. +Fabryki, które są częścią [zasady # 3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], i reprezentują inwersję dla operatora `new`, są również składnikami tej koncepcji. + +*Dependency Injection* (DI) dotyczy tego, jak jeden obiekt wie o innym obiekcie, czyli zależności. Jest to wzorzec projektowy, który wymaga jawnego przekazywania zależności między obiektami. + +Można więc powiedzieć, że DI jest specyficzną formą IoC. Jednak nie wszystkie formy IoC są odpowiednie pod względem czystości kodu. Na przykład do anty-wzorców zaliczamy wszystkie techniki pracujące z [globalnym stanem |global state] lub tzw. [Service Locator |#What is a Service Locator]. + + +Czym jest Service Locator? .[#toc-what-is-a-service-locator] +------------------------------------------------------------ + +Service Locator jest alternatywą dla Dependency Injection. Działa on poprzez stworzenie centralnego magazynu, w którym zarejestrowane są wszystkie dostępne usługi lub zależności. Kiedy obiekt potrzebuje zależności, żąda jej z lokalizatora usług. + +Jednakże, w porównaniu z Dependency Injection, traci na przejrzystości: zależności nie są bezpośrednio przekazywane do obiektów i dlatego nie są łatwo identyfikowalne, co wymaga zbadania kodu, aby odkryć i zrozumieć wszystkie połączenia. Testowanie jest również bardziej skomplikowane, ponieważ nie możemy po prostu przekazać obiektów mock do testowanych obiektów, ale musimy przejść przez Service Locator. Ponadto Service Locator zakłóca projektowanie kodu, ponieważ poszczególne obiekty muszą być świadome jego istnienia, co różni się od Dependency Injection, gdzie obiekty nie mają wiedzy o kontenerze DI. + + +Kiedy lepiej nie używać DI? .[#toc-when-is-it-better-not-to-use-di] +------------------------------------------------------------------- + +Nie są znane trudności związane z używaniem wzorca projektowego Dependency Injection. Wręcz przeciwnie, uzyskiwanie zależności z globalnie dostępnych lokalizacji prowadzi do [wielu komplikacji |global-state], podobnie jak używanie Service Locatora. +Dlatego zaleca się, aby zawsze używać DI. Nie jest to podejście dogmatyczne, ale po prostu nie znaleziono lepszej alternatywy. + +Istnieją jednak pewne sytuacje, w których nie przekazujemy sobie obiektów i nie uzyskujemy ich z przestrzeni globalnej. Na przykład podczas debugowania kodu i konieczności zrzucenia wartości zmiennej w określonym punkcie programu, zmierzenia czasu trwania pewnego fragmentu programu, czy też zalogowania komunikatu. +W takich przypadkach, gdy chodzi o działania tymczasowe, które później zostaną usunięte z kodu, uzasadnione jest użycie globalnie dostępnego dumpera, stopera czy loggera. Narzędzia te nie należą przecież do projektu kodu. + + +Czy używanie DI ma swoje wady? .[#toc-does-using-di-have-its-drawbacks] +----------------------------------------------------------------------- + +Czy używanie Dependency Injection wiąże się z jakimiś wadami, takimi jak zwiększenie złożoności pisania kodu lub gorsza wydajność? Co tracimy, gdy zaczynamy pisać kod zgodnie z DI? + +DI nie ma wpływu na wydajność aplikacji ani na wymagania dotyczące pamięci. Wydajność kontenera DI może odgrywać pewną rolę, ale w przypadku [Nette DI | nette-container], kontener jest skompilowany do czystego PHP, więc jego narzut w czasie działania aplikacji jest w zasadzie zerowy. + +Podczas pisania kodu konieczne jest tworzenie konstruktorów, które akceptują zależności. W przeszłości mogło to być czasochłonne, ale dzięki nowoczesnym IDE i [promowaniu właściwości |https://blog.nette.org/pl/php-8-0-kompletny-przeglad-nowosci#toc-constructor-property-promotion] konstruktorów jest to obecnie kwestia kilku sekund. Fabryki można łatwo wygenerować za pomocą Nette DI i wtyczki PhpStorm za pomocą kilku kliknięć. +Z drugiej strony nie ma potrzeby pisania singletonów i statycznych punktów dostępu. + +Można stwierdzić, że prawidłowo zaprojektowana aplikacja wykorzystująca DI nie jest ani krótsza, ani dłuższa w porównaniu z aplikacją wykorzystującą singletony. Części kodu pracujące z zależnościami są po prostu wyodrębnione z poszczególnych klas i przeniesione w nowe miejsca, czyli do kontenera DI i fabryk. + + +Jak przepisać starszą aplikację na DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] +---------------------------------------------------------------------------------------- + +Migracja ze starszej aplikacji do Dependency Injection może być trudnym procesem, szczególnie w przypadku dużych i złożonych aplikacji. Ważne jest, aby podejść do tego procesu systematycznie. + +- Podczas przechodzenia na Dependency Injection ważne jest, aby wszyscy członkowie zespołu rozumieli zasady i praktyki, które są używane. +- Najpierw przeprowadź analizę istniejącej aplikacji, aby zidentyfikować kluczowe komponenty i ich zależności. Stwórz plan, które części będą refaktoryzowane i w jakiej kolejności. +- Zaimplementuj kontener DI lub, jeszcze lepiej, użyj istniejącej biblioteki, takiej jak Nette DI. +- Stopniowo refaktoryzuj każdą część aplikacji, aby użyć Dependency Injection. Może to obejmować modyfikację konstruktorów lub metod, aby akceptowały zależności jako parametry. +- Zmodyfikuj miejsca w kodzie, gdzie tworzone są obiekty zależności, tak aby zależności były zamiast tego wstrzykiwane przez kontener. Może to obejmować użycie fabryk. + +Pamiętaj, że przejście na Dependency Injection jest inwestycją w jakość kodu i długoterminową stabilność aplikacji. Chociaż wprowadzenie tych zmian może być wyzwaniem, rezultatem powinien być czystszy, bardziej modułowy i łatwo testowalny kod, który jest gotowy do przyszłych rozszerzeń i konserwacji. + + +Dlaczego kompozycja jest preferowana nad dziedziczeniem? .[#toc-why-composition-is-preferred-over-inheritance] +-------------------------------------------------------------------------------------------------------------- +Lepiej jest używać kompozycji niż dziedziczenia, ponieważ służy to celowi ponownego użycia kodu bez potrzeby martwienia się o efekt podstępnej zmiany. W ten sposób zapewnia bardziej luźne sprzężenie, gdzie nie musimy się martwić o to, że zmiana jakiegoś kodu powoduje, że inny zależny kod wymaga zmiany. Typowym przykładem jest sytuacja określona jako [piekło konstruktorów |passing-dependencies#Constructor hell]. + + +Czy Nette DI Container może być używany poza Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] +----------------------------------------------------------------------------------------------------------- + +Absolutnie. Nette DI Container jest częścią Nette, ale został zaprojektowany jako samodzielna biblioteka, która może być używana niezależnie od innych części frameworka. Wystarczy zainstalować ją za pomocą Composera, stworzyć plik konfiguracyjny definiujący Twoje usługi, a następnie użyć kilku linii kodu PHP do stworzenia kontenera DI. +I możesz natychmiast zacząć wykorzystywać Dependency Injection w swoich projektach. + +Rozdział [Nette DI Container |nette-container] opisuje, jak wygląda konkretny przypadek użycia, łącznie z kodem. + + +Dlaczego konfiguracja znajduje się w plikach NEON? .[#toc-why-is-the-configuration-in-neon-files] +------------------------------------------------------------------------------------------------- + +NEON to prosty i czytelny język konfiguracyjny opracowany w ramach Nette, służący do konfigurowania aplikacji, usług i ich zależności. W porównaniu do JSON czy YAML, oferuje znacznie bardziej intuicyjne i elastyczne opcje w tym zakresie. W NEONie możesz naturalnie opisać wiązania, które w Symfony & YAML nie byłyby możliwe do napisania w ogóle lub tylko poprzez skomplikowany opis. + + +Czy parsowanie plików NEON spowalnia działanie aplikacji? .[#toc-does-parsing-neon-files-slow-down-the-application] +------------------------------------------------------------------------------------------------------------------- + +Chociaż pliki NEON są parsowane bardzo szybko, aspekt ten nie ma większego znaczenia. Powodem jest to, że parsowanie plików występuje tylko raz podczas pierwszego uruchomienia aplikacji. Następnie kod kontenera DI jest generowany, przechowywany na dysku i wykonywany dla każdego kolejnego żądania bez potrzeby dalszego parsowania. + +Tak właśnie działa to w środowisku produkcyjnym. Podczas tworzenia aplikacji pliki NEON są parsowane za każdym razem, gdy zmienia się ich zawartość, co zapewnia, że programista ma zawsze aktualny kontener DI. Jak wspomniano wcześniej, faktyczne parsowanie jest kwestią chwili. + + +Jak uzyskać dostęp do parametrów z pliku konfiguracyjnego w mojej klasie? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +-------------------------------------------------------------------------------------------------------------------------------------------------------- + +Należy pamiętać o [regule #1: Let It Be Passed to |introduction#Rule #1: Let It Be Passed to You] You. Jeśli klasa wymaga informacji z pliku konfiguracyjnego, nie musimy wymyślać jak uzyskać dostęp do tych informacji; zamiast tego, po prostu o nie prosimy - na przykład poprzez konstruktor klasy. A my wykonujemy przekazywanie informacji w pliku konfiguracyjnym. + +W tym przykładzie, `%myParameter%` to placeholder dla wartości parametru `myParameter`, który zostanie przekazany do konstruktora `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Jeśli chcesz przekazać wiele parametrów lub użyć autowiring, warto [zawinąć parametry w obiekt |best-practices:passing-settings-to-presenters]. + + +Czy Nette obsługuje interfejs PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] +----------------------------------------------------------------------------------------------------- + +Nette DI Container nie obsługuje bezpośrednio PSR-11. Jeśli jednak potrzebujesz interoperacyjności między Nette DI Container a bibliotekami lub frameworkami, które oczekują interfejsu PSR-11 Container, możesz stworzyć [prosty adapter |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], który posłuży jako most między Nette DI Container a PSR-11. diff --git a/dependency-injection/pl/global-state.texy b/dependency-injection/pl/global-state.texy new file mode 100644 index 0000000000..d44453c450 --- /dev/null +++ b/dependency-injection/pl/global-state.texy @@ -0,0 +1,312 @@ +Stan globalny i singletony +************************** + +.[perex] +Ostrzeżenie: poniższe konstrukcje są objawami złego projektowania kodu: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` lub `static::$var` + +Czy któryś z tych konstruktów występuje w Twoim kodzie? W takim razie masz okazję do poprawy. Możesz myśleć, że są to powszechne konstrukcje, które widzimy w przykładowych rozwiązaniach różnych bibliotek i frameworków. +Niestety, nadal są one wyraźnym wskaźnikiem złego projektu. Mają one jedną wspólną cechę: wykorzystanie stanu globalnego. + +Teraz z pewnością nie mówimy o jakiejś akademickiej czystości. Używanie globalnego stanu i singletonów ma destrukcyjny wpływ na jakość kodu. Jego zachowanie staje się nieprzewidywalne, zmniejsza produktywność programistów i zmusza interfejsy klas do kłamania na temat ich prawdziwych zależności. Co dezorientuje programistów. + +W tym rozdziale pokażemy, jak to jest możliwe. + + +Globalne powiązania .[#toc-global-interlinking] +----------------------------------------------- + +Podstawowy problem z globalnym stanem polega na tym, że jest on globalnie dostępny. Umożliwia to zapis do bazy danych poprzez globalną (statyczną) metodę `DB::insert()`. +W idealnym świecie obiekt powinien być w stanie komunikować się tylko z innymi obiektami, które zostały [bezpośrednio przekazane do |passing-dependencies] niego. +Jeśli stworzę dwa obiekty `A` i `B` i nigdy nie przekażę referencji z `A` do `B`, to ani `A`, ani `B` nie mogą uzyskać dostępu do drugiego obiektu ani zmienić jego stanu. +Jest to bardzo pożądana cecha kodu. Jest to podobne do posiadania baterii i żarówki; żarówka nie będzie świecić, dopóki nie połączysz ich przewodem. + +Nie jest to prawdą w przypadku zmiennych globalnych (statycznych) lub singletonów. Obiekt `A` mógłby *bezprzewodowo* uzyskać dostęp do obiektu `C` i zmodyfikować go bez przekazywania referencji, poprzez wywołanie `C::changeSomething()`. +Jeśli obiekt `B` chwyci również globalny `C`, to `A` i `B` mogą współdziałać ze sobą poprzez `C`. + +Użycie zmiennych globalnych wprowadza do systemu nową formę *bezprzewodowego* sprzężenia, która nie jest widoczna z zewnątrz. +Tworzy zasłonę dymną komplikującą zrozumienie i wykorzystanie kodu. +Programiści muszą czytać każdą linię kodu źródłowego, aby naprawdę zrozumieć zależności. Zamiast po prostu zapoznać się z interfejsem klas. +Co więcej, jest to całkowicie niepotrzebne sprzężenie. + +.[note] +Pod względem zachowania nie ma różnicy między zmienną globalną a statyczną. Są one równie szkodliwe. + + +Upiorne działanie na odległość .[#toc-the-spooky-action-at-a-distance] +---------------------------------------------------------------------- + +"Upiorne działanie na odległość" - tak Albert Einstein nazwał słynne zjawisko w fizyce kwantowej, które w 1935 roku przyprawiło go o dreszcze. +Chodzi o splątanie kwantowe, którego osobliwość polega na tym, że gdy mierzymy informację o jednej cząstce, natychmiast wpływamy na inną cząstkę, nawet jeśli są one oddalone od siebie o miliony lat świetlnych. +Co pozornie narusza podstawowe prawo wszechświata, że nic nie może podróżować szybciej niż światło. + +W świecie oprogramowania "upiornym działaniem na odległość" możemy nazwać sytuację, w której uruchamiamy proces, o którym myślimy, że jest izolowany (bo nie przekazaliśmy mu żadnych referencji), ale nieoczekiwane interakcje i zmiany stanu zachodzą w odległych miejscach systemu, o których nie powiedzieliśmy obiektowi. Może to nastąpić tylko poprzez stan globalny. + +Wyobraź sobie, że dołączasz do zespołu rozwijającego projekt, który ma dużą, dojrzałą bazę kodu. Twój nowy lider prosi cię o wdrożenie nowej funkcji i, jak dobry deweloper, zaczynasz od napisania testu. Ale ponieważ jesteś nowy w projekcie, robisz wiele testów eksploracyjnych "co się stanie, jeśli zadzwonię do tej metody" typu. I próbujesz napisać następujący test: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // numer karty + $cc->charge(100); +} +``` + +Uruchamiasz kod, może kilka razy, i po jakimś czasie zauważasz na swoim telefonie powiadomienia z banku, że przy każdym uruchomieniu 100$ zostało pobrane z Twojej karty kredytowej 🤦‍♂️ + +Jak u licha test mógł spowodować faktyczne obciążenie? Nie jest łatwo operować kartą kredytową. Musisz wejść w interakcję z usługą internetową strony trzeciej, musisz znać adres URL tej usługi internetowej, musisz się zalogować i tak dalej. +Żadna z tych informacji nie jest zawarta w teście. Co gorsza, nie wiesz nawet, gdzie te informacje są obecne, a zatem jak kpić z zewnętrznych zależności, aby każdy bieg nie powodował ponownego naliczania 100 USD. A jako nowy deweloper, jak miałeś wiedzieć, że to, co zamierzasz zrobić, doprowadzi do tego, że będziesz uboższy o 100 dolarów? + +To jest upiorne działanie na odległość! + +Nie masz wyboru, musisz przekopać się przez wiele kodu źródłowego, pytając starszych i bardziej doświadczonych kolegów, aż zrozumiesz, jak działają połączenia w projekcie. +Wynika to z faktu, że patrząc na interfejs klasy `CreditCard`, nie można określić stanu globalnego, który musi zostać zainicjalizowany. Nawet patrząc na kod źródłowy klasy nie dowiesz się, którą metodę inicjalizacji należy wywołać. W najlepszym przypadku możesz znaleźć zmienną globalną, do której uzyskuje się dostęp, i spróbować zgadnąć, jak ją zainicjalizować z tego. + +Klasy w takim projekcie są patologicznymi kłamcami. Karta płatnicza udaje, że można ją po prostu zainicjować i wywołać metodę `charge()`. Jednak potajemnie współdziała z inną klasą, `PaymentGateway`. Nawet jej interfejs mówi, że może być inicjalizowana niezależnie, ale w rzeczywistości ściąga poświadczenia z jakiegoś pliku konfiguracyjnego i tak dalej. +Dla programistów, którzy napisali ten kod, jest jasne, że `CreditCard` potrzebuje `PaymentGateway`. Napisali kod w ten sposób. Ale dla każdego nowego w projekcie jest to kompletna tajemnica i utrudnia naukę. + +Jak naprawić tę sytuację? Łatwo. **Pozwól API zadeklarować zależności**. + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Zauważ, jak zależności wewnątrz kodu są nagle oczywiste. Deklarując, że metoda `charge()` potrzebuje `PaymentGateway`, nie musisz nikogo pytać, w jaki sposób kod jest współzależny. Wiesz, że musisz stworzyć jego instancję, a kiedy próbujesz to zrobić, natrafiasz na fakt, że musisz dostarczyć parametry dostępu. Bez nich kod nawet by się nie uruchomił. + +I co najważniejsze, możesz teraz kpić z bramki płatności, więc nie zostaniesz obciążony 100 $ za każdym razem, gdy uruchomisz test. + +Stan globalny powoduje, że twoje obiekty mogą potajemnie uzyskać dostęp do rzeczy, które nie są zadeklarowane w ich interfejsach API, a w rezultacie czyni twoje interfejsy API patologicznymi kłamcami. + +Być może wcześniej nie myślałeś o tym w ten sposób, ale zawsze, gdy używasz stanu globalnego, tworzysz tajne kanały komunikacji bezprzewodowej. Przerażające zdalne działanie zmusza programistów do czytania każdej linijki kodu, aby zrozumieć potencjalne interakcje, zmniejsza produktywność programistów i dezorientuje nowych członków zespołu. +Jeśli jesteś tym, który stworzył kod, znasz prawdziwe zależności, ale każdy, kto przyjdzie po tobie, nie ma pojęcia. + +Nie pisz kodu, który używa globalnego stanu, wolą przekazać zależności. To jest zastrzyk zależności. + + +Kruchość globalnego państwa .[#toc-brittleness-of-the-global-state] +------------------------------------------------------------------- + +W kodzie, który używa stanu globalnego i singletonów, nigdy nie ma pewności, kiedy i przez kogo ten stan został zmieniony. To ryzyko pojawia się już podczas inicjalizacji. Poniższy kod ma za zadanie utworzyć połączenie z bazą danych i zainicjalizować bramkę płatniczą, ale ciągle rzuca wyjątek, a znalezienie przyczyny jest niezwykle żmudne: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Musisz szczegółowo przejrzeć kod, aby znaleźć, że obiekt `PaymentGateway` uzyskuje dostęp do innych obiektów bezprzewodowo, z których niektóre wymagają połączenia z bazą danych. Musisz więc zainicjalizować bazę danych przed `PaymentGateway`. Jednak zasłona dymna stanu globalnego ukrywa to przed tobą. Ile czasu zaoszczędziłbyś, gdyby API każdej klasy nie kłamało i nie deklarowało swoich zależności? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Podobny problem pojawia się podczas korzystania z globalnego dostępu do połączenia z bazą danych: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Podczas wywoływania metody `save()` nie ma pewności, czy połączenie z bazą danych zostało już utworzone i kto jest odpowiedzialny za jego utworzenie. Na przykład, gdybyśmy chcieli zmienić połączenie z bazą danych w locie, być może w celach testowych, prawdopodobnie musielibyśmy stworzyć dodatkowe metody, takie jak `DB::reconnect(...)` lub `DB::reconnectForTest()`. + +Rozważmy przykład: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Skąd możemy mieć pewność, że testowa baza danych jest naprawdę używana podczas wywoływania metody `$article->save()`? Co by było, gdyby metoda `Foo::doSomething()` zmieniła globalne połączenie z bazą danych? Aby się tego dowiedzieć, musielibyśmy zbadać kod źródłowy klasy `Foo` i prawdopodobnie wielu innych klas. Jednak takie podejście dałoby tylko krótkotrwałą odpowiedź, ponieważ sytuacja może się zmienić w przyszłości. + +Co by się stało, gdybyśmy przenieśli połączenie z bazą danych do zmiennej statycznej wewnątrz klasy `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +To w ogóle niczego nie zmienia. Problem jest stanem globalnym i nie ma znaczenia, w której klasie się ukrywa. W tym przypadku, podobnie jak w poprzednim, nie mamy pojęcia, do jakiej bazy danych jest zapisywana w momencie wywołania metody `$article->save()`. Ktokolwiek na odległym końcu aplikacji mógłby w każdej chwili zmienić bazę danych za pomocą `Article::setDb()`. Pod naszymi rękami. + +Stan globalny sprawia, że nasza aplikacja jest **ekstremalnie krucha**. + +Istnieje jednak prosty sposób na poradzenie sobie z tym problemem. Wystarczy kazać API zadeklarować zależności, aby zapewnić odpowiednią funkcjonalność. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Takie podejście eliminuje obawy o ukryte i nieoczekiwane zmiany w połączeniach z bazą danych. Teraz mamy pewność, gdzie przechowywany jest artykuł i żadne modyfikacje kodu wewnątrz innej niepowiązanej klasy nie mogą już zmienić sytuacji. Kod nie jest już kruchy, ale stabilny. + +Nie pisz kodu, który korzysta z globalnego stanu, wolisz przekazać zależności. A więc dependency injection. + + +Singleton .[#toc-singleton] +--------------------------- + +Singleton to wzorzec projektowy, który z [definicji |https://en.wikipedia.org/wiki/Singleton_pattern] ze słynnej publikacji Gang of Four ogranicza klasę do pojedynczej instancji i oferuje do niej globalny dostęp. Implementacja tego wzorca zazwyczaj przypomina następujący kod: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // oraz inne metody realizujące funkcje klasy +} +``` + +Niestety, singleton wprowadza do aplikacji stan globalny. A jak pokazaliśmy powyżej, stan globalny jest niepożądany. Dlatego właśnie singleton jest uważany za antypattern. + +Nie używaj singletonów w swoim kodzie i zastąp je innymi mechanizmami. Naprawdę nie potrzebujesz singletonów. Jeśli jednak musisz zagwarantować istnienie pojedynczej instancji klasy dla całej aplikacji, zostaw to [kontenerowi DI |container]. +W ten sposób utwórz singleton aplikacji, czyli usługę. Dzięki temu klasa przestanie zapewniać własną unikalność (tzn. Nie będzie miała metody `getInstance()` i zmiennej statycznej) i będzie wykonywać tylko swoje funkcje. Tym samym przestanie naruszać zasadę pojedynczej odpowiedzialności. + + +Stan globalny a testy .[#toc-global-state-versus-tests] +------------------------------------------------------- + +Pisząc testy, zakładamy, że każdy test jest izolowaną jednostką i że nie wchodzi do niego żaden zewnętrzny stan. I żaden stan nie opuszcza testów. Kiedy test się kończy, wszelkie stany związane z testem powinny być automatycznie usuwane przez garbage collector. To sprawia, że testy są odizolowane. Dlatego możemy uruchamiać testy w dowolnej kolejności. + +Jednakże, jeśli obecne są globalne stany/singletony, wszystkie te miłe założenia ulegają załamaniu. Stan może wejść i wyjść z testu. Nagle okazuje się, że kolejność wykonywania testów może mieć znaczenie. + +Aby w ogóle testować singletony, programiści często muszą rozluźnić ich właściwości, być może pozwalając na zastąpienie jednej instancji inną. Takie rozwiązania są w najlepszym wypadku hackami, które produkują kod trudny do utrzymania i zrozumienia. Każdy test lub metoda `tearDown()`, która wpływa na jakikolwiek stan globalny, musi cofnąć te zmiany. + +Globalny stan jest największym bólem głowy w testach jednostkowych! + +Jak naprawić tę sytuację? Proste. Nie pisz kodu, który używa singletonów, wolisz przekazywać zależności. Czyli dependency injection. + + +Stałe globalne .[#toc-global-constants] +--------------------------------------- + +Stan globalny nie jest ograniczony do używania singletonów i zmiennych statycznych, ale może również dotyczyć stałych globalnych. + +Stałe, których wartość nie dostarcza nam żadnych nowych (`M_PI`) lub użytecznych (`PREG_BACKTRACK_LIMIT_ERROR`) informacji są oczywiście OK. +I odwrotnie, stałe, które służą jako sposób na *bezprzewodowe* przekazywanie informacji wewnątrz kodu, są niczym więcej niż ukrytą zależnością. Jak `LOG_FILE` w poniższym przykładzie. +Używanie stałej `FILE_APPEND` jest całkowicie poprawne. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +W tym przypadku powinniśmy zadeklarować parametr w konstruktorze klasy `Foo`, aby uczynić go częścią interfejsu API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Teraz możemy przekazać informację o ścieżce do pliku logowania i łatwo ją zmienić w razie potrzeby, co ułatwi testowanie i utrzymanie kodu. + + +Funkcje globalne i metody statyczne .[#toc-global-functions-and-static-methods] +------------------------------------------------------------------------------- + +Chcemy podkreślić, że używanie metod statycznych i funkcji globalnych samo w sobie nie jest problematyczne. Wyjaśniliśmy niewłaściwość używania `DB::insert()` i podobnych metod, ale zawsze chodziło o stan globalny przechowywany w zmiennej statycznej. Metoda `DB::insert()` wymaga istnienia zmiennej statycznej, ponieważ przechowuje ona połączenie z bazą danych. Bez tej zmiennej implementacja metody byłaby niemożliwa. + +Stosowanie deterministycznych metod i funkcji statycznych, takich jak `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` i wielu innych, jest doskonale zgodne z wstrzykiwaniem zależności. Funkcje te zawsze zwracają te same wyniki z tych samych parametrów wejściowych i dlatego są przewidywalne. Nie używają one żadnego stanu globalnego. + +W PHP istnieją jednak funkcje, które nie są deterministyczne. Należy do nich na przykład funkcja `htmlspecialchars()`. Jej trzeci parametr, `$encoding`, jeśli nie zostanie określony, domyślnie przyjmuje wartość opcji konfiguracyjnej `ini_get('default_charset')`. Dlatego zaleca się zawsze podawać ten parametr, aby uniknąć ewentualnego nieprzewidywalnego zachowania funkcji. Nette konsekwentnie to robi. + +Niektóre funkcje, takie jak `strtolower()`, `strtoupper()`, i tym podobne, miały w niedawnej przeszłości niedeterministyczne zachowanie i zależały od ustawienia `setlocale()`. Powodowało to wiele komplikacji, najczęściej podczas pracy z językiem tureckim. +Dzieje się tak dlatego, że język turecki rozróżnia duże i małe litery `I` z i bez kropki. Tak więc `strtolower('I')` zwracał znak `ı`, a `strtoupper('i')` zwracał znak `İ`, co prowadziło do aplikacji powodujących wiele tajemniczych błędów. +Jednak ten problem został naprawiony w PHP w wersji 8.2 i funkcje nie są już zależne od locale. + +Jest to ładny przykład tego, jak stan globalny nękał tysiące programistów na całym świecie. Rozwiązaniem było zastąpienie go zastrzykiem zależności. + + +Kiedy można użyć stanu globalnego? .[#toc-when-is-it-possible-to-use-global-state] +---------------------------------------------------------------------------------- + +Istnieją pewne specyficzne sytuacje, w których możliwe jest użycie stanu globalnego. Na przykład, gdy debugujemy kod i musimy zrzucić wartość zmiennej lub zmierzyć czas trwania określonej części programu. W takich przypadkach, które dotyczą działań tymczasowych, które później zostaną usunięte z kodu, uzasadnione jest użycie dostępnego globalnie dumpera lub stopera. Narzędzia te nie są częścią projektu kodu. + +Innym przykładem są funkcje do pracy z wyrażeniami regularnymi `preg_*`, które wewnętrznie przechowują skompilowane wyrażenia regularne w statycznej pamięci podręcznej w pamięci. Gdy wywołujesz to samo wyrażenie regularne wiele razy w różnych częściach kodu, jest ono kompilowane tylko raz. Cache oszczędza wydajność, a także jest całkowicie niewidoczny dla użytkownika, więc takie użycie można uznać za uzasadnione. + + +Podsumowanie .[#toc-summary] +---------------------------- + +Pokazaliśmy, dlaczego to ma sens + +1) Usuń z kodu wszystkie zmienne statyczne +2) Zadeklarować zależności +3) I używać zastrzyku zależności + +Rozważając projekt kodu, pamiętaj, że każdy `static $foo` reprezentuje problem. Aby twój kod był środowiskiem respektującym DI, konieczne jest całkowite wyeliminowanie stanu globalnego i zastąpienie go zastrzykiem zależności. + +Podczas tego procesu może się okazać, że musisz podzielić klasę, ponieważ ma ona więcej niż jedną odpowiedzialność. Nie przejmuj się tym; dąż do zasady jednej odpowiedzialności. + +*Chciałbym podziękować Miško Hevery'emu, którego artykuły takie jak [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] stanowią podstawę tego rozdziału.* diff --git a/dependency-injection/pl/introduction.texy b/dependency-injection/pl/introduction.texy index 875ab2f85f..8431bfb782 100644 --- a/dependency-injection/pl/introduction.texy +++ b/dependency-injection/pl/introduction.texy @@ -2,17 +2,17 @@ Co to jest Dependency Injection? ******************************** .[perex] -Ten rozdział wprowadza Cię w podstawowe praktyki programistyczne, których powinieneś przestrzegać podczas pisania jakiejkolwiek aplikacji. Są to podstawy wymagane do pisania czystego, zrozumiałego i możliwego do utrzymania kodu. +Ten rozdział zapozna Cię z podstawowymi praktykami programistycznymi, których powinieneś przestrzegać podczas pisania dowolnej aplikacji. Są to podstawy potrzebne do pisania czystego, zrozumiałego i możliwego do utrzymania kodu. -Jeśli poznasz te zasady i będziesz ich przestrzegał, Nette będzie cię wspierać na każdym kroku. Będzie obsługiwał rutynowe zadania za Ciebie i sprawi, że będziesz tak wygodny, jak to tylko możliwe, abyś mógł skupić się na samej logice. +Jeśli nauczysz się i będziesz przestrzegać tych zasad, Nette będzie Cię wspierać na każdym kroku. Zajmie się rutynowymi zadaniami za Ciebie i zapewni maksymalny komfort, dzięki czemu będziesz mógł skupić się na samej logice. -Zasady, które tu pokażemy, są dość proste. Nie masz się o co martwić. +Zasady, które tu pokażemy, są dość proste. Nie musisz się o nic martwić. Pamiętasz swój pierwszy program? .[#toc-remember-your-first-program] -------------------------------------------------------------------- -Nie mamy pojęcia w jakim języku go napisałeś, ale jeśli był to PHP, to prawdopodobnie wyglądałby coś takiego: +Nie wiemy, w jakim języku go napisałeś, ale jeśli był to PHP, mógł wyglądać coś takiego: ```php function suma(float $a, float $b): float @@ -25,31 +25,31 @@ echo suma(23, 1); // wykazy 24 Kilka banalnych linii kodu, ale tak wiele kluczowych pojęć w nich ukrytych. To, że istnieją zmienne. Że kod jest rozbity na mniejsze jednostki, którymi są na przykład funkcje. Że przekazujemy im argumenty wejściowe, a one zwracają wyniki. Brakuje tylko warunków i pętli. -To, że przekazujemy dane wejściowe do funkcji i ona zwraca wynik, jest doskonale zrozumiałym pojęciem, które jest wykorzystywane w innych dziedzinach, np. w matematyce. +Fakt, że funkcja przyjmuje dane wejściowe i zwraca wynik, jest całkowicie zrozumiałym pojęciem, które jest również wykorzystywane w innych dziedzinach, takich jak matematyka. -Funkcja ma sygnaturę, która składa się z jej nazwy, listy parametrów i ich typów, wreszcie typu zwracanej wartości. Jako użytkowników interesuje nas sygnatura; zwykle nie musimy wiedzieć nic o wewnętrznej implementacji. +Funkcja ma swoją sygnaturę, która składa się z jej nazwy, listy parametrów i ich typów, wreszcie typu wartości zwracanej. Jako użytkowników interesuje nas sygnatura, a o wewnętrznej implementacji zwykle nie musimy nic wiedzieć. -Teraz wyobraź sobie, że sygnatura funkcji wygląda tak: +Teraz wyobraź sobie, że sygnatura funkcji wyglądała tak: ```php function suma(float $x): float ``` -Dodawanie z jednym parametrem? To dziwne... A może tak? +Dodatek z jednym parametrem? To dziwne... A co z tym? ```php function suma(): float ``` -To jest naprawdę dziwne, prawda? Jak myślisz, w jaki sposób ta funkcja jest wykorzystywana? +Teraz to jest naprawdę dziwne, prawda? Jak ta funkcja jest używana? ```php echo suma(); // co wypisuje? ``` -Patrząc na taki kod, jesteśmy zdezorientowani. Nie tylko początkujący nie zrozumie go, nawet wprawny programista nie zrozumie takiego kodu. +Patrząc na taki kod, bylibyśmy zdezorientowani. Nie tylko początkujący nie zrozumiałby go, ale nawet doświadczony programista nie zrozumiałby takiego kodu. -Zastanawiasz się, jak taka funkcja wyglądałaby w rzeczywistości w środku? Skąd wzięłaby dodawarki? Zapewne zdobywałaby je *jakoś* samodzielnie, np. w ten sposób: +Zastanawiasz się jak właściwie wyglądałaby taka funkcja w środku? Skąd brałaby sumy? Prawdopodobnie *w jakiś sposób* sama by je uzyskała, być może w taki sposób: ```php function suma(): float @@ -66,13 +66,13 @@ Okazuje się, że w ciele funkcji znajdują się ukryte wiązania do innych funk Nie tędy droga! .[#toc-not-this-way] ------------------------------------ -Projekt, który właśnie nam pokazano, jest esencją wielu negatywnych cech: +Projekt, który właśnie pokazaliśmy, jest esencją wielu negatywnych cech: -- sygnatura funkcji udawała, że nie potrzebuje dopełnień, co nas zmyliło +- sygnatura funkcji udawała, że nie potrzebuje sumatorów, co nas dezorientowało - nie mamy pojęcia, jak sprawić, żeby funkcja obliczała z dwoma innymi liczbami -- musieliśmy zajrzeć do kodu, żeby zobaczyć, skąd bierze addytywność -- odkryliśmy ukryte wiązania -- aby w pełni zrozumieć, musimy zbadać również te wiązania +- musieliśmy zajrzeć do kodu, żeby dowiedzieć się, skąd wzięły się sumy +- znaleźliśmy ukryte zależności +- pełne zrozumienie wymaga zbadania także tych zależności A czy zadaniem funkcji dodawania jest w ogóle pozyskiwanie wejść? Oczywiście, że nie. Jej zadaniem jest tylko dodawanie. @@ -93,20 +93,20 @@ Zasada #1: Niech ci to zostanie przekazane .[#toc-rule-1-let-it-be-passed-to-you Najważniejszą zasadą jest: **wszystkie dane, których potrzebują funkcje lub klasy, muszą być do nich przekazane**. -Zamiast wymyślać ukryte mechanizmy, dzięki którym w jakiś sposób sami się do nich dostaną, po prostu przekaż im parametry. Zaoszczędzisz czas potrzebny na wymyślanie ukrytego sposobu, który na pewno nie poprawi twojego kodu. +Zamiast wymyślać ukryte sposoby, aby mogli sami uzyskać dostęp do danych, po prostu przekaż parametry. Zaoszczędzisz czas, który zostałby poświęcony na wymyślanie ukrytych ścieżek, które z pewnością nie poprawią twojego kodu. -Jeśli będziesz przestrzegał tej zasady zawsze i wszędzie, jesteś na dobrej drodze do kodu bez ukrytych wiązań. W kierunku kodu, który jest zrozumiały nie tylko dla autora, ale także dla każdego, kto go potem czyta. Gdzie wszystko jest zrozumiałe z sygnatur funkcji i klas i nie ma potrzeby szukania ukrytych sekretów w implementacji. +Jeśli zawsze i wszędzie będziesz przestrzegał tej zasady, jesteś na dobrej drodze do kodu bez ukrytych zależności. Do kodu, który jest zrozumiały nie tylko dla autora, ale także dla każdego, kto go potem przeczyta. Gdzie wszystko jest zrozumiałe z sygnatur funkcji i klas, i nie ma potrzeby szukania ukrytych sekretów w implementacji. -Ta technika nazywa się fachowo **wstrzykiwaniem zależności**. A dane nazywa się **zależnościami**, ale to zwykłe przekazywanie parametrów, nic więcej. +Ta technika nazywa się fachowo **dependency injection**. A te dane nazywane są **zależnościami**. To tylko zwykłe przekazywanie parametrów, nic więcej. .[note] -Proszę nie mylić wtrysku zależności, który jest wzorcem projektowym, z "kontenerem wtrysku zależności", który jest narzędziem, czymś zupełnie innym. Kontenery omówimy później. +Proszę nie mylić wtrysku zależności, który jest wzorcem projektowym, z "kontenerem wtrysku zależności", który jest narzędziem, czymś diametralnie różnym. Kontenerami zajmiemy się później. Od funkcji do klas .[#toc-from-functions-to-classes] ---------------------------------------------------- -A jak klasy mają się do tego? Klasa jest bardziej złożonym bytem niż prosta funkcja, ale zasada #1 ma zastosowanie również tutaj. Jest po prostu [więcej sposobów na przekazywanie argumentów |passing-dependencies]. Na przykład, dość podobny do przypadku funkcji: +A jak klasy są powiązane? Klasa jest bardziej złożoną jednostką niż prosta funkcja, ale zasada #1 ma tutaj również całkowite zastosowanie. Jest po prostu [więcej sposobów na przekazywanie argumentów |passing-dependencies]. Na przykład, dość podobny do przypadku funkcji: ```php class Matematika @@ -121,7 +121,7 @@ $math = new Matematika; echo $math->suma(23, 1); // 24 ``` -Lub poprzez użycie innych metod, lub bezpośrednio przez konstruktora: +Lub poprzez inne metody, lub bezpośrednio poprzez konstruktor: ```php class Suma @@ -149,9 +149,9 @@ Oba przykłady są całkowicie zgodne z zastrzykiem zależności. Przykłady z życia wzięte .[#toc-real-life-examples] --------------------------------------------------- -W prawdziwym świecie nie będziesz pisał klas do dodawania liczb. Przejdźmy więc do przykładów z prawdziwego świata. +W prawdziwym świecie nie będziesz pisał klas do dodawania liczb. Przejdźmy więc do praktycznych przykładów. -Miejmy klasę `Article` reprezentującą artykuł na blogu: +Miejmy klasę `Article` reprezentującą wpis na blogu: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Metoda `save()` zapisuje artykuł w tabeli bazy danych. Wdrożenie jej przy użyciu [Nette Database |database:] byłoby bułką z masłem, gdyby nie jeden problem: skąd `Article` ma wziąć połączenie z bazą danych, czyli obiekt klasy `Nette\Database\Connection`? +Metoda `save()` zapisze artykuł do tabeli w bazie danych. Wdrożenie jej przy użyciu [Nette Database |database:] to bułka z masłem, gdyby nie jeden problem: skąd `Article` ma wziąć połączenie z bazą danych, czyli obiekt klasy `Nette\Database\Connection`? -Wydaje się, że mamy wiele możliwości. Może wziąć je skądś w zmiennej statycznej. Albo odziedziczyć po klasie, która zapewni połączenie z bazą danych. Albo skorzystać z [singletonu |global-state#Singleton]. Albo tzw. fasady, które są stosowane w Laravelu: +Wydaje się, że mamy wiele możliwości. Może wziąć je gdzieś ze zmiennej statycznej. Albo dziedziczyć po klasie, która zapewnia połączenie z bazą danych. Albo skorzystać z [singletonu |global-state#Singleton]. Albo wykorzystać tzw. fasady, które są stosowane w Laravelu: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ class Article A może jednak? -Przypomnijmy sobie [zasadę #1: Niech ci to zostanie przekazane |#rule #1: Let It Be Passed to You]: wszystkie zależności, których potrzebuje klasa, muszą być do niej przekazane. Bo jeśli tego nie zrobimy i złamiemy tę zasadę, to rozpoczęliśmy drogę do brudnego kodu pełnego ukrytych wiązań, niezrozumiałości, a rezultatem będzie aplikacja, która jest bólem w utrzymaniu i rozwoju. +Przypomnijmy sobie [regułę #1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: wszystkie zależności, których potrzebuje klasa, muszą być do niej przekazane. Bo jeśli złamiemy tę regułę, to wkroczyliśmy na drogę do brudnego kodu pełnego ukrytych zależności, niezrozumiałości, a efektem będzie aplikacja, której utrzymanie i rozwój będą bolesne. -Użytkownik klasy `Article` nie ma pojęcia, gdzie metoda `save()` przechowuje artykuł. W tabeli bazy danych? W której, produkcyjnej czy deweloperskiej? I jak można to zmienić? +Użytkownik klasy `Article` nie ma pojęcia, gdzie metoda `save()` przechowuje artykuł. W tabeli bazy danych? W której, produkcyjnej czy testowej? I jak można ją zmienić? -Użytkownik musi przyjrzeć się temu, jak zaimplementowana jest metoda `save()`, aby znaleźć zastosowanie metody `DB::insert()`. Musi więc szukać dalej, aby dowiedzieć się, jak ta metoda pozyskuje połączenie z bazą danych. A ukryte wiązania mogą tworzyć dość długi łańcuch. +Użytkownik musi przyjrzeć się, jak zaimplementowana jest metoda `save()`, i znajduje zastosowanie metody `DB::insert()`. Musi więc szukać dalej, aby dowiedzieć się, jak ta metoda uzyskuje połączenie z bazą danych. A ukryte zależności mogą tworzyć dość długi łańcuch. -Ukryte wiązania, fasady Laravel czy zmienne statyczne nigdy nie występują w czystym, dobrze zaprojektowanym kodzie. W czystym i dobrze zaprojektowanym kodzie przekazywane są argumenty: +W czystym i dobrze zaprojektowanym kodzie nigdy nie ma ukrytych zależności, fasad Laravel czy zmiennych statycznych. W czystym i dobrze zaprojektowanym kodzie przekazywane są argumenty: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Jeszcze bardziej praktyczne, jak zobaczymy dalej, jest użycie konstruktora: +Jeszcze bardziej praktyczne podejście, jak zobaczymy później, będzie poprzez konstruktor: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Jeśli jesteś doświadczonym programistą, być może myślisz, że `Article` w ogóle nie powinien mieć metody `save()`, powinien być czystym komponentem danych, a o przechowywanie danych powinno zadbać osobne repozytorium. To ma sens. Ale to zabrałoby nas daleko poza temat, który jest zastrzykiem zależności i próbą podania prostych przykładów. +Jeśli jesteś doświadczonym programistą, możesz pomyśleć, że `Article` nie powinien w ogóle mieć metody `save()`; powinien reprezentować składnik czysto danych, a oddzielne repozytorium powinno zająć się zapisywaniem. To ma sens. Ale to zabrałoby nas daleko poza zakres tego tematu, który jest zastrzykiem zależności, a także wysiłek, aby zapewnić proste przykłady. -Jeśli masz zamiar napisać klasę, która wymaga bazy danych do działania, na przykład, nie wymyślaj, skąd ją wziąć, ale mieć ją przekazaną do ciebie. Być może jako parametr do konstruktora lub innej metody. Deklaruj zależności. Eksponuj je w API swojej klasy. Otrzymasz zrozumiały i przewidywalny kod. +Jeśli piszesz klasę, która do swojego działania wymaga np. bazy danych, to nie wymyślaj skąd ją wziąć, tylko zleć jej przekazanie. Albo jako parametr konstruktora, albo innej metody. Przyznaj się do zależności. Przyznaj się do nich w API swojej klasy. Otrzymasz zrozumiały i przewidywalny kod. -Co powiesz na tę klasę, która rejestruje komunikaty o błędach: +A co z tą klasą, która loguje komunikaty o błędach: ```php class Logger @@ -266,9 +266,9 @@ Jak myślicie, czy zastosowaliśmy się do [zasady nr 1: Niech ci to zostanie pr Nie. -Kluczowa informacja, katalog pliku dziennika, jest *pozyskiwana* przez klasę ze stałej. +Kluczowa informacja, czyli katalog z plikiem dziennika, jest *pozyskiwana* przez samą klasę ze stałej. -Zobacz przykładowe użycie: +Spójrz na przykład użycia: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Bez znajomości implementacji, czy mógłbyś odpowiedzieć na pytanie, gdzie zapisywane są wiadomości? Czy sugerowałoby ci to, że istnienie stałej LOG_DIR jest konieczne, aby działało? I czy byłbyś w stanie stworzyć drugą instancję, która zapisuje do innej lokalizacji? Z pewnością nie. +Czy nie znając implementacji, mógłbyś odpowiedzieć na pytanie, gdzie zapisywane są wiadomości? Czy domyśliłbyś się, że istnienie stałej `LOG_DIR` jest niezbędne do jej funkcjonowania? A czy mógłbyś stworzyć drugą instancję, która zapisywałaby w innym miejscu? Z pewnością nie. Naprawmy więc klasę: @@ -295,7 +295,7 @@ class Logger } ``` -Klasa jest teraz znacznie bardziej przejrzysta, bardziej konfigurowalna, a zatem bardziej użyteczna. +Klasa jest teraz znacznie bardziej zrozumiała, konfigurowalna, a zatem bardziej użyteczna. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Ale nie obchodzi mnie to! .[#toc-but-i-don-t-care] -------------------------------------------------- -* "Kiedy tworzę obiekt Article i wywołuję save(), nie chcę zajmować się bazą danych, chcę tylko, aby został zapisany do tej, którą ustawiłem w konfiguracji. "* +*"Kiedy tworzę obiekt Article i wywołuję save(), nie chcę mieć do czynienia z bazą danych; chcę tylko, aby został zapisany w tej, którą ustawiłem w konfiguracji."* -*"Kiedy używam Loggera, chcę tylko, aby wiadomość została zapisana i nie chcę zajmować się tym, gdzie. Pozwól, aby użyto ustawień globalnych. "* +*"Kiedy używam Loggera, chcę tylko, aby wiadomość została zapisana i nie chcę zajmować się tym, gdzie. Pozwól, aby użyto ustawień globalnych."* -To są poprawne komentarze. +To są ważne punkty. -Jako przykład weźmy klasę, która wysyła biuletyny i rejestruje, jak to poszło: +Jako przykład spójrzmy na klasę, która wysyła biuletyny i rejestruje, jak to się stało: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -Rozszerzony `Logger`, który nie używa już stałej `LOG_DIR`, wymaga ścieżki pliku w konstruktorze. Jak to rozwiązać? Klasa `NewsletterDistributor` nie dba o to, gdzie są zapisane wiadomości, chce je tylko zapisać. +Ulepszony `Logger`, który nie używa już stałej `LOG_DIR`, wymaga określenia ścieżki pliku w konstruktorze. Jak to rozwiązać? Klasa `NewsletterDistributor` nie dba o to, gdzie zapisywane są wiadomości; chce je po prostu zapisać. -Rozwiązaniem jest ponownie [zasada #1: Niech ci to zostanie przekazane |#rule #1: Let It Be Passed to You]: przekaż wszystkie dane, których potrzebuje klasa. +Rozwiązaniem jest ponownie [zasada #1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: przekaż wszystkie dane, których potrzebuje klasa. -Przekazujemy więc ścieżkę do dziennika do konstruktora, którego następnie używamy do stworzenia obiektu `Logger`? +Czy oznacza to więc, że przekazujemy ścieżkę do dziennika poprzez konstruktor, którego następnie używamy podczas tworzenia obiektu `Logger`? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Nie tak!!! Ponieważ ścieżka **nie** należy do danych, których potrzebuje klasa `NewsletterDistributor`; potrzebuje ona `Logger`. Klasa potrzebuje samego loggera. I właśnie to przekażemy dalej: +Nie, nie w ten sposób! Ścieżka nie należy do danych, których potrzebuje klasa `NewsletterDistributor`; w rzeczywistości potrzebuje jej `Logger`. Czy widzisz różnicę? Klasa `NewsletterDistributor` potrzebuje samego loggera. Więc to jest to, co przekażemy: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Teraz z sygnatur klasy `NewsletterDistributor` jasno wynika, że logowanie jest częścią jej funkcjonalności. A zadanie zastąpienia loggera innym, być może w celach testowych, jest dość trywialne. -Ponadto, jeśli konstruktor klasy `Logger` zostanie zmieniony, nie będzie to miało żadnego wpływu na naszą klasę. +Teraz z podpisów klasy `NewsletterDistributor` jasno wynika, że logowanie jest również częścią jej funkcjonalności. A zadanie zamiany loggera na inny, być może w celu przetestowania, jest zupełnie trywialne. +Co więcej, jeśli zmieni się konstruktor klasy `Logger`, nie będzie to miało wpływu na naszą klasę. -Zasada #2: Bierz to, co jest twoje .[#toc-rule-2-take-what-is-yours] --------------------------------------------------------------------- +Zasada #2: Bierz co twoje .[#toc-rule-2-take-what-s-yours] +---------------------------------------------------------- -Nie daj się zwieść i nie pozwól, aby parametry twoich zależności zostały przekazane do ciebie. Przekazuj zależności bezpośrednio. +Nie daj się zwieść i nie pozwól sobie na przechodzenie przez zależności swoich zależności. Przekazuj tylko swoje własne zależności. -Dzięki temu kod korzystający z innych obiektów będzie całkowicie niezależny od zmian w ich konstruktorach. Jego interfejs API będzie prawdziwszy. A co najważniejsze, trywialnie będzie zamienić te zależności na inne. +Dzięki temu kod korzystający z innych obiektów będzie całkowicie niezależny od zmian w ich konstruktorach. Jego API będzie bardziej zgodne z prawdą. A przede wszystkim trywialne będzie zastąpienie tych zależności innymi. -Nowy członek rodziny .[#toc-a-new-member-of-the-family] -------------------------------------------------------- +Nowy członek rodziny .[#toc-new-family-member] +---------------------------------------------- -Zespół programistów postanowił stworzyć drugi logger, który zapisuje do bazy danych. Stworzyliśmy więc klasę `DatabaseLogger`. Mamy więc dwie klasy, `Logger` i `DatabaseLogger`, jedna zapisuje do pliku, druga do bazy danych ... nie uważasz, że jest coś dziwnego w tej nazwie? -Czy nie lepiej byłoby zmienić nazwę `Logger` na `FileLogger`? Pewnie, że tak. +Zespół programistów postanowił stworzyć drugi logger, który zapisuje do bazy danych. Więc tworzymy klasę `DatabaseLogger`. Mamy więc dwie klasy, `Logger` i `DatabaseLogger`, jedna zapisuje do pliku, druga do bazy danych ... czy to nazewnictwo nie wydaje Ci się dziwne? +Czy nie lepiej byłoby zmienić nazwę `Logger` na `FileLogger`? Zdecydowanie tak. -Ale zróbmy to mądrze. Stworzymy interfejs pod oryginalną nazwą: +Ale zróbmy to sprytnie. Tworzymy interfejs pod oryginalną nazwą: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -, który oba loggery będą implementować: +...które oba rejestratory zaimplementują: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -I w ten sposób nie trzeba będzie nic zmieniać w pozostałej części kodu, w którym logger jest używany. Na przykład konstruktor klasy `NewsletterDistributor` nadal będzie się zadowalał wymaganiem `Logger` jako parametru. A od nas będzie zależało, którą instancję mu przekażemy. +I z tego powodu nie będzie trzeba nic zmieniać w pozostałej części kodu, w której używany jest logger. Na przykład konstruktor klasy `NewsletterDistributor` nadal będzie zadowalał się wymaganiem `Logger` jako parametru. I to od nas będzie zależało, którą instancję przekażemy. -**To właśnie dlatego nigdy nie nadajemy nazwom interfejsów przyrostka `Interface` lub przedrostka `I`.** W przeciwnym razie niemożliwe byłoby tak ładne opracowanie kodu. +**To właśnie dlatego nigdy nie dodajemy do nazw interfejsów przyrostka `Interface` ani przedrostka `I`** Inaczej nie dałoby się tak ładnie rozwinąć kodu. Houston, mamy problem .[#toc-houston-we-have-a-problem] ------------------------------------------------------- -O ile w całej aplikacji możemy zadowolić się pojedynczą instancją loggera, czy to plikowego, czy bazodanowego, i po prostu przekazać ją wszędzie tam, gdzie coś jest logowane, to w przypadku klasy `Article` jest zupełnie inaczej. W rzeczywistości tworzymy jej instancje w zależności od potrzeb, być może wielokrotnie. Jak poradzić sobie z wiązaniem z bazą danych w jej konstruktorze? +O ile możemy sobie poradzić z pojedynczą instancją loggera, czy to plikowego, czy bazodanowego, w całej aplikacji i po prostu przekazać ją wszędzie tam, gdzie coś ma być rejestrowane, to w przypadku klasy `Article` jest zupełnie inaczej. Jej instancje tworzymy w miarę potrzeb, nawet wielokrotnie. Jak poradzić sobie z zależnością od bazy danych w jej konstruktorze? -Jako przykład może posłużyć kontroler, który po przesłaniu formularza powinien zapisać artykuł do bazy danych: +Przykładem może być kontroler, który po przesłaniu formularza powinien zapisać artykuł do bazy danych: ```php class EditController extends Controller @@ -437,9 +437,9 @@ class EditController extends Controller } ``` -Możliwe rozwiązanie jest bezpośrednio oferowane: mieć obiekt bazy danych przekazywany przez konstruktora do `EditController` i używać `$article = new Article($this->db)`. +Możliwe rozwiązanie jest oczywiste: przekazać obiekt bazy danych do konstruktora `EditController` i użyć `$article = new Article($this->db)`. -Podobnie jak w poprzednim przypadku z `Logger` i ścieżką do pliku, nie jest to poprawne podejście. Baza danych nie jest zależna od `EditController`, ale od `Article`. Zatem przekazanie bazy danych jest sprzeczne z [zasadą #2: bierz to, co twoje |#rule #2: take what is yours]. Kiedy konstruktor klasy `Article` zostanie zmieniony (zostanie dodany nowy parametr), kod we wszystkich miejscach, w których tworzone są instancje, również będzie musiał zostać zmodyfikowany. Ufff. +Podobnie jak w poprzednim przypadku z `Logger` i ścieżką do pliku, nie jest to właściwe podejście. Baza danych nie jest zależna od `EditController`, ale od `Article`. Przekazanie bazy danych jest sprzeczne z [zasadą #2: bierz to, co twoje |#rule #2: take what's yours]. Jeśli konstruktor klasy `Article` ulegnie zmianie (zostanie dodany nowy parametr), będziesz musiał zmodyfikować kod wszędzie tam, gdzie tworzone są instancje. Ufff. Houston, co proponujesz? @@ -447,11 +447,11 @@ Houston, co proponujesz? Zasada #3: Niech fabryka się tym zajmie .[#toc-rule-3-let-the-factory-handle-it] -------------------------------------------------------------------------------- -Usuwając ukryte wiązania i przekazując wszystkie zależności jako argumenty, otrzymujemy bardziej konfigurowalne i elastyczne klasy. I dlatego potrzebujemy czegoś innego do tworzenia i konfigurowania tych bardziej elastycznych klas. Nazwiemy to fabrykami. +Eliminując ukryte zależności i przekazując wszystkie zależności jako argumenty, zyskaliśmy bardziej konfigurowalne i elastyczne klasy. I dlatego potrzebujemy czegoś innego, co stworzy i skonfiguruje dla nas te bardziej elastyczne klasy. Nazwiemy to fabrykami. Zasadą jest: jeśli klasa ma zależności, pozostaw tworzenie ich instancji fabryce. -Fabryki są inteligentnym zamiennikiem dla operatora `new` w świecie dependency injection. +Fabryki są mądrzejszym zamiennikiem dla operatora `new` w świecie zastrzyku zależności. .[note] Proszę nie mylić z wzorcem projektowym *factory method*, który opisuje specyficzny sposób używania fabryk i nie jest związany z tym tematem. @@ -460,7 +460,7 @@ Proszę nie mylić z wzorcem projektowym *factory method*, który opisuje specyf Fabryka .[#toc-factory] ----------------------- -Fabryka to metoda lub klasa, która produkuje i konfiguruje obiekty. Klasę produkującą `Article` nazywamy `ArticleFactory` i może ona wyglądać tak: +Fabryka to metoda lub klasa, która tworzy i konfiguruje obiekty. Klasę produkującą `Article` nazwiemy jako `ArticleFactory`, a mogłaby ona wyglądać tak: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Jej użycie w kontrolerze byłoby następujące: +Jego wykorzystanie w sterowniku będzie wyglądało następująco: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -W tym momencie, gdy zmieni się sygnatura konstruktora klasy `Article`, jedyną częścią kodu, która musi zareagować, jest sama fabryka `ArticleFactory`. Każdy inny kod, który pracuje z obiektami `Article`, takimi jak `EditController`, nie zostanie dotknięty. +W tym momencie, jeśli zmieni się podpis konstruktora klasy `Article`, jedyną częścią kodu, która musi zareagować, jest sam `ArticleFactory`. Wszystkie inne kody pracujące z obiektami `Article`, takie jak `EditController`, nie zostaną dotknięte. -Być może stukasz się teraz w czoło, zastanawiając się, czy w ogóle sobie pomogliśmy. Ilość kodu wzrosła i całość zaczyna wyglądać podejrzanie skomplikowanie. +Możesz się zastanawiać, czy faktycznie poprawiliśmy sytuację. Ilość kodu wzrosła, a wszystko zaczyna wyglądać podejrzanie skomplikowanie. -Nie martw się, wkrótce dotrzemy do kontenera Nette DI. A ma on w rękawie kilka asów, które sprawią, że budowanie aplikacji z wykorzystaniem dependency injection będzie niezwykle proste. Na przykład zamiast klasy `ArticleFactory` wystarczy [napisać prosty interfejs |factory]: +Nie martw się, wkrótce dotrzemy do kontenera Nette DI. A ma on w rękawie kilka sztuczek, które znacznie ułatwią budowanie aplikacji wykorzystujących wstrzykiwanie zależności. Na przykład zamiast klasy `ArticleFactory` wystarczy [napisać prosty interfejs |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Ale wyprzedzamy się, trzymaj się :-) +Ale wyprzedzamy się; prosimy o cierpliwość :-) Podsumowanie .[#toc-summary] ---------------------------- -Na początku tego rozdziału obiecaliśmy, że pokażemy Ci sposób na projektowanie czystego kodu. Wystarczy nadać klasom. +Na początku tego rozdziału obiecaliśmy, że pokażemy Ci proces projektowania czystego kodu. Wystarczy, że klasy: -- [zależności, których potrzebują |#Rule #1: Let It Be Passed to You] -- [a nie to, czego bezpośrednio nie potrzebują |#Rule #2: Take What Is Yours] +- [przekazywały zależności, których potrzebują |#Rule #1: Let It Be Passed to You] +- [odwrotnie, nie przekazywały tego, czego bezpośrednio nie potrzebują |#Rule #2: Take What's Yours] - [oraz że obiekty z zależnościami najlepiej tworzyć w fabrykach |#Rule #3: Let the Factory Handle it] -Na pierwszy rzut oka może się tak nie wydawać, ale te trzy zasady mają daleko idące implikacje. Prowadzą do radykalnie innego spojrzenia na projektowanie kodu. Czy to się opłaca? Programiści, którzy wyrzucili stare nawyki i zaczęli konsekwentnie stosować dependency injection, uważają to za przełomowy moment w swoim życiu zawodowym. Otworzył on przed nimi świat przejrzystych i trwałych aplikacji. +Na pierwszy rzut oka te trzy zasady mogą nie wydawać się mieć daleko idących konsekwencji, ale prowadzą do radykalnie innego spojrzenia na projektowanie kodu. Czy to się opłaca? Deweloperzy, którzy porzucili stare nawyki i zaczęli konsekwentnie używać dependency injection, uważają ten krok za kluczowy moment w swoim życiu zawodowym. Otworzył on przed nimi świat przejrzystych i możliwych do utrzymania aplikacji. -Ale co jeśli kod nie korzysta konsekwentnie z dependency injection? Co jeśli jest zbudowany na statycznych metodach lub singletonach? Czy przynosi to jakieś problemy? [Tak jest, i to bardzo znaczące |global-state]. +Ale co jeśli kod nie korzysta konsekwentnie z dependency injection? Co jeśli opiera się na metodach statycznych lub singletonach? Czy to powoduje jakieś problemy? [Tak, powoduje, i to bardzo podstawowe |global-state]. diff --git a/dependency-injection/pl/passing-dependencies.texy b/dependency-injection/pl/passing-dependencies.texy index 99b2c0d27c..d4e4656bd3 100644 --- a/dependency-injection/pl/passing-dependencies.texy +++ b/dependency-injection/pl/passing-dependencies.texy @@ -12,7 +12,7 @@ Argumenty, lub "zależności" w terminologii DI, mogą być przekazywane do klas </div> -Pierwsze trzy metody obowiązują ogólnie we wszystkich językach obiektowych, czwarta jest specyficzna dla prezenterów Nette, dlatego omówiono ją w [osobnym rozdziale |best-practices:inject-method-attribute]. Teraz przyjrzymy się bliżej każdej z tych opcji i pokażemy je na konkretnych przykładach. +Zilustrujemy teraz różne warianty na konkretnych przykładach. Przekazywanie przez konstruktora .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Przekazywanie przez konstruktora .[#toc-constructor-injection] Zależności są przekazywane jako argumenty do konstruktora w czasie tworzenia obiektu: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Ta forma jest odpowiednia dla obowiązkowych zależności, które klasa bezwzględnie potrzebuje do funkcjonowania, ponieważ bez nich nie można utworzyć instancji. @@ -40,10 +40,10 @@ Od PHP 8.0 możemy używać krótszej formy notacji ([constructor property promo ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ Od PHP 8.1 zmienna może być oznaczona flagą `readonly`, która deklaruje, że ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService Kontener DI przekazuje zależność do konstruktora automatycznie przez [autowiring |autowiring]. Argumenty, które nie mogą być przekazane [w |services#Arguments] ten sposób (np. ciągi znaków, liczby, booleans) [są zapisywane w konfiguracji |services#Arguments]. +Piekło konstruktorów .[#toc-constructor-hell] +--------------------------------------------- + +Termin *constructor hell* odnosi się do sytuacji, w której dziecko dziedziczy po klasie rodzica, której konstruktor wymaga zależności, a dziecko również wymaga zależności. Musi ono również przejąć i przekazać zależności rodzica: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Problem pojawia się, gdy chcemy zmienić konstruktor klasy `BaseClass`, na przykład gdy dodamy nową zależność. Wtedy musimy zmodyfikować również wszystkie konstruktory dzieci. Co czyni taką modyfikację piekłem. + +Jak temu zapobiec? Rozwiązaniem jest **priorytet kompozycji nad dziedziczeniem**. + +Zaprojektujmy więc kod inaczej. Będziemy unikać klas abstrakcyjnych `Base*`. Zamiast `MyClass` uzyskać pewną funkcjonalność poprzez dziedziczenie z `BaseClass`, będzie ona miała tę funkcjonalność przekazaną jako zależność: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Przekazywanie przez setera .[#toc-setter-injection] =================================================== -Zależności są przekazywane przez wywołanie metody, która przechowuje je w prywatnej zmiennej. Zwykła konwencja nazewnicza dla tych metod ma postać `set*()`, dlatego nazywa się je seterami. +Zależności są przekazywane przez wywołanie metody, która przechowuje je w prywatnej właściwości. Zwykła konwencja nazewnicza dla tych metod ma postać `set*()`, dlatego są one nazywane setterami, ale oczywiście mogą być nazywane inaczej. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Ta metoda jest przydatna dla opcjonalnych zależności, które nie są konieczne dla funkcji klasy, ponieważ nie jest gwarantowane, że obiekt faktycznie otrzyma zależność (tj. Że użytkownik wywoła metodę). @@ -90,16 +150,16 @@ Ta metoda jest przydatna dla opcjonalnych zależności, które nie są konieczne Jednocześnie metoda ta pozwala na wielokrotne wywoływanie setera w celu zmiany zależności. Jeśli nie jest to pożądane, dodaj do metody kontrolę lub od PHP 8.1 oznacz zmienną `$cache` flagą `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ Wywołanie setera jest zdefiniowane w konfiguracji kontenera DI w [sekcji |servi ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Poprzez ustawienie zmiennej .[#toc-property-injection] Zależności są przekazywane przez zapis bezpośrednio do zmiennej członkowskiej: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Metoda ta jest uważana za niewłaściwą, ponieważ zmienna członkowska musi być zadeklarowana jako `public`. Tym samym nie mamy kontroli nad tym, czy przekazana zależność jest rzeczywiście danego typu (tak było przed PHP 7.4) i tracimy możliwość reagowania na nowo przypisaną zależność własnym kodem, na przykład w celu zapobieżenia późniejszej zmianie. W tym samym czasie zmienna staje się częścią publicznego interfejsu klasy, co może nie być pożądane. @@ -137,12 +197,18 @@ Ustawienia zmiennej definiujemy w konfiguracji kontenera DI w [sekcji setup |ser ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Inject .[#toc-inject] +===================== + +O ile poprzednie trzy metody obowiązują ogólnie we wszystkich językach obiektowych, o tyle wstrzykiwanie za pomocą metody, adnotacji lub atrybutu *inject* jest specyficzne dla prezenterów Nette. Zostały one omówione w [osobnym rozdziale |best-practices:inject-method-attribute]. + + Jaką metodę wybrać? .[#toc-which-way-to-choose] =============================================== diff --git a/dependency-injection/pl/services.texy b/dependency-injection/pl/services.texy index d3efedbba9..d40aece53e 100644 --- a/dependency-injection/pl/services.texy +++ b/dependency-injection/pl/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Tryb wtrysku .[#toc-inject-mode] ================================ -Flaga `inject: true` służy do aktywowania przekazywania zależności poprzez zmienne publiczne z adnotacją [inject |best-practices:inject-method-attribute#Inject-Annotations] i metodami [inject*() |best-practices:inject-method-attribute#inject-Methods]. +Flaga `inject: true` służy do aktywowania przekazywania zależności poprzez zmienne publiczne z adnotacją [inject |best-practices:inject-method-attribute#Inject-Attribute] i metodami [inject*() |best-practices:inject-method-attribute#inject-Methods]. ```neon services: diff --git a/dependency-injection/pt/@home.texy b/dependency-injection/pt/@home.texy index b4a81aaed2..4389971f4a 100644 --- a/dependency-injection/pt/@home.texy +++ b/dependency-injection/pt/@home.texy @@ -5,8 +5,10 @@ Injeção de dependência A injeção de dependência é um padrão de projeto que mudará fundamentalmente a maneira como você vê o código e o desenvolvimento. Ela abre o caminho para um mundo de aplicações sustentáveis e de design limpo. - [O que é Injeção de Dependência? |introduction] -- [O que é DI Container? |container] +- [Estado global e Singletons |global-state] - [Dependências de passagem |passing-dependencies] +- [O que é DI Container? |container] +- [Perguntas mais freqüentes |faq] Nette DI diff --git a/dependency-injection/pt/@left-menu.texy b/dependency-injection/pt/@left-menu.texy index b17de0ee08..a202a79191 100644 --- a/dependency-injection/pt/@left-menu.texy +++ b/dependency-injection/pt/@left-menu.texy @@ -1,8 +1,10 @@ Injeção de dependência ********************** - [O que é DI? |introduction] -- [O que é DI Container? |container] +- [Estado global e Singletons |global-state] - [Dependências de passagem |passing-dependencies] +- [O que é DI Container? |container] +- [Perguntas mais freqüentes |faq] Nette DI diff --git a/dependency-injection/pt/faq.texy b/dependency-injection/pt/faq.texy new file mode 100644 index 0000000000..09f9f07758 --- /dev/null +++ b/dependency-injection/pt/faq.texy @@ -0,0 +1,112 @@ +Perguntas mais freqüentes sobre DI (FAQ) +**************************************** + + +DI é outro nome para IoC? .[#toc-is-di-another-name-for-ioc] +------------------------------------------------------------ + +A *Inversion of Control* (IoC) é um princípio focado na forma como o código é executado - se seu código inicia o código externo ou se seu código é integrado ao código externo, que então o chama. +IoC é um conceito amplo que inclui [eventos |nette:glossary#Events], o chamado [Princípio de Hollywood |application:components#Hollywood style], e outros aspectos. +Fábricas, que fazem parte da [Regra #3: Deixe a Fábrica tratar disso |introduction#Rule #3: Let the Factory Handle It], e representam a inversão para o operador `new`, também são componentes deste conceito. + +A *Dependency Injection* (DI) é sobre como um objeto sabe sobre outro objeto, ou seja, dependência. É um padrão de desenho que requer a passagem explícita de dependências entre objetos. + +Assim, pode-se dizer que a DI pode ser uma forma específica de IoC. No entanto, nem todas as formas de IoC são adequadas em termos de pureza de código. Por exemplo, entre os anti-padrões, incluímos todas as técnicas que funcionam com o [estado global |global state] ou o chamado [Service Locator |#What is a Service Locator]. + + +O que é um Localizador de Serviços? .[#toc-what-is-a-service-locator] +--------------------------------------------------------------------- + +Um Localizador de Serviços é uma alternativa à Injeção de Dependência. Ele funciona criando um armazenamento central onde todos os serviços ou dependências disponíveis são registrados. Quando um objeto precisa de uma dependência, ele o solicita ao Service Locator. + +Entretanto, em comparação com a Injeção de Dependência, ela perde transparência: as dependências não são passadas diretamente aos objetos e, portanto, não são facilmente identificáveis, o que exige o exame do código para descobrir e compreender todas as conexões. Os testes também são mais complicados, pois não podemos simplesmente passar objetos simulados para os objetos testados, mas temos que passar pelo Localizador de Serviços. Além disso, o Localizador de Serviços perturba o projeto do código, pois objetos individuais devem estar cientes de sua existência, o que difere da Injeção de Dependência, onde os objetos não têm conhecimento do recipiente DI. + + +Quando é melhor não usar DI? .[#toc-when-is-it-better-not-to-use-di] +-------------------------------------------------------------------- + +Não há dificuldades conhecidas associadas ao uso do padrão de projeto de injeção de dependência. Pelo contrário, a obtenção de dependências a partir de locais globalmente acessíveis leva a [uma série de complicações |global-state], assim como o uso de um Localizador de Serviços. +Portanto, é aconselhável usar sempre o DI. Esta não é uma abordagem dogmática, mas simplesmente não foi encontrada uma alternativa melhor. + +Entretanto, há certas situações em que não passamos objetos um para o outro e os obtemos do espaço global. Por exemplo, ao depurar um código e precisar descarregar um valor variável em um ponto específico do programa, medir a duração de uma determinada parte do programa, ou registrar uma mensagem. +Nesses casos, quando se trata de ações temporárias que serão posteriormente removidas do código, é legítimo usar um dumper, cronômetro ou registrador globalmente acessível. Estas ferramentas, afinal, não pertencem ao projeto do código. + + +O uso de DI tem seus inconvenientes? .[#toc-does-using-di-have-its-drawbacks] +----------------------------------------------------------------------------- + +O uso da Injeção de Dependência envolve alguma desvantagem, como o aumento da complexidade de escrita de código ou pior desempenho? O que perdemos quando começamos a escrever código de acordo com a DI? + +DI não tem impacto no desempenho da aplicação ou nos requisitos de memória. O desempenho do Recipiente DI pode desempenhar um papel, mas no caso da [Nette DI | nette-container], o recipiente é compilado em PHP puro, de modo que suas despesas gerais durante o tempo de execução da aplicação são essencialmente zero. + +Ao escrever o código, é necessário criar construtoras que aceitem dependências. No passado, isto poderia ser demorado, mas graças às modernas IDEs e à [promoção da propriedade dos construtores |https://blog.nette.org/pt/php-8-0-visao-geral-completa-das-noticias#toc-constructor-property-promotion], agora é uma questão de poucos segundos. As fábricas podem ser facilmente geradas usando Nette DI e um plugin PhpStorm com apenas alguns cliques. +Por outro lado, não há necessidade de escrever singletons e pontos de acesso estáticos. + +Pode-se concluir que uma aplicação devidamente projetada usando DI não é nem mais curta nem mais longa em comparação com uma aplicação usando singletons. Partes do código que funcionam com dependências são simplesmente extraídas de classes individuais e movidas para novos locais, ou seja, o container DI e as fábricas. + + +Como reescrever um aplicativo legado para DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] +----------------------------------------------------------------------------------------------- + +A migração de uma aplicação herdada para a Injeção de Dependência pode ser um processo desafiador, especialmente para aplicações grandes e complexas. É importante abordar este processo de forma sistemática. + +- Ao passar para a Injeção de Dependência, é importante que todos os membros da equipe compreendam os princípios e práticas que estão sendo utilizados. +- Primeiro, realizar uma análise da aplicação existente para identificar os componentes-chave e suas dependências. Crie um plano para quais peças serão refatoradas e em que ordem. +- Implementar um recipiente DI ou, melhor ainda, utilizar uma biblioteca existente, como a Nette DI. +- Refatorar gradualmente cada parte da aplicação para usar a Injeção de Dependência. Isto pode envolver a modificação de construtores ou métodos para aceitar dependências como parâmetros. +- Modificar os lugares no código onde os objetos de dependência são criados de modo que as dependências sejam injetadas pelo contêiner. Isto pode incluir o uso de fábricas. + +Lembre-se de que passar para a Injeção de Dependência é um investimento na qualidade do código e na sustentabilidade a longo prazo da aplicação. Embora possa ser um desafio fazer estas mudanças, o resultado deve ser um código mais limpo, mais modular e facilmente testável que esteja pronto para futuras extensões e manutenção. + + +Por que a composição é preferível à herança? .[#toc-why-composition-is-preferred-over-inheritance] +-------------------------------------------------------------------------------------------------- +É preferível usar composição em vez de herança, pois serve ao propósito da reutilização do código sem ter a necessidade de se preocupar com o efeito de trickle down da mudança. Assim, ele proporciona um acoplamento mais frouxo onde não temos que nos preocupar em mudar algum código causando algum outro código dependente que necessite de mudança. Um exemplo típico é a situação identificada como [o inferno do construtor |passing-dependencies#Constructor hell]. + + +O Nette DI Container pode ser usado fora da Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] +---------------------------------------------------------------------------------------------------------- + +Absolutamente. O Nette DI Container faz parte da Nette, mas foi projetado como uma biblioteca independente que pode ser usada independentemente de outras partes da estrutura. Basta instalá-lo usando o Composer, criar um arquivo de configuração definindo seus serviços, e depois usar algumas linhas de código PHP para criar o container DI. +E você pode começar imediatamente a aproveitar a Injeção de Dependência em seus projetos. + +O capítulo [Nette DI Container |nette-container] descreve como é um caso de uso específico, incluindo o código. + + +Por que a configuração está nos arquivos NEON? .[#toc-why-is-the-configuration-in-neon-files] +--------------------------------------------------------------------------------------------- + +NEON é uma linguagem de configuração simples e de fácil leitura desenvolvida dentro da Nette para a criação de aplicações, serviços e suas dependências. Em comparação com JSON ou YAML, oferece opções muito mais intuitivas e flexíveis para este fim. Em NEON, você pode descrever naturalmente as ligações que não seriam possíveis de escrever em Symfony & YAML, ou apenas através de uma descrição complexa. + + +A análise dos arquivos NEON torna a aplicação mais lenta? .[#toc-does-parsing-neon-files-slow-down-the-application] +------------------------------------------------------------------------------------------------------------------- + +Embora os arquivos NEON sejam analisados muito rapidamente, este aspecto não importa muito. A razão é que os arquivos de análise ocorrem apenas uma vez durante o primeiro lançamento do aplicativo. Depois disso, o código do recipiente DI é gerado, armazenado no disco e executado para cada pedido subseqüente sem a necessidade de análise posterior. + +É assim que funciona em um ambiente de produção. Durante o desenvolvimento, os arquivos NEON são analisados cada vez que seu conteúdo muda, garantindo que o desenvolvedor tenha sempre um container DI atualizado. Como mencionado anteriormente, a análise propriamente dita é uma questão de um instante. + + +Como posso acessar os parâmetros do arquivo de configuração em minha classe? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +----------------------------------------------------------------------------------------------------------------------------------------------------------- + +Tenha em mente a [Regra nº 1: Deixe que seja passada a você |introduction#Rule #1: Let It Be Passed to You]. Se uma classe requer informações de um arquivo de configuração, não precisamos descobrir como acessar essas informações; em vez disso, simplesmente pedimos por elas - por exemplo, através do construtor da classe. E realizamos a passagem no arquivo de configuração. + +Neste exemplo, `%myParameter%` é um espaço reservado para o valor do parâmetro `myParameter`, que será passado para o construtor de `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Se você quiser passar vários parâmetros ou usar a fiação automática, é útil [embrulhar os parâmetros em um objeto |best-practices:passing-settings-to-presenters]. + + +A Nette suporta a interface do Container PSR-11? .[#toc-does-nette-support-psr-11-container-interface] +------------------------------------------------------------------------------------------------------ + +A Nette DI Container não suporta diretamente o PSR-11. Entretanto, se você precisar de interoperabilidade entre o Container Nette DI e bibliotecas ou estruturas que esperam a interface do Container PSR-11, você pode criar um [adaptador simples |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] para servir como ponte entre o Container Nette DI e o PSR-11. diff --git a/dependency-injection/pt/global-state.texy b/dependency-injection/pt/global-state.texy new file mode 100644 index 0000000000..ca1f29c34a --- /dev/null +++ b/dependency-injection/pt/global-state.texy @@ -0,0 +1,312 @@ +Estado global e Singletons +************************** + +.[perex] +Advertência: as seguintes construções são sintomas de mau desenho de código: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` ou `static::$var` + +Alguma dessas construções ocorre em seu código? Então você tem uma oportunidade de melhorar. Você pode estar pensando que estas são construções comuns que vemos em exemplos de soluções de várias bibliotecas e estruturas. +Infelizmente, elas ainda são um claro indicador de mau projeto. Elas têm uma coisa em comum: o uso do estado global. + +Agora, certamente não estamos falando de algum tipo de pureza acadêmica. O uso do estado global e singletons tem efeitos destrutivos sobre a qualidade do código. Seu comportamento se torna imprevisível, reduz a produtividade do desenvolvedor e força as interfaces de classe a mentir sobre suas verdadeiras dependências. O que confunde os programadores. + +Neste capítulo, mostraremos como isto é possível. + + +Interligação global .[#toc-global-interlinking] +----------------------------------------------- + +O problema fundamental com o estado global é que ele é acessível globalmente. Isto torna possível escrever para o banco de dados através do método global (estático) `DB::insert()`. +Em um mundo ideal, um objeto só deve ser capaz de se comunicar com outros objetos que lhe tenham sido [diretamente passados |passing-dependencies]. +Se eu criar dois objetos `A` e `B` e nunca passar uma referência de `A` para `B`, então nem `A`, nem `B` podem acessar o outro objeto ou mudar seu estado. +Esta é uma característica muito desejável do código. É semelhante a ter uma bateria e uma lâmpada; a lâmpada não acenderá até que você as ligue juntas. + +Isto não é verdade para variáveis globais (estáticas) ou singletons. O objeto `A` poderia *sem fio* acessar o objeto `C` e modificá-lo sem passar por nenhuma referência, ligando para `C::changeSomething()`. +Se o objeto `B` também pegar o global `C`, então `A` e `B` podem interagir entre si através de `C`. + +O uso de variáveis globais introduz uma nova forma de acoplamento *sem fio* no sistema que não é visível do exterior. +Ele cria uma cortina de fumaça complicando a compreensão e o uso do código. +Os desenvolvedores devem ler cada linha de código fonte para compreender verdadeiramente as dependências. Ao invés de apenas se familiarizarem com a interface das classes. +Além disso, é um acoplamento completamente desnecessário. + +.[note] +Em termos de comportamento, não há diferença entre uma variável global e uma variável estática. Elas são igualmente prejudiciais. + + +A ação assustadora à distância .[#toc-the-spooky-action-at-a-distance] +---------------------------------------------------------------------- + +"Ação assustadora à distância" - é o que Albert Einstein chamou famoso fenômeno na física quântica que lhe deu arrepios em 1935. +É um emaranhado quântico, cuja peculiaridade é que quando você mede informações sobre uma partícula, você afeta imediatamente outra partícula, mesmo que elas estejam a milhões de anos-luz de distância. +o que aparentemente viola a lei fundamental do universo de que nada pode viajar mais rápido do que a luz. + +No mundo do software, podemos chamar uma "ação assustadora à distância" de uma situação em que executamos um processo que pensamos estar isolado (porque não passamos nenhuma referência), mas interações e mudanças de estado inesperadas acontecem em locais distantes do sistema, das quais não falamos ao objeto. Isto só pode acontecer através do estado global. + +Imagine se juntar a uma equipe de desenvolvimento de projetos que tenha uma base de código grande e madura. Sua nova liderança lhe pede para implementar uma nova funcionalidade e, como um bom desenvolvedor, você começa escrevendo um teste. Mas como você é novo no projeto, você faz um monte de testes exploratórios do tipo "o que acontece se eu chamar este método". E você tenta escrever o seguinte teste: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // o número de seu cartão + $cc->charge(100); +} +``` + +Você executa o código, talvez várias vezes, e depois de um tempo você nota em seu telefone notificações do banco de que cada vez que você o executa, $100 foram cobrados em seu cartão de crédito 🤦♂️ + +Como diabos o teste poderia causar uma carga real? Não é fácil de operar com cartão de crédito. Você tem que interagir com um serviço web de terceiros, você tem que conhecer o URL desse serviço web, você tem que fazer o login, e assim por diante. +Nenhuma destas informações está incluída no teste. Pior ainda, você nem sabe onde estas informações estão presentes e, portanto, como zombar das dependências externas para que cada execução não resulte na cobrança de US$ 100 novamente. E como um novo desenvolvedor, como você deveria saber que o que você estava prestes a fazer o levaria a ser $100 mais pobre? + +Isso é uma ação assustadora à distância! + +Você não tem escolha a não ser cavar muito código fonte, perguntando aos colegas mais velhos e mais experientes, até entender como funcionam as conexões no projeto. +Isto se deve ao fato de que, ao olhar para a interface da classe `CreditCard`, você não pode determinar o estado global que precisa ser inicializado. Mesmo olhando para o código-fonte da classe, você não dirá qual método de inicialização deve ser chamado. Na melhor das hipóteses, você pode encontrar a variável global a ser acessada e tentar adivinhar como inicializá-la a partir disso. + +As aulas em tal projeto são mentirosos patológicos. O cartão de pagamento finge que você pode simplesmente instanciá-lo e ligar para o método `charge()`. No entanto, ele interage secretamente com outra classe, `PaymentGateway`. Mesmo sua interface diz que pode ser inicializada independentemente, mas na realidade, ela retira credenciais de algum arquivo de configuração e assim por diante. +É claro para os desenvolvedores que escreveram este código que `CreditCard` precisa `PaymentGateway`. Eles escreveram o código desta forma. Mas para qualquer novato no projeto, isto é um mistério completo e dificulta o aprendizado. + +Como consertar a situação? Fácil. **Deixe a API declarar as dependências.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Observe como as relações dentro do código são subitamente óbvias. Ao declarar que o método `charge()` precisa `PaymentGateway`, você não precisa perguntar a ninguém como o código é interdependente. Você sabe que tem que criar uma instância dele, e quando você tenta fazer isso, você se depara com o fato de que tem que fornecer parâmetros de acesso. Sem eles, o código não funcionaria. + +E o mais importante, agora você pode zombar da porta de pagamento para que não lhe sejam cobrados 100 dólares cada vez que fizer um teste. + +O estado global faz com que seus objetos possam acessar secretamente coisas que não estão declaradas em seus APIs e, como resultado, torna seus APIs mentirosos patológicos. + +Você pode não ter pensado nisso antes, mas sempre que você usa o estado global, você está criando canais secretos de comunicação sem fio. A ação remota assustadora força os desenvolvedores a ler cada linha de código para entender as possíveis interações, reduz a produtividade dos desenvolvedores e confunde os novos membros da equipe. +Se foi você quem criou o código, você conhece as dependências reais, mas qualquer um que venha atrás de você não tem a menor idéia. + +Não escreva código que use o estado global, prefira passar dependências. Ou seja, injeção de dependência. + + +Brittleness do Estado Global .[#toc-brittleness-of-the-global-state] +-------------------------------------------------------------------- + +Em código que usa estado global e singletons, nunca é certo quando e por quem esse estado mudou. Este risco já está presente na inicialização. O código a seguir deve criar uma conexão de banco de dados e inicializar o gateway de pagamento, mas ele continua lançando uma exceção e encontrar a causa é extremamente enfadonho: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Você tem que percorrer o código em detalhes para descobrir que o objeto `PaymentGateway` acessa outros objetos sem fio, alguns dos quais requerem uma conexão de banco de dados. Portanto, você deve inicializar o banco de dados antes de `PaymentGateway`. No entanto, a cortina de fumaça do estado global esconde isso de você. Quanto tempo você economizaria se a API de cada classe não mentisse e declarasse suas dependências? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Um problema semelhante surge quando se utiliza o acesso global a uma conexão de banco de dados: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Ao chamar o método `save()`, não é certo se já foi criada uma conexão de banco de dados e quem é o responsável por criá-la. Por exemplo, se quiséssemos mudar a conexão de banco de dados na hora, talvez para fins de teste, provavelmente teríamos que criar métodos adicionais, como `DB::reconnect(...)` ou `DB::reconnectForTest()`. + +Considere um exemplo: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Onde podemos ter certeza de que o banco de dados de testes está realmente sendo usado quando se liga para `$article->save()`? E se o método `Foo::doSomething()` mudou a conexão global do banco de dados? Para descobrir, teríamos que examinar o código fonte da classe `Foo` e provavelmente muitas outras classes. Entretanto, esta abordagem forneceria apenas uma resposta a curto prazo, pois a situação pode mudar no futuro. + +E se movermos a conexão de banco de dados para uma variável estática dentro da classe `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Isto não muda absolutamente nada. O problema é um estado global e não importa em qual classe ele se esconde. Neste caso, como no anterior, não temos nenhuma pista de qual banco de dados está sendo escrito quando o método `$article->save()` é chamado. Qualquer pessoa no extremo distante da aplicação poderia alterar o banco de dados a qualquer momento usando `Article::setDb()`. Sob nossas mãos. + +O estado global torna nossa aplicação **extremamente frágil**. + +Entretanto, há uma maneira simples de lidar com este problema. Basta que o API declare as dependências para garantir a funcionalidade adequada. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Esta abordagem elimina a preocupação de mudanças ocultas e inesperadas nas conexões de banco de dados. Agora temos certeza de onde o artigo é armazenado e nenhuma modificação de código dentro de outra classe não relacionada pode mais alterar a situação. O código não é mais frágil, mas estável. + +Não escreva código que utilize o estado global, prefira passar dependências. Portanto, injeção de dependência. + + +Singleton .[#toc-singleton] +--------------------------- + +Singleton é um padrão de design que, por [definição |https://en.wikipedia.org/wiki/Singleton_pattern] da famosa publicação Gang of Four, restringe uma classe a uma única instância e oferece acesso global a ela. A implementação deste padrão geralmente se assemelha ao seguinte código: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // e outros métodos que desempenham as funções da classe +} +``` + +Infelizmente, o singleton introduz o estado global na aplicação. E como demonstramos acima, o estado global é indesejável. É por isso que o singleton é considerado um antipadrão. + +Não use singletons em seu código e substitua-os por outros mecanismos. Você realmente não precisa de singletons. Entretanto, se você precisar garantir a existência de uma única instância de uma classe para toda a aplicação, deixe-a para o [container DI |container]. +Assim, crie um singleton de aplicação, ou serviço. Isto impedirá a classe de fornecer sua própria singularidade (ou seja, ela não terá um método `getInstance()` e uma variável estática) e só desempenhará suas funções. Assim, deixará de violar o princípio da responsabilidade única. + + +Testes de estado global versus testes .[#toc-global-state-versus-tests] +----------------------------------------------------------------------- + +Ao escrever testes, assumimos que cada teste é uma unidade isolada e que nenhum estado externo entra nele. E que nenhum estado deixa os testes. Quando um teste é concluído, qualquer estado associado com o teste deve ser removido automaticamente pelo coletor de lixo. Isto faz com que os testes sejam isolados. Portanto, podemos executar os testes em qualquer ordem. + +Entretanto, se estados/cingilões globais estiverem presentes, todas essas simpáticas suposições se desmoronam. Um estado pode entrar e sair de um teste. De repente, a ordem dos testes pode ser importante. + +Para testar singletons, os desenvolvedores muitas vezes têm que relaxar suas propriedades, talvez permitindo que uma instância seja substituída por outra. Tais soluções são, na melhor das hipóteses, hacks que produzem códigos difíceis de manter e de entender. Qualquer teste ou método `tearDown()` que afete qualquer estado global deve desfazer essas mudanças. + +O estado global é a maior dor de cabeça em testes unitários! + +Como consertar a situação? Fácil. Não escreva código que utilize singletons, prefira passar dependências. Ou seja, injeção de dependência. + + +Constantes globais .[#toc-global-constants] +------------------------------------------- + +O estado global não se limita ao uso de singletons e variáveis estáticas, mas também pode se aplicar a constantes globais. + +Constantes cujo valor não nos fornece nenhuma informação nova (`M_PI`) ou útil (`PREG_BACKTRACK_LIMIT_ERROR`) são claramente OK. +Por outro lado, constantes que servem como uma forma de *sem fio* passar informações dentro do código nada mais são do que uma dependência oculta. Como `LOG_FILE` no exemplo a seguir. +O uso da constante `FILE_APPEND` é perfeitamente correto. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Neste caso, devemos declarar o parâmetro no construtor da classe `Foo` para torná-la parte da API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Agora podemos passar informações sobre o caminho para o arquivo de registro e facilmente alterá-lo conforme necessário, facilitando o teste e a manutenção do código. + + +Funções globais e métodos estáticos .[#toc-global-functions-and-static-methods] +------------------------------------------------------------------------------- + +Queremos enfatizar que o uso de métodos estáticos e funções globais não é, por si só, problemático. Explicamos a inadequação do uso de `DB::insert()` e métodos similares, mas sempre foi uma questão de estado global armazenada em uma variável estática. O método `DB::insert()` requer a existência de uma variável estática porque armazena a conexão de banco de dados. Sem esta variável, seria impossível implementar o método. + +O uso de métodos e funções estáticas determinísticas, tais como `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` e muitas outras, é perfeitamente consistente com a injeção de dependência. Estas funções sempre retornam os mesmos resultados a partir dos mesmos parâmetros de entrada e são, portanto, previsíveis. Elas não utilizam nenhum estado global. + +No entanto, há funções no PHP que não são determinísticas. Estas incluem, por exemplo, a função `htmlspecialchars()`. Seu terceiro parâmetro, `$encoding`, se não for especificado, é o valor padrão da opção de configuração `ini_get('default_charset')`. Portanto, recomenda-se sempre especificar este parâmetro para evitar um possível comportamento imprevisível da função. A Nette faz isto de forma consistente. + +Algumas funções, tais como `strtolower()`, `strtoupper()`, e similares, tiveram um comportamento não determinístico no passado recente e dependeram da configuração `setlocale()`. Isto causou muitas complicações, na maioria das vezes quando se trabalha com o idioma turco. +Isto porque o idioma turco distingue entre maiúsculas e minúsculas `I` com e sem um ponto. Assim, `strtolower('I')` devolveu o caracter `ı` e `strtoupper('i')` devolveu o caracter `İ`, o que levou a aplicações que causaram uma série de erros misteriosos. +Entretanto, este problema foi corrigido na versão 8.2 do PHP e as funções não são mais dependentes do locale. + +Este é um bom exemplo de como o estado global tem atormentado milhares de desenvolvedores em todo o mundo. A solução foi substituí-lo por uma injeção de dependência. + + +Quando é possível usar o Estado Global? .[#toc-when-is-it-possible-to-use-global-state] +--------------------------------------------------------------------------------------- + +Existem certas situações específicas em que é possível utilizar o estado global. Por exemplo, quando se depura o código e é necessário descarregar o valor de uma variável ou medir a duração de uma parte específica do programa. Em tais casos, que dizem respeito a ações temporárias que serão posteriormente removidas do código, é legítimo usar um dumper ou cronômetro disponível globalmente. Estas ferramentas não fazem parte do projeto do código. + +Outro exemplo são as funções para trabalhar com expressões regulares `preg_*`, que armazenam internamente expressões regulares compiladas em um cache estático na memória. Quando você chama a mesma expressão regular várias vezes em diferentes partes do código, ela é compilada apenas uma vez. O cache economiza desempenho e também é completamente invisível para o usuário, de modo que tal uso pode ser considerado legítimo. + + +Sumário .[#toc-summary] +----------------------- + +Mostramos porque faz sentido + +1) Remover todas as variáveis estáticas do código +2) Declarar as dependências +3) E usar injeção de dependência + +Ao contemplar o projeto do código, tenha em mente que cada `static $foo` representa um problema. Para que seu código seja um ambiente respeitador do DI, é essencial erradicar completamente o estado global e substituí-lo por injeção de dependência. + +Durante este processo, você pode descobrir que precisa dividir uma classe porque ela tem mais de uma responsabilidade. Não se preocupe com isso; esforce-se pelo princípio de uma responsabilidade. + +*Gostaria de agradecer a Miško Hevery, cujos artigos como [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] formam a base deste capítulo.* diff --git a/dependency-injection/pt/introduction.texy b/dependency-injection/pt/introduction.texy index 02d20bad2a..102337536f 100644 --- a/dependency-injection/pt/introduction.texy +++ b/dependency-injection/pt/introduction.texy @@ -2,17 +2,17 @@ O que é Injeção de Dependência? ******************************* .[perex] -Este capítulo apresenta as práticas básicas de programação que você deve seguir ao escrever qualquer solicitação. Estas são as bases necessárias para escrever um código limpo, compreensível e de fácil manutenção. +Este capítulo lhe apresentará as práticas básicas de programação que você deve seguir ao redigir qualquer solicitação. Estes são os fundamentos necessários para escrever um código limpo, compreensível e de fácil manutenção. -Se você aprender e seguir estas regras, Nette estará lá para você a cada passo do caminho. Ela cuidará das tarefas de rotina para você e o deixará o mais confortável possível para que você possa se concentrar na própria lógica. +Se você aprender e seguir estas regras, Nette estará lá para você a cada passo do caminho. Ela cuidará das tarefas rotineiras para você e lhe proporcionará o máximo de conforto, para que você possa se concentrar na própria lógica. -Os princípios que vamos mostrar aqui são bastante simples. Você não tem nada com que se preocupar. +Os princípios que vamos mostrar aqui são bastante simples. Você não tem que se preocupar com nada. Lembra-se de seu primeiro programa? .[#toc-remember-your-first-program] ----------------------------------------------------------------------- -Não temos idéia em que linguagem você a escreveu, mas se fosse PHP, provavelmente seria algo parecido com isto: +Não sabemos em que linguagem você a escreveu, mas se fosse PHP, poderia ter sido algo parecido com isto: ```php function addition(float $a, float $b): float @@ -25,31 +25,31 @@ echo addition(23, 1); // impressões 24 Algumas linhas triviais de código, mas tantos conceitos-chave escondidos nelas. Que existem variáveis. Que esse código é dividido em unidades menores, que são funções, por exemplo. Que nós as passamos argumentos de entrada e elas retornam resultados. Tudo o que está faltando são condições e loops. -O fato de passarmos a entrada para uma função e ela retornar um resultado é um conceito perfeitamente compreensível que é usado em outros campos, como a matemática. +O fato de uma função pegar dados de entrada e retornar um resultado é um conceito perfeitamente compreensível, que também é usado em outros campos, como a matemática. -Uma função tem uma assinatura, que consiste em seu nome, uma lista de parâmetros e seus tipos e, finalmente, o tipo de valor de retorno. Como usuários, estamos interessados na assinatura; normalmente não precisamos saber nada sobre a implementação interna. +Uma função tem sua assinatura, que consiste em seu nome, uma lista de parâmetros e seus tipos, e finalmente o tipo do valor de retorno. Como usuários, estamos interessados na assinatura, e normalmente não precisamos saber nada sobre a implementação interna. -Agora imagine que a assinatura de uma função se parece com isto: +Agora imagine que a assinatura da função tivesse este aspecto: ```php function addition(float $x): float ``` -Uma adição com um parâmetro? Isso é estranho... Que tal isto? +Uma adição com um parâmetro? Isso é estranho... E quanto a isto? ```php function addition(): float ``` -Isso é realmente estranho, não é? Como você acha que a função é usada? +Isso é realmente estranho, certo? Como a função é utilizada? ```php echo addition(); // o que imprime? ``` -Olhando para tal código, ficamos confusos. Não só um iniciante não o entenderia, nem mesmo um programador hábil entenderia tal código. +Olhando para tal código, ficaríamos confusos. Não só um iniciante não entenderia, mas até mesmo um programador experiente não entenderia tal código. -Você se pergunta como seria realmente uma função desse tipo por dentro? Onde obteria as víboras? Provavelmente os conseguiria *somente* por si só, assim: +Você está se perguntando como seria realmente uma função desse tipo por dentro? Onde obteria as somas? Provavelmente, de alguma forma, ela os obteria sozinha, talvez assim: ```php function addition(): float @@ -68,11 +68,11 @@ Não desta maneira! .[#toc-not-this-way] O projeto que acabamos de mostrar é a essência de muitas características negativas: -- a assinatura da função fingia que não precisava de adendos, o que nos confundia +- a assinatura da função fingia que não precisava das somas, o que nos confundia - não temos idéia de como fazer o cálculo da função com dois outros números -- tivemos que olhar para o código para ver onde ele leva os adendos -- descobrimos encadernações ocultas -- para compreender plenamente, precisamos explorar também estas ligações +- tivemos que olhar para o código para descobrir de onde veio o summands +- encontramos dependências ocultas +- um entendimento completo requer o exame destas dependências também E é mesmo o trabalho da função de adição a aquisição de insumos? Claro que não é. Sua responsabilidade é apenas a de acrescentar. @@ -91,22 +91,22 @@ function addition(float $a, float $b): float Regra nº 1: Deixe que seja passado para você .[#toc-rule-1-let-it-be-passed-to-you] ----------------------------------------------------------------------------------- -A regra mais importante é: **todos os dados que funcionam ou classes precisam ser passados a eles***. +A regra mais importante é: **todos os dados que funcionam ou classes precisam ser passados a eles**. -Em vez de inventar mecanismos ocultos para ajudá-los de alguma forma a chegar até eles mesmos, basta passar os parâmetros para dentro. Você economizará o tempo necessário para inventar mecanismos ocultos, o que definitivamente não irá melhorar seu código. +Em vez de inventar formas ocultas de acesso aos dados em si, basta passar os parâmetros. Você economizará tempo que seria gasto inventando caminhos ocultos que certamente não irão melhorar seu código. -Se você segue esta regra sempre e em qualquer lugar, você está a caminho de codificar sem amarrações ocultas. Para um código que seja compreensível não só para o autor, mas também para qualquer pessoa que o leia depois. Onde tudo é compreensível a partir das assinaturas de funções e classes e não há necessidade de buscar segredos ocultos na implementação. +Se você sempre e em todos os lugares segue esta regra, está a caminho de codificar sem dependências ocultas. A um código que seja compreensível não só para o autor, mas também para qualquer pessoa que o leia depois. Onde tudo é compreensível a partir das assinaturas de funções e classes, e não há necessidade de buscar segredos ocultos na implementação. -Esta técnica é denominada habilmente **injeção de dependência***. E os dados são chamados de **dependências.** Mas é um parâmetro simples de passagem, nada mais. +Esta técnica é denominada habilmente **injeção de dependência**. E os dados são chamados de **dependências**. Mas é um parâmetro simples de passagem, nada mais. .[note] -Por favor, não confunda injeção de dependência, que é um padrão de projeto, com "recipiente de injeção de dependência", que é uma ferramenta, algo completamente diferente. Discutiremos os recipientes mais tarde. +Por favor, não confunda a injeção por dependência, que é um padrão de projeto, com um "recipiente de injeção por dependência", que é uma ferramenta, algo diametralmente diferente. Trataremos dos recipientes mais tarde. Das funções às aulas .[#toc-from-functions-to-classes] ------------------------------------------------------ -E como as aulas se relacionam com isso? Uma classe é uma entidade mais complexa do que uma simples função, mas a regra nº 1 também se aplica aqui. Há apenas [mais maneiras de passar argumentos |passing-dependencies]. Por exemplo, bem parecido com o caso de uma função: +E como as classes estão relacionadas? Uma classe é uma unidade mais complexa do que uma simples função, mas a regra nº 1 também se aplica inteiramente aqui. Há apenas [mais maneiras de passar argumentos |passing-dependencies]. Por exemplo, bem parecido com o caso de uma função: ```php class Math @@ -121,7 +121,7 @@ $math = new Math; echo $math->addition(23, 1); // 24 ``` -Ou através de outros métodos, ou diretamente pelo construtor: +Ou através de outros métodos, ou diretamente através do construtor: ```php class Addition @@ -149,9 +149,9 @@ Ambos os exemplos estão completamente de acordo com a injeção de dependência Exemplos da vida real .[#toc-real-life-examples] ------------------------------------------------ -No mundo real, você não vai escrever aulas para adicionar números. Passemos aos exemplos do mundo real. +No mundo real, você não estará escrevendo aulas para adicionar números. Passemos a exemplos práticos. -Vamos ter uma aula `Article` representando um artigo de blog: +Vamos ter uma aula `Article` representando um post no blog: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -O método `save()` salva o artigo em uma tabela de banco de dados. Implementá-lo usando o [Nette Database |database:] seria canja, se não fosse por um único engate: onde `Article` deveria obter a conexão com o banco de dados, ou seja, o objeto de classe `Nette\Database\Connection`? +O método `save()` salvará o artigo em uma tabela de banco de dados. A implementação usando o [Nette Database |database:] será canja, se não fosse por uma única questão: onde `Article` obtém a conexão com o banco de dados, ou seja, um objeto de classe `Nette\Database\Connection`? -Parece que temos muitas opções. Ela pode ser tirada de algum lugar em uma variável estática. Ou herdá-la de uma classe que fornecerá a conexão com o banco de dados. Ou tirar vantagem de um [único botão |global-state#Singleton]. Ou as chamadas fachadas que são usadas em Laravel: +Parece que temos muitas opções. Ele pode tirá-lo de uma variável estática em algum lugar. Ou herdá-la de uma classe que fornece uma conexão de banco de dados. Ou tirar vantagem de um [único botão |global-state#Singleton]. Ou usar as chamadas fachadas, que são usadas em Laravel: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ class Article Ou temos? -Vamos relembrar a [regra nº 1: Deixe que seja passado para você |#rule #1: Let It Be Passed to You]: todas as dependências que a classe precisa devem ser passadas a ela. Porque se não o fizermos, e quebrarmos a regra, começamos pelo caminho do código sujo cheio de encadernações ocultas, incompreensibilidade, e o resultado será uma aplicação que é uma dor para manter e desenvolver. +Vamos relembrar a [regra nº 1: Que seja passada a você |#rule #1: Let It Be Passed to You]: todas as dependências que a classe precisa devem ser passadas a ela. Porque se quebrarmos a regra, embarcamos num caminho de código sujo cheio de dependências ocultas, incompreensíveis, e o resultado será uma aplicação que será dolorosa de manter e desenvolver. -O usuário da classe `Article` não tem idéia de onde o método `save()` armazena o artigo. Em uma tabela de banco de dados? Em qual delas, produção ou desenvolvimento? E como isso pode ser mudado? +O usuário da classe `Article` não tem idéia onde o método `save()` armazena o artigo. Em uma tabela de banco de dados? Qual delas, produção ou teste? E como pode ser mudado? -O usuário tem que olhar como o método `save()` é implementado para encontrar o uso do método `DB::insert()`. Portanto, ele tem que pesquisar mais para descobrir como este método provê uma conexão de banco de dados. E as amarrações ocultas podem formar uma cadeia bastante longa. +O usuário tem que olhar como o método `save()` é implementado, e encontra o uso do método `DB::insert()`. Portanto, ele tem que pesquisar mais para descobrir como este método obtém uma conexão de banco de dados. E as dependências ocultas podem formar uma cadeia bastante longa. -Ligações ocultas, fachadas de Laravel ou variáveis estáticas nunca estão presentes em código limpo e bem desenhado. Em código limpo e bem desenhado, os argumentos são passados: +Em código limpo e bem projetado, nunca há dependências ocultas, fachadas de Laravel, ou variáveis estáticas. Em código limpo e bem desenhado, os argumentos são passados: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Ainda mais prático, como veremos a seguir, é usar um construtor: +Uma abordagem ainda mais prática, como veremos mais adiante, será através do construtor: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Se você é um programador experiente, você pode estar pensando que `Article` não deveria ter um método `save()`, deveria ser um componente de dados puro, e um repositório separado deveria cuidar do armazenamento. Isto faz sentido. Mas isso nos levaria muito além do tópico, que é a injeção de dependência, e tentando dar exemplos simples. +Se você é um programador experiente, você pode pensar que `Article` não deveria ter um método `save()`; ele deveria representar um componente de dados puramente, e um repositório separado deveria se encarregar de salvar. Isso faz sentido. Mas isso nos levaria muito além do escopo do tópico, que é a injeção de dependência, e o esforço para fornecer exemplos simples. -Se você vai escrever uma classe que requer um banco de dados para operar, por exemplo, não descubra de onde obtê-lo, mas faça-o passar para você. Talvez como um parâmetro para um construtor ou outro método. Declare as dependências. Exponha-as na API de sua classe. Você obterá um código compreensível e previsível. +Se você escreve uma classe que requer, por exemplo, um banco de dados para seu funcionamento, não invente de onde obtê-lo, mas faça com que ele passe. Seja como um parâmetro do construtor ou outro método. Admita dependências. Admita-as na API de sua classe. Você obterá um código compreensível e previsível. -Que tal esta classe que registra mensagens de erro: +E quanto a esta classe, que registra mensagens de erro: ```php class Logger @@ -266,9 +266,9 @@ O que você acha, nós seguimos a [regra nº 1: Deixe que seja passado para voc Nós não o fizemos. -A informação chave, o diretório de arquivos de log, é *obtida* pela classe da constante. +A informação chave, ou seja, o diretório com o arquivo de log, é *obtida* pela própria classe a partir da constante. -Veja o exemplo de uso: +Vejam o exemplo de uso: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Sem conhecer a implementação, você poderia responder à pergunta onde as mensagens são escritas? Sugeriria a você que a existência da constante LOG_DIR é necessária para que ela funcione? E você seria capaz de criar uma segunda instância que escreva para um local diferente? Certamente que não. +Sem conhecer a implementação, você poderia responder à questão de onde as mensagens são escritas? Você adivinharia que a existência da constante `LOG_DIR` é necessária para seu funcionamento? E você poderia criar uma segunda instância que escrevesse para um local diferente? Certamente que não. Vamos consertar a classe: @@ -295,7 +295,7 @@ class Logger } ``` -A classe é agora muito mais clara, mais configurável e, portanto, mais útil. +A classe é agora muito mais compreensível, configurável e, portanto, mais útil. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Mas eu não me importo! .[#toc-but-i-don-t-care] ----------------------------------------------- -*"Quando eu crio um objeto de Artigo e chamo salvar(), não quero lidar com o banco de dados, apenas quero que ele seja salvo para aquele que eu defini na configuração. "* +*"Quando eu crio um objeto de Artigo e chamo salvar(), eu não quero lidar com o banco de dados; eu só quero que ele seja salvo no que eu defini na configuração."* -*"Quando uso o Logger, só quero que a mensagem seja escrita, e não quero lidar com onde. Deixe que as configurações globais sejam usadas. "* +*"Quando uso o Logger, só quero que a mensagem seja escrita, e não quero lidar com onde. Deixe que as configurações globais sejam usadas."* -Estes são os comentários corretos. +Estes são pontos válidos. -Como exemplo, vamos dar uma aula que envia boletins informativos e registros de como isso foi feito: +Como exemplo, vejamos uma aula que envia boletins informativos e registros de como foi: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -O `Logger` melhorado, que não usa mais a constante `LOG_DIR`, requer um caminho de arquivo no construtor. Como resolver isto? A classe `NewsletterDistributor` não se importa onde as mensagens são escritas, ela só quer escrevê-las. +O melhorado `Logger`, que não usa mais a constante `LOG_DIR`, requer a especificação do caminho do arquivo no construtor. Como resolver isso? A classe `NewsletterDistributor` não se importa onde as mensagens são escritas; ela só quer escrevê-las. -A solução é novamente a [regra nº 1: Deixe que seja passado para você |#rule #1: Let It Be Passed to You]: passe todos os dados que a classe precisa para ela. +A solução é novamente a [regra nº 1: Que seja passada a você |#rule #1: Let It Be Passed to You]: passe todos os dados que a classe precisa. -Então passamos o caminho para o toro para o construtor, que depois usamos para criar o objeto `Logger`? +Então isso significa que passamos o caminho para o tronco através do construtor, que depois usamos ao criar o objeto `Logger`? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Não dessa maneira! Porque o caminho ** não*** pertence aos dados que a classe `NewsletterDistributor` precisa; precisa `Logger`. A classe precisa do próprio madeireiro. E é isso que vamos passar adiante: +Não, assim não! O caminho não faz parte dos dados que a classe `NewsletterDistributor` precisa; na verdade, o `Logger` precisa dele. Você vê a diferença? A classe `NewsletterDistributor` precisa do próprio madeireiro. Então, é isso que vamos passar: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Agora fica claro a partir das assinaturas da classe `NewsletterDistributor` que a extração de madeira é parte de sua funcionalidade. E a tarefa de substituir o madeireiro por outro, talvez para fins de teste, é bastante trivial. -Além disso, se o construtor da classe `Logger` for alterado, isso não terá nenhum efeito sobre nossa classe. +Agora fica claro a partir das assinaturas da classe `NewsletterDistributor` que a extração de madeira também faz parte de sua funcionalidade. E a tarefa de trocar o madeireiro por outro, talvez para testes, é completamente trivial. +Além disso, se o construtor da classe `Logger` mudar, isso não afetará nossa classe. -Regra nº 2: Tome o que é seu .[#toc-rule-2-take-what-is-yours] --------------------------------------------------------------- +Regra # 2: Tome o que é seu .[#toc-rule-2-take-what-s-yours] +------------------------------------------------------------ -Não se deixe enganar e não deixe que os parâmetros de suas dependências lhe sejam passados. Transmita diretamente as dependências. +Não se deixe enganar e não se deixe passar pelas dependências de suas dependências. Basta passar suas próprias dependências. -Isto tornará o código usando outros objetos completamente independente de mudanças em seus construtores. Sua API será mais verdadeira. E o mais importante, será trivial trocar essas dependências por outras. +Graças a isso, o código que utiliza outros objetos será completamente independente das mudanças em seus construtores. Sua API será mais verdadeira. E acima de tudo, será trivial substituir estas dependências por outras. -Um novo membro da família .[#toc-a-new-member-of-the-family] ------------------------------------------------------------- +Novo membro da família .[#toc-new-family-member] +------------------------------------------------ -A equipe de desenvolvimento decidiu criar um segundo logger que escreva para o banco de dados. Por isso, criamos uma classe `DatabaseLogger`. Então temos duas classes, `Logger` e `DatabaseLogger`, uma escreve para um arquivo, a outra escreve para um banco de dados ... você não acha que há algo de estranho nesse nome? -Não seria melhor renomear `Logger` para `FileLogger`? Claro que sim. +A equipe de desenvolvimento decidiu criar um segundo logger que escreva para o banco de dados. Por isso, criamos uma classe `DatabaseLogger`. Então temos duas classes, `Logger` e `DatabaseLogger`, uma que escreve para um arquivo, a outra para um banco de dados ... o nome não lhe parece estranho? +Não seria melhor renomear `Logger` para `FileLogger`? Definitivamente sim. -Mas vamos fazer isso com inteligência. Vamos criar uma interface com o nome original: +Mas façamos isso de forma inteligente. Criamos uma interface com o nome original: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...que ambos os madeireiros implementarão: +... que ambos os madeireiros irão implementar: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -E desta forma, nada precisará ser alterado no resto do código onde o madeireiro é utilizado. Por exemplo, o construtor da classe `NewsletterDistributor` ainda ficará feliz em exigir `Logger` como parâmetro. E caberá a nós qual instância passaremos para ele. +E por causa disso, não haverá necessidade de alterar nada no resto do código onde o madeireiro é utilizado. Por exemplo, o construtor da classe `NewsletterDistributor` ainda estará satisfeito com a exigência de `Logger` como parâmetro. E caberá a nós qual instância passaremos. -** É por isso que nunca damos nomes de interface o sufixo `Interface` ou o prefixo `I`.** Caso contrário, seria impossível desenvolver código tão bem. +**É por isso que nunca adicionamos o sufixo `Interface` ou o prefixo `I` aos nomes das interfaces.** Caso contrário, não seria possível desenvolver o código tão bem. Houston, temos um problema .[#toc-houston-we-have-a-problem] ------------------------------------------------------------ -Enquanto em toda a aplicação podemos ficar satisfeitos com uma única instância de um registrador, seja de arquivo ou banco de dados, e simplesmente passá-la onde quer que algo esteja registrado, é bem diferente no caso da classe `Article`. Na verdade, criamos instâncias dela conforme a necessidade, possivelmente várias vezes. Como lidar com a encadernação do banco de dados em seu construtor? +Embora possamos passar com uma única instância do registrador, seja baseada em arquivo ou em banco de dados, em toda a aplicação e simplesmente passá-la onde quer que algo esteja registrado, é bastante diferente para a classe `Article`. Criamos suas instâncias conforme a necessidade, mesmo várias vezes. Como lidar com a dependência do banco de dados em seu construtor? -Como exemplo, podemos usar um controlador que deve salvar um artigo no banco de dados após o envio de um formulário: +Um exemplo pode ser um controlador que deve salvar um artigo no banco de dados depois de submeter um formulário: ```php class EditController extends Controller @@ -437,17 +437,17 @@ class EditController extends Controller } ``` -Uma possível solução é oferecida diretamente: fazer passar o objeto do banco de dados pelo construtor para `EditController` e usar `$article = new Article($this->db)`. +Uma possível solução é óbvia: passar o objeto do banco de dados para o construtor `EditController` e usar `$article = new Article($this->db)`. -Como no caso anterior com `Logger` e o caminho do arquivo, esta não é a abordagem correta. O banco de dados não é uma dependência do `EditController`, mas do `Article`. Assim, a passagem do banco de dados vai contra a [regra nº 2: pegue o que é seu |#rule #2: take what is yours]. Quando o construtor da classe `Article` é modificado (um novo parâmetro é adicionado), o código em todos os lugares onde as instâncias são criadas também precisará ser modificado. Ufff. +Assim como no caso anterior com `Logger` e o caminho do arquivo, esta não é a abordagem correta. O banco de dados não é uma dependência do `EditController`, mas do `Article`. Passar o banco de dados vai contra a [regra nº 2: pegue o que é seu |#rule #2: take what's yours]. Se o construtor da classe `Article` mudar (um novo parâmetro é adicionado), você precisará modificar o código onde quer que as instâncias sejam criadas. Ufff. -Houston, o que você está sugerindo? +Houston, o que você sugere? Regra nº 3: Deixe a Fábrica tratar disso .[#toc-rule-3-let-the-factory-handle-it] --------------------------------------------------------------------------------- -Ao remover as amarrações ocultas e passar todas as dependências como argumentos, obtemos classes mais configuráveis e flexíveis. E assim, precisamos de algo mais para criar e configurar essas classes mais flexíveis. Vamos chamá-lo de fábricas. +Ao eliminar dependências ocultas e passar todas as dependências como argumentos, ganhamos classes mais configuráveis e flexíveis. E, portanto, precisamos de algo mais para criar e configurar essas classes mais flexíveis para nós. Vamos chamá-la de fábricas. A regra básica é: se uma classe tem dependências, deixar a criação de suas instâncias para a fábrica. @@ -460,7 +460,7 @@ Por favor, não confunda com o padrão de projeto *método de fábrica*, que des Fábrica .[#toc-factory] ----------------------- -Uma fábrica é um método ou classe que produz e configura objetos. Chamamos `Article` produzindo a classe `ArticleFactory` e poderia parecer assim: +Uma fábrica é um método ou classe que cria e configura objetos. Vamos nomear a classe que produz `Article` como `ArticleFactory`, e pode parecer assim: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Sua utilização no controlador seria a seguinte: +Sua utilização no controlador será a seguinte: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -Neste ponto, quando a assinatura do construtor da classe `Article` muda, a única parte do código que precisa responder é a própria fábrica `ArticleFactory`. Qualquer outro código que funcione com objetos `Article`, tais como `EditController`, não será afetado. +Neste ponto, se a assinatura do construtor da classe `Article` mudar, a única parte do código que precisa reagir é o próprio `ArticleFactory`. Todos os outros códigos que trabalham com objetos `Article`, como o `EditController`, não serão afetados. -Você pode estar batendo a testa agora mesmo se perguntando se nós nos ajudamos de alguma forma. A quantidade de código cresceu e tudo isso está começando a parecer suspeitamente complicado. +Você pode estar se perguntando se nós realmente fizemos as coisas melhorarem. A quantidade de código aumentou, e tudo começa a parecer suspeitosamente complicado. -Não se preocupe, em breve chegaremos ao recipiente Nette DI. E ele tem uma série de ases na manga que tornarão as aplicações de construção utilizando a injeção de dependência extremamente simples. Por exemplo, em vez da classe `ArticleFactory`, será suficiente [escrever uma interface simples |factory]: +Não se preocupe, logo chegaremos ao recipiente Nette DI. E ele tem vários truques na manga, o que simplificará muito as aplicações de construção usando a injeção de dependência. Por exemplo, ao invés da classe `ArticleFactory`, você só precisará [escrever uma interface simples |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Mas estamos nos adiantando, segurem-se :-) +Mas estamos nos adiantando; por favor, seja paciente :-) Sumário .[#toc-summary] ----------------------- -No início deste capítulo, prometemos mostrar-lhe uma maneira de projetar um código limpo. Basta dar as aulas +No início deste capítulo, prometemos mostrar-lhe um processo para projetar um código limpo. Tudo o que é preciso é que as aulas o façam: -- [as dependências de que eles precisam |#Rule #1: Let It Be Passed to You] -- [e não o que eles não precisam diretamente |#Rule #2: Take What Is Yours] -- [e que os objetos com dependências são melhor feitos em fábricas |#Rule #3: Let the Factory Handle it] +- [passar as dependências de que necessitam |#Rule #1: Let It Be Passed to You] +- [por outro lado, não passar o que eles não precisam diretamente |#Rule #2: Take What's Yours] +- [e que os objetos com dependências são melhor criados em fábricas |#Rule #3: Let the Factory Handle it] -Pode não parecer assim à primeira vista, mas estas três regras têm implicações de longo alcance. Elas levam a uma visão radicalmente diferente do projeto do código. Será que vale a pena? Programadores que expulsaram velhos hábitos e começaram a usar de forma consistente a injeção de dependência consideram isto um momento crucial em suas vidas profissionais. Isso abriu um mundo de aplicações claras e sustentáveis. +À primeira vista, estas três regras podem não parecer ter conseqüências de longo alcance, mas elas levam a uma perspectiva radicalmente diferente sobre o desenho de códigos. Será que vale a pena? Os desenvolvedores que abandonaram velhos hábitos e começaram a usar de forma consistente a injeção de dependência consideram esta etapa um momento crucial em suas vidas profissionais. Ela abriu o mundo de aplicações claras e de fácil manutenção para eles. -Mas e se o código não usar a injeção de dependência de forma consistente? E se ele for construído sobre métodos estáticos ou singletons? Isso traz algum problema? [Traz, e é muito significativo |global-state]. +Mas e se o código não usar a injeção de dependência de forma consistente? E se ele se baseia em métodos estáticos ou singletons? Isso causa algum problema? [Sim, e muito fundamentais |global-state]. diff --git a/dependency-injection/pt/passing-dependencies.texy b/dependency-injection/pt/passing-dependencies.texy index 0b5cb02247..a6b4434e8d 100644 --- a/dependency-injection/pt/passing-dependencies.texy +++ b/dependency-injection/pt/passing-dependencies.texy @@ -12,7 +12,7 @@ Argumentos, ou "dependências" na terminologia DI, podem ser passados às aulas </div> -Os três primeiros métodos se aplicam em geral em todas as línguas orientadas a objetos, o quarto é específico para os apresentadores Nette, por isso é discutido em [capítulo separado |best-practices:inject-method-attribute]. Vamos agora analisar mais de perto cada uma destas opções e mostrá-las com exemplos específicos. +Vamos agora ilustrar as diferentes variantes com exemplos concretos. Injeção do construtor .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Injeção do construtor .[#toc-constructor-injection] As dependências são passadas como argumentos para o construtor quando o objeto é criado: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Este formulário é útil para as dependências obrigatórias que a classe precisa absolutamente funcionar, pois sem elas a instância não pode ser criada. @@ -40,10 +40,10 @@ Desde o PHP 8.0, podemos usar uma forma mais curta de notação que é funcional ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ A partir do PHP 8.1, uma propriedade pode ser marcada com uma bandeira `readonly ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService Recipiente DI passa automaticamente as dependências para o construtor usando [a fiação automática |autowiring]. Argumentos que não podem ser passados desta forma (por exemplo, strings, números, booleans) [escrevem na configuração |services#Arguments]. +Construtor Inferno .[#toc-constructor-hell] +------------------------------------------- + +O termo *inferno construtor* refere-se a uma situação em que uma criança herda de uma classe de pais cujo construtor requer dependências, e a criança requer dependências também. Também deve assumir e transmitir as dependências dos pais: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +O problema ocorre quando queremos mudar o construtor da classe `BaseClass`, por exemplo, quando uma nova dependência é acrescentada. Então temos que modificar todos os construtores das crianças também. O que faz de tal modificação um inferno. + +Como evitar isso? A solução é **priorizar a composição sobre a herança***. + +Portanto, vamos projetar o código de forma diferente. Evitaremos as aulas abstratas `Base*`. Ao invés de `MyClass` obter alguma funcionalidade herdando de `BaseClass`, terá essa funcionalidade passada como uma dependência: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Injeção de setter .[#toc-setter-injection] ========================================== -As dependências são passadas chamando um método que as armazena em uma propriedade privada. A convenção usual de nomenclatura destes métodos é a forma `set*()`, razão pela qual eles são chamados de setters. +As dependências são passadas chamando um método que as armazena em uma propriedade privada. A convenção usual de nomes para estes métodos é a forma `set*()`, razão pela qual eles são chamados de setters, mas é claro que eles podem ser chamados de qualquer outra coisa. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Este método é útil para dependências opcionais que não são necessárias para a função de classe, uma vez que não é garantido que o objeto realmente as receberá (ou seja, que o usuário chamará o método). @@ -90,16 +150,16 @@ Este método é útil para dependências opcionais que não são necessárias pa Ao mesmo tempo, este método permite que o setter seja chamado repetidamente para mudar a dependência. Se isto não for desejável, acrescente uma verificação ao método, ou a partir do PHP 8.1, marque a propriedade `$cache` com a bandeira `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ A chamada do setter é definida na configuração do recipiente DI na [configura ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Injeção de propriedade .[#toc-property-injection] As dependências são passadas diretamente para a propriedade: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Este método é considerado inadequado porque a propriedade deve ser declarada como `public`. Assim, não temos controle sobre se a dependência passada será realmente do tipo especificado (isto era verdade antes do PHP 7.4) e perdemos a capacidade de reagir à dependência recém-atribuída com nosso próprio código, por exemplo, para evitar mudanças subseqüentes. Ao mesmo tempo, a propriedade se torna parte da interface pública da classe, o que pode não ser desejável. @@ -137,12 +197,18 @@ A configuração da variável é definida na configuração do recipiente DI na ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Injetar .[#toc-inject] +====================== + +Enquanto os três métodos anteriores são geralmente válidos em todos os idiomas orientados a objetos, a injeção por método, a anotação ou o atributo *injet* é específico para os apresentadores Nette. Eles são discutidos em [um capítulo separado |best-practices:inject-method-attribute]. + + Qual a maneira de escolher? .[#toc-which-way-to-choose] ======================================================= diff --git a/dependency-injection/pt/services.texy b/dependency-injection/pt/services.texy index 571528ca23..d8f55bada1 100644 --- a/dependency-injection/pt/services.texy +++ b/dependency-injection/pt/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Modo injetado .[#toc-inject-mode] ================================= -A bandeira `inject: true` é usada para ativar a passagem de dependências através de variáveis públicas com a anotação de [injeção |best-practices:inject-method-attribute#Inject Annotations] e os métodos de [injeção*() |best-practices:inject-method-attribute#inject Methods]. +A bandeira `inject: true` é usada para ativar a passagem de dependências através de variáveis públicas com a anotação de [injeção |best-practices:inject-method-attribute#Inject Attributes] e os métodos de [injeção*() |best-practices:inject-method-attribute#inject Methods]. ```neon services: diff --git a/dependency-injection/ro/@home.texy b/dependency-injection/ro/@home.texy index 8df66d51f5..560356122d 100644 --- a/dependency-injection/ro/@home.texy +++ b/dependency-injection/ro/@home.texy @@ -5,8 +5,10 @@ Injectarea dependenței Injectarea dependenței este un model de proiectare care va schimba fundamental modul în care priviți codul și dezvoltarea. Acesta deschide calea către o lume a aplicațiilor cu design curat și sustenabil. - [Ce este injecția de dependență? |introduction] -- [Ce este DI Container? |container] +- [Starea globală și singletonii |global-state] - [Transmiterea dependențelor |passing-dependencies] +- [Ce este DI Container? |container] +- [Întrebări frecvente |faq] Nette DI diff --git a/dependency-injection/ro/@left-menu.texy b/dependency-injection/ro/@left-menu.texy index 8d0bd5b8f0..6708d7c8d7 100644 --- a/dependency-injection/ro/@left-menu.texy +++ b/dependency-injection/ro/@left-menu.texy @@ -1,8 +1,10 @@ Injectarea dependenței ********************** - [Ce este DI? |introduction] -- [Ce este DI Container? |container] +- [Stare globală și singletoni |global-state] - [Transmiterea dependențelor |passing-dependencies] +- [Ce este DI Container? |container] +- [Întrebări frecvente |faq] Nette DI diff --git a/dependency-injection/ro/faq.texy b/dependency-injection/ro/faq.texy new file mode 100644 index 0000000000..9d09d2e6fe --- /dev/null +++ b/dependency-injection/ro/faq.texy @@ -0,0 +1,112 @@ +Întrebări frecvente despre DI (FAQ) +*********************************** + + +Este DI un alt nume pentru IoC? .[#toc-is-di-another-name-for-ioc] +------------------------------------------------------------------ + +*Inversion of Control* (IoC) este un principiu care se concentrează asupra modului în care este executat codul - dacă codul dumneavoastră inițiază codul extern sau dacă codul dumneavoastră este integrat în codul extern, care îl apelează. +IoC este un concept larg care include [evenimente |nette:glossary#Events], așa-numitul [principiu Hollywood |application:components#Hollywood style] și alte aspecte. +Fabricile, care fac parte din [Regula nr. 3: Lasă fabrica să se ocupe |introduction#Rule #3: Let the Factory Handle It], și reprezintă inversiunea pentru operatorul `new`, sunt, de asemenea, componente ale acestui concept. + +*Dependency Injection* (DI) se referă la modul în care un obiect știe despre un alt obiect, adică la dependență. Este un model de proiectare care necesită transmiterea explicită a dependențelor între obiecte. + +Astfel, se poate spune că DI este o formă specifică de IoC. Cu toate acestea, nu toate formele de IoC sunt potrivite din punct de vedere al purității codului. De exemplu, printre antimodele, includem toate tehnicile care lucrează cu [starea globală |global state] sau așa-numitul [Service Locator |#What is a Service Locator]. + + +Ce este un Service Locator? .[#toc-what-is-a-service-locator] +------------------------------------------------------------- + +Un localizator de servicii este o alternativă la injecția de dependență. Funcționează prin crearea unui depozit central în care sunt înregistrate toate serviciile sau dependențele disponibile. Atunci când un obiect are nevoie de o dependență, o solicită de la Localizatorul de servicii. + +Cu toate acestea, în comparație cu injecția de dependență, pierde din transparență: dependențele nu sunt transmise direct obiectelor și, prin urmare, nu sunt ușor de identificat, ceea ce necesită examinarea codului pentru a descoperi și înțelege toate conexiunile. Testarea este, de asemenea, mai complicată, deoarece nu putem trece pur și simplu obiecte simulate către obiectele testate, ci trebuie să trecem prin intermediul Localizatorului de servicii. În plus, Service Locator perturbă proiectarea codului, deoarece obiectele individuale trebuie să fie conștiente de existența sa, ceea ce diferă de Dependency Injection, unde obiectele nu au cunoștință de containerul DI. + + +Când este mai bine să nu folosiți DI? .[#toc-when-is-it-better-not-to-use-di] +----------------------------------------------------------------------------- + +Nu există dificultăți cunoscute asociate cu utilizarea modelului de proiectare Dependency Injection. Dimpotrivă, obținerea dependențelor din locații accesibile la nivel global duce la [o serie de complicații |global-state], la fel ca și utilizarea unui Service Locator. +Prin urmare, este recomandabil să se utilizeze întotdeauna DI. Aceasta nu este o abordare dogmatică, ci pur și simplu nu s-a găsit o alternativă mai bună. + +Cu toate acestea, există anumite situații în care nu ne transmitem obiecte între noi și le obținem din spațiul global. De exemplu, atunci când se depanează codul și este nevoie să se descarce valoarea unei variabile într-un anumit punct al programului, să se măsoare durata unei anumite părți a programului sau să se înregistreze un mesaj. +În astfel de cazuri, în care este vorba despre acțiuni temporare care vor fi ulterior eliminate din cod, este legitim să se utilizeze un dumper, un cronometru sau un logger accesibil la nivel global. Aceste instrumente, la urma urmei, nu fac parte din proiectarea codului. + + +Utilizarea DI are dezavantajele sale? .[#toc-does-using-di-have-its-drawbacks] +------------------------------------------------------------------------------ + +Utilizarea Injecției de dependență implică dezavantaje, cum ar fi creșterea complexității scrierii codului sau o performanță mai slabă? Ce pierdem atunci când începem să scriem cod în conformitate cu DI? + +DI nu are niciun impact asupra performanței aplicației sau a cerințelor de memorie. Performanța containerului DI poate juca un rol, dar în cazul [Nette DI | nette-container], containerul este compilat în PHP pur, astfel că sarcina sa în timpul rulării aplicației este practic zero. + +La scrierea codului, este necesar să se creeze constructori care acceptă dependențe. În trecut, acest lucru putea consuma mult timp, dar datorită IDE-urilor moderne și [promovării proprietăților constructorilor |https://blog.nette.org/ro/php-8-0-prezentare-completa-a-noutatilor#toc-constructor-property-promotion], acum este o chestiune de câteva secunde. Constructorii pot fi generați cu ușurință folosind Nette DI și un plugin PhpStorm cu doar câteva clicuri. +Pe de altă parte, nu este nevoie să scrieți singletons și puncte de acces statice. + +Se poate concluziona că o aplicație proiectată corespunzător care utilizează DI nu este nici mai scurtă, nici mai lungă în comparație cu o aplicație care utilizează singletons. Părțile de cod care lucrează cu dependențele sunt pur și simplu extrase din clasele individuale și mutate în locații noi, adică în containerul DI și în fabrici. + + +Cum să rescrieți o aplicație moștenită în DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] +----------------------------------------------------------------------------------------------- + +Migrarea de la o aplicație moștenită la Injecția de dependență poate fi un proces dificil, în special pentru aplicațiile mari și complexe. Este important să abordați acest proces în mod sistematic. + +- Atunci când se trece la Injectarea dependenței, este important ca toți membrii echipei să înțeleagă principiile și practicile utilizate. +- În primul rând, efectuați o analiză a aplicației existente pentru a identifica componentele cheie și dependențele acestora. Creați un plan pentru care părți vor fi refactorizate și în ce ordine. +- Implementați un container DI sau, și mai bine, utilizați o bibliotecă existentă, cum ar fi Nette DI. +- Refaceți treptat fiecare parte a aplicației pentru a utiliza Injecția de dependență. Acest lucru poate implica modificarea constructorilor sau a metodelor pentru a accepta dependențele ca parametri. +- Modificați locurile din cod în care sunt create obiecte de dependență, astfel încât dependențele să fie injectate de container. Acest lucru poate include utilizarea fabricilor. + +Nu uitați că trecerea la Injectarea dependențelor este o investiție în calitatea codului și în durabilitatea pe termen lung a aplicației. Deși poate fi o provocare să faceți aceste schimbări, rezultatul ar trebui să fie un cod mai curat, mai modular și ușor de testat, pregătit pentru extinderi și întreținere viitoare. + + +De ce este preferată compoziția în locul moștenirii? .[#toc-why-composition-is-preferred-over-inheritance] +---------------------------------------------------------------------------------------------------------- +Este preferabilă utilizarea compoziției în locul moștenirii, deoarece servește scopului de reutilizare a codului, fără a fi nevoie să vă faceți griji cu privire la efectul de schimbare. Astfel, se asigură o cuplare mai lejeră, în care nu trebuie să ne facem griji că modificarea unui cod ar putea duce la modificarea unui alt cod dependent. Un exemplu tipic este situația identificată drept [iadul constructorilor |passing-dependencies#Constructor hell]. + + +Poate fi utilizat Nette DI Container în afara Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] +------------------------------------------------------------------------------------------------------------ + +Absolut. Nette DI Container face parte din Nette, dar este conceput ca o bibliotecă autonomă care poate fi utilizată independent de alte părți ale cadrului. Trebuie doar să o instalați folosind Composer, să creați un fișier de configurare care să definească serviciile dvs. și apoi să folosiți câteva linii de cod PHP pentru a crea containerul DI. +Și puteți începe imediat să profitați de Injecția de dependență în proiectele dumneavoastră. + +Capitolul [Nette DI Container |nette-container] descrie cum arată un caz de utilizare specific, inclusiv codul. + + +De ce este configurația în fișiere NEON? .[#toc-why-is-the-configuration-in-neon-files] +--------------------------------------------------------------------------------------- + +NEON este un limbaj de configurare simplu și ușor de citit, dezvoltat în cadrul Nette pentru configurarea aplicațiilor, a serviciilor și a dependențelor acestora. În comparație cu JSON sau YAML, acesta oferă opțiuni mult mai intuitive și mai flexibile în acest scop. În NEON, puteți descrie în mod natural legături care nu ar fi posibil de scris în Symfony & YAML, fie deloc, fie doar printr-o descriere complexă. + + +Analiza fișierelor NEON încetinește aplicația? .[#toc-does-parsing-neon-files-slow-down-the-application] +-------------------------------------------------------------------------------------------------------- + +Deși fișierele NEON sunt analizate foarte rapid, acest aspect nu contează cu adevărat. Motivul este că analiza fișierelor are loc o singură dată în timpul primei lansări a aplicației. După aceea, codul containerului DI este generat, stocat pe disc și executat pentru fiecare solicitare ulterioară, fără a mai fi nevoie de o analiză suplimentară. + +Acesta este modul în care funcționează într-un mediu de producție. În timpul dezvoltării, fișierele NEON sunt analizate de fiecare dată când conținutul lor se modifică, asigurându-se că dezvoltatorul are întotdeauna un container DI actualizat. După cum am menționat mai devreme, parsarea efectivă este o chestiune de o clipă. + + +Cum pot accesa parametrii din fișierul de configurare în clasa mea? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +-------------------------------------------------------------------------------------------------------------------------------------------------- + +Rețineți [regula nr. 1: Lăsați să vă fie transmisă |introduction#Rule #1: Let It Be Passed to You]. Dacă o clasă necesită informații dintr-un fișier de configurare, nu trebuie să ne dăm seama cum să accesăm aceste informații; în schimb, pur și simplu le cerem - de exemplu, prin intermediul constructorului clasei. Și efectuăm transmiterea în fișierul de configurare. + +În acest exemplu, `%myParameter%` este un spațiu rezervat pentru valoarea parametrului `myParameter`, care va fi transmis constructorului `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Dacă doriți să transmiteți mai mulți parametri sau să utilizați autowiring, este util să includeți [parametrii într-un obiect |best-practices:passing-settings-to-presenters]. + + +Nette acceptă interfața PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] +----------------------------------------------------------------------------------------------- + +Nette DI Container nu acceptă direct PSR-11. Cu toate acestea, dacă aveți nevoie de interoperabilitate între containerul Nette DI și bibliotecile sau cadrele care așteaptă interfața PSR-11 Container, puteți crea un [adaptor simplu |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] care să servească drept punte între containerul Nette DI și PSR-11. diff --git a/dependency-injection/ro/global-state.texy b/dependency-injection/ro/global-state.texy new file mode 100644 index 0000000000..d408ba28fe --- /dev/null +++ b/dependency-injection/ro/global-state.texy @@ -0,0 +1,312 @@ +Starea globală și singletoni +**************************** + +.[perex] +Atenție: următoarele construcții sunt simptome ale unei proiectări proaste a codului: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` sau `static::$var` + +Apare vreuna dintre aceste construcții în codul dumneavoastră? Atunci aveți ocazia de a vă îmbunătăți. Poate că vă gândiți că acestea sunt construcții comune pe care le vedem în soluțiile de exemplu ale diferitelor biblioteci și cadre. +Din nefericire, ele reprezintă totuși un indicator clar al unei proiectări deficitare. Ele au un lucru în comun: utilizarea stării globale. + +Acum, cu siguranță nu vorbim despre un fel de puritate academică. Utilizarea stării globale și a singletonilor are efecte distructive asupra calității codului. Comportamentul său devine imprevizibil, reduce productivitatea dezvoltatorilor și forțează interfețele de clasă să mintă în legătură cu adevăratele lor dependențe. Ceea ce îi derutează pe programatori. + +În acest capitol, vom arăta cum este posibil acest lucru. + + +Interconectarea globală .[#toc-global-interlinking] +--------------------------------------------------- + +Problema fundamentală a statului global este că acesta este accesibil la nivel global. Acest lucru face posibilă scrierea în baza de date prin intermediul metodei globale (statice) `DB::insert()`. +Într-o lume ideală, un obiect ar trebui să poată comunica doar cu alte obiecte care [i-au fost transmise direct |passing-dependencies]. +Dacă creez două obiecte `A` și `B` și nu transmit niciodată o referință de la `A` la `B`, atunci nici `A`, nici `B` nu pot accesa celălalt obiect sau schimba starea acestuia. +Aceasta este o caracteristică foarte dorită a codului. Este similar cu a avea o baterie și un bec; becul nu se va aprinde până când nu le conectați împreună. + +Acest lucru nu este valabil pentru variabilele globale (statice) sau singletone. Obiectul `A` ar putea să acceseze *fără fir* obiectul `C` și să îl modifice fără a trece nicio referință, prin apelarea `C::changeSomething()`. +Dacă obiectul `B` prinde și obiectul global `C`, atunci `A` și `B` pot interacționa unul cu celălalt prin intermediul `C`. + +Utilizarea variabilelor globale introduce o nouă formă de cuplare *fără fir* în sistem, care nu este vizibilă din exterior. +Aceasta creează o perdea de fum care complică înțelegerea și utilizarea codului. +Dezvoltatorii trebuie să citească fiecare linie de cod sursă pentru a înțelege cu adevărat dependențele. În loc să se familiarizeze doar cu interfața claselor. +În plus, este un cuplaj complet inutil. + +.[note] +În ceea ce privește comportamentul, nu există nicio diferență între o variabilă globală și una statică. Ele sunt la fel de dăunătoare. + + +Acțiunea înfricoșătoare la distanță .[#toc-the-spooky-action-at-a-distance] +--------------------------------------------------------------------------- + +"Acțiunea ciudată la distanță" - așa a numit Albert Einstein un fenomen din fizica cuantică care i-a dat fiori în 1935. +Este vorba despre entanglarea cuantică, a cărei particularitate este că atunci când măsori informații despre o particulă, afectezi imediat o altă particulă, chiar dacă acestea se află la milioane de ani lumină distanță. +Ceea ce aparent încalcă legea fundamentală a universului conform căreia nimic nu poate călători mai repede decât lumina. + +În lumea software-ului, putem numi "acțiune fantomatică la distanță" o situație în care rulăm un proces pe care îl considerăm izolat (deoarece nu i-am transmis nicio referință), dar interacțiuni neașteptate și schimbări de stare au loc în locații îndepărtate ale sistemului, despre care nu am informat obiectul. Acest lucru se poate întâmpla numai prin intermediul stării globale. + +Imaginați-vă că vă alăturați unei echipe de dezvoltare a unui proiect care are o bază de cod mare și matură. Noul dvs. șef vă cere să implementați o nouă caracteristică și, ca un bun dezvoltator, începeți prin a scrie un test. Dar, pentru că sunteți nou în proiect, faceți o mulțime de teste exploratorii de tipul "ce se întâmplă dacă apelez această metodă". Și încercați să scrieți următorul test: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // numărul cardului dvs. + $cc->charge(100); +} +``` + +Rulați codul, poate de mai multe ori, și după un timp observați notificări pe telefon de la bancă care vă anunță că, de fiecare dată când îl executați, 100 de dolari au fost debitați de pe cardul dvs. de credit 🤦‍♂️. + +Cum naiba a putut testul să provoace o încărcare reală? Nu este ușor de operat cu cardul de credit. Trebuie să interacționezi cu un serviciu web terț, trebuie să cunoști URL-ul acelui serviciu web, trebuie să te loghezi și așa mai departe. +Niciuna dintre aceste informații nu este inclusă în test. Chiar mai rău, nici măcar nu știți unde sunt prezente aceste informații și, prin urmare, nu știți cum să vă bateți joc de dependențele externe, astfel încât fiecare execuție să nu ducă la o nouă taxare de 100 de dolari. Și, în calitate de dezvoltator nou, de unde să știi că ceea ce urma să faci te va duce la o sărăcie de 100 de dolari? + +Aceasta este o acțiune înfricoșătoare la distanță! + +Nu ai altă soluție decât să scotocești prin mult cod sursă, întrebând colegi mai vechi și mai experimentați, până când înțelegi cum funcționează conexiunile din proiect. +Acest lucru se datorează faptului că, atunci când vă uitați la interfața clasei `CreditCard`, nu puteți determina starea globală care trebuie inițializată. Nici măcar dacă vă uitați la codul sursă al clasei nu vă va spune ce metodă de inițializare trebuie să apelați. În cel mai bun caz, puteți găsi variabila globală care este accesată și încercați să ghiciți cum să o inițializați pornind de la aceasta. + +Clasele dintr-un astfel de proiect sunt niște mincinoși patologici. Cardul de plată pretinde că puteți pur și simplu să îl instanți și să apelați metoda `charge()`. Cu toate acestea, ea interacționează în secret cu o altă clasă, `PaymentGateway`. Chiar și interfața sa spune că poate fi inițializată în mod independent, dar în realitate trage acreditările dintr-un fișier de configurare și așa mai departe. +Este clar pentru dezvoltatorii care au scris acest cod că `CreditCard` are nevoie de `PaymentGateway`. Aceștia au scris codul în acest fel. Dar pentru oricine este nou în proiect, acest lucru este un mister complet și împiedică învățarea. + +Cum se poate remedia situația? Ușor. **Lasă API-ul să declare dependențele.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Observați cum relațiile din cadrul codului sunt brusc evidente. Declarând că metoda `charge()` are nevoie de `PaymentGateway`, nu mai trebuie să întrebați pe nimeni cum este interdependent codul. Știți că trebuie să creați o instanță a acesteia, iar când încercați să faceți acest lucru, vă loviți de faptul că trebuie să furnizați parametri de acces. Fără aceștia, codul nici măcar nu ar funcționa. + +Și, cel mai important, acum puteți să vă bateți joc de gateway-ul de plată, astfel încât să nu fiți taxat cu 100 de dolari de fiecare dată când executați un test. + +Starea globală face ca obiectele dvs. să poată accesa în secret lucruri care nu sunt declarate în API-urile lor și, ca urmare, face ca API-urile dvs. să fie mincinoase patologice. + +Poate că nu v-ați gândit la asta până acum, dar ori de câte ori folosiți starea globală, creați canale secrete de comunicare fără fir. Acțiunile înfiorătoare de la distanță îi obligă pe dezvoltatori să citească fiecare linie de cod pentru a înțelege interacțiunile potențiale, reduc productivitatea dezvoltatorilor și îi derutează pe noii membri ai echipei. +Dacă tu ești cel care a creat codul, cunoști dependențele reale, dar oricine vine după tine nu știe nimic. + +Nu scrieți cod care utilizează starea globală, preferați să treceți dependențele. Adică injectarea dependențelor. + + +Bătălia statului global .[#toc-brittleness-of-the-global-state] +--------------------------------------------------------------- + +În codul care utilizează starea globală și singletonii, nu este niciodată sigur când și de către cine a fost schimbată acea stare. Acest risc este deja prezent la inițializare. Următorul cod ar trebui să creeze o conexiune la baza de date și să inițializeze gateway-ul de plată, dar continuă să arunce o excepție, iar găsirea cauzei este extrem de anevoioasă: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Trebuie să parcurgeți codul în detaliu pentru a descoperi că obiectul `PaymentGateway` accesează alte obiecte fără fir, dintre care unele necesită o conexiune la baza de date. Astfel, trebuie să inițializați baza de date înainte de `PaymentGateway`. Cu toate acestea, perdeaua de fum a statului global vă ascunde acest lucru. Cât timp ați economisi dacă API-ul fiecărei clase nu ar minți și nu și-ar declara dependențele? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +O problemă similară apare atunci când se utilizează accesul global la o conexiune la o bază de date: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Atunci când se apelează metoda `save()`, nu se știe cu siguranță dacă a fost deja creată o conexiune la baza de date și cine este responsabil pentru crearea acesteia. De exemplu, dacă am dori să modificăm din mers conexiunea la baza de date, poate în scopuri de testare, probabil că ar trebui să creăm metode suplimentare, cum ar fi `DB::reconnect(...)` sau `DB::reconnectForTest()`. + +Luați în considerare un exemplu: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +De unde putem fi siguri că baza de date de testare este într-adevăr utilizată atunci când apelăm `$article->save()`? Ce se întâmplă dacă metoda `Foo::doSomething()` a schimbat conexiunea globală la baza de date? Pentru a afla, ar trebui să examinăm codul sursă al clasei `Foo` și, probabil, al multor alte clase. Cu toate acestea, această abordare ar oferi doar un răspuns pe termen scurt, deoarece situația se poate schimba în viitor. + +Ce se întâmplă dacă mutăm conexiunea la baza de date într-o variabilă statică în interiorul clasei `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Acest lucru nu schimbă absolut nimic. Problema este o stare globală și nu contează în ce clasă se ascunde. În acest caz, ca și în cel precedent, nu avem niciun indiciu cu privire la baza de date în care se scrie atunci când este apelată metoda `$article->save()`. Oricine aflat la capătul îndepărtat al aplicației ar putea schimba baza de date în orice moment folosind `Article::setDb()`. În mâinile noastre. + +Starea globală face ca aplicația noastră să fie **extrem de fragilă**. + +Cu toate acestea, există o modalitate simplă de a rezolva această problemă. Este suficient ca API-ul să declare dependențele pentru a asigura o funcționalitate corespunzătoare. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Această abordare elimină grija modificărilor ascunse și neașteptate ale conexiunilor la baza de date. Acum suntem siguri unde este stocat articolul și nicio modificare de cod în interiorul unei alte clase fără legătură nu mai poate schimba situația. Codul nu mai este fragil, ci stabil. + +Nu scrieți cod care utilizează starea globală, preferați să treceți dependențele. Astfel, injecția de dependențe. + + +Singleton .[#toc-singleton] +--------------------------- + +Singleton este un model de proiectare care, prin [definiția |https://en.wikipedia.org/wiki/Singleton_pattern] din celebra publicație Gang of Four, limitează o clasă la o singură instanță și oferă acces global la aceasta. Implementarea acestui model seamănă, de obicei, cu următorul cod: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // și alte metode care îndeplinesc funcțiile clasei +} +``` + +Din păcate, singletonul introduce o stare globală în aplicație. Și, după cum am arătat mai sus, starea globală nu este de dorit. De aceea, singletonul este considerat un antipattern. + +Nu folosiți singletonii în codul dvs. și înlocuiți-i cu alte mecanisme. Chiar nu aveți nevoie de singletons. Cu toate acestea, dacă trebuie să garantați existența unei singure instanțe a unei clase pentru întreaga aplicație, lăsați acest lucru în seama [containerului DI |container]. +Astfel, creați un singleton de aplicație, sau serviciu. Acest lucru va împiedica clasa să își asigure propria unicitate (adică nu va avea o metodă `getInstance()` și o variabilă statică) și își va îndeplini doar funcțiile. Astfel, nu va mai încălca principiul responsabilității unice. + + +Starea globală față de teste .[#toc-global-state-versus-tests] +-------------------------------------------------------------- + +Atunci când scriem teste, presupunem că fiecare test este o unitate izolată și că nicio stare externă nu intră în el. Și nicio stare nu părăsește testele. Atunci când un test se finalizează, orice stare asociată cu testul ar trebui să fie eliminată automat de către garbage collector. Acest lucru face ca testele să fie izolate. Prin urmare, putem rula testele în orice ordine. + +Cu toate acestea, dacă sunt prezente stări globale/singletele globale, toate aceste presupuneri frumoase se prăbușesc. O stare poate intra și ieși dintr-un test. Dintr-o dată, ordinea testelor poate conta. + +Pentru a testa singletonii, dezvoltatorii trebuie adesea să relaxeze proprietățile acestora, poate permițând ca o instanță să fie înlocuită cu alta. Astfel de soluții sunt, în cel mai bun caz, hack-uri care produc un cod dificil de întreținut și de înțeles. Orice test sau metodă `tearDown()` care afectează orice stare globală trebuie să anuleze aceste modificări. + +Starea globală este cea mai mare bătaie de cap în testarea unitară! + +Cum se poate remedia situația? Ușor. Nu scrieți cod care folosește singletoni, preferați să treceți dependențele. Adică injecția de dependență. + + +Constante globale .[#toc-global-constants] +------------------------------------------ + +Starea globală nu se limitează la utilizarea singletonilor și a variabilelor statice, ci se poate aplica și constantelor globale. + +Constantele a căror valoare nu ne furnizează informații noi (`M_PI`) sau utile (`PREG_BACKTRACK_LIMIT_ERROR`) sunt în mod clar în regulă. +Dimpotrivă, constantele care servesc drept modalitate de a transmite *fără fir* informații în interiorul codului nu sunt altceva decât o dependență ascunsă. Cum ar fi `LOG_FILE` din exemplul următor. +Utilizarea constantei `FILE_APPEND` este perfect corectă. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +În acest caz, ar trebui să declarăm parametrul în constructorul clasei `Foo` pentru a-l face parte din API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Acum putem transmite informații despre calea către fișierul de logare și o putem modifica cu ușurință, după cum este necesar, facilitând testarea și întreținerea codului. + + +Funcții globale și metode statice .[#toc-global-functions-and-static-methods] +----------------------------------------------------------------------------- + +Dorim să subliniem faptul că utilizarea metodelor statice și a funcțiilor globale nu este în sine problematică. Am explicat caracterul nepotrivit al utilizării `DB::insert()` și a metodelor similare, dar întotdeauna a fost vorba de starea globală stocată într-o variabilă statică. Metoda `DB::insert()` necesită existența unei variabile statice, deoarece stochează conexiunea la baza de date. Fără această variabilă, ar fi imposibil de implementat metoda. + +Utilizarea metodelor și funcțiilor statice deterministe, cum ar fi `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` și multe altele, este perfect coerentă cu injecția de dependență. Aceste funcții returnează întotdeauna aceleași rezultate la aceiași parametri de intrare și, prin urmare, sunt previzibile. Ele nu utilizează nicio stare globală. + +Cu toate acestea, există funcții în PHP care nu sunt deterministe. Printre acestea se numără, de exemplu, funcția `htmlspecialchars()`. Cel de-al treilea parametru al acesteia, `$encoding`, dacă nu este specificat, este valoarea implicită a opțiunii de configurare `ini_get('default_charset')`. Prin urmare, se recomandă să specificați întotdeauna acest parametru pentru a evita un eventual comportament imprevizibil al funcției. Nette face acest lucru în mod constant. + +Unele funcții, cum ar fi `strtolower()`, `strtoupper()`, și altele similare, au avut un comportament nedeterminist în trecutul recent și au depins de setarea `setlocale()`. Acest lucru a cauzat multe complicații, cel mai adesea atunci când se lucra cu limba turcă. +Acest lucru se datorează faptului că limba turcă face distincție între majuscule și minuscule `I` cu și fără punct. Astfel, `strtolower('I')` returna caracterul `ı`, iar `strtoupper('i')` returna caracterul `İ`, ceea ce a dus la aplicații care provocau o serie de erori misterioase. +Cu toate acestea, această problemă a fost rezolvată în versiunea 8.2 a PHP, iar funcțiile nu mai depind de locale. + +Acesta este un exemplu frumos al modului în care statul global a afectat mii de dezvoltatori din întreaga lume. Soluția a fost înlocuirea acesteia cu injecția de dependență. + + +Când este posibil să se utilizeze statul global? .[#toc-when-is-it-possible-to-use-global-state] +------------------------------------------------------------------------------------------------ + +Există anumite situații specifice în care este posibil să se utilizeze starea globală. De exemplu, atunci când depanați codul și trebuie să descărcați valoarea unei variabile sau să măsurați durata unei anumite părți a programului. În astfel de cazuri, care se referă la acțiuni temporare care vor fi ulterior eliminate din cod, este legitim să se utilizeze un dumper sau un cronometru disponibil la nivel global. Aceste instrumente nu fac parte din proiectarea codului. + +Un alt exemplu este reprezentat de funcțiile de lucru cu expresii regulate `preg_*`, care stochează intern expresiile regulate compilate într-o memorie cache statică în memorie. Atunci când apelați aceeași expresie regulată de mai multe ori în diferite părți ale codului, aceasta este compilată o singură dată. Memoria cache economisește performanță și este, de asemenea, complet invizibilă pentru utilizator, astfel încât o astfel de utilizare poate fi considerată legitimă. + + +Rezumat .[#toc-summary] +----------------------- + +Am arătat de ce are sens + +1) Să eliminăm toate variabilele statice din cod +2) Declarați dependențele +3) Și folosiți injectarea dependențelor + +Atunci când vă gândiți la proiectarea codului, nu uitați că fiecare `static $foo` reprezintă o problemă. Pentru ca codul dumneavoastră să fie un mediu care respectă DI, este esențial să eradicați complet starea globală și să o înlocuiți cu injecția de dependență. + +În timpul acestui proces, s-ar putea să descoperiți că trebuie să divizați o clasă deoarece aceasta are mai multe responsabilități. Nu vă faceți griji în această privință; străduiți-vă să respectați principiul unei singure responsabilități. + +*Doresc să îi mulțumesc lui Miško Hevery, ale cărui articole, precum [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], constituie baza acestui capitol.* diff --git a/dependency-injection/ro/introduction.texy b/dependency-injection/ro/introduction.texy index cebd6763f7..6b5fb2ed25 100644 --- a/dependency-injection/ro/introduction.texy +++ b/dependency-injection/ro/introduction.texy @@ -2,17 +2,17 @@ Ce este Injecția de dependență? ******************************* .[perex] -Acest capitol vă prezintă practicile de programare de bază pe care ar trebui să le urmați atunci când scrieți orice aplicație. Acestea sunt elementele de bază necesare pentru a scrie un cod curat, ușor de înțeles și de întreținut. +Acest capitol vă va prezenta practicile de programare de bază pe care trebuie să le urmați atunci când scrieți orice aplicație. Acestea sunt elementele fundamentale necesare pentru a scrie un cod curat, ușor de înțeles și de întreținut. -Dacă învățați și respectați aceste reguli, Nette vă va fi alături la fiecare pas. Se va ocupa de sarcinile de rutină în locul dumneavoastră și vă va face să vă simțiți cât mai confortabil posibil, astfel încât să vă puteți concentra pe logica propriu-zisă. +Dacă învățați și urmați aceste reguli, Nette vă va fi alături la fiecare pas. Se va ocupa de sarcinile de rutină în locul dumneavoastră și vă va oferi un confort maxim, astfel încât să vă puteți concentra asupra logicii propriu-zise. -Principiile pe care le vom arăta aici sunt destul de simple. Nu aveți de ce să vă faceți griji. +Principiile pe care le vom arăta aici sunt destul de simple. Nu trebuie să vă faceți griji pentru nimic. Vă amintiți primul program? .[#toc-remember-your-first-program] --------------------------------------------------------------- -Nu avem nicio idee în ce limbaj l-ați scris, dar dacă era PHP, probabil că ar fi arătat cam așa: +Nu știm în ce limbaj ați scris-o, dar dacă a fost PHP, ar fi putut arăta cam așa: ```php function addition(float $a, float $b): float @@ -25,31 +25,31 @@ echo addition(23, 1); // amprente 24 Câteva linii de cod banale, dar atât de multe concepte cheie ascunse în ele. Că există variabile. Că codul este împărțit în unități mai mici, care sunt funcții, de exemplu. Că le trecem argumente de intrare și că ele returnează rezultate. Tot ceea ce lipsește sunt condițiile și buclele. -Faptul că trecem date de intrare unei funcții și că aceasta returnează un rezultat este un concept perfect de înțeles, care este folosit și în alte domenii, cum ar fi matematica. +Faptul că o funcție primește date de intrare și returnează un rezultat este un concept perfect inteligibil, care este utilizat și în alte domenii, cum ar fi matematica. -O funcție are o semnătură, care constă din numele său, o listă de parametri și tipurile acestora și, în final, tipul valorii de retur. În calitate de utilizatori, suntem interesați de semnătură; de obicei, nu trebuie să știm nimic despre implementarea internă. +O funcție are o semnătură, care constă în numele său, o listă de parametri și tipurile acestora și, în final, tipul valorii de returnare. În calitate de utilizatori, suntem interesați de semnătură și, de obicei, nu trebuie să știm nimic despre implementarea internă. -Acum imaginați-vă că semnătura unei funcții arată astfel: +Acum imaginați-vă că semnătura funcției arată astfel: ```php function addition(float $x): float ``` -O adunare cu un singur parametru? Asta e ciudat... Ce zici de asta? +O adiție cu un singur parametru? Asta e ciudat... Ce zici de asta? ```php function addition(): float ``` -E foarte ciudat, nu-i așa? Cum credeți că este folosită această funcție? +Asta e foarte ciudat, nu? Cum este folosită funcția? ```php echo addition(); // ce imprimă? ``` -Privind un astfel de cod, suntem confuzi. Nu doar un începător nu l-ar înțelege, ci chiar și un programator experimentat nu ar înțelege un astfel de cod. +Privind un astfel de cod, am fi confuzi. Nu numai că un începător nu l-ar înțelege, dar nici măcar un programator experimentat nu ar înțelege un astfel de cod. -Vă întrebați cum ar arăta de fapt o astfel de funcție în interior? De unde ar lua sumatorii? Probabil că le-ar obține *într-un fel* de una singură, astfel: +Vă întrebați cum ar arăta de fapt o astfel de funcție în interior? De unde ar lua summenele? Probabil că le-ar obține *într-un fel* de la sine, poate în felul următor: ```php function addition(): float @@ -66,13 +66,13 @@ Se pare că există legături ascunse cu alte funcții (sau metode statice) în Nu pe aici! .[#toc-not-this-way] -------------------------------- -Designul care tocmai ne-a fost prezentat este esența multor caracteristici negative: +Designul pe care tocmai l-am arătat este esența multor caracteristici negative: -- semnătura funcției pretindea că nu are nevoie de adaosuri, ceea ce ne-a derutat. -- nu avem nicio idee despre cum să facem funcția să calculeze cu alte două numere -- a trebuit să ne uităm în cod pentru a vedea unde ia adunările -- am descoperit legături ascunse -- pentru a înțelege pe deplin, trebuie să explorăm și aceste legături +- semnătura funcției se pretindea că nu are nevoie de sumanți, ceea ce ne-a derutat. +- nu avem nicio idee cum să facem funcția să calculeze cu alte două numere +- a trebuit să ne uităm în cod pentru a afla de unde provin sumatorii +- am găsit dependențe ascunse +- pentru o înțelegere completă este necesar să examinăm și aceste dependențe Și chiar este treaba funcției de adunare să procure intrări? Bineînțeles că nu este. Responsabilitatea sa este doar de a adăuga. @@ -93,20 +93,20 @@ Regula nr. 1: Lăsați să vi se transmită .[#toc-rule-1-let-it-be-passed-to-yo Cea mai importantă regulă este: **toate datele de care au nevoie funcțiile sau clasele trebuie să le fie transmise**. -În loc să inventați mecanisme ascunse pentru a le ajuta să ajungă singure la ele, pur și simplu treceți parametrii. Veți economisi timpul necesar pentru a inventa modalități ascunse, care cu siguranță nu vă vor îmbunătăți codul. +În loc să inventați modalități ascunse de accesare a datelor de către ei înșiși, pur și simplu transmiteți parametrii. Veți economisi timp pe care l-ați petrece inventând căi ascunse care cu siguranță nu vă vor îmbunătăți codul. -Dacă respectați această regulă întotdeauna și peste tot, sunteți pe drumul către un cod fără legături ascunse. Spre un cod care să fie ușor de înțeles nu doar pentru autor, ci și pentru oricine îl citește ulterior. Unde totul este de înțeles din semnăturile funcțiilor și claselor și nu este nevoie să căutați secrete ascunse în implementare. +Dacă urmați întotdeauna și peste tot această regulă, sunteți pe drumul către un cod fără dependențe ascunse. Spre un cod care este inteligibil nu numai pentru autor, ci și pentru oricine îl citește ulterior. În care totul este de înțeles din semnăturile funcțiilor și claselor și nu este nevoie să căutați secrete ascunse în implementare. -Această tehnică se numește în mod specializat **injecție de dependență**. Iar datele se numesc **dependențe.** Dar este o simplă trecere de parametri, nimic mai mult. +Această tehnică se numește în mod profesional **injecție de dependență**. Iar aceste date se numesc **dependențe**. Este doar o simplă trecere de parametri obișnuită, nimic mai mult. .[note] -Vă rugăm să nu confundați injecția de dependență, care este un model de proiectare, cu "containerul de injecție de dependență", care este un instrument, ceva complet diferit. Vom discuta despre containere mai târziu. +Vă rugăm să nu confundați injectarea dependențelor, care este un model de proiectare, cu un "container de injectare a dependențelor", care este un instrument, ceva diametral diferit. Ne vom ocupa de containere mai târziu. De la funcții la clase .[#toc-from-functions-to-classes] -------------------------------------------------------- -Și cum se leagă clasele de acest lucru? O clasă este o entitate mai complexă decât o simplă funcție, dar regula nr. 1 se aplică și aici. Există doar [mai multe moduri de a transmite argumente |passing-dependencies]. De exemplu, destul de asemănător cu cazul unei funcții: +Și cum sunt legate clasele? O clasă este o unitate mai complexă decât o simplă funcție, dar regula nr. 1 se aplică în întregime și aici. Există doar [mai multe moduri de a transmite argumente |passing-dependencies]. De exemplu, destul de asemănător cu cazul unei funcții: ```php class Math @@ -121,7 +121,7 @@ $math = new Math; echo $math->addition(23, 1); // 24 ``` -Sau prin utilizarea altor metode, sau direct prin constructor: +Sau prin alte metode, sau direct prin constructor: ```php class Addition @@ -149,9 +149,9 @@ Ambele exemple sunt în deplină conformitate cu injecția de dependență. Exemple din viața reală .[#toc-real-life-examples] -------------------------------------------------- -În lumea reală, nu veți scrie clase pentru adunarea numerelor. Să trecem la exemple din lumea reală. +În lumea reală, nu veți scrie cursuri pentru adunarea numerelor. Să trecem la exemple practice. -Să avem o clasă `Article` care să reprezinte un articol de blog: +Să avem o clasă `Article` care reprezintă o postare pe blog: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Metoda `save()` salvează articolul într-un tabel din baza de date. Implementarea acesteia folosind [Nette Database |database:] ar fi floare la ureche, dacă nu ar exista o singură problemă: de unde ar trebui ca `Article` să obțină conexiunea la baza de date, adică obiectul de clasă `Nette\Database\Connection`? +Metoda `save()` va salva articolul într-un tabel din baza de date. Implementarea acesteia folosind [Nette Database |database:] va fi floare la ureche, dacă nu ar exista o singură problemă: de unde obține `Article` conexiunea la baza de date, adică un obiect din clasa `Nette\Database\Connection`? -Se pare că avem o mulțime de opțiuni. Poate să o ia de undeva dintr-o variabilă statică. Sau să o moștenească de la o clasă care va furniza conexiunea la baza de date. Sau să profite de un [singleton |global-state#Singleton]. Sau de așa-numitele fațade care sunt folosite în Laravel: +Se pare că avem o mulțime de opțiuni. Poate să o ia de la o variabilă statică undeva. Sau să moștenească dintr-o clasă care oferă o conexiune la baza de date. Sau să profite de un [singleton |global-state#Singleton]. Sau să folosească așa-numitele facade, care sunt folosite în Laravel: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ Minunat, am rezolvat problema. Sau am făcut-o? -Să ne amintim [regula nr. 1: Lăsați să vi se transmită |#rule #1: Let It Be Passed to You]: toate dependențele de care clasa are nevoie trebuie să îi fie transmise. Pentru că, dacă nu o facem și încălcăm regula, am pornit pe calea unui cod murdar, plin de legături ascunse, incomprehensibil, iar rezultatul va fi o aplicație care va fi o pacoste de întreținut și dezvoltat. +Să ne amintim [regula nr. 1: Să ți |#rule #1: Let It Be Passed to You] se transmită: toate dependențele de care clasa are nevoie trebuie să îi fie transmise. Pentru că, dacă încălcăm regula, am pornit pe drumul spre un cod murdar, plin de dependențe ascunse, incomprehensibil, iar rezultatul va fi o aplicație care va fi dureros de întreținut și dezvoltat. -Utilizatorul clasei `Article` nu are nicio idee despre locul în care metoda `save()` stochează articolul. Într-un tabel din baza de date? În care, în cea de producție sau în cea de dezvoltare? Și cum poate fi schimbat acest lucru? +Utilizatorul clasei `Article` nu are nicio idee despre locul în care metoda `save()` stochează articolul. Într-un tabel din baza de date? În care, în cea de producție sau în cea de testare? Și cum poate fi modificat? -Utilizatorul trebuie să se uite la modul în care este implementată metoda `save()` pentru a găsi utilizarea metodei `DB::insert()`. Astfel, el trebuie să caute mai departe pentru a afla cum această metodă procură o conexiune la baza de date. Iar legăturile ascunse pot forma un lanț destul de lung. +Utilizatorul trebuie să se uite la modul în care este implementată metoda `save()` și găsește utilizarea metodei `DB::insert()`. Deci, el trebuie să caute mai departe pentru a afla cum obține această metodă o conexiune la baza de date. Iar dependențele ascunse pot forma un lanț destul de lung. -Legăturile ascunse, fațadele Laravel sau variabilele statice nu sunt niciodată prezente în codul curat și bine conceput. În codul curat și bine conceput, argumentele sunt transmise: +În codul curat și bine conceput, nu există niciodată dependențe ascunse, fațade Laravel sau variabile statice. În codul curat și bine conceput, argumentele sunt transmise: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Chiar mai practic, așa cum vom vedea în continuare, este să se folosească un constructor: +O abordare și mai practică, după cum vom vedea mai târziu, va fi prin intermediul constructorului: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Dacă sunteți un programator experimentat, probabil că vă gândiți că `Article` nu ar trebui să aibă o metodă `save()`, ci ar trebui să fie o componentă de date pură, iar de stocare ar trebui să se ocupe un depozit separat. Acest lucru are sens. Dar acest lucru ne-ar duce mult dincolo de subiect, care este injecția de dependență, și încercarea de a da exemple simple. +Dacă sunteți un programator experimentat, ați putea crede că `Article` nu ar trebui să aibă o metodă `save()`; ar trebui să reprezinte o componentă pur de date, iar un depozit separat ar trebui să se ocupe de salvare. Acest lucru are sens. Dar acest lucru ne-ar duce cu mult dincolo de scopul subiectului, care este injectarea dependențelor, și de efortul de a oferi exemple simple. -Dacă aveți de gând să scrieți o clasă care are nevoie de o bază de date pentru a funcționa, de exemplu, nu vă gândiți de unde să o obțineți, ci să vă fie transmisă. Poate ca un parametru al unui constructor sau al unei alte metode. Declarați dependențele. Expuneți-le în API-ul clasei dumneavoastră. Veți obține un cod ușor de înțeles și previzibil. +Dacă scrieți o clasă care are nevoie, de exemplu, de o bază de date pentru funcționarea sa, nu inventați de unde să o luați, ci să o aveți trecută. Fie ca parametru al constructorului, fie ca parametru al unei alte metode. Admiteți dependențele. Admiteți-le în API-ul clasei dumneavoastră. Veți obține un cod ușor de înțeles și previzibil. -Ce ziceți de această clasă care înregistrează mesaje de eroare: +Și cum rămâne cu această clasă, care înregistrează mesaje de eroare: ```php class Logger @@ -266,9 +266,9 @@ Ce părere aveți, am respectat [regula #1: Lăsați să vi se transmită |#rule Nu am respectat-o. -Informația cheie, directorul fișierului de jurnal, este *obținută* de clasă din constantă. +Informația cheie, și anume directorul cu fișierul jurnal, este *obținută* de clasa însăși din constantă. -A se vedea exemplul de utilizare: +Priviți exemplul de utilizare: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Fără a cunoaște implementarea, puteți răspunde la întrebarea unde sunt scrise mesajele? V-aș sugera că existența constantei LOG_DIR este necesară pentru ca aceasta să funcționeze? Și ați putea să creați o a doua instanță care să scrie într-o altă locație? Cu siguranță că nu. +Fără a cunoaște implementarea, ați putea răspunde la întrebarea unde sunt scrise mesajele? Ați putea ghici că existența constantei `LOG_DIR` este necesară pentru funcționarea sa? Și ați putea crea o a doua instanță care să scrie într-o altă locație? Cu siguranță că nu. Haideți să reparăm clasa: @@ -295,7 +295,7 @@ class Logger } ``` -Clasa este acum mult mai clară, mai ușor de configurat și, prin urmare, mai utilă. +Clasa este acum mult mai ușor de înțeles, mai ușor de configurat și, prin urmare, mai utilă. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Dar nu-mi pasă! .[#toc-but-i-don-t-care] ---------------------------------------- -*"Când creez un obiect articol și apelez la save(), nu vreau să am de-a face cu baza de date, vreau doar să fie salvat în baza de date pe care am stabilit-o în configurație. "* +*"Când creez un obiect articol și apelez la save(), nu vreau să am de-a face cu baza de date; vreau doar să fie salvat în cea pe care am stabilit-o în configurație."* -*"Atunci când folosesc Logger, vreau doar ca mesajul să fie scris și nu vreau să mă ocup de locul în care se scrie. Să fie folosite setările globale. "* +*"Când folosesc Logger, vreau doar ca mesajul să fie scris și nu vreau să mă ocup de locul în care este scris. Să se folosească setările globale. "* -Acestea sunt comentarii corecte. +Acestea sunt puncte valide. -Ca exemplu, să luăm o clasă care trimite buletine de știri și să înregistrăm cum a decurs acest lucru: +Ca exemplu, să ne uităm la o clasă care trimite buletine informative și înregistrează cum a decurs: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -Versiunea îmbunătățită `Logger`, care nu mai utilizează constanta `LOG_DIR`, necesită o cale de acces la fișier în constructor. Cum se poate rezolva acest lucru? Clasei `NewsletterDistributor` nu-i pasă unde sunt scrise mesajele, ci vrea doar să le scrie. +Varianta îmbunătățită `Logger`, care nu mai utilizează constanta `LOG_DIR`, necesită specificarea căii de acces la fișier în constructor. Cum se poate rezolva acest lucru? Clasei `NewsletterDistributor` nu-i pasă unde sunt scrise mesajele; ea vrea doar să le scrie. -Soluția este din nou [regula nr. 1: Lăsați să vi se transmită |#rule #1: Let It Be Passed to You]: treceți-i toate datele de care clasa are nevoie. +Soluția este din nou [regula nr. 1: Lasă să ți se transmită |#rule #1: Let It Be Passed to You]: transmite toate datele de care clasa are nevoie. -Deci, transmitem calea către jurnal constructorului, pe care o folosim apoi pentru a crea obiectul `Logger`? +Asta înseamnă că trebuie să transmitem calea către jurnal prin constructor, pe care o folosim apoi la crearea obiectului `Logger`? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Nu este așa! Pentru că calea **nu** aparține datelor de care are nevoie clasa `NewsletterDistributor`; aceasta are nevoie de `Logger`. Clasa are nevoie de loggerul însuși. Și asta este ceea ce vom transmite mai departe: +Nu, nu așa! Calea nu face parte dintre datele de care are nevoie clasa `NewsletterDistributor`; de fapt, `Logger` are nevoie de ea. Vedeți care este diferența? Clasa `NewsletterDistributor` are nevoie de loggerul însuși. Așa că asta este ceea ce vom trece: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Acum este clar din semnăturile clasei `NewsletterDistributor` că înregistrarea face parte din funcționalitatea sa. Iar sarcina de a înlocui loggerul cu un altul, poate în scopuri de testare, este destul de banală. -În plus, dacă se modifică constructorul clasei `Logger`, acest lucru nu va avea niciun efect asupra clasei noastre. +Din semnăturile clasei `NewsletterDistributor` reiese clar că și jurnalizarea face parte din funcționalitatea sa. Iar sarcina de a schimba loggerul cu un altul, poate pentru testare, este complet trivială. +În plus, dacă se schimbă constructorul clasei `Logger`, acest lucru nu va afecta clasa noastră. -Regula nr. 2: Luați ceea ce vă aparține .[#toc-rule-2-take-what-is-yours] -------------------------------------------------------------------------- +Regula nr. 2: Luați ceea ce este al dumneavoastră .[#toc-rule-2-take-what-s-yours] +---------------------------------------------------------------------------------- -Nu vă lăsați păcăliți și nu lăsați să vă fie trecuți parametrii dependențelor dumneavoastră. Transmiteți dependențele direct. +Nu vă lăsați păcăliți și nu vă lăsați să treceți peste dependențele dependențelor voastre. Treceți-vă doar propriile dependențe. -Astfel, codul care utilizează alte obiecte va fi complet independent de modificările aduse constructorilor acestora. API-ul său va fi mai adevărat. Și, cel mai important, va fi trivial să schimbați acele dependențe cu altele. +Datorită acestui lucru, codul care utilizează alte obiecte va fi complet independent de modificările din constructorii acestora. API-ul său va fi mai veridic. Și, mai presus de toate, va fi trivial să înlocuiți aceste dependențe cu altele. -Un nou membru al familiei .[#toc-a-new-member-of-the-family] ------------------------------------------------------------- +Un nou membru al familiei .[#toc-new-family-member] +--------------------------------------------------- -Echipa de dezvoltare a decis să creeze un al doilea logger care să scrie în baza de date. Așa că am creat o clasă `DatabaseLogger`. Deci avem două clase, `Logger` și `DatabaseLogger`, una scrie într-un fișier, cealaltă scrie într-o bază de date ... nu credeți că este ceva ciudat în legătură cu acest nume? -Nu ar fi mai bine să redenumim `Logger` în `FileLogger`? Sigur că da. +Echipa de dezvoltare a decis să creeze un al doilea logger care să scrie în baza de date. Așa că am creat o clasă `DatabaseLogger`. Deci avem două clase, `Logger` și `DatabaseLogger`, una scrie într-un fișier, cealaltă într-o bază de date ... nu vi se pare ciudată denumirea? +Nu ar fi mai bine să redenumim `Logger` în `FileLogger`? Categoric da. -Dar haideți să o facem inteligent. Vom crea o interfață sub numele original: +Dar haideți să o facem în mod inteligent. Creăm o interfață sub numele original: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...pe care o vor implementa ambii jurnaliști: +... pe care le vor pune în aplicare ambele loguri: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -Și în acest fel, nu va trebui să se schimbe nimic în restul codului în care este utilizat loggerul. De exemplu, constructorul clasei `NewsletterDistributor` se va mulțumi în continuare să solicite `Logger` ca parametru. Și va depinde de noi ce instanță îi vom transmite. +Și, din acest motiv, nu va fi nevoie să se schimbe nimic în restul codului în care este utilizat loggerul. De exemplu, constructorul clasei `NewsletterDistributor` se va mulțumi în continuare să solicite `Logger` ca parametru. Și va fi la latitudinea noastră ce instanță vom trece. -**Acesta este motivul pentru care nu dăm niciodată numelor de interfețe sufixul `Interface` sau prefixul `I`. ** Altfel, ar fi imposibil să dezvoltăm cod atât de frumos. +**De aceea nu adăugăm niciodată sufixul `Interface` sau prefixul `I` la numele interfețelor.** Altfel, nu ar fi posibilă dezvoltarea atât de frumoasă a codului. Houston, avem o problemă .[#toc-houston-we-have-a-problem] ---------------------------------------------------------- -În timp ce în întreaga aplicație ne putem mulțumi cu o singură instanță a unui logger, fie că este vorba de un fișier sau de o bază de date, și pur și simplu îl putem trece oriunde se înregistrează ceva, este cu totul altceva în cazul clasei `Article`. De fapt, creăm instanțe ale acesteia în funcție de necesități, eventual de mai multe ori. Cum să tratăm legătura cu baza de date în constructorul său? +În timp ce ne putem descurca cu o singură instanță a logger-ului, fie că este bazat pe fișier sau pe bază de date, în întreaga aplicație și pur și simplu să o trecem oriunde este înregistrat ceva, este cu totul altceva pentru clasa `Article`. Creăm instanțele sale după cum este necesar, chiar și de mai multe ori. Cum să tratăm dependența de baza de date în constructorul său? -Ca exemplu, putem folosi un controler care ar trebui să salveze un articol în baza de date după trimiterea unui formular: +Un exemplu poate fi un controler care ar trebui să salveze un articol în baza de date după trimiterea unui formular: ```php class EditController extends Controller @@ -437,9 +437,9 @@ class EditController extends Controller } ``` -O posibilă soluție este oferită direct: să avem obiectul bazei de date transmis de constructor la `EditController` și să folosim `$article = new Article($this->db)`. +O posibilă soluție este evidentă: treceți obiectul bazei de date la constructorul `EditController` și utilizați `$article = new Article($this->db)`. -La fel ca în cazul precedent cu `Logger` și calea de acces la fișier, aceasta nu este abordarea corectă. Baza de date nu este o dependență a `EditController`, ci a `Article`. Astfel, trecerea bazei de date contravine [regulii nr. 2: ia ceea ce este al tău |#rule #2: take what is yours]. Atunci când constructorul clasei `Article` este modificat (se adaugă un nou parametru), va trebui modificat și codul din toate locurile în care sunt create instanțe. Ufff. +La fel ca în cazul precedent cu `Logger` și calea de acces la fișier, aceasta nu este abordarea corectă. Baza de date nu este o dependență a `EditController`, ci a `Article`. Transmiterea bazei de date contravine [regulii nr. 2: ia ceea ce este al tău |#rule #2: take what's yours]. Dacă se modifică constructorul clasei `Article` (se adaugă un nou parametru), va trebui să modificați codul acolo unde sunt create instanțe. Ufff. Houston, ce sugerezi? @@ -447,7 +447,7 @@ Houston, ce sugerezi? Regula nr. 3: Lăsați fabrica să se ocupe de asta .[#toc-rule-3-let-the-factory-handle-it] ----------------------------------------------------------------------------------------- -Prin eliminarea legăturilor ascunse și prin transmiterea tuturor dependențelor ca argumente, obținem clase mai configurabile și mai flexibile. Și astfel avem nevoie de altceva pentru a crea și configura aceste clase mai flexibile. Îl vom numi "fabrici". +Prin eliminarea dependențelor ascunse și prin transmiterea tuturor dependențelor ca argumente, am obținut clase mai configurabile și mai flexibile. Și, prin urmare, avem nevoie de altceva pentru a crea și configura aceste clase mai flexibile pentru noi. Îl vom numi fabrici. Regula de bază este: dacă o clasă are dependențe, lăsați crearea instanțelor acestora în seama fabricii. @@ -460,7 +460,7 @@ Vă rugăm să nu faceți confuzie cu modelul de proiectare *factory method*, ca Fabrica .[#toc-factory] ----------------------- -O fabrică este o metodă sau o clasă care produce și configurează obiecte. Clasa de producție `Article` se numește `ArticleFactory` și ar putea arăta în felul următor: +O fabrică este o metodă sau o clasă care creează și configurează obiecte. Vom numi clasa care produce `Article` ca `ArticleFactory`, iar aceasta ar putea arăta astfel: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Utilizarea sa în controler ar fi următoarea: +Utilizarea sa în controler va fi următoarea: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -În acest moment, atunci când se schimbă semnătura constructorului clasei `Article`, singura parte a codului care trebuie să răspundă este fabrica `ArticleFactory`. Orice alt cod care lucrează cu obiectele `Article`, cum ar fi `EditController`, nu va fi afectat. +În acest moment, dacă semnătura constructorului clasei `Article` se schimbă, singura parte a codului care trebuie să reacționeze este `ArticleFactory`. Toate celelalte coduri care lucrează cu obiectele `Article`, cum ar fi `EditController`, nu vor fi afectate. -Poate că acum vă bateți în frunte întrebându-vă dacă ne-am ajutat cumva. Cantitatea de cod a crescut și totul începe să pară suspect de complicat. +S-ar putea să vă întrebați dacă am îmbunătățit de fapt lucrurile. Cantitatea de cod a crescut și totul începe să pară suspect de complicat. -Nu vă faceți griji, vom ajunge în curând la containerul Nette DI. Iar acesta are o serie de ași în mânecă care vor face extrem de simplă construirea de aplicații folosind injecția de dependență. De exemplu, în loc de clasa `ArticleFactory`, va fi suficient să [scrieți o interfață simplă |factory]: +Nu vă faceți griji, în curând vom ajunge la containerul Nette DI. Iar acesta are câteva trucuri în mânecă, care vor simplifica foarte mult construirea de aplicații folosind injecția de dependență. De exemplu, în loc de clasa `ArticleFactory`, va trebui să [scrieți |factory] doar [o interfață simplă |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Dar ne devansăm, stați puțin :-) +Dar ne devansăm; vă rugăm să aveți răbdare :-) Rezumat .[#toc-summary] ----------------------- -La începutul acestui capitol, am promis să vă arătăm o modalitate de a proiecta cod curat. Doar dați clasele +La începutul acestui capitol, am promis să vă arătăm un proces de proiectare a unui cod curat. Tot ce este nevoie este ca clasele să: -- [dependențele de care au nevoie |#Rule #1: Let It Be Passed to You] -- [și nu cele de care nu au nevoie în mod direct |#Rule #2: Take What Is Yours] -- [și că obiectele cu dependențe sunt cel mai bine realizate în fabrici |#Rule #3: Let the Factory Handle it] +- [să treacă dependențele de care au nevoie |#Rule #1: Let It Be Passed to You] +- [invers, să nu treacă ceea ce nu au nevoie în mod direct |#Rule #2: Take What's Yours] +- [și că obiectele cu dependențe sunt cel mai bine create în fabrici |#Rule #3: Let the Factory Handle it] -Poate că nu pare așa la prima vedere, dar aceste trei reguli au implicații profunde. Ele conduc la o viziune radical diferită asupra proiectării codului. Merită? Programatorii care au renunțat la vechile obiceiuri și au început să folosească în mod consecvent injecția de dependență consideră că acesta este un moment crucial în viața lor profesională. A deschis o lume de aplicații clare și durabile. +La prima vedere, aceste trei reguli pot părea să nu aibă consecințe profunde, dar ele conduc la o perspectivă radical diferită asupra proiectării codului. Merită? Dezvoltatorii care au renunțat la vechile obiceiuri și au început să folosească în mod consecvent injecția de dependență consideră acest pas un moment crucial în viața lor profesională. Le-a deschis lumea aplicațiilor clare și ușor de întreținut. -Dar ce se întâmplă dacă codul nu folosește în mod consecvent injecția de dependență? Ce se întâmplă dacă este construit pe metode statice sau singletoni? Aduce probleme? [Da, și este foarte semnificativ |global-state]. +Dar ce se întâmplă dacă codul nu folosește în mod consecvent injecția de dependență? Ce se întâmplă dacă se bazează pe metode statice sau singletoni? Cauzează asta probleme? [Da, da, și unele foarte fundamentale |global-state]. diff --git a/dependency-injection/ro/passing-dependencies.texy b/dependency-injection/ro/passing-dependencies.texy index badf6a4eb2..2437edaeb2 100644 --- a/dependency-injection/ro/passing-dependencies.texy +++ b/dependency-injection/ro/passing-dependencies.texy @@ -12,7 +12,7 @@ Argumentele, sau "dependențele" în terminologia DI, pot fi transmise claselor </div> -Primele trei metode se aplică în general în toate limbajele orientate pe obiecte, cea de-a patra este specifică prezentatorilor Nette, așa că este discutată într-un [capitol separat |best-practices:inject-method-attribute]. În continuare, vom analiza mai îndeaproape fiecare dintre aceste opțiuni și le vom prezenta cu exemple specifice. +În continuare, vom ilustra diferitele variante cu exemple concrete. Injectarea constructorilor .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Injectarea constructorilor .[#toc-constructor-injection] Dependențele sunt transmise ca argumente constructorului atunci când obiectul este creat: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Această formă este utilă pentru dependențele obligatorii de care clasa are neapărat nevoie pentru a funcționa, deoarece fără ele instanța nu poate fi creată. @@ -40,10 +40,10 @@ Această formă este utilă pentru dependențele obligatorii de care clasa are n ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ class MyService ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService Containerul DI transmite dependențele către constructor în mod automat, utilizând [autowiring |autowiring]. Argumentele care nu pot fi transmise [în |services#Arguments] acest mod (de exemplu, șiruri de caractere, numere, booleeni) se [scriu în configurație |services#Arguments]. +Iadul Constructorilor .[#toc-constructor-hell] +---------------------------------------------- + +Termenul *constructor hell* se referă la o situație în care un copil moștenește dintr-o clasă părinte al cărei constructor necesită dependențe, iar copilul necesită și el dependențe. De asemenea, acesta trebuie să preia și să transmită dependențele părintelui: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Problema apare atunci când dorim să modificăm constructorul clasei `BaseClass`, de exemplu atunci când se adaugă o nouă dependență. Atunci trebuie să modificăm și toți constructorii copiilor. Ceea ce face ca o astfel de modificare să fie un iad. + +Cum să prevenim acest lucru? Soluția este să **prioritizăm compoziția în detrimentul moștenirii**. + +Așadar, haideți să proiectăm codul în mod diferit. Vom evita clasele abstracte `Base*`. În loc ca `MyClass` să obțină o anumită funcționalitate prin moștenirea de la `BaseClass`, aceasta va avea acea funcționalitate transmisă ca dependență: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Injectarea setterilor .[#toc-setter-injection] ============================================== -Dependențele sunt transmise prin apelarea unei metode care le stochează într-o proprietate privată. Convenția de denumire obișnuită pentru aceste metode este de forma `set*()`, motiv pentru care sunt numite setteri. +Dependențele sunt transmise prin apelarea unei metode care le stochează într-o proprietate privată. Convenția de denumire obișnuită pentru aceste metode este de forma `set*()`, motiv pentru care sunt numite setters, dar, desigur, pot fi numite în orice alt mod. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Această metodă este utilă pentru dependențele opționale care nu sunt necesare pentru funcția clasei, deoarece nu este garantat faptul că obiectul le va primi efectiv (adică, că utilizatorul va apela metoda). @@ -90,16 +150,16 @@ Această metodă este utilă pentru dependențele opționale care nu sunt necesa În același timp, această metodă permite ca setterul să fie apelat în mod repetat pentru a modifica dependența. Dacă acest lucru nu este de dorit, adăugați o verificare la metodă sau, începând cu PHP 8.1, marcați proprietatea `$cache` cu steagul `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ Apelarea setterului este definită în configurația containerului DI în [secț ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Injectarea proprietăților .[#toc-property-injection] Dependențele sunt trecute direct în proprietate: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Această metodă este considerată nepotrivită deoarece proprietatea trebuie declarată ca fiind `public`. Prin urmare, nu avem niciun control asupra faptului că dependența transmisă va fi de tipul specificat (acest lucru era valabil înainte de PHP 7.4) și pierdem posibilitatea de a reacționa la dependența nou atribuită cu propriul cod, de exemplu pentru a preveni modificările ulterioare. În același timp, proprietatea devine parte a interfeței publice a clasei, ceea ce poate să nu fie de dorit. @@ -137,12 +197,18 @@ Setarea variabilei este definită în configurația containerului DI în [secți ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Injectați .[#toc-inject] +======================== + +În timp ce cele trei metode anterioare sunt, în general, valabile în toate limbajele orientate pe obiecte, injectarea prin metodă, adnotare sau atributul *inject* este specifică prezentatorilor Nette. Acestea sunt discutate [într-un capitol separat |best-practices:inject-method-attribute]. + + Ce modalitate să alegeți? .[#toc-which-way-to-choose] ===================================================== diff --git a/dependency-injection/ro/services.texy b/dependency-injection/ro/services.texy index 701381b2d3..162f918633 100644 --- a/dependency-injection/ro/services.texy +++ b/dependency-injection/ro/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Modul de injectare .[#toc-inject-mode] ====================================== -Indicatorul `inject: true` este utilizat pentru a activa trecerea dependențelor prin intermediul variabilelor publice cu adnotarea [inject |best-practices:inject-method-attribute#Inject Annotations] și metodele [inject*() |best-practices:inject-method-attribute#inject Methods]. +Indicatorul `inject: true` este utilizat pentru a activa trecerea dependențelor prin intermediul variabilelor publice cu adnotarea [inject |best-practices:inject-method-attribute#Inject Attributes] și metodele [inject*() |best-practices:inject-method-attribute#inject Methods]. ```neon services: diff --git a/dependency-injection/ru/@home.texy b/dependency-injection/ru/@home.texy index 82fb1a2649..5d1773d00f 100644 --- a/dependency-injection/ru/@home.texy +++ b/dependency-injection/ru/@home.texy @@ -5,8 +5,10 @@ Dependency Injection - это паттерн проектирования, который в корне изменит ваш взгляд на код и разработку. Он открывает путь в мир чистых и устойчивых приложений. - [Что такое инжекция зависимостей? |introduction] -- [Что такое DI-контейнер? |container] +- [Глобальное состояние и синглтоны |global-state] - [Передача зависимостей |passing-dependencies] +- [Что такое DI-контейнер? |container] +- [Часто задаваемые вопросы |faq] Nette DI diff --git a/dependency-injection/ru/@left-menu.texy b/dependency-injection/ru/@left-menu.texy index 83c03d4772..2080e574e0 100644 --- a/dependency-injection/ru/@left-menu.texy +++ b/dependency-injection/ru/@left-menu.texy @@ -1,8 +1,10 @@ Внедрение зависимостей ********************** - [Что такое «внедрение зависимостей»? |introduction] -- [Что такое «DI-контейнер»? |container] +- [Глобальное состояние и синглтоны |global-state] - [Передача зависимостей |passing-dependencies] +- [Что такое «DI-контейнер»? |container] +- [Часто задаваемые вопросы |faq] Nette DI diff --git a/dependency-injection/ru/faq.texy b/dependency-injection/ru/faq.texy new file mode 100644 index 0000000000..bf7d5b1260 --- /dev/null +++ b/dependency-injection/ru/faq.texy @@ -0,0 +1,112 @@ +Часто задаваемые вопросы об DI (FAQ) +************************************ + + +Является ли DI другим названием для IoC? .[#toc-is-di-another-name-for-ioc] +--------------------------------------------------------------------------- + +Принцип *Inversion of Control* (IoC) - это принцип, сосредоточенный на способе выполнения кода - инициирует ли ваш код внешний код или ваш код интегрирован во внешний код, который затем вызывает его. +IoC - это широкая концепция, включающая [события |nette:glossary#Events], так называемый [голливудский принцип |application:components#Hollywood style] и другие аспекты. +Фабрики, которые являются частью [правила №3: Let the Factory Handle It |introduction#Rule #3: Let the Factory Handle It], и представляют собой инверсию для оператора `new`, также являются компонентами этой концепции. + +*Dependency Injection* (DI) - это то, как один объект знает о другом объекте, т.е. зависимость. Это паттерн проектирования, который требует явной передачи зависимостей между объектами. + +Таким образом, можно сказать, что DI является специфической формой IoC. Однако не все формы IoC подходят с точки зрения чистоты кода. Например, к антипаттернам мы относим все техники, работающие с [глобальным состоянием |global state], или так называемый [Service Locator |#What is a Service Locator]. + + +Что такое локатор сервисов? .[#toc-what-is-a-service-locator] +------------------------------------------------------------- + +Сервисный локатор - это альтернатива инъекции зависимостей. Он работает путем создания центрального хранилища, в котором регистрируются все доступные сервисы или зависимости. Когда объекту нужна зависимость, он запрашивает ее в Service Locator. + +Однако, по сравнению с Dependency Injection, этот метод теряет в прозрачности: зависимости не передаются объектам напрямую и поэтому их нелегко идентифицировать, что требует изучения кода для выявления и понимания всех связей. Тестирование также усложняется, поскольку мы не можем просто передать имитационные объекты тестируемым объектам, а должны пройти через Service Locator. Кроме того, Service Locator нарушает дизайн кода, поскольку отдельные объекты должны знать о его существовании, что отличается от Dependency Injection, где объекты не знают о контейнере DI. + + +Когда лучше не использовать DI? .[#toc-when-is-it-better-not-to-use-di] +----------------------------------------------------------------------- + +Не существует известных трудностей, связанных с использованием паттерна проектирования Dependency Injection. Напротив, получение зависимостей из глобально доступных мест приводит к [ряду осложнений |global-state], как и использование локатора служб. +Поэтому рекомендуется всегда использовать DI. Это не догматический подход, просто лучшей альтернативы пока не найдено. + +Однако существуют определенные ситуации, когда мы не передаем объекты друг другу, а получаем их из глобального пространства. Например, при отладке кода и необходимости сбросить значение переменной в определенный момент программы, измерить длительность определенной части программы или записать сообщение в журнал. +В таких случаях, когда речь идет о временных действиях, которые впоследствии будут удалены из кода, вполне законно использовать глобально доступный дампер, секундомер или логгер. Эти инструменты, в конце концов, не относятся к дизайну кода. + + +Есть ли у использования DI недостатки? .[#toc-does-using-di-have-its-drawbacks] +------------------------------------------------------------------------------- + +Имеет ли использование Dependency Injection какие-либо недостатки, такие как увеличение сложности написания кода или ухудшение производительности? Что мы теряем, когда начинаем писать код в соответствии с DI? + +DI не влияет на производительность приложения или требования к памяти. Производительность контейнера DI может играть определенную роль, но в случае с [Nette DI | nette-container] контейнер компилируется в чистый PHP, поэтому его накладные расходы во время выполнения приложения практически равны нулю. + +При написании кода необходимо создавать конструкторы, принимающие зависимости. Раньше это могло отнимать много времени, но благодаря современным IDE и [продвижению свойств конструкторов |https://blog.nette.org/ru/php-8-0-polnyj-obzor-novostej#toc-constructor-property-promotion] теперь это дело нескольких секунд. Фабрики могут быть легко созданы с помощью Nette DI и плагина PhpStorm всего за несколько кликов. +С другой стороны, нет необходимости писать синглтоны и статические точки доступа. + +Можно сделать вывод, что правильно спроектированное приложение с использованием DI не короче и не длиннее по сравнению с приложением, использующим синглтоны. Части кода, работающие с зависимостями, просто извлекаются из отдельных классов и переносятся в новые места, т.е. в контейнер DI и фабрики. + + +Как переписать унаследованное приложение на DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] +------------------------------------------------------------------------------------------------- + +Переход от устаревшего приложения к Dependency Injection может быть сложным процессом, особенно для больших и сложных приложений. Важно подходить к этому процессу систематически. + +- При переходе на Dependency Injection важно, чтобы все члены команды понимали используемые принципы и практики. +- Сначала проведите анализ существующего приложения, чтобы определить ключевые компоненты и их зависимости. Составьте план, какие части будут рефакторизоваться и в каком порядке. +- Реализуйте DI-контейнер или, что еще лучше, используйте существующую библиотеку, такую как Nette DI. +- Постепенно рефакторить каждую часть приложения для использования Dependency Injection. Это может включать модификацию конструкторов или методов для принятия зависимостей в качестве параметров. +- Измените места в коде, где создаются объекты зависимостей, так, чтобы вместо этого зависимости внедрялись контейнером. Это может включать использование фабрик. + +Помните, что переход на Dependency Injection - это инвестиции в качество кода и долгосрочную устойчивость приложения. Хотя внести эти изменения может быть непросто, результатом должен стать более чистый, модульный и легко тестируемый код, готовый к будущим расширениям и обслуживанию. + + +Почему композиция предпочтительнее наследования? .[#toc-why-composition-is-preferred-over-inheritance] +------------------------------------------------------------------------------------------------------ +Предпочтительнее использовать композицию, а не наследование, так как она служит цели повторного использования кода без необходимости беспокоиться о нисходящем эффекте изменений. Таким образом, это обеспечивает более свободную связь, где нам не нужно беспокоиться о том, что изменение какого-то кода приведет к тому, что другой зависимый код потребует изменений. Типичным примером является ситуация, называемая [ад конструкторов |passing-dependencies#Constructor hell]. + + +Можно ли использовать Nette DI Container вне Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] +----------------------------------------------------------------------------------------------------------- + +Безусловно. Nette DI Container является частью Nette, но разработан как отдельная библиотека, которую можно использовать независимо от других частей фреймворка. Просто установите его с помощью Composer, создайте конфигурационный файл, определяющий ваши сервисы, а затем используйте несколько строк PHP-кода для создания DI-контейнера. +И вы можете немедленно начать использовать преимущества Dependency Injection в своих проектах. + +В главе [Nette DI Container |nette-container] описано, как выглядит конкретный пример использования, включая код. + + +Почему конфигурация находится в файлах NEON? .[#toc-why-is-the-configuration-in-neon-files] +------------------------------------------------------------------------------------------- + +NEON - это простой и легко читаемый язык конфигурации, разработанный в Nette для настройки приложений, сервисов и их зависимостей. По сравнению с JSON или YAML, он предлагает гораздо более интуитивно понятные и гибкие возможности для этой цели. В NEON можно естественным образом описать привязки, которые невозможно было бы написать в Symfony & YAML либо вообще, либо только через сложное описание. + + +Замедляет ли парсинг NEON файлов работу приложения? .[#toc-does-parsing-neon-files-slow-down-the-application] +------------------------------------------------------------------------------------------------------------- + +Хотя файлы NEON разбираются очень быстро, этот аспект не имеет особого значения. Причина в том, что разбор файлов происходит только один раз во время первого запуска приложения. После этого код контейнера DI генерируется, сохраняется на диске и выполняется для каждого последующего запроса без необходимости дальнейшего разбора. + +Именно так это работает в производственной среде. Во время разработки файлы NEON разбираются при каждом изменении их содержимого, что гарантирует, что у разработчика всегда есть актуальный контейнер DI. Как упоминалось ранее, фактический разбор происходит мгновенно. + + +Как я могу получить доступ к параметрам из конфигурационного файла в своем классе? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Помните о [правиле №1: "Пусть вам переда |introduction#Rule #1: Let It Be Passed to You]дут". Если класс требует информацию из конфигурационного файла, нам не нужно выяснять, как получить доступ к этой информации; вместо этого мы просто запрашиваем ее - например, через конструктор класса. А передачу мы выполняем в конфигурационном файле. + +В этом примере `%myParameter%` - это место для значения параметра `myParameter`, который будет передан конструктору `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Если вы хотите передать несколько параметров или использовать автоподключение, полезно [обернуть параметры в объект |best-practices:passing-settings-to-presenters]. + + +Поддерживает ли Nette интерфейс PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] +------------------------------------------------------------------------------------------------------- + +Nette DI Container не поддерживает PSR-11 напрямую. Однако, если вам нужна совместимость между Nette DI Container и библиотеками или фреймворками, которые ожидают контейнерный интерфейс PSR-11, вы можете создать [простой адаптер |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], который будет служить мостом между Nette DI Container и PSR-11. diff --git a/dependency-injection/ru/global-state.texy b/dependency-injection/ru/global-state.texy new file mode 100644 index 0000000000..5300272f92 --- /dev/null +++ b/dependency-injection/ru/global-state.texy @@ -0,0 +1,312 @@ +Глобальное состояние и синглтоны +******************************** + +.[perex] +Предупреждение: следующие конструкции являются симптомами плохого дизайна кода: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` или `static::$var` + +Встречаются ли в вашем коде какие-либо из этих конструкций? Тогда у вас есть возможность улучшиться. Возможно, вы думаете, что это обычные конструкции, которые мы видим в примерах решений различных библиотек и фреймворков. +К сожалению, они все еще являются четким индикатором плохого дизайна. У них есть одна общая черта: использование глобального состояния. + +Мы, конечно, не говорим о какой-то академической чистоте. Использование глобального состояния и синглтонов разрушительно сказывается на качестве кода. Его поведение становится непредсказуемым, снижает производительность разработчиков и заставляет интерфейсы классов лгать о своих истинных зависимостях. Что сбивает программистов с толку. + +В этой главе мы покажем, как это возможно. + + +Глобальное сцепление .[#toc-global-interlinking] +------------------------------------------------ + +Фундаментальная проблема глобального состояния заключается в том, что оно глобально доступно. Это делает возможным запись в базу данных через глобальный (статический) метод `DB::insert()`. +В идеальном мире объект должен иметь возможность взаимодействовать только с другими объектами, которые были ему [непосредственно переда |passing-dependencies] ны. +Если я создам два объекта `A` и `B` и никогда не передам ссылку с `A` на `B`, то ни `A`, ни `B` не смогут получить доступ к другому объекту или изменить его состояние. +Это очень желательная особенность кода. Это похоже на наличие батарейки и лампочки; лампочка не загорится, пока вы не соедините их вместе. + +Это не относится к глобальным (статическим) переменным или синглтонам. Объект `A` может *без проводов* получить доступ к объекту `C` и изменить его без передачи ссылки, вызвав `C::changeSomething()`. +Если объект `B` также получает доступ к глобальной `C`, то `A` и `B` могут взаимодействовать друг с другом через `C`. + +Использование глобальных переменных вводит в систему новую форму *беспроводной* связи, которая не видна извне. +Это создает дымовую завесу, усложняющую понимание и использование кода. +Разработчики должны читать каждую строчку исходного кода, чтобы действительно понять зависимости. Вместо того чтобы просто ознакомиться с интерфейсом классов. +Более того, это совершенно ненужное сцепление. + +.[note] +С точки зрения поведения, нет никакой разницы между глобальной и статической переменной. Они одинаково вредны. + + +Зловещее действие на расстоянии .[#toc-the-spooky-action-at-a-distance] +----------------------------------------------------------------------- + +"Зловещее действие на расстоянии" - так Альберт Эйнштейн в 1935 году назвал явление в квантовой физике, от которого у него мурашки по коже. +Это квантовая запутанность, особенность которой заключается в том, что когда вы измеряете информацию об одной частице, вы немедленно воздействуете на другую частицу, даже если они находятся на расстоянии миллионов световых лет друг от друга. +Это, казалось бы, нарушает фундаментальный закон Вселенной, согласно которому ничто не может двигаться быстрее света. + +В мире программного обеспечения мы можем назвать "spooky action at a distance" ситуацию, когда мы запускаем процесс, который, как нам кажется, изолирован (потому что мы не передавали ему никаких ссылок), но неожиданные взаимодействия и изменения состояния происходят в удаленных местах системы, о которых мы не сообщили объекту. Это может произойти только через глобальное состояние. + +Представьте, что вы присоединились к команде разработчиков проекта, которая имеет большую, зрелую кодовую базу. Ваш новый руководитель просит вас реализовать новую функцию, и, как хороший разработчик, вы начинаете с написания теста. Но поскольку вы новичок в проекте, вы делаете много исследовательских тестов типа "что произойдет, если я вызову этот метод". И вы пытаетесь написать следующий тест: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // номер вашей карты + $cc->charge(100); +} +``` + +Вы запускаете код, возможно, несколько раз, и через некоторое время замечаете на своем телефоне уведомления от банка о том, что при каждом запуске с вашей кредитной карты было списано $100 🤦‍♂️. + +Как тест мог вызвать фактическое списание средств? С кредитной картой не так-то просто работать. Вы должны взаимодействовать с веб-службой третьей стороны, вы должны знать URL этой веб-службы, вы должны войти в систему и так далее. +Ни одна из этих сведений не включена в тест. Хуже того, вы даже не знаете, где эта информация присутствует, и, следовательно, как высмеять внешние зависимости, чтобы каждый прогон не приводил к повторному начислению $100. А как начинающий разработчик, откуда вы могли знать, что то, что вы собираетесь сделать, приведет к тому, что вы станете беднее на 100 долларов? + +Это жуткое действие на расстоянии! + +У вас нет другого выбора, кроме как копаться в большом количестве исходного кода, спрашивая старших и более опытных коллег, пока вы не поймете, как работают связи в проекте. +Это связано с тем, что, глядя на интерфейс класса `CreditCard`, вы не можете определить глобальное состояние, которое необходимо инициализировать. Даже просмотр исходного кода класса не подскажет вам, какой метод инициализации нужно вызвать. В лучшем случае, вы можете найти глобальную переменную, к которой обращаются, и попытаться угадать, как ее инициализировать, исходя из этого. + +Классы в таком проекте - патологические лжецы. Платежная карта делает вид, что вы можете просто инстанцировать ее и вызвать метод `charge()`. Однако она тайно взаимодействует с другим классом, `PaymentGateway`. Даже его интерфейс говорит, что он может быть инициализирован самостоятельно, но на самом деле он берет учетные данные из какого-то конфигурационного файла и так далее. +Разработчикам, написавшим этот код, ясно, что `CreditCard` нуждается в `PaymentGateway`. Они написали код таким образом. Но для новичков это полная загадка и мешает обучению. + +Как исправить ситуацию? Легко. **Пусть API объявляет зависимости.**. + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Обратите внимание, как внезапно стали очевидны взаимосвязи внутри кода. Объявив, что метод `charge()` нуждается в методе `PaymentGateway`, вам не нужно никого спрашивать о взаимозависимости кода. Вы знаете, что вам нужно создать его экземпляр, и когда вы пытаетесь это сделать, вы сталкиваетесь с тем, что вам нужно предоставить параметры доступа. Без них код даже не запустится. + +И самое главное, теперь вы можете поиздеваться над платежным шлюзом, чтобы с вас не снимали 100 долларов каждый раз, когда вы запускаете тест. + +Глобальное состояние заставляет ваши объекты тайно получать доступ к тому, что не объявлено в их API, и в результате делает ваши API патологическими лжецами. + +Возможно, вы не думали об этом раньше, но всякий раз, когда вы используете глобальное состояние, вы создаете секретные беспроводные каналы связи. Жуткие удаленные действия заставляют разработчиков читать каждую строчку кода, чтобы понять потенциальное взаимодействие, снижают производительность разработчиков и сбивают с толку новых членов команды. +Если вы один из тех, кто создал код, вы знаете реальные зависимости, но все, кто приходит после вас, ничего не знают. + +Не пишите код, который использует глобальное состояние, предпочитая передавать зависимости. Это и есть инъекция зависимостей. + + +Хрупкость глобального государства .[#toc-brittleness-of-the-global-state] +------------------------------------------------------------------------- + +В коде, использующем глобальное состояние и синглтоны, никогда нельзя точно сказать, когда и кем это состояние было изменено. Этот риск присутствует уже при инициализации. Следующий код должен создать соединение с базой данных и инициализировать платежный шлюз, но он постоянно выбрасывает исключение, и поиск причины крайне утомителен: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Приходится подробно изучать код, чтобы обнаружить, что объект `PaymentGateway` обращается к другим объектам по беспроводной связи, некоторые из которых требуют подключения к базе данных. Таким образом, вы должны инициализировать базу данных перед `PaymentGateway`. Однако дымовая завеса глобального состояния скрывает это от вас. Сколько времени вы бы сэкономили, если бы API каждого класса не лгал и не объявлял о своих зависимостях? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Аналогичная проблема возникает при использовании глобального доступа к соединению с базой данных: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +При вызове метода `save()` неясно, было ли уже создано соединение с базой данных и кто отвечает за его создание. Например, если бы мы захотели изменить соединение с базой данных "на лету", возможно, в целях тестирования, нам, вероятно, пришлось бы создать дополнительные методы, такие как `DB::reconnect(...)` или `DB::reconnectForTest()`. + +Рассмотрим пример: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Где мы можем быть уверены, что при вызове `$article->save()` действительно используется тестовая база данных? Что если метод `Foo::doSomething()` изменит глобальное подключение к базе данных? Чтобы выяснить это, нам придется изучить исходный код класса `Foo` и, вероятно, многих других классов. Однако такой подход даст лишь краткосрочный ответ, поскольку в будущем ситуация может измениться. + +Что если мы перенесем соединение с базой данных в статическую переменную внутри класса `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Это совершенно ничего не изменит. Проблема является глобальным состоянием, и не имеет значения, в каком классе она скрывается. В этом случае, как и в предыдущем, у нас нет никакой информации о том, в какую базу данных производится запись, когда вызывается метод `$article->save()`. Любой человек на удаленном конце приложения может в любой момент изменить базу данных с помощью `Article::setDb()`. Под нашими руками. + +Глобальное состояние делает наше приложение **очень хрупким**. + +Однако есть простой способ решить эту проблему. Просто попросите API объявить зависимости для обеспечения надлежащей функциональности. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Такой подход устраняет беспокойство о скрытых и неожиданных изменениях соединений с базой данных. Теперь мы точно знаем, где хранится статья, и никакие модификации кода внутри другого несвязанного класса уже не смогут изменить ситуацию. Код больше не хрупкий, а стабильный. + +Не пишите код, использующий глобальное состояние, предпочитайте передавать зависимости. Таким образом, инъекция зависимостей. + + +Синглтон .[#toc-singleton] +-------------------------- + +Синглтон - это паттерн проектирования, который, по [определению |https://en.wikipedia.org/wiki/Singleton_pattern] из знаменитой публикации Gang of Four, ограничивает класс одним экземпляром и предоставляет к нему глобальный доступ. Реализация этого паттерна обычно похожа на следующий код: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // и другие методы, выполняющие функции класса +} +``` + +К сожалению, синглтон вносит глобальное состояние в приложение. А как мы показали выше, глобальное состояние нежелательно. Вот почему синглтон считается антипаттерном. + +Не используйте синглтоны в своем коде и замените их другими механизмами. Вам действительно не нужны синглтоны. Однако если вам нужно гарантировать существование единственного экземпляра класса для всего приложения, предоставьте это [DI-контейнеру |container]. +Таким образом, создайте синглтон приложения, или сервис. В результате класс перестанет обеспечивать собственную уникальность (т.е. у него не будет метода `getInstance()` и статической переменной) и будет выполнять только свои функции. Таким образом, он перестанет нарушать принцип единой ответственности. + + +Глобальное состояние против тестов .[#toc-global-state-versus-tests] +-------------------------------------------------------------------- + +При написании тестов мы предполагаем, что каждый тест является изолированной единицей и никакое внешнее состояние в него не попадает. И никакое состояние не покидает тесты. Когда тест завершается, любое состояние, связанное с тестом, должно быть автоматически удалено сборщиком мусора. Это делает тесты изолированными. Поэтому мы можем запускать тесты в любом порядке. + +Однако если присутствуют глобальные состояния/синглтоны, все эти приятные предположения разрушаются. Состояние может войти в тест и выйти из него. Неожиданно порядок выполнения тестов может иметь значение. + +Чтобы вообще тестировать синглтоны, разработчикам часто приходится ослаблять их свойства, возможно, позволяя заменять один экземпляр другим. Такие решения в лучшем случае являются хаками, которые создают код, который трудно поддерживать и понимать. Любой тест или метод `tearDown()`, который влияет на любое глобальное состояние, должен отменить эти изменения. + +Глобальное состояние - это самая большая головная боль в модульном тестировании! + +Как исправить ситуацию? Легко. Не пишите код, использующий синглтоны, предпочитайте передавать зависимости. То есть, инъекция зависимостей. + + +Глобальные константы .[#toc-global-constants] +--------------------------------------------- + +Глобальное состояние не ограничивается использованием синглтонов и статических переменных, но также может применяться к глобальным константам. + +Константы, значение которых не предоставляет нам никакой новой (`M_PI`) или полезной (`PREG_BACKTRACK_LIMIT_ERROR`) информации, явно не являются нормальными. +И наоборот, константы, которые служат способом *беспроводной* передачи информации внутри кода, являются ничем иным, как скрытой зависимостью. Например, `LOG_FILE` в следующем примере. +Использование константы `FILE_APPEND` совершенно корректно. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +В этом случае мы должны объявить параметр в конструкторе класса `Foo`, чтобы сделать его частью API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Теперь мы можем передавать информацию о пути к файлу протоколирования и легко изменять его по мере необходимости, что облегчает тестирование и сопровождение кода. + + +Глобальные функции и статические методы .[#toc-global-functions-and-static-methods] +----------------------------------------------------------------------------------- + +Мы хотим подчеркнуть, что использование статических методов и глобальных функций само по себе не является проблематичным. Мы уже объясняли неуместность использования `DB::insert()` и подобных методов, но это всегда было связано с глобальным состоянием, хранящимся в статической переменной. Метод `DB::insert()` требует существования статической переменной, поскольку в ней хранится соединение с базой данных. Без этой переменной реализовать метод было бы невозможно. + +Использование детерминированных статических методов и функций, таких как `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` и многих других, полностью соответствует инъекции зависимостей. Эти функции всегда возвращают одни и те же результаты при одних и тех же входных параметрах и поэтому предсказуемы. Они не используют никакого глобального состояния. + +Однако в PHP есть функции, которые не являются детерминированными. К ним относится, например, функция `htmlspecialchars()`. Ее третий параметр, `$encoding`, если не указан, по умолчанию принимает значение параметра конфигурации `ini_get('default_charset')`. Поэтому рекомендуется всегда указывать этот параметр, чтобы избежать возможного непредсказуемого поведения функции. Nette последовательно делает это. + +Некоторые функции, такие как `strtolower()`, `strtoupper()`, и тому подобные, в недавнем прошлом имели недетерминированное поведение и зависели от параметра `setlocale()`. Это вызывало множество осложнений, чаще всего при работе с турецким языком. +Это связано с тем, что турецкий язык различает верхний и нижний регистр `I` с точкой и без точки. Таким образом, `strtolower('I')` возвращал символ `ı`, а `strtoupper('i')` - символ `İ`, что приводило к тому, что приложения выдавали ряд загадочных ошибок. +Однако эта проблема была исправлена в PHP версии 8.2, и функции больше не зависят от локали. + +Это наглядный пример того, как глобальное состояние мучает тысячи разработчиков по всему миру. Решением было заменить его инъекцией зависимостей. + + +Когда можно использовать глобальное состояние? .[#toc-when-is-it-possible-to-use-global-state] +---------------------------------------------------------------------------------------------- + +Существуют определенные ситуации, когда можно использовать глобальное состояние. Например, при отладке кода, когда вам нужно сбросить значение переменной или измерить длительность определенной части программы. В таких случаях, когда речь идет о временных действиях, которые впоследствии будут удалены из кода, вполне законно использовать глобально доступный дампер или секундомер. Эти инструменты не являются частью дизайна кода. + +Другой пример - функции для работы с регулярными выражениями `preg_*`, которые внутренне хранят скомпилированные регулярные выражения в статическом кэше в памяти. Когда вы вызываете одно и то же регулярное выражение несколько раз в разных частях кода, оно компилируется только один раз. Кэш экономит производительность и совершенно незаметен для пользователя, поэтому такое использование можно считать легитимным. + + +Резюме .[#toc-summary] +---------------------- + +Мы показали, почему это имеет смысл + +1) Убрать из кода все статические переменные +2) Объявить зависимости +3) И использовать инъекцию зависимостей + +Обдумывая дизайн кода, помните, что каждый `static $foo` представляет собой проблему. Для того чтобы ваш код стал средой, уважающей DI, необходимо полностью искоренить глобальное состояние и заменить его инъекцией зависимостей. + +Во время этого процесса вы можете обнаружить, что вам нужно разделить класс, потому что он несет более одной ответственности. Не беспокойтесь об этом; стремитесь к принципу одной ответственности. + +*Я хотел бы поблагодарить Мишко Хевери, чьи статьи, такие как [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], легли в основу этой главы*. diff --git a/dependency-injection/ru/introduction.texy b/dependency-injection/ru/introduction.texy index 112a945c3c..b857b16e93 100644 --- a/dependency-injection/ru/introduction.texy +++ b/dependency-injection/ru/introduction.texy @@ -49,7 +49,7 @@ echo addition(); // что здесь выводится? Глядя на такой код, мы приходим в замешательство. Не только новичок не поймет его, даже опытный программист не разберется в таком коде. -Интересно, как такая функция будет выглядеть внутри? Откуда она возьмет переменные? Вероятно, она могла бы получить их *каким-то образом* самостоятельно, вот так: +Вам интересно, как на самом деле будет выглядеть такая функция внутри? Откуда она будет брать слагаемые? Скорее всего, она бы *каким-то образом* получала их сама, возможно, следующим образом: ```php function addition(): float @@ -203,13 +203,13 @@ class Article Или нет? -Давайте вспомним [правило №1: Пусть вам передадут |#rule #1: Let It Be Passed to You]: все зависимости, которые нужны классу, должны быть переданы ему. Потому что если мы этого не сделаем и нарушим правило, мы начнем путь к грязному коду, полному скрытых привязок, непонятности, и в результате получим приложение, которое будет больно поддерживать и развивать. +Давайте вспомним [правило №1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: все зависимости, которые нужны классу, должны быть переданы ему. Потому что если мы нарушим это правило, то вступим на путь грязного кода, полного скрытых зависимостей, непонятности, и в результате получим приложение, которое будет больно поддерживать и развивать. -Пользователь класса `Article` понятия не имеет, где метод `save()` хранит статью. В таблице базы данных? В какой, в производственной или в разработке? И как это можно изменить? +Пользователь класса `Article` понятия не имеет, где метод `save()` хранит статью. В таблице базы данных? В какой, в производственной или тестовой? И как ее можно изменить? -Пользователь должен посмотреть, как реализован метод `save()`, чтобы найти использование метода `DB::insert()`. Поэтому ему приходится искать дальше, чтобы выяснить, как этот метод обеспечивает подключение к базе данных. А скрытые привязки могут образовывать довольно длинную цепочку. +Пользователь должен посмотреть, как реализован метод `save()`, и находит использование метода `DB::insert()`. Значит, ему придется искать дальше, чтобы выяснить, как этот метод получает соединение с базой данных. А скрытые зависимости могут образовать довольно длинную цепочку. -Скрытые привязки, фасады Laravel или статические переменные никогда не присутствуют в чистом, хорошо продуманном коде. В чистом и хорошо продуманном коде передаются аргументы: +В чистом и хорошо спроектированном коде никогда нет никаких скрытых зависимостей, фасадов Laravel или статических переменных. В чистом и хорошо продуманном коде передаются аргументы: ```php class Article @@ -245,7 +245,7 @@ class Article ``` .[note] -Если вы опытный программист, вы можете подумать, что у `Article` вообще не должно быть метода `save()`, это должен быть чистый компонент данных, а о хранении данных должен позаботиться отдельный репозиторий. В этом есть смысл. Но это выведет нас далеко за рамки темы - инъекции зависимостей - и попыток привести простые примеры. +Если вы опытный программист, вы можете подумать, что у `Article` вообще не должно быть метода `save()`; он должен представлять собой чисто компонент данных, а о сохранении должен позаботиться отдельный репозиторий. В этом есть смысл. Но это выведет нас далеко за рамки данной темы - инъекции зависимостей - и попыток привести простые примеры. Если вы, например, собираетесь написать класс, которому для работы требуется база данных, не выясняйте, откуда ее взять, а пусть она будет передана вам. Возможно, в качестве параметра конструктора или другого метода. Объявляйте зависимости. Выявляйте их в API вашего класса. Вы получите понятный и предсказуемый код. @@ -379,19 +379,19 @@ class NewsletterDistributor Более того, если конструктор класса `Logger` будет изменен, это никак не повлияет на наш класс. -Правило №2: Берите то, что принадлежит вам .[#toc-rule-2-take-what-is-yours] ----------------------------------------------------------------------------- +Правило №2: Берите то, что принадлежит вам .[#toc-rule-2-take-what-s-yours] +--------------------------------------------------------------------------- Не вводите себя в заблуждение и не позволяйте передавать вам параметры зависимостей. Передавайте зависимости напрямую. Это сделает код, использующий другие объекты, полностью независимым от изменений в их конструкторах. Его API будет более правильным. И самое главное, будет тривиально поменять эти зависимости на другие. -Новый член семьи .[#toc-a-new-member-of-the-family] ---------------------------------------------------- +Новый член семьи .[#toc-new-family-member] +------------------------------------------ -Команда разработчиков решила создать второй регистратор, который пишет в базу данных. Поэтому мы создаем класс `DatabaseLogger`. Итак, у нас есть два класса, `Logger` и `DatabaseLogger`, один пишет в файл, другой - в базу данных... не кажется ли вам, что в этом названии есть что-то странное? -Не лучше ли переименовать `Logger` в `FileLogger`? Конечно, лучше. +Команда разработчиков решила создать второй регистратор, который пишет в базу данных. Поэтому мы создаем класс `DatabaseLogger`. Итак, у нас есть два класса, `Logger` и `DatabaseLogger`, один пишет в файл, другой в базу данных... не кажется ли вам такое именование странным? +Не лучше ли переименовать `Logger` в `FileLogger`? Определенно да. Но давайте сделаем это по-умному. Мы создадим интерфейс под оригинальным именем: @@ -402,7 +402,7 @@ interface Logger } ``` -...который будут реализовывать оба регистратора: +... которые будут реализованы обоими регистраторами: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -И таким образом, ничего не нужно будет менять в остальной части кода, где используется логгер. Например, конструктор класса `NewsletterDistributor` по-прежнему будет рад потребовать `Logger` в качестве параметра. И только от нас будет зависеть, какой экземпляр мы ему передадим. +И благодаря этому не нужно будет ничего менять в остальной части кода, где используется логгер. Например, конструктор класса `NewsletterDistributor` по-прежнему будет удовлетворен тем, что потребует в качестве параметра `Logger`. И только от нас будет зависеть, какой экземпляр мы передадим. -**Вот почему мы никогда не даем именам интерфейсов суффикс `Interface` или префикс `I`.** Иначе было бы невозможно разработать такой красивый код. +**Вот почему мы никогда не добавляем суффикс `Interface` или префикс `I` к именам интерфейсов.** Иначе невозможно было бы так красиво разработать код. Хьюстон, у нас проблема .[#toc-houston-we-have-a-problem] --------------------------------------------------------- -Если во всем приложении мы можем быть довольны одним экземпляром регистратора, будь то файл или база данных, и просто передавать его везде, где что-то регистрируется, то в случае с классом `Article` все обстоит совсем иначе. Фактически, мы создаем его экземпляры по мере необходимости, возможно, несколько раз. Как быть с привязкой к базе данных в его конструкторе? +В то время как мы можем обойтись одним экземпляром регистратора, будь то файловый или основанный на базе данных, во всем приложении и просто передавать его везде, где что-то регистрируется, с классом `Article` дело обстоит совсем иначе. Мы создаем его экземпляры по мере необходимости, даже несколько раз. Как быть с зависимостью от базы данных в его конструкторе? -В качестве примера мы можем использовать контроллер, который должен сохранять статью в базу данных после отправки формы: +Примером может быть контроллер, который должен сохранять статью в базу данных после отправки формы: ```php class EditController extends Controller @@ -437,9 +437,9 @@ class EditController extends Controller } ``` -Возможное решение предлагается напрямую: пусть объект базы данных передается конструктором в `EditController` и используется `$article = new Article($this->db)`. +Возможное решение очевидно: передать объект базы данных в конструктор `EditController` и использовать `$article = new Article($this->db)`. -Как и в предыдущем случае с `Logger` и путем к файлу, это неправильный подход. База данных является зависимостью не от `EditController`, а от `Article`. Поэтому передача базы данных противоречит [правилу #2: бери то, что принадлежит тебе |#rule #2: take what is yours]. Когда конструктор класса `Article` будет изменен (добавлен новый параметр), код во всех местах, где создаются экземпляры, также должен быть изменен. Уффф. +Как и в предыдущем случае с `Logger` и путем к файлу, это неправильный подход. База данных является зависимостью не от `EditController`, а от `Article`. Передача базы данных противоречит [правилу #2: бери то, что принадлежит тебе |#rule #2: take what's yours]. Если конструктор класса `Article` изменится (добавится новый параметр), вам придется модифицировать код везде, где создаются экземпляры. Уффф. Хьюстон, что вы предлагаете? @@ -447,11 +447,11 @@ class EditController extends Controller Правило №3: Пусть завод сам разбирается с этим .[#toc-rule-3-let-the-factory-handle-it] --------------------------------------------------------------------------------------- -Убрав скрытые привязки и передавая все зависимости в качестве аргументов, мы получаем более настраиваемые и гибкие классы. И поэтому нам нужно что-то еще для создания и настройки этих более гибких классов. Мы назовем это фабриками. +Устранив скрытые зависимости и передавая все зависимости в качестве аргументов, мы получили более настраиваемые и гибкие классы. И поэтому нам нужно что-то еще, чтобы создавать и конфигурировать эти более гибкие классы для нас. Мы будем называть это фабриками. Эмпирическое правило таково: если класс имеет зависимости, оставьте создание их экземпляров фабрике. -Фабрики являются более разумной заменой оператору `new` в мире внедрения зависимостей. +Фабрики - это более разумная замена оператора `new` в мире инъекций зависимостей. .[note] Пожалуйста, не путайте с шаблоном проектирования *factory method*, который описывает конкретный способ использования фабрик и не имеет отношения к данной теме. @@ -517,12 +517,12 @@ interface ArticleFactory Резюме .[#toc-summary] ---------------------- -В начале этой главы мы обещали показать вам способ разработки чистого кода. Просто дайте классам +В начале этой главы мы обещали показать вам процесс разработки чистого кода. Все, что для этого требуется, это чтобы классы: -- [те зависимости, которые им нужны |#Rule #1: Let It Be Passed to You] -- [а не то, что им напрямую не нужно |#Rule #2: Take What Is Yours] -- [и что объекты с зависимостями лучше всего создавать в фабриках |#Rule #3: Let the Factory Handle it] +- [передавать необходимые им зависимости |#Rule #1: Let It Be Passed to You] +- [и наоборот, не передавать то, что им напрямую не нужно |#Rule #2: Take What's Yours] +- [и чтобы объекты с зависимостями лучше всего создавались в фабриках |#Rule #3: Let the Factory Handle it] -На первый взгляд может показаться, что это не так, но эти три правила имеют далеко идущие последствия. Они приводят к радикально иному взгляду на проектирование кода. Стоит ли оно того? Программисты, которые отбросили старые привычки и начали последовательно использовать внедрение зависимостей, считают это поворотным моментом в своей профессиональной жизни. Он открыл мир понятных и устойчивых приложений. +На первый взгляд может показаться, что эти три правила не имеют далеко идущих последствий, но они приводят к радикально иному взгляду на проектирование кода. Стоит ли оно того? Разработчики, которые отказались от старых привычек и начали последовательно использовать внедрение зависимостей, считают этот шаг переломным моментом в своей профессиональной жизни. Он открыл для них мир понятных и поддерживаемых приложений. -Но что, если в коде не используется последовательное внедрение зависимостей? Что если он построен на статических методах или синглтонах? Приносит ли это какие-либо проблемы? Приносит [, и очень существенные |global-state]. +Но что если код не использует инъекцию зависимостей последовательно? Что если он опирается на статические методы или синглтоны? Вызывает ли это какие-либо проблемы? [Да, вызывает, и очень серьез ные |global-state]. diff --git a/dependency-injection/ru/passing-dependencies.texy b/dependency-injection/ru/passing-dependencies.texy index f772722762..31202bcf86 100644 --- a/dependency-injection/ru/passing-dependencies.texy +++ b/dependency-injection/ru/passing-dependencies.texy @@ -12,7 +12,7 @@ </div> -Первые три метода применимы вообще во всех объектно-ориентированных языках, четвертый специфичен для презентаторов Nette, поэтому он обсуждается в [отдельной главе |best-practices:inject-method-attribute]. Сейчас мы подробнее рассмотрим каждый из этих вариантов и покажем их на конкретных примерах. +Теперь мы проиллюстрируем различные варианты на конкретных примерах. Внедрение через конструктор .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Зависимости передаются в качестве аргументов конструктору при создании объекта: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Эта форма полезна для обязательных зависимостей, которые абсолютно необходимы классу для функционирования, так как без них экземпляр не может быть создан. @@ -40,10 +40,10 @@ $service = new MyService($cache); ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ class MyService ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService DI контейнер передает зависимости в конструктор автоматически, используя [autowiring]. Аргументы, которые нельзя передавать таким образом (например, строки, числа, булевы) [записать в конфигурации |services#Arguments]. +Конструкторский ад .[#toc-constructor-hell] +------------------------------------------- + +Термин *ад конструктора* относится к ситуации, когда дочерний класс наследуется от родительского класса, конструктор которого требует зависимостей, и дочерний класс тоже требует зависимостей. Он также должен принять и передать зависимости родительского класса: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Проблема возникает, когда мы хотим изменить конструктор класса `BaseClass`, например, когда добавляется новая зависимость. Тогда мы должны изменить все конструкторы дочерних классов. Что превращает такую модификацию в ад. + +Как предотвратить это? Решение заключается в **приоритете композиции над наследованием**. + +Поэтому давайте спроектируем код по-другому. Мы будем избегать абстрактных классов `Base*`. Вместо того чтобы `MyClass` получал некоторую функциональность, наследуя от `BaseClass`, эта функциональность будет передаваться ему как зависимость: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Внедрение через сеттеры .[#toc-setter-injection] ================================================ -Зависимости передаются путем вызова метода, который хранит их в приватном свойстве. Обычное соглашение об именовании этих методов имеет вид `set*()`, поэтому они называются сеттерами. +Зависимости передаются путем вызова метода, который хранит их в приватном свойстве. Обычно эти методы именуются `set*()`, поэтому их называют сеттерами, но, конечно, они могут называться и по-другому. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Этот метод полезен для необязательных зависимостей, которые не нужны для функционирования класса, поскольку не гарантируется, что объект действительно получит их (т. е. что пользователь вызовет метод). @@ -90,16 +150,16 @@ $service->setCache($cache); В то же время, этот метод позволяет неоднократно вызывать сеттер для изменения зависимости. Если это нежелательно, добавьте проверку в метод, или, начиная с PHP 8.1, пометьте свойство `$cache` флагом `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ The setter call is defined in the DI container configuration in [section setup | ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ services: Зависимости передаются непосредственно в свойство: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Этот метод считается неприемлемым, поскольку свойство должно быть объявлено как `public`. Следовательно, мы не можем контролировать, будет ли переданная зависимость действительно иметь указанный тип (это было верно до версии PHP 7.4), и мы теряем возможность реагировать на новую назначенную зависимость своим собственным кодом, например, чтобы предотвратить последующие изменения. В то же время, свойство становится частью публичного интерфейса класса, что может быть нежелательно. @@ -137,12 +197,18 @@ $service->cache = $cache; ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Инъекция .[#toc-inject] +======================= + +В то время как предыдущие три метода в целом применимы во всех объектно-ориентированных языках, инъектирование методом, аннотацией или атрибутом *inject* специфично для презентаторов Nette. Они рассматриваются в [отдельной главе |best-practices:inject-method-attribute]. + + Какой путь выбрать? .[#toc-which-way-to-choose] =============================================== diff --git a/dependency-injection/ru/services.texy b/dependency-injection/ru/services.texy index f2da22424f..3af9d1f2e7 100644 --- a/dependency-injection/ru/services.texy +++ b/dependency-injection/ru/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Режим внедрения .[#toc-inject-mode] =================================== -Флаг `inject: true` используется для активации передачи зависимостей через публичные переменные с помощью аннотации [inject |best-practices:inject-method-attribute#Inject Annotations] и методов [inject*() |best-practices:inject-method-attribute#inject Methods]. +Флаг `inject: true` используется для активации передачи зависимостей через публичные переменные с помощью аннотации [inject |best-practices:inject-method-attribute#Inject Attributes] и методов [inject*() |best-practices:inject-method-attribute#inject Methods]. ```neon services: diff --git a/dependency-injection/sl/@home.texy b/dependency-injection/sl/@home.texy index 3246122b8f..0e76f144d6 100644 --- a/dependency-injection/sl/@home.texy +++ b/dependency-injection/sl/@home.texy @@ -5,8 +5,10 @@ Injekcija odvisnosti Vbrizgavanje odvisnosti je načrtovalski vzorec, ki bo temeljito spremenil vaš pogled na kodo in razvoj. Odpira pot v svet čisto zasnovanih in trajnostnih aplikacij. - [Kaj je vrivanje odvisnosti? |introduction] -- [Kaj je zabojnik DI? |container] +- [Globalno stanje in enojni elementi |global-state] - [Posredovanje odvisnosti |passing-dependencies] +- [Kaj je zabojnik DI? |container] +- [Pogosto zastavljena vprašanja |faq] Nette DI diff --git a/dependency-injection/sl/@left-menu.texy b/dependency-injection/sl/@left-menu.texy index dd817c041b..84855a4098 100644 --- a/dependency-injection/sl/@left-menu.texy +++ b/dependency-injection/sl/@left-menu.texy @@ -1,8 +1,10 @@ Injekcija odvisnosti ******************** - [Kaj je DI? |introduction] -- [Kaj je vsebnik DI? |container] +- [Globalno stanje in singletoni |global-state] - [Posredovanje odvisnosti |passing-dependencies] +- [Kaj je vsebnik DI? |container] +- [Pogosto zastavljena vprašanja |faq] Nette DI diff --git a/dependency-injection/sl/faq.texy b/dependency-injection/sl/faq.texy new file mode 100644 index 0000000000..52b344bd7a --- /dev/null +++ b/dependency-injection/sl/faq.texy @@ -0,0 +1,112 @@ +Pogosta vprašanja o DI (FAQ) +**************************** + + +Ali je DI drugo ime za IoC? .[#toc-is-di-another-name-for-ioc] +-------------------------------------------------------------- + +*Inversion of Control* (IoC) je načelo, ki se osredotoča na način izvajanja kode - ali vaša koda sproži zunanjo kodo ali pa je vaša koda vključena v zunanjo kodo, ki jo nato pokliče. +IoC je širok koncept, ki vključuje [dogodke |nette:glossary#Events], tako imenovano [hollywoodsko načelo |application:components#Hollywood style] in druge vidike. +Sestavni deli tega koncepta so tudi tovarne, ki so del [pravila #3: Naj to opravi tovarna |introduction#Rule #3: Let the Factory Handle It], in predstavljajo inverzijo za operator `new`. + +Pri *Dependency Injection* (DI) gre za to, kako en objekt ve za drug objekt, tj. za odvisnost. Gre za načrtovalski vzorec, ki zahteva izrecno posredovanje odvisnosti med objekti. + +Tako lahko rečemo, da je DI posebna oblika IoC. Vendar pa vse oblike IoC niso primerne z vidika čistosti kode. Med anti-vzorce na primer uvrščamo vse tehnike, ki delajo z [globalnim stanjem |global state], ali tako imenovani [Service Locator |#What is a Service Locator]. + + +Kaj je iskalnik storitev? .[#toc-what-is-a-service-locator] +----------------------------------------------------------- + +Iskalnik storitev je alternativa vbrizganju odvisnosti. Deluje tako, da ustvari osrednje skladišče, v katerem so registrirane vse razpoložljive storitve ali odvisnosti. Ko objekt potrebuje odvisnost, jo zahteva od iskalnika storitev. + +Vendar v primerjavi z vbrizgavanjem odvisnosti izgublja preglednost: odvisnosti niso neposredno posredovane objektom in jih zato ni mogoče zlahka prepoznati, zato je treba pregledati kodo, da bi odkrili in razumeli vse povezave. Tudi testiranje je bolj zapleteno, saj testiranim objektom ne moremo preprosto posredovati posnemovalnih objektov, temveč moramo to storiti prek iskalnika storitev. Poleg tega Service Locator moti načrtovanje kode, saj se morajo posamezni objekti zavedati njegovega obstoja, kar se razlikuje od vbrizgavanja odvisnosti, kjer objekti ne poznajo vsebnika DI. + + +Kdaj je bolje, da ne uporabljate DI? .[#toc-when-is-it-better-not-to-use-di] +---------------------------------------------------------------------------- + +Ni znanih težav, povezanih z uporabo oblikovnega vzorca Dependency Injection. Nasprotno, pridobivanje odvisnosti z globalno dostopnih lokacij povzroča [številne zaplete, |global-state] prav tako kot uporaba iskalnika storitev. +Zato je priporočljivo vedno uporabljati DI. To ni dogmatičen pristop, ampak preprosto ni bilo mogoče najti boljše alternative. + +Vendar pa obstajajo določene situacije, v katerih si predmetov ne posredujemo med seboj in jih pridobivamo iz globalnega prostora. Na primer pri razhroščevanju kode, ko moramo izpisati vrednost spremenljivke na določeni točki programa, izmeriti trajanje določenega dela programa ali zabeležiti sporočilo. +V takih primerih, ko gre za začasna dejanja, ki bodo pozneje odstranjena iz kode, je upravičena uporaba globalno dostopnega odlagalnika, štoparice ali loggerja. Ta orodja navsezadnje ne sodijo v zasnovo kode. + + +Ali ima uporaba DI svoje slabosti? .[#toc-does-using-di-have-its-drawbacks] +--------------------------------------------------------------------------- + +Ali ima uporaba vrivanja odvisnosti kakšne slabosti, na primer večjo zapletenost pisanja kode ali slabšo zmogljivost? Kaj izgubimo, ko začnemo pisati kodo v skladu z DI? + +DI ne vpliva na zmogljivost aplikacije ali pomnilniške zahteve. Delovanje vsebnika DI lahko igra vlogo, vendar je v primeru [Nette DI | nette-container] vsebnik sestavljen v čistem jeziku PHP, zato je njegova obremenitev med izvajanjem aplikacije v bistvu nična. + +Pri pisanju kode je treba ustvariti konstruktorje, ki sprejemajo odvisnosti. V preteklosti je bilo to lahko zamudno, vendar je zaradi sodobnih IDE in [spodbujanja lastnosti konstruktorjev |https://blog.nette.org/sl/php-8-0-popoln-pregled-novic#toc-constructor-property-promotion] zdaj to vprašanje nekaj sekund. Konstruktorje lahko preprosto ustvarite z uporabo Nette DI in vtičnika PhpStorm z le nekaj kliki. +Po drugi strani pa ni treba pisati singletonov in statičnih točk dostopa. + +Zaključimo lahko, da pravilno zasnovana aplikacija z uporabo DI ni niti krajša niti daljša v primerjavi z aplikacijo, ki uporablja singletone. Deli kode, ki delajo z odvisnostmi, se preprosto izločijo iz posameznih razredov in premaknejo na nova mesta, tj. v vsebnik DI in tovarne. + + +Kako prepisati starejšo aplikacijo na DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] +------------------------------------------------------------------------------------------- + +Prehod iz starejše aplikacije na vbrizgavanje odvisnosti je lahko zahteven proces, zlasti pri velikih in zapletenih aplikacijah. Pomembno je, da se tega procesa lotite sistematično. + +- Pri prehodu na vbrizgavanje odvisnosti je pomembno, da vsi člani ekipe razumejo uporabljena načela in prakse. +- Najprej opravite analizo obstoječe aplikacije, da ugotovite ključne komponente in njihove odvisnosti. Ustvarite načrt, kateri deli bodo preoblikovani in v kakšnem vrstnem redu. +- Izvedite vsebnik DI ali, še bolje, uporabite obstoječo knjižnico, kot je Nette DI. +- Postopoma izboljšajte vsak del aplikacije, da bo uporabljal vbrizgavanje odvisnosti. To lahko vključuje spreminjanje konstruktorjev ali metod, da sprejmejo odvisnosti kot parametre. +- Spremenite mesta v kodi, kjer se ustvarjajo objekti odvisnosti, tako da odvisnosti namesto tega vbrizga vsebnik. To lahko vključuje uporabo tovarn. + +Ne pozabite, da je prehod na vbrizgavanje odvisnosti naložba v kakovost kode in dolgoročno trajnost aplikacije. Čeprav je izvajanje teh sprememb morda zahtevno, mora biti rezultat čistejša, bolj modularna in zlahka testabilna koda, ki je pripravljena na prihodnje razširitve in vzdrževanje. + + +Zakaj ima sestava prednost pred dedovanjem? .[#toc-why-composition-is-preferred-over-inheritance] +------------------------------------------------------------------------------------------------- +Namesto dedovanja je bolje uporabiti sestavo, saj je tako mogoče kodo ponovno uporabiti, ne da bi bilo treba skrbeti za učinek sprememb. Tako zagotavlja bolj ohlapno vezavo, pri kateri nam ni treba skrbeti, da bi sprememba neke kode povzročila spremembo druge odvisne kode. Tipičen primer je situacija, ki je opredeljena kot [pekel konstruktorjev |passing-dependencies#Constructor hell]. + + +Ali se lahko vsebnik Nette DI Container uporablja zunaj sistema Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] +------------------------------------------------------------------------------------------------------------------------------ + +Absolutno. Nette DI Container je del sistema Nette, vendar je zasnovan kot samostojna knjižnica, ki se lahko uporablja neodvisno od drugih delov ogrodja. Preprosto jo namestite s programom Composer, ustvarite konfiguracijsko datoteko, ki opredeljuje vaše storitve, in nato z nekaj vrsticami kode PHP ustvarite vsebnik DI. +V svojih projektih lahko takoj začnete uporabljati prednosti vbrizgavanja odvisnosti (Dependency Injection). + +V poglavju [Nette DI Container |nette-container] je opisano, kako je videti določen primer uporabe, vključno s kodo. + + +Zakaj je konfiguracija v datotekah NEON? .[#toc-why-is-the-configuration-in-neon-files] +--------------------------------------------------------------------------------------- + +NEON je preprost in lahko berljiv konfiguracijski jezik, ki je bil razvit v Nette za nastavljanje aplikacij, storitev in njihovih odvisnosti. V primerjavi z JSON ali YAML v ta namen ponuja veliko bolj intuitivne in prilagodljive možnosti. V jeziku NEON lahko na naraven način opišete povezave, ki jih v jezikih Symfony in YAML sploh ne bi bilo mogoče zapisati ali pa le z zapletenim opisom. + + +Ali razčlenjevanje datotek NEON upočasnjuje aplikacijo? .[#toc-does-parsing-neon-files-slow-down-the-application] +----------------------------------------------------------------------------------------------------------------- + +Čeprav se datoteke NEON analizirajo zelo hitro, ta vidik ni pomemben. Razlog za to je, da se razčlenjevanje datotek izvede samo enkrat med prvim zagonom aplikacije. Po tem se ustvarja vsebniška koda DI, ki se shrani na disku in izvede za vsako naslednjo zahtevo brez potrebe po nadaljnjem razčlenjevanju. + +Tako deluje v produkcijskem okolju. Med razvojem se datoteke NEON analizirajo vsakič, ko se spremeni njihova vsebina, kar zagotavlja, da ima razvijalec vedno na voljo posodobljen vsebnik DI. Kot je bilo že omenjeno, je dejansko razčlenjevanje vprašanje enega trenutka. + + +Kako lahko v svojem razredu dostopam do parametrov iz konfiguracijske datoteke? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +-------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Ne pozabite na [pravilo št. 1: Pustite, da vam ga posredujejo |introduction#Rule #1: Let It Be Passed to You]. Če razred zahteva informacije iz konfiguracijske datoteke, nam ni treba ugotavljati, kako dostopati do teh informacij, temveč jih preprosto zahtevamo - na primer prek konstruktorja razreda. Predajo pa izvedemo v konfiguracijski datoteki. + +V tem primeru je `%myParameter%` nadomestek za vrednost parametra `myParameter`, ki bo posredovan konstruktorju `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Če želite posredovati več parametrov ali uporabiti samodejno povezovanje, je koristno, da [parametre zavijete v objekt |best-practices:passing-settings-to-presenters]. + + +Ali Nette podpira vmesnik PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] +------------------------------------------------------------------------------------------------- + +Vmesnik Nette DI Container ne podpira PSR-11 neposredno. Če pa potrebujete interoperabilnost med vsebnikom Nette DI Container in knjižnicami ali ogrodji, ki pričakujejo vmesnik PSR-11 Container, lahko ustvarite [preprost adapter |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], ki služi kot most med Nette DI Container in PSR-11. diff --git a/dependency-injection/sl/global-state.texy b/dependency-injection/sl/global-state.texy new file mode 100644 index 0000000000..48de9a56ae --- /dev/null +++ b/dependency-injection/sl/global-state.texy @@ -0,0 +1,312 @@ +Globalno stanje in singletoni +***************************** + +.[perex] +Opozorilo: naslednji konstrukti so simptomi slabe zasnove kode: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` ali `static::$var` + +Ali se v vaši kodi pojavlja katera od teh konstrukcij? Potem imate priložnost za izboljšave. Morda mislite, da so to pogosti konstrukti, ki jih vidimo v vzorčnih rešitvah različnih knjižnic in ogrodij. +Na žalost so še vedno jasen pokazatelj slabe zasnove. Skupno jim je eno: uporaba globalnega stanja. + +Zdaj zagotovo ne govorimo o neki akademski čistosti. Uporaba globalnega stanja in singletonov ima uničujoče učinke na kakovost kode. Njeno obnašanje postane nepredvidljivo, zmanjšuje produktivnost razvijalcev in sili vmesnike razredov, da lažejo o svojih resničnih odvisnostih. To zmede programerje. + +V tem poglavju bomo pokazali, kako je to mogoče. + + +Globalno medsebojno povezovanje .[#toc-global-interlinking] +----------------------------------------------------------- + +Temeljna težava globalne države je, da je globalno dostopna. To omogoča pisanje v podatkovno zbirko prek globalne (statične) metode `DB::insert()`. +V idealnem svetu bi moral biti objekt sposoben komunicirati le z drugimi objekti, ki so mu bili [neposredno posredovani |passing-dependencies]. +Če ustvarim dva objekta `A` in `B` in nikoli ne prenesem reference z `A` na `B`, potem niti `A`, niti `B` ne moreta dostopati do drugega objekta ali spreminjati njegovega stanja. +To je zelo zaželena lastnost kode. To je podobno, kot če bi imeli baterijo in žarnico; žarnica ne bo svetila, dokler ju ne povežete z žico. + +To ne velja za globalne (statične) spremenljivke ali singletone. Objekt `A` bi lahko *brezžično* dostopal do objekta `C` in ga spreminjal brez posredovanja reference, tako da bi poklical `C::changeSomething()`. +Če objekt `B` zagrabi tudi globalno spremenljivko `C`, potem lahko `A` in `B` medsebojno komunicirata prek `C`. + +Uporaba globalnih spremenljivk v sistem uvede novo obliko *brezžične* povezave, ki navzven ni vidna. +Ustvarja dimno zaveso, ki otežuje razumevanje in uporabo kode. +Razvijalci morajo prebrati vsako vrstico izvorne kode, da resnično razumejo odvisnosti. Namesto da bi se le seznanili z vmesnikom razredov. +Poleg tega gre za popolnoma nepotrebno povezovanje. + +.[note] +Kar zadeva obnašanje, ni razlike med globalno in statično spremenljivko. So enako škodljive. + + +Strašljivo delovanje na daljavo .[#toc-the-spooky-action-at-a-distance] +----------------------------------------------------------------------- + +"Spooky action at a distance" - tako je Albert Einstein leta 1935 poimenoval pojav v kvantni fiziki, ki ga je spravil ob živce. +Gre za kvantno prepletenost, katere posebnost je, da ko izmerite informacijo o enem delcu, takoj vplivate na drug delec, tudi če sta med seboj oddaljena na milijone svetlobnih let. +kar navidezno krši temeljni zakon vesolja, da nič ne more potovati hitreje od svetlobe. + +V svetu programske opreme lahko "strašljivo delovanje na daljavo" imenujemo situacijo, ko zaženemo proces, za katerega mislimo, da je izoliran (ker mu nismo posredovali nobenih referenc), vendar se na oddaljenih lokacijah sistema zgodijo nepričakovane interakcije in spremembe stanja, o katerih objektu nismo povedali. To se lahko zgodi le prek globalnega stanja. + +Predstavljajte si, da se pridružite skupini za razvoj projekta, ki ima veliko in zrelo bazo kode. Vaš novi vodja vas prosi, da izvedete novo funkcijo, in kot dober razvijalec začnete s pisanjem testa. Ker pa ste novinec v projektu, naredite veliko raziskovalnih testov tipa "kaj se zgodi, če pokličem to metodo". In poskušate napisati naslednji test: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // številko vaše kartice. + $cc->charge(100); +} +``` + +Po določenem času na svojem telefonu opazite obvestila iz banke, da je bilo ob vsakem zagonu na vašo kreditno kartico 🤦‍♂️ zaračunanih 100 dolarjev. + +Kako bi lahko test povzročil dejansko obremenitev? S kreditno kartico ni enostavno upravljati. Sodelovati morate s spletno storitvijo tretje osebe, poznati morate naslov URL te spletne storitve, prijaviti se morate in tako naprej. +Nobena od teh informacij ni vključena v test. Še huje, ne veste niti, kje so te informacije prisotne, in zato ne veste, kako zasmehovati zunanje odvisnosti, da se ob vsakem zagonu ne bi ponovno zaračunalo 100 USD. In kako naj bi kot nov razvijalec vedeli, da bo to, kar boste naredili, privedlo do tega, da boste za 100 dolarjev revnejši? + +To je strašljivo delovanje na daljavo! + +Ne preostane vam drugega, kot da se prekopate skozi veliko izvorne kode in pri tem sprašujete starejše in izkušenejše kolege, dokler ne razumete, kako delujejo povezave v projektu. +To je posledica dejstva, da ob pogledu na vmesnik razreda `CreditCard` ne morete določiti globalnega stanja, ki ga je treba inicializirati. Tudi pogled v izvorno kodo razreda vam ne bo povedal, katero metodo za inicializacijo je treba poklicati. V najboljšem primeru lahko poiščete globalno spremenljivko, do katere se dostopa, in na podlagi tega poskušate uganiti, kako jo inicializirati. + +Razredi v takem projektu so patološki lažnivci. Plačilna kartica se pretvarja, da jo lahko preprosto instancirate in pokličete metodo `charge()`. Vendar na skrivaj sodeluje z drugim razredom, `PaymentGateway`. Tudi njegov vmesnik pravi, da ga je mogoče inicializirati samostojno, v resnici pa iz neke konfiguracijske datoteke potegne poverilnice in tako naprej. +Razvijalcem, ki so napisali to kodo, je jasno, da `CreditCard` potrebuje `PaymentGateway`. Zato so kodo napisali na ta način. Toda za vsakogar, ki je novinec v projektu, je to popolna uganka in ovira učenje. + +Kako popraviti situacijo? Enostavno. **Pustite, da API razglasi odvisnosti.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Opazite, kako so odnosi v kodi nenadoma očitni. Z izjavo, da metoda `charge()` potrebuje `PaymentGateway`, vam ni treba nikogar spraševati, kako je koda medsebojno odvisna. Veste, da morate ustvariti njen primerek, in ko to poskušate storiti, naletite na dejstvo, da morate zagotoviti parametre dostopa. Brez njih se koda sploh ne bi mogla zagnati. + +In kar je najpomembneje, zdaj lahko zasmehujete plačilni prehod, tako da vam ne bo treba plačati 100 dolarjev vsakič, ko boste zagnali test. + +Globalno stanje povzroča, da lahko vaši objekti skrivaj dostopajo do stvari, ki niso deklarirane v njihovih API-jih, in posledično naredi vaše API-je patološke lažnivce. + +Morda o tem še niste razmišljali na ta način, toda kadarkoli uporabljate globalno stanje, ustvarjate skrivne brezžične komunikacijske kanale. Strašljivo delovanje na daljavo sili razvijalce, da preberejo vsako vrstico kode, da bi razumeli morebitne interakcije, zmanjšuje produktivnost razvijalcev in zmede nove člane ekipe. +Če ste kodo ustvarili vi, poznate prave odvisnosti, vsi, ki pridejo za vami, pa so nevedni. + +Ne pišite kode, ki uporablja globalno stanje, temveč raje prenašajte odvisnosti. To je vbrizgavanje odvisnosti. + + +Krhkost globalne države .[#toc-brittleness-of-the-global-state] +--------------------------------------------------------------- + +V kodi, ki uporablja globalno stanje in singletone, nikoli ni gotovo, kdaj in kdo je to stanje spremenil. To tveganje je prisotno že pri inicializaciji. Naslednja koda naj bi ustvarila povezavo s podatkovno bazo in inicializirala plačilni prehod, vendar vedno znova vrže izjemo, iskanje vzroka pa je izredno zamudno: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Podrobno morate pregledati kodo, da ugotovite, da objekt `PaymentGateway` brezžično dostopa do drugih objektov, od katerih nekateri zahtevajo povezavo s podatkovno bazo. Tako morate inicializirati podatkovno zbirko, preden `PaymentGateway`. Vendar vam to skriva dimna zavesa globalnega stanja. Koliko časa bi prihranili, če API vsakega razreda ne bi lagal in deklariral svojih odvisnosti? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Podobna težava se pojavi pri uporabi globalnega dostopa do povezave s podatkovno bazo: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +Pri klicu metode `save()` ni gotovo, ali je bila povezava s podatkovno bazo že ustvarjena in kdo je odgovoren za njeno ustvarjanje. Če bi na primer želeli spremeniti povezavo s podatkovno bazo sproti, morda za namene testiranja, bi verjetno morali ustvariti dodatne metode, kot sta `DB::reconnect(...)` ali `DB::reconnectForTest()`. + +Oglejmo si primer: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Kje se lahko prepričamo, da se testna podatkovna zbirka res uporablja, ko kličemo `$article->save()`? Kaj pa, če je metoda `Foo::doSomething()` spremenila globalno povezavo s podatkovno bazo? Da bi to ugotovili, bi morali pregledati izvorno kodo razreda `Foo` in verjetno še mnogih drugih razredov. Vendar bi takšen pristop zagotovil le kratkoročni odgovor, saj se lahko stanje v prihodnosti spremeni. + +Kaj pa, če povezavo s podatkovno bazo prenesemo v statično spremenljivko znotraj razreda `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +To ne spremeni ničesar. Problem je globalno stanje in ni pomembno, v katerem razredu se skriva. V tem primeru, tako kot v prejšnjem, nimamo pojma, v katero zbirko podatkov se zapiše, ko se kliče metoda `$article->save()`. Kdorkoli na oddaljenem koncu aplikacije lahko kadarkoli spremeni podatkovno zbirko z uporabo metode `Article::setDb()`. Pod našimi rokami. + +Zaradi globalnega stanja je naša aplikacija **izjemno občutljiva**. + +Vendar obstaja preprost način za reševanje te težave. Preprosto zahtevajte, da API razglasi odvisnosti, da se zagotovi pravilno delovanje. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Ta pristop odpravlja skrb zaradi skritih in nepričakovanih sprememb povezav s podatkovno bazo. Zdaj smo prepričani, kje je shranjen članek, in nobena sprememba kode znotraj drugega nepovezanega razreda ne more več spremeniti stanja. Koda ni več krhka, temveč stabilna. + +Ne pišite kode, ki uporablja globalno stanje, temveč raje prenašajte odvisnosti. Tako je na voljo vbrizgavanje odvisnosti (dependency injection). + + +Singleton .[#toc-singleton] +--------------------------- + +Singleton je oblikovni vzorec, ki po [definiciji |https://en.wikipedia.org/wiki/Singleton_pattern] iz znane publikacije Gang of Four omejuje razred na en primerek in mu omogoča globalni dostop. Izvedba tega vzorca je običajno podobna naslednji kodi: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // in druge metode, ki izvajajo funkcije razreda +} +``` + +Na žalost singleton v aplikacijo vnese globalno stanje. Kot smo pokazali zgoraj, je globalno stanje nezaželeno. Zato singleton velja za protivzorec. + +V svoji kodi ne uporabljajte singletonov in jih nadomestite z drugimi mehanizmi. Singletonov resnično ne potrebujete. Če pa morate zagotoviti obstoj enega primerka razreda za celotno aplikacijo, to prepustite [vsebniku DI |container]. +Tako ustvarite aplikacijski singleton ali storitev. S tem razred ne bo več zagotavljal svoje edinstvenosti (tj. ne bo imel metode `getInstance()` in statične spremenljivke) in bo izvajal le svoje funkcije. Tako bo prenehal kršiti načelo ene odgovornosti. + + +Globalno stanje v primerjavi s testi .[#toc-global-state-versus-tests] +---------------------------------------------------------------------- + +Pri pisanju testov predpostavljamo, da je vsak test izolirana enota in da vanj ne vstopa zunanje stanje. In nobeno stanje ne zapusti testov. Ko se test konča, mora zbiralnik smeti samodejno odstraniti vsako stanje, povezano s testom. S tem so testi izolirani. Zato lahko teste izvajamo v poljubnem vrstnem redu. + +Če pa so prisotna globalna stanja/singletoni, se vse te lepe predpostavke porušijo. Stanje lahko vstopi v test in izstopi iz njega. Nenadoma je vrstni red testov lahko pomemben. + +Da bi razvijalci sploh lahko testirali singletone, morajo pogosto omiliti njihove lastnosti, morda tako, da dovolijo zamenjavo primerka z drugim. Takšne rešitve so v najboljšem primeru kretnje, ki ustvarjajo kodo, ki jo je težko vzdrževati in razumeti. Vsak test ali metoda `tearDown()`, ki vpliva na katero koli globalno stanje, mora te spremembe razveljaviti. + +Globalno stanje je največji glavobol pri testiranju enot! + +Kako popraviti situacijo? Enostavno. Ne pišite kode, ki uporablja singletone, ampak raje prenašajte odvisnosti. To je vbrizgavanje odvisnosti. + + +Globalne konstante .[#toc-global-constants] +------------------------------------------- + +Globalno stanje ni omejeno na uporabo singletonov in statičnih spremenljivk, temveč se lahko uporablja tudi za globalne konstante. + +Konstante, katerih vrednost nam ne zagotavlja nobenih novih (`M_PI`) ali koristnih (`PREG_BACKTRACK_LIMIT_ERROR`) informacij, so nedvomno v redu. +Nasprotno pa konstante, ki služijo kot način za *brezžično* posredovanje informacij znotraj kode, niso nič drugega kot skrita odvisnost. Kot je `LOG_FILE` v naslednjem primeru. +Uporaba konstante `FILE_APPEND` je popolnoma pravilna. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +V tem primeru moramo parameter deklarirati v konstruktorju razreda `Foo`, da postane del API-ja: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Zdaj lahko posredujemo informacije o poti do datoteke za beleženje in jih po potrebi preprosto spremenimo, kar olajša testiranje in vzdrževanje kode. + + +Globalne funkcije in statične metode .[#toc-global-functions-and-static-methods] +-------------------------------------------------------------------------------- + +Poudariti želimo, da uporaba statičnih metod in globalnih funkcij sama po sebi ni problematična. Razložili smo neprimernost uporabe `DB::insert()` in podobnih metod, vedno pa je šlo za globalno stanje, shranjeno v statični spremenljivki. Metoda `DB::insert()` zahteva obstoj statične spremenljivke, ker shranjuje povezavo s podatkovno bazo. Brez te spremenljivke metode ne bi bilo mogoče izvesti. + +Uporaba determinističnih statičnih metod in funkcij, kot so `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` in številne druge, je popolnoma skladna z vbrizgavanjem odvisnosti. Te funkcije iz istih vhodnih parametrov vedno vrnejo enake rezultate in so zato predvidljive. Ne uporabljajo nobenega globalnega stanja. + +Vendar v PHP obstajajo funkcije, ki niso deterministične. Med njimi je na primer funkcija `htmlspecialchars()`. Njen tretji parameter, `$encoding`, če ni določen, je privzeta vrednost konfiguracijske možnosti `ini_get('default_charset')`. Zato je priporočljivo, da ta parameter vedno navedete, da se izognete morebitnemu nepredvidljivemu obnašanju funkcije. Nette to dosledno počne. + +Nekatere funkcije, kot so `strtolower()`, `strtoupper()` in podobne, so imele v bližnji preteklosti nedeterministično obnašanje in so bile odvisne od nastavitve `setlocale()`. To je povzročilo številne zaplete, najpogosteje pri delu s turškim jezikom. +Turški jezik namreč razlikuje med velikimi in malimi črkami `I` s piko in brez nje. Tako je `strtolower('I')` vrnil znak `ı`, `strtoupper('i')` pa znak `İ`, zaradi česar so aplikacije povzročale številne skrivnostne napake. +Vendar je bila ta težava odpravljena v različici PHP 8.2 in funkcije niso več odvisne od lokalnega jezika. + +To je lep primer, kako je globalno stanje prizadelo na tisoče razvijalcev po vsem svetu. Rešitev je bila zamenjava z vbrizgavanjem odvisnosti. + + +Kdaj je mogoče uporabiti globalno stanje? .[#toc-when-is-it-possible-to-use-global-state] +----------------------------------------------------------------------------------------- + +V nekaterih posebnih primerih je mogoče uporabiti globalno stanje. Na primer pri razhroščevanju kode, ko morate izpisati vrednost spremenljivke ali izmeriti trajanje določenega dela programa. V takih primerih, ki zadevajo začasna dejanja, ki bodo pozneje odstranjena iz kode, je upravičena uporaba globalno razpoložljivega odlagalnika ali štoparice. Ta orodja niso del zasnove kode. + +Drug primer so funkcije za delo z regularnimi izrazi `preg_*`, ki interno shranjujejo sestavljene regularne izraze v statični predpomnilnik v pomnilniku. Kadar isti regularni izraz večkrat pokličete v različnih delih kode, se sestavi samo enkrat. Predpomnilnik prihrani zmogljivost, poleg tega pa je za uporabnika popolnoma neviden, zato lahko takšno uporabo štejemo za zakonito. + + +Povzetek .[#toc-summary] +------------------------ + +Pokazali smo, zakaj je smiselno + +1) Odstranite vse statične spremenljivke iz kode +2) Deklarirajte odvisnosti +3) In uporabite vbrizgavanje odvisnosti + +Ko razmišljate o oblikovanju kode, imejte v mislih, da vsaka stran `static $foo` predstavlja težavo. Če želite, da bo vaša koda okolje, ki spoštuje DI, nujno popolnoma izkoreniniti globalno stanje in ga nadomestiti z vbrizgavanjem odvisnosti. + +Med tem postopkom boste morda ugotovili, da morate razred razdeliti, ker ima več kot eno odgovornost. Ne skrbite zaradi tega; prizadevajte si za načelo ene odgovornosti. + +*Zahvaljujem se Mišku Heveryju, čigar članki, kot je [Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], so podlaga za to poglavje.* diff --git a/dependency-injection/sl/introduction.texy b/dependency-injection/sl/introduction.texy index 71969a3c00..89e58a9aec 100644 --- a/dependency-injection/sl/introduction.texy +++ b/dependency-injection/sl/introduction.texy @@ -2,17 +2,17 @@ Kaj je vrivanje odvisnosti? *************************** .[perex] -V tem poglavju so predstavljene osnovne programske prakse, ki jih je treba upoštevati pri pisanju katere koli aplikacije. To so osnove, ki so potrebne za pisanje čiste, razumljive in vzdrževane kode. +V tem poglavju boste spoznali osnovne programerske prakse, ki jih morate upoštevati pri pisanju katere koli aplikacije. To so osnove, ki so potrebne za pisanje čiste, razumljive in vzdrževane kode. -Če se naučite teh pravil in jih upoštevate, vam bo Nette pomagal na vsakem koraku. Za vas bo opravila rutinska opravila in poskrbela za čim večje udobje, da se boste lahko osredotočili na samo logiko. +Če se naučite teh pravil in jih upoštevate, vam bo Nette pomagal na vsakem koraku. Za vas bo opravljala rutinska opravila in zagotavljala največje udobje, tako da se boste lahko osredotočili na samo logiko. -Načela, ki jih bomo prikazali tukaj, so precej preprosta. Ničesar vam ni treba skrbeti. +Načela, ki jih bomo prikazali tukaj, so precej preprosta. Ni vam treba skrbeti za ničesar. Se spomnite svojega prvega programa? .[#toc-remember-your-first-program] ------------------------------------------------------------------------ -Nimamo pojma, v katerem jeziku ste ga napisali, a če je bil PHP, bi bil verjetno videti nekako takole: +Ne vemo, v katerem jeziku ste ga napisali, vendar če je bil PHP, bi bil lahko videti nekako takole: ```php function addition(float $a, float $b): float @@ -25,31 +25,31 @@ echo addition(23, 1); // odtisi 24 Nekaj trivialnih vrstic kode, v katerih pa se skriva toliko ključnih konceptov. Da obstajajo spremenljivke. Da je koda razdeljena na manjše enote, ki so na primer funkcije. Da jim posredujemo vhodne argumente in da nam vrnejo rezultate. Manjkajo le še pogoji in zanke. -To, da funkciji posredujemo vhodne argumente in ta vrne rezultat, je povsem razumljiv koncept, ki se uporablja tudi na drugih področjih, na primer v matematiki. +Dejstvo, da funkcija sprejme vhodne podatke in vrne rezultat, je povsem razumljiv koncept, ki se uporablja tudi na drugih področjih, na primer v matematiki. -Funkcija ima signaturo, ki je sestavljena iz njenega imena, seznama parametrov in njihovih vrst ter nazadnje vrste vrnjene vrednosti. Kot uporabnike nas zanima signatura; običajno nam ni treba vedeti ničesar o notranji implementaciji. +Funkcija ima svoj podpis, ki je sestavljen iz njenega imena, seznama parametrov in njihovih tipov ter tipa vrnjene vrednosti. Kot uporabnike nas zanima signatura in nam običajno ni treba vedeti ničesar o notranji implementaciji. -Predstavljajte si, da je podpis funkcije videti takole: +Predstavljajte si, da bi bil podpis funkcije videti takole: ```php function addition(float $x): float ``` -Dodatek z enim parametrom? To je čudno... Kaj pa tole? +Dodatek z enim parametrom? To je čudno... Kaj pa to? ```php function addition(): float ``` -To je res čudno, kajne? Kako mislite, da se ta funkcija uporablja? +To je res čudno, kajne? Kako se funkcija uporablja? ```php echo addition(); // kaj natisne? ``` -Ob pogledu na takšno kodo smo zmedeni. Ne samo, da je ne bi razumel začetnik, takšne kode ne bi razumel niti izkušen programer. +Ob pogledu na takšno kodo bi bili zmedeni. Ne le, da je ne bi razumel začetnik, tudi izkušen programer ne bi razumel takšne kode. -Se sprašujete, kako bi bila takšna funkcija dejansko videti v notranjosti? Kje bi dobila seštevalnike? Verjetno bi jih dobila *nekako* sama od sebe, kot je to: +Se sprašujete, kako bi bila takšna funkcija dejansko videti v notranjosti? Kje bi dobila vsote? Verjetno bi jih *nekako* dobila sama, morda takole: ```php function addition(): float @@ -66,13 +66,13 @@ Izkazalo se je, da so v telesu funkcije skrite povezave z drugimi funkcijami (al Ne na ta način! .[#toc-not-this-way] ------------------------------------ -Zasnova, ki smo jo pravkar videli, je bistvo številnih negativnih lastnosti: +Zasnova, ki smo jo pravkar prikazali, je bistvo številnih negativnih lastnosti: -- podpis funkcije se je pretvarjal, da ne potrebuje dodatkov, kar nas je zmedlo +- podpis funkcije se je pretvarjal, da ne potrebuje seštevkov, kar nas je zmotilo - nimamo pojma, kako bi funkcijo pripravili do tega, da bi računala z dvema drugima številoma -- morali smo pogledati v kodo, da smo videli, kje vzame dodatke -- odkrili smo skrite vezi -- za popolno razumevanje moramo raziskati tudi te vezi +- morali smo pogledati kodo, da smo ugotovili, od kod prihajajo seštevki +- našli smo skrite odvisnosti +- za popolno razumevanje je treba preučiti tudi te odvisnosti In ali je sploh naloga funkcije seštevanja, da pridobiva vhodne podatke? Seveda ni. Njena naloga je le dodajanje. @@ -93,20 +93,20 @@ Pravilo št. 1: Naj vam ga prenesejo .[#toc-rule-1-let-it-be-passed-to-you] Najpomembnejše pravilo je: **Vse podatke, ki jih potrebujejo funkcije ali razredi, jim je treba posredovati**. -Namesto da izumljate skrite mehanizme, ki bi jim pomagali, da bi do njih nekako prišli sami, jim preprosto posredujte parametre. Prihranili boste čas, ki je potreben za izumljanje skritega načina, ki zagotovo ne bo izboljšal vaše kode. +Namesto da bi izumljali skrite načine, kako sami dostopajo do podatkov, jim preprosto posredujte parametre. Prihranili boste čas, ki bi ga porabili za izumljanje skritih poti, ki zagotovo ne bodo izboljšale vaše kode. -Če boste vedno in povsod upoštevali to pravilo, ste na poti do kode brez skritih vezav. Na poti do kode, ki ni razumljiva le avtorju, temveč tudi vsem, ki jo kasneje preberejo. Kjer je vse razumljivo iz podpisov funkcij in razredov in kjer ni treba iskati skritih skrivnosti v implementaciji. +Če boste vedno in povsod upoštevali to pravilo, ste na poti do kode brez skritih odvisnosti. Do kode, ki je razumljiva ne le avtorju, temveč tudi vsakomur, ki jo prebere pozneje. Kjer je vse razumljivo iz podpisov funkcij in razredov in ni treba iskati skritih skrivnosti v implementaciji. -Ta tehnika se strokovno imenuje **vbrizgavanje odvisnosti**. Podatki pa se imenujejo **odvisnosti**, vendar gre za preprosto posredovanje parametrov, nič več. +Ta tehnika se strokovno imenuje **vbrizgavanje odvisnosti**. Ti podatki pa se imenujejo **odvisnosti**. To je le običajno posredovanje parametrov, nič več. .[note] -Ne zamenjujte vbrizgavanja odvisnosti, ki je načrtovalski vzorec, z "vsebnikom za vbrizgavanje odvisnosti", ki je orodje, nekaj povsem drugega. O vsebnikih bomo razpravljali pozneje. +Ne zamenjujte vbrizgavanja odvisnosti, ki je načrtovalski vzorec, z "vsebnikom za vbrizgavanje odvisnosti", ki je orodje, nekaj diametralno različnega. S kontejnerji se bomo ukvarjali pozneje. Od funkcij do razredov .[#toc-from-functions-to-classes] -------------------------------------------------------- -In kako so s tem povezani razredi? Razred je bolj zapletena entiteta kot preprosta funkcija, vendar tudi tu velja pravilo št. 1. Obstaja le [več načinov za posredovanje argumentov |passing-dependencies]. Na primer, precej podobno kot pri funkciji: +In kako so razredi povezani? Razred je bolj zapletena enota kot preprosta funkcija, vendar tudi tu v celoti velja pravilo št. 1. Obstaja le [več načinov za posredovanje argumentov |passing-dependencies]. Na primer, zelo podobno kot pri funkciji: ```php class Math @@ -121,7 +121,7 @@ $math = new Math; echo $math->addition(23, 1); // 24 ``` -ali z uporabo drugih metod ali neposredno s konstruktorjem: +Ali prek drugih metod ali neposredno prek konstruktorja: ```php class Addition @@ -149,9 +149,9 @@ Oba primera sta v celoti skladna z vbrizgavanjem odvisnosti. Primeri iz resničnega življenja .[#toc-real-life-examples] ---------------------------------------------------------- -V resničnem svetu ne boste pisali razredov za seštevanje števil. Preidimo na primere iz resničnega sveta. +V resničnem svetu ne boste pisali razredov za seštevanje številk. Preidimo na praktične primere. -Imejmo razred `Article`, ki predstavlja blogovski članek: +Imejmo razred `Article`, ki predstavlja objavo na blogu: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Metoda `save()` shrani članek v tabelo podatkovne zbirke. Implementacija z uporabo [podatkovne baze Nette |database:] bi bila prava mala malica, če ne bi bilo ene zadrege: kje naj `Article` dobi povezavo s podatkovno bazo, tj. objekt razreda `Nette\Database\Connection`? +Metoda `save()` bo članek shranila v tabelo podatkovne zbirke. Implementacija z uporabo [podatkovne baze Nette |database:] bo prava mala malica, če ne bi bilo ene zadrege: kje `Article` dobi povezavo s podatkovno bazo, tj. objekt razreda `Nette\Database\Connection`? -Zdi se, da imamo veliko možnosti. Lahko jo vzame od nekod iz statične spremenljivke. Ali pa jo podeduje od razreda, ki bo zagotovil povezavo s podatkovno bazo. Ali pa izkoristimo prednosti razreda [singleton |global-state#Singleton]. Ali pa tako imenovane fasade, ki se uporabljajo v Laravelu: +Zdi se, da imamo veliko možnosti. Lahko jo vzame nekje iz statične spremenljivke. Ali pa podeduje od razreda, ki zagotavlja povezavo s podatkovno bazo. Ali pa izkoristi prednosti [enojnega razreda (singleton) |global-state#Singleton]. Ali pa uporabimo tako imenovane fasade, ki se uporabljajo v Laravelu: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ Odlično, problem smo rešili. Ali pa smo? -Spomnimo se na [pravilo št. 1: Naj vam ga prenesejo |#rule #1: Let It Be Passed to You]: vse odvisnosti, ki jih razred potrebuje, mu morajo biti posredovane. Ker če tega ne storimo in prekršimo pravilo, smo se podali na pot umazane kode, polne skritih povezav, nerazumljivosti, rezultat pa bo aplikacija, ki jo je neprijetno vzdrževati in razvijati. +Spomnimo se na [pravilo št. 1: Naj vam bo posredovano |#rule #1: Let It Be Passed to You]: vse odvisnosti, ki jih razred potrebuje, mu morajo biti posredovane. Če namreč prekršimo to pravilo, smo se podali na pot umazane kode, polne skritih odvisnosti, nerazumljivosti, rezultat pa bo aplikacija, ki jo bo boleče vzdrževati in razvijati. -Uporabnik razreda `Article` nima pojma, kam metoda `save()` shrani članek. V tabeli podatkovne zbirke? V kateri, produkcijski ali razvojni? In kako je to mogoče spremeniti? +Uporabnik razreda `Article` nima pojma, kam metoda `save()` shrani članek. V tabeli podatkovne zbirke? V kateri, produkcijski ali testni? In kako jo lahko spremeni? -Uporabnik mora pogledati, kako je implementirana metoda `save()`, da bi našel uporabo metode `DB::insert()`. Torej mora iskati naprej, da bi ugotovil, kako ta metoda pridobi povezavo s podatkovno bazo. Skrite vezi pa lahko tvorijo precej dolgo verigo. +Uporabnik mora pogledati, kako je implementirana metoda `save()`, in najde uporabo metode `DB::insert()`. Torej mora iskati naprej, da bi ugotovil, kako ta metoda pridobi povezavo s podatkovno bazo. In skrite odvisnosti lahko tvorijo precej dolgo verigo. -Skrite vezi, Laravelove fasade ali statične spremenljivke niso nikoli prisotne v čisti, dobro zasnovani kodi. V čisti in dobro zasnovani kodi se posredujejo argumenti: +V čisti in dobro zasnovani kodi nikoli ni skritih odvisnosti, Laravelovih fasad ali statičnih spremenljivk. V čisti in dobro zasnovani kodi se posredujejo argumenti: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Še bolj praktično, kot bomo videli v nadaljevanju, je uporabiti konstruktor: +Še bolj praktičen pristop, kot bomo videli pozneje, je uporaba konstruktorja: ```php class Article @@ -245,9 +245,9 @@ class Article ``` .[note] -Če ste izkušen programer, boste morda pomislili, da `Article` sploh ne bi smel imeti metode `save()`, da bi moral biti čista podatkovna komponenta, za shranjevanje pa bi moralo skrbeti ločeno skladišče. To je smiselno. Vendar bi to močno preseglo temo, ki je vbrizgavanje odvisnosti, in poskus podajanja preprostih primerov. +Če ste izkušen programer, boste morda pomislili, da `Article` sploh ne bi smel imeti metode `save()`; predstavljal bi izključno podatkovno komponento, za shranjevanje pa bi moral skrbeti ločen repozitorij. To je smiselno. Toda to bi daleč preseglo obseg teme, ki je vbrizgavanje odvisnosti, in prizadevanje, da bi navedli preproste primere. -Če boste na primer napisali razred, ki za svoje delovanje potrebuje podatkovno zbirko, ne razmišljajte, od kod jo dobiti, temveč naj vam jo posreduje. Morda kot parameter konstruktorja ali druge metode. Razglasite odvisnosti. Izpostavite jih v API svojega razreda. Dobili boste razumljivo in predvidljivo kodo. +Če napišete razred, ki za svoje delovanje potrebuje na primer podatkovno zbirko, si ne izmišljujte, od kod jo dobiti, temveč naj bo posredovana. Bodisi kot parameter konstruktorja ali druge metode. Priznajte odvisnosti. Priznajte jih v API svojega razreda. Dobili boste razumljivo in predvidljivo kodo. Kaj pa ta razred, ki beleži sporočila o napakah: @@ -266,7 +266,7 @@ Kaj menite, ali smo upoštevali [pravilo št. 1: Naj vam ga prenesejo |#rule #1: Nismo. -Ključno informacijo, imenik dnevniške datoteke, razred pridobi iz konstante. +Ključne informacije, tj. imenik z datoteko dnevnika, razred pridobi iz konstante. Oglejte si primer uporabe: @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Ali lahko brez poznavanja izvajanja odgovorite na vprašanje, kje so sporočila zapisana? Ali bi lahko sklepali, da je za delovanje potreben obstoj konstante LOG_DIR? In ali bi lahko ustvarili drugi primerek, ki bi pisal na drugo lokacijo? Zagotovo ne. +Ali lahko brez poznavanja izvajanja odgovorite na vprašanje, kje so zapisana sporočila? Ali bi uganili, da je obstoj konstante `LOG_DIR` potreben za njeno delovanje? In ali bi lahko ustvarili drugi primerek, ki bi pisal na drugo lokacijo? Zagotovo ne. Popravimo razred: @@ -295,7 +295,7 @@ class Logger } ``` -Razred je zdaj veliko jasnejši, bolj nastavljiv in zato bolj uporaben. +Razred je zdaj veliko bolj razumljiv, nastavljiv in s tem uporaben. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Ampak meni je vseeno! .[#toc-but-i-don-t-care] ---------------------------------------------- -*"Ko ustvarim objekt Article in kličem save(), se ne želim ukvarjati s podatkovno bazo, temveč želim le, da se shrani v tisto, ki sem jo določil v konfiguraciji. "* +*"Ko ustvarim objekt Article in kličem save(), se ne želim ukvarjati s podatkovno bazo; želim le, da se shrani v tisto, ki sem jo določil v konfiguraciji."* -*"Ko uporabljam Logger, želim samo, da se sporočilo zapiše, in se ne želim ukvarjati s tem, kam. Naj se uporabijo globalne nastavitve. "* +*"Ko uporabljam Logger, želim samo, da se sporočilo zapiše, in se ne želim ukvarjati s tem, kam. Naj se uporabijo globalne nastavitve."* -To so pravilne pripombe. +To so veljavne pripombe. -Kot primer vzemimo razred, ki pošilja glasila in beleži, kako je to potekalo: +Kot primer si oglejmo razred, ki pošilja glasila in beleži, kako je potekalo pošiljanje: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -Izboljšani `Logger`, ki ne uporablja več konstante `LOG_DIR`, zahteva v konstruktorju pot do datoteke. Kako to rešiti? Razredu `NewsletterDistributor` je vseeno, kje so sporočila zapisana, želi jih le zapisati. +Izboljšani `Logger`, ki ne uporablja več konstante `LOG_DIR`, zahteva navedbo poti do datoteke v konstruktorju. Kako to rešiti? Razredu `NewsletterDistributor` je vseeno, kje so sporočila zapisana; želi jih le zapisati. -Rešitev je spet [pravilo št. 1: Naj vam ga prenesejo |#rule #1: Let It Be Passed to You]: vse podatke, ki jih razred potrebuje, mu posredujemo. +Rešitev je spet [pravilo št. 1: Naj vam ga prenesejo |#rule #1: Let It Be Passed to You]: Predajajte vse podatke, ki jih razred potrebuje. -Tako konstruktorju posredujemo pot do dnevnika, ki ga nato uporabimo za ustvarjanje objekta `Logger`? +Ali to torej pomeni, da skozi konstruktor posredujemo pot do dnevnika, ki jo nato uporabimo pri ustvarjanju predmeta `Logger`? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Ne tako! Kajti pot **ne pripada** podatkom, ki jih potrebuje razred `NewsletterDistributor`; ta potrebuje `Logger`. Razred potrebuje sam logger. In to je tisto, kar bomo posredovali naprej: +Ne, ne tako! Pot ne sodi med podatke, ki jih potrebuje razred `NewsletterDistributor`; pravzaprav jih potrebuje razred `Logger`. Ali vidite razliko? Razred `NewsletterDistributor` potrebuje sam dnevnik. Zato bomo posredovali le tega: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Zdaj je iz podpisov razreda `NewsletterDistributor` jasno razvidno, da je beleženje del njegove funkcionalnosti. In naloga zamenjave loggerja z drugim, morda za namene testiranja, je precej trivialna. -Poleg tega, če spremenimo konstruktor razreda `Logger`, to ne bo imelo nobenega vpliva na naš razred. +Zdaj je iz podpisov razreda `NewsletterDistributor` razvidno, da je del njegove funkcionalnosti tudi beleženje. In naloga zamenjave loggerja z drugim, morda za testiranje, je povsem trivialna. +Poleg tega, če se konstruktor razreda `Logger` spremeni, to ne bo vplivalo na naš razred. -Pravilo št. 2: Vzemite, kar je vaše .[#toc-rule-2-take-what-is-yours] ---------------------------------------------------------------------- +Pravilo 2: Vzemi, kar je tvoje .[#toc-rule-2-take-what-s-yours] +--------------------------------------------------------------- -Ne pustite se zavesti in ne dovolite, da bi vam bili posredovani parametri vaših odvisnosti. Odvisnosti posredujejte neposredno. +Ne pustite se zavajati in ne dovolite, da bi prešli v odvisnost od vaših odvisnikov. Prepustite le svoje lastne odvisnosti. -Tako bo koda, ki uporablja druge predmete, popolnoma neodvisna od sprememb njihovih konstruktorjev. Njen programski vmesnik API bo resničnejši. In kar je najpomembneje, te odvisnosti bo trivialno zamenjati z drugimi. +Zaradi tega bo koda, ki uporablja druge predmete, popolnoma neodvisna od sprememb v njihovih konstruktorjih. Njen API bo bolj resničen. Predvsem pa bo te odvisnosti trivialno zamenjati z drugimi. -Nov član družine .[#toc-a-new-member-of-the-family] ---------------------------------------------------- +Novi član družine .[#toc-new-family-member] +------------------------------------------- -Razvojna skupina se je odločila, da bo ustvarila drugi logger, ki bo pisal v podatkovno zbirko. Zato smo ustvarili razred `DatabaseLogger`. Tako imamo dva razreda, `Logger` in `DatabaseLogger`, eden piše v datoteko, drugi pa v podatkovno zbirko ... se vam ne zdi, da je v tem imenu nekaj čudnega? -Ali ne bi bilo bolje preimenovati `Logger` v `FileLogger`? Zagotovo bi bilo. +Razvojna skupina se je odločila, da bo ustvarila drugi logger, ki bo pisal v podatkovno zbirko. Zato ustvarimo razred `DatabaseLogger`. Torej imamo dva razreda, `Logger` in `DatabaseLogger`, eden piše v datoteko, drugi v podatkovno zbirko ... se vam poimenovanje ne zdi čudno? +Ali ne bi bilo bolje preimenovati `Logger` v `FileLogger`? Vsekakor da. -Toda naredimo to pametno. Ustvarili bomo vmesnik pod prvotnim imenom: +Toda naredimo to pametno. Ustvarimo vmesnik pod prvotnim imenom: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...ki ga bosta implementirala oba loggerja: +... ki ga bosta izvajala oba zapisovalnika: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -Na ta način ne bo treba ničesar spreminjati v preostalem delu kode, kjer se uporablja dnevnik. Na primer, konstruktor razreda `NewsletterDistributor` bo še vedno zadovoljen s tem, da bo kot parameter zahteval `Logger`. Od nas pa bo odvisno, kateri primerek mu bomo posredovali. +Zaradi tega v preostalem delu kode, kjer se dnevnik uporablja, ne bo treba ničesar spreminjati. Na primer, konstruktor razreda `NewsletterDistributor` se bo še vedno zadovoljil s tem, da bo kot parameter zahteval `Logger`. Od nas pa bo odvisno, kateri primerek bomo posredovali. -**Tudi zato imenom vmesnikov nikoli ne dajemo končnice `Interface` ali predpone `I`.** V nasprotnem primeru bi bilo nemogoče razviti tako lepo kodo. +**Zato imenom vmesnikov nikoli ne dodajamo končnice `Interface` ali predpone `I`.** V nasprotnem primeru kode ne bi bilo mogoče tako lepo razviti. Houston, imamo težavo .[#toc-houston-we-have-a-problem] ------------------------------------------------------- -Medtem ko smo v celotni aplikaciji lahko zadovoljni z enim samim primerkom loggerja, ne glede na to, ali gre za datoteko ali zbirko podatkov, in ga preprosto posredujemo povsod, kjer se kaj beleži, pa je v primeru razreda `Article` povsem drugače. Dejansko ustvarjamo njegove instance po potrebi, po možnosti večkrat. Kako ravnati z vezavo na podatkovno zbirko v njegovem konstruktorju? +Medtem ko lahko v celotni aplikaciji uporabimo en sam primerek loggerja, ki temelji na datoteki ali podatkovni zbirki, in ga preprosto posredujemo povsod, kjer se kaj beleži, je pri razredu `Article` precej drugače. Njegove instance ustvarjamo po potrebi, tudi večkrat. Kako ravnati z odvisnostjo od podatkovne zbirke v njegovem konstruktorju? -Kot primer lahko uporabimo krmilnik, ki naj bi po oddaji obrazca shranil članek v zbirko podatkov: +Primer je lahko krmilnik, ki mora po oddaji obrazca shraniti članek v zbirko podatkov: ```php class EditController extends Controller @@ -437,9 +437,9 @@ class EditController extends Controller } ``` -Možna rešitev se ponuja neposredno: objekt podatkovne zbirke naj konstruktor posreduje na naslov `EditController` in uporabi `$article = new Article($this->db)`. +Možna rešitev je očitna: konstruktorju `EditController` posredujemo objekt podatkovne zbirke in uporabimo `$article = new Article($this->db)`. -Tako kot v prejšnjem primeru s `Logger` in potjo do datoteke to ni pravilen pristop. Podatkovna baza ni odvisna od `EditController`, temveč od `Article`. Zato je posredovanje podatkovne baze v nasprotju s [pravilom 2: vzemi, kar je tvoje |#rule #2: take what is yours]. Ko spremenimo konstruktor razreda `Article` (dodamo nov parameter), bo treba spremeniti tudi kodo na vseh mestih, kjer se ustvarjajo primerki. Ufff. +Tako kot v prejšnjem primeru s `Logger` in potjo do datoteke to ni pravi pristop. Podatkovna baza ni odvisna od `EditController`, temveč od `Article`. Posredovanje podatkovne baze je v nasprotju s [pravilom 2: vzemi, kar je tvoje |#rule #2: take what's yours]. Če se konstruktor razreda `Article` spremeni (doda se nov parameter), boste morali spremeniti kodo povsod, kjer se ustvarjajo primerki. Ufff. Houston, kaj predlagate? @@ -447,11 +447,11 @@ Houston, kaj predlagate? Pravilo št. 3: Pustite, da se s tem ukvarja tovarna .[#toc-rule-3-let-the-factory-handle-it] -------------------------------------------------------------------------------------------- -Z odstranitvijo skritih vezi in posredovanjem vseh odvisnosti kot argumentov dobimo bolj nastavljive in prilagodljive razrede. Zato potrebujemo nekaj drugega za ustvarjanje in konfiguriranje teh bolj prilagodljivih razredov. Imenovali ga bomo tovarne. +Z odpravo skritih odvisnosti in posredovanjem vseh odvisnosti kot argumentov smo pridobili bolj nastavljive in prilagodljive razrede. Zato potrebujemo nekaj drugega, kar bo ustvarilo in konfiguriralo te bolj prilagodljive razrede za nas. Imenovali ga bomo tovarne. Velja pravilo: če ima razred odvisnosti, ustvarjanje njihovih primerkov prepustite tovarni. -Tovarne so pametnejša zamenjava za operator `new` v svetu vbrizgavanja odvisnosti. +Tovarne so pametnejša zamenjava za operater `new` v svetu vbrizgavanja odvisnosti. .[note] Ne zamenjujte z oblikovnim vzorcem *factory method*, ki opisuje poseben način uporabe tovarn in ni povezan s to temo. @@ -460,7 +460,7 @@ Ne zamenjujte z oblikovnim vzorcem *factory method*, ki opisuje poseben način u Tovarna .[#toc-factory] ----------------------- -Tovarna je metoda ali razred, ki proizvaja in konfigurira predmete. Razred `Article`, ki proizvaja, imenujemo `ArticleFactory` in je lahko videti takole: +Tovarna je metoda ali razred, ki ustvarja in konfigurira predmete. Razred, ki proizvaja `Article`, bomo poimenovali `ArticleFactory`, izgledal pa bi lahko takole: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Njegova uporaba v krmilniku bi bila naslednja: +Njegova uporaba v krmilniku je naslednja: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -Na tej točki je edini del kode, ki se mora odzvati na spremembo podpisa konstruktorja razreda `Article`, sama tovarna `ArticleFactory`. Na nobeno drugo kodo, ki dela z objekti `Article`, kot je `EditController`, to ne bo vplivalo. +Če se spremeni podpis konstruktorja razreda `Article`, je na tej točki edini del kode, ki se mora odzvati, sam konstruktor `ArticleFactory`. Na vso drugo kodo, ki dela z objekti `Article`, kot je `EditController`, to ne bo vplivalo. -Morda se zdaj trkate po čelu in se sprašujete, ali smo si sploh pomagali. Količina kode se je povečala in celotna stvar je videti sumljivo zapletena. +Morda se sprašujete, ali smo stvari dejansko izboljšali. Količina kode se je povečala in vse skupaj je videti sumljivo zapleteno. -Ne skrbite, kmalu bomo prišli do vsebnika Nette DI. Ta ima v rokavu številne ase, s katerimi bo gradnja aplikacij z uporabo vbrizgavanja odvisnosti izjemno preprosta. Namesto razreda `ArticleFactory` bo na primer dovolj, da [napišemo preprost vmesnik |factory]: +Ne skrbite, kmalu bomo prišli do vsebnika Nette DI. Ta pa ima v rokavu več trikov, ki bodo močno poenostavili gradnjo aplikacij z uporabo vbrizgavanja odvisnosti. Na primer, namesto razreda `ArticleFactory` boste morali [napisati |factory] le [preprost vmesnik |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Toda prehitevamo, počakajte :-) +Vendar prehitevamo sami sebe; bodite potrpežljivi :-) Povzetek .[#toc-summary] ------------------------ -Na začetku tega poglavja smo vam obljubili, da vam bomo pokazali način oblikovanja čiste kode. Samo dajte razredom +Na začetku tega poglavja smo vam obljubili, da vam bomo predstavili postopek za oblikovanje čiste kode. Vse, kar je potrebno, je, da razredi: -- [odvisnosti, ki jih potrebujejo |#Rule #1: Let It Be Passed to You] -- [in ne tistih, ki jih neposredno ne potrebujejo |#Rule #2: Take What Is Yours] -- [in da je predmete z odvisnostmi najbolje izdelati v tovarnah |#Rule #3: Let the Factory Handle it] +- [posredujejo odvisnosti, ki jih potrebujejo |#Rule #1: Let It Be Passed to You] +- [nasprotno, ne posredujejo tistega, česar neposredno ne potrebujejo |#Rule #2: Take What's Yours] +- [in da je predmete z odvisnostmi najbolje ustvariti v tovarnah |#Rule #3: Let the Factory Handle it] -Morda se na prvi pogled ne zdi tako, vendar imajo ta tri pravila daljnosežne posledice. Privedejo do korenito drugačnega pogleda na oblikovanje kode. Ali je vredno? Programerji, ki so zavrgli stare navade in začeli dosledno uporabljati vbrizgavanje odvisnosti, menijo, da je to ključni trenutek v njihovem poklicnem življenju. Odprl jim je svet jasnih in trajnostnih aplikacij. +Na prvi pogled se zdi, da ta tri pravila nimajo daljnosežnih posledic, vendar vodijo do korenito drugačnega pogleda na oblikovanje kode. Ali je vredno? Razvijalci, ki so opustili stare navade in začeli dosledno uporabljati vbrizgavanje odvisnosti, menijo, da je ta korak ključen trenutek v njihovem poklicnem življenju. Z njim se jim je odprl svet preglednih in vzdržljivih aplikacij. -Kaj pa, če koda ne uporablja dosledno vbrizgavanja odvisnosti? Kaj pa, če je zgrajena na statičnih metodah ali singletonih? Ali to prinaša kakšne težave? [Težave so, in to zelo velike |global-state]. +Kaj pa, če koda ne uporablja dosledno vbrizgavanja odvisnosti? Kaj pa, če se zanaša na statične metode ali enojne metode? Ali to povzroča težave? [Da, povzroča, in to zelo temeljne |global-state]. diff --git a/dependency-injection/sl/passing-dependencies.texy b/dependency-injection/sl/passing-dependencies.texy index 9313d1aff4..451f940105 100644 --- a/dependency-injection/sl/passing-dependencies.texy +++ b/dependency-injection/sl/passing-dependencies.texy @@ -12,7 +12,7 @@ Argumente ali "odvisnosti" v terminologiji DI lahko razredom posredujete na nasl </div> -Prve tri metode veljajo na splošno v vseh objektno usmerjenih jezikih, četrta pa je specifična za predstavnike Nette, zato je obravnavana v [posebnem poglavju |best-practices:inject-method-attribute]. Zdaj si bomo podrobneje ogledali vsako od teh možnosti in jih prikazali s konkretnimi primeri. +Različne različice bomo zdaj ponazorili s konkretnimi primeri. Vbrizgavanje konstruktorjev .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Vbrizgavanje konstruktorjev .[#toc-constructor-injection] Odvisnosti se konstruktorju ob ustvarjanju predmeta posredujejo kot argumenti: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Ta oblika je uporabna za obvezne odvisnosti, ki jih razred nujno potrebuje za delovanje, saj brez njih primerka ni mogoče ustvariti. @@ -40,10 +40,10 @@ Od različice PHP 8.0 lahko uporabimo krajšo obliko zapisa ([constructor proper ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ Od PHP 8.1 lahko lastnost označimo z zastavico `readonly`, ki izjavlja, da se v ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService Kontejner DI samodejno posreduje odvisnosti konstruktorju z uporabo [samodejnega povezovanja |autowiring]. Argumente, ki jih ni mogoče posredovati na ta način (npr. nize, številke, logične vrednosti), [zapiše v konfiguracijo |services#Arguments]. +Konstruktorski pekel .[#toc-constructor-hell] +--------------------------------------------- + +Izraz *konstruktorski pekel* se nanaša na situacijo, ko potomec podeduje od starševskega razreda, katerega konstruktor zahteva odvisnosti, in tudi potomec zahteva odvisnosti. Prav tako mora prevzeti in prenesti odvisnosti starševskega razreda: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Težava se pojavi, ko želimo spremeniti konstruktor razreda `BaseClass`, na primer ko dodamo novo odvisnost. Takrat moramo spremeniti tudi vse konstruktorje otrok. Zaradi tega je takšna sprememba pekel. + +Kako to preprečiti? Rešitev je, da **prednost dajemo kompoziciji pred dedovanjem**. + +Zato kodo oblikujmo drugače. Izogibali se bomo abstraktnim `Base*` razredom. Namesto da bi razred `MyClass` pridobil neko funkcionalnost s podedovanjem od razreda `BaseClass`, mu bo ta funkcionalnost posredovana kot odvisnost: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Vbrizgavanje množice .[#toc-setter-injection] ============================================= -Odvisnosti se posredujejo s klicem metode, ki jih shrani v zasebne lastnosti. Običajno je poimenovanje teh metod v obliki `set*()`, zato jih imenujemo setterji. +Odvisnosti se posredujejo s klicem metode, ki jih shrani v zasebne lastnosti. Običajno so te metode poimenovane v obliki `set*()`, zato se imenujejo setterji, seveda pa se lahko imenujejo tudi kako drugače. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Ta metoda je uporabna za neobvezne odvisnosti, ki niso potrebne za delovanje razreda, saj ni zagotovljeno, da jih bo objekt dejansko prejel (tj. da bo uporabnik poklical metodo). @@ -90,16 +150,16 @@ Ta metoda je uporabna za neobvezne odvisnosti, ki niso potrebne za delovanje raz Hkrati ta metoda omogoča, da se setter večkrat pokliče za spremembo odvisnosti. Če to ni zaželeno, metodi dodajte preverjanje ali pa od različice PHP 8.1 označite lastnost `$cache` z zastavico `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ Klic setterja je opredeljen v konfiguraciji vsebnika DI v [razdelku setup |servi ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Vbrizgavanje lastnosti .[#toc-property-injection] Odvisnosti se posredujejo neposredno lastnosti: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` `public`Tako nimamo nadzora nad tem, ali bo posredovana odvisnost dejansko določenega tipa (to je veljalo pred PHP 7.4), in izgubimo možnost, da bi se na novo dodeljeno odvisnost odzvali z lastno kodo, na primer da bi preprečili nadaljnje spremembe. Hkrati lastnost postane del javnega vmesnika razreda, kar morda ni zaželeno. @@ -137,12 +197,18 @@ Nastavitev spremenljivke je opredeljena v konfiguraciji vsebnika DI v [razdelku ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Vbrizgajte .[#toc-inject] +========================= + +Medtem ko so prejšnje tri metode na splošno veljavne v vseh objektno usmerjenih jezikih, je injiciranje z metodo, anotacijo ali atributom *inject* značilno za predstavnike Nette. Obravnavani so v [ločenem poglavju |best-practices:inject-method-attribute]. + + Kateri način izbrati? .[#toc-which-way-to-choose] ================================================= diff --git a/dependency-injection/sl/services.texy b/dependency-injection/sl/services.texy index d4662bdb2d..854a61e05a 100644 --- a/dependency-injection/sl/services.texy +++ b/dependency-injection/sl/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Inject Mode .[#toc-inject-mode] =============================== -Z zastavico `inject: true` se aktivira posredovanje odvisnosti prek javnih spremenljivk z opombo [inject |best-practices:inject-method-attribute#Inject Annotations] in metodami [inject*( |best-practices:inject-method-attribute#inject Methods] ). +Z zastavico `inject: true` se aktivira posredovanje odvisnosti prek javnih spremenljivk z opombo [inject |best-practices:inject-method-attribute#Inject Attributes] in metodami [inject*( |best-practices:inject-method-attribute#inject Methods] ). ```neon services: diff --git a/dependency-injection/tr/@home.texy b/dependency-injection/tr/@home.texy index 7840585b16..4d557bb6f0 100644 --- a/dependency-injection/tr/@home.texy +++ b/dependency-injection/tr/@home.texy @@ -5,8 +5,10 @@ Bağımlılık Enjeksiyonu Dependency Injection, koda ve geliştirmeye bakış açınızı temelden değiştirecek bir tasarım modelidir. Temiz tasarlanmış ve sürdürülebilir uygulamalardan oluşan bir dünyaya giden yolu açar. - [Dependency Injection Nedir? |introduction] -- [DI Container Nedir? |container] +- [Küresel Durum ve Tekiller |global-state] - [Bağımlılıkları |passing-dependencies]Geçirmek +- [DI Container Nedir? |container] +- [Sıkça Sorulan Sorular |faq] Nette DI diff --git a/dependency-injection/tr/@left-menu.texy b/dependency-injection/tr/@left-menu.texy index 31323b0841..4146c092ba 100644 --- a/dependency-injection/tr/@left-menu.texy +++ b/dependency-injection/tr/@left-menu.texy @@ -1,8 +1,10 @@ Bağımlılık Enjeksiyonu ********************** - [DI Nedir? |introduction] -- [DI Konteyner Nedir? |container] +- [Küresel Durum ve Tekiller |global-state] - [Bağımlılıkları Geçme |passing-dependencies] +- [DI Konteyner Nedir? |container] +- [Sıkça Sorulan Sorular |faq] Nette DI diff --git a/dependency-injection/tr/faq.texy b/dependency-injection/tr/faq.texy new file mode 100644 index 0000000000..cb6cb89752 --- /dev/null +++ b/dependency-injection/tr/faq.texy @@ -0,0 +1,112 @@ +DI Hakkında SSS +*************** + + +DI, IoC için başka bir isim mi? .[#toc-is-di-another-name-for-ioc] +------------------------------------------------------------------ + +*Inversion of Control* (IoC), kodun yürütülme şekline odaklanan bir ilkedir - kodunuzun harici kodu başlatması veya kodunuzun harici koda entegre olması ve daha sonra onu çağırması. +IoC, [olayları |nette:glossary#Events], [Hollywood İlkesi |application:components#Hollywood style] olarak adlandırılan ilkeyi ve diğer unsurları içeren geniş bir kavramdır. +[Kural #3: Bırakın Fabrika Hall |introduction#Rule #3: Let the Factory Handle It] etsin'in bir parçası olan ve `new` operatörü için tersine çevirmeyi temsil eden fabrikalar da bu kavramın bileşenleridir. + +*Dependency Injection* (DI), bir nesnenin başka bir nesne hakkında nasıl bilgi sahibi olduğu, yani bağımlılık ile ilgilidir. Nesneler arasında bağımlılıkların açık bir şekilde aktarılmasını gerektiren bir tasarım modelidir. + +Dolayısıyla DI'nin IoC'nin özel bir biçimi olduğu söylenebilir. Bununla birlikte, IoC'nin tüm biçimleri kod saflığı açısından uygun değildir. Örneğin, anti-paternler arasında, [küresel durumla |global state] çalışan veya [Hizmet Bul |#What is a Service Locator]ucu olarak adlandırılan tüm teknikleri dahil ediyoruz. + + +Hizmet Bulucu Nedir? .[#toc-what-is-a-service-locator] +------------------------------------------------------ + +Hizmet Bulucu, Bağımlılık Enjeksiyonuna bir alternatiftir. Mevcut tüm hizmetlerin veya bağımlılıkların kaydedildiği merkezi bir depolama alanı oluşturarak çalışır. Bir nesne bir bağımlılığa ihtiyaç duyduğunda, bunu Hizmet Bulucu'dan talep eder. + +Bununla birlikte, Bağımlılık Enjeksiyonu ile karşılaştırıldığında, şeffaflık kaybeder: bağımlılıklar doğrudan nesnelere aktarılmaz ve bu nedenle kolayca tanımlanamaz, bu da tüm bağlantıları ortaya çıkarmak ve anlamak için kodun incelenmesini gerektirir. Test etmek de daha karmaşıktır, çünkü sahte nesneleri test edilen nesnelere basitçe geçiremeyiz, ancak Hizmet Bulucu'dan geçmemiz gerekir. Ayrıca, Service Locator kodun tasarımını bozar, çünkü her bir nesnenin onun varlığından haberdar olması gerekir, bu da nesnelerin DI konteyneri hakkında hiçbir bilgiye sahip olmadığı Dependency Injection'dan farklıdır. + + +DI kullanmamak ne zaman daha iyidir? .[#toc-when-is-it-better-not-to-use-di] +---------------------------------------------------------------------------- + +Dependency Injection tasarım modelinin kullanımıyla ilgili bilinen herhangi bir zorluk yoktur. Aksine, bağımlılıkları global olarak erişilebilir konumlardan elde etmek, bir Hizmet Bulucu kullanmak gibi bir [dizi komplikasyona |global-state] yol açar. +Bu nedenle, her zaman DI kullanılması tavsiye edilir. Bu dogmatik bir yaklaşım değildir, ancak daha iyi bir alternatif bulunamamıştır. + +Ancak, nesneleri birbirlerine aktarmadığımız ve global alandan elde etmediğimiz bazı durumlar vardır. Örneğin, kodda hata ayıklama yaparken ve programın belirli bir noktasında bir değişken değerini dökmek, programın belirli bir bölümünün süresini ölçmek veya bir mesajı günlüğe kaydetmek gerektiğinde. +Daha sonra koddan kaldırılacak geçici eylemlerle ilgili olan bu gibi durumlarda, global olarak erişilebilir bir dumper, kronometre veya logger kullanmak meşrudur. Sonuçta bu araçlar kodun tasarımına ait değildir. + + +DI kullanmanın dezavantajları var mı? .[#toc-does-using-di-have-its-drawbacks] +------------------------------------------------------------------------------ + +Bağımlılık Enjeksiyonu kullanmanın kod yazma karmaşıklığının artması veya performansın düşmesi gibi dezavantajları var mıdır? DI'ye uygun kod yazmaya başladığımızda ne kaybederiz? + +DI'nin uygulama performansı veya bellek gereksinimleri üzerinde hiçbir etkisi yoktur. DI Konteynerinin performansı bir rol oynayabilir, ancak [Nette DI | nette-container] durumunda, konteyner saf PHP'ye derlenir, bu nedenle uygulama çalışma zamanı sırasında ek yükü esasen sıfırdır. + +Kod yazarken, bağımlılıkları kabul eden yapıcılar oluşturmak gerekir. Geçmişte, bu zaman alıcı olabilirdi, ancak modern IDE'ler ve [yapıcı özellik tanıtımı |https://blog.nette.org/tr/php-8-0-haberlere-genel-bakis#toc-constructor-property-promotion] sayesinde artık birkaç saniye meselesi. Fabrikalar Nette DI ve bir PhpStorm eklentisi kullanılarak sadece birkaç tıklama ile kolayca oluşturulabilir. +Öte yandan singleton ve statik erişim noktaları yazmaya da gerek kalmıyor. + +DI kullanan düzgün tasarlanmış bir uygulamanın, tekli sınıf kullanan bir uygulamaya kıyasla ne daha kısa ne de daha uzun olduğu sonucuna varılabilir. Bağımlılıklarla çalışan kod parçaları basitçe tek tek sınıflardan çıkarılır ve DI konteyneri ve fabrikaları gibi yeni konumlara taşınır. + + +Eski bir uygulama DI için nasıl yeniden yazılır? .[#toc-how-to-rewrite-a-legacy-application-to-di] +-------------------------------------------------------------------------------------------------- + +Eski bir uygulamadan Dependency Injection'a geçiş, özellikle büyük ve karmaşık uygulamalar için zorlu bir süreç olabilir. Bu sürece sistematik bir şekilde yaklaşmak önemlidir. + +- Bağımlılık Enjeksiyonuna geçerken, tüm ekip üyelerinin kullanılan ilke ve uygulamaları anlaması önemlidir. +- İlk olarak, temel bileşenleri ve bağımlılıklarını belirlemek için mevcut uygulamanın bir analizini yapın. Hangi parçaların hangi sırayla yeniden düzenleneceğine dair bir plan oluşturun. +- Bir DI konteyneri uygulayın ya da daha iyisi Nette DI gibi mevcut bir kütüphaneyi kullanın. +- Bağımlılık Enjeksiyonunu kullanmak için uygulamanın her bir parçasını kademeli olarak yeniden düzenleyin. Bu, bağımlılıkları parametre olarak kabul etmek için kurucuları veya yöntemleri değiştirmeyi içerebilir. +- Kodda bağımlılık nesnelerinin oluşturulduğu yerleri değiştirin, böylece bağımlılıklar bunun yerine kapsayıcı tarafından enjekte edilir. Bu, fabrikaların kullanımını içerebilir. + +Dependency Injection'a geçmenin kod kalitesine ve uygulamanın uzun vadeli sürdürülebilirliğine yapılan bir yatırım olduğunu unutmayın. Bu değişiklikleri yapmak zor olsa da, sonuçta daha temiz, daha modüler ve kolayca test edilebilir, gelecekteki uzantılara ve bakıma hazır bir kod ortaya çıkacaktır. + + +Neden kalıtım yerine kompozisyon tercih edilir? .[#toc-why-composition-is-preferred-over-inheritance] +----------------------------------------------------------------------------------------------------- +Değişimin damlama etkisi konusunda endişelenmeye gerek kalmadan kodun yeniden kullanılabilirliği amacına hizmet ettiği için kalıtım yerine bileşimin kullanılması tercih edilir. Böylece, bazı kodların değiştirilmesinin diğer bazı bağımlı kodların değişmesine neden olması konusunda endişelenmemizi gerektirmeyen daha gevşek bir bağlantı sağlar. Tipik bir örnek, [kurucu cehennemi |passing-dependencies#Constructor hell] olarak tanımlanan durumdur. + + +Nette DI Container Nette dışında kullanılabilir mi? .[#toc-can-nette-di-container-be-used-outside-of-nette] +----------------------------------------------------------------------------------------------------------- + +Kesinlikle. Nette DI Container, Nette'in bir parçasıdır, ancak çerçevenin diğer bölümlerinden bağımsız olarak kullanılabilen bağımsız bir kütüphane olarak tasarlanmıştır. Composer'ı kullanarak kurun, hizmetlerinizi tanımlayan bir yapılandırma dosyası oluşturun ve ardından DI konteynerini oluşturmak için birkaç satır PHP kodu kullanın. +Böylece projelerinizde Dependency Injection'dan hemen yararlanmaya başlayabilirsiniz. + + [Nette DI Container |nette-container] bölümü, kod da dahil olmak üzere belirli bir kullanım durumunun neye benzediğini açıklar. + + +NEON dosyalarındaki yapılandırma neden? .[#toc-why-is-the-configuration-in-neon-files] +-------------------------------------------------------------------------------------- + +NEON, uygulamaları, hizmetleri ve bunların bağımlılıklarını ayarlamak için Nette içinde geliştirilen basit ve kolay okunabilir bir yapılandırma dilidir. JSON veya YAML ile karşılaştırıldığında, bu amaç için çok daha sezgisel ve esnek seçenekler sunar. NEON'da, Symfony & YAML'de yazılması mümkün olmayan bağları doğal olarak ya hiç ya da sadece karmaşık bir açıklama yoluyla tanımlayabilirsiniz. + + +NEON dosyalarını ayrıştırmak uygulamayı yavaşlatıyor mu? .[#toc-does-parsing-neon-files-slow-down-the-application] +------------------------------------------------------------------------------------------------------------------ + +NEON dosyaları çok hızlı bir şekilde ayrıştırılsa da, bu özellik gerçekten önemli değildir. Bunun nedeni, dosyaların ayrıştırılmasının uygulamanın ilk başlatılması sırasında yalnızca bir kez gerçekleşmesidir. Bundan sonra, DI konteyner kodu oluşturulur, diskte saklanır ve sonraki her istek için daha fazla ayrıştırmaya gerek kalmadan yürütülür. + +Üretim ortamında bu şekilde çalışır. Geliştirme sırasında, NEON dosyaları içerikleri her değiştiğinde ayrıştırılarak geliştiricinin her zaman güncel bir DI konteynerine sahip olması sağlanır. Daha önce de belirtildiği gibi, gerçek ayrıştırma bir anlık bir meseledir. + + +Sınıfımdaki yapılandırma dosyasından parametrelere nasıl erişebilirim? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +----------------------------------------------------------------------------------------------------------------------------------------------------- + + [Kural #1 |introduction#Rule #1: Let It Be Passed to You]'i aklınızda tutun [: Bırakın Size İletilsin |introduction#Rule #1: Let It Be Passed to You]. Bir sınıf bir yapılandırma dosyasından bilgi gerektiriyorsa, bu bilgiye nasıl erişeceğimizi bulmamız gerekmez; bunun yerine, örneğin sınıf kurucusu aracılığıyla basitçe sorarız. Ve aktarma işlemini yapılandırma dosyasında gerçekleştiririz. + +Bu örnekte `%myParameter%`, `MyClass` kurucusuna aktarılacak olan `myParameter` parametresinin değeri için bir yer tutucudur: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Birden fazla parametre geçirmek veya otomatik bağlantı kullanmak istiyorsanız, parametreleri [bir nesneye sarmak |best-practices:passing-settings-to-presenters] yararlı olacaktır. + + +Nette PSR-11 Konteyner arayüzünü destekliyor mu? .[#toc-does-nette-support-psr-11-container-interface] +------------------------------------------------------------------------------------------------------ + +Nette DI Container PSR-11'i doğrudan desteklemez. Ancak, Nette DI Container ile PSR-11 Container Interface'i bekleyen kütüphaneler veya çerçeveler arasında birlikte çalışabilirliğe ihtiyacınız varsa, Nette DI Container ile PSR-11 arasında bir köprü görevi görecek [basit bir adaptör |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f] oluşturabilirsiniz. diff --git a/dependency-injection/tr/global-state.texy b/dependency-injection/tr/global-state.texy new file mode 100644 index 0000000000..d3034f1477 --- /dev/null +++ b/dependency-injection/tr/global-state.texy @@ -0,0 +1,312 @@ +Küresel Durum ve Singletonlar +***************************** + +.[perex] +Uyarı: Aşağıdaki yapılar kötü kod tasarımının belirtileridir: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` veya `static::$var` + +Kodunuzda bu yapılardan herhangi biri var mı? O halde kendinizi geliştirmek için bir fırsatınız var. Bunların çeşitli kütüphanelerin ve çerçevelerin örnek çözümlerinde gördüğümüz yaygın yapılar olduğunu düşünüyor olabilirsiniz. +Ne yazık ki, bunlar hala kötü tasarımın açık bir göstergesidir. Ortak bir noktaları var: küresel durum kullanımı. + +Şimdi, kesinlikle bir tür akademik saflıktan bahsetmiyoruz. Global state ve singleton kullanımının kod kalitesi üzerinde yıkıcı etkileri vardır. Davranışları öngörülemez hale gelir, geliştirici verimliliğini azaltır ve sınıf arayüzlerini gerçek bağımlılıkları hakkında yalan söylemeye zorlar. Bu da programcıların kafasını karıştırır. + +Bu bölümde, bunun nasıl mümkün olduğunu göstereceğiz. + + +Küresel Bağlantı .[#toc-global-interlinking] +-------------------------------------------- + +Global durumla ilgili temel sorun, global olarak erişilebilir olmasıdır. Bu, `DB::insert()` global (statik) yöntemi aracılığıyla veritabanına yazmayı mümkün kılar. +İdeal bir dünyada, bir nesne yalnızca kendisine [doğrudan aktarılan |passing-dependencies] diğer nesnelerle iletişim kurabilmelidir. +Eğer `A` ve `B` adında iki nesne yaratırsam ve `A` 'den `B`'a asla bir referans aktarmazsam, o zaman ne `A` ne de `B` diğer nesneye erişemez veya durumunu değiştiremez. +Bu, kodun çok arzu edilen bir özelliğidir. Bu, bir pil ve bir ampule sahip olmaya benzer; siz onları birbirine bağlayana kadar ampul yanmayacaktır. + +Bu durum global (statik) değişkenler veya tekil değişkenler için geçerli değildir. `A` nesnesi `C` nesnesine *kablosuz* olarak erişebilir ve `C::changeSomething()` adresini çağırarak herhangi bir referans iletmeden onu değiştirebilir. +Eğer `B` nesnesi global `C` nesnesini de yakalarsa, `A` ve `B` birbirleriyle `C` üzerinden etkileşime girebilir. + +Global değişkenlerin kullanımı, sisteme dışarıdan görünmeyen yeni bir *kablosuz* bağlantı biçimi getirir. +Kodun anlaşılmasını ve kullanılmasını zorlaştıran bir sis perdesi yaratır. +Geliştiriciler bağımlılıkları gerçekten anlamak için kaynak kodun her satırını okumalıdır. Sadece sınıfların arayüzüne aşina olmak yerine. +Dahası, bu tamamen gereksiz bir bağlantıdır. + +.[note] +Davranış açısından, global ve statik değişken arasında bir fark yoktur. İkisi de eşit derecede zararlıdır. + + +Uzaktaki Ürkütücü Eylem .[#toc-the-spooky-action-at-a-distance] +--------------------------------------------------------------- + +"Uzaktaki ürkütücü eylem" - Albert Einstein 1935 yılında kuantum fiziğinde kendisini ürküten bir olguyu böyle adlandırmıştı. +Bu kuantum dolanıklığıdır ve özelliği, bir parçacık hakkındaki bilgiyi ölçtüğünüzde, milyonlarca ışık yılı uzakta olsalar bile başka bir parçacığı hemen etkilemenizdir. +Bu da evrenin temel yasası olan hiçbir şeyin ışıktan hızlı gidemeyeceği ilkesini ihlal eder. + +Yazılım dünyasında, izole olduğunu düşündüğümüz bir süreci çalıştırdığımız (çünkü ona herhangi bir referans iletmedik), ancak sistemin nesneye söylemediğimiz uzak konumlarında beklenmedik etkileşimlerin ve durum değişikliklerinin meydana geldiği bir durumu "uzaktan ürkütücü eylem" olarak adlandırabiliriz. Bu yalnızca global durum aracılığıyla gerçekleşebilir. + +Büyük ve olgun bir kod tabanına sahip bir proje geliştirme ekibine katıldığınızı düşünün. Yeni lideriniz sizden yeni bir özelliği hayata geçirmenizi istiyor ve siz de iyi bir geliştirici gibi işe bir test yazarak başlıyorsunuz. Ancak projede yeni olduğunuz için, "bu yöntemi çağırırsam ne olur" türünde çok sayıda keşif testi yaparsınız. Ve aşağıdaki testi yazmaya çalışıyorsunuz: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // kart numaranız + $cc->charge(100); +} +``` + +Kodu belki birkaç kez çalıştırıyorsunuz ve bir süre sonra telefonunuza bankadan gelen bildirimlerde kodu her çalıştırdığınızda kredi kartınızdan 100 $ çekildiğini fark ediyorsunuz 🤦‍♂️ + +Test nasıl olur da gerçek bir ücretlendirmeye neden olabilir? Kredi kartı ile işlem yapmak kolay değildir. Üçüncü taraf bir web hizmetiyle etkileşime girmeniz, bu web hizmetinin URL'sini bilmeniz, oturum açmanız vb. gerekir. +Bu bilgilerin hiçbiri testte yer almıyor. Daha da kötüsü, bu bilgilerin nerede bulunduğunu ve bu nedenle her çalıştırmanın tekrar 100 $ ücretlendirilmesiyle sonuçlanmaması için harici bağımlılıkları nasıl taklit edeceğinizi bile bilmiyorsunuz. Ve yeni bir geliştirici olarak, yapmak üzere olduğunuz şeyin 100 dolar daha fakir olmanıza yol açacağını nereden bilebilirdiniz? + +Bu uzaktan ürkütücü bir hareket! + +Projedeki bağlantıların nasıl çalıştığını anlayana kadar, daha yaşlı ve daha deneyimli meslektaşlarınıza sorarak çok sayıda kaynak kodu incelemekten başka seçeneğiniz yoktur. +Bunun nedeni, `CreditCard` sınıfının arayüzüne baktığınızda, başlatılması gereken global durumu belirleyememenizdir. Sınıfın kaynak koduna bakmak bile size hangi ilklendirme yöntemini çağırmanız gerektiğini söylemeyecektir. En iyi ihtimalle, erişilen global değişkeni bulabilir ve buradan nasıl başlatılacağını tahmin etmeye çalışabilirsiniz. + +Böyle bir projedeki sınıflar patolojik yalancılardır. Ödeme kartı, sadece onu örnekleyebileceğinizi ve `charge()` yöntemini çağırabileceğinizi iddia eder. Ancak, gizlice başka bir sınıf olan `PaymentGateway` ile etkileşim halindedir. Arayüzü bile bağımsız olarak başlatılabileceğini söylüyor, ancak gerçekte bazı yapılandırma dosyalarından kimlik bilgilerini çekiyor vb. +Bu kodu yazan geliştiriciler için `CreditCard` 'un `PaymentGateway`'a ihtiyacı olduğu açıktır. Kodu bu şekilde yazmışlardır. Ancak projede yeni olan herkes için bu tam bir gizemdir ve öğrenmeyi engeller. + +Bu durum nasıl düzeltilir? Çok kolay. **API'nin bağımlılıkları bildirmesine izin verin.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Kod içindeki ilişkilerin birdenbire nasıl belirgin hale geldiğine dikkat edin. `charge()` yönteminin `PaymentGateway` adresine ihtiyaç duyduğunu beyan ederek, kodun nasıl birbirine bağlı olduğunu kimseye sormak zorunda kalmazsınız. Bunun bir örneğini oluşturmanız gerektiğini biliyorsunuz ve bunu yapmaya çalıştığınızda erişim parametreleri sağlamanız gerektiği gerçeğiyle karşılaşıyorsunuz. Onlar olmadan kod çalışmaz bile. + +Ve en önemlisi, artık ödeme ağ geçidini taklit edebilirsiniz, böylece her test çalıştırdığınızda 100 $ ücretlendirilmezsiniz. + +Küresel durum, nesnelerinizin API'lerinde bildirilmeyen şeylere gizlice erişebilmesine neden olur ve sonuç olarak API'lerinizi patolojik yalancılar haline getirir. + +Daha önce bu şekilde düşünmemiş olabilirsiniz, ancak global state kullandığınızda gizli kablosuz iletişim kanalları oluşturmuş olursunuz. Ürkütücü uzaktan eylem, geliştiricileri potansiyel etkileşimleri anlamak için her kod satırını okumaya zorlar, geliştirici verimliliğini azaltır ve yeni ekip üyelerinin kafasını karıştırır. +Kodu oluşturan kişi sizseniz, gerçek bağımlılıkları bilirsiniz, ancak sizden sonra gelenlerin hiçbir şeyden haberi olmaz. + +Global durum kullanan kod yazmayın, bağımlılıkları aktarmayı tercih edin. Yani, bağımlılık enjeksiyonu. + + +Küresel Devletin Kırılganlığı .[#toc-brittleness-of-the-global-state] +--------------------------------------------------------------------- + +Global state ve singleton kullanan kodlarda, bu state'in ne zaman ve kim tarafından değiştirildiği asla kesin değildir. Bu risk başlatma sırasında zaten mevcuttur. Aşağıdaki kodun bir veritabanı bağlantısı oluşturması ve ödeme ağ geçidini başlatması gerekiyor, ancak sürekli bir istisna atıyor ve nedenini bulmak son derece sıkıcı: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + + `PaymentGateway` nesnesinin diğer nesnelere kablosuz olarak eriştiğini ve bunlardan bazılarının veritabanı bağlantısı gerektirdiğini bulmak için kodu ayrıntılı olarak incelemeniz gerekir. Bu nedenle, `PaymentGateway` adresinden önce veritabanını başlatmanız gerekir. Ancak, global durum sis perdesi bunu sizden gizler. Her sınıfın API'si yalan söylemeseydi ve bağımlılıklarını beyan etseydi ne kadar zaman kazanırdınız? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Bir veritabanı bağlantısına genel erişim kullanıldığında da benzer bir sorun ortaya çıkar: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + + `save()` yöntemi çağrıldığında, bir veritabanı bağlantısının zaten oluşturulup oluşturulmadığı ve oluşturulmasından kimin sorumlu olduğu kesin değildir. Örneğin, veritabanı bağlantısını anında değiştirmek istersek, belki de test amacıyla, muhtemelen `DB::reconnect(...)` veya `DB::reconnectForTest()` gibi ek yöntemler oluşturmamız gerekecektir. + +Bir örnek düşünün: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + + `$article->save()` adresini çağırırken test veritabanının gerçekten kullanıldığından nasıl emin olabiliriz? Ya `Foo::doSomething()` yöntemi global veritabanı bağlantısını değiştirdiyse? Bunu öğrenmek için `Foo` sınıfının ve muhtemelen diğer birçok sınıfın kaynak kodunu incelememiz gerekir. Ancak, durum gelecekte değişebileceğinden, bu yaklaşım yalnızca kısa vadeli bir yanıt sağlayacaktır. + +Veritabanı bağlantısını `Article` sınıfının içindeki statik bir değişkene taşırsak ne olur? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Bu hiçbir şeyi değiştirmez. Sorun global bir durumdur ve hangi sınıfta saklandığı önemli değildir. Bu durumda, bir öncekinde olduğu gibi, `$article->save()` yöntemi çağrıldığında hangi veritabanına yazıldığına dair hiçbir ipucumuz yoktur. Uygulamanın uzak ucundaki herhangi biri `Article::setDb()` adresini kullanarak istediği zaman veritabanını değiştirebilir. Elimizin altında. + +Küresel durum uygulamamızı **son derece kırılgan** hale getirir. + +Ancak bu sorunun üstesinden gelmenin basit bir yolu vardır. Uygun işlevselliği sağlamak için API'nin bağımlılıkları bildirmesi yeterlidir. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Bu yaklaşım, veritabanı bağlantılarında gizli ve beklenmedik değişiklikler yapılması endişesini ortadan kaldırır. Artık makalenin nerede saklandığından eminiz ve başka bir ilgisiz sınıfın içindeki hiçbir kod değişikliği artık durumu değiştiremez. Kod artık kırılgan değil, kararlı. + +Global durum kullanan kod yazmayın, bağımlılıkları aktarmayı tercih edin. Böylece, bağımlılık enjeksiyonu. + + +Singleton .[#toc-singleton] +--------------------------- + +Singleton, ünlü Gang of Four yayınındaki [tanımıyla |https://en.wikipedia.org/wiki/Singleton_pattern], bir sınıfı tek bir örnekle sınırlayan ve ona global erişim sunan bir tasarım modelidir. Bu kalıbın uygulaması genellikle aşağıdaki koda benzer: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // ve sınıfın işlevlerini yerine getiren diğer yöntemler +} +``` + +Ne yazık ki, singleton uygulamaya global durum ekler. Ve yukarıda gösterdiğimiz gibi, global durum istenmeyen bir durumdur. Bu yüzden singleton bir antipattern olarak kabul edilir. + +Kodunuzda singleton kullanmayın ve bunları başka mekanizmalarla değiştirin. Tekillere gerçekten ihtiyacınız yok. Ancak, tüm uygulama için bir sınıfın tek bir örneğinin varlığını garanti etmeniz gerekiyorsa, bunu [DI konteynerine |container] bırakın. +Böylece, bir uygulama singletonu veya hizmeti oluşturun. Bu, sınıfın kendi benzersizliğini sağlamasını durduracak (yani, bir `getInstance()` yöntemine ve statik bir değişkene sahip olmayacaktır) ve yalnızca işlevlerini yerine getirecektir. Böylece, tek sorumluluk ilkesini ihlal etmeyi durduracaktır. + + +Testlere Karşı Küresel Durum .[#toc-global-state-versus-tests] +-------------------------------------------------------------- + +Testleri yazarken, her testin izole bir birim olduğunu ve hiçbir dış durumun teste girmediğini varsayarız. Ve hiçbir durum testleri terk etmez. Bir test tamamlandığında, testle ilişkili tüm durumlar çöp toplayıcı tarafından otomatik olarak kaldırılmalıdır. Bu, testleri yalıtılmış hale getirir. Bu nedenle testleri istediğimiz sırada çalıştırabiliriz. + +Ancak, küresel durumlar/singletonlar mevcutsa, tüm bu güzel varsayımlar bozulur. Bir durum bir teste girebilir ve çıkabilir. Birdenbire, testlerin sırası önemli olabilir. + +Tekil öğeleri test etmek için, geliştiricilerin genellikle bir örneğin başka bir örnekle değiştirilmesine izin vererek özelliklerini gevşetmeleri gerekir. Bu tür çözümler, en iyi ihtimalle, bakımı ve anlaşılması zor kodlar üreten hilelerdir. Herhangi bir global durumu etkileyen herhangi bir test veya yöntem `tearDown()` bu değişiklikleri geri almalıdır. + +Global durum, birim testindeki en büyük baş ağrısıdır! + +Bu durum nasıl düzeltilir? Çok kolay. Singleton kullanan kod yazmayın, bağımlılıkları aktarmayı tercih edin. Yani bağımlılık enjeksiyonu. + + +Küresel Sabitler .[#toc-global-constants] +----------------------------------------- + +Küresel durum tekil ve statik değişkenlerin kullanımıyla sınırlı değildir, aynı zamanda küresel sabitler için de geçerli olabilir. + +Değeri bize yeni (`M_PI`) veya faydalı (`PREG_BACKTRACK_LIMIT_ERROR`) bilgi sağlamayan sabitler açıkça tamamdır. +Tersine, kod içinde *kablosuz* bilgi aktarmanın bir yolu olarak hizmet eden sabitler, gizli bir bağımlılıktan başka bir şey değildir. Aşağıdaki örnekte `LOG_FILE` gibi. + `FILE_APPEND` sabitini kullanmak tamamen doğrudur. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Bu durumda, API'nin bir parçası haline getirmek için parametreyi `Foo` sınıfının kurucusunda bildirmeliyiz: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Artık günlük dosyasının yolu hakkında bilgi aktarabilir ve gerektiğinde kolayca değiştirebiliriz, bu da kodu test etmeyi ve bakımını yapmayı kolaylaştırır. + + +Global İşlevler ve Statik Yöntemler .[#toc-global-functions-and-static-methods] +------------------------------------------------------------------------------- + +Statik yöntemlerin ve global fonksiyonların kullanımının kendi içinde sorunlu olmadığını vurgulamak istiyoruz. `DB::insert()` ve benzeri yöntemlerin kullanılmasının uygunsuzluğunu açıkladık, ancak bu her zaman statik bir değişkende saklanan global durum meselesi olmuştur. `DB::insert()` yöntemi, veritabanı bağlantısını sakladığı için statik bir değişkenin varlığını gerektirir. Bu değişken olmadan yöntemi uygulamak imkansızdır. + + `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` ve diğerleri gibi deterministik statik yöntem ve fonksiyonların kullanımı bağımlılık enjeksiyonu ile tamamen tutarlıdır. Bu fonksiyonlar her zaman aynı girdi parametrelerinden aynı sonuçları döndürür ve bu nedenle öngörülebilirdir. Herhangi bir global durum kullanmazlar. + +Ancak, PHP'de deterministik olmayan işlevler de vardır. Bunlara örnek olarak `htmlspecialchars()` fonksiyonu verilebilir. Üçüncü parametresi olan `$encoding` belirtilmezse, varsayılan olarak `ini_get('default_charset')` yapılandırma seçeneğinin değerini alır. Bu nedenle, fonksiyonun olası öngörülemeyen davranışını önlemek için bu parametrenin her zaman belirtilmesi önerilir. Nette bunu sürekli olarak yapar. + + `strtolower()`, `strtoupper()` ve benzerleri gibi bazı fonksiyonlar yakın geçmişte deterministik olmayan davranışlara sahipti ve `setlocale()` ayarına bağlıydı. Bu, çoğunlukla Türkçe diliyle çalışırken birçok komplikasyona neden olmuştur. +Bunun nedeni, Türkçe dilinin noktalı ve noktasız büyük ve küçük harf `I` arasında ayrım yapmasıdır. Bu yüzden `strtolower('I')`, `ı` karakterini ve `strtoupper('i')`, `İ` karakterini döndürüyordu, bu da uygulamaların bir dizi gizemli hataya neden olmasına yol açıyordu. +Ancak, bu sorun PHP 8.2 sürümünde düzeltilmiştir ve işlevler artık yerel ayara bağlı değildir. + +Bu, küresel durumun dünyanın dört bir yanındaki binlerce geliştiriciyi nasıl rahatsız ettiğinin güzel bir örneğidir. Çözüm, bağımlılık enjeksiyonu ile değiştirmekti. + + +Global State Ne Zaman Kullanılabilir? .[#toc-when-is-it-possible-to-use-global-state] +------------------------------------------------------------------------------------- + +Global durumu kullanmanın mümkün olduğu bazı özel durumlar vardır. Örneğin, kodda hata ayıklama yaparken bir değişkenin değerini dökmeniz veya programın belirli bir bölümünün süresini ölçmeniz gerekir. Daha sonra koddan kaldırılacak geçici eylemlerle ilgili olan bu gibi durumlarda, global olarak kullanılabilen bir dumper veya kronometre kullanmak meşrudur. Bu araçlar kod tasarımının bir parçası değildir. + +Başka bir örnek, derlenmiş düzenli ifadeleri dahili olarak bellekteki statik bir önbellekte saklayan `preg_*` düzenli ifadelerle çalışma işlevleridir. Aynı düzenli ifadeyi kodun farklı bölümlerinde birden çok kez çağırdığınızda, bu ifade yalnızca bir kez derlenir. Önbellek performans tasarrufu sağlar ve ayrıca kullanıcı için tamamen görünmezdir, bu nedenle bu tür kullanım meşru kabul edilebilir. + + +Özet .[#toc-summary] +-------------------- + +Bunun neden mantıklı olduğunu gösterdik + +1) Koddan tüm statik değişkenleri kaldırın +2) Bağımlılıkları beyan edin +3) Ve bağımlılık enjeksiyonu kullanın + +Kod tasarımını düşünürken, her `static $foo` adresinin bir sorunu temsil ettiğini unutmayın. Kodunuzun DI'ya saygılı bir ortam olması için, global durumu tamamen ortadan kaldırmak ve bağımlılık enjeksiyonu ile değiştirmek çok önemlidir. + +Bu süreç sırasında, birden fazla sorumluluğu olduğu için bir sınıfı bölmeniz gerektiğini fark edebilirsiniz. Bu konuda endişelenmeyin; tek sorumluluk ilkesi için çaba gösterin. + +*[Flaw: Brittle Global State & Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/] gibi makaleleri bu bölümün temelini oluşturan Miško Hevery'ye teşekkür ederim.* diff --git a/dependency-injection/tr/introduction.texy b/dependency-injection/tr/introduction.texy index ce9edd9134..a388e5eb6b 100644 --- a/dependency-injection/tr/introduction.texy +++ b/dependency-injection/tr/introduction.texy @@ -2,17 +2,17 @@ Bağımlılık Enjeksiyonu Nedir? ***************************** .[perex] -Bu bölüm size herhangi bir uygulama yazarken izlemeniz gereken temel programlama uygulamalarını tanıtmaktadır. Bunlar temiz, anlaşılabilir ve bakımı yapılabilir kod yazmak için gereken temel bilgilerdir. +Bu bölüm size herhangi bir uygulama yazarken izlemeniz gereken temel programlama uygulamalarını tanıtacaktır. Bunlar temiz, anlaşılabilir ve sürdürülebilir kod yazmak için gereken temel bilgilerdir. -Bu kuralları öğrenir ve uygularsanız, Nette her adımda yanınızda olacaktır. Rutin görevleri sizin için halledecek ve sizi olabildiğince rahat ettirecek, böylece siz de mantığın kendisine odaklanabileceksiniz. +Bu kuralları öğrenir ve uygularsanız, Nette her adımda yanınızda olacaktır. Rutin görevleri sizin için halledecek ve maksimum konfor sağlayacaktır, böylece siz mantığın kendisine odaklanabilirsiniz. -Burada göstereceğimiz ilkeler oldukça basittir. Endişelenmenizi gerektirecek bir şey yok. +Burada göstereceğimiz ilkeler oldukça basittir. Hiçbir şey için endişelenmenize gerek yok. İlk Programınızı Hatırlıyor musunuz? .[#toc-remember-your-first-program] ------------------------------------------------------------------------ -Hangi dilde yazdığınızı bilmiyoruz, ancak PHP olsaydı muhtemelen şöyle bir şey olurdu: +Hangi dilde yazdığınızı bilmiyoruz, ancak PHP olsaydı, şöyle bir şeye benzeyebilirdi: ```php function toplami(float $a, float $b): float @@ -25,31 +25,31 @@ echo toplami(23, 1); // 24 yazdırır Birkaç önemsiz kod satırı, ancak içlerinde çok fazla anahtar kavram gizli. Değişkenler var. Kodun daha küçük birimlere ayrıldığı, örneğin fonksiyonlar. Onlara girdi argümanları iletiyoruz ve onlar da sonuç döndürüyorlar. Eksik olan tek şey koşullar ve döngüler. -Bir fonksiyona girdi aktardığımız ve fonksiyonun bir sonuç döndürdüğü gerçeği, matematik gibi diğer alanlarda da kullanılan son derece anlaşılabilir bir kavramdır. +Bir fonksiyonun girdi verilerini alması ve bir sonuç döndürmesi, matematik gibi diğer alanlarda da kullanılan, tamamen anlaşılabilir bir kavramdır. -Bir fonksiyonun adı, parametrelerin listesi ve türleri ile son olarak geri dönüş değerinin türünden oluşan bir imzası vardır. Kullanıcılar olarak biz imzayla ilgileniriz; genellikle dahili uygulama hakkında bir şey bilmemiz gerekmez. +Bir fonksiyonun imzası vardır ve bu imza fonksiyonun adı, parametrelerin listesi ve tipleri ile son olarak geri dönüş değerinin tipinden oluşur. Kullanıcılar olarak biz imzayla ilgileniriz ve genellikle dahili uygulama hakkında bir şey bilmemiz gerekmez. -Şimdi bir fonksiyonun imzasının aşağıdaki gibi olduğunu düşünün: +Şimdi fonksiyon imzasının aşağıdaki gibi olduğunu hayal edin: ```php function toplami(float $x): float ``` -Tek parametreli bir ekleme mi? Bu çok garip. Buna ne dersin? +Tek parametreli bir ekleme mi? Bu çok garip. Peki ya bu? ```php function toplami(): float ``` -Bu gerçekten garip, değil mi? Bu fonksiyonun nasıl kullanıldığını düşünüyorsunuz? +Şimdi bu gerçekten garip, değil mi? Fonksiyon nasıl kullanılıyor? ```php echo toplami(); // ne yazdırıyor? ``` -Böyle bir koda baktığımızda kafamız karışır. Sadece yeni başlayanlar değil, yetenekli bir programcı bile böyle bir kodu anlamayacaktır. +Böyle bir koda baktığımızda kafamız karışır. Sadece yeni başlayanlar değil, deneyimli bir programcı bile böyle bir kodu anlamayacaktır. -Böyle bir fonksiyonun içinde gerçekte neye benzeyeceğini merak ediyor musunuz? Toplayıcıları nereden bulacak? Muhtemelen onları *bir şekilde* kendi başına alırdı, bunun gibi: +Böyle bir fonksiyonun içinde gerçekte neye benzeyeceğini merak ediyor musunuz? Toplamları nereden alırdı? Muhtemelen *bir şekilde* kendi kendine alırdı, belki de şu şekilde: ```php function toplami(): float @@ -66,13 +66,13 @@ function toplami(): float Bu şekilde değil! .[#toc-not-this-way] -------------------------------------- -Az önce bize gösterilen tasarım, birçok olumsuz özelliğin özüdür: +Az önce gösterdiğimiz tasarım, birçok olumsuz özelliğin özüdür: -- fonksiyon imzası eklentilere ihtiyaç duymuyormuş gibi davranıyordu, bu da kafamızı karıştırıyordu +- fonksiyon imzası toplamlara ihtiyaç duymuyormuş gibi davranıyordu, bu da kafamızı karıştırıyordu - fonksiyonun diğer iki sayı ile nasıl hesaplanacağı hakkında hiçbir fikrimiz yok -- ekleri nereye götürdüğünü görmek için kodun içine bakmamız gerekti -- gizli bağları keşfettik -- tam olarak anlamak için bu bağları da keşfetmemiz gerekir +- toplamların nereden geldiğini bulmak için koda bakmamız gerekiyordu +- gizli bağımlılıklar bulduk +- tam olarak anlaşılabilmesi için bu bağımlılıkların da incelenmesi gerekir Peki girdileri tedarik etmek toplama işlevinin görevi midir? Elbette değildir. Onun sorumluluğu sadece eklemektir. @@ -93,20 +93,20 @@ Kural #1: Sana geçirilsin .[#toc-rule-1-let-it-be-passed-to-you] En önemli kural şudur: **Fonksiyonların veya sınıfların ihtiyaç duyduğu tüm veriler onlara aktarılmalıdır**. -Bir şekilde kendi başlarına ulaşmalarına yardımcı olmak için gizli mekanizmalar icat etmek yerine, sadece parametreleri iletin. Kodunuzu kesinlikle iyileştirmeyecek gizli bir yol bulmak için harcadığınız zamandan tasarruf edeceksiniz. +Verilere kendilerinin erişmesi için gizli yollar icat etmek yerine, sadece parametreleri aktarın. Kodunuzu kesinlikle iyileştirmeyecek gizli yollar icat etmek için harcayacağınız zamandan tasarruf edeceksiniz. -Bu kurala her zaman ve her yerde uyarsanız, gizli bağları olmayan koda doğru yol alırsınız. Sadece yazan için değil, daha sonra okuyan herkes için de anlaşılabilir bir koda doğru. Fonksiyonların ve sınıfların imzalarından her şeyin anlaşılabilir olduğu ve uygulamada gizli sırlar aramaya gerek olmadığı bir kod. +Her zaman ve her yerde bu kurala uyarsanız, gizli bağımlılıkları olmayan bir koda doğru yol alırsınız. Sadece yazan için değil, daha sonra okuyan herkes için de anlaşılabilir bir koda. Fonksiyonların ve sınıfların imzalarından her şeyin anlaşılabilir olduğu ve uygulamada gizli sırlar aramaya gerek olmadığı bir kod. -Bu teknik ustalıkla **bağımlılık enjeksiyonu** olarak adlandırılır. Ve verilere **bağımlılıklar** denir. Ancak bu basit bir parametre aktarımıdır, başka bir şey değildir. +Bu teknik profesyonel olarak **bağımlılık enjeksiyonu** olarak adlandırılır. Ve bu verilere **bağımlılıklar** denir. Bu sadece sıradan bir parametre aktarımıdır, başka bir şey değildir. .[note] -Lütfen bir tasarım kalıbı olan bağımlılık enjeksiyonunu, tamamen farklı bir şey olan bir araç olan "bağımlılık enjeksiyon konteyneri" ile karıştırmayın. Kapsayıcıları daha sonra tartışacağız. +Lütfen bir tasarım kalıbı olan bağımlılık enjeksiyonunu bir araç olan "bağımlılık enjeksiyon konteyneri" ile karıştırmayın, bu tamamen farklı bir şeydir. Kapsayıcılar ile daha sonra ilgileneceğiz. Fonksiyonlardan Sınıflara .[#toc-from-functions-to-classes] ----------------------------------------------------------- -Peki sınıfların bununla ilişkisi nedir? Bir sınıf basit bir fonksiyondan daha karmaşık bir varlıktır, ancak 1. kural burada da geçerlidir. Sadece [argümanları aktarmanın daha fazla yolu |passing-dependencies] vardır. Örneğin, bir fonksiyonun durumuna oldukça benzer: +Peki sınıflar nasıl ilişkilidir? Bir sınıf basit bir fonksiyondan daha karmaşık bir birimdir, ancak 1. kural burada da tamamen geçerlidir. Sadece [argümanları aktarmanın daha fazla yolu |passing-dependencies] vardır. Örneğin, bir fonksiyonun durumuna oldukça benzer: ```php class Matematik @@ -121,7 +121,7 @@ $math = new Matematik; echo $math->toplami(23, 1); // 24 ``` -Ya da diğer yöntemleri kullanarak veya doğrudan kurucu tarafından: +Veya diğer yöntemler aracılığıyla ya da doğrudan kurucu aracılığıyla: ```php class Toplami @@ -149,9 +149,9 @@ Her iki örnek de bağımlılık enjeksiyonu ile tamamen uyumludur. Gerçek Hayattan Örnekler .[#toc-real-life-examples] --------------------------------------------------- -Gerçek dünyada, sayıları toplamak için sınıflar yazmayacaksınız. Gerçek dünya örneklerine geçelim. +Gerçek dünyada, sayıları toplamak için sınıflar yazmayacaksınız. Şimdi pratik örneklere geçelim. -Bir blog makalesini temsil eden bir `Article` sınıfımız olsun: +Bir blog gönderisini temsil eden bir `Article` sınıfımız olsun: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` - `save()` yöntemi makaleyi bir veritabanı tablosuna kaydeder. Bir aksaklık olmasaydı, [Nette Database |database:] kullanarak bunu uygulamak çocuk oyuncağı olurdu: `Article` [veritabanı |database:] bağlantısını, yani `Nette\Database\Connection` sınıf nesnesini nereden almalıdır? + `save()` yöntemi makaleyi bir veritabanı tablosuna kaydedecektir. Bunu [Nette Database |database:] kullanarak uygulamak, bir aksaklık olmasaydı, çocuk oyuncağı olacaktı: `Article` [veritabanı |database:] bağlantısını, yani `Nette\Database\Connection` sınıfının bir nesnesini nereden alacak? -Çok fazla seçeneğimiz var gibi görünüyor. Statik bir değişkende bir yerden alabilir. Ya da veritabanı bağlantısını sağlayacak bir sınıftan miras alabilir. Ya da bir [singleton |global-state#Singleton]'dan yararlanabilir. Ya da Laravel'de kullanılan sözde facades: +Görünüşe göre birçok seçeneğimiz var. Bir yerdeki statik bir değişkenden alabilir. Ya da veritabanı bağlantısı sağlayan bir sınıftan miras alabilir. Ya da bir [singleton |global-state#Singleton]'dan yararlanabilir. Ya da Laravel'de kullanılan sözde facade'leri kullanabilir: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ Harika, sorunu çözdük. Yoksa biz mi? -[Kural 1: Sana geçirilsin |#rule #1: Let It Be Passed to You] sınıfın ihtiyaç duyduğu tüm bağımlılıklar ona geçmelidir. Çünkü bunu yapmazsak ve kuralı ihlal edersek, gizli bağlarla dolu kirli koda, anlaşılmazlığa giden yola girmiş oluruz ve sonuçta bakımı ve geliştirilmesi eziyet olan bir uygulama ortaya çıkar. +[Kural 1: Sana geçirilsin |#rule #1: Let It Be Passed to You] Geçsin: sınıfın ihtiyaç duyduğu tüm bağımlılıklar ona geçmelidir. Çünkü bu kuralı ihlal edersek, gizli bağımlılıklarla dolu kirli bir koda, anlaşılmazlığa giden bir yola girmiş oluruz ve sonuçta bakımı ve geliştirilmesi sancılı bir uygulama ortaya çıkar. - `Article` sınıfının kullanıcısı, `save()` yönteminin makaleyi nerede sakladığı konusunda hiçbir fikre sahip değildir. Bir veritabanı tablosunda mı? Hangisinde, üretimde mi yoksa geliştirmede mi? Ve bu nasıl değiştirilebilir? + `Article` sınıfının kullanıcısı, `save()` yönteminin makaleyi nerede sakladığı konusunda hiçbir fikre sahip değildir. Bir veritabanı tablosunda mı? Hangisinde, üretim mi test mi? Ve nasıl değiştirilebilir? -Kullanıcı `DB::insert()` yönteminin kullanımını bulmak için `save()` yönteminin nasıl uygulandığına bakmalıdır. Dolayısıyla, bu yöntemin bir veritabanı bağlantısını nasıl sağladığını bulmak için daha fazla araştırma yapması gerekir. Ve gizli bağlar oldukça uzun bir zincir oluşturabilir. +Kullanıcı `save()` yönteminin nasıl uygulandığına bakmalı ve `DB::insert()` yönteminin kullanımını bulmalıdır. Dolayısıyla, bu yöntemin bir veritabanı bağlantısını nasıl elde ettiğini bulmak için daha fazla araştırma yapması gerekir. Ve gizli bağımlılıklar oldukça uzun bir zincir oluşturabilir. -Gizli bağlar, Laravel cepheleri veya statik değişkenler temiz, iyi tasarlanmış kodda asla bulunmaz. Temiz ve iyi tasarlanmış kodda, argümanlar geçirilir: +Temiz ve iyi tasarlanmış kodda, hiçbir zaman gizli bağımlılıklar, Laravel cepheleri veya statik değişkenler yoktur. Temiz ve iyi tasarlanmış kodda, argümanlar geçirilir: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Daha sonra göreceğimiz gibi, bir kurucu kullanmak daha da pratiktir: +Daha sonra göreceğimiz gibi, daha da pratik bir yaklaşım kurucu aracılığıyla olacaktır: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Deneyimli bir programcıysanız, `Article` 'un `save()` yöntemine hiç sahip olmaması gerektiğini, saf bir veri bileşeni olması gerektiğini ve ayrı bir deponun depolamayla ilgilenmesi gerektiğini düşünüyor olabilirsiniz. Bu mantıklıdır. Ancak bu bizi bağımlılık enjeksiyonu olan ve basit örnekler vermeye çalıştığımız konunun çok ötesine götürecektir. +Deneyimli bir programcıysanız, `Article` adresinin bir `save()` yöntemine sahip olmaması gerektiğini düşünebilirsiniz; tamamen bir veri bileşenini temsil etmeli ve ayrı bir depo kaydetme işlemiyle ilgilenmelidir. Bu mantıklıdır. Ancak bu bizi bağımlılık enjeksiyonu olan konunun kapsamının ve basit örnekler sunma çabasının çok ötesine götürecektir. -Örneğin, çalışması için bir veritabanına ihtiyaç duyan bir sınıf yazacaksanız, onu nereden alacağınızı bulmayın, ancak size aktarılmasını sağlayın. Belki bir yapıcıya veya başka bir yönteme parametre olarak. Bağımlılıkları beyan edin. Bunları sınıfınızın API'sinde gösterin. Anlaşılabilir ve öngörülebilir bir kod elde edeceksiniz. +Örneğin çalışması için bir veritabanına ihtiyaç duyan bir sınıf yazıyorsanız, veritabanını nereden alacağınızı icat etmeyin, ancak veritabanını geçirin. Ya kurucunun bir parametresi olarak ya da başka bir yöntemle. Bağımlılıkları kabul edin. Bunları sınıfınızın API'sinde kabul edin. Anlaşılabilir ve öngörülebilir bir kod elde edeceksiniz. -Hata mesajlarını günlüğe kaydeden bu sınıfa ne dersiniz? +Peki ya hata mesajlarını günlüğe kaydeden bu sınıf? ```php class Logger @@ -266,9 +266,9 @@ Ne düşünüyorsunuz, [kurala 1: Sana geçirilsin |#rule #1: Let It Be Passed t Biz yapmadık. -Anahtar bilgi olan günlük dosyası dizini, sınıf tarafından sabitten *elde edilir*. +Anahtar bilgi, yani günlük dosyasının bulunduğu dizin, sınıfın kendisi tarafından sabitten *elde edilir*. -Örnek kullanıma bakın: +Kullanım örneğine bakın: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Uygulamayı bilmeden, mesajların nereye yazıldığı sorusuna cevap verebilir misiniz? Bu size LOG_DIR sabitinin varlığının çalışması için gerekli olduğunu düşündürür mü? Ve farklı bir konuma yazan ikinci bir örnek oluşturabilir misiniz? Kesinlikle hayır. +Uygulamayı bilmeden, mesajların nereye yazıldığı sorusuna cevap verebilir misiniz? Çalışması için `LOG_DIR` sabitinin varlığının gerekli olduğunu tahmin eder miydiniz? Ve farklı bir konuma yazacak ikinci bir örnek oluşturabilir misiniz? Kesinlikle hayır. Sınıfı düzeltelim: @@ -295,7 +295,7 @@ class Logger } ``` -Sınıf artık çok daha net, daha yapılandırılabilir ve dolayısıyla daha kullanışlı. +Sınıf artık çok daha anlaşılır, yapılandırılabilir ve dolayısıyla daha kullanışlı. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Ama umurumda değil! .[#toc-but-i-don-t-care] -------------------------------------------- -*"Bir Article nesnesi oluşturduğumda ve save() işlevini çağırdığımda, veritabanıyla uğraşmak istemiyorum, sadece yapılandırmada ayarladığım veritabanına kaydedilmesini istiyorum. "* +*"Bir Article nesnesi oluşturup save() işlevini çağırdığımda, veritabanıyla uğraşmak istemiyorum; sadece yapılandırmada ayarladığım veritabanına kaydedilmesini istiyorum."* -*"Logger kullandığımda, sadece mesajın yazılmasını istiyorum ve nerede olduğuyla uğraşmak istemiyorum. Bırakın global ayarlar kullanılsın. "* +*"Logger kullandığımda, sadece mesajın yazılmasını istiyorum ve nerede olduğuyla uğraşmak istemiyorum. Bırakın global ayarlar kullanılsın."* -Bunlar doğru yorumlar. +Bunlar geçerli noktalar. -Örnek olarak, haber bültenleri gönderen ve bunun nasıl gittiğini kaydeden bir sınıfı ele alalım: +Örnek olarak, haber bültenleri gönderen ve nasıl gittiğini günlüğe kaydeden bir sınıfa bakalım: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -Artık `LOG_DIR` sabitini kullanmayan geliştirilmiş `Logger`, kurucuda bir dosya yolu gerektirir. Bu nasıl çözülür? `NewsletterDistributor` sınıfı mesajların nereye yazıldığı ile ilgilenmez, sadece onları yazmak ister. +Artık `LOG_DIR` sabitini kullanmayan geliştirilmiş `Logger`, dosya yolunun yapıcıda belirtilmesini gerektirir. Bu nasıl çözülür? `NewsletterDistributor` sınıfı mesajların nereye yazıldığı ile ilgilenmez; sadece onları yazmak ister. -Çözüm yine [kural 1: Sana geçirilsin |#rule #1: Let It Be Passed to You] Geçsin: sınıfın ihtiyaç duyduğu tüm verileri ona iletin. +Çözüm yine [1 numaralı kuraldır: Bırakın Size |#rule #1: Let It Be Passed to You] Geçsin: sınıfın ihtiyaç duyduğu tüm verileri iletin. -Yani günlüğe giden yolu kurucuya aktarıyoruz ve daha sonra bunu `Logger` nesnesini oluşturmak için kullanıyoruz? +Yani bu, günlüğe giden yolu kurucu aracılığıyla ilettiğimiz ve daha sonra `Logger` nesnesini oluştururken kullandığımız anlamına mı geliyor? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Bu şekilde değil! Çünkü yol ** `NewsletterDistributor` sınıfının ihtiyaç duyduğu verilere ait değildir; `Logger`. Sınıfın logger'ın kendisine ihtiyacı var. Ve biz de bunu aktaracağız: +Hayır, bu şekilde değil! Yol, `NewsletterDistributor` sınıfının ihtiyaç duyduğu veriler arasında yer almaz; aslında `Logger` 'un buna ihtiyacı vardır. Aradaki farkı görüyor musunuz? `NewsletterDistributor` sınıfının logger'ın kendisine ihtiyacı var. Bu yüzden bunu geçeceğiz: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Artık `NewsletterDistributor` sınıfının imzalarından, günlük tutmanın işlevselliğinin bir parçası olduğu açıkça anlaşılmaktadır. Ve belki de test amaçlı olarak logger'ı başka bir logger ile değiştirme görevi oldukça önemsizdir. -Dahası, `Logger` sınıfının kurucusu değiştirilirse, bunun bizim sınıfımız üzerinde hiçbir etkisi olmayacaktır. +Artık `NewsletterDistributor` sınıfının imzalarından, günlük tutmanın da işlevselliğinin bir parçası olduğu açıkça anlaşılmaktadır. Ve belki de test için kaydediciyi başka bir kaydediciyle değiştirme görevi tamamen önemsizdir. +Dahası, `Logger` sınıfının kurucusu değişirse, bu bizim sınıfımızı etkilemeyecektir. -Kural #2: Sizin Olanı Alın .[#toc-rule-2-take-what-is-yours] ------------------------------------------------------------- +Kural #2: Senin Olanı Al .[#toc-rule-2-take-what-s-yours] +--------------------------------------------------------- -Yanlış yönlendirilmeyin ve bağımlılıklarınızın parametrelerinin size aktarılmasına izin vermeyin. Bağımlılıkları doğrudan aktarın. +Yanlış yönlendirilmeyin ve bağımlılıklarınızın bağımlılıklarını kendinizin geçmesine izin vermeyin. Sadece kendi bağımlılıklarınızı geçirin. -Bu, diğer nesneleri kullanan kodu, kurucularındaki değişikliklerden tamamen bağımsız hale getirecektir. API'si daha doğru olacaktır. Ve en önemlisi, bu bağımlılıkları başkalarıyla değiştirmek önemsiz olacaktır. +Bu sayede, diğer nesneleri kullanan kod, yapıcılarındaki değişikliklerden tamamen bağımsız olacaktır. API'si daha doğru olacaktır. Ve hepsinden önemlisi, bu bağımlılıkları başkalarıyla değiştirmek önemsiz olacaktır. -Ailenin Yeni Üyesi .[#toc-a-new-member-of-the-family] ------------------------------------------------------ +Yeni Aile Üyesi .[#toc-new-family-member] +----------------------------------------- -Geliştirme ekibi, veritabanına yazan ikinci bir logger oluşturmaya karar verdi. Böylece bir sınıf oluşturduk `DatabaseLogger`. Yani iki sınıfımız var, `Logger` ve `DatabaseLogger`, biri dosyaya yazıyor, diğeri veritabanına yazıyor ... sizce de bu isimde bir gariplik yok mu? - `Logger` adını `FileLogger` olarak değiştirmek daha iyi olmaz mıydı? Elbette olurdu. +Geliştirme ekibi, veritabanına yazan ikinci bir logger oluşturmaya karar verdi. Böylece bir `DatabaseLogger` sınıfı oluşturduk. Yani iki sınıfımız var, `Logger` ve `DatabaseLogger`, biri bir dosyaya, diğeri bir veritabanına yazıyor ... adlandırma size de garip gelmiyor mu? + `Logger` 'u `FileLogger` olarak yeniden adlandırmak daha iyi olmaz mıydı? Kesinlikle evet. -Ama bunu akıllıca yapalım. Orijinal isim altında bir arayüz oluşturacağız: +Ama bunu akıllıca yapalım. Orijinal isim altında bir arayüz oluşturalım: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...her iki kaydedicinin de uygulayacağı: +... her iki kaydedicinin de uygulayacağı: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -Ve bu şekilde, logger'ın kullanıldığı kodun geri kalanında hiçbir şeyin değiştirilmesi gerekmeyecektir. Örneğin, `NewsletterDistributor` sınıfının kurucusu, parametre olarak `Logger` 'a ihtiyaç duymaktan memnun olacaktır. Ve ona hangi örneği aktaracağımız bize bağlı olacaktır. +Ve bu nedenle, logger'ın kullanıldığı kodun geri kalanında hiçbir şeyi değiştirmeye gerek kalmayacaktır. Örneğin, `NewsletterDistributor` sınıfının kurucusu hala parametre olarak `Logger` 'a ihtiyaç duymakla yetinecektir. Ve hangi örneği geçireceğimiz bize bağlı olacaktır. -**Bu nedenle arayüz isimlerine asla `Interface` son ekini veya `I` ön ekini vermiyoruz.** Aksi takdirde, bu kadar güzel kod geliştirmek imkansız olurdu. +**Bu yüzden arayüz isimlerine asla `Interface` son ekini veya `I` ön ekini eklemiyoruz.** Aksi takdirde kodu bu kadar güzel geliştirmek mümkün olmazdı. Houston, Bir Sorunumuz Var .[#toc-houston-we-have-a-problem] ------------------------------------------------------------ -Tüm uygulamada, ister dosya ister veritabanı olsun, bir logger'ın tek bir örneğiyle mutlu olabilir ve bir şeyin günlüğe kaydedildiği her yerde onu basitçe geçirebilirken, `Article` sınıfı söz konusu olduğunda durum oldukça farklıdır. Aslında, gerektiğinde, muhtemelen birden çok kez bunun örneklerini oluşturuyoruz. Kurucusundaki veritabanı bağlayıcısıyla nasıl başa çıkılır? +İster dosya tabanlı ister veritabanı tabanlı olsun, tüm uygulama boyunca tek bir logger örneğiyle idare edebilir ve bir şeyin günlüğe kaydedildiği her yerde onu basitçe geçirebilirken, `Article` sınıfı için durum oldukça farklıdır. Gerektiğinde, hatta birden çok kez örneklerini oluşturuyoruz. Kurucusundaki veritabanı bağımlılığı ile nasıl başa çıkılır? -Örnek olarak, bir form gönderdikten sonra bir makaleyi veritabanına kaydetmesi gereken bir denetleyici kullanabiliriz: +Örnek olarak, bir form gönderdikten sonra bir makaleyi veritabanına kaydetmesi gereken bir denetleyici verilebilir: ```php class EditController extends Controller @@ -437,9 +437,9 @@ class EditController extends Controller } ``` -Olası bir çözüm doğrudan sunulmaktadır: veritabanı nesnesinin kurucu tarafından `EditController` adresine aktarılmasını sağlayın ve `$article = new Article($this->db)` adresini kullanın. +Olası bir çözüm açıktır: veritabanı nesnesini `EditController` kurucusuna aktarın ve `$article = new Article($this->db)` adresini kullanın. -`Logger` ve dosya yolu ile ilgili önceki durumda olduğu gibi, bu doğru bir yaklaşım değildir. Veritabanı `EditController`'un değil, `Article`'un bir bağımlılığıdır. Bu nedenle veritabanını geçmek [2. kurala senin olanı al|#rule #2: take what is yours] aykırıdır. `Article` sınıfının kurucusu değiştirildiğinde (yeni bir parametre eklendiğinde), örneklerin oluşturulduğu tüm yerlerdeki kodun da değiştirilmesi gerekecektir. Ufff. +Tıpkı `Logger` ve dosya yolu ile ilgili önceki durumda olduğu gibi, bu doğru bir yaklaşım değildir. Veritabanı `EditController`'un değil, `Article`'un bir bağımlılığıdır. Veritabanını geçmek 2. [kurala |#rule #2: take what's yours] aykırıdır [: senin olanı al |#rule #2: take what's yours]. `Article` sınıf kurucusu değişirse (yeni bir parametre eklenirse), örneklerin oluşturulduğu her yerde kodu değiştirmeniz gerekecektir. Ufff. Houston, ne öneriyorsun? @@ -447,7 +447,7 @@ Houston, ne öneriyorsun? Kural #3: Bırakın Fabrika Halletsin .[#toc-rule-3-let-the-factory-handle-it] ---------------------------------------------------------------------------- -Gizli bağları kaldırarak ve tüm bağımlılıkları argüman olarak ileterek, daha yapılandırılabilir ve esnek sınıflar elde ederiz. Ve böylece bu daha esnek sınıfları oluşturmak ve yapılandırmak için başka bir şeye ihtiyacımız var. Buna fabrikalar diyeceğiz. +Gizli bağımlılıkları ortadan kaldırarak ve tüm bağımlılıkları argüman olarak geçirerek, daha yapılandırılabilir ve esnek sınıflar elde ettik. Bu nedenle, bizim için bu daha esnek sınıfları oluşturmak ve yapılandırmak için başka bir şeye ihtiyacımız var. Biz buna fabrikalar diyeceğiz. Temel kural şudur: Bir sınıfın bağımlılıkları varsa, örneklerinin oluşturulmasını fabrikaya bırakın. @@ -460,7 +460,7 @@ Lütfen fabrikaları kullanmanın belirli bir yolunu tanımlayan ve bu konuyla i Fabrika .[#toc-factory] ----------------------- -Fabrika, nesneleri üreten ve yapılandıran bir yöntem veya sınıftır. `Article` üreten sınıfa `ArticleFactory` diyoruz ve şöyle görünebilir: +Fabrika, nesneleri oluşturan ve yapılandıran bir yöntem veya sınıftır. `Article` üreten sınıfı `ArticleFactory` olarak adlandıracağız ve şu şekilde görünebilir: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Kontrol ünitesinde kullanımı aşağıdaki gibi olacaktır: +Kontrolördeki kullanımı aşağıdaki gibi olacaktır: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -Bu noktada, `Article` sınıf kurucusunun imzası değiştiğinde, kodun yanıt vermesi gereken tek kısmı `ArticleFactory` fabrikasının kendisidir. `Article` nesneleriyle çalışan `EditController` gibi diğer kodlar bundan etkilenmeyecektir. +Bu noktada, `Article` sınıf kurucusunun imzası değişirse, kodun tepki vermesi gereken tek kısmı `ArticleFactory` 'un kendisidir. `Article` nesneleriyle çalışan `EditController` gibi diğer tüm kodlar etkilenmeyecektir. -Şu anda kendimize hiç yardımcı olup olmadığımızı merak ederek alnınıza vuruyor olabilirsiniz. Kod miktarı arttı ve her şey şüpheli derecede karmaşık görünmeye başladı. +İşleri gerçekten daha iyi hale getirip getirmediğimizi merak ediyor olabilirsiniz. Kod miktarı arttı ve hepsi şüpheli bir şekilde karmaşık görünmeye başladı. -Merak etmeyin, yakında Nette DI konteynerine geçeceğiz. Ve bağımlılık enjeksiyonu kullanarak uygulama oluşturmayı son derece basit hale getirecek bir dizi asa sahiptir. Örneğin, `ArticleFactory` sınıfı yerine [basit bir arayüz yazmak |factory] yeterli olacaktır: +Merak etmeyin, yakında Nette DI konteynerine geçeceğiz. Ve bağımlılık enjeksiyonu kullanarak uygulama oluşturmayı büyük ölçüde basitleştirecek birkaç hilesi var. Örneğin, `ArticleFactory` sınıfı yerine sadece [basit bir arayüz yazmanız |factory] gerekecek: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Ama kendimizi aşıyoruz, bekleyin :-) +Ama biz kendimizi aşıyoruz; lütfen sabırlı olun :-) Özet .[#toc-summary] -------------------- -Bu bölümün başında size temiz kod tasarlamak için bir yol göstereceğimize söz vermiştik. Sadece sınıfları verin +Bu bölümün başında size temiz kod tasarlamak için bir süreç göstereceğimize söz vermiştik. Tek gereken sınıflar için -- [ihtiyaç duydukları bağımlılıklar |#Rule #1: Let It Be Passed to You] -- [ve doğrudan ihtiyaç duymadıkları şeyleri değil |#Rule #2: Take What Is Yours] -- [ve bağımlılıkları olan nesnelerin en iyi fabrikalarda yapıldığını |#Rule #3: Let the Factory Handle it] +- [ihtiyaç duydukları bağımlılıkları iletir |#Rule #1: Let It Be Passed to You] +- [tersine, doğrudan ihtiyaç duymadıkları şeyleri vermemek |#Rule #2: Take What's Yours] +- [ve bağımlılıkları olan nesnelerin en iyi fabrikalarda oluşturulduğunu |#Rule #3: Let the Factory Handle it] -İlk bakışta öyle görünmeyebilir, ancak bu üç kuralın geniş kapsamlı etkileri vardır. Kod tasarımına kökten farklı bir bakış açısı getirirler. Buna değer mi? Eski alışkanlıklarını bir kenara bırakıp bağımlılık enjeksiyonunu tutarlı bir şekilde kullanmaya başlayan programcılar, bunu profesyonel yaşamlarında çok önemli bir an olarak görüyorlar. Net ve sürdürülebilir uygulamalar dünyasının kapılarını açmıştır. +İlk bakışta, bu üç kuralın geniş kapsamlı sonuçları yokmuş gibi görünebilir, ancak kod tasarımına kökten farklı bir bakış açısı getirirler. Buna değer mi? Eski alışkanlıklarını terk edip bağımlılık enjeksiyonunu tutarlı bir şekilde kullanmaya başlayan geliştiriciler, bu adımı profesyonel yaşamlarında çok önemli bir an olarak görüyorlar. Onlar için açık ve sürdürülebilir uygulamalar dünyasının kapılarını açmıştır. -Peki ya kod sürekli olarak bağımlılık enjeksiyonu kullanmıyorsa? Ya statik metotlar ya da singletonlar üzerine inşa edilmişse? Bu herhangi bir sorun yaratır mı? Getirir [ve bu çok önemlidir |global-state]. +Peki ya kod sürekli olarak bağımlılık enjeksiyonu kullanmıyorsa? Ya statik yöntemlere ya da tekillere dayanıyorsa? Bu herhangi bir soruna neden olur mu? [Evet, neden olur, hem de çok temel |global-state] sorunlara. diff --git a/dependency-injection/tr/passing-dependencies.texy b/dependency-injection/tr/passing-dependencies.texy index a6d0a0b69e..6cb6795534 100644 --- a/dependency-injection/tr/passing-dependencies.texy +++ b/dependency-injection/tr/passing-dependencies.texy @@ -12,7 +12,7 @@ Argümanlar veya DI terminolojisinde "bağımlılıklar" sınıflara aşağıdak </div> -İlk üç yöntem genel olarak tüm nesne yönelimli dillerde geçerlidir, dördüncüsü ise Nette sunucularına özeldir, bu nedenle [ayrı bir bölümde |best-practices:inject-method-attribute] ele alınmıştır. Şimdi bu seçeneklerin her birine daha yakından bakacağız ve bunları belirli örneklerle göstereceğiz. +Şimdi farklı varyantları somut örneklerle açıklayacağız. Kurucu Enjeksiyonu .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Kurucu Enjeksiyonu .[#toc-constructor-injection] Nesne oluşturulduğunda bağımlılıklar yapıcıya argüman olarak aktarılır: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Bu form, sınıfın çalışması için kesinlikle ihtiyaç duyduğu zorunlu bağımlılıklar için kullanışlıdır, çünkü bunlar olmadan örnek oluşturulamaz. @@ -40,10 +40,10 @@ PHP 8.0'dan beri, işlevsel olarak eşdeğer olan daha kısa bir gösterim biçi ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ PHP 8.1'den itibaren bir özellik, içeriğinin değişmeyeceğini bildiren `rea ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService DI konteyneri, [otomatik |autowiring] bağlantı kullanarak bağımlılıkları otomatik olarak kurucuya geçirir. Bu şekilde aktarılamayan bağımsız değişkenler (örn. dizeler, sayılar, booleanlar) [yapılandırmada yazılır |services#Arguments]. +İnşaatçı Cehennemi .[#toc-constructor-hell] +------------------------------------------- + +*İnşaatçı cehennemi* terimi, bir çocuğun yapıcısı bağımlılıklar gerektiren bir ana sınıftan miras aldığı ve çocuğun da bağımlılıklara ihtiyaç duyduğu bir durumu ifade eder. Ayrıca ebeveynin bağımlılıklarını da devralmalı ve aktarmalıdır: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Sorun, `BaseClass` sınıfının kurucusunu değiştirmek istediğimizde, örneğin yeni bir bağımlılık eklendiğinde ortaya çıkar. O zaman tüm alt sınıfların kurucularını da değiştirmemiz gerekir. Bu da böyle bir değişikliği cehenneme çevirir. + +Bu nasıl önlenebilir? Çözüm **kalıtım yerine bileşime öncelik vermektir**. + +Öyleyse kodu farklı bir şekilde tasarlayalım. Soyut `Base*` sınıflarından kaçınacağız. `MyClass` , `BaseClass`'dan miras alarak bazı işlevler elde etmek yerine, bu işlevleri bir bağımlılık olarak aktaracaktır: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Ayarlayıcı Enjeksiyonu .[#toc-setter-injection] =============================================== -Bağımlılıklar, onları özel bir özellikte saklayan bir yöntem çağrılarak aktarılır. Bu yöntemler için olağan adlandırma kuralı `set*()` şeklindedir, bu nedenle ayarlayıcılar olarak adlandırılırlar. +Bağımlılıklar, onları özel bir özellikte saklayan bir yöntem çağrılarak aktarılır. Bu yöntemler için olağan adlandırma kuralı `set*()` şeklindedir, bu nedenle ayarlayıcılar olarak adlandırılırlar, ancak elbette başka herhangi bir şekilde adlandırılabilirler. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Bu yöntem, nesnenin bunları gerçekten alacağı (yani kullanıcının yöntemi çağıracağı) garanti edilmediğinden, sınıf işlevi için gerekli olmayan isteğe bağlı bağımlılıklar için kullanışlıdır. @@ -90,16 +150,16 @@ Bu yöntem, nesnenin bunları gerçekten alacağı (yani kullanıcının yöntem Aynı zamanda, bu yöntem bağımlılığı değiştirmek için setter'ın tekrar tekrar çağrılmasına izin verir. Bu istenmiyorsa, yönteme bir kontrol ekleyin veya PHP 8.1'den itibaren `$cache` özelliğini `readonly` bayrağı ile işaretleyin. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ Setter çağrısı, [kurulum bölümündeki |services#Setup] DI konteyner yapıl ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ Mülkiyet Enjeksiyonu .[#toc-property-injection] Bağımlılıklar doğrudan özelliğe aktarılır: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Bu yöntemin uygun olmadığı düşünülmektedir, çünkü özellik `public` olarak bildirilmelidir. Bu nedenle, aktarılan bağımlılığın gerçekten belirtilen türde olup olmayacağı üzerinde hiçbir kontrolümüz yoktur (bu PHP 7.4'ten önce doğruydu) ve yeni atanan bağımlılığa kendi kodumuzla tepki verme yeteneğimizi kaybederiz, örneğin sonraki değişiklikleri önlemek için. Aynı zamanda, özellik sınıfın genel arayüzünün bir parçası haline gelir ki bu da arzu edilen bir durum olmayabilir. @@ -137,12 +197,18 @@ Değişkenin ayarı, [kurulum bölümündeki |services#Setup] DI konteyner yapı ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Enjeksiyon .[#toc-inject] +========================= + +Önceki üç yöntem genel olarak tüm nesne yönelimli dillerde geçerli olsa da, yöntem, ek açıklama veya *inject* niteliği ile enjekte etme Nette sunucularına özgüdür. Bunlar [ayrı bir bölümde |best-practices:inject-method-attribute] ele alınmaktadır. + + Hangi Yolu Seçmeli? .[#toc-which-way-to-choose] =============================================== diff --git a/dependency-injection/tr/services.texy b/dependency-injection/tr/services.texy index 2d4ec7381f..a4b3fb86c4 100644 --- a/dependency-injection/tr/services.texy +++ b/dependency-injection/tr/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Enjeksiyon Modu .[#toc-inject-mode] =================================== -`inject: true` bayrağı, bağımlılıkların [inject |best-practices:inject-method-attribute#Inject Annotations] ek açıklaması ve [inject*() |best-practices:inject-method-attribute#inject Methods] yöntemleri ile genel değişkenler aracılığıyla aktarılmasını etkinleştirmek için kullanılır. +`inject: true` bayrağı, bağımlılıkların [inject |best-practices:inject-method-attribute#Inject Attributes] ek açıklaması ve [inject*() |best-practices:inject-method-attribute#inject Methods] yöntemleri ile genel değişkenler aracılığıyla aktarılmasını etkinleştirmek için kullanılır. ```neon services: diff --git a/dependency-injection/uk/@home.texy b/dependency-injection/uk/@home.texy index 16aed36c8a..3b06c6aa4d 100644 --- a/dependency-injection/uk/@home.texy +++ b/dependency-injection/uk/@home.texy @@ -5,8 +5,10 @@ Dependency Injection - це патерн проектування, який докорінно змінить ваш погляд на код і розробку. Він відкриває шлях до світу чисто спроектованих і стійких додатків. - [Що таке Dependency Injection? |introduction] -- [Що таке DI-контейнер? |container] +- [Глобальний стан та синглетони |global-state] - [Передача залежностей |passing-dependencies] +- [Що таке DI-контейнер? |container] +- [Часті запитання |faq] Nette DI diff --git a/dependency-injection/uk/@left-menu.texy b/dependency-injection/uk/@left-menu.texy index 2d5abd2a6a..edbb7e6927 100644 --- a/dependency-injection/uk/@left-menu.texy +++ b/dependency-injection/uk/@left-menu.texy @@ -1,8 +1,10 @@ Впровадження залежностей ************************ - [Що таке "впровадження залежностей"? |introduction] -- [Що таке "DI-контейнер"? |container] +- [Глобальний стан та синглетони |global-state] - [Передача залежностей |passing-dependencies] +- [Що таке "DI-контейнер"? |container] +- [Часті запитання |faq] Nette DI diff --git a/dependency-injection/uk/faq.texy b/dependency-injection/uk/faq.texy new file mode 100644 index 0000000000..c04c4b1a9e --- /dev/null +++ b/dependency-injection/uk/faq.texy @@ -0,0 +1,112 @@ +Поширені запитання про DI (FAQ) +******************************* + + +Чи є DI іншою назвою для IoC? .[#toc-is-di-another-name-for-ioc] +---------------------------------------------------------------- + +*Inversion of Control* (IoC) - це принцип, який фокусується на тому, як виконується код - чи ваш код ініціює зовнішній код, чи ваш код інтегрується в зовнішній код, який потім викликає його. +IoC - це широке поняття, яке включає в себе [події |nette:glossary#Events], так званий [голлівудський принцип |application:components#Hollywood style] та інші аспекти. +Фабрики, які є частиною [Правила №3: Нехай фабрика розбирається з цим |introduction#Rule #3: Let the Factory Handle It] і представляють собою інверсію для оператора `new`, також є компонентами цієї концепції. + +*Dependency Injection* (DI) - це те, як один об'єкт знає про інший об'єкт, тобто залежність. Це патерн проектування, який вимагає явної передачі залежностей між об'єктами. + +Таким чином, можна сказати, що DI є специфічною формою IoC. Однак не всі форми IoC підходять з точки зору чистоти коду. Наприклад, до антипаттернів ми відносимо всі техніки, які працюють з [глобальним станом |global state] або так званим [Service Locator |#What is a Service Locator]. + + +Що таке Service Locator? .[#toc-what-is-a-service-locator] +---------------------------------------------------------- + +Сервіс-локатор - це альтернатива ін'єкції залежностей. Він працює шляхом створення центрального сховища, де реєструються всі доступні сервіси або залежності. Коли об'єкту потрібна залежність, він запитує її з Service Locator. + +Однак, порівняно з Dependency Injection, він втрачає прозорість: залежності не передаються безпосередньо об'єктам, і тому їх не так легко ідентифікувати, що вимагає вивчення коду, щоб виявити і зрозуміти всі зв'язки. Тестування також ускладнюється, оскільки ми не можемо просто передати імітаційні об'єкти об'єктам, що тестуються, а повинні пройти через Service Locator. Крім того, Service Locator порушує дизайн коду, оскільки окремі об'єкти повинні знати про його існування, що відрізняється від Dependency Injection, де об'єкти не знають про контейнер DI. + + +Коли краще не використовувати DI? .[#toc-when-is-it-better-not-to-use-di] +------------------------------------------------------------------------- + +Не існує відомих труднощів, пов'язаних з використанням патерну проектування Ін'єкція залежностей (Dependency Injection). Навпаки, отримання залежностей з глобально доступних місць призводить до [ряду ускладнень |global-state], так само як і використання Service Locator. +Тому бажано завжди використовувати DI. Це не догматичний підхід, просто кращої альтернативи не знайдено. + +Однак є певні ситуації, коли ми не передаємо об'єкти один одному, а отримуємо їх з глобального простору. Наприклад, при налагодженні коду і необхідності вивести значення змінної в певній точці програми, виміряти тривалість виконання певної частини програми або записати повідомлення в лог. +У таких випадках, коли мова йде про тимчасові дії, які згодом будуть видалені з коду, цілком виправданим є використання глобально доступного дампера, секундоміра або логгера. Ці інструменти, зрештою, не належать до проектування коду. + + +Чи має використання DI свої недоліки? .[#toc-does-using-di-have-its-drawbacks] +------------------------------------------------------------------------------ + +Чи має використання Dependency Injection якісь недоліки, такі як збільшення складності написання коду чи погіршення продуктивності? Що ми втрачаємо, коли починаємо писати код відповідно до DI? + +DI не впливає на продуктивність програми або вимоги до пам'яті. Продуктивність DI-контейнера може відігравати певну роль, але у випадку [Nette DI | nette-container] контейнер компілюється в чистий PHP, тому його накладні витрати під час виконання програми практично дорівнюють нулю. + +Під час написання коду необхідно створювати конструктори, які приймають залежності. Раніше це могло зайняти багато часу, але завдяки сучасним IDE і розширенню [властивостей конструкторів |https://blog.nette.org/uk/php-8-0-povnij-oglyad-novin#toc-constructor-property-promotion], тепер це справа кількох секунд. За допомогою Nette DI і плагіна PhpStorm можна легко створювати фабрики всього за кілька кліків. +З іншого боку, немає необхідності писати синглетони і статичні точки доступу. + +Можна зробити висновок, що правильно спроектований додаток з використанням DI не є ні коротшим, ні довшим у порівнянні з додатком, що використовує синглетони. Частини коду, що працюють з залежностями, просто витягуються з окремих класів і переміщуються в нові місця, тобто в контейнер DI і фабрики. + + +Як переписати старий додаток на DI? .[#toc-how-to-rewrite-a-legacy-application-to-di] +------------------------------------------------------------------------------------- + +Міграція зі старого додатку на Dependency Injection може бути складним процесом, особливо для великих і складних додатків. Важливо підходити до цього процесу систематично. + +- При переході на Dependency Injection важливо, щоб всі члени команди розуміли принципи і практики, які використовуються. +- По-перше, проведіть аналіз існуючого додатку, щоб визначити ключові компоненти та їх залежності. Складіть план, які частини будуть рефакторингуватися і в якому порядку. +- Впровадьте DI-контейнер або, ще краще, використовуйте існуючу бібліотеку, таку як Nette DI. +- Поступово рефакторингуйте кожну частину програми для використання Dependency Injection. Це може включати модифікацію конструкторів або методів, щоб вони приймали залежності в якості параметрів. +- Змініть місця в коді, де створюються об'єкти залежностей, так, щоб замість цього залежності інжектувалися контейнером. Це може включати використання фабрик. + +Пам'ятайте, що перехід на інжекцію залежностей - це інвестиція в якість коду і довгострокову стійкість програми. Хоча внести ці зміни може бути складно, в результаті ви отримаєте чистіший, більш модульний і легко тестуємий код, готовий до майбутніх розширень і обслуговування. + + +Чому композиція краща за успадкування? .[#toc-why-composition-is-preferred-over-inheritance] +-------------------------------------------------------------------------------------------- +Краще використовувати композицію, ніж успадкування, оскільки вона слугує для повторного використання коду без необхідності турбуватися про ефект "просочування" змін. Таким чином, це забезпечує більш вільний зв'язок, коли нам не потрібно турбуватися про те, що зміна одного коду спричинить зміну іншого залежного коду, який також потребує змін. Типовим прикладом є ситуація, яку називають [пеклом конструктора |passing-dependencies#Constructor hell]. + + +Чи можна використовувати Nette DI Container за межами Nette? .[#toc-can-nette-di-container-be-used-outside-of-nette] +-------------------------------------------------------------------------------------------------------------------- + +Безумовно, так. Nette DI Container є частиною Nette, але розроблений як окрема бібліотека, яку можна використовувати незалежно від інших частин фреймворку. Просто встановіть її за допомогою Composer, створіть конфігураційний файл, що визначає ваші сервіси, а потім за допомогою декількох рядків PHP-коду створіть DI-контейнер. +І ви можете одразу ж почати використовувати переваги Dependency Injection у своїх проектах. + +У розділі [Контейнер Nette DI |nette-container] описано, як виглядає конкретний варіант використання, включно з кодом. + + +Чому конфігурація зберігається у файлах NEON? .[#toc-why-is-the-configuration-in-neon-files] +-------------------------------------------------------------------------------------------- + +NEON - це проста і зрозуміла мова конфігурації, розроблена в рамках Nette для налаштування програм, сервісів та їх залежностей. У порівнянні з JSON або YAML, вона пропонує набагато більш інтуїтивно зрозумілі та гнучкі можливості для цього. У NEON ви можете природно описувати прив'язки, які неможливо було б написати в Symfony & YAML або взагалі, або тільки через складний опис. + + +Чи сповільнює розбір NEON-файлів роботу програми? .[#toc-does-parsing-neon-files-slow-down-the-application] +----------------------------------------------------------------------------------------------------------- + +Хоча NEON-файли розбираються дуже швидко, цей аспект не має особливого значення. Причина в тому, що розбір файлів відбувається лише один раз під час першого запуску програми. Після цього генерується код DI-контейнера, який зберігається на диску і виконується при кожному наступному запиті без необхідності подальшого розбору. + +Саме так це працює у виробничому середовищі. Під час розробки NEON-файли аналізуються щоразу, коли змінюється їхній вміст, що гарантує, що розробник завжди має актуальний DI-контейнер. Як згадувалося раніше, фактичний синтаксичний аналіз - це справа однієї миті. + + +Як отримати доступ до параметрів з конфігураційного файлу в моєму класі? .[#toc-how-do-i-access-the-parameters-from-the-configuration-file-in-my-class] +------------------------------------------------------------------------------------------------------------------------------------------------------- + +Пам'ятайте про [Правило №1: Дозвольте, щоб вам передали |introduction#Rule #1: Let It Be Passed to You]. Якщо класу потрібна інформація з конфігураційного файлу, нам не потрібно з'ясовувати, як отримати доступ до цієї інформації; натомість, ми просто запитуємо її - наприклад, через конструктор класу. І виконуємо передачу у конфігураційному файлі. + +У цьому прикладі `%myParameter%` - це заповнювач для значення параметра `myParameter`, яке буде передано в конструктор `MyClass`: + +```php +# config.neon +parameters: + myParameter: Some value + +services: + - MyClass(%myParameter%) +``` + +Якщо ви хочете передати декілька параметрів або використовувати автопідстановку, корисно обернути [параметри в об'єкт |best-practices:passing-settings-to-presenters]. + + +Чи підтримує Nette інтерфейс PSR-11 Container? .[#toc-does-nette-support-psr-11-container-interface] +---------------------------------------------------------------------------------------------------- + +Nette DI Container не підтримує PSR-11 безпосередньо. Однак, якщо вам потрібна сумісність між Nette DI Container і бібліотеками або фреймворками, які очікують інтерфейс PSR-11 Container Interface, ви можете створити [простий адаптер |https://gist.github.com/dg/7f02403bd36d9d1c73802a6268a4361f], який слугуватиме мостом між Nette DI Container і PSR-11. diff --git a/dependency-injection/uk/global-state.texy b/dependency-injection/uk/global-state.texy new file mode 100644 index 0000000000..85242211cd --- /dev/null +++ b/dependency-injection/uk/global-state.texy @@ -0,0 +1,312 @@ +Глобальна держава та одинаки +**************************** + +.[perex] +Попередження: наступні конструкції є симптомами поганого дизайну коду: + +- `Foo::getInstance()` +- `DB::insert(...)` +- `Article::setDb($db)` +- `ClassName::$var` або `static::$var` + +У вашому коді зустрічається будь-яка з цих конструкцій? Тоді у вас є можливість покращити його. Можливо, ви думаєте, що це звичайні конструкції, які ми бачимо у зразках рішень різних бібліотек та фреймворків. +На жаль, вони все ще є явним індикатором поганого дизайну. Їх об'єднує одне: використання глобального стану. + +Звісно, ми не говоримо про якусь академічну чистоту. Використання глобального стану та синглетонів руйнівно впливає на якість коду. Його поведінка стає непередбачуваною, знижує продуктивність розробника і змушує інтерфейси класів брехати про свої справжні залежності. Що збиває з пантелику програмістів. + +У цій главі ми покажемо, як це можливо. + + +Глобальне взаємозв'язування .[#toc-global-interlinking] +------------------------------------------------------- + +Фундаментальною проблемою глобального стану є те, що він є глобально доступним. Це робить можливим запис до бази даних через глобальний (статичний) метод `DB::insert()`. +В ідеальному світі об'єкт повинен мати можливість взаємодіяти тільки з тими об'єктами, які були йому [безпосередньо передані |passing-dependencies]. +Якщо я створюю два об'єкти `A` і `B` і ніколи не передаю посилання з `A` на `B`, то ні `A`, ні `B` не зможуть отримати доступ до іншого об'єкта або змінити його стан. +Це дуже бажана властивість коду. Це схоже на наявність батарейки і лампочки; лампочка не засвітиться, поки ви не з'єднаєте їх разом. + +Це не стосується глобальних (статичних) змінних або синглетонів. Об'єкт `A` може отримати *бездротовий* доступ до об'єкта `C` і модифікувати його, не передаючи жодних посилань, викликавши `C::changeSomething()`. +Якщо об'єкт `B` також захоплює глобальний `C`, то `A` і `B` можуть взаємодіяти один з одним через `C`. + +Використання глобальних змінних вводить в систему нову форму *бездротового* зв'язку, яку не видно ззовні. +Це створює димову завісу, що ускладнює розуміння і використання коду. +Розробники повинні читати кожен рядок вихідного коду, щоб по-справжньому зрозуміти залежності. Замість того, щоб просто ознайомитися з інтерфейсом класів. +До того ж, це абсолютно непотрібна зв'язка. + +.[note] +З точки зору поведінки, немає ніякої різниці між глобальною і статичною змінною. Вони однаково шкідливі. + + +Страшна дія на відстані .[#toc-the-spooky-action-at-a-distance] +--------------------------------------------------------------- + +"Моторошна дія на відстані" - так Альберт Ейнштейн назвав явище у квантовій фізиці, від якого у 1935 році у нього мурашки по шкірі. +Це квантова заплутаність, особливість якої полягає в тому, що коли ви вимірюєте інформацію про одну частинку, ви одразу ж впливаєте на іншу частинку, навіть якщо вони знаходяться на відстані мільйонів світлових років одна від одної. +Що, здавалося б, порушує фундаментальний закон Всесвіту, згідно з яким ніщо не може рухатися швидше за світло. + +У світі програмного забезпечення ми можемо назвати "моторошною дією на відстані" ситуацію, коли ми запускаємо процес, який вважаємо ізольованим (бо не передали йому жодних посилань), але у віддалених місцях системи відбуваються неочікувані взаємодії та зміни стану, про які ми не повідомили об'єкту. Це може відбуватися тільки через глобальний стан. + +Уявіть, що ви приєдналися до команди розробників проекту, яка має велику, зрілу кодову базу. Ваш новий керівник просить вас реалізувати нову функцію і, як хороший розробник, ви починаєте з написання тесту. Але оскільки ви новачок у проекті, ви робите багато дослідницьких тестів типу "що станеться, якщо я викличу цей метод". І ви намагаєтеся написати наступний тест: + +```php +function testCreditCardCharge() +{ + $cc = new CreditCard('1234567890123456', 5, 2028); // номер вашої картки + $cc->charge(100); +} +``` + +Ви запускаєте код, можливо, кілька разів, і через деякий час помічаєте на своєму телефоні повідомлення від банку про те, що кожного разу, коли ви його запускали, з вашої кредитної картки було знято $100 🤦‍♂️. + +Як тест міг спричинити реальне списання коштів? Працювати з кредитною карткою непросто. Ви повинні взаємодіяти зі стороннім веб-сервісом, знати URL-адресу цього веб-сервісу, увійти в систему і так далі. +Жодна з цих відомостей не міститься в тесті. Навіть гірше, ви навіть не знаєте, де ця інформація присутня, а отже, як імітувати зовнішні залежності так, щоб кожен запуск не призводив до повторного списання 100 доларів. І як розробник-початківець, звідки ви могли знати, що те, що ви збираєтесь робити, призведе до того, що ви станете на 100 доларів біднішими? + +На відстані це виглядає моторошно! + +У вас немає іншого вибору, окрім як перелопатити купу вихідного коду, розпитуючи старших і досвідченіших колег, поки ви не зрозумієте, як працюють зв'язки в проекті. +Це пов'язано з тим, що, дивлячись на інтерфейс класу `CreditCard`, ви не можете визначити глобальний стан, який потрібно ініціалізувати. Навіть перегляд вихідного коду класу не підкаже вам, який метод ініціалізації викликати. У кращому випадку, ви можете знайти глобальну змінну, до якої здійснюється доступ, і спробувати здогадатися, як її ініціалізувати, виходячи з цього. + +Класи в такому проекті - патологічні брехуни. Платіжна картка робить вигляд, що ви можете просто створити її екземпляр і викликати метод `charge()`. Однак вона таємно взаємодіє з іншим класом, `PaymentGateway`. Навіть в її інтерфейсі написано, що вона може ініціалізуватися самостійно, але насправді вона витягує облікові дані з якогось конфігураційного файлу і так далі. +Розробникам, які написали цей код, зрозуміло, що `CreditCard` потрібен `PaymentGateway`. Вони написали код саме так. Але для новачків у проекті це повна загадка і заважає навчанню. + +Як виправити ситуацію? Дуже просто. **Дозвольте API оголошувати залежності.** + +```php +function testCreditCardCharge() +{ + $gateway = new PaymentGateway(/* ... */); + $cc = new CreditCard('1234567890123456', 5, 2028); + $cc->charge($gateway, 100); +} +``` + +Зверніть увагу, як взаємозв'язки в коді стають раптово очевидними. Оголосивши, що метод `charge()` потребує `PaymentGateway`, вам не потрібно нікого питати, як код взаємозалежний. Ви знаєте, що маєте створити його екземпляр, і коли ви намагаєтесь це зробити, то стикаєтесь з тим, що вам потрібно вказати параметри доступу. Без них код навіть не запуститься. + +І найголовніше, тепер ви можете погратися з платіжним шлюзом, щоб з вас не знімали $100 щоразу, коли ви запускаєте тест. + +Глобальний стан призводить до того, що ваші об'єкти можуть таємно отримувати доступ до речей, які не оголошені в їхніх API, і, як наслідок, робить ваші API патологічними брехунами. + +Можливо, ви не замислювалися про це раніше, але кожного разу, коли ви використовуєте глобальний стан, ви створюєте таємні бездротові канали зв'язку. Моторошні віддалені дії змушують розробників читати кожен рядок коду, щоб зрозуміти потенційні взаємодії, знижують продуктивність розробників і заплутують нових членів команди. +Якщо ви той, хто створив код, ви знаєте реальні залежності, але будь-хто, хто прийде після вас, нічого не знає. + +Не пишіть код, який використовує глобальний стан, краще передавайте залежності. Тобто, ін'єкція залежностей. + + +Крихкість глобальної держави .[#toc-brittleness-of-the-global-state] +-------------------------------------------------------------------- + +У коді, який використовує глобальний стан та синглетони, ніколи не можна бути впевненим, коли і ким цей стан було змінено. Цей ризик присутній вже на етапі ініціалізації. Наступний код повинен створити з'єднання з базою даних та ініціалізувати платіжний шлюз, але він постійно видає виключення, і пошук причини цього є надзвичайно нудним: + +```php +PaymentGateway::init(); +DB::init('mysql:', 'user', 'password'); +``` + +Ви повинні детально переглянути код, щоб виявити, що об'єкт `PaymentGateway` отримує доступ до інших об'єктів бездротовим способом, деякі з яких вимагають з'єднання з базою даних. Таким чином, ви повинні ініціалізувати базу даних перед `PaymentGateway`. Однак димова завіса глобального стану приховує це від вас. Скільки часу ви б заощадили, якби API кожного класу не брехав і оголошував свої залежності? + +```php +$db = new DB('mysql:', 'user', 'password'); +$gateway = new PaymentGateway($db, ...); +``` + +Схожа проблема виникає при використанні глобального доступу до з'єднання з базою даних: + +```php +use Illuminate\Support\Facades\DB; + +class Article +{ + public function save(): void + { + DB::insert(/* ... */); + } +} +``` + +При виклику методу `save()` немає впевненості, що з'єднання з базою даних вже створено і хто відповідає за його створення. Наприклад, якщо ми захочемо змінити підключення до бази даних на льоту, можливо, з метою тестування, нам, ймовірно, доведеться створити додаткові методи, такі як `DB::reconnect(...)` або `DB::reconnectForTest()`. + +Розглянемо приклад: + +```php +$article = new Article; +// ... +DB::reconnectForTest(); +Foo::doSomething(); +$article->save(); +``` + +Де ми можемо бути впевнені, що при виклику `$article->save()` дійсно використовується тестова база даних? Що, якщо метод `Foo::doSomething()` змінив глобальне з'єднання з базою даних? Щоб з'ясувати це, нам доведеться вивчити вихідний код класу `Foo` і, ймовірно, багатьох інших класів. Однак такий підхід дасть лише короткострокову відповідь, оскільки в майбутньому ситуація може змінитися. + +Що, якщо ми перемістимо підключення до бази даних у статичну змінну всередині класу `Article`? + +```php +class Article +{ + private static DB $db; + + public static function setDb(DB $db): void + { + self::$db = $db; + } + + public function save(): void + { + self::$db->insert(/* ... */); + } +} +``` + +Це нічого не змінить. Проблема в глобальному стані, і не має значення, в якому класі він ховається. У цьому випадку, як і в попередньому, ми не маємо жодного уявлення про те, до якої бази даних відбувається запис, коли викликається метод `$article->save()`. Будь-хто на віддаленому кінці програми може в будь-який момент змінити базу даних за допомогою методу `Article::setDb()`. Під нашими руками. + +Глобальний стан робить наш додаток **надзвичайно вразливим**. + +Однак є простий спосіб вирішити цю проблему. Просто змусьте API декларувати залежності, щоб забезпечити належну функціональність. + +```php +class Article +{ + public function __construct( + private DB $db, + ) { + } + + public function save(): void + { + $this->db->insert(/* ... */); + } +} + +$article = new Article($db); +// ... +Foo::doSomething(); +$article->save(); +``` + +Такий підхід позбавляє вас від необхідності турбуватися про приховані та неочікувані зміни у з'єднаннях з базою даних. Тепер ми точно знаємо, де зберігається стаття, і жодні модифікації коду в іншому, не пов'язаному з нею класі, більше не зможуть змінити ситуацію. Код більше не крихкий, а стабільний. + +Не пишіть код, який використовує глобальний стан, краще передавайте залежності. Таким чином, ін'єкція залежностей. + + +Синглтон .[#toc-singleton] +-------------------------- + +Синглтон - це патерн проектування, який, за [визначенням |https://en.wikipedia.org/wiki/Singleton_pattern] з відомої публікації Gang of Four, обмежує клас єдиним екземпляром і пропонує глобальний доступ до нього. Реалізація цього патерну зазвичай нагадує наступний код: + +```php +class Singleton +{ + private static self $instance; + + public static function getInstance(): self + { + self::$instance ??= new self; + return self::$instance; + } + + // та інші методи, що виконують функції класу +} +``` + +На жаль, синглтон вводить в додаток глобальний стан. А як ми показали вище, глобальний стан небажаний. Саме тому синглтон вважається антипаттерном. + +Не використовуйте синглетони у своєму коді і замініть їх іншими механізмами. Синглетони насправді не потрібні. Однак, якщо вам потрібно гарантувати існування єдиного екземпляру класу для всього додатку, залиште це [контейнеру DI |container]. +Таким чином, створіть синглтон додатку, або сервіс. Тоді клас не буде надавати власної унікальності (тобто не матиме методу `getInstance()` та статичної змінної) і виконуватиме лише свої функції. Таким чином, він перестане порушувати принцип єдиної відповідальності. + + +Глобальний стан проти тестів .[#toc-global-state-versus-tests] +-------------------------------------------------------------- + +При написанні тестів ми припускаємо, що кожен тест є ізольованою одиницею, і жоден зовнішній стан не входить до нього. І жоден стан не виходить з тесту. Коли тест завершується, будь-який стан, пов'язаний з тестом, повинен бути автоматично видалений збирачем сміття. Це робить тести ізольованими. Тому ми можемо запускати тести у будь-якому порядку. + +Однак, якщо присутні глобальні стани/синглетони, всі ці гарні припущення руйнуються. Стан може входити і виходити з тесту. Несподівано, порядок виконання тестів може мати значення. + +Щоб взагалі тестувати синглетони, розробникам часто доводиться послаблювати їх властивості, можливо, дозволяючи замінювати один екземпляр іншим. Такі рішення, в кращому випадку, є хаками, які створюють код, який важко підтримувати і розуміти. Будь-який тест або метод `tearDown()`, який впливає на будь-який глобальний стан, повинен скасувати ці зміни. + +Глобальний стан - це найбільший головний біль у модульному тестуванні! + +Як виправити ситуацію? Дуже просто. Не пишіть код, який використовує синглетони, краще передавайте залежності. Тобто, ін'єкція залежностей. + + +Глобальні константи .[#toc-global-constants] +-------------------------------------------- + +Глобальний стан не обмежується використанням синглетонів і статичних змінних, але також може застосовуватися до глобальних констант. + +З константами, значення яких не надає нам ніякої нової (`M_PI`) або корисної (`PREG_BACKTRACK_LIMIT_ERROR`) інформації, явно все гаразд. +І навпаки, константи, які слугують способом "бездротової" передачі інформації всередині коду, є нічим іншим, як прихованою залежністю. Як `LOG_FILE` у наступному прикладі. +Використання константи `FILE_APPEND` є абсолютно коректним. + +```php +const LOG_FILE = '...'; + +class Foo +{ + public function doSomething() + { + // ... + file_put_contents(LOG_FILE, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +У цьому випадку ми повинні оголосити параметр у конструкторі класу `Foo`, щоб зробити його частиною API: + +```php +class Foo +{ + public function __construct( + private string $logFile, + ) { + } + + public function doSomething() + { + // ... + file_put_contents($this->logFile, $message . "\n", FILE_APPEND); + // ... + } +} +``` + +Тепер ми можемо передавати інформацію про шлях до файлу логування і легко змінювати його за потреби, що полегшує тестування і підтримку коду. + + +Глобальні функції та статичні методи .[#toc-global-functions-and-static-methods] +-------------------------------------------------------------------------------- + +Ми хочемо підкреслити, що використання статичних методів і глобальних функцій саме по собі не є проблематичним. Ми вже пояснювали недоречність використання `DB::insert()` та подібних методів, але завжди йшлося про глобальний стан, що зберігається у статичній змінній. Метод `DB::insert()` вимагає наявності статичної змінної, оскільки він зберігає з'єднання з базою даних. Без цієї змінної реалізація методу була б неможливою. + +Використання детермінованих статичних методів і функцій, таких як `DateTime::createFromFormat()`, `Closure::fromCallable`, `strlen()` і багатьох інших, цілком узгоджується з ін'єкцією залежностей. Ці функції завжди повертають однакові результати від однакових вхідних параметрів і тому є передбачуваними. Вони не використовують жодного глобального стану. + +Однак, в PHP є функції, які не є детермінованими. До них відноситься, наприклад, функція `htmlspecialchars()`. Її третій параметр, `$encoding`, якщо не вказано, за замовчуванням приймає значення параметра конфігурації `ini_get('default_charset')`. Тому рекомендується завжди вказувати цей параметр, щоб уникнути можливої непередбачуваної поведінки функції. Nette послідовно це робить. + +Деякі функції, такі як `strtolower()`, `strtoupper()`, тощо, у недалекому минулому мали недетерміновану поведінку і залежали від параметра `setlocale()`. Це викликало багато ускладнень, найчастіше при роботі з турецькою мовою. +Це пов'язано з тим, що турецька мова розрізняє верхній і нижній регістр `I` з крапкою і без неї. Тому `strtolower('I')` повертав символ `ı`, а `strtoupper('i')` повертав символ `İ`, що призводило до того, що додатки викликали ряд загадкових помилок. +Однак ця проблема була виправлена у версії PHP 8.2, і функції більше не залежать від локалі. + +Це гарний приклад того, як глобальний стан дошкуляв тисячам розробників по всьому світу. Рішенням було замінити його на ін'єкцію залежностей. + + +Коли можна використовувати глобальну державу? .[#toc-when-is-it-possible-to-use-global-state] +--------------------------------------------------------------------------------------------- + +Існують певні специфічні ситуації, коли можна використовувати глобальний стан. Наприклад, під час налагодження коду, коли потрібно вивести значення змінної або виміряти тривалість виконання певної частини програми. У таких випадках, коли йдеться про тимчасові дії, які згодом будуть видалені з коду, правомірно використовувати глобально доступний дампер або секундомір. Ці інструменти не є частиною дизайну коду. + +Інший приклад - функції для роботи з регулярними виразами `preg_*`, які внутрішньо зберігають скомпільовані регулярні вирази в статичному кеші в пам'яті. Коли ви викликаєте один і той самий регулярний вираз кілька разів у різних частинах коду, він компілюється лише один раз. Кеш економить продуктивність, а також є повністю невидимим для користувача, тому таке використання можна вважати легітимним. + + +Підсумок .[#toc-summary] +------------------------ + +Ми показали, чому це має сенс + +1) Видалити всі статичні змінні з коду +2) Оголосити залежності +3) І використовувати ін'єкцію залежностей + +Коли ви обмірковуєте дизайн коду, пам'ятайте, що кожне `static $foo` представляє проблему. Для того, щоб ваш код був середовищем, що поважає DI, важливо повністю викорінити глобальний стан і замінити його на ін'єкцію залежностей. + +Під час цього процесу ви можете виявити, що вам потрібно розділити клас, оскільки він має більше однієї відповідальності. Не турбуйтеся про це; прагніть до принципу єдиної відповідальності. + +*Я хотів би подякувати Мішко Хевері, чиї статті, такі як [Flaw: Brittle Global State та Singletons |http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/], лягли в основу цієї глави. Я хотів би подякувати Мішко Хевері.* diff --git a/dependency-injection/uk/introduction.texy b/dependency-injection/uk/introduction.texy index 4cd289c358..002eb58b89 100644 --- a/dependency-injection/uk/introduction.texy +++ b/dependency-injection/uk/introduction.texy @@ -2,17 +2,17 @@ *********************************** .[perex] -Ця глава знайомить вас з основними практиками програмування, яких слід дотримуватися при написанні будь-якої програми. Це основи, необхідні для написання чистого, зрозумілого та зручного для супроводу коду. +У цьому розділі ви познайомитеся з основними практиками програмування, яких слід дотримуватися при написанні будь-якої програми. Це основи, необхідні для написання чистого, зрозумілого та зручного для супроводу коду. -Якщо ви вивчите і будете дотримуватися цих правил, Nette буде поруч з вами на кожному кроці. Вона виконуватиме рутинні завдання за вас і зробить так, щоб вам було максимально комфортно, щоб ви могли зосередитися на самій логіці. +Якщо ви вивчите і будете дотримуватися цих правил, Nette буде поруч з вами на кожному кроці. Він виконає за вас рутинні завдання і забезпечить максимальний комфорт, щоб ви могли зосередитися на самій логіці. -Принципи, які ми покажемо тут, досить прості. Вам немає про що турбуватися. +Принципи, які ми покажемо тут, досить прості. Вам не потрібно ні про що турбуватися. Пам'ятаєте свою першу програму? .[#toc-remember-your-first-program] ------------------------------------------------------------------- -Ми не знаємо, якою мовою ви її написали, але якщо це був PHP, то вона, ймовірно, виглядала б приблизно так: +Ми не знаємо, якою мовою ви його написали, але якщо це був PHP, то він міг би виглядати приблизно так: ```php function addition(float $a, float $b): float @@ -25,9 +25,9 @@ echo addition(23, 1); // відбитки 24 Кілька тривіальних рядків коду, але в них заховано стільки ключових понять. Що є змінні. Що код розбивається на менші одиниці, які є функціями, наприклад. Що ми передаємо їм вхідні аргументи, а вони повертають результати. Не вистачає лише умов та циклів. -Те, що ми передаємо функції вхідні дані, а вона повертає результат, - це цілком зрозуміла концепція, яка використовується в інших галузях, наприклад, у математиці. +Той факт, що функція отримує вхідні дані і повертає результат, є цілком зрозумілою концепцією, яка також використовується в інших галузях, таких як математика. -Функція має сигнатуру, яка складається з її імені, списку параметрів та їх типів і, нарешті, типу значення, що повертається. Як користувачів, нас цікавить сигнатура; зазвичай нам не потрібно нічого знати про внутрішню реалізацію. +Функція має сигнатуру, яка складається з її імені, списку параметрів та їх типів і, нарешті, типу значення, що повертається. Як користувачів, нас цікавить сигнатура, а про внутрішню реалізацію нам зазвичай не потрібно нічого знати. Тепер уявіть, що сигнатура функції має такий вигляд: @@ -35,21 +35,21 @@ echo addition(23, 1); // відбитки 24 function addition(float $x): float ``` -Додавання з одним параметром? Дивно... А як щодо цього? +Додавання з одним параметром? Це дивно... А як щодо цього? ```php function addition(): float ``` -Це дійсно дивно, чи не так? Як ви думаєте, як використовується ця функція? +Це дійсно дивно, чи не так? Як використовується ця функція? ```php echo addition(); // що він друкує? ``` -Дивлячись на такий код, ми розгублюємося. Його не зрозуміє не тільки новачок, але навіть досвідчений програміст не розбереться в такому коді. +Дивлячись на такий код, ми б розгубилися. Його не зрозуміє не тільки новачок, але навіть досвідчений програміст не розбереться в такому коді. -Вам цікаво, як така функція виглядатиме всередині? Куди б вона виводила доданки? Напевно, вона брала б їх *якимось чином* сама, ось так: +Вам цікаво, як така функція виглядатиме всередині? Звідки б вона брала доданки? Напевно, вона буде отримувати їх сама, можливо, ось так: ```php function addition(): float @@ -66,13 +66,13 @@ function addition(): float Але не сюди! .[#toc-not-this-way] --------------------------------- -Дизайн, який нам щойно показали, є сутністю багатьох негативних рис: +Дизайн, який ми щойно показали, є суттю багатьох негативних рис: - сигнатура функції робила вигляд, що їй не потрібні доданки, що збивало нас з пантелику - ми поняття не маємо, як змусити функцію обчислювати з двома іншими числами -- довелося зазирнути в код, щоб подивитися, звідки він бере доданки -- ми виявили приховані зв'язки -- щоб повністю зрозуміти, нам потрібно дослідити ці прив'язки також +- довелося зазирнути в код, щоб дізнатися, звідки беруться доданки +- ми знайшли приховані залежності +- для повного розуміння потрібно вивчити і ці залежності І чи взагалі функція додавання повинна отримувати вхідні дані? Звичайно, ні. Її обов'язок - лише додавати. @@ -93,20 +93,20 @@ function addition(float $a, float $b): float Найважливішим правилом є: **всі дані, які потрібні функціям або класам, повинні бути передані їм**. -Замість того, щоб вигадувати приховані механізми, які допоможуть їм якось дістатися до них самостійно, просто передайте параметри. Ви заощадите час, необхідний для вигадування прихованого способу, який точно не покращить ваш код. +Замість того, щоб вигадувати для них приховані шляхи доступу до даних, просто передайте параметри. Ви заощадите час, який могли б витратити на винайдення прихованих шляхів, які точно не покращать ваш код. -Якщо ви будете дотримуватися цього правила завжди і всюди, ви на шляху до коду без прихованих прив'язок. До коду, який зрозумілий не тільки автору, але й будь-кому, хто його потім прочитає. Де все зрозуміло з сигнатур функцій і класів і не потрібно шукати приховані секрети в реалізації. +Якщо ви завжди і всюди будете дотримуватися цього правила, ви на шляху до коду без прихованих залежностей. До коду, який зрозумілий не тільки автору, але й будь-кому, хто його прочитає. Де все зрозуміло з сигнатур функцій і класів, і не потрібно шукати приховані секрети в реалізації. -Ця техніка має професійну назву **впровадження залежностей**. А дані називаються **залежностями.** Але це проста передача параметрів, не більше того. +Ця техніка професійно називається **ін'єкція залежностей**. А самі дані називаються **залежностями**. Це звичайна передача параметрів, не більше. .[note] -Будь ласка, не плутайте ін'єкцію залежностей, яка є шаблоном проектування, з "контейнером для ін'єкції залежностей", який є інструментом, це зовсім різні речі. Про контейнери ми поговоримо пізніше. +Будь ласка, не плутайте ін'єкцію залежностей, яка є шаблоном проектування, з "контейнером для ін'єкції залежностей", який є інструментом, тобто чимось діаметрально відмінним. З контейнерами ми розберемося пізніше. Від функцій до класів .[#toc-from-functions-to-classes] ------------------------------------------------------- -А як до цього відносяться класи? Клас є більш складною сутністю, ніж проста функція, але правило №1 застосовується і тут. Просто є [більше способів передачі аргументів |passing-dependencies]. Наприклад, так само, як і у випадку з функцією: +А як класи пов'язані між собою? Клас є більш складною одиницею, ніж проста функція, але правило №1 повністю застосовується і тут. Просто є [більше способів передачі аргументів |passing-dependencies]. Наприклад, так само, як і у випадку з функцією: ```php class Math @@ -121,7 +121,7 @@ $math = new Math; echo $math->addition(23, 1); // 24 ``` -Або за допомогою інших методів, або безпосередньо конструктором: +Або іншими методами, або безпосередньо через конструктор: ```php class Addition @@ -149,9 +149,9 @@ echo $addition->calculate(); // 24 Приклади з реального життя .[#toc-real-life-examples] ----------------------------------------------------- -У реальному світі ви не будете писати класи для додавання чисел. Давайте перейдемо до реальних прикладів. +У реальному світі ви не будете писати уроки для додавання чисел. Перейдемо до практичних прикладів. -Нехай у нас є клас `Article`, що представляє статтю в блозі: +Нехай у нас є клас `Article`, що представляє запис у блозі: ```php class Article @@ -176,9 +176,9 @@ $article->content = 'Every year millions of people in ...'; $article->save(); ``` -Метод `save()` зберігає статтю в таблиці бази даних. Реалізувати це за допомогою [Nette Database |database:] було б дуже просто, якби не одна заковика: звідки `Article` має отримати з'єднання з базою даних, тобто об'єкт класу `Nette\Database\Connection`? +Метод `save()` збереже статтю в таблицю бази даних. Реалізувати його за допомогою [Nette Database |database:] буде простіше простого, якби не одна заковика: звідки `Article` бере з'єднання з базою даних, тобто об'єкт класу `Nette\Database\Connection`? -Здається, у нас є багато варіантів. Він може взяти його звідкись зі статичної змінної. Або успадкувати його від класу, який буде забезпечувати з'єднання з базою даних. Або скористатися [синглетоном |global-state#Singleton]. Або так званими фасадами, які використовуються в Laravel: +Здається, у нас є багато варіантів. Він може взяти його десь зі статичної змінної. Або успадкувати від класу, який забезпечує підключення до бази даних. Або скористатися [синглетоном |global-state#Singleton]. Або використовувати так звані фасади, які використовуються в Laravel: ```php use Illuminate\Support\Facades\DB; @@ -203,13 +203,13 @@ class Article Чи ні? -Згадаймо [правило №1: Нехай передадуть вам |#rule #1: Let It Be Passed to You]: всі залежності, які потрібні класу, повинні бути передані йому. Тому що якщо ми цього не зробимо і порушимо це правило, то станемо на шлях брудного коду, повного прихованих прив'язок, незрозумілості, і в результаті отримаємо додаток, який буде складно підтримувати і розвивати. +Згадаймо [правило №1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: всі залежності, які потрібні класу, повинні бути передані йому. Тому що якщо ми порушуємо це правило, ми стаємо на шлях до брудного коду, повного прихованих залежностей, незрозумілості, і в результаті отримуємо додаток, який буде болісно підтримувати і розвивати. -Користувач класу `Article` поняття не має, де метод `save()` зберігає статтю. У таблиці бази даних? В якій саме, виробничій чи розробницькій? І як це можна змінити? +Користувач класу `Article` поняття не має, де метод `save()` зберігає статтю. У таблиці бази даних? В якій саме, робочій чи тестовій? І як її можна змінити? -Щоб знайти застосування методу `save()`, користувачеві доводиться дивитися, як реалізовано метод `DB::insert()`. Отже, він повинен шукати далі, щоб дізнатися, як цей метод забезпечує з'єднання з базою даних. А приховані зв'язки можуть утворювати досить довгий ланцюжок. +Користувач дивиться на те, як реалізовано метод `save()`, і знаходить використання методу `DB::insert()`. Отже, йому доводиться шукати далі, щоб дізнатися, як цей метод отримує з'єднання з базою даних. А приховані залежності можуть утворювати досить довгий ланцюжок. -Приховані зв'язки, фасади Laravel або статичні змінні ніколи не присутні в чистому, добре спроектованому коді. У чистому і добре спроектованому коді передаються аргументи: +У чистому і добре спроектованому коді ніколи не буває прихованих залежностей, фасадів Laravel або статичних змінних. У чистому і добре спроектованому коді передаються аргументи: ```php class Article @@ -224,7 +224,7 @@ class Article } ``` -Ще більш практичним, як ми побачимо далі, є використання конструктора: +Ще більш практичним підходом, як ми побачимо пізніше, буде використання конструктора: ```php class Article @@ -245,11 +245,11 @@ class Article ``` .[note] -Якщо ви досвідчений програміст, ви можете подумати, що `Article` взагалі не повинен мати методу `save()`, це має бути чистий компонент даних, а про зберігання має дбати окремий репозиторій. Це має сенс. Але це вивело б нас далеко за межі теми, якою є ін'єкція залежностей, і спроби навести прості приклади. +Якщо ви досвідчений програміст, ви можете подумати, що `Article` взагалі не повинен мати методу `save()`; він повинен представляти суто компонент даних, а збереженням повинен займатися окремий репозиторій. У цьому є сенс. Але це вивело б нас далеко за межі теми, якою є ін'єкція залежностей, і спроби навести прості приклади. -Якщо ви збираєтеся написати клас, якому для роботи потрібна база даних, наприклад, не вигадуйте, звідки її взяти, а просто передайте її вам. Можливо, як параметр до конструктора або іншого методу. Оголосіть залежності. Розкрийте їх в API вашого класу. Ви отримаєте зрозумілий і передбачуваний код. +Якщо ви пишете клас, якому для роботи потрібна, наприклад, база даних, не вигадуйте, звідки її взяти, а передайте її. Або як параметр конструктора, або іншого методу. Визнавайте залежності. Допускайте їх в API вашого класу. Ви отримаєте зрозумілий і передбачуваний код. -Як щодо цього класу, який реєструє повідомлення про помилки: +А що з цим класом, який записує повідомлення про помилки: ```php class Logger @@ -266,9 +266,9 @@ class Logger Не дотрималися. -Ключова інформація, директорія лог-файлу, *отримується* класом з константи. +Ключова інформація, тобто директорія з лог-файлом, *отримується* самим класом з константи. -Дивіться приклад використання: +Подивіться на приклад використання: ```php $logger = new Logger; @@ -276,7 +276,7 @@ $logger->log('The temperature is 23 °C'); $logger->log('The temperature is 10 °C'); ``` -Не знаючи реалізації, чи могли б ви відповісти на питання, куди записуються повідомлення? Чи припускаєте ви, що існування константи LOG_DIR є необхідним для її роботи? І чи змогли б ви створити другий екземпляр, який би писав в інше місце? Звичайно, ні. +Не знаючи реалізації, чи могли б ви відповісти на питання, куди записуються повідомлення? Чи здогадалися б ви, що наявність константи `LOG_DIR` необхідна для його функціонування? І чи змогли б ви створити другий екземпляр, який би писав в інше місце? Звісно, що ні. Давайте виправимо клас: @@ -295,7 +295,7 @@ class Logger } ``` -Клас тепер набагато зрозуміліший, легше налаштовується і, отже, більш корисний. +Тепер клас став набагато зрозумілішим, конфігурованішим, а отже і кориснішим. ```php $logger = new Logger('/path/to/log.txt'); @@ -306,13 +306,13 @@ $logger->log('The temperature is 15 °C'); Але мені все одно! .[#toc-but-i-don-t-care] ------------------------------------------- -*"Коли я створюю об'єкт Article і викликаю save(), я не хочу мати справу з базою даних, я просто хочу, щоб він був збережений до тієї, яку я встановив у конфігурації. "* +*"Коли я створюю об'єкт Article і викликаю save(), я не хочу мати справу з базою даних; я просто хочу, щоб він був збережений у тій, яку я встановив у конфігурації."* -*"Коли я використовую Logger, я просто хочу, щоб повідомлення було записано, і не хочу розбиратися з тим, куди. Дозвольте використовувати глобальні налаштування. "* +*"Коли я використовую Logger, я просто хочу, щоб повідомлення було записано, і не хочу розбиратися з тим, куди. Дозвольте використовувати глобальні налаштування."* -Це правильні зауваження. +Це слушні зауваження. -Для прикладу візьмемо клас, який розсилає розсилки і записує в журнал, як це відбувалося: +Як приклад, давайте розглянемо клас, який надсилає розсилки і записує в лог, як це відбувалося: ```php class NewsletterDistributor @@ -332,11 +332,11 @@ class NewsletterDistributor } ``` -Вдосконалений `Logger`, який більше не використовує константу `LOG_DIR`, вимагає в конструкторі шлях до файлу. Як це вирішити? Класу `NewsletterDistributor` все одно, куди писати повідомлення, він просто хоче їх писати. +Покращений `Logger`, який більше не використовує константу `LOG_DIR`, вимагає вказівки шляху до файлу в конструкторі. Як це вирішити? Класу `NewsletterDistributor` все одно, куди писати повідомлення, він просто хоче їх писати. -Рішення знову ж таки полягає в [правилі №1: Нехай передадуть вам |#rule #1: Let It Be Passed to You]: передайте йому всі дані, які потрібні класу. +Рішення знову ж таки полягає в [правилі №1: Let It Be Passed to You |#rule #1: Let It Be Passed to You]: передайте всі дані, які потрібні класу. -Отже, ми передаємо шлях до логу конструктору, який потім використовуємо для створення об'єкта `Logger`? +Чи означає це, що ми передаємо шлях до логу через конструктор, який потім використовуємо при створенні об'єкта `Logger`? ```php class NewsletterDistributor @@ -351,7 +351,7 @@ class NewsletterDistributor $logger = new Logger($this->file); ``` -Ні, не так! Тому що шлях **не** належить до даних, які потрібні класу `NewsletterDistributor`; йому потрібен `Logger`. Класу потрібен сам логгер. І це те, що ми передамо: +Ні, не так! Шлях не належить до даних, які потрібні класу `NewsletterDistributor`; насправді він потрібен класу `Logger`. Відчуваєте різницю? Класу `NewsletterDistributor` потрібен сам логгер. Ось його ми і передамо: ```php class NewsletterDistributor @@ -375,25 +375,25 @@ class NewsletterDistributor } ``` -Тепер з сигнатур класу `NewsletterDistributor` зрозуміло, що ведення логів є частиною його функціоналу. І задача заміни логгера на інший, можливо, з метою тестування, є досить тривіальною. -Більше того, якщо буде змінено конструктор класу `Logger`, то це ніяк не вплине на наш клас. +Тепер з сигнатур класу `NewsletterDistributor` зрозуміло, що ведення логів також є частиною його функціоналу. І завдання замінити логгер на інший, можливо, для тестування, є абсолютно тривіальним. +Більш того, якщо зміниться конструктор класу `Logger`, то це ніяк не вплине на наш клас. -Правило №2: Бери своє .[#toc-rule-2-take-what-is-yours] -------------------------------------------------------- +Правило №2: Бери своє .[#toc-rule-2-take-what-s-yours] +------------------------------------------------------ -Не вводьте себе в оману і не дозволяйте передавати вам параметри ваших залежностей. Передавайте залежності безпосередньо. +Не вводьте себе в оману і не дозволяйте собі проходити повз залежності ваших залежностей. Пройдіть повз власні залежності. -Це зробить код, що використовує інші об'єкти, повністю незалежним від змін у їхніх конструкторах. Його API буде більш коректним. І найголовніше, буде тривіально поміняти ці залежності на інші. +Завдяки цьому код, що використовує інші об'єкти, буде повністю незалежним від змін в їхніх конструкторах. Його API буде більш правдивим. А головне, замінити ці залежності на інші буде тривіально просто. -Новий член родини .[#toc-a-new-member-of-the-family] ----------------------------------------------------- +Новий член сім'ї .[#toc-new-family-member] +------------------------------------------ -Команда розробників вирішила створити другий логгер, який записуватиметься до бази даних. Тому ми створюємо клас `DatabaseLogger`. Отже, у нас є два класи, `Logger` і `DatabaseLogger`, один пише у файл, інший - в базу даних... Вам не здається, що в цій назві є щось дивне? -Чи не краще було б перейменувати `Logger` в `FileLogger`? Звичайно, краще. +Команда розробників вирішила створити другий логгер, який буде писати до бази даних. Тому ми створюємо клас `DatabaseLogger`. Отже, у нас є два класи, `Logger` і `DatabaseLogger`, один пише в файл, інший в базу даних ... Вам не здається дивним таке іменування? +Чи не краще було б перейменувати `Logger` в `FileLogger`? Однозначно так. -Але давайте зробимо це розумно. Ми створимо інтерфейс під оригінальною назвою: +Але давайте зробимо це з розумом. Створимо інтерфейс під оригінальною назвою: ```php interface Logger @@ -402,7 +402,7 @@ interface Logger } ``` -...який будуть реалізовувати обидва логери: +... яку реалізують обидва логери: ```php class FileLogger implements Logger @@ -412,17 +412,17 @@ class DatabaseLogger implements Logger // ... ``` -Таким чином, у решті коду, де використовується логгер, нічого не потрібно буде змінювати. Наприклад, конструктор класу `NewsletterDistributor`, як і раніше, буде щасливий отримати в якості параметра `Logger`. І від нас буде залежати, який саме екземпляр ми йому передамо. +І тому не потрібно буде нічого змінювати в решті коду, де використовується логгер. Наприклад, конструктор класу `NewsletterDistributor` як і раніше буде задовольнятися передачею `Logger` в якості параметра. А який саме екземпляр ми передамо, буде залежати тільки від нас. -**Ось чому ми ніколи не додаємо до імен інтерфейсів суфікс `Interface` або префікс `I`.** Інакше неможливо було б так гарно розробляти код. +**Ось чому ми ніколи не додаємо суфікс `Interface` або префікс `I` до імен інтерфейсів.** Інакше не було б можливості так гарно розробляти код. Х'юстон, у нас проблема .[#toc-houston-we-have-a-problem] --------------------------------------------------------- -У той час як у всьому додатку ми можемо задовольнитися одним екземпляром логгера, будь то файл або база даних, і просто передавати його скрізь, де щось реєструється, у випадку з класом `Article` все зовсім інакше. Фактично, ми створюємо його екземпляри за потребою, можливо, декілька разів. Як працювати з прив'язкою до бази даних у його конструкторі? +У той час як ми можемо обійтися одним екземпляром логгера, файловим або базованим на базі даних, для всього додатку і просто передавати його туди, де щось реєструється, з класом `Article` все зовсім інакше. Ми створюємо його екземпляри за потребою, навіть декілька разів. Як працювати з залежністю від бази даних в його конструкторі? -Як приклад, ми можемо використати контролер, який повинен зберігати статтю в базі даних після відправлення форми: +Прикладом може бути контролер, який повинен зберігати статтю в базі даних після відправлення форми: ```php class EditController extends Controller @@ -437,9 +437,9 @@ class EditController extends Controller } ``` -Прямо пропонується можливе рішення: передати об'єкт бази даних у конструкторі за адресою `EditController` і використовувати `$article = new Article($this->db)`. +Можливе рішення очевидне: передайте об'єкт бази даних в конструктор `EditController` і використовуйте `$article = new Article($this->db)`. -Як і у попередньому випадку з `Logger` та шляхом до файлу, це не є правильним підходом. База даних є залежністю не від `EditController`, а від `Article`. Тому передача бази даних суперечить [правилу №2: Бери своє |#rule #2: take what is yours]. Коли змінюється конструктор класу `Article` (додається новий параметр), код у всіх місцях, де створюються екземпляри, також потрібно буде модифікувати. Уффф. +Як і в попередньому випадку з `Logger` і шляхом до файлу, це неправильний підхід. База даних є залежністю не від `EditController`, а від `Article`. Передача бази даних суперечить [правилу №2: |#rule #2: take what's yours] бери те, що належить тобі. Якщо змінюється конструктор класу `Article` (додається новий параметр), вам потрібно буде модифікувати код скрізь, де створюються екземпляри. Уффф. Х'юстон, що ти пропонуєш? @@ -447,11 +447,11 @@ class EditController extends Controller Правило №3: Нехай завод розбирається з цим .[#toc-rule-3-let-the-factory-handle-it] ----------------------------------------------------------------------------------- -Видаляючи приховані прив'язки і передаючи всі залежності в якості аргументів, ми отримуємо більш гнучкі та конфігуровані класи. А отже, нам потрібно щось інше для створення та налаштування цих більш гнучких класів. Назвемо це фабриками. +Усунувши приховані залежності та передавши всі залежності як аргументи, ми отримали більш гнучкі та конфігуровані класи. А отже, нам потрібно щось інше, щоб створювати та налаштовувати ці більш гнучкі класи для нас. Назвемо це фабриками. Емпіричне правило: якщо клас має залежності, залиште створення їх екземплярів фабриці. -Фабрики є розумнішою заміною оператору `new` у світі ін'єкції залежностей. +Фабрики - розумніша заміна оператору `new` у світі ін'єкцій залежності. .[note] Будь ласка, не плутайте з паттерном проектування *factory method*, який описує специфічний спосіб використання фабрик і не має відношення до цієї теми. @@ -460,7 +460,7 @@ class EditController extends Controller Фабрика .[#toc-factory] ----------------------- -Фабрика - це метод або клас, який створює та налаштовує об'єкти. Ми називаємо `Article` виробляючий клас `ArticleFactory` і це може виглядати так: +Фабрика - це метод або клас, який створює та налаштовує об'єкти. Назвемо клас, що створює `Article`, `ArticleFactory`, і він може виглядати так: ```php class ArticleFactory @@ -477,7 +477,7 @@ class ArticleFactory } ``` -Його використання у контролері буде наступним: +Його використання в контролері буде наступним: ```php class EditController extends Controller @@ -498,11 +498,11 @@ class EditController extends Controller } ``` -У цей момент, коли сигнатура конструктора класу `Article` змінюється, єдиною частиною коду, яка повинна відреагувати, є сама фабрика `ArticleFactory`. Будь-який інший код, що працює з об'єктами `Article`, наприклад, `EditController`, не буде зачеплений. +На даний момент, якщо сигнатура конструктора класу `Article` змінюється, єдиною частиною коду, яка повинна відреагувати на це, є сам `ArticleFactory`. Весь інший код, що працює з об'єктами `Article`, наприклад, `EditController`, не постраждає. -Можливо, ви зараз постукуєте себе по лобі, задаючись питанням, чи допомогли ми собі взагалі. Кількість коду зросла, і все це починає виглядати підозріло складним. +Вам може бути цікаво, чи дійсно ми зробили все краще. Обсяг коду збільшився, і все це починає виглядати підозріло складним. -Не хвилюйтеся, скоро ми дійдемо до контейнера Nette DI. І він має кілька козирів у рукаві, які зроблять створення додатків з використанням ін'єкції залежностей надзвичайно простим. Наприклад, замість класу `ArticleFactory` буде достатньо [написати простий інтерфейс |factory]: +Не хвилюйтеся, скоро ми дійдемо до контейнера Nette DI. А він має кілька козирів у рукаві, які значно спростять створення додатків з використанням ін'єкції залежностей. Наприклад, замість класу `ArticleFactory` вам потрібно буде [написати |factory] лише [простий інтерфейс |factory]: ```php interface ArticleFactory @@ -511,18 +511,18 @@ interface ArticleFactory } ``` -Але ми забігаємо наперед, зачекайте :-) +Але ми забігаємо наперед, будь ласка, наберіться терпіння :-) Резюме .[#toc-summary] ---------------------- -На початку цієї глави ми обіцяли показати вам спосіб проектування чистого коду. Просто дайте класам +На початку цієї глави ми обіцяли показати вам процес проектування чистого коду. Все, що для цього потрібно, це щоб класи: -- [залежності, які їм потрібні |#Rule #1: Let It Be Passed to You] -- [а не ті, які їм безпосередньо |#Rule #2: Take What Is Yours]не потрібні +- [передавати залежності, які їм потрібні |#Rule #1: Let It Be Passed to You] +- [і навпаки, не передавати те, що їм безпосередньо не потрібно |#Rule #2: Take What's Yours] - [і що об'єкти з залежностями краще створювати на фабриках |#Rule #3: Let the Factory Handle it] -На перший погляд може здатися, що це не так, але ці три правила мають далекосяжні наслідки. Вони призводять до радикально іншого погляду на дизайн коду. Чи варто воно того? Програмісти, які відкинули старі звички і почали послідовно використовувати ін'єкцію залежностей, вважають це поворотним моментом у своєму професійному житті. Це відкрило їм світ зрозумілих і стійких додатків. +На перший погляд може здатися, що ці три правила не мають далекосяжних наслідків, але вони призводять до радикально іншого погляду на дизайн коду. Чи варто воно того? Розробники, які відмовилися від старих звичок і почали послідовно використовувати ін'єкцію залежностей, вважають цей крок переломним моментом у своєму професійному житті. Він відкрив для них світ зрозумілих і зручних для супроводу додатків. -Але що, якщо код не використовує ін'єкцію залежностей послідовно? Що, якщо він побудований на статичних методах або синглетонах? Чи виникнуть з цим проблеми? Так, [і дуже суттє |global-state]ві. +Але що, якщо код не використовує ін'єкцію залежностей послідовно? Що, якщо він покладається на статичні методи або синглетони? Чи виникають через це проблеми? [Так, створює, і дуже |global-state] серйозні. diff --git a/dependency-injection/uk/passing-dependencies.texy b/dependency-injection/uk/passing-dependencies.texy index 7a8b88b18f..5badf00eb0 100644 --- a/dependency-injection/uk/passing-dependencies.texy +++ b/dependency-injection/uk/passing-dependencies.texy @@ -12,7 +12,7 @@ </div> -Перші три методи застосовні взагалі у всіх об'єктно-орієнтованих мовах, четвертий специфічний для презентаторів Nette, тому його обговорюють в [окремому розділі |best-practices:inject-method-attribute]. Зараз ми докладніше розглянемо кожен із цих варіантів і покажемо їх на конкретних прикладах. +Зараз ми проілюструємо різні варіанти на конкретних прикладах. Впровадження через конструктор .[#toc-constructor-injection] @@ -21,17 +21,17 @@ Залежності передаються як аргументи конструктору під час створення об'єкта: ```php -class MyService +class MyClass { private Cache $cache; - public function __construct(Cache $service) + public function __construct(Cache $cache) { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService($cache); +$obj = new MyClass($cache); ``` Ця форма корисна для обов'язкових залежностей, які абсолютно необхідні класу для функціонування, оскільки без них екземпляр не може бути створений. @@ -40,10 +40,10 @@ $service = new MyService($cache); ```php // PHP 8.0 -class MyService +class MyClass { public function __construct( - private Cache $service, + private Cache $cache, ) { } } @@ -53,10 +53,10 @@ class MyService ```php // PHP 8.1 -class MyService +class MyClass { public function __construct( - private readonly Cache $service, + private readonly Cache $cache, ) { } } @@ -65,24 +65,84 @@ class MyService DI контейнер передає залежності в конструктор автоматично, використовуючи [autowiring |autowiring]. Аргументи, які не можна передавати таким чином (наприклад, рядки, числа, булеви) [записати в конфігурації |services#Arguments]. +Пекло конструктора .[#toc-constructor-hell] +------------------------------------------- + +Термін *пекло конструктора* стосується ситуації, коли дочірній клас успадковує від батьківського класу, конструктор якого потребує залежностей, і дочірній клас також потребує залежностей. Він також повинен перейняти і передати залежності батьківського класу: + +```php +abstract class BaseClass +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass extends BaseClass +{ + private Database $db; + + // ⛔ CONSTRUCTOR HELL + public function __construct(Cache $cache, Database $db) + { + parent::__construct($cache); + $this->db = $db; + } +} +``` + +Проблема виникає, коли ми хочемо змінити конструктор класу `BaseClass`, наприклад, коли додається нова залежність. Тоді нам доведеться модифікувати всі конструктори дочірніх класів. Що перетворює таку модифікацію на справжнє пекло. + +Як цього уникнути? Рішення полягає в тому, щоб **приоритизувати композицію над успадкуванням**. + +Отже, давайте проектувати код по-іншому. Уникаймо абстрактних класів `Base*`. Замість того, щоб `MyClass` отримував певну функціональність шляхом успадкування від `BaseClass`, він отримає цю функціональність як залежність: + +```php +final class SomeFunctionality +{ + private Cache $cache; + + public function __construct(Cache $cache) + { + $this->cache = $cache; + } +} + +final class MyClass +{ + private SomeFunctionality $sf; + private Database $db; + + public function __construct(SomeFunctionality $sf, Database $db) // ✅ + { + $this->sf = $sf; + $this->db = $db; + } +} +``` + + Впровадження через сеттери .[#toc-setter-injection] =================================================== -Залежності передаються шляхом виклику методу, який зберігає їх у приватній властивості. Звичайна угода про іменування цих методів має вигляд `set*()`, тому вони називаються сетерами. +Залежності передаються шляхом виклику методу, який зберігає їх у приватній властивості. Звичайна конвенція іменування цих методів має вигляд `set*()`, тому їх називають сеттерами, але, звісно, їх можна називати як завгодно. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { - $this->cache = $service; + $this->cache = $cache; } } -$service = new MyService; -$service->setCache($cache); +$obj = new MyClass; +$obj->setCache($cache); ``` Цей метод корисний для необов'язкових залежностей, які не потрібні для функціонування класу, оскільки не гарантується, що об'єкт дійсно отримає їх (тобто що користувач викличе метод). @@ -90,16 +150,16 @@ $service->setCache($cache); Водночас, цей метод дозволяє неодноразово викликати сеттер для зміни залежності. Якщо це небажано, додайте перевірку в метод, або, починаючи з PHP 8.1, позначте властивість `$cache` прапором `readonly`. ```php -class MyService +class MyClass { private Cache $cache; - public function setCache(Cache $service): void + public function setCache(Cache $cache): void { if ($this->cache) { throw new RuntimeException('The dependency has already been set'); } - $this->cache = $service; + $this->cache = $cache; } } ``` @@ -109,7 +169,7 @@ class MyService ```neon services: - - create: MyService + create: MyClass setup: - setCache ``` @@ -121,13 +181,13 @@ services: Залежності передаються безпосередньо у властивість: ```php -class MyService +class MyClass { public Cache $cache; } -$service = new MyService; -$service->cache = $cache; +$obj = new MyClass; +$obj->cache = $cache; ``` Цей метод вважається неприйнятним, оскільки властивість має бути оголошена як `public`. Отже, ми не можемо контролювати, чи буде передана залежність справді мати вказаний тип (це було вірно до версії PHP 7.4), і ми втрачаємо можливість реагувати на нову призначену залежність своїм власним кодом, наприклад, щоб запобігти подальшим змінам. Водночас, властивість стає частиною публічного інтерфейсу класу, що може бути небажано. @@ -137,12 +197,18 @@ $service->cache = $cache; ```neon services: - - create: MyService + create: MyClass setup: - $cache = @\Cache ``` +Ін'єкція .[#toc-inject] +======================= + +У той час як попередні три методи загалом працюють у всіх об'єктно-орієнтованих мовах, ін'єкція за методом, анотацією або атрибутом *inject* є специфічною для презентаторів Nette. Вони обговорюються в [окремій главі |best-practices:inject-method-attribute]. + + Який шлях обрати? .[#toc-which-way-to-choose] ============================================= diff --git a/dependency-injection/uk/services.texy b/dependency-injection/uk/services.texy index 6672337d55..10bb015505 100644 --- a/dependency-injection/uk/services.texy +++ b/dependency-injection/uk/services.texy @@ -389,7 +389,7 @@ $names = $container->findByTag('logger'); Режим впровадження .[#toc-inject-mode] ====================================== -Прапор `inject: true` використовується для активації передавання залежностей через публічні змінні за допомогою анотації [inject |best-practices:inject-method-attribute#Inject Annotations] і методів [inject*() |best-practices:inject-method-attribute#inject Methods]. +Прапор `inject: true` використовується для активації передавання залежностей через публічні змінні за допомогою анотації [inject |best-practices:inject-method-attribute#Inject Attributes] і методів [inject*() |best-practices:inject-method-attribute#inject Methods]. ```neon services: diff --git a/forms/bg/in-presenter.texy b/forms/bg/in-presenter.texy index 8eaf24ef10..ae2285540f 100644 --- a/forms/bg/in-presenter.texy +++ b/forms/bg/in-presenter.texy @@ -30,11 +30,11 @@ $form->onSuccess[] = [$this, 'formSucceeded']; От гледна точка на водещия формулярът е общ компонент. Затова той се третира като компонент и се включва в презентатора чрез [метода factory |application:components#Factory-Methods]. Това ще изглежда по следния начин: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name съдържа името // $data->password съдържа парола $this->flashMessage('Регистрирахте се успешно.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` А визуализирането в шаблона се извършва с помощта на тага `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Регистрация</h1> {control registrationForm} diff --git a/forms/bg/rendering.texy b/forms/bg/rendering.texy index 16c448a9a1..a9347307a8 100644 --- a/forms/bg/rendering.texy +++ b/forms/bg/rendering.texy @@ -1,13 +1,13 @@ Изобразяване на форми ********************* -Външният вид на формите може да варира значително. Всъщност има две крайности. Първият е, че се налага да създадете отново набор от много сходни формуляри с малко или никакви усилия. Обикновено това са формуляри в задната част на системата. +Външният вид на формите може да бъде много разнообразен. На практика можем да се сблъскаме с две крайности. От една страна, има нужда от визуализиране на поредица от формуляри в дадено приложение, които визуално си приличат един на друг, и оценяваме лесното визуализиране без шаблон с помощта на `$form->render()`. Такъв обикновено е случаят с административните интерфейси. -На другия полюс са малки, сладки форми, всяка от които е произведение на изкуството. Тяхното оформление е най-добре да бъде написано в HTML. Разбира се, отвъд тези крайности има много междинни форми. +От друга страна, има различни формуляри, в които всеки един е уникален. Техният външен вид се описва най-добре с помощта на езика HTML в шаблона. И разбира се, освен двете споменати крайности, ще срещнем много форми, които попадат някъде по средата. -Latte .[#toc-latte] -=================== +Рендъринг с Latte .[#toc-rendering-with-latte] +============================================== [Системата за шаблони Latte |latte:] улеснява значително изчертаването на форми и техните елементи. Първо ще ви покажем как да визуализирате формулярите ръчно, елемент по елемент, за да получите пълен контрол върху кода. По-късно ще ви покажем как да [автоматизирате |#Automatic-Rendering] такова визуализиране. @@ -152,6 +152,12 @@ protected function createComponentSignInForm(): Form ``` +`{form}` +-------- + +Етикети `{form signInForm}...{/form}` са алтернатива на `<form n:name="signInForm">...</form>`. + + Автоматично визуализиране .[#toc-automatic-rendering] ----------------------------------------------------- @@ -256,8 +262,8 @@ protected function createComponentSignInForm(): Form ``` -Без Latte .[#toc-without-latte] -=============================== +Изобразяване без Latte .[#toc-rendering-without-latte] +====================================================== Най-лесният начин за показване на формата е да извикате @@ -321,13 +327,13 @@ Renderer .[#toc-renderer] <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ $form->render(); <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/bg/validation.texy b/forms/bg/validation.texy index 28901e139a..742c787c3c 100644 --- a/forms/bg/validation.texy +++ b/forms/bg/validation.texy @@ -234,7 +234,7 @@ public function validateSignInForm(Form $form, \stdClass $data): void try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ $form->addText('zip', 'postcode:') Или инсталирайте чрез [npm |https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/cs/configuration.texy b/forms/cs/configuration.texy index ffbd314602..313f3faa12 100644 --- a/forms/cs/configuration.texy +++ b/forms/cs/configuration.texy @@ -52,7 +52,7 @@ forms: MaxFileSize: 'Velikost nahraného souboru může být nejvýše %d bytů.' MaxPostSize: 'Nahraná data překračují limit %d bytů.' MimeType: 'Nahraný soubor není v očekávaném formátu.' - Image: 'Nahraný soubor musí být obraz ve formátu JPEG, GIF, PNG nebo WebP.' + Image: 'Nahraný soubor musí být obraz ve formátu JPEG, GIF, PNG, WebP nebo AVIF.' Nette\Forms\Controls\SelectBox::Valid: 'Vyberte prosím platnou možnost.' Nette\Forms\Controls\UploadControl::Valid: 'Při nahrávání souboru došlo k chybě.' Nette\Forms\Controls\CsrfProtection::Protection: 'Vaše relace vypršela. Vraťte se na domovskou stránku a zkuste to znovu.' diff --git a/forms/cs/controls.texy b/forms/cs/controls.texy index 2ac4b81719..ff382a7ed6 100644 --- a/forms/cs/controls.texy +++ b/forms/cs/controls.texy @@ -218,7 +218,7 @@ Přidá políčko pro upload souboru (třída [UploadControl |api:Nette\Forms\Co ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar musí být JPEG, PNG, GIF or WebP.') + ->addRule($form::Image, 'Avatar musí být JPEG, PNG, GIF, WebP or AVIF.') ->addRule($form::MaxFileSize, 'Maximální velikost je 1 MB.', 1024 * 1024); ``` diff --git a/forms/cs/in-presenter.texy b/forms/cs/in-presenter.texy index e4934e14b5..f3174f1c0a 100644 --- a/forms/cs/in-presenter.texy +++ b/forms/cs/in-presenter.texy @@ -30,11 +30,11 @@ Formulář v presenteru je objekt třídy `Nette\Application\UI\Form`, její př Z pohledu presenteru je formulář běžná komponenta. Proto se s ním jako s komponentou zachází a začleníme ji do presenteru pomocí [tovární metody |application:components#Tovární metody]. Bude to vypadat takto: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name obsahuje jméno // $data->password obsahuje heslo $this->flashMessage('Byl jste úspěšně registrován.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` A v šabloně formulář vykreslíme značkou `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registrace</h1> {control registrationForm} diff --git a/forms/cs/rendering.texy b/forms/cs/rendering.texy index 0d0ad1a046..ec272f49fb 100644 --- a/forms/cs/rendering.texy +++ b/forms/cs/rendering.texy @@ -1,13 +1,13 @@ Vykreslování formulářů ********************** -Vzhled formulářů může být velmi různorodý. V praxi můžeme narazit na dva extrémy. Na jedné straně stojí potřeba v aplikaci vykreslovat řadu formulářů, které jsou si vizuálně podobné jako vejce vejci, a oceníme snadné vykreslení pomocí `$form->render()`. Jde obvykle o případ administračních rozhraní. +Vzhled formulářů může být velmi různorodý. V praxi můžeme narazit na dva extrémy. Na jedné straně stojí potřeba v aplikaci vykreslovat řadu formulářů, které jsou si vizuálně podobné jako vejce vejci, a oceníme snadné vykreslení bez šablony pomocí `$form->render()`. Jde obvykle o případ administračních rozhraní. -Na druhé straně tu jsou rozmanité formuláře, kde platí: co kus, to originál. Jejich podobu nejlépe popíšeme jazykem HTML. A samozřejmě kromě obou zmíněných extrémů narazíme na spoustu formulářů, které se pohybují někde mezi. +Na druhé straně tu jsou rozmanité formuláře, kde platí: co kus, to originál. Jejich podobu nejlépe popíšeme jazykem HTML v šabloně formuláře. A samozřejmě kromě obou zmíněných extrémů narazíme na spoustu formulářů, které se pohybují někde mezi. -Latte -===== +Vykreslení pomocí Latte +======================= [Šablonovací sytém Latte|latte:] zásadně usnadňuje vykreslení formulářů a jejich prvků. Nejprve si ukážeme, jak formuláře vykreslovat ručně po jednotlivých prvcích a tím získat plnou kontrolu nad kódem. Později si ukážeme, jak lze takové vykreslování [zautomatizovat |#Automatické vykreslování]. @@ -152,6 +152,12 @@ Přítomnost chyby můžeme zjistit metodou `hasErrors()` a podle toho nastavit ``` +`{form}` +-------- + +Značky `{form signInForm}...{/form}` jsou alternativou k `<form n:name="signInForm">...</form>`. + + Automatické vykreslování ------------------------ @@ -256,8 +262,8 @@ S vykreslením prvků uvnitř formulářového kontejneru pomůže tag `{formCon ``` -Bez Latte -========= +Vykreslení bez Latte +==================== Nejjednodušší způsob, jak vykreslit formulář, je zavolat: @@ -321,13 +327,13 @@ Pokud nenastavíme vlastní renderer, bude použit výchozí vykreslovač [api:N <tr class="required"> <th><label class="required" for="frm-name">Jméno:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Věk:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ Výsledkem je tento HTML kód: <dl> <dt><label class="required" for="frm-name">Jméno:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Věk:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Pohlaví:</label></dt> diff --git a/forms/cs/validation.texy b/forms/cs/validation.texy index 819d6445ac..39017bbaf4 100644 --- a/forms/cs/validation.texy +++ b/forms/cs/validation.texy @@ -61,7 +61,7 @@ U prvků `addUpload()`, `addMultiUpload()` lze použít i následující pravidl | `MaxFileSize` | maximální velikost souboru | `int` | `MimeType` | MIME type, povoleny zástupné znaky (`'video/*'`) | `string\|string[]` -| `Image` | obrázek JPEG, PNG, GIF, WebP | - +| `Image` | obrázek JPEG, PNG, GIF, WebP, AVIF | - | `Pattern` | jméno souboru vyhovuje regulárnímu výrazu | `string` | `PatternInsensitive` | jako `Pattern`, ale nezávislé na velikosti písmen | `string` @@ -234,7 +234,7 @@ V mnoha případech se o chybě dozvíme až ve chvíli, kdy zpracováváme plat try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ Nebo zkopírovat lokálně do veřejné složky projektu (např. z `vendor/nette Nebo nainstalovat přes [npm|https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/de/in-presenter.texy b/forms/de/in-presenter.texy index 4373a2fdb6..41f8158caf 100644 --- a/forms/de/in-presenter.texy +++ b/forms/de/in-presenter.texy @@ -30,11 +30,11 @@ Das Formular im Presenter ist ein Objekt der Klasse `Nette\Application\UI\Form`, Aus der Sicht des Präsentators ist das Formular eine gemeinsame Komponente. Daher wird es als Komponente behandelt und mit der [Factory-Methode |application:components#Factory Methods] in den Presenter eingebunden. Das sieht dann wie folgt aus: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name enthält Name // $data->password enthält das Passwort $this->flashMessage('Sie haben sich erfolgreich angemeldet.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` Und das Rendern in der Vorlage erfolgt mit dem Tag `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registration</h1> {control registrationForm} diff --git a/forms/de/rendering.texy b/forms/de/rendering.texy index 1147cb5146..ca24a4ae2f 100644 --- a/forms/de/rendering.texy +++ b/forms/de/rendering.texy @@ -1,13 +1,13 @@ Formulare Rendering ******************* -Das Erscheinungsbild von Formularen kann sehr unterschiedlich sein. In der Tat gibt es zwei Extreme. Die eine Seite ist die Notwendigkeit, eine Reihe sehr ähnlicher Formulare mit wenig bis gar keinem Aufwand immer wieder neu zu rendern. In der Regel sind das Verwaltungen und Back-Ends. +Das Erscheinungsbild von Formularen kann sehr unterschiedlich sein. In der Praxis können wir auf zwei Extreme stoßen. Einerseits besteht die Notwendigkeit, eine Reihe von Formularen in einer Anwendung darzustellen, die sich optisch ähneln, und wir schätzen die einfache Darstellung ohne Vorlage mit `$form->render()`. Dies ist normalerweise bei Verwaltungsschnittstellen der Fall. -Die andere Seite sind kleine, süße Formulare, von denen jedes einzelne ein Kunstwerk ist. Ihr Layout kann am besten in HTML geschrieben werden. Natürlich gibt es neben diesen Extremen auch viele Formulare, die genau dazwischen liegen. +Andererseits gibt es verschiedene Formulare, von denen jedes einzelne einzigartig ist. Ihr Aussehen wird am besten durch die HTML-Sprache in der Vorlage beschrieben. Und natürlich werden wir neben den beiden genannten Extremen auch viele Formulare finden, die irgendwo dazwischen liegen. -Latte .[#toc-latte] -=================== +Rendering mit Latte .[#toc-rendering-with-latte] +================================================ Das [Latte-Templating-System |latte:] erleichtert das Rendern von Formularen und ihren Elementen grundlegend. Wir werden zunächst zeigen, wie man Formulare manuell, Element für Element, rendern kann, um die volle Kontrolle über den Code zu erhalten. Später werden wir zeigen, wie man dieses Rendering [automatisieren |#Automatic rendering] kann. @@ -152,6 +152,12 @@ Wir können das Vorhandensein eines Fehlers mit der Methode `hasErrors()` erkenn ``` +`{form}` +-------- + +Tags `{form signInForm}...{/form}` sind eine Alternative zu `<form n:name="signInForm">...</form>`. + + Automatisches Rendering .[#toc-automatic-rendering] --------------------------------------------------- @@ -256,8 +262,8 @@ Das Tag `formContainer` hilft beim Rendern von Eingaben innerhalb eines Formular ``` -Ohne Latte .[#toc-without-latte] -================================ +Rendering ohne Latte .[#toc-rendering-without-latte] +==================================================== Der einfachste Weg, ein Formular zu rendern, ist der Aufruf: @@ -321,13 +327,13 @@ Wenn wir keinen benutzerdefinierten Renderer festlegen, wird der Standard-Render <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ Das Ergebnis ist der folgende Ausschnitt: <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/de/validation.texy b/forms/de/validation.texy index 8335b37961..29dd24e760 100644 --- a/forms/de/validation.texy +++ b/forms/de/validation.texy @@ -234,7 +234,7 @@ In vielen Fällen entdecken wir einen Fehler, wenn wir ein gültiges Formular ve try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ Oder kopieren Sie es lokal in den öffentlichen Ordner des Projekts (z. B. von ` Oder über [npm |https://www.npmjs.com/package/nette-forms] installieren: -```bash +```shell npm install nette-forms ``` diff --git a/forms/el/in-presenter.texy b/forms/el/in-presenter.texy index 1e2a7a8105..9221cee979 100644 --- a/forms/el/in-presenter.texy +++ b/forms/el/in-presenter.texy @@ -30,11 +30,11 @@ $form->onSuccess[] = [$this, 'formSucceeded']; Από τη σκοπιά του παρουσιαστή, η φόρμα είναι ένα κοινό συστατικό. Ως εκ τούτου, αντιμετωπίζεται ως συστατικό και ενσωματώνεται στον παρουσιαστή χρησιμοποιώντας τη [μέθοδο factory |application:components#Factory Methods]. Αυτό θα έχει την εξής μορφή: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name περιέχει όνομα // $data->password περιέχει κωδικό πρόσβασης $this->flashMessage('You have successfully signed up.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` Και η απόδοση στο πρότυπο γίνεται με τη χρήση της ετικέτας `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registration</h1> {control registrationForm} diff --git a/forms/el/rendering.texy b/forms/el/rendering.texy index 82f4cc74e8..d56be8d3f5 100644 --- a/forms/el/rendering.texy +++ b/forms/el/rendering.texy @@ -1,13 +1,13 @@ Μορφές απόδοσης *************** -Η εμφάνιση των φορμών μπορεί να διαφέρει σημαντικά. Στην πραγματικότητα, υπάρχουν δύο ακραίες καταστάσεις. Η μία πλευρά είναι η ανάγκη να αποδώσετε ένα σύνολο πολύ παρόμοιων φορμών από την αρχή, με ελάχιστη έως καθόλου προσπάθεια. Συνήθως πρόκειται για διαχειρίσεις και back-ends. +Η εμφάνιση των μορφών μπορεί να είναι πολύ διαφορετική. Στην πράξη, μπορούμε να συναντήσουμε δύο άκρα. Από τη μία πλευρά, υπάρχει η ανάγκη απόδοσης μιας σειράς φορμών σε μια εφαρμογή που είναι οπτικά παρόμοιες μεταξύ τους και εκτιμούμε την εύκολη απόδοση χωρίς πρότυπο με τη χρήση του `$form->render()`. Αυτή είναι συνήθως η περίπτωση των διοικητικών διεπαφών. -Η άλλη πλευρά είναι οι μικροσκοπικές γλυκές φόρμες, κάθε μία από τις οποίες είναι ένα κομμάτι τέχνης. Η διάταξή τους μπορεί να γραφτεί καλύτερα σε HTML. Φυσικά, εκτός από αυτά τα άκρα, υπάρχουν πολλές φόρμες ακριβώς στο ενδιάμεσο. +Από την άλλη πλευρά, υπάρχουν διάφορες φόρμες όπου η κάθε μία είναι μοναδική. Η εμφάνισή τους περιγράφεται καλύτερα με τη χρήση της γλώσσας HTML στο πρότυπο. Και φυσικά, εκτός από τα δύο προαναφερθέντα άκρα, θα συναντήσουμε πολλές φόρμες που βρίσκονται κάπου ενδιάμεσα. -Latte .[#toc-latte] -=================== +Απεικόνιση με Latte .[#toc-rendering-with-latte] +================================================ Το [σύστημα προτύπων Latte |latte:] διευκολύνει ουσιαστικά την απόδοση των μορφών και των στοιχείων τους. Αρχικά, θα δείξουμε πώς να αποδίδουμε τις φόρμες χειροκίνητα, στοιχείο προς στοιχείο, για να αποκτήσουμε πλήρη έλεγχο του κώδικα. Αργότερα θα δείξουμε πώς να [αυτοματοποιήσουμε |#Automatic rendering] την εν λόγω απόδοση. @@ -152,6 +152,12 @@ protected function createComponentSignInForm(): Form ``` +`{form}` +-------- + +Ετικέτες `{form signInForm}...{/form}` είναι μια εναλλακτική λύση για `<form n:name="signInForm">...</form>`. + + Αυτόματη απόδοση .[#toc-automatic-rendering] -------------------------------------------- @@ -256,8 +262,8 @@ protected function createComponentSignInForm(): Form ``` -Χωρίς Latte .[#toc-without-latte] -================================= +Εκτέλεση χωρίς Latte .[#toc-rendering-without-latte] +==================================================== Ο ευκολότερος τρόπος για να αποδώσετε μια φόρμα είναι να καλέσετε: @@ -321,13 +327,13 @@ Renderer .[#toc-renderer] <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ $form->render(); <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/el/validation.texy b/forms/el/validation.texy index 297dd536ef..0cee3fe0f0 100644 --- a/forms/el/validation.texy +++ b/forms/el/validation.texy @@ -234,7 +234,7 @@ public function validateSignInForm(Form $form, \stdClass $data): void try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ $form->addText('zip', 'Postcode:') Ή εγκαταστήστε μέσω [του npm |https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/en/configuration.texy b/forms/en/configuration.texy index 735dea55c0..f3a6ceb996 100644 --- a/forms/en/configuration.texy +++ b/forms/en/configuration.texy @@ -24,7 +24,7 @@ forms: MaxFileSize: 'The size of the uploaded file can be up to %d bytes.' MaxPostSize: 'The uploaded data exceeds the limit of %d bytes.' MimeType: 'The uploaded file is not in the expected format.' - Image: 'The uploaded file must be image in format JPEG, GIF, PNG or WebP.' + Image: 'The uploaded file must be image in format JPEG, GIF, PNG, WebP or AVIF.' Nette\Forms\Controls\SelectBox::Valid: 'Please select a valid option.' Nette\Forms\Controls\UploadControl::Valid: 'An error occurred during file upload.' Nette\Forms\Controls\CsrfProtection::Protection: 'Your session has expired. Please return to the home page and try again.' diff --git a/forms/en/controls.texy b/forms/en/controls.texy index 4b59401647..237761d601 100644 --- a/forms/en/controls.texy +++ b/forms/en/controls.texy @@ -218,7 +218,7 @@ Adds file upload field (class [UploadControl |api:Nette\Forms\Controls\UploadCon ```php $form->addUpload('avatar', 'Avatar:') - ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF or WebP') + ->addRule($form::Image, 'Avatar must be JPEG, PNG, GIF, WebP or AVIF') ->addRule($form::MaxFileSize, 'Maximum size is 1 MB', 1024 * 1024); ``` diff --git a/forms/en/in-presenter.texy b/forms/en/in-presenter.texy index 831db327ce..e09ebee337 100644 --- a/forms/en/in-presenter.texy +++ b/forms/en/in-presenter.texy @@ -30,11 +30,11 @@ The form in the presenter is an object of the class `Nette\Application\UI\Form`, From the presenter's point of view, the form is a common component. Therefore, it is treated as a component and incorporated into the presenter using [factory method |application:components#Factory Methods]. It will look like this: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name contains name // $data->password contains password $this->flashMessage('You have successfully signed up.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` And render in template is done using `{control}` tag: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registration</h1> {control registrationForm} diff --git a/forms/en/rendering.texy b/forms/en/rendering.texy index 2d0590f8b9..27c71677e4 100644 --- a/forms/en/rendering.texy +++ b/forms/en/rendering.texy @@ -1,13 +1,13 @@ Forms Rendering *************** -Forms' appearances can differ greatly. In fact, there are two extremes. One side is the need to render a set of very similar forms all over again, with little to none effort. Usually administrations and back-ends. +The appearance of forms can be very diverse. In practice, we can encounter two extremes. On one hand, there is a need to render a series of forms in an application that are visually similar to each other, and we appreciate the easy rendering without a template using `$form->render()`. This is usually the case with administrative interfaces. -The other side is tiny sweet forms, each one being a piece of art. Their layout can best be written in HTML. Of course, besides those extremes, there are many forms just in between. +On the other hand, there are various forms where each one is unique. Their appearance is best described using HTML language in the template. And of course, in addition to both mentioned extremes, we will encounter many forms that fall somewhere in between. -Latte -===== +Rendering With Latte +==================== The [Latte templating system|latte:] fundamentally facilitates the rendering of forms and their elements. First, we'll show how to render forms manually, element by element, to gain full control over the code. Later we will show how to [automate |#Automatic rendering] such rendering. @@ -152,6 +152,12 @@ We can detect the presence of an error using the `hasErrors()` method and set th ``` +`{form}` +-------- + +Tags `{form signInForm}...{/form}` are an alternative to `<form n:name="signInForm">...</form>`. + + Automatic Rendering ------------------- @@ -256,8 +262,8 @@ Tag `formContainer` helps with rendering of inputs inside a form container. ``` -Without Latte -============= +Rendering Without Latte +======================= The easiest way to render a form is to call: @@ -321,13 +327,13 @@ If we don't set a custom renderer, the default renderer [api:Nette\Forms\Renderi <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ Results into the following snippet: <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/en/validation.texy b/forms/en/validation.texy index d8c53fdd17..18db69d0e0 100644 --- a/forms/en/validation.texy +++ b/forms/en/validation.texy @@ -61,7 +61,7 @@ For controls `addUpload()`, `addMultiUpload()` the following rules can also be u | `MaxFileSize` | maximal file size | `int` | `MimeType` | MIME type, accepts wildcards (`'video/*'`) | `string\|string[]` -| `Image` | uploaded file is JPEG, PNG, GIF, WebP | - +| `Image` | uploaded file is JPEG, PNG, GIF, WebP, AVIF | - | `Pattern` | file name matches regular expression | `string` | `PatternInsensitive` | like `Pattern`, but case-insensitive | `string` @@ -234,7 +234,7 @@ In many cases, we discover an error when we are processing a valid form, e.g. wh try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ Or copy locally to the public folder of the project (e.g. from `vendor/nette/for Or install via [npm|https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/es/in-presenter.texy b/forms/es/in-presenter.texy index 63a3ece9e5..2dddcee064 100644 --- a/forms/es/in-presenter.texy +++ b/forms/es/in-presenter.texy @@ -30,11 +30,11 @@ El formulario en el presentador es un objeto de la clase `Nette\Application\UI\F Desde el punto de vista del presentador, el formulario es un componente común. Por lo tanto, se trata como un componente y se incorpora al presentador utilizando [el método factory |application:components#Factory Methods]. Tendrá el siguiente aspecto: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name contiene nombre // $data->password contiene contraseña $this->flashMessage('You have successfully signed up.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` Y el renderizado en la plantilla se realiza utilizando la etiqueta `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registro</h1> {control registrationForm} diff --git a/forms/es/rendering.texy b/forms/es/rendering.texy index 1e760c9ed2..91ce9f6d5e 100644 --- a/forms/es/rendering.texy +++ b/forms/es/rendering.texy @@ -1,13 +1,13 @@ Renderizado de formularios ************************** -La apariencia de los formularios puede variar mucho. De hecho, hay dos extremos. Un extremo es la necesidad de renderizar un conjunto de formularios muy similares de nuevo, con poco o ningún esfuerzo. Suelen ser administraciones y back-ends. +La apariencia de las formas puede ser muy diversa. En la práctica, podemos encontrarnos con dos extremos. Por un lado, existe la necesidad de renderizar una serie de formularios en una aplicación que son visualmente similares entre sí, y apreciamos la fácil renderización sin plantilla utilizando `$form->render()`. Este suele ser el caso de las interfaces administrativas. -El otro extremo son los pequeños formularios dulces, cada uno de los cuales es una obra de arte. Su diseño puede escribirse mejor en HTML. Por supuesto, además de estos extremos, hay muchos formularios intermedios. +Por otro lado, hay varios formularios en los que cada uno es único. Su aspecto se describe mejor utilizando el lenguaje HTML en la plantilla. Y, por supuesto, además de los dos extremos mencionados, nos encontraremos con muchos formularios que se sitúan en algún punto intermedio. -Latte .[#toc-latte] -=================== +Renderizado con Latte .[#toc-rendering-with-latte] +================================================== El [sistema de plantillas |latte:] Latte facilita fundamentalmente el renderizado de formularios y sus elementos. En primer lugar, mostraremos cómo renderizar formularios manualmente, elemento por elemento, para obtener un control total sobre el código. Más adelante mostraremos cómo [automatizar |#Automatic rendering] dicho renderizado. @@ -152,6 +152,12 @@ Podemos detectar la presencia de un error utilizando el método `hasErrors()` y ``` +`{form}` +-------- + +Etiquetas `{form signInForm}...{/form}` son una alternativa a `<form n:name="signInForm">...</form>`. + + Renderizado automático .[#toc-automatic-rendering] -------------------------------------------------- @@ -256,8 +262,8 @@ La etiqueta `formContainer` ayuda con la representación de entradas dentro de u ``` -Sin Latte .[#toc-without-latte] -=============================== +Renderizado sin Latte .[#toc-rendering-without-latte] +===================================================== La forma más sencilla de renderizar un formulario es llamar a: @@ -321,13 +327,13 @@ Si no establecemos un renderizador personalizado, se utilizará el renderizador <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ El resultado es el siguiente fragmento: <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/es/validation.texy b/forms/es/validation.texy index a5974e94f9..9b7e35bc31 100644 --- a/forms/es/validation.texy +++ b/forms/es/validation.texy @@ -234,7 +234,7 @@ En muchos casos, descubrimos un error cuando estamos procesando un formulario v try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ O copiarlo localmente en la carpeta pública del proyecto (por ejemplo, desde `v O instalar a través de [npm |https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/fr/in-presenter.texy b/forms/fr/in-presenter.texy index 2c44d71d95..0fb310f2cb 100644 --- a/forms/fr/in-presenter.texy +++ b/forms/fr/in-presenter.texy @@ -30,11 +30,11 @@ Le formulaire dans le présentateur est un objet de la classe `Nette\Application Du point de vue du présentateur, le formulaire est un composant commun. Par conséquent, il est traité comme un composant et incorporé dans le présentateur à l'aide de la [méthode factory |application:components#Factory Methods]. Cela ressemblera à ceci : -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name contient le nom // $data->password contient password $this->flashMessage('Vous vous êtes inscrit avec succès.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` Et le rendu dans le modèle est effectué à l'aide de la balise `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registration</h1> {control registrationForm} diff --git a/forms/fr/rendering.texy b/forms/fr/rendering.texy index c5cbb77cd1..2915a9b2c9 100644 --- a/forms/fr/rendering.texy +++ b/forms/fr/rendering.texy @@ -1,13 +1,13 @@ Rendu des formulaires ********************* -Les apparences des formulaires peuvent être très différentes. En fait, il existe deux extrêmes. D'un côté, il est nécessaire de rendre à nouveau un ensemble de formulaires très similaires, avec peu ou pas d'effort. Généralement, les administrations et les back-ends. +L'aspect des formes peut être très varié. Dans la pratique, nous pouvons rencontrer deux extrêmes. D'une part, il est nécessaire de rendre une série de formulaires dans une application qui sont visuellement similaires les uns aux autres, et nous apprécions le rendu facile sans modèle à l'aide de `$form->render()`. C'est généralement le cas des interfaces administratives. -De l'autre côté, on trouve de minuscules formulaires, chacun étant une œuvre d'art. Leur mise en page peut être écrite au mieux en HTML. Bien sûr, à côté de ces extrêmes, il existe de nombreuses formes qui se situent juste entre les deux. +D'autre part, il existe différents formulaires dont chacun est unique. Leur apparence est mieux décrite en utilisant le langage HTML dans le modèle. Et bien sûr, en plus des deux extrêmes mentionnés, nous rencontrerons de nombreux formulaires qui se situent quelque part entre les deux. -Latte .[#toc-latte] -=================== +Rendu avec Latte .[#toc-rendering-with-latte] +============================================= Le [système de templates Latte |latte:] facilite fondamentalement le rendu des formulaires et de leurs éléments. Nous allons d'abord montrer comment rendre les formulaires manuellement, élément par élément, afin d'avoir un contrôle total sur le code. Plus tard, nous montrerons comment [automatiser |#Automatic rendering] ce rendu. @@ -152,6 +152,12 @@ Nous pouvons détecter la présence d'une erreur à l'aide de la méthode `hasEr ``` +`{form}` +-------- + +Les étiquettes `{form signInForm}...{/form}` sont une alternative à `<form n:name="signInForm">...</form>`. + + Rendu automatique .[#toc-automatic-rendering] --------------------------------------------- @@ -256,8 +262,8 @@ La balise `formContainer` aide à rendre les entrées à l'intérieur d'un conte ``` -Sans Latte .[#toc-without-latte] -================================ +Rendu sans Latte .[#toc-rendering-without-latte] +================================================ La façon la plus simple de rendre un formulaire est d'appeler : @@ -321,13 +327,13 @@ Si nous ne définissons pas de moteur de rendu personnalisé, le moteur de rendu <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ Ce qui donne l'extrait suivant : <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/fr/validation.texy b/forms/fr/validation.texy index fdf9797815..361456d8c7 100644 --- a/forms/fr/validation.texy +++ b/forms/fr/validation.texy @@ -234,7 +234,7 @@ Dans de nombreux cas, nous découvrons une erreur lors du traitement d'un formul try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ Ou le copier localement dans le dossier public du projet (par exemple à partir Ou installer via [npm |https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/hu/in-presenter.texy b/forms/hu/in-presenter.texy index f8771aca48..d3bc0884a4 100644 --- a/forms/hu/in-presenter.texy +++ b/forms/hu/in-presenter.texy @@ -30,11 +30,11 @@ A prezenterben lévő űrlap a `Nette\Application\UI\Form` osztály objektuma, e A bemutató szempontjából az űrlap egy közös komponens. Ezért komponensként kezeljük, és a [factory metódus |application:components#Factory Methods] segítségével beépítjük a prezentálóba. Ez így fog kinézni: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name tartalmazza a nevet // $data->password tartalmazza a jelszót $this->flashMessage('Sikeresen regisztrált.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` A sablonban történő megjelenítés pedig a `{control}` tag használatával történik: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registration</h1> {control registrationForm} diff --git a/forms/hu/rendering.texy b/forms/hu/rendering.texy index 3a6f14ac62..813324306e 100644 --- a/forms/hu/rendering.texy +++ b/forms/hu/rendering.texy @@ -1,13 +1,13 @@ Forms Rendering *************** -Az űrlapok megjelenése nagymértékben eltérhet egymástól. Valójában két szélsőség létezik. Az egyik oldal az, amikor egy sor nagyon hasonló űrlapot kell újra és újra megjeleníteni, kevés vagy semmi erőfeszítéssel. Általában adminisztrációk és back-endek. +A formák megjelenése nagyon változatos lehet. A gyakorlatban két szélsőséggel találkozhatunk. Egyrészt szükség van arra, hogy egy alkalmazásban egy sor, egymáshoz vizuálisan hasonló űrlapot jelenítsünk meg, és értékeljük a `$form->render()` segítségével történő egyszerű, sablon nélküli megjelenítést. Ez általában az adminisztrációs felületek esetében fordul elő. -A másik oldal az apró, édes formák, amelyek mindegyike egy-egy műalkotás. Ezek elrendezése a legjobban HTML-ben írható meg. Természetesen ezeken a végleteken kívül is sok űrlap létezik a kettő között. +Másrészt vannak különböző űrlapok, ahol mindegyik egyedi. Megjelenésüket a legjobban a sablonban található HTML nyelv segítségével lehet leírni. És természetesen a két említett szélsőség mellett számos olyan űrlappal is találkozunk, amelyek valahol a kettő között helyezkednek el. -Latte .[#toc-latte] -=================== +Renderelés Latte-val .[#toc-rendering-with-latte] +================================================= A [Latte templating rendszer |latte:] alapvetően megkönnyíti az űrlapok és elemeik megjelenítését. Először megmutatjuk, hogyan lehet az űrlapokat manuálisan, elemenként renderelni, hogy teljes kontrollt kapjunk a kód felett. Később megmutatjuk, hogyan lehet [automatizálni |#Automatic rendering] ezt a renderelést. @@ -152,6 +152,12 @@ A `hasErrors()` metódus segítségével észlelhetjük a hiba jelenlétét, és ``` +`{form}` +-------- + +Címkék `{form signInForm}...{/form}` alternatívája a `<form n:name="signInForm">...</form>`. + + Automatikus renderelés .[#toc-automatic-rendering] -------------------------------------------------- @@ -256,8 +262,8 @@ A `formContainer` címke az űrlapkonténeren belüli bemenetek megjelenítésé ``` -Latte nélkül .[#toc-without-latte] -================================== +Renderelés Latte nélkül .[#toc-rendering-without-latte] +======================================================= A legegyszerűbb módja egy űrlap megjelenítésének a következő hívás: @@ -321,13 +327,13 @@ Ha nem állítunk be egyéni renderelőt, akkor az alapértelmezett renderelőt <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ Az eredmény a következő részlet: <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/hu/validation.texy b/forms/hu/validation.texy index c90d71cf7a..c75844fbdb 100644 --- a/forms/hu/validation.texy +++ b/forms/hu/validation.texy @@ -234,7 +234,7 @@ Sok esetben egy érvényes űrlap feldolgozása közben fedezünk fel hibát, p try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ Vagy másolja be helyileg a projekt nyilvános mappájába (pl. a `vendor/nette/ Vagy telepítse az [npm |https://www.npmjs.com/package/nette-forms] segítségével: -```bash +```shell npm install nette-forms ``` diff --git a/forms/it/in-presenter.texy b/forms/it/in-presenter.texy index 3b4c40bce0..a7b68bbb17 100644 --- a/forms/it/in-presenter.texy +++ b/forms/it/in-presenter.texy @@ -30,11 +30,11 @@ Il modulo nel presentatore è un oggetto della classe `Nette\Application\UI\Form Dal punto di vista del presentatore, il modulo è un componente comune. Pertanto, viene trattato come un componente e incorporato nel presentatore con il [metodo factory |application:components#Factory Methods]. L'aspetto sarà il seguente: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name contiene nome // $data->password contiene la password $this->flashMessage('Ti sei iscritto con successo.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` E il rendering nel template viene effettuato utilizzando il tag `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registration</h1> {control registrationForm} diff --git a/forms/it/rendering.texy b/forms/it/rendering.texy index 2d0c9c209e..b1ac2c635b 100644 --- a/forms/it/rendering.texy +++ b/forms/it/rendering.texy @@ -1,13 +1,13 @@ Rendering dei moduli ******************** -L'aspetto dei moduli può essere molto diverso. In effetti, esistono due estremi. Da un lato c'è la necessità di renderizzare da capo un insieme di moduli molto simili, con uno sforzo minimo. Di solito le amministrazioni e i back-end. +L'aspetto delle forme può essere molto vario. In pratica, si possono incontrare due estremi. Da un lato, c'è la necessità di rendere una serie di moduli in un'applicazione che siano visivamente simili tra loro e si apprezza la facilità di renderli senza un modello usando `$form->render()`. Questo è solitamente il caso delle interfacce amministrative. -L'altro estremo è costituito da moduli piccoli e dolci, ognuno dei quali è un'opera d'arte. Il loro layout può essere scritto al meglio in HTML. Naturalmente, oltre a questi estremi, esistono molte forme intermedie. +D'altra parte, esistono vari moduli, ognuno dei quali è unico. Il loro aspetto è meglio descritto utilizzando il linguaggio HTML nel template. E naturalmente, oltre ai due estremi citati, incontreremo molti moduli che si collocano a metà strada. -Latte .[#toc-latte] -=================== +Rendering con Latte .[#toc-rendering-with-latte] +================================================ Il [sistema di template Latte |latte:] facilita fondamentalmente il rendering dei moduli e dei loro elementi. In primo luogo, mostreremo come rendere i moduli manualmente, elemento per elemento, per ottenere il pieno controllo sul codice. In seguito mostreremo come [automatizzare |#Automatic rendering] tale rendering. @@ -152,6 +152,12 @@ Possiamo rilevare la presenza di un errore usando il metodo `hasErrors()` e impo ``` +`{form}` +-------- + +I tag `{form signInForm}...{/form}` sono un'alternativa a `<form n:name="signInForm">...</form>`. + + Rendering automatico .[#toc-automatic-rendering] ------------------------------------------------ @@ -256,8 +262,8 @@ Il tag `formContainer` aiuta a rendere gli input all'interno di un contenitore d ``` -Senza Latte .[#toc-without-latte] -================================= +Rendering senza Latte .[#toc-rendering-without-latte] +===================================================== Il modo più semplice per rendere un modulo è chiamare: @@ -321,13 +327,13 @@ Se non si imposta un renderer personalizzato, verrà utilizzato il renderer pred <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ Il risultato è il seguente snippet: <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/it/validation.texy b/forms/it/validation.texy index e9ce00d9f0..1602645bce 100644 --- a/forms/it/validation.texy +++ b/forms/it/validation.texy @@ -234,7 +234,7 @@ In molti casi, si scopre un errore durante l'elaborazione di un modulo valido, a try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ Oppure copiare localmente nella cartella pubblica del progetto (ad esempio da `v Oppure installare tramite [npm |https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/pl/in-presenter.texy b/forms/pl/in-presenter.texy index f148d9e8b7..ff8db944ce 100644 --- a/forms/pl/in-presenter.texy +++ b/forms/pl/in-presenter.texy @@ -30,11 +30,11 @@ Formularz w prezenterze jest obiektem klasy `Nette\Application\UI\Form`, jego po Z punktu widzenia prezentera formularz jest normalnym komponentem. Dlatego traktujemy go jako komponent i włączamy do prezentera za pomocą [metody factory |application:components#Factory-Methods]. Będzie to wyglądało tak: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -59,7 +59,7 @@ class HomepagePresenter extends Nette\Application\UI\Presenter A w szablonie renderujemy formularz z tagiem `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registrace</h1> {control registrationForm} diff --git a/forms/pl/rendering.texy b/forms/pl/rendering.texy index b659779443..b224e8d73d 100644 --- a/forms/pl/rendering.texy +++ b/forms/pl/rendering.texy @@ -1,13 +1,13 @@ Rendering formularzy ******************** -Wygląd form może być bardzo zróżnicowany. W praktyce możemy spotkać się z dwoma skrajnościami. Z jednej strony istnieje potrzeba renderowania w aplikacji wielu formularzy, które są do siebie podobne wizualnie jak jajka do jajek i doceniamy łatwość renderowania za pomocą `$form->render()`. Tak jest zazwyczaj w przypadku interfejsów administracyjnych. +Wygląd form może być bardzo zróżnicowany. W praktyce możemy spotkać się z dwoma skrajnościami. Z jednej strony istnieje potrzeba renderowania w aplikacji serii formularzy, które są do siebie wizualnie podobne, a my cenimy sobie łatwe renderowanie bez szablonu za pomocą `$form->render()`. Taka sytuacja ma miejsce zazwyczaj w przypadku interfejsów administracyjnych. -Z drugiej strony istnieje wiele form, w których obowiązuje zasada: jeden utwór jest oryginałem. I oczywiście oprócz dwóch wymienionych skrajności istnieje wiele form, które mieszczą się gdzieś pomiędzy nimi. +Z drugiej strony istnieją różne formularze, gdzie każdy z nich jest unikalny. Ich wygląd najlepiej opisać za pomocą języka HTML w szablonie. I oczywiście oprócz obu wymienionych skrajności spotkamy wiele form, które mieszczą się gdzieś pomiędzy. -Latte .[#toc-latte] -=================== +Rendering z Latte .[#toc-rendering-with-latte] +============================================== [System szablonów Latte |latte:] zasadniczo ułatwia renderowanie formularzy i ich elementów. Najpierw pokażemy, jak ręcznie renderować formularze element po elemencie i dzięki temu zyskać pełną kontrolę nad kodem. Później pokażemy, jak [zautomatyzować |#Automatické vykreslování] takie renderowanie. @@ -152,6 +152,12 @@ Możemy wykryć obecność błędu za pomocą metody `hasErrors()` i odpowiednio ``` +`{form}` +-------- + +Tagi `{form signInForm}...{/form}` są alternatywą dla `<form n:name="signInForm">...</form>`. + + Automatyczne renderowanie .[#toc-automatic-rendering] ----------------------------------------------------- @@ -256,8 +262,8 @@ Znacznik `{formContainer}` pomoże w rysowaniu elementów wewnątrz kontenera fo ``` -No Latte .[#toc-without-latte] -============================== +Rendering bez Latte .[#toc-rendering-without-latte] +=================================================== Najprostszym sposobem renderowania formularza jest wywołanie: @@ -321,13 +327,13 @@ Jeśli nie ustawimy niestandardowego renderera, zostanie użyty domyślny render <tr class="required"> <th><label class="required" for="frm-name">Jméno:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Věk:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ W rezultacie otrzymujemy taki oto kod HTML: <dl> <dt><label class="required" for="frm-name">Jméno:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Věk:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Pohlaví:</label></dt> diff --git a/forms/pl/validation.texy b/forms/pl/validation.texy index 0ca69803f0..75e7c62d54 100644 --- a/forms/pl/validation.texy +++ b/forms/pl/validation.texy @@ -292,7 +292,7 @@ Albo skopiuj go lokalnie do publicznego folderu projektu (np. z `vendor/nette/fo Lub zainstalować za pośrednictwem [npm: |https://www.npmjs.com/package/nette-forms] -```bash +```shell npm install nette-forms ``` diff --git a/forms/pt/in-presenter.texy b/forms/pt/in-presenter.texy index a79555276c..81cf7df2ae 100644 --- a/forms/pt/in-presenter.texy +++ b/forms/pt/in-presenter.texy @@ -30,11 +30,11 @@ A forma no apresentador é um objeto da classe `Nette\Application\UI\Form`, seu Do ponto de vista do apresentador, a forma é um componente comum. Portanto, ele é tratado como um componente e incorporado ao apresentador usando [o método de fábrica |application:components#Factory Methods]. Será parecido com isto: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name contém nome // $data->password contém a senha $this->flashMessage('Você se inscreveu com sucesso'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` E a renderização em modelo é feita usando a tag `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registration</h1> {control registrationForm} diff --git a/forms/pt/rendering.texy b/forms/pt/rendering.texy index 80582dd96e..37a6d94fdc 100644 --- a/forms/pt/rendering.texy +++ b/forms/pt/rendering.texy @@ -1,13 +1,13 @@ Prestação de formulários ************************ -As aparências dos formulários podem ser muito diferentes. Na verdade, existem dois extremos. Um lado é a necessidade de tornar um conjunto de formas muito semelhantes novamente, com pouco ou nenhum esforço. Normalmente administrações e back-ends. +A aparência das formas pode ser muito diversificada. Na prática, podemos encontrar dois extremos. Por um lado, há a necessidade de renderizar uma série de formulários em uma aplicação que são visualmente semelhantes entre si, e apreciamos a fácil renderização sem um modelo usando `$form->render()`. Este é geralmente o caso das interfaces administrativas. -O outro lado são pequenas formas doces, sendo cada uma delas uma obra de arte. Seu layout pode ser melhor escrito em HTML. É claro que, além desses extremos, há muitos formulários entre eles. +Por outro lado, existem várias formas onde cada uma é única. Sua aparência é melhor descrita usando a linguagem HTML no modelo. E é claro que, além dos dois extremos mencionados, vamos encontrar muitos formulários que se enquadram em algum lugar no meio. -Latte .[#toc-latte] -=================== +Renderização com Latte .[#toc-rendering-with-latte] +=================================================== O [sistema de modelos Latte |latte:] facilita fundamentalmente a renderização de formulários e seus elementos. Primeiro, mostraremos como renderizar formulários manualmente, elemento por elemento, para obter controle total sobre o código. Mais tarde, mostraremos como [automatizar |#Automatic rendering] tal renderização. @@ -152,6 +152,12 @@ Podemos detectar a presença de um erro usando o método `hasErrors()` e definir ``` +`{form}` +-------- + +Etiquetas `{form signInForm}...{/form}` são uma alternativa para `<form n:name="signInForm">...</form>`. + + Renderização automática .[#toc-automatic-rendering] --------------------------------------------------- @@ -256,8 +262,8 @@ A etiqueta `formContainer` ajuda na renderização de entradas dentro de um cont ``` -Sem o Latte .[#toc-without-latte] -================================= +Renderização sem Latte .[#toc-rendering-without-latte] +====================================================== A maneira mais fácil de entregar um formulário é telefonar: @@ -321,13 +327,13 @@ Se não definirmos um renderizador personalizado, será usado o renderizador pad <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ Resultados no seguinte trecho: <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/pt/validation.texy b/forms/pt/validation.texy index c2332a4a09..fe1ab31f0d 100644 --- a/forms/pt/validation.texy +++ b/forms/pt/validation.texy @@ -234,7 +234,7 @@ Em muitos casos, descobrimos um erro quando estamos processando um formulário v try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ Ou copiar localmente para a pasta pública do projeto (por exemplo, de `vendor/n Ou instalar via [npm |https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/ro/in-presenter.texy b/forms/ro/in-presenter.texy index 9284a8e606..fb18bf4c66 100644 --- a/forms/ro/in-presenter.texy +++ b/forms/ro/in-presenter.texy @@ -30,11 +30,11 @@ Formularul din prezentator este un obiect din clasa `Nette\Application\UI\Form`, Din punctul de vedere al prezentatorului, formularul este o componentă comună. Prin urmare, acesta este tratat ca o componentă și încorporat în prezentator folosind [metoda factory |application:components#Factory Methods]. Acesta va arăta astfel: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name conține nume // $data->password conține parola $this->flashMessage('You have successfully signed up.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` Iar redarea în șablon se face cu ajutorul etichetei `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registration</h1> {control registrationForm} diff --git a/forms/ro/rendering.texy b/forms/ro/rendering.texy index 0efe54f078..1eb25cc947 100644 --- a/forms/ro/rendering.texy +++ b/forms/ro/rendering.texy @@ -1,13 +1,13 @@ Redarea formularelor ******************** -Aspectul formularelor poate fi foarte diferit. De fapt, există două extreme. O parte este necesitatea de a reda din nou un set de formulare foarte asemănătoare, cu un efort redus sau chiar inexistent. De obicei, administrațiile și back-end-urile. +Aspectul formelor poate fi foarte divers. În practică, putem întâlni două extreme. Pe de o parte, există necesitatea de a reda într-o aplicație o serie de formulare care să fie asemănătoare din punct de vedere vizual, iar noi apreciem redarea ușoară fără șablon cu ajutorul `$form->render()`. Acesta este, de obicei, cazul interfețelor administrative. -Cealaltă parte este reprezentată de formulare mici și dulci, fiecare dintre ele fiind o operă de artă. Aspectul lor poate fi scris cel mai bine în HTML. Bineînțeles, pe lângă aceste extreme, există multe formulare chiar între ele. +Pe de altă parte, există diverse formulare în care fiecare este unic. Aspectul lor este cel mai bine descris folosind limbajul HTML în șablon. Și, bineînțeles, pe lângă cele două extreme menționate, vom întâlni multe forme care se încadrează undeva la mijloc. -Latte .[#toc-latte] -=================== +Renderizare cu Latte .[#toc-rendering-with-latte] +================================================= [Sistemul de modelare Latte |latte:] facilitează în mod fundamental redarea formularelor și a elementelor acestora. În primul rând, vom arăta cum să redăm formularele manual, element cu element, pentru a obține un control total asupra codului. Ulterior vom arăta cum să [automatizăm |#Automatic rendering] această redare. @@ -152,6 +152,12 @@ Putem detecta prezența unei erori folosind metoda `hasErrors()` și putem seta ``` +`{form}` +-------- + +Etichete `{form signInForm}...{/form}` sunt o alternativă la `<form n:name="signInForm">...</form>`. + + Redare automată .[#toc-automatic-rendering] ------------------------------------------- @@ -256,8 +262,8 @@ Tag-ul `formContainer` ajută la redarea intrărilor în interiorul unui contain ``` -Fără Latte .[#toc-without-latte] -================================ +Rendering fără Latte .[#toc-rendering-without-latte] +==================================================== Cel mai simplu mod de a reda un formular este de a apela: @@ -321,13 +327,13 @@ Este un obiect care asigură redarea formularului. Acesta poate fi setat prin me <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ Rezultă următorul fragment: <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/ro/validation.texy b/forms/ro/validation.texy index 74c1d1d0b1..87d26482db 100644 --- a/forms/ro/validation.texy +++ b/forms/ro/validation.texy @@ -234,7 +234,7 @@ Erori de procesare .[#toc-processing-errors] try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ Sau copiați local în folderul public al proiectului (de exemplu, de la `vendor Sau instalați prin [npm |https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/ru/in-presenter.texy b/forms/ru/in-presenter.texy index 9fcc9be442..f8ba0bff66 100644 --- a/forms/ru/in-presenter.texy +++ b/forms/ru/in-presenter.texy @@ -30,11 +30,11 @@ $form->onSuccess[] = [$this, 'formSucceeded']; С точки зрения презентера форма является общим компонентом. Поэтому она рассматривается как компонент и включается в презентер с помощью [фабричного метода |application:components#Factory-Methods]. Это будет выглядеть следующим образом: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name содержит имя // $data->password содержит пароль $this->flashMessage('Вы успешно зарегистрировались.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` А рендеринг в шаблоне осуществляется с помощью тега `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Регистрация</h1> {control registrationForm} diff --git a/forms/ru/rendering.texy b/forms/ru/rendering.texy index 3ef63a16b5..0d1a640600 100644 --- a/forms/ru/rendering.texy +++ b/forms/ru/rendering.texy @@ -1,13 +1,13 @@ Рендеринг форм ************** -Внешний вид форм может сильно отличаться. На самом деле, есть две крайности. Первая — это необходимость заново создавать набор очень похожих форм, практически не прилагая усилий. Как правило, это формы в бэкенде. +Внешний вид форм может быть очень разнообразным. На практике мы можем столкнуться с двумя крайностями. С одной стороны, в приложении необходимо отобразить ряд форм, визуально похожих друг на друга, и мы ценим простоту визуализации без шаблона с помощью `$form->render()`. Обычно это относится к административным интерфейсам. -С другой стороны — крошечные сладкие формы, каждая из которых — произведение искусства. Их макет лучше всего написать на языке HTML. Конечно, помимо этих крайностей, существует множество форм, находящихся между ними. +С другой стороны, существуют различные формы, каждая из которых уникальна. Их внешний вид лучше всего описывать с помощью языка HTML в шаблоне. И конечно, помимо обеих упомянутых крайностей, мы встретим множество форм, которые находятся где-то посередине. -Latte .[#toc-latte] -=================== +Рендеринг с помощью Latte .[#toc-rendering-with-latte] +====================================================== [Система шаблонов Latte |latte:] в корне облегчает отрисовку форм и их элементов. Сначала мы покажем, как отрисовывать формы вручную, элемент за элементом, чтобы получить полный контроль над кодом. Позже мы покажем, как [автоматизировать |#Automatic-Rendering] такой рендеринг. @@ -152,6 +152,12 @@ protected function createComponentSignInForm(): Form ``` +`{form}` +-------- + +Теги `{form signInForm}...{/form}` являются альтернативой `<form n:name="signInForm">...</form>`. + + Автоматический рендеринг .[#toc-automatic-rendering] ---------------------------------------------------- @@ -256,8 +262,8 @@ protected function createComponentSignInForm(): Form ``` -Без Latte .[#toc-without-latte] -=============================== +Рендеринг без Latte .[#toc-rendering-without-latte] +=================================================== Самый простой способ отобразить форму - вызвать: @@ -321,13 +327,13 @@ $form->render(); <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ $form->render(); <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/ru/validation.texy b/forms/ru/validation.texy index dd0efea7f3..b3f60e1a21 100644 --- a/forms/ru/validation.texy +++ b/forms/ru/validation.texy @@ -234,7 +234,7 @@ public function validateSignInForm(Form $form, \stdClass $data): void try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ $form->addText('zip', 'Почтовый индекс:') Или установите через [npm|https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/sl/in-presenter.texy b/forms/sl/in-presenter.texy index 576f38ebcd..bf919a5eb2 100644 --- a/forms/sl/in-presenter.texy +++ b/forms/sl/in-presenter.texy @@ -30,11 +30,11 @@ Obrazec v predstavitvi je objekt razreda `Nette\Application\UI\Form`, njegov pre Z vidika predstavnika je obrazec skupna komponenta. Zato ga obravnavamo kot komponento in ga vključimo v predstavitveni program z uporabo [tovarniške metode |application:components#Factory Methods]. To bo videti takole: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name vsebuje ime // $data->password vsebuje geslo $this->flashMessage('You have successfully signed up.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` Prikaz v predlogi pa se izvede z uporabo oznake `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registration</h1> {control registrationForm} diff --git a/forms/sl/rendering.texy b/forms/sl/rendering.texy index 1198cbb20f..680af79ddd 100644 --- a/forms/sl/rendering.texy +++ b/forms/sl/rendering.texy @@ -1,13 +1,13 @@ Oblikovanje obrazcev ******************** -Videzi obrazcev se lahko zelo razlikujejo. Pravzaprav obstajata dve skrajnosti. Ena stran je potreba po ponovnem upodabljanju niza zelo podobnih obrazcev z malo ali nič truda. Običajno gre za administracije in zaledne strani. +Videz oblik je lahko zelo raznolik. V praksi lahko naletimo na dve skrajnosti. Po eni strani je treba v aplikaciji prikazati vrsto obrazcev, ki so si vizualno podobni, pri čemer cenimo enostavno prikazovanje brez predloge z uporabo spletne strani `$form->render()`. To je običajno primer upravnih vmesnikov. -Druga stran so drobni ljubki obrazci, od katerih je vsak umetniško delo. Njihovo postavitev je najbolje napisati v jeziku HTML. Seveda je poleg teh skrajnosti še veliko vmesnih obrazcev. +Po drugi strani pa obstajajo različni obrazci, pri katerih je vsak izmed njih edinstven. Njihov videz je najbolje opisati s pomočjo jezika HTML v predlogi. In seveda bomo poleg obeh omenjenih skrajnosti naleteli na številne obrazce, ki so nekje vmes. -Latte .[#toc-latte] -=================== +Renderiranje z Latte .[#toc-rendering-with-latte] +================================================= [Sistem predlog Latte |latte:] bistveno olajša upodabljanje obrazcev in njihovih elementov. Najprej bomo pokazali, kako ročno upodabljati obrazce, element za elementom, da bi pridobili popoln nadzor nad kodo. Kasneje bomo pokazali, kako takšno upodabljanje [avtomatizirati |#Automatic rendering]. @@ -152,6 +152,12 @@ Z metodo `hasErrors()` lahko zaznamo prisotnost napake in ustrezno nastavimo raz ``` +`{form}` +-------- + +Oznake `{form signInForm}...{/form}` so alternativa za `<form n:name="signInForm">...</form>`. + + Samodejno upodabljanje .[#toc-automatic-rendering] -------------------------------------------------- @@ -256,8 +262,8 @@ Oznaka `formContainer` pomaga pri izrisovanju vnosov znotraj vsebnika obrazca. ``` -Brez Latte .[#toc-without-latte] -================================ +Renderiranje brez Latte .[#toc-rendering-without-latte] +======================================================= Obrazec najlažje prikažete tako, da pokličete: @@ -321,13 +327,13 @@ To je objekt, ki zagotavlja upodabljanje obrazca. Nastavite ga lahko z metodo `$ <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ Rezultat je naslednji delček: <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/sl/validation.texy b/forms/sl/validation.texy index 2ad03bbfd3..a1f35438fb 100644 --- a/forms/sl/validation.texy +++ b/forms/sl/validation.texy @@ -234,7 +234,7 @@ V številnih primerih odkrijemo napako med obdelavo veljavnega obrazca, npr. ko try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ Lahko pa jo kopirate lokalno v javno mapo projekta (npr. s spletne strani `vendo ali namestite prek [npm |https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/forms/tr/in-presenter.texy b/forms/tr/in-presenter.texy index cb1e9c6ab2..cef4cc8ee8 100644 --- a/forms/tr/in-presenter.texy +++ b/forms/tr/in-presenter.texy @@ -30,11 +30,11 @@ Sunucudaki form `Nette\Application\UI\Form` sınıfının bir nesnesidir, selefi Sunucunun bakış açısından, form ortak bir bileşendir. Bu nedenle, bir bileşen olarak ele alınır ve [fabrika yöntemi |application:components#Factory Methods] kullanılarak sunucuya dahil edilir. Bu şekilde görünecektir: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name isim içeriyor // $data->password parola içerir $this->flashMessage('Başarıyla kaydoldunuz.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` Ve şablonda render işlemi `{control}` etiketi kullanılarak yapılır: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Registration</h1> {control registrationForm} diff --git a/forms/tr/rendering.texy b/forms/tr/rendering.texy index 08d33c6c3e..2835653df8 100644 --- a/forms/tr/rendering.texy +++ b/forms/tr/rendering.texy @@ -1,13 +1,13 @@ Form Oluşturma ************** -Formların görünümleri büyük farklılıklar gösterebilir. Aslında iki uç nokta vardır. Bir tarafta, çok az veya hiç çaba sarf etmeden, çok benzer bir dizi formu baştan oluşturma ihtiyacı vardır. Genellikle yönetimler ve arka uçlar. +Formların görünümü çok çeşitli olabilir. Uygulamada iki uç noktayla karşılaşabiliriz. Bir yandan, bir uygulamada görsel olarak birbirine benzeyen bir dizi form oluşturma ihtiyacı vardır ve `$form->render()` kullanarak şablon olmadan kolay oluşturmayı takdir ederiz. Bu durum genellikle yönetim arayüzleri için geçerlidir. -Diğer tarafta ise her biri bir sanat eseri olan minik tatlı formlar var. Bunların düzeni en iyi HTML ile yazılabilir. Elbette, bu uç noktaların yanı sıra, bu ikisinin arasında kalan pek çok form vardır. +Öte yandan, her birinin benzersiz olduğu çeşitli formlar vardır. Bunların görünümü en iyi şablonda HTML dili kullanılarak tanımlanır. Ve elbette, bahsedilen her iki aşırı uca ek olarak, arada bir yere düşen birçok formla karşılaşacağız. -Latte .[#toc-latte] -=================== +Latte ile Rendering .[#toc-rendering-with-latte] +================================================ [Latte şablonlama sistemi |latte:], formların ve öğelerinin oluşturulmasını temel olarak kolaylaştırır. İlk olarak, kod üzerinde tam kontrol elde etmek için formların öğe öğe manuel olarak nasıl oluşturulacağını göstereceğiz. Daha sonra bu işlemin nasıl [otomatikleştirileceğini |#Automatic rendering] göstereceğiz. @@ -152,6 +152,12 @@ Mesaj yoksa boş bir öğe oluşturmaktan kaçınmak `n:ifcontent` ile zarif bir ``` +`{form}` +-------- + +Etiketler `{form signInForm}...{/form}` bir alternatiftir `<form n:name="signInForm">...</form>`. + + Otomatik Rendering .[#toc-automatic-rendering] ---------------------------------------------- @@ -256,8 +262,8 @@ Etiket `formContainer` bir form konteyneri içindeki girdilerin oluşturulmasın ``` -Latte olmadan .[#toc-without-latte] -=================================== +Latte Olmadan Rendering .[#toc-rendering-without-latte] +======================================================= Bir formu oluşturmanın en kolay yolu çağırmaktır: @@ -321,13 +327,13 @@ Eğer özel bir renderer ayarlamazsak, varsayılan renderer [api:Nette\Forms\Ren <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ Sonuçlar aşağıdaki kod parçacığına dönüşür: <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/tr/validation.texy b/forms/tr/validation.texy index e98a660897..2d02efe8a3 100644 --- a/forms/tr/validation.texy +++ b/forms/tr/validation.texy @@ -234,7 +234,7 @@ public function validateSignInForm(Form $form, \stdClass $data): void try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ Ya da yerel olarak projenin ortak klasörüne kopyalayın (örneğin `vendor/net Veya [npm |https://www.npmjs.com/package/nette-forms] aracılığıyla yükleyin: -```bash +```shell npm install nette-forms ``` diff --git a/forms/uk/in-presenter.texy b/forms/uk/in-presenter.texy index cf22c6a937..ed89773929 100644 --- a/forms/uk/in-presenter.texy +++ b/forms/uk/in-presenter.texy @@ -30,11 +30,11 @@ $form->onSuccess[] = [$this, 'formSucceeded']; З точки зору презентера форма є загальним компонентом. Тому вона розглядається як компонент і включається в презентер за допомогою [фабричного методу |application:components#Factory-Methods]. Це виглядатиме наступним чином: -```php .{file:app/Presenters/HomepagePresenter.php} +```php .{file:app/Presenters/HomePresenter.php} use Nette; use Nette\Application\UI\Form; -class HomepagePresenter extends Nette\Application\UI\Presenter +class HomePresenter extends Nette\Application\UI\Presenter { protected function createComponentRegistrationForm(): Form { @@ -52,14 +52,14 @@ class HomepagePresenter extends Nette\Application\UI\Presenter // $data->name містить ім'я // $data->password містить пароль $this->flashMessage('Ви успішно зареєструвалися.'); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } } ``` А рендеринг у шаблоні здійснюється за допомогою тега `{control}`: -```latte .{file:app/Presenters/templates/Homepage/default.latte} +```latte .{file:app/Presenters/templates/Home/default.latte} <h1>Регистрация</h1> {control registrationForm} diff --git a/forms/uk/rendering.texy b/forms/uk/rendering.texy index fe78f947a9..95bf703c4d 100644 --- a/forms/uk/rendering.texy +++ b/forms/uk/rendering.texy @@ -1,13 +1,13 @@ Рендеринг форм ************** -Зовнішній вигляд форм може сильно відрізнятися. Насправді, є дві крайності. Перша - це необхідність заново створювати набір дуже схожих форм, практично не докладаючи зусиль. Як правило, це форми в бекенді. +Зовнішній вигляд форм може бути дуже різноманітним. На практиці ми можемо зіткнутися з двома крайнощами. З одного боку, є потреба відрендерити серію форм у додатку, які візуально схожі одна на одну, і ми цінуємо простий рендеринг без шаблону за допомогою `$form->render()`. Зазвичай це стосується адміністративних інтерфейсів. -З іншого боку - крихітні солодкі форми, кожна з яких - витвір мистецтва. Їхній макет найкраще написати мовою HTML. Звісно, крім цих крайнощів, існує безліч форм, що знаходяться між ними. +З іншого боку, існують різні форми, де кожна з них унікальна. Їх зовнішній вигляд найкраще описати за допомогою мови HTML в шаблоні. І, звичайно, крім обох згаданих крайнощів, ми зустрінемо багато форм, які знаходяться десь посередині. -Latte .[#toc-latte] -=================== +Візуалізація за допомогою Latte .[#toc-rendering-with-latte] +============================================================ [Система шаблонів Latte |latte:] докорінно полегшує відтворення форм та їхніх елементів. Спочатку ми покажемо, як відтворювати форми вручну, елемент за елементом, щоб отримати повний контроль над кодом. Пізніше ми покажемо, як [автоматизувати |#Automatic-Rendering] такий рендеринг. @@ -152,6 +152,12 @@ protected function createComponentSignInForm(): Form ``` +`{form}` +-------- + +Мітки `{form signInForm}...{/form}` є альтернативою `<form n:name="signInForm">...</form>`. + + Автоматичний рендеринг .[#toc-automatic-rendering] -------------------------------------------------- @@ -256,8 +262,8 @@ protected function createComponentSignInForm(): Form ``` -Без Latte .[#toc-without-latte] -=============================== +Рендеринг без латте .[#toc-rendering-without-latte] +=================================================== Найпростіший спосіб відобразити форму - викликати: @@ -321,13 +327,13 @@ $form->render(); <tr class="required"> <th><label class="required" for="frm-name">Name:</label></th> - <td><input type="text" class="text" name="name" id="frm-name" value=""></td> + <td><input type="text" class="text" name="name" id="frm-name" required value=""></td> </tr> <tr class="required"> <th><label class="required" for="frm-age">Age:</label></th> - <td><input type="text" class="text" name="age" id="frm-age" value=""></td> + <td><input type="text" class="text" name="age" id="frm-age" required value=""></td> </tr> <tr> @@ -357,12 +363,12 @@ $form->render(); <dl> <dt><label class="required" for="frm-name">Name:</label></dt> - <dd><input type="text" class="text" name="name" id="frm-name" value=""></dd> + <dd><input type="text" class="text" name="name" id="frm-name" required value=""></dd> <dt><label class="required" for="frm-age">Age:</label></dt> - <dd><input type="text" class="text" name="age" id="frm-age" value=""></dd> + <dd><input type="text" class="text" name="age" id="frm-age" required value=""></dd> <dt><label>Gender:</label></dt> diff --git a/forms/uk/validation.texy b/forms/uk/validation.texy index 7e833e0a68..a9ca94d241 100644 --- a/forms/uk/validation.texy +++ b/forms/uk/validation.texy @@ -234,7 +234,7 @@ public function validateSignInForm(Form $form, \stdClass $data): void try { $data = $form->getValues(); $this->user->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { if ($e->getCode() === Nette\Security\Authenticator::InvalidCredential) { @@ -292,7 +292,7 @@ $form->addText('zip', 'Поштовий індекс:') Або встановіть через [npm |https://www.npmjs.com/package/nette-forms]: -```bash +```shell npm install nette-forms ``` diff --git a/http/cs/request.texy b/http/cs/request.texy index f4f76bc948..6a23793c3f 100644 --- a/http/cs/request.texy +++ b/http/cs/request.texy @@ -355,7 +355,7 @@ Nevěřte hodnotě vrácené touto metodou. Klient mohl odeslat škodlivý náze getSanitizedName(): string .[method] ------------------------------------ -Vrací sanitizovaný název souboru. Obsahuje pouze ASCII znaky `[a-zA-Z0-9.-]`. Pokud název takové znaky neobsahuje, vrátí `'unknown'`. Pokud je soubor obrázek ve formátu JPEG, PNG, GIF, nebo WebP, vrátí i správnou příponu. +Vrací sanitizovaný název souboru. Obsahuje pouze ASCII znaky `[a-zA-Z0-9.-]`. Pokud název takové znaky neobsahuje, vrátí `'unknown'`. Pokud je soubor obrázek ve formátu JPEG, PNG, GIF, WebP nebo AVIF, vrátí i správnou příponu. getUntrustedFullPath(): string .[method] @@ -378,7 +378,7 @@ Vrací cestu k dočasné lokaci uploadovaného souboru. V případě, že upload isImage(): bool .[method] ------------------------- -Vrací `true`, pokud nahraný soubor je obrázek ve formátu JPEG, PNG, GIF, nebo WebP. Detekce probíhá na základě jeho signatury a neověřuje se integrita celého souboru. Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení|#toImage]. +Vrací `true`, pokud nahraný soubor je obrázek ve formátu JPEG, PNG, GIF, WebP nebo AVIF. Detekce probíhá na základě jeho signatury a neověřuje se integrita celého souboru. Zda není obrázek poškozený lze zjistit například pokusem o jeho [načtení|#toImage]. .[caution] Vyžaduje PHP rozšíření `fileinfo`. diff --git a/http/en/request.texy b/http/en/request.texy index edf7a57531..080b6d54ff 100644 --- a/http/en/request.texy +++ b/http/en/request.texy @@ -355,7 +355,7 @@ Do not trust the value returned by this method. A client could send a malicious getSanitizedName(): string .[method] ------------------------------------ -Returns the sanitized file name. It contains only ASCII characters `[a-zA-Z0-9.-]`. If the name does not contain such characters, it returns 'unknown'. If the file is JPEG, PNG, GIF, or WebP image, it returns the correct file extension. +Returns the sanitized file name. It contains only ASCII characters `[a-zA-Z0-9.-]`. If the name does not contain such characters, it returns 'unknown'. If the file is JPEG, PNG, GIF, WebP or AVIF image, it returns the correct file extension. getUntrustedFullPath(): string .[method] @@ -378,7 +378,7 @@ Returns the path of the temporary location of the uploaded file. If the upload w isImage(): bool .[method] ------------------------- -Returns `true` if the uploaded file is a JPEG, PNG, GIF, or WebP image. Detection is based on its signature. The integrity of the entire file is not checked. You can find out if an image is not corrupted for example by trying to [load it|#toImage]. +Returns `true` if the uploaded file is a JPEG, PNG, GIF, WebP or AVIF image. Detection is based on its signature. The integrity of the entire file is not checked. You can find out if an image is not corrupted for example by trying to [load it|#toImage]. .[caution] Requires PHP extension `fileinfo`. diff --git a/latte/bg/@left-menu.texy b/latte/bg/@left-menu.texy index 2edb276425..eaeb13b43f 100644 --- a/latte/bg/@left-menu.texy +++ b/latte/bg/@left-menu.texy @@ -1,4 +1,5 @@ - [Започване на работа |Guide] +- [Защо да използвате шаблони? |why-use] - Концепции - [Безопасността на първо място |Safety First] - [Наследяване на шаблона |Template Inheritance] diff --git a/latte/bg/cookbook/@home.texy b/latte/bg/cookbook/@home.texy index b034b37866..8d6307deee 100644 --- a/latte/bg/cookbook/@home.texy +++ b/latte/bg/cookbook/@home.texy @@ -1,6 +1,9 @@ Готварска книга *************** +.[perex] +Примерни кодове и рецепти за изпълнение на често срещани задачи с Latte. + - [Всичко, което винаги сте искали да знаете за {iterateWhile} |iteratewhile] - [Как се пишат SQL заявки в Latte |how-to-write-sql-queries-in-latte]? - [Миграция от PHP |migration-from-php] diff --git a/latte/bg/tags.texy b/latte/bg/tags.texy index be066500be..ec38cd5e94 100644 --- a/latte/bg/tags.texy +++ b/latte/bg/tags.texy @@ -102,7 +102,7 @@ .[table-latte-tags language-latte] |## Налично само в Nette Forms -| `{form}`... `{/form}` |# [Отпечатва елемента на формата |forms:rendering#Latte] +| `{form}`... `{/form}` |# [Отпечатва елемента на формата |forms:rendering#form] | `{label}`|00 ... `{/label}` | [отпечатва тага за въвеждане на формуляра |forms:rendering#label-input] | `{input}` | [отпечатва елемента за влизане във формата|forms:rendering#label-input] | `{inputError}` | [отпечатва съобщението за грешка за елемента за въвеждане на форма |forms:rendering#inputError] @@ -110,6 +110,7 @@ | `{formPrint}` [генерира чертеж на формата Latte |forms:rendering#formPrint] | `{formPrintClass}` [отпечатва PHP клас за данните от формата |forms:in-presenter#Mapping-to-Classes] | `{formContext}`... `{/formContext}` | [Частично визуализиране на формата |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [визуализиране на контейнера за формуляри |forms:rendering#special-cases] Отпечатъци .[#toc-printing] @@ -595,7 +596,7 @@ Lata е умен и `$iterator->last` работи не само за масив Разрешените шаблони нямат достъп до променливите на активния контекст, но имат достъп до глобалните променливи. -Можете да предавате променливи по този начин: +Можете да предавате променливи на вмъкнатия шаблон по следния начин: ```latte {* от Latte 2.9 *} diff --git a/latte/bg/template-inheritance.texy b/latte/bg/template-inheritance.texy index b35b1dfea1..3e9e01133c 100644 --- a/latte/bg/template-inheritance.texy +++ b/latte/bg/template-inheritance.texy @@ -246,7 +246,7 @@ bar: {$bar ?? 'not defined'} // prints: not defined Производните блокове нямат достъп до променливите на активния контекст, освен ако блокът не е дефиниран в същия файл, в който е включен. Те обаче имат достъп до глобални променливи. -Можете да предавате променливи по този начин: +Можете да предавате променливи на блока по следния начин: ```latte {* от Latte 2.9 *} @@ -603,7 +603,7 @@ Hi, I am Mary. ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/bg/why-use.texy b/latte/bg/why-use.texy new file mode 100644 index 0000000000..f2ef746501 --- /dev/null +++ b/latte/bg/why-use.texy @@ -0,0 +1,80 @@ +Защо да използвате шаблони? +*************************** + + +Защо трябва да използвам система за шаблониране в PHP? .[#toc-why-should-i-use-a-templating-system-in-php] +---------------------------------------------------------------------------------------------------------- + +Защо да се използва система за шаблони в PHP, след като самият PHP е език за шаблониране? + +Нека първо да обобщим накратко историята на този език, която е изпълнена с интересни обрати. Един от първите езици за програмиране, използвани за генериране на HTML страници, е езикът C. Скоро обаче стана ясно, че използването му за тази цел е непрактично. Така Расмус Лердорф създава PHP, който улеснява генерирането на динамичен HTML с помощта на езика C на гърба. Първоначално PHP е замислен като език за шаблониране, но с течение на времето придобива допълнителни функции и се превръща в пълноценен език за програмиране. + +Въпреки това той все още функционира като език за шаблониране. PHP файл може да съдържа HTML страница, в която променливите се извеждат с помощта на `<?= $foo ?>`, и т.н. + +В началото на историята на PHP е създадена системата за шаблони Smarty, чиято цел е строго да отдели външния вид (HTML/CSS) от логиката на приложението. Тя умишлено предоставяше по-ограничен език от самия PHP, така че например разработчикът да не може да направи заявка към база данни от шаблон и т.н. От друга страна, той представляваше допълнителна зависимост в проектите, увеличаваше тяхната сложност и изискваше от програмистите да научат нов език Smarty. Тези предимства бяха спорни и обикновеният PHP продължи да се използва за шаблони. + +С течение на времето системите за шаблони започнаха да стават полезни. Те въведоха концепции като [наследяване |template-inheritance], [режим на пясъчна кутия |sandbox] и редица други функции, които значително опростиха създаването на шаблони в сравнение с чистия PHP. На преден план излезе темата за сигурността, съществуването на [уязвимости като XSS |safety-first] и необходимостта от [ескейпване |#What is escaping]. Системите за шаблони въведоха автоматичното извеждане, за да се елиминира рискът програмистът да го забрави и да създаде сериозна дупка в сигурността (скоро ще видим, че това има определени подводни камъни). + +Днес ползите от шаблонните системи далеч надхвърлят разходите, свързани с внедряването им. Следователно има смисъл да ги използвате. + + +Защо Latte е по-добър от Twig или Blade? .[#toc-why-is-latte-better-than-twig-or-blade] +--------------------------------------------------------------------------------------- + +Причините са няколко - някои от тях са приятни, а други са изключително полезни. Latte е комбинация от приятно и полезно. + +*Първо, приятните:* Latte има същия [синтаксис като PHP |syntax#Latte Understands PHP]. Единствената разлика е в записването на таговете, като се предпочитат по-кратките `{` и `}` вместо `<?=` и `?>`. Това означава, че не е необходимо да учите нов език. Разходите за обучение са минимални. Най-важното е, че по време на разработката не се налага постоянно да "превключвате" между езика PHP и езика на шаблоните, тъй като и двата са еднакви. Това е така за разлика от шаблоните Twig, които използват езика Python, което принуждава програмиста да превключва между два различни езика. + +*Сега за изключително полезната причина:* Всички системи за шаблони, като Twig, Blade или Smarty, са се развили така, че да включват защита срещу XSS под формата на автоматично [ескапиране |#What is escaping]. По-точно, автоматично извикване на функцията `htmlspecialchars()`. Създателите на Latte обаче осъзнаха, че това съвсем не е правилното решение. Това е така, защото различните части на документа изискват различни методи за ескапиране. Наивното автоматично ескапиране е опасна функция, защото създава фалшиво чувство за сигурност. + +За да може автоматичното ескапиране да бъде функционално и надеждно, то трябва да разпознава къде в документа се извеждат данните (наричаме ги контексти) и съответно да избира функцията за ескапиране. Следователно то трябва да е [чувствително към контекста |safety-first#Context-Aware Escaping]. Именно това може да направи Latte. Той разбира HTML. Той не възприема шаблона само като низ от символи, а разбира какво представляват таговете, атрибутите и т.н. Следователно той ескапира по различен начин в HTML текст, в рамките на HTML тагове, в JavaScript и т.н. + +Latte е първата и единствена система за шаблони на PHP с контекстно чувствително ескапиране. Тя представлява единствената наистина сигурна система за шаблони. + +*И още една приятна причина:* Тъй като Latte разбира HTML, тя предлага други много приятни функции. Например, [n:attributes |syntax#n:attributes]. Или възможност за [проверка на връзки |safety-first#Link checking]. И много други. + + +Какво е ескейпинг? .[#toc-what-is-escaping] +------------------------------------------- + +Ескапирането е процес, който включва заместване на символи със специално значение със съответните последователности при вмъкване на един низ в друг, за да се предотвратят нежелани ефекти или грешки. Например при вмъкване на низ в HTML текст, в който символът `<` има специално значение, тъй като указва началото на таг, го заменяме със съответната последователност, която е HTML ентитетът `<`. Това позволява на браузъра да показва правилно символа `<`. + +Прост пример за директно ескапиране при писане на PHP код е вмъкването на кавички в низ чрез поставяне на обратна наклонена черта пред тях. + +Разглеждаме ескапирането по-подробно в главата [Как да се защитим от XSS |safety-first#How to Defend Against XSS?]. + + +Може ли да се изпълни заявка за база данни от шаблон Latte? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +-------------------------------------------------------------------------------------------------------------------------- + +В шаблоните можете да работите с обекти, които програмистът им предава. Ако програмистът иска, може да подаде обект от база данни на шаблона и да изпълни заявка. Ако възнамеряват да го направят, няма причина да им се пречи. + +Различна ситуация възниква, ако искате да дадете на клиенти или външни програмисти възможност да редактират шаблони. В този случай определено не искате те да имат достъп до базата данни. Разбира се, няма да предадете обекта на базата данни на шаблона, но какво ще стане, ако той може да бъде достъпен чрез друг обект? Решението е [режимът на пясъчника |sandbox], който ви позволява да определите кои методи могат да се извикват в шаблоните. Благодарение на това не е необходимо да се притеснявате за пробиви в сигурността. + + +Какви са основните разлики между системите за шаблониране като Latte, Twig и Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +Разликите между системите за шаблониране като Latte, Twig и Blade се състоят главно в техния синтаксис, сигурност и интеграция с фреймуърки: + +- Latte: използва синтаксиса на езика PHP, което я прави по-лесна за научаване и използване. Тя осигурява първокласна защита срещу XSS атаки. +- Twig: използва синтаксис, подобен на този на Python, който е доста различен от този на PHP. Той избягва без разграничаване на контекста. Добре интегриран е с рамката Symfony. +- Blade: използва комбинация от PHP и собствен синтаксис. Той не различава контекста. Тясно интегриран е с функциите и екосистемата на Laravel. + + +Заслужава ли си компаниите да използват система за шаблониране? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +------------------------------------------------------------------------------------------------------------------------------- + +Първо, разходите, свързани с обучението, използването и общите ползи, варират значително в зависимост от системата. Системата за шаблониране Latte, благодарение на използването на синтаксиса на PHP, значително опростява обучението за програмисти, които вече са запознати с този език. Обикновено са необходими няколко часа, за да може програмистът да се запознае в достатъчна степен с Latte, което намалява разходите за обучение и ускорява усвояването на технологията и, което е най-важно, ефективността при ежедневната употреба. + +Освен това Latte осигурява високо ниво на защита срещу XSS уязвимост благодарение на уникалната си технология за избягване на контекст. Тази защита е от решаващо значение за гарантиране на сигурността на уеб приложенията и за свеждане до минимум на риска от атаки, които могат да застрашат потребителите или данните на компанията. Сигурността на уеб приложенията е важна и за поддържането на добрата репутация на компанията. Проблемите със сигурността могат да доведат до загуба на доверие от страна на клиентите и да навредят на репутацията на компанията на пазара. + +Използването на Latte също така намалява общите разходи за разработка и поддръжка, като улеснява и двете. Следователно използването на система за шаблониране определено си заслужава. + + +Влияе ли Latte върху производителността на уеб приложенията? .[#toc-does-latte-affect-the-performance-of-web-applications] +-------------------------------------------------------------------------------------------------------------------------- + +Въпреки че шаблоните Latte се обработват бързо, този аспект няма особено значение. Причината е, че парсирането на файловете се извършва само веднъж по време на първото показване. След това те се компилират в PHP код, съхраняват се на диска и се изпълняват при всяка следваща заявка, без да се налага повторно компилиране. + +Това е начинът, по който се работи в производствена среда. По време на разработката шаблоните Latte се прекомпилират всеки път, когато съдържанието им се промени, така че разработчикът винаги вижда актуалната версия. diff --git a/latte/cs/@left-menu.texy b/latte/cs/@left-menu.texy index 9c119e9632..545d015f3d 100644 --- a/latte/cs/@left-menu.texy +++ b/latte/cs/@left-menu.texy @@ -1,4 +1,5 @@ - [Začínáme s Latte |guide] +- [Proč používat šablony? |why-use] - Koncepty - [Bezpečnost především |safety-first] - [Dědičnost šablon |Template Inheritance] diff --git a/latte/cs/cookbook/@home.texy b/latte/cs/cookbook/@home.texy index 809774dfb5..1cdf0a908b 100644 --- a/latte/cs/cookbook/@home.texy +++ b/latte/cs/cookbook/@home.texy @@ -1,6 +1,9 @@ Návody a postupy **************** +.[perex] +Příklady kódů a receptů pro provádění běžných úkolů pomocí Latte. + - [Všechno, co jste kdy chtěli vědět o {iterateWhile} |iteratewhile] - [Jak psát SQL queries v Latte? |how-to-write-sql-queries-in-latte] - [Migrace z Latte 2 |migration-from-latte2] diff --git a/latte/cs/tags.texy b/latte/cs/tags.texy index 68d7c6adb5..22155a7ac6 100644 --- a/latte/cs/tags.texy +++ b/latte/cs/tags.texy @@ -102,7 +102,7 @@ Přehled a popis všech tagů (neboli značek či maker) šablonovacího systém .[table-latte-tags language-latte] |## Dostupné pouze s Nette Forms -| `{form}` … `{/form}` | [vykreslí značky formuláře |forms:rendering#latte] +| `{form}` … `{/form}` | [vykreslí značky formuláře |forms:rendering#form] | `{label}` … `{/label}` | [vykreslí popisku formulářového prvku |forms:rendering#label-input] | `{input}` | [vykreslí formulářový prvek |forms:rendering#label-input] | `{inputError}` | [vypíše chybovou hlášku formulářového prvku |forms:rendering#inputError] @@ -110,6 +110,7 @@ Přehled a popis všech tagů (neboli značek či maker) šablonovacího systém | `{formPrint}` | [navrhne Latte kód pro formulář |forms:rendering#formPrint] | `{formPrintClass}` | [navrhne PHP kód třídy s daty formuláře |forms:in-presenter#mapovani-na-tridy] | `{formContext}` … `{/formContext}` | [částečné kreslení formuláře |forms:rendering#specialni-pripady] +| `{formContainer}` … `{/formContainer}` | [kreslení formulářového kontejneru |forms:rendering#specialni-pripady] Vypisování @@ -595,7 +596,7 @@ Značka `{include}` načte a vykreslí uvedenou šablonu. Pokud bychom se bavili Vložené šablony nemají přístup k proměnným aktivního kontextu, mají přístup jen ke globálním proměnným. -Další proměnné můžete předávat tímto způsobem: +Proměnné do vložené šablony můžete předávat tímto způsobem: ```latte {* od Latte 2.9 *} diff --git a/latte/cs/template-inheritance.texy b/latte/cs/template-inheritance.texy index 9f711b4c7d..7ce430c84d 100644 --- a/latte/cs/template-inheritance.texy +++ b/latte/cs/template-inheritance.texy @@ -215,7 +215,7 @@ Značku lze také zapsat jako [n:attribut|syntax#n:atributy]: Lokální bloky .{data-version:2.9} --------------------------------- -Každý blok přepisuje obsah nadřazeného bloku se stejným názvem – kromě lokálních bloků. Ve třídách by šlo o něco jako privátní metody. Šablonu tak můžete tvořit bez obav, že kvůli shodě jmen bloků by byly přepsány z jiné šablonoy. +Každý blok přepisuje obsah nadřazeného bloku se stejným názvem – kromě lokálních bloků. Ve třídách by šlo o něco jako privátní metody. Šablonu tak můžete tvořit bez obav, že kvůli shodě jmen bloků by byly přepsány z jiné šablony. ```latte {block local helper} @@ -246,7 +246,7 @@ Lze také vypsat blok z jiné šablony: Vykreslovaný blok nemá přístup k proměnným aktivního kontextu, kromě případů, kdy je blok definován ve stejném souboru, kde je i vložen. Má však přístup ke globálním proměnným. -Proměnné můžete předávat tímto způsobem: +Proměnné můžete do bloku předávat tímto způsobem: ```latte {* od Latte 2.9 *} @@ -603,7 +603,7 @@ V Latte existují různé typy dědičnosti a opětovného použití kódu. Poj ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/cs/why-use.texy b/latte/cs/why-use.texy new file mode 100644 index 0000000000..1ae700909a --- /dev/null +++ b/latte/cs/why-use.texy @@ -0,0 +1,80 @@ +Proč používat šablony? +********************** + + +Proč bych měl použít šablonovací systém v PHP? +---------------------------------------------- + +Proč používat šablonovací systém v PHP, když je PHP samo o sobě šablonovací jazyk? + +Pojďme si nejprve stručně zrekapitulovat historii tohoto jazyka, která je plná zajímavých zvratů. Jeden z prvních programovacích jazyků používaných pro generování HTML stránek byl jazyk C. Brzy se však ukázalo, že jeho použití pro tento účel je nepraktické. Rasmus Lerdorf proto vytvořil PHP, které usnadnilo generování dynamického HTML s jazykem C na backendu. PHP bylo tedy původně navrženo jako šablonovací jazyk, ale postupem času získalo další funkce a stalo se plnohodnotným programovacím jazykem. + +Přesto stále funguje i jako šablonovací jazyk. V souboru PHP může být zapsaná HTML stránka, ve které se pomocí `<?= $foo ?>` vypisují proměnné atd. + +Již v počátcích historie PHP vznikl šablonovací systém Smarty, jehož účelem bylo striktně oddělit vzhled (HTML/CSS) od aplikační logiky. Tedy záměrně poskytoval omezenější jazyk, než samotné PHP, aby vývojář nemohl například provést dotaz do databáze ze šablony apod. Na druhou stranu představoval další závislost v projektech, zvyšoval jejich složitost a programátoři se museli učit nový jazyk Smarty. Takový přínos byl sporný a nadále se pro šablony používalo prosté PHP. + +V průběhu času se začaly šablonovací systémy stávat užitečnými. Přišly s konceptem [dědičnosti |template-inheritance], [sandbox režimem|sandbox] a řadou dalších funkcí, který významně zjednodušily tvorbu šablon oproti čistému PHP. Do popředí se dostalo téma bezpečnosti, existence [zranitelností jako XSS|safety-first] a nutnost [escapování|#Co je to escapování]. Šablonovací systémy přišly s autoescapováním, aby zmizelo riziko, že programátor na to zapomene a vznikne vážná bezpečnostní díra (za chvíli si ukážeme, že to má jistá úskalí). + +Přínosy šablonovacích systému dnes výrazně převyšují náklady spojené s jejich nasazením. Proto má smysl je používat. + + +Proč je lepší Latte než třeba Twig nebo Blade? +---------------------------------------------- + +Důvodů je hned několik – některé jsou příjemné a jiné zásadně užitečné. Latte je kombinací příjemného s užitečným. + +*Nejprve ten příjemný:* Latte má stejnou [syntaxi jako PHP|syntax#Latte rozumí PHP]. Liší se jen zápis značek, místo `<?=` a `?>` preferuje kratší `{` a `}`. To znamená, že se nemusíte učit nový jazyk. Náklady na zaškolení jsou minimální. A hlavně se při vývoji nemusíte neustále "přepínat" mezi jazykem PHP a jazykem šablony, jelikož jsou oba stejné. Na rozdíl od šablon Twig, které používají jazyk Python, a programátor se tak musí přepínat mezi dvěma odlišnými jazyky. + +*A nyní důvod nesmírně užitečný*: Všechny šablonovací systémy, jako Twig, Blade nebo Smarty, přišly v průběhu evoluce s ochranou proti XSS v podobě automatického [escapování|#Co je to escapování]. Přesněji řečeno automatického volání funkce `htmlspecialchars()`. Tvůrci Latte si ale uvědomili, že tohle vůbec není správné řešení. Protože na různých místech dokumentu se escapuje různými způsoby. Naivní autoescapování je nebezpečná funkce, protože vytváří falešný pocit bezpečí. + +Aby bylo autoescapování funkční a spolehlivé, musí rozeznávat, ve kterém místě dokumentu se data vypisují (říkáme jim kontexty) a podle něj volit escapovací funkci. Tedy musí být [kontextově-sensitivní|safety-first#Kontextově sensitivní escapování]. A tohle právě Latte umí. Rozumí HTML. Nevnímá šablonu jen jako řetězec znaků, ale chápe, co jsou značky, atributy atd. A proto jinak escapuje v HTML textu, jinak uvnitř HTML značky, jinak uvnitř JavaScriptu atd. + +Latte je prvním a jediným šablonovacím systémem v PHP, který má kontextově citlivé escapování. Představuje tak jediný opravdu bezpečný šablonovací systém. + +*A ještě jeden příjemný důvod*: Díky tomu, že Latte chápe HTML, nabízí další velmi příjemné vychytávky. Například [n:atributy|syntax#n:atributy]. Nebo schopnost [kontrolovat odkazy|safety-first#Kontrola odkazů]. A mnoho dalších. + + +Co je to escapování? +-------------------- + +Escapování je proces, která spočívá v nahrazování znaků se speciálním významem odpovídajícími sekvencemi při vkládání jednoho řetězce do druhého, aby se zabránilo nežádoucím jevům či chybám. Například když vkládáme řetězec do HTML textu, ve kterém má znak `<` zvláštní význam, jelikož označuje začátek značky, nahradíme jej odpovídající sekvencí, což je HTML entita `<`. Díky tomu prohlížeč správně zobrazí symbol `<`. + +Jednoduchým příkladem escapování přímo při psaní kódu v PHP je vložení uvozovky do řetězce, kdy před ni napíšeme zpětné lomítko. + +Podrobněji escapování rozebíráme v kapitole [Jak se bránit XSS|safety-first#Jak se bránit XSS]. + + +Lze v Latte provést dotaz do databáze ze šablony? +------------------------------------------------- + +V šablonách lze pracovat s objekty, které do nich programátor předá. Pokud tedy programátor chce, může do šablony předat objekt databáze a nad ním provést dotaz. Pokud má takový záměr, není důvod mu v tom bránit. + +Jiná situace nastává, pokud chcete dát možnost editovat šablony klientům nebo externím kodérům. V takovém případě rozhodně nechcete, aby měli přístup k databázi. Samozřejmě šabloně objekt databáze nepředáte, ale co když se k ní lze dostat skrze jiný objekt? Řešením je [sandbox režim|sandbox], který umožňuje definovat, které metody lze v šablonách volat. Díky tomu se nemusíte obávat narušení bezpečnosti. + + +Jaké jsou hlavní rozdíly mezi šablonovacími systémy jako Latte, Twig a Blade? +----------------------------------------------------------------------------- + +Rozdíly mezi šablonovacími systémy Latte, Twig a Blade spočívají hlavně v syntaxi, zabezpečení a způsobu integrace do frameworků + +- Latte: používá syntaxi jazyka PHP, což usnadňuje učení a používání. Poskytuje špičkovou ochranu proti XSS útokům. +- Twig: používá syntax jazyka Python, která se od PHP dosti liší. Escapuje bez rozlišení kontextu. Je dobře integrován do Symfony frameworku. +- Blade: používá směs PHP a vlastní syntaxe. Escapuje bez rozlišení kontextu. Je těsně integrován s Laravel funkcemi a ekosystémem. + + +Vyplatí se firmám používat šablonovací systém? +---------------------------------------------- + +Předně, náklady spojené se zaškolením, používáním a celkovým přínosem, se významně liší podle systému. Šablonovací systém Latte díky tomu, že používá syntaxi PHP, velice zjednodušuje učení pro programátory již obeznámené s tímto jazykem. Obvykle trvá několik hodin, než se programátor s Latte dostatečně seznámí. Snižuje tedy náklady na školení. Zároveň zrychluje osvojení technologie a především efektivitu při každodenním používání. + +Dále Latte poskytuje vysokou úroveň ochrany proti zranitelnosti XSS díky unikátní technologii kontextově citlivého escapování. Tato ochrana je klíčová pro zajištění bezpečnosti webových aplikací a minimalizaci rizika útoků, které by mohly ohrozit uživatele či firemní data. Ochrana bezpečnosti webových aplikací je důležitá také pro udržení dobré pověsti firmy. Bezpečnostní problémy mohou způsobit ztrátu důvěry ze strany zákazníků a poškodit reputaci firmy na trhu. + +Použití Latte také snižuje celkové náklady na vývoj a údržbu aplikace tím, že obojí usnadňuje. Použití šablonovacího systému se tedy jednoznačně vyplatí. + + +Ovlivňuje Latte výkon webových aplikací? +---------------------------------------- + +Ačkoliv šablony Latte jsou zpracovávány rychle, na tomto aspektu vlastně nezáleží. Důvodem je, že parsování souborů proběhne pouze jednou při prvním zobrazení. Následně jsou zkompilovány do PHP kódu, uloženy na disk a spouštěny při každém dalším požadavku, aniž by bylo nutné provádět opětovnou kompilaci. + +Toto je způsob fungování v produkčním prostředí. Během vývoje se Latte šablony překompilují pokaždé, když dojde ke změně jejich obsahu, aby vývojář viděl vždy aktuální podobu. diff --git a/latte/de/@left-menu.texy b/latte/de/@left-menu.texy index c2d380373e..b0f0fc6fa1 100644 --- a/latte/de/@left-menu.texy +++ b/latte/de/@left-menu.texy @@ -1,4 +1,5 @@ - [Erste Schritte |Guide] +- [Warum Vorlagen verwenden? |why-use] - Konzepte - [Sicherheit zuerst |Safety First] - [Vererbung von Vorlagen |Template Inheritance] diff --git a/latte/de/cookbook/@home.texy b/latte/de/cookbook/@home.texy index dba032e0d2..32d8a0126c 100644 --- a/latte/de/cookbook/@home.texy +++ b/latte/de/cookbook/@home.texy @@ -1,6 +1,9 @@ Kochbuch ******** +.[perex] +Beispielcodes und Rezepte für die Erledigung gängiger Aufgaben mit Latte. + - [Alles, was Sie schon immer über {iterateWhile} wissen wollten |iteratewhile] - [Wie schreibt man SQL-Abfragen in Latte? |how-to-write-sql-queries-in-latte] - [Umstieg von PHP |migration-from-php] diff --git a/latte/de/tags.texy b/latte/de/tags.texy index 6433c5853f..916051055c 100644 --- a/latte/de/tags.texy +++ b/latte/de/tags.texy @@ -102,7 +102,7 @@ Zusammenfassung und Beschreibung aller in Latte integrierten Tags. .[table-latte-tags language-latte] |## Nur mit Nette Forms verfügbar -| `{form}`... `{/form}` | [druckt ein Formularelement |forms:rendering#latte] +| `{form}`... `{/form}` | [druckt ein Formularelement |forms:rendering#form] | `{label}`... `{/label}` | [druckt eine Formulareingabebezeichnung |forms:rendering#label-input] | `{input}` | [druckt ein Formulareingabeelement |forms:rendering#label-input] | `{inputError}` | [gibt eine Fehlermeldung für ein Formulareingabeelement aus |forms:rendering#inputError] @@ -110,6 +110,7 @@ Zusammenfassung und Beschreibung aller in Latte integrierten Tags. | `{formPrint}` | [erzeugt einen Latte-Formular-Blaupause |forms:rendering#formPrint] | `{formPrintClass}` | [gibt PHP-Klasse für Formulardaten aus |forms:in-presenter#mapping-to-classes] | `{formContext}`... `{/formContext}` | [Teilweise Formularwiedergabe |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [Darstellung des Formular-Containers |forms:rendering#special-cases] Drucken .[#toc-printing] @@ -595,7 +596,7 @@ Der `{include}` Tag lädt und rendert die angegebene Vorlage. In unserer Lieblin Eingebundene Templates haben keinen Zugriff auf die Variablen des aktiven Kontexts, aber sie haben Zugriff auf die globalen Variablen. -Auf diese Weise können Sie Variablen übergeben: +Sie können der eingefügten Vorlage auf folgende Weise Variablen übergeben: ```latte {* seit Latte 2.9 *} diff --git a/latte/de/template-inheritance.texy b/latte/de/template-inheritance.texy index 8ef36e9e12..b797b663c4 100644 --- a/latte/de/template-inheritance.texy +++ b/latte/de/template-inheritance.texy @@ -246,7 +246,7 @@ Sie können auch einen Block aus einer anderen Vorlage anzeigen: Gedruckte Blöcke haben keinen Zugriff auf die Variablen des aktiven Kontexts, es sei denn, der Block ist in derselben Datei definiert, in die er eingebunden ist. Sie haben jedoch Zugriff auf die globalen Variablen. -Sie können Variablen auf diese Weise übergeben: +Sie können Variablen auf folgende Weise an den Block übergeben: ```latte {* seit Latte 2.9 *} @@ -603,7 +603,7 @@ Es gibt verschiedene Arten der Vererbung und der Wiederverwendung von Code in La ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/de/why-use.texy b/latte/de/why-use.texy new file mode 100644 index 0000000000..b52c5a8b85 --- /dev/null +++ b/latte/de/why-use.texy @@ -0,0 +1,80 @@ +Warum Vorlagen verwenden? +************************* + + +Warum sollte ich ein Template-System in PHP verwenden? .[#toc-why-should-i-use-a-templating-system-in-php] +---------------------------------------------------------------------------------------------------------- + +Warum sollte man ein Template-System in PHP verwenden, wenn PHP selbst eine Vorlagensprache ist? + +Lassen Sie uns zunächst kurz die Geschichte dieser Sprache rekapitulieren, die voller interessanter Wendungen ist. Eine der ersten Programmiersprachen, die zum Erstellen von HTML-Seiten verwendet wurde, war die Sprache C. Es stellte sich jedoch bald heraus, dass ihre Verwendung für diesen Zweck unpraktisch war. Rasmus Lerdorf schuf daher PHP, das die Erstellung von dynamischem HTML mit der Sprache C auf dem Backend erleichterte. PHP wurde ursprünglich als Vorlagensprache entwickelt, erhielt im Laufe der Zeit jedoch weitere Funktionen und wurde zu einer vollwertigen Programmiersprache. + +Dennoch funktioniert es immer noch als Vorlagensprache. In einer PHP-Datei kann eine HTML-Seite geschrieben werden, in der Variablen usw. mit `<?= $foo ?>` ausgegeben werden. + +In den Anfängen der PHP-Geschichte entstand das Template-System Smarty, dessen Ziel es war, das Aussehen (HTML/CSS) strikt von der Anwendungslogik zu trennen. Es bot also absichtlich eine eingeschränktere Sprache als PHP selbst, damit der Entwickler zum Beispiel keine Datenbankabfrage aus einer Vorlage ausführen konnte usw. Auf der anderen Seite stellte es eine zusätzliche Abhängigkeit in Projekten dar, erhöhte ihre Komplexität und die Programmierer mussten die neue Smarty-Sprache erlernen. Dieser Nutzen war umstritten und PHP wurde weiterhin für Vorlagen verwendet. + +Im Laufe der Zeit wurden Vorlagensysteme nützlicher. Sie führten Konzepte wie [Vererbung|template-inheritance], [Sandbox-Modus|sandbox] und eine Reihe weiterer Funktionen ein, die die Erstellung von Vorlagen im Vergleich zu reinem PHP erheblich vereinfachten. Sicherheit wurde ein zentrales Thema, die Existenz von [Schwachstellen wie XSS|safety-first] und die Notwendigkeit des [Escapings|#What is escaping]. Vorlagensysteme führten das Autoescaping ein, um das Risiko zu beseitigen, dass der Programmierer es vergisst und eine schwerwiegende Sicherheitslücke entsteht (wir werden in Kürze sehen, dass dies einige Fallstricke hat). + +Die Vorteile von Vorlagensystemen überwiegen heute deutlich die Kosten für deren Implementierung. Daher ist es sinnvoll, sie zu verwenden. + + +Warum ist Latte besser als Twig oder Blade? .[#toc-why-is-latte-better-than-twig-or-blade] +------------------------------------------------------------------------------------------ + +Es gibt mehrere Gründe - einige sind angenehm und andere grundlegend nützlich. Latte ist eine Kombination aus angenehm und nützlich. + +*Zuerst das Angenehme:* Latte hat die gleiche [Syntax wie PHP|syntax#Latte Understands PHP]. Es unterscheidet sich nur in der Schreibweise der Tags, statt `<?=` und `?>` bevorzugt es die kürzeren `{` und `}`. Das bedeutet, dass Sie keine neue Sprache lernen müssen. Die Schulungskosten sind minimal. Vor allem müssen Sie sich bei der Entwicklung nicht ständig zwischen der PHP-Sprache und der Vorlagensprache "umschalten", da beide gleich sind. Im Gegensatz zu Twig-Vorlagen, die Python verwenden, und der Programmierer muss daher zwischen zwei verschiedenen Sprachen wechseln. + +*Und nun ein äußerst nützlicher Grund:* Alle Vorlagensysteme wie Twig, Blade oder Smarty haben im Laufe ihrer Evolution Schutz gegen XSS in Form von automatischem [Escaping|#What is escaping] eingeführt. Genauer gesagt, automatisches Aufrufen der Funktion `htmlspecialchars()`. Die Schöpfer von Latte haben jedoch erkannt, dass dies keine gute Lösung ist. Denn an verschiedenen Stellen im Dokument wird auf unterschiedliche Weise escaped. Naives Autoescaping ist eine gefährliche Funktion, weil es ein falsches Gefühl von Sicherheit erzeugt. + +Damit Autoescaping funktioniert und zuverlässig ist, muss es erkennen, an welcher Stelle im Dokument Daten ausgegeben werden (wir nennen sie Kontexte) und die Escaping-Funktion entsprechend auswählen. Es muss also [kontextsensitiv sein|safety-first#Context-Aware Escaping]. Und das kann Latte. Es versteht HTML. Es sieht die Vorlage nicht nur als Zeichenkette, sondern versteht, was Tags, Attribute usw. sind. Daher escapet es im HTML-Text, innerhalb eines HTML-Tags, innerhalb von JavaScript usw. unterschiedlich. + +Latte ist das erste und einzige Vorlagensystem in PHP, das kontextsensitives Escaping bietet. Es ist das einzige wirklich sichere Vorlagensystem. + +*Und noch ein angenehmer Grund:* Da Latte HTML versteht, bietet es einige sehr angenehme Extras. Zum Beispiel [n:attribute|syntax#n:attributes]. Oder die Fähigkeit, [Links zu überprüfen|safety-first#Link checking]. Und vieles mehr. + + +Was ist Escaping? .[#toc-what-is-escaping] +------------------------------------------ + +Escaping ist ein Prozess, bei dem Zeichen mit besonderer Bedeutung durch entsprechende Sequenzen ersetzt werden, wenn eine Zeichenfolge in eine andere eingefügt wird, um unerwünschte Effekte oder Fehler zu vermeiden. Wenn wir beispielsweise eine Zeichenkette in einen HTML-Text einfügen, in dem das Zeichen `<` eine besondere Bedeutung hat, weil es den Anfang eines Tags anzeigt, ersetzen wir es durch die entsprechende Sequenz, nämlich die HTML-Entität `<`. Dadurch kann der Browser das Symbol `<` korrekt anzeigen. + +Ein einfaches Beispiel für das direkte Escaping beim Schreiben von PHP-Code ist das Einfügen eines Anführungszeichens in eine Zeichenkette, indem ein Backslash davor gesetzt wird. + +Wir besprechen Escaping ausführlicher in dem Kapitel [Wie man sich gegen XSS verteidigt |safety-first#How to Defend Against XSS?]. + + +Kann eine Datenbankabfrage von einer Latte-Vorlage aus ausgeführt werden? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +---------------------------------------------------------------------------------------------------------------------------------------- + +In Vorlagen können Sie mit Objekten arbeiten, die der Programmierer an sie übergibt. Wenn der Programmierer das möchte, kann er ein Datenbankobjekt an die Vorlage übergeben und eine Abfrage durchführen. Wenn er dies beabsichtigt, gibt es keinen Grund, ihn daran zu hindern. + +Anders verhält es sich, wenn Sie Kunden oder externen Programmierern die Möglichkeit geben wollen, Vorlagen zu bearbeiten. In diesem Fall wollen Sie auf keinen Fall, dass sie Zugriff auf die Datenbank haben. Natürlich werden Sie das Datenbankobjekt nicht an die Vorlage übergeben, aber was ist, wenn über ein anderes Objekt darauf zugegriffen werden kann? Die Lösung ist der [Sandbox-Modus |sandbox], mit dem Sie festlegen können, welche Methoden in Vorlagen aufgerufen werden können. So müssen Sie sich keine Sorgen über Sicherheitslücken machen. + + +Was sind die Hauptunterschiede zwischen Templating-Systemen wie Latte, Twig und Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Die Unterschiede zwischen Templating-Systemen wie Latte, Twig und Blade liegen hauptsächlich in der Syntax, der Sicherheit und der Integration mit Frameworks: + +- Latte: verwendet die Syntax der PHP-Sprache und ist daher leichter zu erlernen und zu verwenden. Es bietet einen erstklassigen Schutz gegen XSS-Angriffe. +- Twig: verwendet eine Python-ähnliche Syntax, die sich deutlich von PHP unterscheidet. Es bricht ohne Kontextunterscheidung aus. Es ist gut mit dem Symfony-Framework integriert. +- Blade: verwendet eine Mischung aus PHP und benutzerdefinierter Syntax. Es bricht ohne Kontextunterscheidung aus. Es ist eng mit den Funktionen und dem Ökosystem von Laravel integriert. + + +Lohnt es sich für Unternehmen, ein Templating-System zu verwenden? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +---------------------------------------------------------------------------------------------------------------------------------- + +Zunächst einmal sind die Kosten für die Schulung, die Nutzung und den Gesamtnutzen je nach System sehr unterschiedlich. Das Templating-System Latte vereinfacht dank der Verwendung der PHP-Syntax das Lernen für Programmierer, die mit dieser Sprache bereits vertraut sind, erheblich. In der Regel benötigt ein Programmierer nur wenige Stunden, um sich ausreichend mit Latte vertraut zu machen, was die Schulungskosten senkt und die Übernahme der Technologie und vor allem die Effizienz im täglichen Gebrauch beschleunigt. + +Darüber hinaus bietet Latte dank seiner einzigartigen kontextabhängigen Escape-Technologie ein hohes Maß an Schutz vor XSS-Schwachstellen. Dieser Schutz ist entscheidend, um die Sicherheit von Webanwendungen zu gewährleisten und das Risiko von Angriffen zu minimieren, die Benutzer oder Unternehmensdaten gefährden könnten. Die Sicherheit von Webanwendungen ist auch wichtig für die Aufrechterhaltung des guten Rufs eines Unternehmens. Sicherheitsprobleme können zu einem Vertrauensverlust bei den Kunden führen und den Ruf des Unternehmens auf dem Markt schädigen. + +Die Verwendung von Latte senkt auch die Gesamtkosten für Entwicklung und Wartung, da beides einfacher wird. Daher lohnt sich der Einsatz eines Templating-Systems auf jeden Fall. + + +Beeinträchtigt Latte die Leistung von Webanwendungen? .[#toc-does-latte-affect-the-performance-of-web-applications] +------------------------------------------------------------------------------------------------------------------- + +Obwohl Latte-Vorlagen schnell verarbeitet werden, ist dieser Aspekt nicht wirklich von Bedeutung. Der Grund dafür ist, dass das Parsen der Dateien nur einmal bei der ersten Anzeige erfolgt. Anschließend werden sie in PHP-Code kompiliert, auf der Festplatte gespeichert und bei jeder nachfolgenden Anfrage ausgeführt, ohne dass eine erneute Kompilierung erforderlich ist. + +So funktioniert es auch in einer Produktionsumgebung. Während der Entwicklung werden die Latte-Vorlagen jedes Mal neu kompiliert, wenn sich ihr Inhalt ändert, so dass der Entwickler immer die aktuelle Version sieht. diff --git a/latte/el/@left-menu.texy b/latte/el/@left-menu.texy index a05bc96ad4..b0beae99e9 100644 --- a/latte/el/@left-menu.texy +++ b/latte/el/@left-menu.texy @@ -1,4 +1,5 @@ - [Ξεκινώντας |Guide] +- [Γιατί να χρησιμοποιήσετε πρότυπα; |why-use] - Έννοιες - [Πρώτα η ασφάλεια |Safety First] - [Κληρονομικότητα προτύπων |Template Inheritance] diff --git a/latte/el/cookbook/@home.texy b/latte/el/cookbook/@home.texy index 0f70d85cd2..ef2031d915 100644 --- a/latte/el/cookbook/@home.texy +++ b/latte/el/cookbook/@home.texy @@ -1,6 +1,9 @@ Βιβλίο μαγειρικής ***************** +.[perex] +Παραδείγματα κωδικών και συνταγών για την εκτέλεση κοινών εργασιών με το Latte. + - [Όλα όσα πάντα θέλατε να ξέρετε για το {iterateWhile} |iteratewhile] - [Πώς να γράψετε ερωτήματα SQL στο Latte? |how-to-write-sql-queries-in-latte] - [Μετανάστευση από PHP |migration-from-php] diff --git a/latte/el/tags.texy b/latte/el/tags.texy index f64dc85fc8..d52d64f598 100644 --- a/latte/el/tags.texy +++ b/latte/el/tags.texy @@ -102,7 +102,7 @@ .[table-latte-tags language-latte] |## Διαθέσιμο μόνο με Nette Forms -| `{form}`... `{/form}` | [εκτυπώνει ένα στοιχείο φόρμας |forms:rendering#latte] +| `{form}`... `{/form}` | [εκτυπώνει ένα στοιχείο φόρμας |forms:rendering#form] | `{label}`... `{/label}` | [εκτυπώνει μια ετικέτα εισόδου φόρμας |forms:rendering#label-input] | `{input}` | [εκτυπώνει ένα στοιχείο εισόδου φόρμας |forms:rendering#label-input] | `{inputError}` | [εκτυπώνει μήνυμα σφάλματος για το στοιχείο εισόδου φόρμας |forms:rendering#inputError] @@ -110,6 +110,7 @@ | `{formPrint}` | [δημιουργεί σχέδιο φόρμας Latte |forms:rendering#formPrint] | `{formPrintClass}` | [εκτυπώνει κλάση PHP για τα δεδομένα της φόρμας |forms:in-presenter#mapping-to-classes] | `{formContext}`... `{/formContext}` | [μερική απόδοση φόρμας |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [απόδοση του δοχείου της φόρμας |forms:rendering#special-cases] Εκτύπωση .[#toc-printing] @@ -595,7 +596,7 @@ Age: {date('Y') - $birth}<br> Τα περιεχόμενα πρότυπα δεν έχουν πρόσβαση στις μεταβλητές του ενεργού πλαισίου, αλλά έχουν πρόσβαση στις παγκόσμιες μεταβλητές. -Μπορείτε να περάσετε μεταβλητές με αυτόν τον τρόπο: +Μπορείτε να περάσετε μεταβλητές στο εισαγόμενο πρότυπο με τον ακόλουθο τρόπο: ```latte {* από Latte 2.9 *} diff --git a/latte/el/template-inheritance.texy b/latte/el/template-inheritance.texy index eece8ef222..975286428d 100644 --- a/latte/el/template-inheritance.texy +++ b/latte/el/template-inheritance.texy @@ -246,7 +246,7 @@ bar: {$bar ?? 'not defined'} // prints: not defined εκτός αν το μπλοκ έχει οριστεί στο ίδιο αρχείο όπου περιλαμβάνεται. Ωστόσο, έχουν πρόσβαση στις παγκόσμιες μεταβλητές. -Μπορείτε να περάσετε μεταβλητές με αυτόν τον τρόπο: +Μπορείτε να περάσετε μεταβλητές στο μπλοκ με τον ακόλουθο τρόπο: ```latte {* από Latte 2.9 *} @@ -603,7 +603,7 @@ Hi, I am Mary. ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/el/why-use.texy b/latte/el/why-use.texy new file mode 100644 index 0000000000..1f0085843a --- /dev/null +++ b/latte/el/why-use.texy @@ -0,0 +1,80 @@ +Γιατί να χρησιμοποιήσετε πρότυπα; +********************************* + + +Γιατί πρέπει να χρησιμοποιήσω ένα σύστημα δημιουργίας προτύπων στην PHP; .[#toc-why-should-i-use-a-templating-system-in-php] +---------------------------------------------------------------------------------------------------------------------------- + +Γιατί να χρησιμοποιήσετε ένα σύστημα προτύπων στην PHP όταν η ίδια η PHP είναι μια γλώσσα δημιουργίας προτύπων; + +Ας ανακεφαλαιώσουμε πρώτα εν συντομία την ιστορία αυτής της γλώσσας, η οποία είναι γεμάτη από ενδιαφέρουσες ανατροπές. Μια από τις πρώτες γλώσσες προγραμματισμού που χρησιμοποιήθηκαν για τη δημιουργία σελίδων HTML ήταν η γλώσσα C. Ωστόσο, σύντομα έγινε φανερό ότι η χρήση της για το σκοπό αυτό ήταν μη πρακτική. Έτσι, ο Rasmus Lerdorf δημιούργησε την PHP, η οποία διευκόλυνε τη δημιουργία δυναμικής HTML με τη γλώσσα C στο backend. Η PHP σχεδιάστηκε αρχικά ως γλώσσα δημιουργίας προτύπων, αλλά με την πάροδο του χρόνου απέκτησε πρόσθετα χαρακτηριστικά και έγινε μια ολοκληρωμένη γλώσσα προγραμματισμού. + +Παρ' όλα αυτά, εξακολουθεί να λειτουργεί ως γλώσσα διαμόρφωσης προτύπων. Ένα αρχείο PHP μπορεί να περιέχει μια σελίδα HTML, στην οποία οι μεταβλητές εξάγονται χρησιμοποιώντας `<?= $foo ?>`, κ.λπ. + +Στις αρχές της ιστορίας της PHP, δημιουργήθηκε το σύστημα προτύπων Smarty, με σκοπό τον αυστηρό διαχωρισμό της εμφάνισης (HTML/CSS) από τη λογική της εφαρμογής. Σκόπιμα παρείχε μια πιο περιορισμένη γλώσσα από την ίδια την PHP, έτσι ώστε, για παράδειγμα, ένας προγραμματιστής να μην μπορεί να κάνει ένα ερώτημα στη βάση δεδομένων από ένα πρότυπο, κλπ. Από την άλλη πλευρά, αντιπροσώπευε μια πρόσθετη εξάρτηση στα έργα, αύξανε την πολυπλοκότητά τους και απαιτούσε από τους προγραμματιστές να μάθουν μια νέα γλώσσα Smarty. Τα οφέλη αυτά ήταν αμφιλεγόμενα και η απλή PHP συνέχισε να χρησιμοποιείται για τα πρότυπα. + +Με την πάροδο του χρόνου, τα συστήματα προτύπων άρχισαν να γίνονται χρήσιμα. Εισήγαγαν έννοιες όπως η [κληρονομικότητα |template-inheritance], η [λειτουργία sandbox |sandbox] και μια σειρά από άλλα χαρακτηριστικά που απλοποιούσαν σημαντικά τη δημιουργία προτύπων σε σύγκριση με την καθαρή PHP. Το θέμα της ασφάλειας, η ύπαρξη [ευπαθειών όπως το XSS |safety-first] και η ανάγκη για [escaping |#What is escaping] ήρθαν στο προσκήνιο. Τα συστήματα προτύπων εισήγαγαν την αυτόματη διαφυγή για να εξαλείψουν τον κίνδυνο να την ξεχάσει ένας προγραμματιστής και να δημιουργήσει ένα σοβαρό κενό ασφαλείας (θα δούμε σύντομα ότι αυτό έχει ορισμένες παγίδες). + +Σήμερα, τα οφέλη των συστημάτων προτύπων υπερτερούν κατά πολύ του κόστους που συνδέεται με την ανάπτυξή τους. Ως εκ τούτου, έχει νόημα η χρήση τους. + + +Γιατί το Latte είναι καλύτερο από το Twig ή το Blade; .[#toc-why-is-latte-better-than-twig-or-blade] +---------------------------------------------------------------------------------------------------- + +Υπάρχουν διάφοροι λόγοι - ορισμένοι είναι ευχάριστοι και άλλοι είναι εξαιρετικά χρήσιμοι. Το Latte είναι ένας συνδυασμός ευχάριστου και χρήσιμου. + +*Πρώτα, το ευχάριστο:* Το Latte έχει την ίδια [σύνταξη με την PHP |syntax#Latte Understands PHP]. Η μόνη διαφορά είναι στη σημειογραφία των ετικετών, προτιμώντας τα συντομότερα `{` και `}` αντί των `<?=` και `?>`. Αυτό σημαίνει ότι δεν χρειάζεται να μάθετε μια νέα γλώσσα. Το κόστος εκπαίδευσης είναι ελάχιστο. Το σημαντικότερο, κατά τη διάρκεια της ανάπτυξης, δεν χρειάζεται να "αλλάζετε" συνεχώς μεταξύ της γλώσσας PHP και της γλώσσας προτύπων, αφού και οι δύο είναι ίδιες. Αυτό συμβαίνει σε αντίθεση με τα πρότυπα Twig, τα οποία χρησιμοποιούν τη γλώσσα Python, αναγκάζοντας τον προγραμματιστή να εναλλάσσεται μεταξύ δύο διαφορετικών γλωσσών. + +*Και τώρα για τον εξαιρετικά χρήσιμο λόγο:* Όλα τα συστήματα προτύπων, όπως το Twig, το Blade ή το Smarty, έχουν εξελιχθεί ώστε να περιλαμβάνουν προστασία κατά του XSS με τη μορφή αυτόματης [διαφυγής |#What is escaping]. Πιο συγκεκριμένα, την αυτόματη κλήση της συνάρτησης `htmlspecialchars()`. Ωστόσο, οι δημιουργοί του Latte συνειδητοποίησαν ότι αυτή δεν είναι καθόλου η σωστή λύση. Αυτό συμβαίνει επειδή διαφορετικά μέρη του εγγράφου απαιτούν διαφορετικές μεθόδους διαφυγής. Η αφελής αυτόματη διαφυγή είναι ένα επικίνδυνο χαρακτηριστικό, επειδή δημιουργεί μια ψευδή αίσθηση ασφάλειας. + +Για να είναι λειτουργική και αξιόπιστη η αυτόματη απομάκρυνση, πρέπει να αναγνωρίζει πού στο έγγραφο εξάγονται τα δεδομένα (τα ονομάζουμε πλαίσια) και να επιλέγει τη λειτουργία απομάκρυνσης αναλόγως. Επομένως, πρέπει να είναι [ευαίσθητη στα συμφραζόμενα |safety-first#Context-Aware Escaping]. Και αυτό είναι που μπορεί να κάνει η Latte. Κατανοεί την HTML. Δεν αντιλαμβάνεται το πρότυπο ως μια απλή συμβολοσειρά χαρακτήρων, αλλά καταλαβαίνει τι είναι οι ετικέτες, τα χαρακτηριστικά κ.λπ. Ως εκ τούτου, αποφεύγει με διαφορετικό τρόπο το κείμενο HTML, μέσα σε ετικέτες HTML, μέσα σε JavaScript κ.λπ. + +Το Latte είναι το πρώτο και μοναδικό σύστημα προτύπων PHP με διαφυγή με ευαισθησία περιβάλλοντος. Αποτελεί το μόνο πραγματικά ασφαλές σύστημα προτύπων. + +*Και ένας άλλος ευχάριστος λόγος:* Επειδή το Latte καταλαβαίνει την HTML, προσφέρει και άλλα πολύ ευχάριστα χαρακτηριστικά. Για παράδειγμα, το [n:attributes |syntax#n:attributes]. Ή τη δυνατότητα [ελέγχου των συνδέσμων |safety-first#Link checking]. Και πολλά άλλα. + + +Τι είναι η διαφυγή; .[#toc-what-is-escaping] +-------------------------------------------- + +Η διαφυγή είναι μια διαδικασία που περιλαμβάνει την αντικατάσταση χαρακτήρων με ειδικές σημασίες με αντίστοιχες ακολουθίες κατά την εισαγωγή μιας συμβολοσειράς σε μια άλλη για την αποφυγή ανεπιθύμητων αποτελεσμάτων ή σφαλμάτων. Για παράδειγμα, κατά την εισαγωγή μιας συμβολοσειράς σε κείμενο HTML, στην οποία ο χαρακτήρας `<` έχει ειδική σημασία επειδή υποδηλώνει την αρχή μιας ετικέτας, τον αντικαθιστούμε με την αντίστοιχη ακολουθία, η οποία είναι η οντότητα HTML `<`. Αυτό επιτρέπει στο πρόγραμμα περιήγησης να εμφανίζει σωστά το σύμβολο `<`. + +Ένα απλό παράδειγμα άμεσης διαφυγής κατά τη συγγραφή κώδικα PHP είναι η εισαγωγή ενός εισαγωγικού σήματος σε μια συμβολοσειρά τοποθετώντας μια backslash μπροστά του. + +Συζητάμε το escaping με περισσότερες λεπτομέρειες στο κεφάλαιο [Πώς να αμυνθείτε κατά του XSS |safety-first#How to Defend Against XSS?]. + + +Μπορεί να εκτελεστεί ένα ερώτημα βάσης δεδομένων από ένα πρότυπο Latte; .[#toc-can-a-database-query-be-executed-from-a-latte-template] +-------------------------------------------------------------------------------------------------------------------------------------- + +Στα πρότυπα, μπορείτε να εργάζεστε με αντικείμενα που τους περνάει ο προγραμματιστής. Αν ο προγραμματιστής το επιθυμεί, μπορεί να περάσει ένα αντικείμενο βάσης δεδομένων στο πρότυπο και να εκτελέσει ένα ερώτημα. Αν σκοπεύουν να το κάνουν, δεν υπάρχει λόγος να τους εμποδίσουμε. + +Μια διαφορετική κατάσταση προκύπτει αν θέλετε να δώσετε σε πελάτες ή εξωτερικούς προγραμματιστές τη δυνατότητα να επεξεργάζονται πρότυπα. Σε αυτή την περίπτωση, σίγουρα δεν θέλετε να έχουν πρόσβαση στη βάση δεδομένων. Φυσικά, δεν θα περάσετε το αντικείμενο της βάσης δεδομένων στο πρότυπο, αλλά τι γίνεται αν μπορεί να γίνει πρόσβαση σε αυτό μέσω ενός άλλου αντικειμένου; Η λύση είναι η [λειτουργία sandbox |sandbox], η οποία σας επιτρέπει να ορίσετε ποιες μέθοδοι μπορούν να κληθούν στα πρότυπα. Χάρη σε αυτό, δεν χρειάζεται να ανησυχείτε για παραβιάσεις της ασφάλειας. + + +Ποιες είναι οι κύριες διαφορές μεταξύ συστημάτων template όπως το Latte, το Twig και το Blade; .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Οι διαφορές μεταξύ συστημάτων διαμόρφωσης προτύπων όπως το Latte, το Twig και το Blade έγκεινται κυρίως στη σύνταξη, την ασφάλεια και την ενσωμάτωση με πλαίσια: + +- Latte: χρησιμοποιεί σύνταξη της γλώσσας PHP, καθιστώντας την ευκολότερη στην εκμάθηση και τη χρήση. Παρέχει κορυφαία προστασία από επιθέσεις XSS. +- Twig: χρησιμοποιεί σύνταξη που μοιάζει με Python, η οποία είναι αρκετά διαφορετική από την PHP. Διαφεύγει χωρίς διάκριση πλαισίου. Είναι καλά ενσωματωμένο με το πλαίσιο Symfony. +- Blade: χρησιμοποιεί ένα μείγμα σύνταξης PHP και προσαρμοσμένης σύνταξης. Διαφεύγει χωρίς διάκριση πλαισίου. Είναι στενά ενσωματωμένο με τα χαρακτηριστικά και το οικοσύστημα του Laravel. + + +Αξίζει τον κόπο για τις εταιρείες να χρησιμοποιούν ένα σύστημα templating; .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +------------------------------------------------------------------------------------------------------------------------------------------ + +Πρώτον, το κόστος που σχετίζεται με την εκπαίδευση, τη χρήση και τα συνολικά οφέλη ποικίλλει σημαντικά ανάλογα με το σύστημα. Το σύστημα δημιουργίας προτύπων Latte, χάρη στη χρήση του συντακτικού της PHP, απλοποιεί σημαντικά την εκμάθηση για τους προγραμματιστές που είναι ήδη εξοικειωμένοι με αυτή τη γλώσσα. Συνήθως χρειάζονται λίγες ώρες για να εξοικειωθεί επαρκώς ένας προγραμματιστής με το Latte, μειώνοντας το κόστος εκπαίδευσης και επιταχύνοντας την υιοθέτηση της τεχνολογίας και, κυρίως, την αποτελεσματικότητα στην καθημερινή χρήση. + +Επιπλέον, το Latte παρέχει υψηλό επίπεδο προστασίας από την ευπάθεια XSS χάρη στη μοναδική τεχνολογία διαφυγής με επίγνωση του περιβάλλοντος. Αυτή η προστασία είναι ζωτικής σημασίας για τη διασφάλιση της ασφάλειας των εφαρμογών ιστού και την ελαχιστοποίηση του κινδύνου επιθέσεων που θα μπορούσαν να θέσουν σε κίνδυνο τους χρήστες ή τα δεδομένα της εταιρείας. Η ασφάλεια εφαρμογών ιστού είναι επίσης σημαντική για τη διατήρηση της καλής φήμης μιας εταιρείας. Τα ζητήματα ασφάλειας μπορεί να οδηγήσουν σε απώλεια εμπιστοσύνης από τους πελάτες και να βλάψουν τη φήμη της εταιρείας στην αγορά. + +Η χρήση του Latte μειώνει επίσης το συνολικό κόστος ανάπτυξης και συντήρησης, καθώς διευκολύνει και τα δύο. Επομένως, η χρήση ενός συστήματος template αξίζει σίγουρα τον κόπο. + + +Επηρεάζει το Latte την απόδοση των εφαρμογών ιστού; .[#toc-does-latte-affect-the-performance-of-web-applications] +----------------------------------------------------------------------------------------------------------------- + +Παρόλο που τα πρότυπα Latte επεξεργάζονται γρήγορα, αυτή η πτυχή δεν έχει πραγματικά σημασία. Ο λόγος είναι ότι η ανάλυση των αρχείων γίνεται μόνο μία φορά κατά την πρώτη εμφάνιση. Στη συνέχεια μεταγλωττίζονται σε κώδικα PHP, αποθηκεύονται στο δίσκο και εκτελούνται σε κάθε επόμενη αίτηση χωρίς να απαιτείται εκ νέου μεταγλώττιση. + +Έτσι λειτουργεί σε ένα περιβάλλον παραγωγής. Κατά τη διάρκεια της ανάπτυξης, τα πρότυπα Latte μεταγλωττίζονται εκ νέου κάθε φορά που αλλάζει το περιεχόμενό τους, ώστε ο προγραμματιστής να βλέπει πάντα την τρέχουσα έκδοση. diff --git a/latte/en/@left-menu.texy b/latte/en/@left-menu.texy index 0b903a9480..3b1d3a0474 100644 --- a/latte/en/@left-menu.texy +++ b/latte/en/@left-menu.texy @@ -1,4 +1,5 @@ - [Getting Started |Guide] +- [Why Use Templates? |why-use] - Concepts - [Safety First] - [Template Inheritance] diff --git a/latte/en/cookbook/@home.texy b/latte/en/cookbook/@home.texy index 3b989ed46c..cea48d6e0f 100644 --- a/latte/en/cookbook/@home.texy +++ b/latte/en/cookbook/@home.texy @@ -1,6 +1,9 @@ Cookbook ******** +.[perex] +Example codes and recipes for accomplishing common tasks with Latte. + - [Everything You Always Wanted to Know About {iterateWhile} |iteratewhile] - [How to write SQL queries in Latte? |how-to-write-sql-queries-in-latte] - [Migration from Latte 2 |migration-from-latte2] diff --git a/latte/en/tags.texy b/latte/en/tags.texy index 68bd4f0714..8540afb146 100644 --- a/latte/en/tags.texy +++ b/latte/en/tags.texy @@ -102,7 +102,7 @@ Summary and description of all Latte built-in tags. .[table-latte-tags language-latte] |## Available only with Nette Forms -| `{form}` … `{/form}` | [prints a form element |forms:rendering#latte] +| `{form}` … `{/form}` | [prints a form element |forms:rendering#form] | `{label}` … `{/label}` | [prints a form input label |forms:rendering#label-input] | `{input}` | [prints a form input element |forms:rendering#label-input] | `{inputError}` | [prints error message for form input element|forms:rendering#inputError] @@ -110,6 +110,7 @@ Summary and description of all Latte built-in tags. | `{formPrint}` | [generates Latte form blueprint |forms:rendering#formPrint] | `{formPrintClass}` | [prints PHP class for form data |forms:in-presenter#mapping-to-classes] | `{formContext}` … `{/formContext}` | [partial form rendering |forms:rendering#special-cases] +| `{formContainer}` … `{/formContainer}` | [rendering the form container |forms:rendering#special-cases] Printing @@ -595,7 +596,7 @@ The `{include}` tag loads and renders the specified template. In our favorite PH Included templates have not access to the variables of the active context, but have access to the global variables. -You can pass variables this way: +You can pass variables to the inserted template in the following way: ```latte {* since Latte 2.9 *} diff --git a/latte/en/template-inheritance.texy b/latte/en/template-inheritance.texy index 7a5559d0ee..7fd0c769de 100644 --- a/latte/en/template-inheritance.texy +++ b/latte/en/template-inheritance.texy @@ -246,7 +246,7 @@ You can also display block from another template: Printed block have not access to the variables of the active context, except if the block is defined in the same file where it is included. However they have access to the global variables. -You can pass variables this way: +You can pass variables to the block in the following way: ```latte {* since Latte 2.9 *} @@ -603,7 +603,7 @@ There are various types of inheritance and code reuse in Latte. Let's summarize ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/en/why-use.texy b/latte/en/why-use.texy new file mode 100644 index 0000000000..4e1a6280da --- /dev/null +++ b/latte/en/why-use.texy @@ -0,0 +1,80 @@ +Why Use Templates? +****************** + + +Why should I use a templating system in PHP? +-------------------------------------------- + +Why use a template system in PHP when PHP itself is a templating language? + +Let's first briefly recap the history of this language, which is full of interesting twists and turns. One of the first programming languages used for generating HTML pages was the C language. However, it soon became apparent that using it for this purpose was impractical. Rasmus Lerdorf thus created PHP, which facilitated the generation of dynamic HTML with the C language on the backend. PHP was originally designed as a templating language, but over time it acquired additional features and became a fully-fledged programming language. + +Nevertheless, it still functions as a templating language. A PHP file can contain an HTML page, in which variables are output using `<?= $foo ?>`, etc. + +Early in PHP's history, the Smarty template system was created, with the purpose of strictly separating the appearance (HTML/CSS) from the application logic. It deliberately provided a more limited language than PHP itself, so that, for example, a developer could not make a database query from a template, etc. On the other hand, it represented an additional dependency in projects, increased their complexity, and required programmers to learn a new Smarty language. Such benefits were controversial, and plain PHP continued to be used for templates. + +Over time, template systems began to become useful. They introduced concepts such as [inheritance|template-inheritance], [sandbox mode|sandbox], and a range of other features that significantly simplified template creation compared to pure PHP. The topic of security, the existence of [vulnerabilities like XSS|safety-first], and the need for [escaping|#What is escaping] came to the forefront. Template systems introduced auto-escaping to eliminate the risk of a programmer forgetting it and creating a serious security hole (we'll see shortly that this has certain pitfalls). + +Today, the benefits of template systems far outweigh the costs associated with their deployment. Therefore, it makes sense to use them. + + +Why is Latte better than Twig or Blade? +--------------------------------------- + +There are several reasons - some are pleasant and others are immensely useful. Latte is a combination of pleasant and useful. + +*First, the pleasant:* Latte has the same [syntax as PHP|syntax#Latte Understands PHP]. The only difference is in the notation of tags, preferring shorter `{` and `}` instead of `<?=` and `?>`. This means that you don't have to learn a new language. Training costs are minimal. Most importantly, during development, you don't have to constantly "switch" between the PHP language and the template language, since they are both the same. This is unlike Twig templates, which use the Python language, forcing the programmer to switch between two different languages. + +*Now for the immensely useful reason:* All template systems, like Twig, Blade, or Smarty, have evolved to include protection against XSS in the form of automatic [escaping|#What is escaping]. More precisely, the automatic calling of the `htmlspecialchars()` function. However, the creators of Latte realized that this is not the right solution at all. This is because different parts of the document require different escaping methods. Naive auto-escaping is a dangerous feature because it creates a false sense of security. + +For auto-escaping to be functional and reliable, it must recognize where in the document the data is being output (we call these contexts) and choose the escaping function accordingly. Therefore, it must be [context-sensitive|safety-first#Context-Aware Escaping]. And this is what Latte can do. It understands HTML. It doesn't perceive the template as just a string of characters but understands what tags, attributes, etc., are. Therefore, it escapes differently in HTML text, within HTML tags, inside JavaScript, etc. + +Latte is the first and only PHP template system with context-sensitive escaping. It represents the only truly secure template system. + +*And another pleasant reason:* Because Latte understands HTML, it offers other very pleasant features. For example, [n:attributes|syntax#n:attributes]. Or the ability to [check links|safety-first#Link checking]. And many more. + + +What is escaping? +----------------- + +Escaping is a process that involves replacing characters with special meanings with corresponding sequences when inserting one string into another to prevent unwanted effects or errors. For example, when inserting a string into HTML text, in which the character `<` has a special meaning because it indicates the beginning of a tag, we replace it with the corresponding sequence, which is the HTML entity `<`. This allows the browser to correctly display `<` symbol. + +A simple example of escaping directly when writing PHP code is inserting a quotation mark into a string by placing a backslash in front of it. + +We discuss escaping in more detail in the chapter [How to defend against XSS|safety-first#How to Defend Against XSS?]. + + +Can a database query be executed from a Latte template? +------------------------------------------------------- + +In templates, you can work with objects that the programmer passes to them. If the programmer wants to, they can pass a database object to the template and perform a query. If they intend to do so, there is no reason to prevent them. + +A different situation arises if you want to give clients or external coders the ability to edit templates. In this case, you definitely don't want them to have access to the database. Of course, you won't pass the database object to the template, but what if it can be accessed through another object? The solution is the [sandbox mode|sandbox], which allows you to define which methods can be called in templates. Thanks to this, you don't have to worry about security breaches. + + +What are the main differences between templating systems like Latte, Twig, and Blade? +------------------------------------------------------------------------------------- + +The differences between templating systems like Latte, Twig, and Blade mainly lie in their syntax, security, and integration with frameworks: + +- Latte: uses PHP language syntax, making it easier to learn and use. It provides top-notch protection against XSS attacks. +- Twig: uses Python-like syntax, which is quite different from PHP. It escapes without context distinction. It is well integrated with the Symfony framework. +- Blade: uses a mix of PHP and custom syntax. It escapes without context distinction. It is tightly integrated with Laravel features and ecosystem. + + +Is it worth it for companies to use a templating system? +-------------------------------------------------------- + +Firstly, the costs associated with training, usage, and overall benefits vary significantly depending on the system. The Latte templating system, thanks to its use of PHP syntax, greatly simplifies learning for programmers already familiar with this language. It usually takes a few hours for a programmer to become sufficiently acquainted with Latte, reducing training costs and accelerating the adoption of technology and, most importantly, efficiency in daily use. + +Additionally, Latte provides a high level of protection against XSS vulnerability thanks to its unique context-aware escaping technology. This protection is crucial for ensuring web application security and minimizing the risk of attacks that could endanger users or company data. Web application security is also important for maintaining a company's good reputation. Security issues can lead to loss of trust from customers and damage the company's reputation in the market. + +Using Latte also reduces overall development and maintenance costs by making both easier. Therefore, using a templating system is definitely worth it. + + +Does Latte affect the performance of web applications? +------------------------------------------------------ + +Although Latte templates are processed quickly, this aspect does not really matter. The reason is that parsing files occurs only once during the first display. They are then compiled into PHP code, stored on disk, and run on every subsequent request without requiring recompilation. + +This is how it works in a production environment. During development, Latte templates are recompiled every time their content changes, so the developer always sees the current version. diff --git a/latte/es/@left-menu.texy b/latte/es/@left-menu.texy index 6ab06af07a..9b1db0abb8 100644 --- a/latte/es/@left-menu.texy +++ b/latte/es/@left-menu.texy @@ -1,4 +1,5 @@ - [Primeros pasos |Guide] +- [¿Por qué utilizar plantillas? |why-use] - Conceptos - [La seguridad ante todo |Safety First] - [Herencia de plantillas |Template Inheritance] diff --git a/latte/es/cookbook/@home.texy b/latte/es/cookbook/@home.texy index b7f423df48..ca75fda004 100644 --- a/latte/es/cookbook/@home.texy +++ b/latte/es/cookbook/@home.texy @@ -1,6 +1,9 @@ Libro de cocina *************** +.[perex] +Códigos de ejemplo y recetas para realizar tareas comunes con Latte. + - [Todo lo que siempre quiso saber sobre {iterateWhile} |iteratewhile] - [¿Cómo escribir consultas SQL en Latte? |how-to-write-sql-queries-in-latte] - [Migración desde PHP |migration-from-php] diff --git a/latte/es/tags.texy b/latte/es/tags.texy index bf5ad7edc8..75b35671da 100644 --- a/latte/es/tags.texy +++ b/latte/es/tags.texy @@ -102,7 +102,7 @@ Resumen y descripción de todas las etiquetas Latte incorporadas. .[table-latte-tags language-latte] |## Disponible sólo con Nette Forms -| `{form}`... `{/form}` | [imprime un elemento de formulario|forms:rendering#latte] +| `{form}`... `{/form}` | [imprime un elemento de formulario|forms:rendering#form] | `{label}`... `{/label}` | [imprime una etiqueta de entrada de formulario|forms:rendering#label-input] | `{input}` | [imprime un elemento del formulario|forms:rendering#label-input] | `{inputError}` | [imprime un mensaje de error para el elemento de entrada del formulario|forms:rendering#inputError] @@ -110,6 +110,7 @@ Resumen y descripción de todas las etiquetas Latte incorporadas. | `{formPrint}` | [genera un modelo de formulario Latte |forms:rendering#formPrint] | `{formPrintClass}` | [imprime la clase PHP para los datos del formulario |forms:in-presenter#mapping-to-classes] | `{formContext}`... `{/formContext}` | [renderización parcial del formulario|forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [renderización del contenedor de formularios |forms:rendering#special-cases] Impresión .[#toc-printing] @@ -595,7 +596,7 @@ La etiqueta `{include}` carga y renderiza la plantilla especificada. En nuestro Las plantillas incluidas no tienen acceso a las variables del contexto activo, pero tienen acceso a las variables globales. -Puede pasar variables de esta manera: +Puede pasar variables a la plantilla insertada de la siguiente manera: ```latte {* since Latte 2.9 *} diff --git a/latte/es/template-inheritance.texy b/latte/es/template-inheritance.texy index b36ab94df3..f4a540f15a 100644 --- a/latte/es/template-inheritance.texy +++ b/latte/es/template-inheritance.texy @@ -246,7 +246,7 @@ También puede mostrar bloques de otra plantilla: Los bloques impresos no tienen acceso a las variables del contexto activo, excepto si el bloque está definido en el mismo fichero donde se incluye. Sin embargo tienen acceso a las variables globales. -Puede pasar variables de esta forma: +Puede pasar variables al bloque de la siguiente manera: ```latte {* since Latte 2.9 *} @@ -603,7 +603,7 @@ Existen varios tipos de herencia y reutilización de código en Latte. Vamos a r ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/es/why-use.texy b/latte/es/why-use.texy new file mode 100644 index 0000000000..a0e09223ec --- /dev/null +++ b/latte/es/why-use.texy @@ -0,0 +1,80 @@ +¿Por qué usar plantillas? +************************* + + +¿Por qué debería usar un sistema de plantillas en PHP? .[#toc-why-should-i-use-a-templating-system-in-php] +---------------------------------------------------------------------------------------------------------- + +¿Por qué usar un sistema de plantillas en PHP, cuando PHP en sí mismo es un lenguaje de plantillas? + +Primero, repasemos brevemente la historia de este lenguaje, que está llena de giros interesantes. Uno de los primeros lenguajes de programación utilizados para generar páginas HTML fue el lenguaje C. Sin embargo, pronto se demostró que su uso para este propósito era poco práctico. Por lo tanto, Rasmus Lerdorf creó PHP, que facilitó la generación de HTML dinámico con el lenguaje C en el backend. PHP fue diseñado originalmente como un lenguaje de plantillas, pero con el tiempo adquirió más funciones y se convirtió en un lenguaje de programación completo. + +A pesar de esto, PHP sigue siendo un lenguaje de plantillas. En un archivo PHP, se puede escribir una página HTML en la que las variables se imprimen utilizando `<?= $foo ?>`, etc. + +Desde los primeros días de la historia de PHP, se creó el sistema de plantillas Smarty, cuyo propósito era separar estrictamente la apariencia (HTML/CSS) de la lógica de la aplicación. Por lo tanto, proporcionó un lenguaje más limitado que PHP para evitar que el desarrollador, por ejemplo, realice consultas a la base de datos desde la plantilla. Por otro lado, introdujo una dependencia adicional en los proyectos, aumentó su complejidad y los programadores tuvieron que aprender un nuevo lenguaje, Smarty. El beneficio fue discutible y PHP siguió siendo utilizado para las plantillas. + +Con el tiempo, los sistemas de plantillas comenzaron a ser útiles. Introdujeron conceptos como [herencia de plantillas|template-inheritance], [modo sandbox|sandbox] y una serie de otras características que simplificaron significativamente la creación de plantillas en comparación con PHP puro. La seguridad y la existencia de [vulnerabilidades como XSS|safety-first] y la necesidad de [escapar|#What is escaping] se convirtieron en temas relevantes. Los sistemas de plantillas introdujeron el autoescapado para eliminar el riesgo de que el programador lo olvide y se cree una vulnerabilidad de seguridad grave (en breve veremos que esto tiene ciertos problemas). + +Hoy en día, los beneficios de los sistemas de plantillas superan con creces los costos asociados con su implementación. Por lo tanto, tiene sentido utilizarlos. + + +¿Por qué es mejor Latte que, por ejemplo, Twig o Blade? .[#toc-why-is-latte-better-than-twig-or-blade] +------------------------------------------------------------------------------------------------------ + +Hay varias razones, algunas son agradables y otras esencialmente útiles. Latte es una combinación de lo agradable y lo útil. + +*Primero, lo agradable:* Latte tiene la misma [sintaxis que PHP|syntax#Latte Understands PHP]. Sólo difiere en la notación de las etiquetas, en lugar de `<?=` y `?>`, prefiere `{` y `}` más cortos. Esto significa que no tienes que aprender un nuevo lenguaje. Los costos de capacitación son mínimos. Y lo más importante, durante el desarrollo no tienes que cambiar constantemente entre el lenguaje PHP y el lenguaje de plantillas, ya que ambos son iguales. A diferencia de las plantillas Twig, que utilizan el lenguaje Python, y el programador debe alternar entre dos lenguajes diferentes. + +*Y ahora una razón extremadamente útil:* Todos los sistemas de plantillas, como Twig, Blade o Smarty, han introducido protección contra XSS en forma de [escapado automático|#What is escaping]. Más precisamente, llamando automáticamente a la función `htmlspecialchars()`. Sin embargo, los creadores de Latte se dieron cuenta de que esto no es la solución correcta. Porque en diferentes partes del documento, se escapa de diferentes maneras. El escapado automático ingenuo es una función peligrosa porque crea una falsa sensación de seguridad. + +Para que el escapado automático sea funcional y confiable, debe reconocer en qué parte del documento se están imprimiendo los datos (llamamos a esto contextos) y elegir la función de escapado según eso. Por lo tanto, debe ser [sensible al contexto|safety-first#Context-Aware Escaping]. Y esto es precisamente lo que Latte puede hacer. Entiende HTML. No percibe la plantilla solo como una cadena de caracteres, sino que comprende qué son las etiquetas, los atributos, etc. Por lo tanto, escapa de manera diferente en el texto HTML, dentro de las etiquetas HTML, dentro de JavaScript, etc. + +Latte es el primer y único sistema de plantillas en PHP que tiene escapado sensible al contexto. Por lo tanto, representa el único sistema de plantillas verdaderamente seguro. + +*Y otra razón agradable:* Gracias a que Latte entiende HTML, ofrece otras características muy agradables. Por ejemplo, [n:atributos|syntax#n:attributes]. O la capacidad de [verificar enlaces|safety-first#Link checking]. Y muchas más. + + +¿Qué es el escape? .[#toc-what-is-escaping] +------------------------------------------- + +Escapar es un proceso que consiste en sustituir caracteres con significados especiales por las secuencias correspondientes al insertar una cadena en otra para evitar efectos no deseados o errores. Por ejemplo, al insertar una cadena en un texto HTML, en el que el carácter `<` tiene un significado especial porque indica el comienzo de una etiqueta, lo sustituimos por la secuencia correspondiente, que es la entidad HTML `<`. Esto permite al navegador mostrar correctamente el símbolo `<`. + +Un ejemplo sencillo de escape directo al escribir código PHP es insertar una comilla en una cadena colocando una barra invertida delante de ella. + +Discutimos el escape con más detalle en el capítulo [Cómo defenderse contra XSS |safety-first#How to Defend Against XSS?]. + + +¿Puede ejecutarse una consulta a la base de datos desde una plantilla Latte? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +------------------------------------------------------------------------------------------------------------------------------------------- + +En las plantillas se puede trabajar con objetos que el programador les pasa. Si el programador quiere, puede pasar un objeto de base de datos a la plantilla y realizar una consulta. Si tienen intención de hacerlo, no hay ninguna razón para impedírselo. + +Una situación diferente surge si desea dar a los clientes o codificadores externos la capacidad de editar plantillas. En este caso, definitivamente no querrá que tengan acceso a la base de datos. Por supuesto, usted no pasará el objeto de base de datos a la plantilla, pero ¿qué pasa si se puede acceder a través de otro objeto? La solución es el [modo sandbox |sandbox], que te permite definir qué métodos pueden ser llamados en las plantillas. Gracias a esto, no tienes que preocuparte por las brechas de seguridad. + + +¿Cuáles son las principales diferencias entre sistemas de plantillas como Latte, Twig y Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Las diferencias entre sistemas de plantillas como Latte, Twig y Blade radican principalmente en su sintaxis, seguridad e integración con frameworks: + +- Latte: utiliza la sintaxis del lenguaje PHP, lo que facilita su aprendizaje y uso. Ofrece una protección de primer nivel contra ataques XSS. +- Twig: utiliza una sintaxis similar a la de Python, que es bastante diferente de la de PHP. Escapa sin distinción de contexto. Está bien integrado con el framework Symfony. +- Blade: utiliza una mezcla de PHP y sintaxis personalizada. Escapa sin distinción de contexto. Está estrechamente integrado con las características y el ecosistema de Laravel. + + +¿Merece la pena para las empresas utilizar un sistema de plantillas? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +------------------------------------------------------------------------------------------------------------------------------------ + +En primer lugar, los costes asociados a la formación, el uso y los beneficios generales varían significativamente en función del sistema. El sistema de plantillas Latte, gracias a su uso de la sintaxis PHP, simplifica enormemente el aprendizaje para los programadores ya familiarizados con este lenguaje. Por lo general, un programador tarda unas horas en familiarizarse suficientemente con Latte, lo que reduce los costes de formación y acelera la adopción de la tecnología y, lo que es más importante, la eficacia en el uso diario. + +Además, Latte proporciona un alto nivel de protección contra la vulnerabilidad XSS gracias a su exclusiva tecnología de escape sensible al contexto. Esta protección es crucial para garantizar la seguridad de las aplicaciones web y minimizar el riesgo de ataques que puedan poner en peligro a los usuarios o los datos de la empresa. La seguridad de las aplicaciones web también es importante para mantener la buena reputación de una empresa. Los problemas de seguridad pueden provocar la pérdida de confianza de los clientes y dañar la reputación de la empresa en el mercado. + +El uso de Latte también reduce los costes generales de desarrollo y mantenimiento, ya que facilita ambos. Por lo tanto, utilizar un sistema de plantillas merece definitivamente la pena. + + +¿Afecta Latte al rendimiento de las aplicaciones web? .[#toc-does-latte-affect-the-performance-of-web-applications] +------------------------------------------------------------------------------------------------------------------- + +Aunque las plantillas Latte se procesan rápidamente, este aspecto no importa realmente. La razón es que el análisis sintáctico de los archivos sólo se produce una vez durante la primera visualización. Después se compilan en código PHP, se almacenan en disco y se ejecutan en cada petición posterior sin necesidad de recompilación. + +Así es como funciona en un entorno de producción. Durante el desarrollo, las plantillas Latte se recompilan cada vez que cambia su contenido, por lo que el desarrollador siempre ve la versión actual. diff --git a/latte/fr/@left-menu.texy b/latte/fr/@left-menu.texy index 10e3f0b396..16e8f0c1d9 100644 --- a/latte/fr/@left-menu.texy +++ b/latte/fr/@left-menu.texy @@ -1,4 +1,5 @@ - [Mise en route |Guide] +- [Pourquoi utiliser des modèles ? |why-use] - Concepts - [Sécurité d'abord |Safety First] - [Héritage des modèles |Template Inheritance] diff --git a/latte/fr/cookbook/@home.texy b/latte/fr/cookbook/@home.texy index aed8e3b5f7..962867ecfc 100644 --- a/latte/fr/cookbook/@home.texy +++ b/latte/fr/cookbook/@home.texy @@ -1,6 +1,9 @@ Livre de cuisine **************** +.[perex] +Exemples de codes et de recettes pour accomplir des tâches courantes avec Latte. + - [Tout ce que vous avez toujours voulu savoir sur {iterateWhile} |iteratewhile] - [Comment écrire des requêtes SQL dans Latte ? |how-to-write-sql-queries-in-latte] - [Migration depuis PHP |migration-from-php] diff --git a/latte/fr/tags.texy b/latte/fr/tags.texy index c493e2fbfc..050a9311c9 100644 --- a/latte/fr/tags.texy +++ b/latte/fr/tags.texy @@ -102,7 +102,7 @@ Résumé et description de toutes les balises intégrées de Latte. .[table-latte-tags language-latte] |## Disponible uniquement avec Nette Forms -| `{form}`... `{/form}` | [imprime un élément de formulaire |forms:rendering#latte] +| `{form}`... `{/form}` | [imprime un élément de formulaire |forms:rendering#form] | `{label}`... `{/label}` | [imprime une étiquette d'entrée de formulaire |forms:rendering#label-input] | `{input}` | [imprime un élément de saisie de formulaire |forms:rendering#label-input] | `{inputError}` | [imprime un message d'erreur pour l'élément de saisie du formulaire |forms:rendering#inputError] @@ -110,6 +110,7 @@ Résumé et description de toutes les balises intégrées de Latte. | `{formPrint}` | [génère le plan du formulaire Latte |forms:rendering#formPrint] | `{formPrintClass}` | [imprime la classe PHP pour les données du formulaire |forms:in-presenter#mapping-to-classes] | `{formContext}`... `{/formContext}` | [rendu partiel du formulaire |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [rendre le conteneur de formulaire |forms:rendering#special-cases] Impression de .[#toc-printing] @@ -595,7 +596,7 @@ La balise `{include}` charge et rend le modèle spécifié. Dans notre langage P Les templates inclus n'ont pas accès aux variables du contexte actif, mais ont accès aux variables globales. -Vous pouvez passer des variables de cette manière : +Vous pouvez transmettre des variables au modèle inséré de la manière suivante : ```latte {* depuis Latte 2.9 *} diff --git a/latte/fr/template-inheritance.texy b/latte/fr/template-inheritance.texy index 919bf91a70..36942828cb 100644 --- a/latte/fr/template-inheritance.texy +++ b/latte/fr/template-inheritance.texy @@ -246,7 +246,7 @@ Vous pouvez également afficher le bloc à partir d'un autre modèle : Les blocs imprimés n'ont pas accès aux variables du contexte actif, sauf si le bloc est défini dans le même fichier où il est inclus. Cependant, ils ont accès aux variables globales. -Vous pouvez passer des variables de cette manière : +Vous pouvez transmettre des variables au bloc de la manière suivante : ```latte {* depuis Latte 2.9 *} @@ -603,7 +603,7 @@ Il existe différents types d'héritage et de réutilisation du code dans Latte. ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/fr/why-use.texy b/latte/fr/why-use.texy new file mode 100644 index 0000000000..e7da37380b --- /dev/null +++ b/latte/fr/why-use.texy @@ -0,0 +1,80 @@ +Pourquoi utiliser des modèles ? +******************************* + + +Pourquoi utiliser un système de templates en PHP ? .[#toc-why-should-i-use-a-templating-system-in-php] +------------------------------------------------------------------------------------------------------ + +Pourquoi utiliser un système de modèles en PHP alors que PHP lui-même est un langage de création de modèles ? + +Rappelons d'abord brièvement l'histoire de ce langage, qui est pleine de rebondissements intéressants. L'un des premiers langages de programmation utilisés pour générer des pages HTML était le langage C. Cependant, il est vite apparu que son utilisation à cette fin n'était pas pratique. Rasmus Lerdorf a donc créé PHP, qui permettait de générer des pages HTML dynamiques avec le langage C en arrière-plan. À l'origine, PHP était conçu comme un langage de création de modèles, mais au fil du temps, il a acquis des fonctionnalités supplémentaires et est devenu un langage de programmation à part entière. + +Néanmoins, il fonctionne toujours comme un langage de création de modèles. Un fichier PHP peut contenir une page HTML, dans laquelle les variables sont affichées à l'aide de `<?= $foo ?>`etc. + +Au début de l'histoire de PHP, le système de modèles Smarty a été créé dans le but de séparer strictement l'apparence (HTML/CSS) de la logique de l'application. Il a délibérément fourni un langage plus limité que PHP lui-même, de sorte que, par exemple, un développeur ne pouvait pas faire une requête de base de données à partir d'un modèle, etc. D'autre part, il représentait une dépendance supplémentaire dans les projets, augmentait leur complexité et obligeait les programmeurs à apprendre un nouveau langage Smarty. Ces avantages étaient controversés, et le PHP simple a continué à être utilisé pour les modèles. + +Au fil du temps, les systèmes de modèles ont commencé à devenir utiles. Ils ont introduit des concepts tels que l'[héritage |template-inheritance], le [mode "bac à sable" |sandbox] et une série d'autres fonctionnalités qui ont considérablement simplifié la création de modèles par rapport au PHP pur. Le sujet de la sécurité, l'existence de [vulnérabilités telles que XSS |safety-first], et le besoin d'[échappement |#What is escaping] sont apparus sur le devant de la scène. Les systèmes de modèles ont introduit l'échappement automatique pour éliminer le risque qu'un programmeur l'oublie et crée une grave faille de sécurité (nous verrons bientôt que cela comporte certains pièges). + +Aujourd'hui, les avantages des systèmes de modèles dépassent largement les coûts associés à leur déploiement. Il est donc logique de les utiliser. + + +Pourquoi Latte est-il meilleur que Twig ou Blade ? .[#toc-why-is-latte-better-than-twig-or-blade] +------------------------------------------------------------------------------------------------- + +Il y a plusieurs raisons - certaines sont agréables et d'autres sont immensément utiles. Latte est une combinaison d'agréable et d'utile. + +*Tout d'abord, l'agréable:* Latte a la même [syntaxe que PHP |syntax#Latte Understands PHP]. La seule différence réside dans la notation des balises, qui préfère les balises courtes `{` et `}` à `<?=` et `?>`. Cela signifie que vous ne devez pas apprendre un nouveau langage. Les coûts de formation sont minimes. Plus important encore, pendant le développement, vous n'avez pas à "basculer" constamment entre le langage PHP et le langage des modèles, puisqu'il s'agit du même langage. Contrairement aux modèles Twig, qui utilisent le langage Python, le programmeur est obligé de passer d'un langage à l'autre. + +*Tous les systèmes de templates, comme Twig, Blade ou Smarty, ont évolué pour inclure une protection contre les XSS sous la forme d'un [échappement |#What is escaping] automatique. Plus précisément, l'appel automatique de la fonction `htmlspecialchars()`. Cependant, les créateurs de Latte ont réalisé que ce n'était pas du tout la bonne solution. En effet, les différentes parties du document requièrent des méthodes d'échappement différentes. L'échappement automatique naïf est une fonction dangereuse car elle crée un faux sentiment de sécurité. + +Pour que l'échappement automatique soit fonctionnel et fiable, il doit reconnaître à quel endroit du document les données sont émises (ce que nous appelons les contextes) et choisir la fonction d'échappement en conséquence. Il doit donc être [sensible au contexte |safety-first#Context-Aware Escaping]. Et c'est ce que Latte peut faire. Il comprend le langage HTML. Il ne perçoit pas le modèle comme une simple chaîne de caractères, mais comprend ce que sont les balises, les attributs, etc. Par conséquent, il échappe différemment dans le texte HTML, à l'intérieur des balises HTML, à l'intérieur de JavaScript, etc. + +Latte est le premier et le seul système de gabarits PHP avec échappement contextuel. Il représente le seul système de modèles réellement sécurisé. + +*Et une autre raison agréable:* Parce que Latte comprend le HTML, il offre d'autres fonctionnalités très agréables. Par exemple, [n:attributs |syntax#n:attributes]. Ou la possibilité de [vérifier les liens |safety-first#Link checking]. Et bien d'autres encore. + + +Qu'est-ce que l'échappement ? .[#toc-what-is-escaping] +------------------------------------------------------ + +L'échappement est un processus qui consiste à remplacer les caractères ayant une signification particulière par des séquences correspondantes lors de l'insertion d'une chaîne dans une autre afin d'éviter des effets indésirables ou des erreurs. Par exemple, lors de l'insertion d'une chaîne dans un texte HTML, dans laquelle le caractère `<` a une signification particulière car il indique le début d'une balise, nous le remplaçons par la séquence correspondante, qui est l'entité HTML `<`. Cela permet au navigateur d'afficher correctement le symbole `<`. + +Un exemple simple d'échappement direct lors de l'écriture du code PHP est l'insertion d'un guillemet dans une chaîne de caractères en plaçant une barre oblique inverse devant le guillemet. + +Nous discutons de l'échappement plus en détail dans le chapitre [Comment se défendre contre les XSS |safety-first#How to Defend Against XSS?]. + + +Une requête de base de données peut-elle être exécutée à partir d'un modèle Latte ? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +-------------------------------------------------------------------------------------------------------------------------------------------------- + +Dans les modèles, vous pouvez travailler avec les objets que le programmeur leur transmet. Si le programmeur le souhaite, il peut transmettre un objet de base de données au modèle et exécuter une requête. S'il a l'intention de le faire, il n'y a aucune raison de l'en empêcher. + +La situation est différente si vous souhaitez donner à des clients ou à des codeurs externes la possibilité de modifier les modèles. Dans ce cas, vous ne voulez absolument pas qu'ils aient accès à la base de données. Bien entendu, vous ne transmettrez pas l'objet base de données au modèle, mais qu'en est-il si un autre objet permet d'y accéder ? La solution est le [mode "bac à sable |sandbox]", qui vous permet de définir quelles méthodes peuvent être appelées dans les modèles. Grâce à cela, vous n'avez pas à vous soucier des failles de sécurité. + + +Quelles sont les principales différences entre les systèmes de templates tels que Latte, Twig et Blade ? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Les différences entre les systèmes de templating tels que Latte, Twig et Blade résident principalement dans leur syntaxe, leur sécurité et leur intégration avec les frameworks : + +- Latte : utilise la syntaxe du langage PHP, ce qui facilite son apprentissage et son utilisation. Il offre une protection de premier ordre contre les attaques XSS. +- Twig : utilise une syntaxe semblable à celle de Python, qui est assez différente de celle de PHP. Il s'échappe sans distinction de contexte. Il est bien intégré au framework Symfony. +- Blade : utilise un mélange de PHP et de syntaxe personnalisée. Il s'échappe sans distinction de contexte. Il est étroitement intégré aux fonctionnalités et à l'écosystème de Laravel. + + +Cela vaut-il la peine pour les entreprises d'utiliser un système de templating ? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +------------------------------------------------------------------------------------------------------------------------------------------------ + +Tout d'abord, les coûts associés à la formation, à l'utilisation et aux avantages globaux varient considérablement en fonction du système. Le système de templates Latte, grâce à son utilisation de la syntaxe PHP, simplifie grandement l'apprentissage des programmeurs déjà familiarisés avec ce langage. Il suffit généralement de quelques heures pour qu'un programmeur se familiarise suffisamment avec Latte, ce qui réduit les coûts de formation et accélère l'adoption de la technologie et, surtout, l'efficacité dans l'utilisation quotidienne. + +En outre, Latte offre un haut niveau de protection contre les vulnérabilités XSS grâce à sa technologie unique d'échappement contextuel. Cette protection est cruciale pour assurer la sécurité des applications web et minimiser le risque d'attaques qui pourraient mettre en danger les utilisateurs ou les données de l'entreprise. La sécurité des applications web est également importante pour maintenir la bonne réputation d'une entreprise. Les problèmes de sécurité peuvent entraîner une perte de confiance de la part des clients et nuire à la réputation de l'entreprise sur le marché. + +L'utilisation de Latte permet également de réduire les coûts globaux de développement et de maintenance en les facilitant. Par conséquent, l'utilisation d'un système de templates en vaut vraiment la peine. + + +Latte affecte-t-il les performances des applications web ? .[#toc-does-latte-affect-the-performance-of-web-applications] +------------------------------------------------------------------------------------------------------------------------ + +Bien que les modèles Latte soient traités rapidement, cet aspect n'a pas vraiment d'importance. En effet, l'analyse des fichiers n'a lieu qu'une seule fois lors du premier affichage. Ils sont ensuite compilés en code PHP, stockés sur le disque et exécutés à chaque demande ultérieure sans nécessiter de recompilation. + +C'est ainsi que cela fonctionne dans un environnement de production. Pendant le développement, les modèles Latte sont recompilés chaque fois que leur contenu est modifié, de sorte que le développeur voit toujours la version actuelle. diff --git a/latte/hu/@left-menu.texy b/latte/hu/@left-menu.texy index bb5bc21b48..7b45ea1690 100644 --- a/latte/hu/@left-menu.texy +++ b/latte/hu/@left-menu.texy @@ -1,4 +1,5 @@ - [Kezdő lépések |Guide] +- [Miért használjon sablonokat? |why-use] - Fogalmak - [Első a biztonság |Safety First] - [Sablon öröklődés |Template Inheritance] diff --git a/latte/hu/cookbook/@home.texy b/latte/hu/cookbook/@home.texy index 73fc60590c..4b9e44b15f 100644 --- a/latte/hu/cookbook/@home.texy +++ b/latte/hu/cookbook/@home.texy @@ -1,6 +1,9 @@ Szakácskönyv ************ +.[perex] +Példakódok és receptek a Latte segítségével végzett gyakori feladatok elvégzéséhez. + - [Minden, amit mindig is tudni akartál az {iterateWhile} |iteratewhile] - [Hogyan írjunk SQL-lekérdezéseket Latte-ban? |how-to-write-sql-queries-in-latte] - [Átállás PHP-ről |migration-from-php] diff --git a/latte/hu/tags.texy b/latte/hu/tags.texy index fcc8210674..44904ecca3 100644 --- a/latte/hu/tags.texy +++ b/latte/hu/tags.texy @@ -102,7 +102,7 @@ Az összes beépített Latte-címke összefoglalása és leírása. .[table-latte-tags language-latte] |## Csak Nette Forms esetén érhető el -| `{form}`... `{/form}` | [nyomtat egy űrlapelemet |forms:rendering#latte] +| `{form}`... `{/form}` | [nyomtat egy űrlapelemet |forms:rendering#form] | `{label}`... `{/label}` | [nyomtat egy űrlap beviteli címkét |forms:rendering#label-input] | `{input}` | [nyomtat egy űrlap beviteli elemet |forms:rendering#label-input] | `{inputError}` | [hibaüzenetet nyomtat az űrlap beviteli eleméhez |forms:rendering#inputError] @@ -110,6 +110,7 @@ Az összes beépített Latte-címke összefoglalása és leírása. | `{formPrint}` | [Latte űrlap tervezetet készít |forms:rendering#formPrint] | `{formPrintClass}` | [PHP osztály nyomtatása az űrlap adataihoz |forms:in-presenter#mapping-to-classes] | `{formContext}`... `{/formContext}` | [részleges űrlap renderelés |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [az űrlap tárolójának renderelése |forms:rendering#special-cases] Nyomtatás .[#toc-printing] @@ -595,7 +596,7 @@ A `{include}` címke betölti és megjeleníti a megadott sablont. Kedvenc PHP n A bevont sablonok nem férnek hozzá az aktív kontextus változóihoz, de hozzáférnek a globális változókhoz. -A változókat így adhatja át: +A beillesztett sablonhoz a következő módon adhat át változókat: ```latte {* Latte 2.9 óta *} diff --git a/latte/hu/template-inheritance.texy b/latte/hu/template-inheritance.texy index 7353e6e8a5..352923011e 100644 --- a/latte/hu/template-inheritance.texy +++ b/latte/hu/template-inheritance.texy @@ -246,7 +246,7 @@ A blokkot egy másik sablonból is megjelenítheti: A nyomtatott blokk nem fér hozzá az aktív kontextus változóihoz, kivéve, ha a blokk ugyanabban a fájlban van definiálva, ahol szerepel. A globális változókhoz azonban hozzáférnek. -A változókat így adhatja át: +A változókat a következő módon adhatja át a blokknak: ```latte {* Latte 2.9 óta *} @@ -603,7 +603,7 @@ A Latte-ban az öröklésnek és a kód újrafelhasználásának különböző t ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/hu/why-use.texy b/latte/hu/why-use.texy new file mode 100644 index 0000000000..871ea8325f --- /dev/null +++ b/latte/hu/why-use.texy @@ -0,0 +1,80 @@ +Miért használjon sablonokat? +**************************** + + +Miért érdemes sablonrendszert használni a PHP-ban? .[#toc-why-should-i-use-a-templating-system-in-php] +------------------------------------------------------------------------------------------------------ + +Miért használjunk sablonrendszert a PHP-ban, amikor a PHP maga is sablonnyelv? + +Először is, foglaljuk össze röviden ennek a nyelvnek a történetét, amely tele van érdekes fordulatokkal. Az egyik első HTML-oldalak generálására használt programozási nyelv a C nyelv volt. Hamarosan kiderült azonban, hogy erre a célra használni nem praktikus. Rasmus Lerdorf ezért megalkotta a PHP-t, amely megkönnyítette a dinamikus HTML generálását a C nyelvvel a háttérben. A PHP-t eredetileg templating nyelvnek tervezték, de idővel további funkciókkal bővült, és teljes értékű programozási nyelvvé vált. + +Ennek ellenére továbbra is templating nyelvként működik. Egy PHP-fájl tartalmazhat egy HTML-oldalt, amelyben a változókat a következő módon adjuk ki `<?= $foo ?>`, stb. + +A PHP történetének korai szakaszában jött létre a Smarty sablonrendszer, amelynek célja a megjelenés (HTML/CSS) és az alkalmazási logika szigorú szétválasztása volt. Szándékosan korlátozottabb nyelvet biztosított, mint maga a PHP, így például egy fejlesztő nem tudott egy sablonból adatbázis-lekérdezést készíteni stb. Másrészt további függőséget jelentett a projektekben, növelte azok összetettségét, és a programozóknak egy új Smarty nyelvet kellett megtanulniuk. Ezek az előnyök ellentmondásosak voltak, és továbbra is a sima PHP-t használták a sablonokhoz. + +Idővel a sablonrendszerek kezdtek hasznosnak bizonyulni. Olyan fogalmakat vezettek be, mint az [öröklés |template-inheritance], a [sandbox mód |sandbox] és egy sor más funkció, amelyek jelentősen leegyszerűsítették a sablonkészítést a tiszta PHP-hoz képest. A biztonság témája, az [olyan sebezhetőségek |safety-first] létezése [, mint az XSS |safety-first], és a [menekülés |#What is escaping] szükségessége előtérbe került. A sablonrendszerek bevezették az automatikus mentést, hogy kiküszöböljék annak kockázatát, hogy a programozó elfelejtse és komoly biztonsági rést hozzon létre (rövidesen látni fogjuk, hogy ennek vannak bizonyos buktatói). + +Ma a sablonrendszerek előnyei messze meghaladják a bevezetésükkel járó költségeket. Ezért van értelme használni őket. + + +Miért jobb a Latte, mint a Twig vagy a Blade? .[#toc-why-is-latte-better-than-twig-or-blade] +-------------------------------------------------------------------------------------------- + +Számos oka van - néhány kellemes, mások pedig mérhetetlenül hasznosak. A Latte a kellemes és a hasznos kombinációja. + +*Először is, a kellemes:* A Latte [szintaxisa |syntax#Latte Understands PHP] megegyezik [a PHP-ével |syntax#Latte Understands PHP]. Az egyetlen különbség a címkék jelölésében van, a `<?=` és a `?>` helyett a rövidebb `{` és `}` jelöléseket részesíti előnyben. Ez azt jelenti, hogy nem kell új nyelvet tanulnia. A képzési költségek minimálisak. A legfontosabb, hogy a fejlesztés során nem kell folyamatosan "váltogatni" a PHP és a sablonnyelv között, mivel mindkettő ugyanaz. Ez ellentétben a Twig sablonokkal, amelyek a Python nyelvet használják, így a programozó kénytelen két különböző nyelv között váltani. + +*Most a mérhetetlenül hasznos ok:* Minden sablonrendszer, mint például a Twig, a Blade vagy a Smarty, úgy fejlődött, hogy tartalmazzon XSS elleni védelmet az automatikus [escaping |#What is escaping] formájában. Pontosabban a `htmlspecialchars()` függvény automatikus meghívása. A Latte készítői azonban rájöttek, hogy ez egyáltalán nem jó megoldás. A dokumentum különböző részei ugyanis különböző escaping módszereket igényelnek. A naiv automatikus szcenírozás veszélyes funkció, mert hamis biztonságérzetet kelt. + +Ahhoz, hogy az automatikus menekítés működőképes és megbízható legyen, fel kell ismernie, hogy a dokumentumban hol történik az adatok kimenete (ezeket nevezzük kontextusoknak), és ennek megfelelően kell kiválasztania a menekítési funkciót. Ezért [kontextusfüggőnek |safety-first#Context-Aware Escaping] kell lennie. És ez az, amire a Latte képes. Megérti a HTML-t. A sablont nem csak egy karaktersorozatként érzékeli, hanem megérti, hogy mik a címkék, attribútumok stb. Ezért másképp lép el a HTML-szövegben, a HTML-címkéken belül, a JavaScriptben stb. + +A Latte az első és egyetlen PHP sablonrendszer, amely kontextusfüggő escapinggel rendelkezik. Ez az egyetlen igazán biztonságos sablonrendszer. + +*És egy másik kellemes ok:* Mivel a Latte érti a HTML-t, más nagyon kellemes funkciókat is kínál. Például az [n:attribútumok |syntax#n:attributes]. Vagy a [linkek ellenőrzésének |safety-first#Link checking] lehetőségét. És még sok minden mást. + + +Mi az a menekülés? .[#toc-what-is-escaping] +------------------------------------------- + +Az escaping egy olyan folyamat, amely során a különleges jelentésű karaktereket megfelelő szekvenciákkal helyettesítjük, amikor az egyik karakterláncot egy másikba illesztjük, hogy megakadályozzuk a nem kívánt hatásokat vagy hibákat. Például, amikor egy olyan karakterláncot illesztünk be HTML-szövegbe, amelyben a `<` karakter különleges jelentéssel bír, mivel egy tag elejét jelzi, a megfelelő szekvenciával helyettesítjük, ami a `<` HTML-egység. Ez lehetővé teszi a böngésző számára a `<` szimbólum helyes megjelenítését. + +Egy egyszerű példa a PHP-kód írásakor a közvetlen menekülésre, amikor idézőjelet illesztünk be egy karakterláncba úgy, hogy egy backslash-t helyezünk elé. + +Az escapinget részletesebben a [Hogyan védekezzünk az XSS ellen |safety-first#How to Defend Against XSS?] című fejezetben tárgyaljuk. + + +Végrehajtható-e adatbázis-lekérdezés egy Latte sablonból? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +------------------------------------------------------------------------------------------------------------------------ + +A sablonokban olyan objektumokkal lehet dolgozni, amelyeket a programozó átad nekik. Ha a programozó úgy akarja, átadhat egy adatbázis-objektumot a sablonhoz, és lekérdezést végezhet. Ha ezt szándékukban áll megtenni, nincs okunk megakadályozni őket ebben. + +Más a helyzet, ha az ügyfeleknek vagy külső programozóknak akarja megadni a sablonok szerkesztésének lehetőségét. Ebben az esetben semmiképpen sem szeretné, ha hozzáférnének az adatbázishoz. Természetesen nem fogod átadni az adatbázis-objektumot a sablonhoz, de mi van akkor, ha az egy másik objektumon keresztül érhető el? A megoldás a [sandbox mód |sandbox], amely lehetővé teszi annak meghatározását, hogy a sablonokban milyen metódusok hívhatók meg. Ennek köszönhetően nem kell aggódnia a biztonsági rések miatt. + + +Mik a fő különbségek az olyan templating rendszerek között, mint a Latte, a Twig és a Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Az olyan templating rendszerek, mint a Latte, a Twig és a Blade közötti különbségek elsősorban a szintaxisukban, a biztonságukban és a keretrendszerekkel való integrációjukban rejlenek: + +- Latte: PHP nyelvi szintaxist használ, így könnyebben megtanulható és használható. Kiváló védelmet nyújt az XSS-támadások ellen. +- Twig: Python-szerű szintaxist használ, ami teljesen eltér a PHP nyelvtől. Kontextus megkülönböztetés nélkül menekül. Jól integrálható a Symfony keretrendszerrel. +- Blade: a PHP és az egyéni szintaxis keverékét használja. A kontextus megkülönböztetése nélkül menekül. Szorosan integrálódik a Laravel funkcióiba és ökoszisztémájába. + + +Megéri a vállalatoknak templating rendszert használni? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +---------------------------------------------------------------------------------------------------------------------- + +Először is, a képzéssel, a használattal és az általános előnyökkel kapcsolatos költségek jelentősen eltérnek a rendszertől függően. A Latte templating rendszer a PHP szintaxis használatának köszönhetően nagyban leegyszerűsíti a tanulást az ezen a nyelven már jártas programozók számára. Egy programozónak általában néhány óra alatt sikerül kellőképpen megismerkednie a Latte-tel, ami csökkenti a képzési költségeket, felgyorsítja a technológia átvételét és - ami a legfontosabb - a mindennapi használat hatékonyságát. + +Emellett a Latte magas szintű védelmet nyújt az XSS sebezhetőséggel szemben az egyedülálló, kontextustudatos escaping technológiának köszönhetően. Ez a védelem kulcsfontosságú a webalkalmazások biztonságának biztosításához és a felhasználókat vagy a vállalati adatokat veszélyeztető támadások kockázatának minimalizálásához. A webalkalmazások biztonsága a vállalat jó hírnevének megőrzése szempontjából is fontos. A biztonsági problémák az ügyfelek bizalmának elvesztéséhez vezethetnek, és ronthatják a vállalat hírnevét a piacon. + +A Latte használata csökkenti az általános fejlesztési és karbantartási költségeket is, mivel mindkettő egyszerűbbé válik. Ezért egy templating rendszer használata mindenképpen megéri. + + +Befolyásolja-e a Latte a webes alkalmazások teljesítményét? .[#toc-does-latte-affect-the-performance-of-web-applications] +------------------------------------------------------------------------------------------------------------------------- + +Bár a Latte sablonok feldolgozása gyors, ez a szempont nem igazán számít. Ennek oka, hogy a fájlok elemzése csak egyszer történik meg az első megjelenítés során. Ezután PHP-kóddá fordítják le őket, a lemezen tárolják, és minden következő kérésnél újbóli fordítás nélkül futtatják őket. + +Ez így működik a termelési környezetben. A fejlesztés során a Latte sablonok minden alkalommal újrafordításra kerülnek, amikor tartalmuk megváltozik, így a fejlesztő mindig az aktuális verziót látja. diff --git a/latte/it/@left-menu.texy b/latte/it/@left-menu.texy index a10314ae6c..68e71464d5 100644 --- a/latte/it/@left-menu.texy +++ b/latte/it/@left-menu.texy @@ -1,4 +1,5 @@ - [Per iniziare |Guide] +- [Perché usare i modelli? |why-use] - Concetti - La [sicurezza prima di tutto |Safety First] - [Ereditarietà dei template |Template Inheritance] diff --git a/latte/it/cookbook/@home.texy b/latte/it/cookbook/@home.texy index ea20d90d12..cf6e79c376 100644 --- a/latte/it/cookbook/@home.texy +++ b/latte/it/cookbook/@home.texy @@ -1,6 +1,9 @@ Libro di cucina *************** +.[perex] +Codici di esempio e ricette per eseguire operazioni comuni con Latte. + - [Tutto quello che avreste sempre voluto sapere su {iterateWhile} |iteratewhile] - [Come scrivere query SQL in Latte? |how-to-write-sql-queries-in-latte] - [Migrazione da PHP |migration-from-php] diff --git a/latte/it/tags.texy b/latte/it/tags.texy index e5fe5a10ce..b04fce5a7c 100644 --- a/latte/it/tags.texy +++ b/latte/it/tags.texy @@ -102,7 +102,7 @@ Riepilogo e descrizione di tutti i tag incorporati di Latte. .[table-latte-tags language-latte] |## Disponibile solo con Nette Forms -| `{form}`... `{/form}` | [stampa un elemento del modulo |forms:rendering#latte] +| `{form}`... `{/form}` | [stampa un elemento del modulo |forms:rendering#form] | `{label}`... `{/label}` | [stampa un'etichetta di input del modulo |forms:rendering#label-input] | `{input}` | [stampa un elemento di input del modulo |forms:rendering#label-input] | `{inputError}` | [stampa il messaggio di errore per l'elemento di input del modulo |forms:rendering#inputError] @@ -110,6 +110,7 @@ Riepilogo e descrizione di tutti i tag incorporati di Latte. | `{formPrint}` | [genera il blueprint del modulo Latte |forms:rendering#formPrint] | `{formPrintClass}` | [stampa la classe PHP per i dati del modulo |forms:in-presenter#mapping-to-classes] | `{formContext}`... `{/formContext}` | [rendering parziale del modulo |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [rendere il contenitore del modulo |forms:rendering#special-cases] Stampa .[#toc-printing] @@ -595,7 +596,7 @@ Il tag `{include}` carica e rende il modello specificato. Nel nostro linguaggio I template inclusi non hanno accesso alle variabili del contesto attivo, ma hanno accesso alle variabili globali. -È possibile passare le variabili in questo modo: +È possibile passare variabili al modello inserito nel modo seguente: ```latte {* da Latte 2.9 *} diff --git a/latte/it/template-inheritance.texy b/latte/it/template-inheritance.texy index 77a692ff1e..7282bf5c17 100644 --- a/latte/it/template-inheritance.texy +++ b/latte/it/template-inheritance.texy @@ -246,7 +246,7 @@ Per stampare un blocco in un punto specifico, utilizzare il tag `{include blockn I blocchi stampati non hanno accesso alle variabili del contesto attivo, tranne se il blocco è definito nello stesso file in cui è incluso. Tuttavia, hanno accesso alle variabili globali. -È possibile passare le variabili in questo modo: +È possibile passare le variabili al blocco nel modo seguente: ```latte {* da Latte 2.9 *} @@ -603,7 +603,7 @@ In Latte esistono vari tipi di ereditarietà e di riutilizzo del codice. Riassum ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/it/why-use.texy b/latte/it/why-use.texy new file mode 100644 index 0000000000..e9c57d842b --- /dev/null +++ b/latte/it/why-use.texy @@ -0,0 +1,80 @@ +Perché usare i modelli? +*********************** + + +Perché usare un sistema di template in PHP? .[#toc-why-should-i-use-a-templating-system-in-php] +----------------------------------------------------------------------------------------------- + +Perché usare un sistema di template in PHP quando PHP stesso è un linguaggio di template? + +Ripercorriamo brevemente la storia di questo linguaggio, che è ricca di colpi di scena interessanti. Uno dei primi linguaggi di programmazione utilizzati per la generazione di pagine HTML è stato il linguaggio C. Tuttavia, ben presto ci si è resi conto che l'utilizzo di questo linguaggio per la generazione di pagine HTML non era sufficiente. Tuttavia, divenne presto evidente che utilizzarlo per questo scopo era poco pratico. Rasmus Lerdorf creò quindi PHP, che facilitava la generazione di HTML dinamico con il linguaggio C sul retro. Originariamente PHP era stato progettato come linguaggio di template, ma col tempo ha acquisito ulteriori funzionalità ed è diventato un linguaggio di programmazione a tutti gli effetti. + +Tuttavia, funziona ancora come un linguaggio di template. Un file PHP può contenere una pagina HTML, in cui le variabili vengono emesse con i caratteri `<?= $foo ?>`, ecc. + +All'inizio della storia di PHP, è stato creato il sistema di template Smarty, con lo scopo di separare rigorosamente l'aspetto (HTML/CSS) dalla logica dell'applicazione. Il sistema forniva deliberatamente un linguaggio più limitato rispetto a PHP stesso, in modo che, ad esempio, uno sviluppatore non potesse fare una query al database da un template, ecc. D'altra parte, rappresentava una dipendenza aggiuntiva nei progetti, ne aumentava la complessità e richiedeva ai programmatori di imparare un nuovo linguaggio Smarty. Questi vantaggi sono stati controversi e per i template si è continuato a usare il semplice PHP. + +Col tempo, i sistemi di template hanno iniziato a diventare utili. Hanno introdotto concetti come l'[ereditarietà |template-inheritance], la [modalità sandbox |sandbox] e una serie di altre caratteristiche che hanno semplificato notevolmente la creazione di template rispetto al PHP puro. Il tema della sicurezza, l'esistenza di [vulnerabilità come gli XSS |safety-first] e la necessità dell'[escape |#What is escaping] sono venuti alla ribalta. I sistemi di template hanno introdotto l'escape automatico per eliminare il rischio che un programmatore lo dimenticasse e creasse una grave falla nella sicurezza (vedremo tra poco che questo comporta alcune insidie). + +Oggi i vantaggi dei sistemi di template superano di gran lunga i costi associati alla loro implementazione. Pertanto, ha senso utilizzarli. + + +Perché Latte è meglio di Twig o Blade? .[#toc-why-is-latte-better-than-twig-or-blade] +------------------------------------------------------------------------------------- + +Ci sono diversi motivi: alcuni sono piacevoli e altri sono immensamente utili. Latte è una combinazione di piacevole e utile. + +*Latte ha la stessa [sintassi di PHP |syntax#Latte Understands PHP]. L'unica differenza è nella notazione dei tag, preferendo i più brevi `{` e `}` invece di `<?=` e `?>`. Ciò significa che non è necessario imparare un nuovo linguaggio. I costi di formazione sono minimi. Soprattutto, durante lo sviluppo, non è necessario "passare" continuamente dal linguaggio PHP a quello dei template, perché sono entrambi uguali. A differenza dei template di Twig, che utilizzano il linguaggio Python, costringendo il programmatore a passare da un linguaggio all'altro. + +*Tutti i sistemi di template, come Twig, Blade o Smarty, si sono evoluti per includere una protezione contro gli XSS sotto forma di [escape |#What is escaping] automatico. Più precisamente, la chiamata automatica della funzione `htmlspecialchars()`. Tuttavia, i creatori di Latte si sono resi conto che questa non è affatto la soluzione giusta. Infatti, parti diverse del documento richiedono metodi di escape diversi. L'escape automatico ingenuo è una funzione pericolosa perché crea un falso senso di sicurezza. + +Affinché l'auto-escaping sia funzionale e affidabile, deve riconoscere in quale punto del documento vengono emessi i dati (che chiamiamo contesti) e scegliere la funzione di escape di conseguenza. Pertanto, deve essere [sensibile al contesto |safety-first#Context-Aware Escaping]. E questo è ciò che Latte può fare. Capisce l'HTML. Non percepisce il modello come una semplice stringa di caratteri, ma capisce cosa sono i tag, gli attributi e così via. Pertanto, esegue l'escape in modo diverso nel testo HTML, all'interno dei tag HTML, all'interno di JavaScript, ecc. + +Latte è il primo e unico sistema di template PHP con escape sensibile al contesto. Rappresenta l'unico sistema di template veramente sicuro. + +*E un'altra ragione piacevole:* Poiché Latte comprende l'HTML, offre altre caratteristiche molto piacevoli. Ad esempio, [n:attributi |syntax#n:attributes]. O la possibilità di [controllare i link |safety-first#Link checking]. E molte altre ancora. + + +Che cos'è l'escape? .[#toc-what-is-escaping] +-------------------------------------------- + +L'escape è un processo che consiste nel sostituire i caratteri con un significato speciale con sequenze corrispondenti quando si inserisce una stringa in un'altra per evitare effetti indesiderati o errori. Ad esempio, quando si inserisce una stringa in un testo HTML, in cui il carattere `<` ha un significato speciale perché indica l'inizio di un tag, lo si sostituisce con la sequenza corrispondente, che è l'entità HTML `<`. Questo permette al browser di visualizzare correttamente il simbolo `<`. + +Un semplice esempio di escape diretto nella scrittura del codice PHP è l'inserimento di una virgoletta in una stringa, anteponendo un backslash. + +L'escape è trattato in modo più dettagliato nel capitolo [Come difendersi dagli XSS |safety-first#How to Defend Against XSS?]. + + +È possibile eseguire una query al database da un template Latte? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +------------------------------------------------------------------------------------------------------------------------------- + +Nei modelli è possibile lavorare con gli oggetti che il programmatore passa loro. Se il programmatore vuole, può passare un oggetto del database al modello ed eseguire una query. Se intende farlo, non c'è motivo di impedirglielo. + +Una situazione diversa si presenta se si vuole dare ai clienti o ai programmatori esterni la possibilità di modificare i template. In questo caso, non si vuole assolutamente che abbiano accesso al database. Naturalmente, non si passerà l'oggetto database al template, ma cosa succede se si può accedere al database attraverso un altro oggetto? La soluzione è la [modalità sandbox |sandbox], che consente di definire quali metodi possono essere chiamati nei template. In questo modo, non ci si deve preoccupare di violazioni della sicurezza. + + +Quali sono le principali differenze tra i sistemi di template come Latte, Twig e Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Le differenze tra i sistemi di template come Latte, Twig e Blade risiedono principalmente nella sintassi, nella sicurezza e nell'integrazione con i framework: + +- Latte: utilizza la sintassi del linguaggio PHP, che lo rende più facile da imparare e da usare. Offre una protezione di alto livello contro gli attacchi XSS. +- Twig: utilizza una sintassi simile a quella di Python, molto diversa da quella di PHP. Esegue l'escape senza distinzione di contesto. È ben integrato con il framework Symfony. +- Blade: utilizza un mix di sintassi PHP e personalizzata. Esegue l'escape senza distinzione di contesto. È strettamente integrato con le funzionalità e l'ecosistema Laravel. + + +Vale la pena per le aziende utilizzare un sistema di template? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +------------------------------------------------------------------------------------------------------------------------------ + +Innanzitutto, i costi associati alla formazione, all'utilizzo e ai vantaggi complessivi variano notevolmente a seconda del sistema. Il sistema di template Latte, grazie all'uso della sintassi PHP, semplifica notevolmente l'apprendimento per i programmatori che hanno già familiarità con questo linguaggio. Di solito bastano poche ore perché un programmatore prenda confidenza con Latte, riducendo i costi di formazione e accelerando l'adozione della tecnologia e, soprattutto, l'efficienza nell'uso quotidiano. + +Inoltre, Latte offre un alto livello di protezione contro le vulnerabilità XSS grazie alla sua esclusiva tecnologia di escape context-aware. Questa protezione è fondamentale per garantire la sicurezza delle applicazioni web e ridurre al minimo il rischio di attacchi che potrebbero mettere in pericolo gli utenti o i dati aziendali. La sicurezza delle applicazioni web è importante anche per mantenere la buona reputazione di un'azienda. I problemi di sicurezza possono portare alla perdita di fiducia da parte dei clienti e danneggiare la reputazione dell'azienda sul mercato. + +L'uso di Latte riduce anche i costi complessivi di sviluppo e di manutenzione, rendendo entrambi più semplici. Pertanto, l'uso di un sistema di template vale sicuramente la pena. + + +Latte influisce sulle prestazioni delle applicazioni web? .[#toc-does-latte-affect-the-performance-of-web-applications] +----------------------------------------------------------------------------------------------------------------------- + +Anche se i modelli di Latte vengono elaborati rapidamente, questo aspetto non ha molta importanza. Il motivo è che il parsing dei file avviene solo una volta durante la prima visualizzazione. Vengono poi compilati in codice PHP, memorizzati su disco ed eseguiti a ogni richiesta successiva senza bisogno di ricompilazione. + +Questo è il modo in cui funziona in un ambiente di produzione. Durante lo sviluppo, i modelli Latte vengono ricompilati ogni volta che il loro contenuto viene modificato, in modo che lo sviluppatore veda sempre la versione corrente. diff --git a/latte/ja/@left-menu.texy b/latte/ja/@left-menu.texy index 0f32757aea..f0733f9e0b 100644 --- a/latte/ja/@left-menu.texy +++ b/latte/ja/@left-menu.texy @@ -1,4 +1,5 @@ - [はじめに |Guide] +- [なぜテンプレートを使うのか? |why-use] - コンセプト - [安全第一 |Safety First] - [テンプレート継承 |Template Inheritance] diff --git a/latte/ja/cookbook/@home.texy b/latte/ja/cookbook/@home.texy index 81e6f4f391..965ad57a73 100644 --- a/latte/ja/cookbook/@home.texy +++ b/latte/ja/cookbook/@home.texy @@ -1,6 +1,9 @@ クックブック ****** +.[perex] +Latteでよくある作業を実現するためのコード例とレシピを紹介。 + - [{iterateWhile}についてあなたがいつも知りたかったことすべて |iteratewhile] - [LatteでSQLクエリを書くには? |how-to-write-sql-queries-in-latte] - [PHPからの移行 |migration-from-php] diff --git a/latte/ja/tags.texy b/latte/ja/tags.texy index caf6ca72d9..8331428010 100644 --- a/latte/ja/tags.texy +++ b/latte/ja/tags.texy @@ -102,7 +102,7 @@ Latte内蔵の全タグの概要と説明。 .[table-latte-tags language-latte] |## Nette Forms のみで利用可能 -|`{form}`...`{/form}` |[フォームエレメントを表示します。|forms:en:rendering#latte] +|`{form}`...`{/form}` |[フォームエレメントを表示します。|forms:en:rendering#form] |`{label}`...`{/label}` |[フォーム入力ラベルを表示します。|forms:en:rendering#label-input] |`{input}` |[フォーム入力要素を表示します。|forms:en:rendering#label-input] |`{inputError}` | [フォーム入力要素のエラーメッセージを表示します。|forms:en:rendering#inputError] @@ -110,6 +110,7 @@ Latte内蔵の全タグの概要と説明。 |`{formPrint}` |[ラテ型フォームの青写真を生成する|forms:en:rendering#formPrint] |`{formPrintClass}` |[フォームデータのための PHP クラスを表示する|forms:en:in-presenter#mapping-to-classes] |`{formContext}`...`{/formContext}` |[フォームの部分的なレンダリング |forms:en:rendering#special-cases] +|`{formContainer}`...`{/formContainer}` |[フォームコンテナのレンダリング |forms:en:rendering#special-cases] 印刷 .[#toc-printing] @@ -595,7 +596,7 @@ n:attributeの前に`inner-` というプレフィックスを付けることが インクルードされたテンプレートは、アクティブなコンテキストの変数にはアクセスできませんが、グローバル変数にはアクセスできます。 -この方法で変数を渡すことができます。 +挿入されたテンプレートには、以下の方法で変数を渡すことができます: ```latte {* since Latte 2.9 *} diff --git a/latte/ja/template-inheritance.texy b/latte/ja/template-inheritance.texy index 605c539d76..b310d3b2d7 100644 --- a/latte/ja/template-inheritance.texy +++ b/latte/ja/template-inheritance.texy @@ -246,7 +246,7 @@ bar: {$bar ?? 'not defined'} // prints: not defined 印刷されたブロックは、アクティブなコンテキストの変数にアクセスできません。ただし、ブロックがインクルードされているのと同じファイルに定義されている場合は例外です。ただし、グローバル変数へのアクセスは可能です。 -この方法で変数を渡すことができます。 +ブロックに変数を渡すには、次のようにします: ```latte {* since Latte 2.9 *} @@ -603,7 +603,7 @@ Latteには様々な種類の継承やコードの再利用があります。よ ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/ja/why-use.texy b/latte/ja/why-use.texy new file mode 100644 index 0000000000..4f544a403e --- /dev/null +++ b/latte/ja/why-use.texy @@ -0,0 +1,80 @@ +なぜテンプレートを使うのか? +************** + + +なぜPHPでテンプレートシステムを使用する必要があるのか? .[#toc-why-should-i-use-a-templating-system-in-php] +--------------------------------------------------------------------------------- + +PHP自体がテンプレート言語なのに、なぜPHPでテンプレートシステムを使うのか? + +まずは、興味深い紆余曲折を経たこの言語の歴史を簡単に振り返ってみましょう。HTMLページの生成に使われた最初のプログラミング言語のひとつにC言語がある。しかし、C言語をHTMLの生成に使うのは現実的でないことがわかった。そこでラスマス・レンドルフは、C言語をバックエンドに動的なHTMLを生成するPHPを開発した。PHPはもともとテンプレート言語として設計されましたが、時間の経過とともに機能が追加され、本格的なプログラミング言語となりました。 + +それでもなお、テンプレート言語として機能しています。PHP ファイルは HTML ページを含むことができ、その中で変数が出力されます。 `<?= $foo ?>`などがあります。 + +PHPの歴史の初期に、外観(HTML/CSS)とアプリケーションロジックを厳密に分離する目的で、Smartyテンプレートシステムが作成されました。これは、意図的にPHP自体よりも制限された言語を提供し、例えば、開発者がテンプレートからデータベースクエリを作成することなどができないようにしました。その反面、プロジェクトに依存する部分が増え、複雑さを増し、プログラマーは新たにSmarty言語を習得する必要がありました。このような利点は賛否両論あり、テンプレートにはプレーンなPHPが使われ続けました。 + +時が経つにつれ、テンプレートシステムは便利なものになり始めました。テンプレートシステムは、[継承 |template-inheritance]、[サンドボックスモードなどの |sandbox]概念を導入し、純粋なPHPと比較してテンプレート作成を大幅に簡素化する様々な機能を備えています。そして、セキュリティの話題、[XSSのような脆弱 |safety-first]性の存在、[エスケープの |#What is escaping]必要性がクローズアップされるようになりました。テンプレートシステムでは、プログラマーがエスケープを忘れて深刻なセキュリティホールを作ってしまうリスクを排除するために、自動エスケープを導入しました(これには一定の落とし穴があることは、すぐにわかります)。 + +現在、テンプレートシステムのメリットは、その導入にかかるコストをはるかに上回っています。だから、使うことに意味があるのです。 + + +TwigやBladeよりもLatteの方が優れているのはなぜですか? .[#toc-why-is-latte-better-than-twig-or-blade] +--------------------------------------------------------------------------------- + +理由はいくつかありますが、楽しいものもあれば、ものすごく便利なものもあります。ラテは、心地よさと便利さを兼ね備えています。 + +*Latteは[PHPと |syntax#Latte Understands PHP]同じ[構文 |syntax#Latte Understands PHP]です。唯一の違いはタグの表記で、`<?=` や`?>` の代わりに、より短い`{` や`}` を好みます。つまり、新しい言語を学ぶ必要がないのです。トレーニングの費用も最小限に抑えられます。最も重要なのは、開発中に、PHP言語とテンプレート言語を常に「切り替える」必要がないことです。これは、Python言語を使用するTwigテンプレートとは異なり、プログラマーは2つの異なる言語を切り替えることを余儀なくされます。 + +*Twig、Blade、Smartyのようなすべてのテンプレートシステムは、自動[エスケープという |#What is escaping]形でXSSに対する保護を含むように進化してきました。より正確には、`htmlspecialchars()` 関数を自動的に呼び出すことです。しかし、Latteの制作者たちは、これがまったく正しい解決策ではないことに気づきました。というのも、文書の異なる部分には異なるエスケープ方法が必要だからです。ナイーブな自動エスケープは、誤った安心感を与えるため、危険な機能です。 + +自動エスケープが機能的で信頼できるものであるためには、データが文書中のどこに出力されているかを認識し(これをコンテキストと呼ぶ)、それに応じてエスケープ関数を選択する必要があります。従って、[文脈を考慮した |safety-first#Context-Aware Escaping]ものでなければならない。そして、これがLatteにできることなのです。ラテはHTMLを理解する。テンプレートを単なる文字列として認識するのではなく、タグや属性などが何であるかを理解している。そのため、HTMLテキスト内、HTMLタグ内、JavaScript内などで異なるエスケープを行います。 + +Latteは、文脈依存のエスケープを持つ最初で唯一のPHPテンプレートシステムです。また、真に安全な唯一のテンプレートシステムです。 + +*LatteはHTMLを理解するため、他にもとても楽しい機能を提供しています。例えば、[n:attributes |syntax#n:attributes]。あるいは[リンクをチェック |safety-first#Link checking]する機能。その他にもたくさんあります。 + + +エスケープとは何ですか? .[#toc-what-is-escaping] +------------------------------------- + +エスケープとは、ある文字列を別の文字列に挿入する際に、特殊な意味を持つ文字を対応する配列に置き換えて、不要な効果やエラーを防止する処理である。例えば、HTMLテキストに文字列を挿入する際、`<` という文字がタグの先頭を示すという特別な意味を持つ場合、これを対応する配列であるHTMLエンティティ`<` に置き換えます。これにより、ブラウザは`<` の記号を正しく表示することができます。 + +PHPのコードを書くときに直接エスケープする簡単な例として、バックスラッシュを前に置いて引用符を文字列に挿入することができます。 + +エスケープについては、「[XSSを防御する方法 |safety-first#How to Defend Against XSS?]」の章で詳しく説明しています。 + + +ラテのテンプレートからデータベースのクエリを実行することは可能ですか? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +-------------------------------------------------------------------------------------------------- + +テンプレートでは、プログラマが渡したオブジェクトを操作することができます。プログラマがその気になれば、テンプレートにデータベース・オブジェクトを渡してクエリを実行することができます。そうしようと思えば、それを妨げる理由はない。 + +クライアントや外部のコーダーにテンプレートを編集させる場合は、別の状況が発生します。この場合、彼らにデータベースへのアクセス権を与えたくないのは間違いありません。もちろん、テンプレートにデータベース・オブジェクトを渡すことはしませんが、他のオブジェクトを通してアクセスできる場合はどうでしょうか。その解決策が[サンドボックスモードで |sandbox]、テンプレートで呼び出せるメソッドを定義することができます。このおかげで、セキュリティ侵害を心配する必要はありません。 + + +Latte、Twig、Bladeなどのテンプレートシステムの主な違いは何ですか? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +----------------------------------------------------------------------------------------------------------------------------------- + +Latte、Twig、Bladeといったテンプレートシステムの違いは、主に構文、セキュリティ、フレームワークとの統合にある: + +- Latte:PHP言語の構文を使用するため、学習と使用が容易です。XSS攻撃に対して最高レベルの保護を提供します。 +- Twig: Pythonのような構文を使用しており、PHPとは全く異なる。文脈を区別せずにエスケープすることができます。Symfonyフレームワークとうまく統合されています。 +- Blade:PHPとカスタム構文のミックスを使用します。文脈を区別することなくエスケープします。Laravelの機能およびエコシステムと緊密に統合されています。 + + +企業がテンプレートシステムを使うことに意義はあるのか? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +------------------------------------------------------------------------------------------- + +まず、トレーニングにかかる費用、使用方法、そして全体的な利点は、システムによって大きく異なります。テンプレートシステム「Latte」は、PHPの文法を使っているため、PHPに慣れているプログラマーにとっては、学習が非常に簡単になる。プログラマーがLatteを十分に使いこなせるようになるには、通常数時間かかるため、トレーニングコストを削減し、技術の導入を加速させ、最も重要な日常使用における効率化を実現します。 + +さらに、Latteは、独自のコンテキストアウェアエスケープ技術により、XSS脆弱性に対する高度な保護を提供します。この保護機能は、Webアプリケーションのセキュリティを確保し、ユーザーや企業データを危険にさらす攻撃のリスクを最小化するために極めて重要です。また、Webアプリケーションのセキュリティは、企業の評判を維持するためにも重要です。セキュリティの問題は、顧客からの信頼を失い、市場における企業の評判を損なうことにつながります。 + +また、Latteを使うことで、開発コストとメンテナンスコストの両方が簡単になり、全体的なコストダウンにもつながります。したがって、テンプレートシステムを使うことは、間違いなく価値があります。 + + +LatteはWebアプリケーションのパフォーマンスに影響を与えるか? .[#toc-does-latte-affect-the-performance-of-web-applications] +------------------------------------------------------------------------------------------------ + +Latteのテンプレートは高速に処理されますが、この点はあまり重要ではありません。なぜなら、ファイルの解析は最初の表示時に一度だけ行われるからです。その後、PHPコードにコンパイルされ、ディスクに保存され、再コンパイルを必要とせずに、その後のリクエストごとに実行されます。 + +本番環境ではこのように動作しています。開発中、Latteのテンプレートは内容が変わるたびに再コンパイルされるため、開発者は常に最新版を見ることができます。 diff --git a/latte/pl/@left-menu.texy b/latte/pl/@left-menu.texy index 961cfb8509..c6d3b03a80 100644 --- a/latte/pl/@left-menu.texy +++ b/latte/pl/@left-menu.texy @@ -1,4 +1,5 @@ - [Zaczynając od Latte |guide] +- [Dlaczego warto używać szablonów? |why-use] - Koncepcje - [Bezpieczeństwo przede wszystkim |safety-first] - [Dziedziczenie szablonów |Template Inheritance] diff --git a/latte/pl/cookbook/@home.texy b/latte/pl/cookbook/@home.texy index 553f1133be..2f49c82fcc 100644 --- a/latte/pl/cookbook/@home.texy +++ b/latte/pl/cookbook/@home.texy @@ -1,6 +1,9 @@ Instrukcje i procedury ********************** +.[perex] +Przykładowe kody i przepisy na wykonanie typowych zadań z Latte. + - [Wszystko, co kiedykolwiek chciałeś wiedzieć o {iterateWhile} |iteratewhile]. - [Jak pisać zapytania SQL w Latte? |how-to-write-sql-queries-in-latte] - [Migracja z PHP |migration-from-php] diff --git a/latte/pl/tags.texy b/latte/pl/tags.texy index 4b258033dd..d3ec2083de 100644 --- a/latte/pl/tags.texy +++ b/latte/pl/tags.texy @@ -102,7 +102,7 @@ Przegląd i opis wszystkich znaczników systemu templatek Latte, które są domy .[table-latte-tags language-latte] |## Dostępne tylko z Nette Forms -| `{form}`... `{/form}` | [renderuje znaczniki formularzy |forms:rendering#Latte] +| `{form}`... `{/form}` | [renderuje znaczniki formularzy |forms:rendering#form] | `{label}`... `{/label}` | [renderuje etykietę elementu formularza |forms:rendering#label-input] | `{input}` | [renderuje element formularza |forms:rendering#label-input] | `{inputError}` | [drukuje komunikat o błędzie elementu formularza |forms:rendering#inputError] @@ -110,6 +110,7 @@ Przegląd i opis wszystkich znaczników systemu templatek Latte, które są domy | `{formPrint}` | [projekt kod latte dla formularza |forms:rendering#formPrint] | `{formPrintClass}` | [zaprojektuj kod PHP dla klasy z danymi formularza |forms:in-presenter#Mapping-to-Classes] | `{formContext}`... `{/formContext}` | [częściowy rysunek formy |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [renderowanie pojemnika na formularze |forms:rendering#special-cases] Pisanie .[#toc-printing] @@ -595,7 +596,7 @@ Znacznik `{include}` ładuje i renderuje podany szablon. Jeśli mówimy w język Szablony zagnieżdżone nie mają dostępu do aktywnych zmiennych kontekstowych, mają tylko dostęp do zmiennych globalnych. -Możesz przekazać inne zmienne w następujący sposób: +Do wstawionego szablonu można przekazać zmienne w następujący sposób: ```latte {* od Latte 2.9 *} diff --git a/latte/pl/template-inheritance.texy b/latte/pl/template-inheritance.texy index 680bc0ed23..0005dea072 100644 --- a/latte/pl/template-inheritance.texy +++ b/latte/pl/template-inheritance.texy @@ -246,7 +246,7 @@ Można również wylistować blok z innego szablonu: Wyrenderowany blok nie ma dostępu do aktywnych zmiennych kontekstowych, z wyjątkiem sytuacji, gdy blok jest zdefiniowany w tym samym pliku, w którym został wstawiony. Ma jednak dostęp do zmiennych globalnych. -Możesz przekazać zmienne w następujący sposób: +Do bloku można przekazać zmienne w następujący sposób: ```latte {* od Latte 2.9 *} @@ -603,7 +603,7 @@ W Latte istnieją różne rodzaje dziedziczenia i ponownego wykorzystania kodu. ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/pl/why-use.texy b/latte/pl/why-use.texy new file mode 100644 index 0000000000..d975bf6792 --- /dev/null +++ b/latte/pl/why-use.texy @@ -0,0 +1,80 @@ +Dlaczego warto używać szablonów? +******************************** + + +Dlaczego warto używać systemu szablonów w PHP? .[#toc-why-should-i-use-a-templating-system-in-php] +-------------------------------------------------------------------------------------------------- + +Po co używać systemu szablonów w PHP, skoro sam PHP jest językiem szablonów? + +Najpierw krótko podsumujmy historię tego języka, która jest pełna ciekawych zwrotów akcji. Jednym z pierwszych języków programowania wykorzystywanych do generowania stron HTML był język C. Szybko jednak okazało się, że używanie go do tego celu jest niepraktyczne. Rasmus Lerdorf stworzył więc PHP, który ułatwiał generowanie dynamicznego HTML z językiem C na zapleczu. PHP został pierwotnie zaprojektowany jako język szablonów, ale z czasem zyskał dodatkowe funkcje i stał się pełnoprawnym językiem programowania. + +Mimo to, nadal funkcjonuje jako język szablonów. Plik PHP może zawierać stronę HTML, w której zmienne są wyprowadzane za pomocą `<?= $foo ?>`, itd. + +Na początku historii PHP powstał system szablonów Smarty, którego celem było ścisłe oddzielenie wyglądu (HTML/CSS) od logiki aplikacji. Celowo zapewniał on bardziej ograniczony język niż sam PHP, tak że np. programista nie mógł wykonać zapytania do bazy danych z szablonu itp. Z drugiej strony, stanowił dodatkową zależność w projektach, zwiększał ich złożoność i wymagał od programistów nauki nowego języka Smarty. Takie korzyści były kontrowersyjne, a do szablonów nadal używano zwykłego PHP. + +Z czasem systemy szablonów zaczęły być użyteczne. Wprowadziły one takie pojęcia jak [dziedziczenie |template-inheritance], [tryb piaskownicy |sandbox] i szereg innych cech, które znacznie uprościły tworzenie szablonów w porównaniu z czystym PHP. Na pierwszy plan wysunął się temat bezpieczeństwa, istnienia luk w zabezpieczeniach [takich jak XSS |safety-first], oraz konieczność [ucieczki |#What is escaping]. Systemy szablonów wprowadziły auto-escaping, aby wyeliminować ryzyko, że programista zapomni o tym i stworzy poważną dziurę w bezpieczeństwie (wkrótce zobaczymy, że ma to pewne pułapki). + +Obecnie korzyści płynące z zastosowania systemów szablonowych znacznie przewyższają koszty związane z ich wdrożeniem. Dlatego korzystanie z nich ma sens. + + +Dlaczego Latte jest lepsze niż Twig czy Blade? .[#toc-why-is-latte-better-than-twig-or-blade] +--------------------------------------------------------------------------------------------- + +Powodów jest kilka - niektóre są przyjemne, a inne ogromnie przydatne. Latte jest połączeniem przyjemnego z pożytecznym. + +*Po pierwsze, przyjemne:* Latte ma taką samą [składnię jak PHP |syntax#Latte Understands PHP]. Jedyną różnicą jest notacja znaczników, preferująca krótsze `{` i `}` zamiast `<?=` i `?>`. Oznacza to, że nie musisz uczyć się nowego języka. Koszty szkolenia są minimalne. Co najważniejsze, podczas rozwoju nie musisz ciągle "przełączać się" między językiem PHP a językiem szablonów, ponieważ oba są takie same. Jest to w przeciwieństwie do szablonów Twig, które używają języka Python, zmuszając programistę do przełączania się między dwoma różnymi językami. + +*Wszystkie systemy szablonów, takie jak Twig, Blade czy Smarty, ewoluowały tak, by zawierać ochronę przed XSS w postaci automatycznej [ucieczki |#What is escaping]. A dokładniej, automatycznego wywołania funkcji `htmlspecialchars()`. Twórcy Latte zdali sobie jednak sprawę, że nie jest to wcale właściwe rozwiązanie. Wynika to z faktu, że różne części dokumentu wymagają różnych metod escapingu. Naiwny auto-escaping jest niebezpieczną funkcją, ponieważ tworzy fałszywe poczucie bezpieczeństwa. + +Aby auto-escaping był funkcjonalny i niezawodny, musi rozpoznać, w którym miejscu dokumentu dane są wyprowadzane (nazywamy to kontekstami) i odpowiednio dobrać funkcję ucieczki. Dlatego musi być [wrażliwy na kontekst |safety-first#Context-Aware Escaping]. I to właśnie potrafi Latte. Rozumie on HTML. Nie postrzega szablonu jako zwykłego ciągu znaków, ale rozumie, czym są znaczniki, atrybuty itp. Dlatego ucieka inaczej w tekście HTML, wewnątrz znaczników HTML, wewnątrz JavaScript itp. + +Latte jest pierwszym i jedynym systemem szablonów PHP z ucieczką kontekstową. Jest to jedyny naprawdę bezpieczny system szablonów. + +*I jeszcze jeden przyjemny powód:* Ponieważ Latte rozumie HTML, oferuje inne bardzo przyjemne funkcje. Na przykład [n:attributes |syntax#n:attributes]. Albo możliwość [sprawdzania linków |safety-first#Link checking]. I wiele innych. + + +Co to jest escaping? .[#toc-what-is-escaping] +--------------------------------------------- + +Ucieczka to proces polegający na zastępowaniu znaków o specjalnym znaczeniu odpowiednimi sekwencjami podczas wstawiania jednego ciągu znaków do drugiego w celu uniknięcia niepożądanych efektów lub błędów. Na przykład wstawiając do tekstu HTML ciąg znaków, w którym znak `<` ma specjalne znaczenie, ponieważ oznacza początek znacznika, zastępujemy go odpowiednią sekwencją, którą jest encja HTML `<`. Dzięki temu przeglądarka poprawnie wyświetla symbol `<`. + +Prostym przykładem ucieczki bezpośrednio podczas pisania kodu PHP jest wstawienie do ciągu znaków cudzysłowu poprzez umieszczenie przed nim odwrotnego ukośnika. + +Ucieczkę omawiamy bardziej szczegółowo w rozdziale [Jak bronić się przed XSS |safety-first#How to Defend Against XSS?]. + + +Czy z szablonu Latte można wykonać zapytanie do bazy danych? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +--------------------------------------------------------------------------------------------------------------------------- + +W szablonach można pracować z obiektami, które przekazuje do nich programista. Jeśli programista chce, może przekazać do szablonu obiekt bazy danych i wykonać zapytanie. Jeśli zamierzają to zrobić, nie ma powodu, by im to uniemożliwić. + +Inna sytuacja pojawia się, jeśli chcesz dać klientom lub zewnętrznym koderom możliwość edycji szablonów. W tym przypadku zdecydowanie nie chcesz, aby mieli dostęp do bazy danych. Oczywiście nie przekażesz obiektu bazy danych do szablonu, ale co jeśli dostęp do niej będzie możliwy poprzez inny obiekt? Rozwiązaniem jest [tryb piaskownicy |sandbox], który pozwala zdefiniować, jakie metody mogą być wywoływane w szablonach. Dzięki temu nie musisz się martwić o naruszenie bezpieczeństwa. + + +Jakie są główne różnice pomiędzy systemami templatkowania takimi jak Latte, Twig i Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +Różnice pomiędzy systemami templatkowania takimi jak Latte, Twig i Blade polegają głównie na ich składni, bezpieczeństwie i integracji z frameworkami: + +- Latte: wykorzystuje składnię języka PHP, dzięki czemu jest łatwiejszy do nauczenia i użycia. Zapewnia najwyższej klasy ochronę przed atakami XSS. +- Twig: używa składni podobnej do Pythona, która jest zupełnie inna niż PHP. Ucieka bez rozróżniania kontekstu. Jest dobrze zintegrowany z frameworkiem Symfony. +- Blade: używa mieszanki PHP i własnej składni. Ucieka bez rozróżniania kontekstu. Jest ściśle zintegrowany z funkcjami i ekosystemem Laravel. + + +Czy warto, aby firmy korzystały z systemu szablonowania? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +------------------------------------------------------------------------------------------------------------------------ + +Po pierwsze, koszty związane ze szkoleniem, użytkowaniem i ogólnymi korzyściami różnią się znacznie w zależności od systemu. System szablonów Latte, dzięki wykorzystaniu składni PHP, znacznie ułatwia naukę programistom znającym już ten język. Zazwyczaj wystarczy kilka godzin, aby programista w wystarczającym stopniu zapoznał się z Latte, co zmniejsza koszty szkolenia i przyspiesza przyswajanie technologii, a co najważniejsze - efektywność w codziennym użytkowaniu. + +Dodatkowo Latte zapewnia wysoki poziom ochrony przed podatnością XSS dzięki unikalnej technologii context-aware escaping. Ochrona ta jest kluczowa dla zapewnienia bezpieczeństwa aplikacji internetowych i zminimalizowania ryzyka ataków, które mogłyby zagrozić użytkownikom lub danym firmowym. Bezpieczeństwo aplikacji internetowych jest również ważne dla utrzymania dobrej reputacji firmy. Problemy z bezpieczeństwem mogą prowadzić do utraty zaufania klientów i zniszczenia reputacji firmy na rynku. + +Korzystanie z Latte zmniejsza również ogólne koszty rozwoju i utrzymania aplikacji poprzez ułatwienie obu tych czynności. Dlatego korzystanie z systemu szablonowania jest zdecydowanie warte zachodu. + + +Czy Latte wpływa na wydajność aplikacji internetowych? .[#toc-does-latte-affect-the-performance-of-web-applications] +-------------------------------------------------------------------------------------------------------------------- + +Chociaż szablony Latte są przetwarzane szybko, ten aspekt nie ma większego znaczenia. Powodem jest to, że parsowanie plików występuje tylko raz podczas pierwszego wyświetlenia. Następnie są one kompilowane do kodu PHP, przechowywane na dysku i uruchamiane przy każdym kolejnym żądaniu bez konieczności ponownej kompilacji. + +Tak właśnie działa to w środowisku produkcyjnym. Podczas rozwoju szablony Latte są rekompilowane za każdym razem, gdy zmienia się ich zawartość, więc programista zawsze widzi aktualną wersję. diff --git a/latte/pt/@left-menu.texy b/latte/pt/@left-menu.texy index bc3950eae9..09fa4aa164 100644 --- a/latte/pt/@left-menu.texy +++ b/latte/pt/@left-menu.texy @@ -1,4 +1,5 @@ - [Como Começar |Guide] +- [Por que usar modelos? |why-use] - Conceitos - [Segurança em primeiro lugar |Safety First] - [Herança do modelo |Template Inheritance] diff --git a/latte/pt/cookbook/@home.texy b/latte/pt/cookbook/@home.texy index 66b4d5e7e9..d668db4db1 100644 --- a/latte/pt/cookbook/@home.texy +++ b/latte/pt/cookbook/@home.texy @@ -1,6 +1,9 @@ Cookbook ******** +.[perex] +Exemplos de códigos e receitas para a realização de tarefas comuns com Latte. + - [Tudo o que você sempre quis saber sobre {alfabetizar-assim} |iteratewhile] - [Como escrever consultas SQL em Latte? |how-to-write-sql-queries-in-latte] - [Migração do PHP |migration-from-php] diff --git a/latte/pt/tags.texy b/latte/pt/tags.texy index bb7b41cac2..cca0271f1a 100644 --- a/latte/pt/tags.texy +++ b/latte/pt/tags.texy @@ -102,7 +102,7 @@ Resumo e descrição de todas as etiquetas incorporadas no Latte. .[table-latte-tags language-latte] |## Disponível apenas com Formulários Nette -| `{form}`... `{/form}` | [imprime um elemento do formulário |forms:rendering#latte] +| `{form}`... `{/form}` | [imprime um elemento do formulário |forms:rendering#form] | `{label}`... `{/label}` | [imprime uma etiqueta de entrada de formulário |forms:rendering#label-input] | `{input}` | [imprime um elemento de entrada do formulário |forms:rendering#label-input] | `{inputError}` | [imprime mensagem de erro para o elemento de entrada do formulário |forms:rendering#inputError] @@ -110,6 +110,7 @@ Resumo e descrição de todas as etiquetas incorporadas no Latte. | `{formPrint}` | [gera o projeto do formulário Latte |forms:rendering#formPrint] | `{formPrintClass}` | [imprime a classe PHP para dados de formulário |forms:in-presenter#mapping-to-classes] | `{formContext}`... `{/formContext}` | [versão parcial da forma |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [tornando o recipiente do formulário |forms:rendering#special-cases] Impressão .[#toc-printing] @@ -595,7 +596,7 @@ A tag `{include}` carrega e torna o modelo especificado. Em nossa linguagem PHP Os modelos incluídos não têm acesso às variáveis do contexto ativo, mas têm acesso às variáveis globais. -Você pode passar as variáveis desta forma: +Você pode passar variáveis para o modelo inserido da seguinte maneira: ```latte {* since Latte 2.9 *} diff --git a/latte/pt/template-inheritance.texy b/latte/pt/template-inheritance.texy index fc2c2f3f05..cd1e579d76 100644 --- a/latte/pt/template-inheritance.texy +++ b/latte/pt/template-inheritance.texy @@ -246,7 +246,7 @@ Você também pode exibir bloco a partir de outro modelo: O bloco impresso não tem acesso às variáveis do contexto ativo, exceto se o bloco estiver definido no mesmo arquivo onde está incluído. No entanto, eles têm acesso às variáveis globais. -Você pode passar as variáveis desta forma: +Você pode passar variáveis para o bloco da seguinte maneira: ```latte {* desde Latte 2.9 *} @@ -603,7 +603,7 @@ Há vários tipos de herança e reutilização de código em Latte. Vamos resumi ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/pt/why-use.texy b/latte/pt/why-use.texy new file mode 100644 index 0000000000..37d8855cdd --- /dev/null +++ b/latte/pt/why-use.texy @@ -0,0 +1,80 @@ +Por que usar modelos? +********************* + + +Por que eu deveria usar um sistema de modelos em PHP? .[#toc-why-should-i-use-a-templating-system-in-php] +--------------------------------------------------------------------------------------------------------- + +Por que usar um sistema de modelos no PHP se o próprio PHP é uma linguagem de modelos? + +Vamos primeiro recapitular brevemente a história dessa linguagem, que é cheia de reviravoltas interessantes. Uma das primeiras linguagens de programação usadas para gerar páginas HTML foi a linguagem C. No entanto, logo ficou claro que usá-la para esse fim não era prático. Rasmus Lerdorf criou então o PHP, que facilitou a geração de HTML dinâmico com a linguagem C no backend. O PHP foi originalmente projetado como uma linguagem de modelos, mas com o tempo adquiriu recursos adicionais e se tornou uma linguagem de programação completa. + +No entanto, ele ainda funciona como uma linguagem de modelos. Um arquivo PHP pode conter uma página HTML, na qual as variáveis são geradas usando `<?= $foo ?>`, etc. + +No início da história do PHP, foi criado o sistema de modelos Smarty, com o objetivo de separar estritamente a aparência (HTML/CSS) da lógica do aplicativo. Ele deliberadamente forneceu uma linguagem mais limitada do que o próprio PHP, de modo que, por exemplo, um desenvolvedor não poderia fazer uma consulta ao banco de dados a partir de um modelo, etc. Por outro lado, ela representava uma dependência adicional nos projetos, aumentava sua complexidade e exigia que os programadores aprendessem uma nova linguagem Smarty. Esses benefícios foram controversos, e o PHP simples continuou a ser usado para modelos. + +Com o tempo, os sistemas de modelos começaram a se tornar úteis. Eles introduziram conceitos como [herança |template-inheritance], [modo sandbox |sandbox] e uma série de outros recursos que simplificaram significativamente a criação de modelos em comparação com o PHP puro. O tópico de segurança, a existência de [vulnerabilidades como XSS |safety-first] e a necessidade de [escape |#What is escaping] vieram à tona. Os sistemas de modelos introduziram o escape automático para eliminar o risco de um programador esquecê-lo e criar uma grave falha de segurança (veremos em breve que isso tem algumas armadilhas). + +Atualmente, os benefícios dos sistemas de modelos superam em muito os custos associados à sua implementação. Portanto, faz sentido usá-los. + + +Por que o Latte é melhor que o Twig ou o Blade? .[#toc-why-is-latte-better-than-twig-or-blade] +---------------------------------------------------------------------------------------------- + +Há vários motivos - alguns são agradáveis e outros são imensamente úteis. O Latte é uma combinação de agradável e útil. + +*Primeiro, o agradável:* O Latte tem a mesma [sintaxe do PHP |syntax#Latte Understands PHP]. A única diferença está na notação das tags, preferindo `{` e `}` mais curtos em vez de `<?=` e `?>`. Isso significa que você não precisa aprender um novo idioma. Os custos de treinamento são mínimos. O mais importante é que, durante o desenvolvimento, você não precisa "alternar" constantemente entre a linguagem PHP e a linguagem do modelo, pois ambas são a mesma. Isso é diferente dos modelos Twig, que usam a linguagem Python, forçando o programador a alternar entre duas linguagens diferentes. + +*Agora, o motivo extremamente útil:* Todos os sistemas de modelos, como Twig, Blade ou Smarty, evoluíram para incluir proteção contra XSS na forma de [escape |#What is escaping] automático. Mais precisamente, a chamada automática da função `htmlspecialchars()`. Entretanto, os criadores do Latte perceberam que essa não é a solução correta. Isso ocorre porque diferentes partes do documento exigem diferentes métodos de escape. O escape automático ingênuo é um recurso perigoso porque cria uma falsa sensação de segurança. + +Para que o escape automático seja funcional e confiável, ele deve reconhecer em que parte do documento os dados estão sendo gerados (chamamos isso de contextos) e escolher a função de escape de acordo. Portanto, ele deve ser [sensível ao contexto |safety-first#Context-Aware Escaping]. E é isso que o Latte pode fazer. Ele entende HTML. Ele não percebe o modelo como apenas uma sequência de caracteres, mas entende o que são tags, atributos etc. Portanto, ele faz escapes diferentes no texto HTML, nas tags HTML, no JavaScript etc. + +O Latte é o primeiro e único sistema de modelos PHP com escape sensível ao contexto. Ele representa o único sistema de modelos realmente seguro. + +*E outro motivo agradável: como o Latte entende HTML, ele oferece outros recursos muito agradáveis. Por exemplo, [n:attributes |syntax#n:attributes]. Ou a capacidade de [verificar links |safety-first#Link checking]. E muito mais. + + +O que é escape? .[#toc-what-is-escaping] +---------------------------------------- + +Escaping é um processo que envolve a substituição de caracteres com significados especiais por sequências correspondentes ao inserir uma cadeia de caracteres em outra para evitar efeitos indesejados ou erros. Por exemplo, ao inserir uma cadeia de caracteres em um texto HTML, no qual o caractere `<` tem um significado especial porque indica o início de uma tag, nós o substituímos pela sequência correspondente, que é a entidade HTML `<`. Isso permite que o navegador exiba corretamente o símbolo `<`. + +Um exemplo simples de escape direto ao escrever código PHP é inserir uma aspa em uma cadeia de caracteres colocando uma barra invertida na frente dela. + +Discutiremos o escape em mais detalhes no capítulo [Como se defender contra XSS |safety-first#How to Defend Against XSS?]. + + +Uma consulta ao banco de dados pode ser executada em um modelo Latte? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +------------------------------------------------------------------------------------------------------------------------------------ + +Nos modelos, você pode trabalhar com objetos que o programador passa para eles. Se o programador quiser, ele poderá passar um objeto de banco de dados para o modelo e executar uma consulta. Se ele pretende fazer isso, não há motivo para impedi-lo. + +Uma situação diferente ocorre se você quiser dar aos clientes ou programadores externos a capacidade de editar modelos. Nesse caso, você definitivamente não quer que eles tenham acesso ao banco de dados. É claro que você não passará o objeto de banco de dados para o modelo, mas e se ele puder ser acessado por meio de outro objeto? A solução é o [modo sandbox |sandbox], que permite que você defina quais métodos podem ser chamados nos modelos. Graças a isso, você não precisa se preocupar com violações de segurança. + + +Quais são as principais diferenças entre sistemas de gabaritos como Latte, Twig e Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +As diferenças entre sistemas de modelos como Latte, Twig e Blade residem principalmente em sua sintaxe, segurança e integração com as estruturas: + +- Latte: utiliza a sintaxe da linguagem PHP, facilitando a aprendizagem e o uso. Ele oferece proteção de primeira linha contra ataques XSS. +- Twig: utiliza uma sintaxe parecida com a do Python, que é bem diferente do PHP. Ele escapa sem distinção de contexto. Está bem integrado com a estrutura Symfony. +- Blade: utiliza uma mistura de PHP e sintaxe personalizada. Ela escapa sem distinção de contexto. Está bem integrado com as características de Laravel e o ecossistema. + + +Vale a pena para as empresas usar um sistema de modelos? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +------------------------------------------------------------------------------------------------------------------------ + +Em primeiro lugar, os custos associados ao treinamento, ao uso e aos benefícios gerais variam significativamente dependendo do sistema. O sistema de modelos Latte, graças ao seu uso da sintaxe PHP, simplifica muito o aprendizado para programadores já familiarizados com esta linguagem. Normalmente leva algumas horas para que um programador se familiarize suficientemente com o Latte, reduzindo os custos de treinamento e acelerando a adoção da tecnologia e, o mais importante, a eficiência no uso diário. + +Além disso, Latte oferece um alto nível de proteção contra a vulnerabilidade XSS, graças a sua tecnologia de escape sensível ao contexto único. Esta proteção é crucial para garantir a segurança das aplicações web e minimizar o risco de ataques que possam colocar em risco os usuários ou os dados da empresa. A segurança das aplicações web também é importante para manter a boa reputação de uma empresa. As questões de segurança podem levar à perda de confiança dos clientes e prejudicar a reputação da empresa no mercado. + +O uso do Latte também reduz os custos gerais de desenvolvimento e manutenção, tornando ambos mais fáceis. Portanto, o uso de um sistema de modelos vale definitivamente a pena. + + +O Latte afeta o desempenho dos aplicativos da Web? .[#toc-does-latte-affect-the-performance-of-web-applications] +---------------------------------------------------------------------------------------------------------------- + +Embora os modelos Latte sejam processados rapidamente, este aspecto realmente não importa. A razão é que os arquivos de análise ocorrem apenas uma vez durante a primeira exibição. Eles são então compilados em código PHP, armazenados em disco e executados em cada solicitação subseqüente sem necessidade de recompilação. + +É assim que funciona em um ambiente de produção. Durante o desenvolvimento, os modelos Latte são recompilados cada vez que seu conteúdo muda, de modo que o desenvolvedor sempre vê a versão atual. diff --git a/latte/ro/@left-menu.texy b/latte/ro/@left-menu.texy index 4caf1f8632..fd0ebd9728 100644 --- a/latte/ro/@left-menu.texy +++ b/latte/ro/@left-menu.texy @@ -1,4 +1,5 @@ - [Noțiuni introductive |Guide] +- [De ce să folosiți șabloane? |why-use] - Concepte - [Siguranța mai întâi |Safety First] - [Moștenirea șablonului |Template Inheritance] diff --git a/latte/ro/cookbook/@home.texy b/latte/ro/cookbook/@home.texy index 41830deb12..6eaa0a78b2 100644 --- a/latte/ro/cookbook/@home.texy +++ b/latte/ro/cookbook/@home.texy @@ -1,6 +1,9 @@ Carte de bucate *************** +.[perex] +Exemple de coduri și rețete pentru realizarea unor sarcini obișnuite cu Latte. + - [Tot ce ați vrut întotdeauna să știți despre {iterateWhile} |iteratewhile] - [Cum să scrieți interogări SQL în Latte? |how-to-write-sql-queries-in-latte] - [Migrarea de la PHP |migration-from-php] diff --git a/latte/ro/tags.texy b/latte/ro/tags.texy index c300dab07e..336ad7cf14 100644 --- a/latte/ro/tags.texy +++ b/latte/ro/tags.texy @@ -102,7 +102,7 @@ Rezumat și descriere a tuturor etichetelor Latte încorporate. .[table-latte-tags language-latte] |## Disponibil numai cu Nette Forms -| `{form}`... `{/form}` | [tipărește un element de formular |forms:rendering#latte] +| `{form}`... `{/form}` | [tipărește un element de formular |forms:rendering#form] | `{label}`... `{/label}` | [tipărește o etichetă de intrare a unui formular |forms:rendering#label-input] | `{input}` | [tipărește un element de intrare în formular |forms:rendering#label-input] | `{inputError}` | [tipărește mesajul de eroare pentru elementul de intrare al formularului |forms:rendering#inputError] @@ -110,6 +110,7 @@ Rezumat și descriere a tuturor etichetelor Latte încorporate. | `{formPrint}` | [generează schița unui formular Latte |forms:rendering#formPrint] | `{formPrintClass}` | [tipărește clasa PHP pentru datele formularului |forms:in-presenter#mapping-to-classes] | `{formContext}`... `{/formContext}` | [redarea parțială a formularului |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [redarea formularului container |forms:rendering#special-cases] Imprimare .[#toc-printing] @@ -595,7 +596,7 @@ Eticheta `{include}` încarcă și redă șablonul specificat. În limbajul nost Șabloanele incluse nu au acces la variabilele contextului activ, dar au acces la variabilele globale. -Puteți transmite variabilele în acest mod: +Puteți transmite variabile către șablonul inserat în felul următor: ```latte {* de la Latte 2.9 *} diff --git a/latte/ro/template-inheritance.texy b/latte/ro/template-inheritance.texy index 6a3e78166d..c0cf52f5a3 100644 --- a/latte/ro/template-inheritance.texy +++ b/latte/ro/template-inheritance.texy @@ -246,7 +246,7 @@ De asemenea, puteți afișa un bloc dintr-un alt șablon: Blocul tipărit nu are acces la variabilele contextului activ, cu excepția cazului în care blocul este definit în același fișier în care este inclus. Cu toate acestea, ele au acces la variabilele globale. -Puteți transmite variabilele în acest mod: +Puteți transmite variabile către bloc în felul următor: ```latte {* de la Latte 2.9 *} @@ -603,7 +603,7 @@ Există diferite tipuri de moștenire și reutilizare a codului în Latte. Să r ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/ro/why-use.texy b/latte/ro/why-use.texy new file mode 100644 index 0000000000..2f086d950f --- /dev/null +++ b/latte/ro/why-use.texy @@ -0,0 +1,80 @@ +De ce să folosiți șabloane? +*************************** + + +De ce ar trebui să folosesc un sistem de șabloane în PHP? .[#toc-why-should-i-use-a-templating-system-in-php] +------------------------------------------------------------------------------------------------------------- + +De ce să folosiți un sistem de șabloane în PHP când PHP însuși este un limbaj de creare de șabloane? + +Să recapitulăm mai întâi pe scurt istoria acestui limbaj, care este plină de răsturnări de situație interesante. Unul dintre primele limbaje de programare utilizate pentru generarea de pagini HTML a fost limbajul C. Cu toate acestea, a devenit curând evident că utilizarea acestuia în acest scop era nepractică. Astfel, Rasmus Lerdorf a creat PHP, care a facilitat generarea de HTML dinamic cu ajutorul limbajului C în backend. PHP a fost conceput inițial ca un limbaj de modelare, dar în timp a dobândit caracteristici suplimentare și a devenit un limbaj de programare cu drepturi depline. + +Cu toate acestea, el funcționează în continuare ca un limbaj de modelare. Un fișier PHP poate conține o pagină HTML, în care variabilele sunt afișate cu ajutorul funcției `<?= $foo ?>`, etc. + +La începutul istoriei PHP, a fost creat sistemul de șabloane Smarty, cu scopul de a separa strict aspectul (HTML/CSS) de logica aplicației. Acesta a oferit în mod deliberat un limbaj mai limitat decât PHP însuși, astfel încât, de exemplu, un programator nu putea face o interogare a bazei de date dintr-un șablon etc. Pe de altă parte, a reprezentat o dependență suplimentară în proiecte, a crescut complexitatea acestora și a cerut programatorilor să învețe un nou limbaj Smarty. Astfel de beneficii au fost controversate, iar PHP simplu a continuat să fie folosit pentru șabloane. + +Cu timpul, sistemele de șabloane au început să devină utile. Acestea au introdus concepte precum [moștenirea |template-inheritance], [modul sandbox |sandbox] și o serie de alte caracteristici care au simplificat semnificativ crearea de șabloane în comparație cu PHP pur. Subiectul securității, existența unor [vulnerabilități precum XSS |safety-first] și necesitatea de a [evada |#What is escaping] a ajuns în prim-plan. Sistemele de șabloane au introdus auto-escaparea pentru a elimina riscul ca un programator să o uite și să creeze o gaură de securitate serioasă (vom vedea în scurt timp că acest lucru are anumite capcane). + +Astăzi, beneficiile sistemelor de șabloane depășesc cu mult costurile asociate cu implementarea lor. Prin urmare, este logic să le folosim. + + +De ce este Latte mai bun decât Twig sau Blade? .[#toc-why-is-latte-better-than-twig-or-blade] +--------------------------------------------------------------------------------------------- + +Există mai multe motive - unele sunt plăcute, iar altele sunt extrem de utile. Latte este o combinație de plăcut și util. + +*În primul rând, plăcutul:* Latte are aceeași [sintaxă ca și PHP |syntax#Latte Understands PHP]. Singura diferență este în notarea etichetelor, preferând mai scurte `{` și `}` în loc de `<?=` și `?>`. Acest lucru înseamnă că nu trebuie să învățați un nou limbaj. Costurile de formare sunt minime. Cel mai important, în timpul dezvoltării, nu trebuie să "comutați" în mod constant între limbajul PHP și limbajul șabloanelor, deoarece ambele sunt identice. Acest lucru este diferit de șabloanele Twig, care utilizează limbajul Python, ceea ce obligă programatorul să treacă de la un limbaj la altul. + +*Acum, pentru motivul extrem de util:* Toate sistemele de șabloane, cum ar fi Twig, Blade sau Smarty, au evoluat pentru a include protecție împotriva XSS sub forma [scăpării |#What is escaping] automate. Mai exact, apelarea automată a funcției `htmlspecialchars()`. Cu toate acestea, creatorii lui Latte și-au dat seama că aceasta nu este deloc soluția potrivită. Acest lucru se datorează faptului că diferite părți ale documentului necesită diferite metode de escaping. Escaparea automată naivă este o funcție periculoasă, deoarece creează un fals sentiment de securitate. + +Pentru ca auto-escaping-ul să fie funcțional și fiabil, acesta trebuie să recunoască în ce parte a documentului sunt emise datele (numim aceste contexte) și să aleagă funcția de scăpare în consecință. Prin urmare, trebuie să fie [sensibilă la context |safety-first#Context-Aware Escaping]. Și asta este ceea ce poate face Latte. Înțelege HTML. Nu percepe șablonul ca pe un simplu șir de caractere, ci înțelege ce sunt etichetele, atributele etc. Prin urmare, el evadează diferit în textul HTML, în interiorul tag-urilor HTML, în interiorul JavaScript, etc. + +Latte este primul și singurul sistem de șabloane PHP cu escaping sensibil la context. Acesta reprezintă singurul sistem de șabloane cu adevărat sigur. + +*Și un alt motiv plăcut:* Deoarece Latte înțelege HTML, oferă și alte caracteristici foarte plăcute. De exemplu, [n:attributes |syntax#n:attributes]. Sau posibilitatea de a [verifica legăturile |safety-first#Link checking]. Și multe altele. + + +Ce înseamnă "escaping"? .[#toc-what-is-escaping] +------------------------------------------------ + +Escaping-ul este un proces care presupune înlocuirea caracterelor cu semnificații speciale cu secvențe corespunzătoare atunci când se inserează un șir de caractere în altul pentru a preveni efectele nedorite sau erorile. De exemplu, atunci când se inserează un șir în textul HTML, în care caracterul `<` are o semnificație specială deoarece indică începutul unei etichete, îl înlocuim cu secvența corespunzătoare, care este entitatea HTML `<`. Acest lucru permite browserului să afișeze corect simbolul `<`. + +Un exemplu simplu de evadare directă la scrierea codului PHP este inserarea unui ghilimele într-un șir de caractere prin plasarea unei backslash în fața acestuia. + +Discutăm mai detaliat despre escaping în capitolul [Cum să vă apărați împotriva XSS |safety-first#How to Defend Against XSS?]. + + +Poate fi executată o interogare a unei baze de date dintr-un șablon Latte? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +----------------------------------------------------------------------------------------------------------------------------------------- + +În șabloane, puteți lucra cu obiecte pe care programatorul le transmite. Dacă programatorul dorește, poate trece un obiect de bază de date în șablon și poate efectua o interogare. Dacă intenționează să facă acest lucru, nu există niciun motiv pentru a-i împiedica. + +O situație diferită apare dacă doriți să oferiți clienților sau programatorilor externi posibilitatea de a edita șabloanele. În acest caz, cu siguranță nu doriți ca aceștia să aibă acces la baza de date. Bineînțeles, nu veți trece obiectul bazei de date în șablon, dar ce se întâmplă dacă acesta poate fi accesat prin intermediul unui alt obiect? Soluția este [modul sandbox |sandbox], care vă permite să definiți ce metode pot fi apelate în șabloane. Datorită acestui lucru, nu trebuie să vă faceți griji cu privire la breșele de securitate. + + +Care sunt principalele diferențe între sistemele de modelare precum Latte, Twig și Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +Diferențele dintre sistemele de modelare precum Latte, Twig și Blade constau în principal în sintaxa, securitatea și integrarea cu cadrele: + +- Latte: utilizează sintaxa limbajului PHP, ceea ce îl face mai ușor de învățat și de utilizat. Oferă o protecție de top împotriva atacurilor XSS. +- Twig: utilizează o sintaxă de tip Python, care este destul de diferită de cea a limbajului PHP. Evadează fără distincție de context. Este bine integrat cu cadrul Symfony. +- Blade: utilizează un amestec de sintaxă PHP și sintaxă personalizată. Evadează fără distincție de context. Este foarte bine integrat cu caracteristicile și ecosistemul Laravel. + + +Merită pentru companii să folosească un sistem de template-uri? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +------------------------------------------------------------------------------------------------------------------------------- + +În primul rând, costurile asociate cu formarea, utilizarea și beneficiile generale variază semnificativ în funcție de sistem. Sistemul de modelare Latte, datorită utilizării sintaxei PHP, simplifică foarte mult învățarea pentru programatorii deja familiarizați cu acest limbaj. De obicei, unui programator îi sunt necesare câteva ore pentru a se familiariza suficient cu Latte, ceea ce reduce costurile de formare și accelerează adoptarea tehnologiei și, cel mai important, eficiența în utilizarea zilnică. + +În plus, Latte oferă un nivel ridicat de protecție împotriva vulnerabilității XSS datorită tehnologiei sale unice de evadare conștientă de context. Această protecție este crucială pentru a asigura securitatea aplicațiilor web și pentru a minimiza riscul de atacuri care ar putea pune în pericol utilizatorii sau datele companiei. De asemenea, securitatea aplicațiilor web este importantă pentru menținerea bunei reputații a unei companii. Problemele de securitate pot duce la pierderea încrederii din partea clienților și pot afecta reputația companiei pe piață. + +Utilizarea Latte reduce, de asemenea, costurile generale de dezvoltare și întreținere, facilitându-le pe ambele. Prin urmare, utilizarea unui sistem de modelare merită cu siguranță. + + +Afectează Latte performanța aplicațiilor web? .[#toc-does-latte-affect-the-performance-of-web-applications] +----------------------------------------------------------------------------------------------------------- + +Deși șabloanele Latte sunt procesate rapid, acest aspect nu contează cu adevărat. Motivul este că analiza fișierelor are loc o singură dată în timpul primei afișări. Ele sunt apoi compilate în cod PHP, stocate pe disc și rulate la fiecare solicitare ulterioară, fără a necesita recompilare. + +Acesta este modul în care funcționează într-un mediu de producție. În timpul dezvoltării, șabloanele Latte sunt recompilate de fiecare dată când conținutul lor se modifică, astfel încât dezvoltatorul vede întotdeauna versiunea curentă. diff --git a/latte/ru/@left-menu.texy b/latte/ru/@left-menu.texy index 16907cbe8e..98c9f92eca 100644 --- a/latte/ru/@left-menu.texy +++ b/latte/ru/@left-menu.texy @@ -1,4 +1,5 @@ - [Начало работы |Guide] +- [Зачем использовать шаблоны? |why-use] - Концепции - [Безопасность превыше всего |Safety First] - [Наследование шаблонов |Template Inheritance] diff --git a/latte/ru/cookbook/@home.texy b/latte/ru/cookbook/@home.texy index 901afa1870..0c94c7cb6c 100644 --- a/latte/ru/cookbook/@home.texy +++ b/latte/ru/cookbook/@home.texy @@ -1,6 +1,9 @@ Кулинарная книга **************** +.[perex] +Примеры кодов и рецепты для выполнения обычных задач с помощью Latte. + - [Все, что вы всегда хотели знать о {iterateWhile} |iteratewhile] - [Как писать SQL-запросы в Latte? |how-to-write-sql-queries-in-latte] - [Миграция с PHP |migration-from-php] diff --git a/latte/ru/tags.texy b/latte/ru/tags.texy index f7b6dc8996..4d2bd8dd44 100644 --- a/latte/ru/tags.texy +++ b/latte/ru/tags.texy @@ -102,7 +102,7 @@ .[table-latte-tags language-latte] |## Доступно только в Nette Forms -| `{form}`... `{/form}` | [печатает элемент формы |forms:rendering#Latte] +| `{form}`... `{/form}` | [печатает элемент формы |forms:rendering#form] | `{label}`... `{/label}` | [печатает метку ввода формы |forms:rendering#label-input] | `{input}` | [печатает элемент ввода формы |forms:rendering#label-input] | `{inputError}` | [печатает сообщение об ошибке для элемента ввода формы |forms:rendering#inputError] @@ -110,6 +110,7 @@ | `{formPrint}` | [генерирует чертеж формы Latte |forms:rendering#formPrint] | `{formPrintClass}` | [печатает PHP класс для данных формы |forms:in-presenter#Mapping-to-Classes] | `{formContext}`... `{/formContext}` | [частичный рендеринг формы |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [рендеринг контейнера формы |forms:rendering#special-cases] Печать .[#toc-printing] @@ -595,7 +596,7 @@ Age: {date('Y') - $birth}<br> Включенные шаблоны не имеют доступа к переменным активного контекста, но имеют доступ к глобальным переменным. -Вы можете передавать переменные таким образом: +Вы можете передавать переменные вставленному шаблону следующим образом: ```latte {* since Latte 2.9 *} diff --git a/latte/ru/template-inheritance.texy b/latte/ru/template-inheritance.texy index 70d52a31db..85aeae0701 100644 --- a/latte/ru/template-inheritance.texy +++ b/latte/ru/template-inheritance.texy @@ -246,7 +246,7 @@ bar: {$bar ?? 'not defined'} // prints: not defined Выводимые блоки не имеют доступа к переменным активного контекста, за исключением случаев, когда блок определен в том же файле, куда он включен. Однако они имеют доступ к глобальным переменным. -Вы можете передавать переменные таким образом: +Вы можете передавать переменные в блок следующим образом: ```latte {* since Latte 2.9 *} @@ -603,7 +603,7 @@ Hi, I am Mary. ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/ru/why-use.texy b/latte/ru/why-use.texy new file mode 100644 index 0000000000..b33b264cec --- /dev/null +++ b/latte/ru/why-use.texy @@ -0,0 +1,80 @@ +Зачем использовать шаблоны? +*************************** + + +Почему я должен использовать систему шаблонов в PHP? .[#toc-why-should-i-use-a-templating-system-in-php] +-------------------------------------------------------------------------------------------------------- + +Зачем использовать систему шаблонов в PHP, если PHP сам является языком шаблонов? + +Давайте сначала кратко вспомним историю этого языка, которая полна интересных поворотов. Одним из первых языков программирования, использовавшихся для генерации HTML-страниц, был язык Си. Однако вскоре стало очевидно, что использовать его для этих целей нецелесообразно. Поэтому Расмус Лердорф создал PHP, который облегчил генерацию динамического HTML с использованием языка C на задней панели. Изначально PHP был разработан как язык шаблонов, но со временем он приобрел дополнительные возможности и стал полноценным языком программирования. + +Тем не менее, он по-прежнему функционирует как язык шаблонов. Файл PHP может содержать HTML-страницу, в которой переменные выводятся с помощью символов `<?= $foo ?>`, и т.д. + +В начале истории PHP была создана система шаблонов Smarty, целью которой было строго отделить внешний вид (HTML/CSS) от логики приложения. Она сознательно предоставляла более ограниченный язык, чем сам PHP, так что, например, разработчик не мог сделать запрос к базе данных из шаблона и т.д. С другой стороны, он представлял собой дополнительную зависимость в проектах, увеличивал их сложность и требовал от программистов изучения нового языка Smarty. Такие преимущества были спорными, и обычный PHP продолжал использоваться для шаблонов. + +Со временем системы шаблонов начали становиться полезными. Они ввели такие понятия, как [наследование |template-inheritance], [режим песочницы |sandbox] и ряд других возможностей, которые значительно упростили создание шаблонов по сравнению с чистым PHP. На первый план вышла тема безопасности, существования таких [уязвимостей, как XSS |safety-first], и необходимости [экранирования |#What is escaping]. Шаблонные системы ввели автоэскейпинг, чтобы устранить риск того, что программист забудет его и создаст серьезную брешь в безопасности (вскоре мы увидим, что это имеет определенные недостатки). + +Сегодня преимущества шаблонных систем значительно превосходят затраты, связанные с их внедрением. Поэтому имеет смысл их использовать. + + +Почему Latte лучше, чем Twig или Blade? .[#toc-why-is-latte-better-than-twig-or-blade] +-------------------------------------------------------------------------------------- + +Есть несколько причин - некоторые из них приятные, а другие - чрезвычайно полезные. Latte - это комбинация приятного и полезного. + +*Сначала о приятном:* Latte имеет тот же [синтаксис, что и PHP |syntax#Latte Understands PHP]. Единственное различие заключается в обозначении тегов, предпочитая более короткие `{` и `}` вместо `<?=` и `?>`. Это означает, что вам не придется учить новый язык. Затраты на обучение минимальны. Самое главное, что во время разработки вам не придется постоянно "переключаться" между языком PHP и языком шаблонов, поскольку они оба одинаковы. Это отличается от шаблонов Twig, которые используют язык Python, заставляя программиста переключаться между двумя разными языками. + +*А теперь о чрезвычайно полезной причине:* Все системы шаблонов, такие как Twig, Blade или Smarty, эволюционировали и включают защиту от XSS в виде автоматического [экранирования |#What is escaping]. Точнее, автоматического вызова функции `htmlspecialchars()`. Однако создатели Latte поняли, что это совсем не верное решение. Это связано с тем, что разные части документа требуют разных методов экранирования. Наивное автоматическое экранирование - это опасная функция, потому что она создает ложное чувство безопасности. + +Чтобы автоэскейпинг был функциональным и надежным, он должен распознавать, в какой части документа выводятся данные (мы называем это контекстами), и выбирать функцию эскейпинга соответствующим образом. Поэтому она должна быть [чувствительной к контексту |safety-first#Context-Aware Escaping]. И это то, что может сделать Latte. Он понимает HTML. Он не воспринимает шаблон как просто строку символов, а понимает, что такое теги, атрибуты и т. д. Поэтому он по-разному экранирует в HTML-тексте, внутри HTML-тегов, внутри JavaScript и т.д. + +Latte - первая и единственная система шаблонов PHP с контекстно-зависимым экранированием. Она представляет собой единственную по-настоящему безопасную систему шаблонов. + +*И еще одна приятная причина:* Поскольку Latte понимает HTML, он предлагает другие очень приятные возможности. Например, [n:attributes |syntax#n:attributes]. Или возможность [проверки ссылок |safety-first#Link checking]. И многое другое. + + +Что такое экранирование? .[#toc-what-is-escaping] +------------------------------------------------- + +Эскейпинг - это процесс замены символов со специальным значением на соответствующие последовательности при вставке одной строки в другую для предотвращения нежелательных эффектов или ошибок. Например, при вставке строки в HTML-текст, в которой символ `<` имеет специальное значение, поскольку обозначает начало тега, мы заменяем его соответствующей последовательностью, которой является HTML-сущность `<`. Это позволяет браузеру правильно отобразить символ `<`. + +Простым примером экранирования непосредственно при написании PHP-кода является вставка кавычек в строку путем размещения перед ними обратной косой черты. + +Более подробно мы обсуждаем экранирование в главе [Как защититься от XSS |safety-first#How to Defend Against XSS?]. + + +Можно ли выполнить запрос к базе данных из шаблона Latte? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +------------------------------------------------------------------------------------------------------------------------ + +В шаблонах можно работать с объектами, которые им передает программист. Если программист захочет, он может передать шаблону объект базы данных и выполнить запрос. Если они намерены это сделать, то нет причин препятствовать им. + +Иная ситуация возникает, если вы хотите предоставить клиентам или внешним программистам возможность редактировать шаблоны. В этом случае вы определенно не захотите, чтобы они имели доступ к базе данных. Конечно, вы не будете передавать объект базы данных в шаблон, но что, если к нему можно получить доступ через другой объект? Решением является [режим песочницы |sandbox], который позволяет вам определить, какие методы могут быть вызваны в шаблонах. Благодаря этому вы можете не беспокоиться о нарушениях безопасности. + + +Каковы основные различия между такими системами шаблонов, как Latte, Twig и Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Различия между такими системами шаблонов, как Latte, Twig и Blade, в основном заключаются в их синтаксисе, безопасности и интеграции с фреймворками: + +- Latte: использует синтаксис языка PHP, что делает его более простым в изучении и использовании. Он обеспечивает первоклассную защиту от XSS-атак. +- Twig: использует Python-подобный синтаксис, который значительно отличается от PHP. Он экранирует без различия контекста. Хорошо интегрирован с фреймворком Symfony. +- Blade: использует смесь PHP и пользовательского синтаксиса. Не различает контекст. Он тесно интегрирован с функциями и экосистемой Laravel. + + +Стоит ли компаниям использовать систему шаблонов? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +----------------------------------------------------------------------------------------------------------------- + +Во-первых, затраты, связанные с обучением, использованием и общими преимуществами, существенно различаются в зависимости от системы. Система шаблонов Latte, благодаря использованию синтаксиса PHP, значительно упрощает обучение для программистов, уже знакомых с этим языком. Обычно программисту требуется несколько часов, чтобы достаточно хорошо освоить Latte, что позволяет снизить затраты на обучение и ускорить освоение технологии и, что самое главное, эффективность в повседневном использовании. + +Кроме того, Latte обеспечивает высокий уровень защиты от XSS-уязвимостей благодаря уникальной технологии контекстно-зависимого экранирования. Эта защита имеет решающее значение для обеспечения безопасности веб-приложений и минимизации риска атак, которые могут поставить под угрозу пользователей или данные компании. Безопасность веб-приложений также важна для поддержания хорошей репутации компании. Проблемы с безопасностью могут привести к потере доверия со стороны клиентов и нанести ущерб репутации компании на рынке. + +Использование Latte также снижает общие затраты на разработку и сопровождение, упрощая и то, и другое. Поэтому использование системы шаблонов определенно стоит того. + + +Влияет ли Latte на производительность веб-приложений? .[#toc-does-latte-affect-the-performance-of-web-applications] +------------------------------------------------------------------------------------------------------------------- + +Хотя шаблоны Latte обрабатываются быстро, этот аспект не имеет особого значения. Причина в том, что парсинг файлов происходит только один раз во время первого показа. Затем они компилируются в PHP-код, сохраняются на диске и запускаются при каждом последующем запросе, не требуя повторной компиляции. + +Вот как это работает в производственной среде. Во время разработки шаблоны Latte перекомпилируются каждый раз, когда меняется их содержимое, поэтому разработчик всегда видит актуальную версию. diff --git a/latte/sl/@left-menu.texy b/latte/sl/@left-menu.texy index 4f1e442445..ba4b8060d8 100644 --- a/latte/sl/@left-menu.texy +++ b/latte/sl/@left-menu.texy @@ -1,4 +1,5 @@ - [Začetek |Guide] +- [Zakaj uporabljati predloge? |why-use] - Pojmi - [Najprej varnost |Safety First] - [Dedovanje predlog |Template Inheritance] diff --git a/latte/sl/cookbook/@home.texy b/latte/sl/cookbook/@home.texy index 51ce4a450e..d6d749e0e4 100644 --- a/latte/sl/cookbook/@home.texy +++ b/latte/sl/cookbook/@home.texy @@ -1,6 +1,9 @@ Kuharska knjiga *************** +.[perex] +Primeri kod in receptov za opravljanje običajnih opravil s programom Latte. + - [Vse, kar ste vedno želeli vedeti o {iterateWhile} |iteratewhile] - [Kako pisati poizvedbe SQL v Latte |how-to-write-sql-queries-in-latte]? - [Migracija iz PHP |migration-from-php] diff --git a/latte/sl/tags.texy b/latte/sl/tags.texy index 8d28e0184a..31125b50ed 100644 --- a/latte/sl/tags.texy +++ b/latte/sl/tags.texy @@ -102,7 +102,7 @@ Povzetek in opis vseh vgrajenih oznak Latte. .[table-latte-tags language-latte] |## Na voljo samo z Nette Forms -| `{form}`... `{/form}` | [natisne element obrazca |forms:rendering#latte] +| `{form}`... `{/form}` | [natisne element obrazca |forms:rendering#form] | `{label}`... `{/label}` | [natisne vhodno oznako obrazca |forms:rendering#label-input] | `{input}` | [natisne vhodni element obrazca |forms:rendering#label-input] | `{inputError}` | [izpiše sporočilo o napaki za vhodni element obrazca |forms:rendering#inputError] @@ -110,6 +110,7 @@ Povzetek in opis vseh vgrajenih oznak Latte. | `{formPrint}` | [ustvari načrt obrazca Latte |forms:rendering#formPrint] | `{formPrintClass}` | [izpiše razred PHP za podatke obrazca |forms:in-presenter#mapping-to-classes] | `{formContext}`... `{/formContext}` | [delni prikaz obrazca |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [upodabljanje vsebnika obrazca |forms:rendering#special-cases] Tiskanje .[#toc-printing] @@ -595,7 +596,7 @@ Oznaka `{include}` naloži in prikaže določeno predlogo. V našem priljubljene Vključene predloge nimajo dostopa do spremenljivk aktivnega konteksta, imajo pa dostop do globalnih spremenljivk. -Spremenljivke lahko posredujete na ta način: +Vstavljeni predlogi lahko spremenljivke posredujete na naslednji način: ```latte {* od različice Latte 2.9 *} diff --git a/latte/sl/template-inheritance.texy b/latte/sl/template-inheritance.texy index d0d873f230..27d71fa9fe 100644 --- a/latte/sl/template-inheritance.texy +++ b/latte/sl/template-inheritance.texy @@ -246,7 +246,7 @@ Prikažete lahko tudi blok iz druge predloge: Izpisani blok nima dostopa do spremenljivk aktivnega konteksta, razen če je blok definiran v isti datoteki, v katero je vključen. Imajo pa dostop do globalnih spremenljivk. -Spremenljivke lahko posredujete na ta način: +Spremenljivke lahko bloku posredujete na naslednji način: ```latte {* od različice Latte 2.9 *} @@ -603,7 +603,7 @@ V sistemu Latte obstajajo različne vrste dedovanja in ponovne uporabe kode. Za ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/sl/why-use.texy b/latte/sl/why-use.texy new file mode 100644 index 0000000000..22cc80f41e --- /dev/null +++ b/latte/sl/why-use.texy @@ -0,0 +1,80 @@ +Zakaj uporabljati predloge? +*************************** + + +Zakaj naj v PHP uporabljam sistem predlog? .[#toc-why-should-i-use-a-templating-system-in-php] +---------------------------------------------------------------------------------------------- + +Zakaj bi v PHP uporabljali sistem predlog, če je PHP sam jezik za oblikovanje predlog? + +Najprej na kratko obnovimo zgodovino tega jezika, ki je polna zanimivih preobratov. Eden prvih programskih jezikov, ki se je uporabljal za izdelavo strani HTML, je bil jezik C. Vendar se je kmalu izkazalo, da je njegova uporaba v ta namen nepraktična. Zato je Rasmus Lerdorf ustvaril PHP, ki je omogočal generiranje dinamičnega HTML z jezikom C v ozadju. PHP je bil prvotno zasnovan kot jezik za oblikovanje predlog, vendar je sčasoma pridobil dodatne funkcije in postal polnopravni programski jezik. + +Kljub temu še vedno deluje kot jezik za oblikovanje predlog. Datoteka PHP lahko vsebuje stran HTML, v kateri se spremenljivke izpišejo z uporabo `<?= $foo ?>`, itd. + +Na začetku zgodovine jezika PHP je bil ustvarjen sistem predlog Smarty, katerega namen je bil strogo ločiti videz (HTML/CSS) od aplikacijske logike. Namenoma je zagotavljal bolj omejen jezik kot sam PHP, tako da razvijalec na primer ni mogel iz predloge opraviti poizvedbe po zbirki podatkov itd. Po drugi strani pa je predstavljal dodatno odvisnost v projektih, povečal njihovo kompleksnost in od programerjev zahteval, da se naučijo novega jezika Smarty. Takšne prednosti so bile sporne, zato se je za predloge še naprej uporabljal navaden PHP. + +Sčasoma so sistemi predlog začeli postajati uporabni. Uvedli so koncepte, kot so [dedovanje |template-inheritance], [način peskovnika |sandbox] in številne druge funkcije, ki so v primerjavi s čistim PHP bistveno poenostavile ustvarjanje predlog. V ospredje so prišli varnost, obstoj [ranljivosti, kot je XSS, |safety-first] in potreba po [pobegih |#What is escaping]. Sistemi predlog so uvedli samodejno izrivanje, da bi odpravili tveganje, da bi programer pozabil na to in ustvaril resno varnostno luknjo (kmalu bomo videli, da ima to določene pasti). + +Danes so prednosti sistemov predlog veliko večje od stroškov, povezanih z njihovo uvedbo. Zato jih je smiselno uporabljati. + + +Zakaj je Latte boljši od Twiga ali Bladea? .[#toc-why-is-latte-better-than-twig-or-blade] +----------------------------------------------------------------------------------------- + +Razlogov je več - nekateri so prijetni, drugi izjemno uporabni. Latte je kombinacija prijetnega in uporabnega. + +*Najprej prijetno:* Latte ima enako [sintakso kot PHP |syntax#Latte Understands PHP]. Edina razlika je v zapisu oznak, saj ima raje krajše `{` in `}` namesto `<?=` in `?>`. To pomeni, da se vam ni treba učiti novega jezika. Stroški usposabljanja so minimalni. Najpomembneje pa je, da vam med razvojem ni treba nenehno "preklapljati" med jezikom PHP in jezikom predlog, saj sta oba enaka. Za razliko od predlog Twig, ki uporabljajo jezik Python, mora programer preklapljati med dvema različnima jezikoma. + +* Zdaj pa še izjemno uporaben razlog:* Vsi sistemi predlog, kot so Twig, Blade ali Smarty, so se razvili tako, da vključujejo zaščito pred XSS v obliki samodejnega [eskapiranja |#What is escaping]. Natančneje, samodejni klic funkcije `htmlspecialchars()`. Vendar so ustvarjalci sistema Latte ugotovili, da to sploh ni prava rešitev. Razlog za to je, da različni deli dokumenta zahtevajo različne metode escapiranja. Naivno samodejno izpisovanje je nevarna funkcija, saj ustvarja lažen občutek varnosti. + +Da bi bilo samodejno izpisovanje funkcionalno in zanesljivo, mora prepoznati, kje v dokumentu se podatki izpisujejo (imenujemo jih konteksti), in ustrezno izbrati funkcijo izpisovanja. Zato mora biti [občutljivo na kontekst |safety-first#Context-Aware Escaping]. In to zna Latte. Razume HTML. Predloge ne dojema le kot niz znakov, temveč razume, kaj so oznake, atributi itd. Zato različno eskapira v besedilu HTML, znotraj oznak HTML, znotraj JavaScripta itd. + +Latte je prvi in edini sistem predlog PHP s kontekstno občutljivim eskapiranjem. Predstavlja edini resnično varen sistem predlog. + +*In še en prijeten razlog:* Ker Latte razume HTML, ponuja še druge zelo prijetne funkcije. Na primer, [n:atributi |syntax#n:attributes]. ali možnost [preverjanja povezav |safety-first#Link checking]. In še veliko več. + + +Kaj je escaping? .[#toc-what-is-escaping] +----------------------------------------- + +Escaping je postopek, ki vključuje zamenjavo znakov s posebnim pomenom z ustreznimi zaporedji pri vstavljanju enega niza v drugega, da se preprečijo neželeni učinki ali napake. Na primer, pri vstavljanju niza v besedilo HTML, v katerem ima znak `<` poseben pomen, saj označuje začetek oznake, ga nadomestimo z ustreznim zaporedjem, ki je entiteta HTML `<`. Tako lahko brskalnik pravilno prikaže simbol `<`. + +Preprost primer neposrednega pobega pri pisanju kode PHP je vstavljanje narekovaja v niz tako, da pred njega postavimo povratno poševnico. + +Podrobneje o pobegih govorimo v poglavju [Kako se braniti pred XSS |safety-first#How to Defend Against XSS?]. + + +Ali je mogoče iz predloge Latte izvesti poizvedbo v zbirko podatkov? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +----------------------------------------------------------------------------------------------------------------------------------- + +V predlogah lahko delate s predmeti, ki jim jih posreduje programer. Če programer želi, lahko predlogi posreduje objekt podatkovne zbirke in izvede poizvedbo. Če nameravajo to storiti, ni razloga, da bi jim to preprečili. + +Drugačen položaj nastane, če želite strankam ali zunanjim programerjem omogočiti urejanje predlog. V tem primeru zagotovo ne želite, da bi imeli dostop do podatkovne zbirke. Seveda objekta podatkovne zbirke ne boste posredovali predlogi, kaj pa, če je do nje mogoče dostopati prek drugega objekta? Rešitev je [način peskovnika |sandbox], ki vam omogoča, da določite, katere metode se lahko kličejo v predlogah. Zaradi tega vam ni treba skrbeti za varnostne kršitve. + + +Katere so glavne razlike med sistemi za oblikovanje predlog, kot so Latte, Twig in Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + +Razlike med sistemi za oblikovanje predlog, kot so Latte, Twig in Blade, so predvsem v njihovi sintaksi, varnosti in integraciji z ogrodji: + +- Latte: uporablja sintakso jezika PHP, zato se ga je lažje naučiti in uporabljati. Zagotavlja vrhunsko zaščito pred napadi XSS. +- Twig: uporablja sintakso, podobno jeziku Python, ki se precej razlikuje od PHP. Izhaja brez razlikovanja konteksta. Dobro je integriran z ogrodjem Symfony. +- Blade: uporablja mešanico sintakse PHP in lastne sintakse. Izhaja brez razlikovanja konteksta. Tesno je povezan s funkcijami in ekosistemom Laravel. + + +Ali se podjetjem splača uporabljati sistem za oblikovanje predlog? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +---------------------------------------------------------------------------------------------------------------------------------- + +Prvič, stroški, povezani z usposabljanjem, uporabo in splošnimi koristmi, se močno razlikujejo glede na sistem. Sistem za oblikovanje predlog Latte zaradi uporabe sintakse PHP močno poenostavi učenje za programerje, ki so že seznanjeni s tem jezikom. Običajno programer potrebuje nekaj ur, da se dovolj dobro seznani s sistemom Latte, kar zmanjša stroške usposabljanja in pospeši uvajanje tehnologije ter, kar je najpomembneje, učinkovitost pri vsakodnevni uporabi. + +Poleg tega program Latte zagotavlja visoko raven zaščite pred ranljivostmi XSS zaradi svoje edinstvene tehnologije pobegov, ki upošteva kontekst. Ta zaščita je ključnega pomena za zagotavljanje varnosti spletnih aplikacij in zmanjševanje tveganja napadov, ki bi lahko ogrozili uporabnike ali podatke podjetja. Varnost spletnih aplikacij je pomembna tudi za ohranjanje dobrega ugleda podjetja. Varnostne težave lahko povzročijo izgubo zaupanja strank in škodijo ugledu podjetja na trgu. + +Z uporabo Latte se zmanjšajo tudi skupni stroški razvoja in vzdrževanja, saj je oboje lažje. Zato se uporaba sistema za oblikovanje predlog vsekakor splača. + + +Ali Latte vpliva na zmogljivost spletnih aplikacij? .[#toc-does-latte-affect-the-performance-of-web-applications] +----------------------------------------------------------------------------------------------------------------- + +Čeprav se predloge Latte obdelujejo hitro, ta vidik ni pomemben. Razlog je v tem, da se razčlenjevanje datotek izvede samo enkrat med prvim prikazom. Nato se sestavijo v kodo PHP, shranijo na disk in se zaženejo pri vsakem naslednjem zahtevku, ne da bi bilo potrebno ponovno sestavljanje. + +Tako deluje v produkcijskem okolju. Med razvojem se predloge Latte ponovno sestavijo vsakič, ko se spremeni njihova vsebina, zato razvijalec vedno vidi trenutno različico. diff --git a/latte/tr/@left-menu.texy b/latte/tr/@left-menu.texy index 83a4693ecc..297637d462 100644 --- a/latte/tr/@left-menu.texy +++ b/latte/tr/@left-menu.texy @@ -1,4 +1,5 @@ - [Başlarken |Guide] +- [Neden Şablon Kullanmalı? |why-use] - Kavramlar - [Önce Güvenlik |Safety First] - [Şablon Kalıtımı |Template Inheritance] diff --git a/latte/tr/cookbook/@home.texy b/latte/tr/cookbook/@home.texy index 3198b3430a..8ffcd3e435 100644 --- a/latte/tr/cookbook/@home.texy +++ b/latte/tr/cookbook/@home.texy @@ -1,6 +1,9 @@ Yemek Kitabı ************ +.[perex] +Latte ile ortak görevleri gerçekleştirmek için örnek kodlar ve tarifler. + - [{iterateWhile} Hakkında Her Zaman Bilmek İstediğiniz Her Şey |iteratewhile] - [Latte'de SQL sorguları nasıl yazılır? |how-to-write-sql-queries-in-latte] - [PHP'den geçiş |migration-from-php] diff --git a/latte/tr/tags.texy b/latte/tr/tags.texy index adfb0def0b..a100129311 100644 --- a/latte/tr/tags.texy +++ b/latte/tr/tags.texy @@ -102,7 +102,7 @@ Tüm Latte yerleşik etiketlerinin özeti ve açıklaması. .[table-latte-tags language-latte] |## Sadece Nette Forms ile kullanılabilir -| `{form}`... `{/form}` | [bir form öğesi yazdırır |forms:rendering#latte] +| `{form}`... `{/form}` | [bir form öğesi yazdırır |forms:rendering#form] | `{label}`... `{/label}` | [bir form giriş etiketi yazdırır |forms:rendering#label-input] | `{input}` | [bir form giriş öğesi yazdırır |forms:rendering#label-input] | `{inputError}` | [form giriş öğesi için hata mesajı yazdırır |forms:rendering#inputError] @@ -110,6 +110,7 @@ Tüm Latte yerleşik etiketlerinin özeti ve açıklaması. | `{formPrint}` | [Latte form planını oluşturur |forms:rendering#formPrint] | `{formPrintClass}` | [form verileri için PHP sınıfını yazdırır |forms:in-presenter#mapping-to-classes] | `{formContext}`... `{/formContext}` | [kısmi form oluşturma |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [form kapsayıcısını oluşturma |forms:rendering#special-cases] Baskı .[#toc-printing] @@ -595,7 +596,7 @@ Ayrıca bakınız [`{include block}` |template-inheritance#printing-blocks] Dahil edilen şablonların aktif bağlamın değişkenlerine erişimi yoktur, ancak global değişkenlere erişimi vardır. -Değişkenleri bu şekilde aktarabilirsiniz: +Eklenen şablona değişkenleri aşağıdaki şekilde aktarabilirsiniz: ```latte {* Latte 2.9'dan beri *} diff --git a/latte/tr/template-inheritance.texy b/latte/tr/template-inheritance.texy index 5cb21fc43a..b46c997cee 100644 --- a/latte/tr/template-inheritance.texy +++ b/latte/tr/template-inheritance.texy @@ -246,7 +246,7 @@ Bloğu başka bir şablondan da görüntüleyebilirsiniz: Yazdırılan blok, bloğun dahil edildiği aynı dosyada tanımlanmış olması dışında, etkin bağlamın değişkenlerine erişemez. Ancak global değişkenlere erişimleri vardır. -Değişkenleri bu şekilde aktarabilirsiniz: +Değişkenleri bloğa aşağıdaki şekilde aktarabilirsiniz: ```latte {* Latte 2.9'dan beri *} @@ -603,7 +603,7 @@ Latte'de çeşitli kalıtım ve kod yeniden kullanımı türleri vardır. Daha f ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/tr/why-use.texy b/latte/tr/why-use.texy new file mode 100644 index 0000000000..ada364fd6f --- /dev/null +++ b/latte/tr/why-use.texy @@ -0,0 +1,80 @@ +Neden Şablon Kullanmalı? +************************ + + +PHP'de neden bir şablonlama sistemi kullanmalıyım? .[#toc-why-should-i-use-a-templating-system-in-php] +------------------------------------------------------------------------------------------------------ + +PHP'nin kendisi bir şablonlama dili iken neden PHP'de bir şablon sistemi kullanılsın? + +Öncelikle bu dilin ilginç dönemeçlerle dolu tarihini kısaca özetleyelim. HTML sayfaları oluşturmak için kullanılan ilk programlama dillerinden biri C diliydi. Ancak kısa süre sonra bu dili bu amaçla kullanmanın pratik olmadığı anlaşıldı. Bunun üzerine Rasmus Lerdorf, arka uçta C dili ile dinamik HTML oluşturulmasını kolaylaştıran PHP'yi yarattı. PHP başlangıçta bir şablonlama dili olarak tasarlanmıştı, ancak zamanla ek özellikler kazandı ve tam teşekküllü bir programlama dili haline geldi. + +Bununla birlikte, hala bir şablonlama dili olarak işlev görmektedir. Bir PHP dosyası bir HTML sayfası içerebilir, burada değişkenler `<?= $foo ?>`vb. + +PHP'nin tarihinin başlarında Smarty şablon sistemi, görünümü (HTML/CSS) uygulama mantığından kesin olarak ayırmak amacıyla oluşturulmuştur. Kasıtlı olarak PHP'nin kendisinden daha sınırlı bir dil sağladı, böylece örneğin bir geliştirici bir şablondan bir veritabanı sorgusu yapamadı, vb. Öte yandan, projelerde ek bir bağımlılığı temsil ediyor, karmaşıklıklarını artırıyor ve programcıların yeni bir Smarty dili öğrenmelerini gerektiriyordu. Bu tür faydalar tartışmalıydı ve şablonlar için düz PHP kullanılmaya devam etti. + +Zamanla şablon sistemleri kullanışlı hale gelmeye başladı. [Kalıtım |template-inheritance], [sandbox modu |sandbox] ve saf PHP'ye kıyasla şablon oluşturmayı önemli ölçüde basitleştiren bir dizi başka özellik gibi kavramları tanıttılar. Güvenlik konusu, [XSS gibi |safety-first] güvenlik [açıklarının |safety-first] varlığı ve [kaçış |#What is escaping] ihtiyacı ön plana çıktı. Şablon sistemleri, bir programcının bunu unutması ve ciddi bir güvenlik açığı yaratması riskini ortadan kaldırmak için otomatik kaçış özelliğini getirdi (bunun bazı tuzakları olduğunu birazdan göreceğiz). + +Günümüzde şablon sistemlerin faydaları, dağıtımlarıyla ilgili maliyetlerden çok daha ağır basmaktadır. Bu nedenle, bunları kullanmak mantıklıdır. + + +Latte neden Twig veya Blade'den daha iyi? .[#toc-why-is-latte-better-than-twig-or-blade] +---------------------------------------------------------------------------------------- + +Bunun çeşitli nedenleri vardır - bazıları hoş, bazıları ise son derece yararlıdır. Latte, hoş ve yararlı olanın bir kombinasyonudur. + +*İlk olarak, hoş:* Latte, [PHP |syntax#Latte Understands PHP] ile aynı [sözdizimine |syntax#Latte Understands PHP] sahiptir. Tek fark etiketlerin gösteriminde, `<?=` ve `?>` yerine daha kısa olan `{` ve `}` tercih ediliyor. Bu, yeni bir dil öğrenmek zorunda olmadığınız anlamına gelir. Eğitim maliyetleri minimum düzeydedir. En önemlisi, geliştirme sırasında PHP dili ile şablon dili arasında sürekli "geçiş" yapmak zorunda kalmazsınız, çünkü ikisi de aynıdır. Bu, Python dilini kullanan Twig şablonlarının aksine, programcıyı iki farklı dil arasında geçiş yapmaya zorlar. + +*Şimdi son derece yararlı bir nedene gelelim:* Twig, Blade veya Smarty gibi tüm şablon sistemleri, otomatik [kaçış |#What is escaping] şeklinde XSS'ye karşı koruma içerecek şekilde gelişmiştir. Daha doğrusu, `htmlspecialchars()` işlevinin otomatik olarak çağrılması. Ancak Latte'nin yaratıcıları bunun hiç de doğru bir çözüm olmadığını fark ettiler. Bunun nedeni, belgenin farklı bölümlerinin farklı kaçış yöntemleri gerektirmesidir. Naif otomatik kaçış tehlikeli bir özelliktir çünkü yanlış bir güvenlik hissi yaratır. + +Otomatik kaçışın işlevsel ve güvenilir olması için, verinin belgenin neresinde çıktılandığını (bunlara bağlam diyoruz) tanıması ve kaçış işlevini buna göre seçmesi gerekir. Bu nedenle, [bağlama duyarlı |safety-first#Context-Aware Escaping] olmalıdır. Latte'nin yapabildiği de budur. HTML'i anlar. Şablonu sadece bir karakter dizisi olarak algılamaz, ancak etiketlerin, niteliklerin vb. ne olduğunu anlar. Bu nedenle, HTML metni içinde, HTML etiketleri içinde, JavaScript içinde vb. farklı kaçışlar yapar. + +Latte, içeriğe duyarlı kaçış özelliğine sahip ilk ve tek PHP şablon sistemidir. Gerçekten güvenli tek şablon sistemini temsil eder. + +*Ve bir başka hoş neden:* Latte HTML'yi anladığı için, çok hoş başka özellikler de sunuyor. Örneğin, [n:attributes |syntax#n:attributes]. Ya da [bağlantıları kontrol |safety-first#Link checking] etme yeteneği. Ve çok daha fazlası. + + +Kaçmak nedir? .[#toc-what-is-escaping] +-------------------------------------- + +Kaçış, istenmeyen etkileri veya hataları önlemek için bir dizeyi başka bir dizeye eklerken özel anlamları olan karakterleri karşılık gelen dizilerle değiştirmeyi içeren bir işlemdir. Örneğin, `<` karakterinin bir etiketin başlangıcını gösterdiği için özel bir anlama sahip olduğu HTML metnine bir dize eklerken, bu karakteri `<` HTML varlığı olan ilgili diziyle değiştiririz. Bu, tarayıcının `<` sembolünü doğru şekilde görüntülemesini sağlar. + +PHP kodu yazarken doğrudan kaçış yapmanın basit bir örneği, önüne ters eğik çizgi koyarak bir dizeye tırnak işareti eklemektir. + +Kaçış konusunu [XSS'ye karşı savunma |safety-first#How to Defend Against XSS?] bölümünde daha ayrıntılı olarak ele alacağız. + + +Latte şablonundan bir veritabanı sorgusu çalıştırılabilir mi? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +---------------------------------------------------------------------------------------------------------------------------- + +Şablonlarda, programcının kendilerine aktardığı nesnelerle çalışabilirsiniz. Programcı isterse şablona bir veritabanı nesnesi aktarabilir ve bir sorgu gerçekleştirebilir. Bunu yapmak istiyorlarsa, onları engellemek için bir neden yoktur. + +Müşterilere veya harici kodlayıcılara şablonları düzenleme olanağı vermek istiyorsanız farklı bir durum ortaya çıkar. Bu durumda, kesinlikle veritabanına erişmelerini istemezsiniz. Elbette veritabanı nesnesini şablona aktarmayacaksınız, ancak ya başka bir nesne aracılığıyla erişilebiliyorsa? Çözüm, şablonlarda hangi yöntemlerin çağrılabileceğini tanımlamanıza olanak tanıyan [sandbox modudur |sandbox]. Bu sayede güvenlik ihlalleri konusunda endişelenmenize gerek kalmaz. + + +Latte, Twig ve Blade gibi şablonlama sistemleri arasındaki temel farklar nelerdir? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Latte, Twig ve Blade gibi şablonlama sistemleri arasındaki farklar temel olarak sözdizimleri, güvenlikleri ve çerçevelerle entegrasyonlarında yatmaktadır: + +- Latte: PHP dili sözdizimini kullanarak öğrenmeyi ve kullanmayı kolaylaştırır. XSS saldırılarına karşı birinci sınıf koruma sağlar. +- Twig: PHP'den oldukça farklı olan Python benzeri sözdizimi kullanır. Bağlam ayrımı yapmadan kaçar. Symfony çerçevesi ile iyi entegre edilmiştir. +- Blade: PHP ve özel sözdiziminin bir karışımını kullanır. Bağlam ayrımı olmadan kaçış yapar. Laravel özellikleri ve ekosistemi ile sıkı bir şekilde entegre edilmiştir. + + +Şirketler için bir şablon sistemi kullanmaya değer mi? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +---------------------------------------------------------------------------------------------------------------------- + +İlk olarak, eğitim, kullanım ve genel faydalarla ilgili maliyetler sisteme bağlı olarak önemli ölçüde değişir. Latte şablonlama sistemi, PHP sözdizimini kullanması sayesinde, bu dile zaten aşina olan programcılar için öğrenmeyi büyük ölçüde kolaylaştırır. Bir programcının Latte ile yeterince tanışması genellikle birkaç saat alır, bu da eğitim maliyetlerini azaltır ve teknolojinin benimsenmesini ve en önemlisi günlük kullanımda verimliliği hızlandırır. + +Ayrıca Latte, içeriğe duyarlı benzersiz kaçış teknolojisi sayesinde XSS güvenlik açığına karşı yüksek düzeyde koruma sağlar. Bu koruma, web uygulaması güvenliğini sağlamak ve kullanıcıları veya şirket verilerini tehlikeye atabilecek saldırı riskini en aza indirmek için çok önemlidir. Web uygulaması güvenliği, bir şirketin iyi itibarını korumak için de önemlidir. Güvenlik sorunları müşterilerin güven kaybına yol açabilir ve şirketin pazardaki itibarına zarar verebilir. + +Latte kullanmak, her ikisini de kolaylaştırarak genel geliştirme ve bakım maliyetlerini de azaltır. Bu nedenle, bir şablonlama sistemi kullanmak kesinlikle buna değer. + + +Latte web uygulamalarının performansını etkiler mi? .[#toc-does-latte-affect-the-performance-of-web-applications] +----------------------------------------------------------------------------------------------------------------- + +Latte şablonları hızlı bir şekilde işlense de, bu özellik gerçekten önemli değildir. Bunun nedeni, dosyaların ayrıştırılmasının ilk görüntüleme sırasında yalnızca bir kez gerçekleşmesidir. Daha sonra PHP koduna derlenir, diskte saklanır ve sonraki her istekte yeniden derleme gerektirmeden çalıştırılırlar. + +Üretim ortamında bu şekilde çalışır. Geliştirme sırasında, Latte şablonları içerikleri her değiştiğinde yeniden derlenir, böylece geliştirici her zaman geçerli sürümü görür. diff --git a/latte/uk/@left-menu.texy b/latte/uk/@left-menu.texy index 38cf9d16af..b1aa36563b 100644 --- a/latte/uk/@left-menu.texy +++ b/latte/uk/@left-menu.texy @@ -1,4 +1,5 @@ - [Початок роботи |Guide] +- [Навіщо використовувати шаблони? |why-use] - Концепції - [Безпека понад усе |Safety First] - Успадкування [шаблонів |Template Inheritance] diff --git a/latte/uk/cookbook/@home.texy b/latte/uk/cookbook/@home.texy index d8d48ad534..5d60e3b64a 100644 --- a/latte/uk/cookbook/@home.texy +++ b/latte/uk/cookbook/@home.texy @@ -1,6 +1,9 @@ Кулінарна книга *************** +.[perex] +Приклади кодів і рецептів для виконання типових завдань за допомогою Latte. + - [Все, що ви завжди хотіли знати про {iterateWhile} |iteratewhile] - [Як писати SQL-запити в Latte? |how-to-write-sql-queries-in-latte] - [Міграція з PHP |migration-from-php] diff --git a/latte/uk/tags.texy b/latte/uk/tags.texy index abe3baaedc..667f3e78ec 100644 --- a/latte/uk/tags.texy +++ b/latte/uk/tags.texy @@ -102,7 +102,7 @@ .[table-latte-tags language-latte] |## Доступно тільки в Nette Forms -| `{form}`... `{/form}` | [друкує елемент форми |forms:rendering#Latte] +| `{form}`... `{/form}` | [друкує елемент форми |forms:rendering#form] | `{label}`... `{/label}` | [друкує мітку введення форми |forms:rendering#label-input] | `{input}` | [друкує елемент введення форми |forms:rendering#label-input] | `{inputError}` | [друкує повідомлення про помилку для елемента введення форми |forms:rendering#inputError] @@ -110,6 +110,7 @@ | `{formPrint}` | [генерує креслення форми Latte |forms:rendering#formPrint] | `{formPrintClass}` | [друкує PHP клас для даних форми |forms:in-presenter#Mapping-to-Classes] | `{formContext}`... `{/formContext}` | [частковий рендеринг форми |forms:rendering#special-cases] +| `{formContainer}`... `{/formContainer}` | [рендеринг контейнера форми |forms:rendering#special-cases] Друк .[#toc-printing] @@ -595,7 +596,7 @@ Age: {date('Y') - $birth}<br> Увімкнені шаблони не мають доступу до змінних активного контексту, але мають доступ до глобальних змінних. -Ви можете передавати змінні таким чином: +Ви можете передати змінні до вставленого шаблону наступним чином: ```latte {* починаючи з Latte 2.9 *} diff --git a/latte/uk/template-inheritance.texy b/latte/uk/template-inheritance.texy index 45d508d970..fad1c663f4 100644 --- a/latte/uk/template-inheritance.texy +++ b/latte/uk/template-inheritance.texy @@ -246,7 +246,7 @@ bar: {$bar ?? 'not defined'} // prints: not defined Блоки, що виводяться, не мають доступу до змінних активного контексту, за винятком випадків, коли блок визначено в тому самому файлі, куди його ввімкнено. Однак вони мають доступ до глобальних змінних. -Ви можете передавати змінні таким чином: +Передавати змінні в блок можна наступним чином: ```latte {* починаючи з Latte 2.9 *} @@ -603,7 +603,7 @@ Hi, I am Mary. ```latte <nav> - <div>Homepage</div> + <div>Home</div> <div>About</div> </nav> ``` diff --git a/latte/uk/why-use.texy b/latte/uk/why-use.texy new file mode 100644 index 0000000000..02572234e1 --- /dev/null +++ b/latte/uk/why-use.texy @@ -0,0 +1,80 @@ +Навіщо використовувати шаблони? +******************************* + + +Навіщо використовувати систему шаблонів в PHP? .[#toc-why-should-i-use-a-templating-system-in-php] +-------------------------------------------------------------------------------------------------- + +Навіщо використовувати систему шаблонів в PHP, якщо PHP сам по собі є мовою шаблонів? + +Давайте спочатку коротко згадаємо історію цієї мови, яка сповнена цікавих поворотів. Однією з перших мов програмування, яка використовувалася для створення HTML-сторінок, була мова C. Однак незабаром стало очевидно, що використовувати її для цієї мети недоцільно. Таким чином, Расмус Лердорф створив PHP, який полегшив генерацію динамічного HTML за допомогою мови C на серверній стороні. Спочатку PHP був розроблений як мова шаблонів, але з часом він набув додаткових можливостей і став повноцінною мовою програмування. + +Тим не менш, вона все ще функціонує як мова шаблонів. PHP-файл може містити HTML-сторінку, в якій змінні виводяться за допомогою `<?= $foo ?>`тощо. + +На початку історії PHP була створена система шаблонів Smarty, яка мала на меті чітко відокремити зовнішній вигляд (HTML/CSS) від логіки програми. Вона свідомо надавала більш обмежену мову, ніж сам PHP, щоб, наприклад, розробник не міг зробити запит до бази даних з шаблону тощо. З іншого боку, це створювало додаткову залежність у проектах, збільшувало їхню складність і вимагало від програмістів вивчення нової мови Smarty. Такі переваги були суперечливими, і для шаблонів продовжували використовувати звичайний PHP. + +З часом шаблонні системи почали ставати корисними. Вони запровадили такі поняття, як [успадкування |template-inheritance], [режим |sandbox] пісочниці та низку інших функцій, які значно спростили створення шаблонів порівняно з чистим PHP. Тема безпеки, існування [вразливостей на кшталт XSS |safety-first] та необхідності [ескейпінгу |#What is escaping] вийшла на перший план. Шаблонні системи запровадили автоматичне екранування, щоб усунути ризик того, що програміст забуде про нього і створить серйозну діру в безпеці (незабаром ми побачимо, що це має певні підводні камені). + +Сьогодні переваги шаблонних систем значно перевищують витрати, пов'язані з їх розгортанням. Тому є сенс їх використовувати. + + +Чому Latte краще, ніж Twig або Blade? .[#toc-why-is-latte-better-than-twig-or-blade] +------------------------------------------------------------------------------------ + +Є кілька причин - деякі з них приємні, а інші надзвичайно корисні. Латте - це поєднання приємного та корисного. + +*По-перше, приємне:* Latte має такий самий [синтаксис, як і PHP |syntax#Latte Understands PHP]. Єдина відмінність полягає в позначенні тегів, надаючи перевагу більш коротким `{` і `}` замість `<?=` і `?>`. Це означає, що вам не доведеться вчити нову мову. Витрати на навчання мінімальні. Найголовніше, що під час розробки вам не доведеться постійно "перемикатися" між мовою PHP і мовою шаблонів, оскільки вони обидві однакові. Це відрізняється від шаблонів Twig, які використовують мову Python, що змушує програміста перемикатися між двома різними мовами. + +*А тепер з надзвичайно корисної причини:* Всі системи шаблонів, такі як Twig, Blade або Smarty, еволюціонували, щоб включити захист від XSS у вигляді автоматичного [ескейпінгу |#What is escaping]. Точніше, автоматичного виклику функції `htmlspecialchars()`. Однак творці Latte зрозуміли, що це зовсім не правильне рішення. Це пов'язано з тим, що різні частини документа вимагають різних методів екранування. Наївне автоматичне екранування є небезпечною функцією, оскільки створює хибне відчуття безпеки. + +Щоб автоматичне екранування було функціональним і надійним, воно повинно розпізнавати, де в документі виводяться дані (ми називаємо це контекстами), і відповідно до цього вибирати функцію екранування. Отже, вона має бути [контекстно-чутливою |safety-first#Context-Aware Escaping]. І це те, що Latte може робити. Він розуміє HTML. Він не сприймає шаблон як просто рядок символів, а розуміє, що таке теги, атрибути тощо. Тому він по-різному виконує екранування в HTML-тексті, всередині HTML-тегів, всередині JavaScript тощо. + +Latte - це перша і єдина система шаблонів PHP з контекстно-залежним екрануванням. Це єдина справді безпечна система шаблонів. + +*І ще одна приємна причина:* Оскільки Latte розуміє HTML, вона пропонує інші дуже приємні можливості. Наприклад, [n:attributes |syntax#n:attributes]. Або можливість перевіряти [посилання |safety-first#Link checking]. І багато інших. + + +Що таке втеча? .[#toc-what-is-escaping] +--------------------------------------- + +Екранування - це процес, який передбачає заміну символів зі спеціальним значенням на відповідні послідовності при вставці одного рядка в інший, щоб запобігти небажаним ефектам або помилкам. Наприклад, при вставці рядка в HTML-текст, в якому символ `<` має особливе значення, оскільки вказує на початок тега, ми замінюємо його на відповідну послідовність, яка є HTML-об'єктом `<`. Це дозволяє браузеру коректно відображати символ `<`. + +Простим прикладом екранування безпосередньо при написанні PHP-коду є вставка лапок в рядок шляхом розміщення перед ними зворотної косої риски. + +Більш детально екранування обговорюється в розділі [Як захиститися від XSS |safety-first#How to Defend Against XSS?]. + + +Чи можна виконати запит до бази даних з шаблону Latte? .[#toc-can-a-database-query-be-executed-from-a-latte-template] +--------------------------------------------------------------------------------------------------------------------- + +У шаблонах можна працювати з об'єктами, які їм передає програміст. Якщо програміст хоче, він може передати шаблону об'єкт бази даних і виконати запит. Якщо він має намір це зробити, немає жодних причин перешкоджати йому. + +Інша ситуація виникає, якщо ви хочете надати клієнтам або зовнішнім програмістам можливість редагувати шаблони. У цьому випадку ви точно не хочете, щоб вони мали доступ до бази даних. Звичайно, ви не будете передавати об'єкт бази даних в шаблон, але що, якщо до неї можна отримати доступ через інший об'єкт? Рішенням є [режим |sandbox] пісочниці, який дозволяє вам визначати, які методи можна викликати в шаблонах. Завдяки цьому ви можете не турбуватися про порушення безпеки. + + +Які основні відмінності між системами шаблонів Latte, Twig та Blade? .[#toc-what-are-the-main-differences-between-templating-systems-like-latte-twig-and-blade] +--------------------------------------------------------------------------------------------------------------------------------------------------------------- + +Відмінності між такими системами шаблонів, як Latte, Twig і Blade, полягають, головним чином, у їхньому синтаксисі, безпеці та інтеграції з фреймворками: + +- Latte: використовує синтаксис мови PHP, що робить її простішою у вивченні та використанні. Він забезпечує першокласний захист від XSS-атак. +- Twig: використовує Python-подібний синтаксис, який суттєво відрізняється від PHP. Він виконує ескейп без розрізнення контексту. Він добре інтегрований з фреймворком Symfony. +- Blade: використовує суміш PHP і власного синтаксису. Екранується без розрізнення контексту. Тісно інтегрована з функціями та екосистемою Laravel. + + +Чи варто компаніям використовувати систему шаблонів? .[#toc-is-it-worth-it-for-companies-to-use-a-templating-system] +-------------------------------------------------------------------------------------------------------------------- + +По-перше, витрати, пов'язані з навчанням, використанням та загальними перевагами, суттєво відрізняються залежно від системи. Система шаблонів Latte, завдяки використанню синтаксису PHP, значно спрощує навчання для програмістів, які вже знайомі з цією мовою. Зазвичай програмісту достатньо кількох годин, щоб достатньою мірою ознайомитися з Latte, що зменшує витрати на навчання і прискорює освоєння технології та, що найголовніше, ефективність у щоденному використанні. + +Крім того, Latte забезпечує високий рівень захисту від XSS-уразливостей завдяки унікальній технології контекстно-залежного обходу. Цей захист має вирішальне значення для забезпечення безпеки веб-додатків і мінімізації ризику атак, які можуть загрожувати користувачам або даним компанії. Безпека веб-додатків також важлива для підтримки гарної репутації компанії. Проблеми з безпекою можуть призвести до втрати довіри з боку клієнтів і зашкодити репутації компанії на ринку. + +Використання Latte також знижує загальні витрати на розробку та підтримку, спрощуючи обидва процеси. Тому використання системи шаблонів, безумовно, того варте. + + +Чи впливає Latte на продуктивність веб-додатків? .[#toc-does-latte-affect-the-performance-of-web-applications] +-------------------------------------------------------------------------------------------------------------- + +Хоча шаблони Latte обробляються швидко, цей аспект не має особливого значення. Причина в тому, що синтаксичний аналіз файлів відбувається лише один раз під час першого відображення. Потім вони компілюються в PHP-код, зберігаються на диску і запускаються при кожному наступному запиті без необхідності перекомпіляції. + +Саме так це працює у виробничому середовищі. Під час розробки шаблони Latte перекомпілюються щоразу, коли змінюється їхній вміст, тому розробник завжди бачить поточну версію. diff --git a/migrations/cs/@home.texy b/migrations/cs/@home.texy index 9ef31ca6f9..24841852b3 100644 --- a/migrations/cs/@home.texy +++ b/migrations/cs/@home.texy @@ -1,6 +1,7 @@ Přechod na novější verze ************************ +- [z Nette 3.1 na 4.0 |to-4-0] - [z Nette 3.0 na 3.1 |to-3-1] - [z Nette 2.4 na 3.0 |to-3-0] - [z Nette 2.3 na 2.4 |to-2-4] diff --git a/migrations/cs/@left-menu.texy b/migrations/cs/@left-menu.texy index 82c0d7e4a7..de7ece8370 100644 --- a/migrations/cs/@left-menu.texy +++ b/migrations/cs/@left-menu.texy @@ -1,6 +1,7 @@ Přechod na novější verze ************************ - [Úvod |@home] +- [Z verze 3.1 na 4.0 |to-4-0] - [Z verze 3.0 na 3.1 |to-3-1] - [Z verze 2.4 na 3.0 |to-3-0] - [Z verze 2.3 na 2.4 |to-2-4] diff --git a/migrations/cs/to-4-0.texy b/migrations/cs/to-4-0.texy new file mode 100644 index 0000000000..390ee9a19a --- /dev/null +++ b/migrations/cs/to-4-0.texy @@ -0,0 +1,61 @@ +Přechod na verzi 4.0 +******************** + +.[note] +Tato stránka vzniká postupně s tím, jak se uvolňují jednotlivé balíčky. + +Minimální požadovaná verze PHP je 8.0 + +Všechny změny názvů uvedené v tomto dokumentu znamenají, že původní název samozřejmě nadále existuje a funguje, jen je označený jako deprecated. Můžete se setkat s tím, že vám je bude IDE vizuálně označovat jako deprecated. + +/--comment +Verzí Nette 4.0 se rozumí, že máte tyto balíčky nainstalované ve verze 4.0.*. Ostatní balíčky mohou mít vyšší nebo nižší čísla verzí, kompatibilitu hlídá Composer. + +```json +"require": { + "nette/application": "4.0.*", + "nette/bootstrap": "3.2.*", + "nette/caching": "3.2.*", + "nette/database": "4.0.*", + "nette/forms": "4.0.*", + "nette/http": "4.0.*", + "nette/security": "4.0.*", +}, +``` +\-- + + +Utils 4.0 +--------- + +Třída `Nette\Utils\Reflection` poskytovala metody pro práci s typy `getParameterType()`, `getPropertyType()` a `getReturnType()`. Metody vznikly v době, kdy PHP nemělo union, intersection nebo nejnovější disjunctive normal form typy, se kterými už nefungují a nahradila je [třída Type |utils:type]. Od verze 4.0 jsou tyto metody odstraněné. + +Metoda `Nette\Utils\Reflection::getParameterDefaultValue()` je deprecated, protože nativní `ReflectionParameter::getDefaultValue()` už funguje správně. + +Zrušená je proměnná `Nette\Utils\Html::$xhtml`. + +Pro instalaci Nette Utils 4.0 je potřeba aktualizovat i RobotLoader na verzi 4, pokud jej používáte, a odstranit balíček `nette/finder`: + +```shell +composer remove nette/finder +composer require "nette/utils:^4.0" "nette/robot-loader:^4.0" +``` + + +Finder 4.0 +---------- + +Finder se přestěhoval do balíčku `nette/utils`, viz [#Utils 4.0]. + +Na Linuxu se nově chová v režimu case-sensitive. + +V předchozí verzi platilo, že metody `exclude()` a `filter()` fungovaly jinak, když byly zavolány **před** `from()` resp. `in()` a **po** ní. Tohle už neplatí, `exclude()` a `filter()` fungují vždy stejně. Dřívější `filter()` volaný *až po* nahradila nová metoda `descentFilter()`. + +Finder již neimplementuje rozhraní Countable. + +Řetězec začínající lomítkem ve `Finder::findFiles('/f*')` se nově považuje za absolutní cestu, je potřeba ho nahradit např. za `Finder::findFiles('./f*')`. + + + + +{{priority: -5}} diff --git a/migrations/en/@home.texy b/migrations/en/@home.texy index d49961c0f8..cb2c6f214f 100644 --- a/migrations/en/@home.texy +++ b/migrations/en/@home.texy @@ -1,6 +1,7 @@ Upgrade Guide ************* +- [from Nette 3.1 to 4.0 |to-4-0] - [from Nette 3.0 to 3.1 |to-3-1] - [from Nette 2.4 to 3.0 |to-3-0] - [from Nette 2.3 to 2.4 |to-2-4] diff --git a/migrations/en/@left-menu.texy b/migrations/en/@left-menu.texy index af1cd6e312..77bd539276 100644 --- a/migrations/en/@left-menu.texy +++ b/migrations/en/@left-menu.texy @@ -1,6 +1,7 @@ Upgrade Guide ************* - [Overview |@home] +- [From 3.1 to 4.0 |to-4-0] - [From 3.0 to 3.1 |to-3-1] - [From 2.4 to 3.0 |to-3-0] - [From 2.3 to 2.4 |to-2-4] diff --git a/migrations/en/to-4-0.texy b/migrations/en/to-4-0.texy new file mode 100644 index 0000000000..879f072589 --- /dev/null +++ b/migrations/en/to-4-0.texy @@ -0,0 +1,59 @@ +Migrating to Version 4.0 +************************ + +.[note] +This page is being built incrementally as packages are released. + +Minimum required PHP version is 8.0 + +All name changes mentioned in this document mean that the original name obviously still exists and works, it is just marked as deprecated. You may encounter the IDE visually marking them as deprecated. + +/--comment +Verzí Nette 4.0 se rozumí, že máte tyto balíčky nainstalované ve verze 4.0.*. Ostatní balíčky mohou mít vyšší nebo nižší čísla verzí, kompatibilitu hlídá Composer. + +```json +"require": { + "nette/application": "4.0.*", + "nette/bootstrap": "3.2.*", + "nette/caching": "3.2.*", + "nette/database": "4.0.*", + "nette/forms": "4.0.*", + "nette/http": "4.0.*", + "nette/security": "4.0.*", +}, +``` +\-- + + +Utils 4.0 +--------- + +The `Nette\Utils\Reflection` class provided methods `getParameterType()`, `getPropertyType()` and `getReturnType()` for working with the types. These methods were created when PHP didn't have union, intersection or the newest disjunctive normal form types, which they no longer work with and were replaced by the [Type class |utils:type]. As of version 4.0, these methods have been removed. + +The method `Nette\Utils\Reflection::getParameterDefaultValue()` is deprecated because the native `ReflectionParameter::getDefaultValue()` already works correctly. + +The `Nette\Utils\Html::$xhtml` variable is removed. + +To install Nette Utils 4.0 you need to update RobotLoader to version 4 if you are using it, and remove the `nette/finder` package: + +```shell +composer remove nette/finder +composer require "nette/utils:^4.0" "nette/robot-loader:^4.0" +``` + + +Finder 4.0 +---------- + +Finder has moved to the package `nette/utils`, see [Utils 4.0 |#Utils 4.0]. + +On Linux, it now behaves in case-sensitive mode. + +In the previous version, the `exclude()` and `filter()` methods worked differently when called **before** `from()` and `in()` respectively, and **after** it. This is no longer the case, `exclude()` and `filter()` always work the same. The former `filter()` called *before* has been replaced by the new `descentFilter()` method. + +The Finder no longer implements the Countable interface. + +A string starting with a slash in `Finder::findFiles('/f*')` is now considered an absolute path, it should be replaced with e.g. `Finder::findFiles('./f*')`. + + +{{priority: -5}} diff --git a/nette/bg/@home.texy b/nette/bg/@home.texy index f9abd5e040..186b0c1b59 100644 --- a/nette/bg/@home.texy +++ b/nette/bg/@home.texy @@ -6,12 +6,16 @@ <div> -Предговор +Въведение --------- -- <b>[Създайте първото си приложение! |quickstart:]<b> +- [Защо да използвате Nette? |www:10-reasons-why-nette] +- [Инсталация |Installation] +- [Създайте първото си приложение! |quickstart:] -- [Пакети и инсталиране |www:packages] +Обща информация +--------------- +- [Списък на пакетите |www:packages] - [Поддръжка и PHP |www:maintenance] - [Бележки към изданието |https://nette.org/releases] - [Ръководство за надграждане |migrations:en] @@ -19,6 +23,7 @@ - [Създатели на Nette |https://nette.org/contributors] - [История на Nette |www:history] - [Включете се |contributing:] +- [Развитие на спонсори |https://nette.org/en/donate] - [Референтно ръководство за API |https://api.nette.org/] </div> diff --git a/nette/bg/installation.texy b/nette/bg/installation.texy new file mode 100644 index 0000000000..2f1ce62cf8 --- /dev/null +++ b/nette/bg/installation.texy @@ -0,0 +1,69 @@ +Инсталиране на Nette +******************** + +.[perex] +Nette е семейство от разширени библиотеки за PHP, които можете да използвате напълно самостоятелно. Nette е също така и цялостна рамка. Тази страница ви показва как да инсталирате всяка библиотека или цялата рамка. + + +.[note] +Нови сте в Nette? Препоръчваме ви да разгледате и [урока Създаване на първото ви приложение |quickstart:]. + + +Как да инсталирате пакети .[#toc-how-to-install-packages] +--------------------------------------------------------- + +Отделните [пакети |www:packages] от фамилията Nette се инсталират с помощта на инструмента [Composer |best-practices:composer]. Ако не сте запознати с него, определено трябва да започнете с него. Той е много полезен инструмент. + +Например, необходимо ли ви е да обхождате файловата система в кода си? [Finder |utils:finder], който е включен в пакета `nette/utils` (вижте дясната колона), е чудесен за това. Можете да го инсталирате от командния ред: + +```shell +composer require nette/utils +``` + +Това ще инсталира и всички останали пакети. + +Алтернативен начин е да добавите всички пакети наведнъж, като инсталирате `nette/nette`: + +```shell +composer require nette/nette +``` + + +Как да създадем нов проект .[#toc-how-to-create-a-new-project] +-------------------------------------------------------------- + +Искате да създадете нов проект в Nette? Най-лесният начин да започнете е да изтеглите основния скелет на уеб приложение, наречен [Web Project |https://github.com/nette/web-project]. + +Отново програмата Composer ще ви помогне да настроите проекта си. Намерете главната директория на вашия уеб сървър (напр. `/var/www` или `C:\InetPub`). Изпълнете следната команда в командния ред, но заменете `my-project` с името на директорията, която трябва да бъде създадена: + +```shell +composer create-project nette/web-project my-project +``` + +(Ако не искате да използвате Composer, [изтеглете архива |https://github.com/nette/web-project/archive/preloaded.zip], разархивирайте го и го копирайте в главната директория на уеб сървъра). + +Ако разработвате на macOS или Linux (или друга Unix-базирана система), все пак ще трябва да [зададете права за запис |nette:troubleshooting#setting-directory-permissions]. + +Чудесното при Nette е, че не е необходимо да конфигурирате или настройвате нищо сложно. Така че до този момент началната страница на уеб проекта трябва да работи. Можете да проверите това, като отворите браузъра си на следния URL адрес: + +``` +http://localhost/my-project/www/ +``` + +и трябва да видите началната страница на Nette Framework: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Ура, скелетът работи! Изтрийте шаблона за посрещане и можете да започнете да пишете чудесно ново приложение. + +.[note] +Ако възникне проблем, [опитайте тези няколко съвета |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Инструменти .[#toc-tools] +------------------------- + +Препоръчваме ви да използвате [качествен IDE и всички необходими плъгини |best-practices:editors-and-tools]- това ще ви направи изключително ефективни. + + +{{leftbar: www:@menu-common}} diff --git a/nette/bg/troubleshooting.texy b/nette/bg/troubleshooting.texy index 129352a151..a7943b2c05 100644 --- a/nette/bg/troubleshooting.texy +++ b/nette/bg/troubleshooting.texy @@ -21,6 +21,11 @@ Nette не работи, показва бяла страница .[#toc-nette-i Една от най-честите причини е остарелият кеш. Докато Nette интелигентно актуализира кеша автоматично в режим на разработка, в производствен режим той се фокусира върху максимална производителност и изчистването на кеша след всяка модификация на кода зависи от вас. Опитайте да премахнете `temp/cache`. +Грешка 404, маршрутизирането не работи .[#toc-error-404-routing-not-working] +---------------------------------------------------------------------------- +Когато всички страници (с изключение на началната страница) връщат грешка 404, това изглежда като проблем с конфигурацията на сървъра за [красиви URL адреси |#How to Configure a Server for Nice URLs?]. + + Грешка `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ---------------------------------------------------------------------------------------------------------------------- Тази грешка се появява, ако сте обновили PHP до версия 8.1, но използвате Nette, която не е съвместима с нея. Решението е да се обнови Nette до по-нова версия с помощта на `composer update`. Nette поддържа PHP 8.1 от версия 3.0. Ако използвате по-стара версия (можете да разберете това, като погледнете `composer.json`), [обновете Nette |migrations:en] или останете с PHP 8.0. @@ -61,7 +66,7 @@ setsebool -P httpd_can_network_connect_db on Как да настроя сървъра за красиви URL адреси? .[#toc-how-to-configure-a-server-for-nice-urls] --------------------------------------------------------------------------------------------- -**Apache**: Разширението mod_rewrite трябва да бъде разрешено и конфигурирано в `.htaccess`. +**Apache**: трябва да разрешите и зададете правила за mod_rewrite във файла `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -За да промените конфигурацията на Apache чрез файловете .htaccess, трябва да е разрешена директивата AllowOverride. Това е поведението по подразбиране за Apache. +Ако се сблъскате с проблеми, уверете се, че: +- файлът `.htaccess` се намира в директорията с корена на документа (т.е. до файла `index.php` ) +- [Apache обработва файловете .htaccess |#Test if .htaccess is working] +- [mod_rewrite е активиран |#Test if mod_rewrite is enabled] + +Ако настройвате приложението в подпапка, може да се наложи да разкоментирате реда за настройката `RewriteBase` и да го зададете към правилната папка. **nginx**: В конфигурацията на сървъра трябва да се използва директивата `try_files`: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args важно + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args Е ВАЖНО! } ``` Блокът `location` трябва да бъде дефиниран точно веднъж за всеки път към файловата система в блока `server`. Ако в конфигурацията ви вече има блок `location /`, добавете директивата `try_files` към съществуващия блок. +Проверка дали `.htaccess` работи .[#toc-test-if-htaccess-is-working] +-------------------------------------------------------------------- +Най-простият начин да тествате дали Apache използва или игнорира вашия файл `.htaccess` е да го нарушите умишлено. Поставете реда `Test` в началото на файла и сега, ако опресните страницата в браузъра си, трябва да видите *Internal Server Error* (Вътрешна грешка на сървъра). + +Ако видите тази грешка, това всъщност е добре! Това означава, че Apache анализира файла `.htaccess` и се сблъсква с грешката, която сме поставили там. Премахнете реда `Test`. + +Ако не видите *вътрешна грешка на сървъра*, вашата настройка на Apache игнорира файла `.htaccess`. Обикновено Apache го игнорира заради липсващата конфигурационна директива `AllowOverride All`. + +Ако хоствате сами, това е достатъчно лесно да се поправи. Отворете вашия `httpd.conf` или `apache.conf` в текстов редактор, намерете съответната `<Directory>` раздел и добавете/променете директивата: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Ако сайтът ви е хостван на друго място, проверете в контролния панел дали можете да активирате `.htaccess` там. Ако не, свържете се с доставчика на хостинг услуги, за да го направи вместо вас. + + +Проверка дали е активиран `mod_rewrite` .[#toc-test-if-mod-rewrite-is-enabled] +------------------------------------------------------------------------------ +Ако сте проверили, че [`.htaccess` работи |#Test if .htaccess is working], можете да проверите дали разширението mod_rewrite е разрешено. Поставете реда `RewriteEngine On` в началото на файла `.htaccess` и опреснете страницата в браузъра си. +Ако видите *Internal Server Error* (Вътрешна грешка на сървъра), това означава, че разширението mod_rewrite не е разрешено. Има няколко начина да го активирате. Вижте Stack Overflow за различните начини, по които това може да се направи при различните настройки. + + Връзките се генерират без `https:`. .[#toc-links-are-generated-without-https] ----------------------------------------------------------------------------- Nette генерира връзки със същия протокол, който използва текущата страница. Така връзките, започващи с `https://foo` и обратно. diff --git a/nette/cs/@home.texy b/nette/cs/@home.texy index d0563c68ea..ec7373b9d4 100644 --- a/nette/cs/@home.texy +++ b/nette/cs/@home.texy @@ -6,13 +6,16 @@ Dokumentace Nette <div> -Obecné ------- +Seznámení +--------- - [Proč používat Nette? |www:10-reasons-why-nette] -- <b>[Píšeme první aplikaci! |quickstart:]</b> +- [Instalace |installation] +- [Píšeme první aplikaci! |quickstart:] -- [Balíčky & instalace |www:packages] +Obecné +------ +- [Seznam balíčků |www:packages] - [Údržba a PHP verze |www:maintenance] - [Release Notes |https://nette.org/releases] - [Přechod na novější verze|migrations:] @@ -20,6 +23,7 @@ Obecné - [Kdo tvoří Nette |https://nette.org/contributors] - [Historie Nette |www:history] - [Zapojte se |contributing:] +- [Podpořte vývoj |https://nette.org/cs/donate] - [API reference |https://api.nette.org/] </div> diff --git a/nette/cs/installation.texy b/nette/cs/installation.texy new file mode 100644 index 0000000000..5766b62e81 --- /dev/null +++ b/nette/cs/installation.texy @@ -0,0 +1,69 @@ +Instalace Nette +*************** + +.[perex] +Nette je rodina vyspělých knihoven pro PHP, které můžete používat zcela samostatně. Nette je také full-stack framework. Tato stránka vám ukáže, jak jednotlivé knihovny nebo celý framework nainstalovat. + + +.[note] +Jste v Nette nováčkem? Doporučujeme vám projít si také [tutorial Píšeme první aplikaci|quickstart:]. + + +Jak instalovat balíčky +---------------------- + +Jednotlivé [balíčky|www:packages] z rodiny Nette se instalují pomocí nástroje [Composer |best-practices:composer]. Pokud jej neznáte, určitě byste měli začít s ním. Je to velice užitečný nástroj. + +Potřebujete kupříkladu ve svém kódu procházet souborovým systémem? Na to se skvěle hodí [Finder |utils:finder], který je součástí balíčku `nette/utils` (viz pravý sloupec). Instalaci provedete z příkazové řádky: + +```shell +composer require nette/utils +``` + +Tímto způsobem nainstalujete i všechny ostatní balíčky. + +Alternativní cestou je přidat úplně všechny balíčky naráz tím, že nainstalujete `nette/nette`: + +```shell +composer require nette/nette +``` + + +Jak vytvořit nový projekt +------------------------- + +Chystáte se na Nette postavit nový projekt? Nejjednodušší start je stáhnout základní skeleton webové aplikace, který se jmenuje [Web Project |https://github.com/nette/web-project]. + +Se založením projektu vám opět pomůže Composer. Najděte si kořenový adresář webového serveru (např. `/var/www` nebo `C:\InetPub`). V příkazové řádce spusťte následující příkaz, jen místo `my-project` zadejte název adresáře, který se má vytvořit: + +```shell +composer create-project nette/web-project my-project +``` + +(Pokud nechcete použít Composer, [stáhněte si archív |https://github.com/nette/web-project/archive/preloaded.zip], rozbalte jej a zkopírujte do kořenového adresáře webového serveru.) + +Pokud vyvíjíte na macOS nebo na Linuxu (nebo na jakémkoliv jiném systému založeném na Unixu), budete muset ještě [nastavit práva zápisu|nette:troubleshooting#Nastavení práv adresářů] webovému serveru. + +Na Nette je skvělé, že nemusíte nic složitě konfigurovat nebo nastavovat. Takže už v tento okamžik by měla úvodní stránka Web Projectu fungovat. Vyzkoušíte to otevřením prohlížeče na následující URL adrese: + +``` +http://localhost/my-project/www/ +``` + +a uvidíte úvodní stránku Nette Frameworku: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Hurá, skeleton funguje! Uvítací šablonu klidně smažte a můžete začít psát novou skvělou aplikaci. + +.[note] +Pokud nastal problém, [zkuste těchto pár tipů |nette:troubleshooting#Nejde mi Nette, zobrazuje se bílá stránka]. + + +Nástroje +-------- + +Doporučujeme používat pro práci [kvalitní IDE a všechny potřebné pluginy |best-practices:editors-and-tools], nesmírně vás to zefektivní. + + +{{leftbar: www:@menu-common}} diff --git a/nette/cs/troubleshooting.texy b/nette/cs/troubleshooting.texy index 81d492ed03..d43b13267d 100644 --- a/nette/cs/troubleshooting.texy +++ b/nette/cs/troubleshooting.texy @@ -21,6 +21,11 @@ Pokud věta `Tracy is unable to log error` v chybové hlášce (už) není, vyč Jedním z nejčastějších důvodů je zastaralá cache. Zatímco Nette ve vývojářském režimu chytře automaticky aktualizuje cache, v produkčním režimu se zaměřuje na maximalizaci výkonu a mazání cache, po každé úpravě kódu, je na vás. Zkuste smazat `temp/cache`. +Chyba 404, nefunguje routování +------------------------------ +Když všechny stránky (kromě homepage) vrací chybu 404, vypadá to na problém s konfigurací serveru pro [hezká URL |#Jak nastavit server pro hezká URL?]. + + Chyba `#[\ReturnTypeWillChange] attribute should be used` --------------------------------------------------------- Tato chyba se objeví, pokud jste aktualizovali PHP na verzi 8.1, ale používáte Nette, která s ní není kompatibilní. Řešením je tedy aktualizovat Nette na novější verzi pomocí `composer update`. Nette podporuje PHP 8.1 od verze 3.0. Pokud používáte verzi starší (zjistíte pohledem do `composer.json`), [upgradujte Nette |migrations:] nebo zůstaňte u PHP 8.0. @@ -61,7 +66,7 @@ Pro zprovoznění aplikace na hostingu je potřeba, abyste v konfiguraci hosting Jak nastavit server pro hezká URL? ---------------------------------- -**Apache**: je potřeba povolit a nastavit rozšíření `mod_rewrite` v souboru `.htaccess`: +**Apache**: je potřeba povolit a nastavit pravidla mod_rewrite v souboru `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Pro ovlivňování chování Apache soubory `.htaccess` je třeba mít povolenou direktivu `AllowOverride`. Toto je v Apache výchozí chování. +Pokud narazíte na problémy, ujistěte se, že: +- soubor `.htaccess` je nachází v adresáři document-root (tedy vedle souboru `index.php`) +- [Apache zpracovává soubory `.htaccess`|#Ověření, že funguje .htaccess] +- [je povolený mod_rewrite|#Ověření, že je povolený mod_rewrite] + +Pokud nastavujete aplikaci v podsložce, možná budete muset odkomentovat řádek pro nastavení `RewriteBase` a nastavit jej na správnou složku. **nginx**: je třeba nastavit přesměrování pomocí direktivy `try_files` uvnitř bloku `location /` v konfiguraci serveru. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args je důležité + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args JE DŮLEŽITÉ! } ``` Block `location` se pro každou filesystémovou cestu smí v bloku `server` vyskytovat jen jednou. Pokud již v konfiguraci `location /` máte, přidejte direktivu `try_files` do něj. +Ověření, že funguje `.htaccess` +------------------------------- +Nejjednodušší způsob, jak otestovat, zda Apache používá nebo ignoruje váš soubor `.htaccess`, je záměrně jej poškodit. Vložte na začátek souboru řádek `Test` a nyní, pokud obnovíte stránku v prohlížeči, měli byste vidět *Internal Server Error*. + +Pokud se vám tato chyba zobrazí, je to vlastně dobře! Znamená to, že Apache analyzuje soubor `.htaccess` a narazí na chybu, kterou jsme tam vložili. Odstraňte řádek `Test`. + +Pokud se nezobrazí *Internal Server Error*, vaše nastavení Apache ignoruje soubor `.htaccess`. Obecně jej Apache ignoruje kvůli chybějící konfigurační direktivě `AllowOverride All`. + +Pokud si jej hostujete sami, lze to snadno opravit. Otevřete soubor `httpd.conf` nebo `apache.conf` v textovém editoru, vyhledejte příslušnou část `<Directory>` a přidejte/změňte tuto direktivu: + +```apacheconf +<Directory "/var/www/htdocs"> # cesta k vašemu document root + AllowOverride All + ... +``` + +Pokud je váš web hostován jinde, podívejte se do ovládacího panelu, zda tam můžete povolit soubor `.htaccess`. Pokud ne, obraťte se na poskytovatele hostingu, aby to udělal za vás. + + +Ověření, že je povolený `mod_rewrite` +------------------------------------- +Pokud máte ověřeno, že [funguje `.htaccess`|#Ověření, že funguje .htaccess], můžete ověřit, zda je povolené rozšíření mod_rewrite. Vložte na začátek souboru `.htaccess` řádek `RewriteEngine On` a obnovte stránku v prohlížeči. +Pokud se zobrazí *Internal Server Error*, znamená to, že mod_rewrite povolený není. Existuje několik způsobů, jak jej povolit. Různé způsoby, jak to lze provést v různých nastaveních, najdete na Stack Overflow. + + Odkazy se generují bez `https:` ------------------------------- Nette generuje odkazy se stejným protokolem, jaký má samotná stránka. Tedy na stránce `https://foo` generuje odkazy začínající na `https:` a obráceně. diff --git a/nette/de/@home.texy b/nette/de/@home.texy index daf185cb0d..b8ef3cad0f 100644 --- a/nette/de/@home.texy +++ b/nette/de/@home.texy @@ -6,19 +6,24 @@ Nette Dokumentation <div> -Allgemein ---------- -- <b>[Erstellen Sie Ihre erste Anwendung! |quickstart:]<b> +Einführung +---------- +- [Warum Nette verwenden? |www:10-reasons-why-nette] +- [Die Installation |Installation] +- [Erstellen Sie Ihre erste Anwendung! |quickstart:] -- [Pakete & Installation |www:packages] +Allgemein +--------- +- [Liste der Pakete |www:packages] - [Wartung und PHP |www:maintenance] -- [Hinweise zum Release |https://nette.org/releases] +- [Anmerkungen zur Veröffentlichung |https://nette.org/releases] - [Upgrade-Anleitung |migrations:en] - [Fehlersuche |nette:Troubleshooting] - [Wer erstellt Nette |https://nette.org/contributors] - [Geschichte von Nette |www:history] - [Beteiligen Sie sich |contributing:] +- [Entwicklung des Sponsors |https://nette.org/en/donate] - [API-Referenz |https://api.nette.org/] </div> diff --git a/nette/de/installation.texy b/nette/de/installation.texy new file mode 100644 index 0000000000..cba9cac1d9 --- /dev/null +++ b/nette/de/installation.texy @@ -0,0 +1,69 @@ +Installation von Nette +********************** + +.[perex] +Nette ist eine Familie von fortschrittlichen Bibliotheken für PHP, die Sie komplett eigenständig verwenden können. Nette ist auch ein Full-Stack-Framework. Diese Seite zeigt Ihnen, wie Sie die einzelnen Bibliotheken oder das gesamte Framework installieren können. + + +.[note] +Sind Sie neu bei Nette? Dann empfehlen wir Ihnen auch [das Tutorial Erstellen Sie Ihre erste Anwendung |quickstart:]. + + +Wie man Pakete installiert .[#toc-how-to-install-packages] +---------------------------------------------------------- + +Die einzelnen [Pakete |www:packages] der Nette-Familie werden mit dem [Composer-Tool |best-practices:composer] installiert. Wenn Sie damit nicht vertraut sind, sollten Sie unbedingt damit beginnen. Es ist ein sehr nützliches Werkzeug. + +Müssen Sie zum Beispiel das Dateisystem in Ihrem Code durchsuchen? Der [Finder |utils:finder], der im Paket `nette/utils` (siehe rechte Spalte) enthalten ist, ist dafür hervorragend geeignet. Sie können ihn von der Kommandozeile aus installieren: + +```shell +composer require nette/utils +``` + +Dadurch werden auch alle anderen Pakete installiert. + +Eine andere Möglichkeit ist, alle Pakete auf einmal hinzuzufügen, indem Sie `nette/nette` installieren: + +```shell +composer require nette/nette +``` + + +Wie man ein neues Projekt erstellt .[#toc-how-to-create-a-new-project] +---------------------------------------------------------------------- + +Möchten Sie ein neues Projekt auf Nette erstellen? Der einfachste Weg, damit zu beginnen, ist der Download des Grundgerüsts für Webanwendungen, genannt [Webprojekt |https://github.com/nette/web-project]. + +Auch hier hilft Ihnen der Composer beim Einrichten Ihres Projekts. Suchen Sie das Stammverzeichnis Ihres Webservers (z. B. `/var/www` oder `C:\InetPub`). Führen Sie den folgenden Befehl an der Eingabeaufforderung aus, wobei Sie `my-project` durch den Namen des zu erstellenden Verzeichnisses ersetzen: + +```shell +composer create-project nette/web-project my-project +``` + +(Wenn Sie den Composer nicht verwenden möchten, [laden Sie das Archiv herunter |https://github.com/nette/web-project/archive/preloaded.zip], entpacken Sie es und kopieren Sie es in das Stammverzeichnis des Webservers). + +Wenn Sie auf macOS oder Linux (oder einem anderen Unix-basierten System) entwickeln, müssen Sie trotzdem [Schreibrechte festlegen |nette:troubleshooting#setting-directory-permissions]. + +Das Tolle an Nette ist, dass Sie nichts Kompliziertes konfigurieren oder einrichten müssen. Jetzt sollte die Webprojekt-Homepage funktionieren. Sie können dies testen, indem Sie Ihren Browser unter der folgenden URL öffnen: + +``` +http://localhost/my-project/www/ +``` + +und Sie sollten die Nette Framework Willkommensseite sehen: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Hurra, das Skelett funktioniert! Löschen Sie die Willkommensvorlage und Sie können mit dem Schreiben einer großartigen neuen Anwendung beginnen. + +.[note] +Wenn es ein Problem gibt, [versuchen Sie diese Tipps |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Werkzeuge .[#toc-tools] +----------------------- + +Wir empfehlen die Verwendung einer [qualitativ hochwertigen IDE und aller notwendigen Plugins |best-practices:editors-and-tools], die Sie extrem effizient machen werden. + + +{{leftbar: www:@menu-common}} diff --git a/nette/de/troubleshooting.texy b/nette/de/troubleshooting.texy index a1d2b56b0c..500d33700c 100644 --- a/nette/de/troubleshooting.texy +++ b/nette/de/troubleshooting.texy @@ -21,6 +21,11 @@ Wenn der Satz `Tracy is unable to log error` nicht (mehr) in der Fehlermeldung s Einer der häufigsten Gründe ist ein veralteter Cache. Während Nette im Entwicklungsmodus den Cache automatisch aktualisiert, konzentriert es sich im Produktionsmodus auf die Maximierung der Leistung, und es liegt an Ihnen, den Cache nach jeder Codeänderung zu leeren. Versuchen Sie, `temp/cache` zu löschen. +Fehler 404, Routing funktioniert nicht .[#toc-error-404-routing-not-working] +---------------------------------------------------------------------------- +Wenn alle Seiten (außer der Homepage) einen 404-Fehler zurückgeben, sieht es nach einem Serverkonfigurationsproblem für [hübsche URLs |#How to Configure a Server for Nice URLs?] aus. + + Fehler `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ---------------------------------------------------------------------------------------------------------------------- Dieser Fehler tritt auf, wenn Sie PHP auf Version 8.1 aktualisiert haben, aber Nette verwenden, das damit nicht kompatibel ist. Die Lösung besteht also darin, Nette mit `composer update` auf eine neuere Version zu aktualisieren. Nette unterstützt PHP 8.1 seit Version 3.0. Wenn Sie eine ältere Version verwenden (Sie können dies unter `composer.json` herausfinden), [aktualisieren Sie Nette |migrations:en] oder bleiben Sie bei PHP 8.0. @@ -61,7 +66,7 @@ Die Lösung **ist** nicht, den Ordner `www/` durch Regeln in der Datei `.htacces Wie konfiguriert man einen Server für schöne URLs? .[#toc-how-to-configure-a-server-for-nice-urls] -------------------------------------------------------------------------------------------------- -**Apache**: Die Erweiterung mod_rewrite muss erlaubt und in einer Datei `.htaccess` konfiguriert sein. +**Apache**: Sie müssen mod_rewrite-Regeln in der Datei `.htaccess` aktivieren und einstellen: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Um die Apache-Konfiguration mit .htaccess-Dateien zu ändern, muss die AllowOverride-Direktive aktiviert werden. Dies ist das Standardverhalten von Apache. +Wenn Sie auf Probleme stoßen, stellen Sie sicher, dass: +- die Datei `.htaccess` sich im Document-Root-Verzeichnis befindet (d. h. neben der Datei `index.php` ) +- [Apache verarbeitet die Dateien .htaccess |#Test if .htaccess is working] +- [mod_rewrite ist aktiviert |#Test if mod_rewrite is enabled] + +Wenn Sie die Anwendung in einem Unterordner einrichten, müssen Sie möglicherweise die Zeile für die Einstellung `RewriteBase` auskommentieren und auf den richtigen Ordner setzen. **nginx**: Die Richtlinie `try_files` sollte in der Serverkonfiguration verwendet werden: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args IST WICHTIG! } ``` Der Block `location` muss genau einmal für jeden Dateisystempfad im Block `server` definiert werden. Wenn Sie bereits einen `location /` -Block in Ihrer Konfiguration haben, fügen Sie die Direktive `try_files` in den bestehenden Block ein. +Testen, ob `.htaccess` funktioniert .[#toc-test-if-htaccess-is-working] +----------------------------------------------------------------------- +Der einfachste Weg, um zu testen, ob der Apache Ihre `.htaccess` Datei verwendet oder ignoriert, ist, sie absichtlich zu unterbrechen. Setzen Sie die Zeile `Test` an den Anfang der Datei. Wenn Sie nun die Seite in Ihrem Browser aktualisieren, sollten Sie einen *Internal Server Error* sehen. + +Wenn Sie diesen Fehler sehen, ist das sogar gut! Das bedeutet, dass der Apache die Datei `.htaccess` analysiert und dabei auf den Fehler stößt, den wir dort eingefügt haben. Entfernen Sie die Zeile `Test`. + +Wenn Sie keinen *Internal Server Error* sehen, ignoriert Ihre Apache-Einrichtung die Datei `.htaccess`. Im Allgemeinen ignoriert der Apache die Datei aufgrund der fehlenden Konfigurationsanweisung `AllowOverride All`. + +Wenn Sie die Datei selbst hosten, ist das Problem leicht zu beheben. Öffnen Sie Ihre `httpd.conf` oder `apache.conf` in einem Texteditor, suchen Sie den entsprechenden `<Directory>` Abschnitt und fügen Sie die Direktive hinzu oder ändern Sie sie: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Wenn Ihre Website woanders gehostet wird, sehen Sie in Ihrem Kontrollpanel nach, ob Sie `.htaccess` dort aktivieren können. Wenn nicht, wenden Sie sich an Ihren Hosting-Anbieter, damit er dies für Sie erledigt. + + +Testen Sie, ob `mod_rewrite` aktiviert ist .[#toc-test-if-mod-rewrite-is-enabled] +--------------------------------------------------------------------------------- +Wenn Sie sich vergewissert haben, dass [`.htaccess` funktioniert |#Test if .htaccess is working], können Sie überprüfen, ob die mod_rewrite-Erweiterung aktiviert ist. Setzen Sie die Zeile `RewriteEngine On` an den Anfang der Datei `.htaccess` und aktualisieren Sie die Seite in Ihrem Browser. +Wenn Sie einen *Internal Server Error* sehen, bedeutet dies, dass mod_rewrite nicht aktiviert ist. Es gibt eine Reihe von Möglichkeiten, es zu aktivieren. Auf Stack Overflow finden Sie verschiedene Möglichkeiten, wie dies bei unterschiedlichen Konfigurationen geschehen kann. + + Links werden ohne `https:` erzeugt. .[#toc-links-are-generated-without-https] ----------------------------------------------------------------------------- Nette generiert Links mit demselben Protokoll, das die aktuelle Seite verwendet. Auf der Seite `https://foo` beginnen, und andersherum. diff --git a/nette/el/@home.texy b/nette/el/@home.texy index 16e3677045..24a21d5a36 100644 --- a/nette/el/@home.texy +++ b/nette/el/@home.texy @@ -6,13 +6,16 @@ Nette Τεκμηρίωση <div> -Γενικά ------- -- [Γιατί να χρησιμοποιήσετε τη Nette; |www:en:10-reasons-why-nette] -- <b>[Δημιουργήστε την πρώτη σας εφαρμογή! |quickstart:]<b> +Εισαγωγή +-------- +- [Γιατί να χρησιμοποιήσετε τη Nette; |www:10-reasons-why-nette] +- [Εγκατάσταση |Installation] +- [Δημιουργήστε την πρώτη σας εφαρμογή! |quickstart:] -- [Πακέτα & Εγκατάσταση |www:packages] +Γενικά +------ +- [Κατάλογος πακέτων |www:packages] - [Συντήρηση και PHP |www:maintenance] - [Σημειώσεις έκδοσης |https://nette.org/releases] - [Οδηγός αναβάθμισης |migrations:en] @@ -20,6 +23,7 @@ Nette Τεκμηρίωση - [Ποιος δημιουργεί το Nette |https://nette.org/contributors] - [Ιστορία της Nette |www:history] - [Συμμετέχετε |contributing:] +- [Ανάπτυξη χορηγού |https://nette.org/en/donate] - [Αναφορά API |https://api.nette.org/] </div> diff --git a/nette/el/installation.texy b/nette/el/installation.texy new file mode 100644 index 0000000000..e19f08781a --- /dev/null +++ b/nette/el/installation.texy @@ -0,0 +1,69 @@ +Εγκατάσταση Nette +***************** + +.[perex] +Η Nette είναι μια οικογένεια προηγμένων βιβλιοθηκών για την PHP που μπορείτε να χρησιμοποιήσετε εντελώς αυτόνομα. Η Nette είναι επίσης ένα πλαίσιο πλήρους υλοποίησης. Αυτή η σελίδα σας δείχνει πώς να εγκαταστήσετε κάθε βιβλιοθήκη ή ολόκληρο το πλαίσιο. + + +.[note] +Είστε νέοι στη Nette; Σας προτείνουμε επίσης να δείτε [το σεμινάριο Δημιουργία της πρώτης σας εφαρμογής |quickstart:]. + + +Πώς να εγκαταστήσετε πακέτα .[#toc-how-to-install-packages] +----------------------------------------------------------- + +Τα επιμέρους [πακέτα |www:packages] της οικογένειας Nette εγκαθίστανται χρησιμοποιώντας το εργαλείο [Composer |best-practices:composer]. Αν δεν είστε εξοικειωμένοι με αυτό, θα πρέπει οπωσδήποτε να ξεκινήσετε με αυτό. Πρόκειται για ένα πολύ χρήσιμο εργαλείο. + +Για παράδειγμα, χρειάζεται να διασχίσετε το σύστημα αρχείων στον κώδικά σας; Το [Finder |utils:finder], το οποίο περιλαμβάνεται στο πακέτο `nette/utils` (βλ. δεξιά στήλη), είναι εξαιρετικό γι' αυτό. Μπορείτε να το εγκαταστήσετε από τη γραμμή εντολών: + +```shell +composer require nette/utils +``` + +Αυτό θα εγκαταστήσει επίσης όλα τα άλλα πακέτα. + +Ένας εναλλακτικός τρόπος είναι να προσθέσετε όλα τα πακέτα ταυτόχρονα εγκαθιστώντας το `nette/nette`: + +```shell +composer require nette/nette +``` + + +Πώς να δημιουργήσετε ένα νέο έργο .[#toc-how-to-create-a-new-project] +--------------------------------------------------------------------- + +Σκοπεύετε να δημιουργήσετε ένα νέο έργο στη Nette; Ο ευκολότερος τρόπος για να ξεκινήσετε είναι να κατεβάσετε το βασικό σκελετό της εφαρμογής ιστού, που ονομάζεται [Web Project |https://github.com/nette/web-project]. + +Και πάλι, το Composer θα σας βοηθήσει να ρυθμίσετε το έργο σας. Βρείτε τον ριζικό κατάλογο του διακομιστή ιστού σας (π.χ. `/var/www` ή `C:\InetPub`). Εκτελέστε την ακόλουθη εντολή στη γραμμή εντολών, αλλά αντικαταστήστε το `my-project` με το όνομα του καταλόγου που θα δημιουργηθεί: + +```shell +composer create-project nette/web-project my-project +``` + +(Αν δεν θέλετε να χρησιμοποιήσετε το Composer, [κατεβάστε το αρχείο |https://github.com/nette/web-project/archive/preloaded.zip], αποσυμπιέστε το και αντιγράψτε το στον ριζικό κατάλογο του διακομιστή ιστού). + +Αν αναπτύσσετε σε macOS ή Linux (ή σε οποιοδήποτε άλλο σύστημα που βασίζεται σε Unix), θα χρειαστεί να [ορίσετε δικαιώματα εγγραφής |nette:troubleshooting#setting-directory-permissions]. + +Το σπουδαίο με το Nette είναι ότι δεν χρειάζεται να διαμορφώσετε ή να ρυθμίσετε κάτι περίπλοκο. Έτσι, σε αυτό το σημείο, η αρχική σελίδα του Web Project θα πρέπει να λειτουργεί. Μπορείτε να το δοκιμάσετε ανοίγοντας το πρόγραμμα περιήγησής σας στην ακόλουθη διεύθυνση URL: + +``` +http://localhost/my-project/www/ +``` + +και θα πρέπει να δείτε τη σελίδα καλωσορίσματος του Nette Framework: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Ζήτω, ο σκελετός λειτουργεί! Διαγράψτε το πρότυπο καλωσορίσματος και μπορείτε να αρχίσετε να γράφετε μια νέα υπέροχη εφαρμογή. + +.[note] +Αν υπάρχει κάποιο πρόβλημα, [δοκιμάστε αυτές τις μερικές συμβουλές |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Εργαλεία .[#toc-tools] +---------------------- + +Σας συνιστούμε να χρησιμοποιείτε ένα [ποιοτικό IDE και όλα τα απαραίτητα plugins |best-practices:editors-and-tools], θα σας κάνει εξαιρετικά αποδοτικούς. + + +{{leftbar: www:@menu-common}} diff --git a/nette/el/troubleshooting.texy b/nette/el/troubleshooting.texy index b17fea9893..1327ae3f3e 100644 --- a/nette/el/troubleshooting.texy +++ b/nette/el/troubleshooting.texy @@ -21,6 +21,11 @@ Μια από τις πιο συνηθισμένες αιτίες είναι μια ξεπερασμένη προσωρινή μνήμη. Ενώ η Nette ενημερώνει έξυπνα αυτόματα την προσωρινή μνήμη σε κατάσταση ανάπτυξης, σε κατάσταση παραγωγής εστιάζει στη μεγιστοποίηση της απόδοσης και η εκκαθάριση της προσωρινής μνήμης μετά από κάθε τροποποίηση κώδικα εξαρτάται από εσάς. Προσπαθήστε να διαγράψετε το `temp/cache`. +Σφάλμα 404, η δρομολόγηση δεν λειτουργεί .[#toc-error-404-routing-not-working] +------------------------------------------------------------------------------ +Όταν όλες οι σελίδες (εκτός από την αρχική σελίδα) επιστρέφουν ένα σφάλμα 404, φαίνεται ότι υπάρχει πρόβλημα διαμόρφωσης του διακομιστή για τις [όμορφες διευθύνσεις URL |#How to Configure a Server for Nice URLs?]. + + Σφάλμα `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ---------------------------------------------------------------------------------------------------------------------- Αυτό το σφάλμα εμφανίζεται αν έχετε αναβαθμίσει την PHP στην έκδοση 8.1 αλλά χρησιμοποιείτε τη Nette, η οποία δεν είναι συμβατή με αυτήν. Έτσι, η λύση είναι να ενημερώσετε τη Nette σε μια νεότερη έκδοση χρησιμοποιώντας το `composer update`. Η Nette υποστηρίζει την PHP 8.1 από την έκδοση 3.0. Αν χρησιμοποιείτε παλαιότερη έκδοση (μπορείτε να το διαπιστώσετε αναζητώντας στο `composer.json`), [αναβαθμίστε το Nette |migrations:en] ή μείνετε με την PHP 8.0. @@ -61,7 +66,7 @@ setsebool -P httpd_can_network_connect_db on Πώς να διαμορφώσετε έναν διακομιστή για ωραίες διευθύνσεις URL; .[#toc-how-to-configure-a-server-for-nice-urls] --------------------------------------------------------------------------------------------------------------- -**Apache**: Η επέκταση mod_rewrite πρέπει να επιτρέπεται και να ρυθμίζεται σε ένα αρχείο `.htaccess`. +**Apache**: πρέπει να ενεργοποιήσετε και να ορίσετε κανόνες mod_rewrite στο αρχείο `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Για να αλλάξετε τις ρυθμίσεις του Apache με αρχεία .htaccess, πρέπει να ενεργοποιήσετε την οδηγία AllowOverride. Αυτή είναι η προεπιλεγμένη συμπεριφορά για τον Apache. +Εάν αντιμετωπίσετε προβλήματα, βεβαιωθείτε ότι: +- το αρχείο `.htaccess` βρίσκεται στον κατάλογο document-root (δηλ. δίπλα στο αρχείο `index.php` ) +- [ο Apache επεξεργάζεται τα αρχεία .htaccess |#Test if .htaccess is working] +- το [mod_rewrite είναι ενεργοποιημένο |#Test if mod_rewrite is enabled] + +Αν ρυθμίζετε την εφαρμογή σε έναν υποφάκελο, ίσως χρειαστεί να ξεσχολιάσετε τη γραμμή για τη ρύθμιση `RewriteBase` και να την ορίσετε στο σωστό φάκελο. **nginx**: η οδηγία `try_files` πρέπει να χρησιμοποιείται στις ρυθμίσεις του διακομιστή: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args ΕΙΝΑΙ ΣΗΜΑΝΤΙΚΟ! } ``` Το μπλοκ `location` πρέπει να ορίζεται ακριβώς μία φορά για κάθε διαδρομή συστήματος αρχείων στο μπλοκ `server`. Εάν έχετε ήδη ένα μπλοκ `location /` στη διαμόρφωσή σας, προσθέστε την οδηγία `try_files` στο υπάρχον μπλοκ. +Ελέγξτε αν το `.htaccess` λειτουργεί .[#toc-test-if-htaccess-is-working] +------------------------------------------------------------------------ +Ο απλούστερος τρόπος για να ελέγξετε αν ο Apache χρησιμοποιεί ή αγνοεί το αρχείο σας `.htaccess`, είναι να το σπάσετε σκόπιμα. Βάλτε τη γραμμή `Test` στην αρχή του αρχείου και τώρα, αν ανανεώσετε τη σελίδα στο πρόγραμμα περιήγησής σας, θα πρέπει να δείτε ένα *Σφάλμα εσωτερικού διακομιστή*. + +Αν δείτε αυτό το σφάλμα, αυτό είναι πραγματικά καλό! Αυτό σημαίνει ότι ο Apache αναλύει το αρχείο `.htaccess` και συναντά το σφάλμα που έχουμε βάλει εκεί. Αφαιρέστε τη γραμμή `Test`. + +Αν δεν δείτε ένα *Εσωτερικό σφάλμα διακομιστή*, η ρύθμιση του Apache αγνοεί το αρχείο `.htaccess`. Γενικά, ο Apache το αγνοεί λόγω της έλλειψης της οδηγίας διαμόρφωσης `AllowOverride All`. + +Εάν το φιλοξενείτε μόνοι σας, είναι αρκετά εύκολο να το διορθώσετε. Ανοίξτε το `httpd.conf` ή το `apache.conf` σε έναν επεξεργαστή κειμένου, εντοπίστε το σχετικό `<Directory>` τμήμα και προσθέστε/αλλάξτε την οδηγία: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Αν ο ιστότοπός σας φιλοξενείται αλλού, ελέγξτε τον πίνακα ελέγχου σας για να δείτε αν μπορείτε να ενεργοποιήσετε το `.htaccess` εκεί. Αν όχι, επικοινωνήστε με τον πάροχο φιλοξενίας σας για να το κάνει για εσάς. + + +Ελέγξτε αν το `mod_rewrite` είναι ενεργοποιημένο .[#toc-test-if-mod-rewrite-is-enabled] +--------------------------------------------------------------------------------------- +Εάν έχετε επαληθεύσει ότι [το`.htaccess` λειτουργεί |#Test if .htaccess is working], μπορείτε να επαληθεύσετε ότι η επέκταση mod_rewrite είναι ενεργοποιημένη. Βάλτε τη γραμμή `RewriteEngine On` στην αρχή του αρχείου `.htaccess` και ανανεώστε τη σελίδα στο πρόγραμμα περιήγησής σας. +Αν δείτε ένα *Σφάλμα εσωτερικού διακομιστή*, αυτό σημαίνει ότι το mod_rewrite δεν είναι ενεργοποιημένο. Υπάρχουν διάφοροι τρόποι για να το ενεργοποιήσετε. Ανατρέξτε στο Stack Overflow για διάφορους τρόπους που μπορεί να γίνει αυτό σε διαφορετικές ρυθμίσεις. + + Οι σύνδεσμοι δημιουργούνται χωρίς το `https:` .[#toc-links-are-generated-without-https] --------------------------------------------------------------------------------------- Η Nette παράγει συνδέσμους με το ίδιο πρωτόκολλο που χρησιμοποιεί η τρέχουσα σελίδα. Έτσι, στη σελίδα `https://foo` και αντίστροφα. diff --git a/nette/en/@home.texy b/nette/en/@home.texy index c73e513c83..2fe80a3dab 100644 --- a/nette/en/@home.texy +++ b/nette/en/@home.texy @@ -6,13 +6,16 @@ Nette Documentation <div> -General -------- +Introduction +------------ - [Why Use Nette?|www:10-reasons-why-nette] -- <b>[Create Your First Application! |quickstart:]<b> +- [Installation] +- [Create Your First Application! |quickstart:] -- [Packages & Installation |www:packages] +General +------- +- [List of Packages |www:packages] - [Maintenance and PHP |www:maintenance] - [Release Notes |https://nette.org/releases] - [Upgrade Guide |migrations:] @@ -20,6 +23,7 @@ General - [Who Creates Nette |https://nette.org/contributors] - [History of Nette |www:history] - [Get Involved |contributing:] +- [Sponsor development |https://nette.org/en/donate] - [API reference |https://api.nette.org/] </div> diff --git a/nette/en/installation.texy b/nette/en/installation.texy new file mode 100644 index 0000000000..c01cb58366 --- /dev/null +++ b/nette/en/installation.texy @@ -0,0 +1,69 @@ +Installing Nette +**************** + +.[perex] +Nette is a family of advanced libraries for PHP that you can use completely standalone. Nette is also a full-stack framework. This page shows you how to install each library or the whole framework. + + +.[note] +Are you new to Nette? We also recommend you to check out [the tutorial Create Your First Application |quickstart:]. + + +How to Install Packages +----------------------- + +The individual [packages |www:packages] in the Nette family are installed using the [Composer |best-practices:composer] tool. If you are not familiar with it, you should definitely start with it. It is a very useful tool. + +For example, do you need to traverse the file system in your code? The [Finder |utils:finder], which is included in the `nette/utils` package (see right column), is great for that. You can install it from the command line: + +```shell +composer require nette/utils +``` + +This will also install all other packages. + +An alternative way is to add all the packages at once by installing `nette/nette`: + +```shell +composer require nette/nette +``` + + +How to Create a New Project +--------------------------- + +Are you going to build a new project on Nette? The easiest way to start is to download the basic web application skeleton, called [Web Project |https://github.com/nette/web-project]. + +Again, Composer will help you set up your project. Find the root directory of your web server (e.g., `/var/www` or `C:\InetPub`). Run the following command at the command prompt, but replace `my-project` with the name of directory to be created: + +```shell +composer create-project nette/web-project my-project +``` + +(If you don't want to use Composer, [download the archive |https://github.com/nette/web-project/archive/preloaded.zip], unzip it, and copy it to the root directory of the web server.) + +If you are developing on macOS or Linux (or any other Unix-based system), you will still need to [set write permissions|nette:troubleshooting#setting-directory-permissions]. + +The great thing about Nette is that you don't have to configure or set up anything complicated. So by this point, the Web Project home page should be working. You can test this by opening your browser at the following URL: + +``` +http://localhost/my-project/www/ +``` + +and you should see the Nette Framework welcome page: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Hooray, the skeleton works! Delete the welcome template and you can start writing a great new application. + +.[note] +If there's a problem, [try these few tips |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Tools +----- + +We recommend using a [quality IDE and all the necessary plugins |best-practices:editors-and-tools], it will make you extremely efficient. + + +{{leftbar: www:@menu-common}} diff --git a/nette/en/troubleshooting.texy b/nette/en/troubleshooting.texy index 4e71bb00ac..e2c92cd0a4 100644 --- a/nette/en/troubleshooting.texy +++ b/nette/en/troubleshooting.texy @@ -21,6 +21,11 @@ If the sentence `Tracy is unable to log error` is not in the error message (anym One of the most common reasons is an outdated cache. While Nette cleverly automatically updates the cache in development mode, in production mode it focuses on maximizing performance, and clearing the cache after each code modification is up to you. Try to delete `temp/cache`. +Error 404, Routing Not Working +------------------------------ +When all pages (except the homepage) return a 404 error, it looks like a server configuration problem for [pretty URLs |#How to Configure a Server for Nice URLs?]. + + Error `#[\ReturnTypeWillChange] attribute should be used` --------------------------------------------------------- This error occurs if you have upgraded PHP to version 8.1 but are using Nette, which is not compatible with it. So the solution is to update Nette to a newer version using `composer update`. Nette has supported PHP 8.1 since version 3.0. If you are using an older version (you can find out by looking in `composer.json`), [upgrade Nette |migrations:] or stay with PHP 8.0. @@ -61,7 +66,7 @@ The solution **isn't** to "get rid" of the `www/` folder using rules in the `.ht How to Configure a Server for Nice URLs? ---------------------------------------- -**Apache**: extension mod_rewrite must be allowed and configured in a `.htaccess` file. +**Apache**: you need to enable and set mod_rewrite rules in the `.htaccess` file: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -To alter Apache configuration with .htaccess files, the AllowOverride directive has to be enabled. This is the default behavior for Apache. +If you run into problems, make sure that: +- the `.htaccess` file is located in the document-root directory (i.e. next to the `index.php` file) +- [Apache is processing the files .htaccess|#Test if .htaccess is working] +- [mod_rewrite is enabled |#Test if mod_rewrite is enabled] + +If you're setting up application in a subfolder, you might have to uncomment the line for the `RewriteBase` setting and set it to the correct folder. **nginx**: the `try_files` directive should be used in server configuration: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args IS IMPORTANT! } ``` Block `location` must be defined exactly once for each filesystem path in `server` block. If you already have a `location /` block in your configuration, add the `try_files` directive into the existing block. +Test If `.Htaccess` Is Working +------------------------------ +The simplest way to test if Apache uses or ignores your `.htaccess` file, is to intentionally break it. Put the line `Test` at the beginning of the file and now, if you refresh the page in your browser, you should see an *Internal Server Error*. + +If you see this error, that's actually good! This means that Apache is parsing the `.htaccess` file, and it encounters the error we've put in there. Remove the line `Test`. + +If you do not see an *Internal Server Error*, your Apache setup ignores the `.htaccess` file. Generally, Apache ignores it because of the missing configuration directive `AllowOverride All`. + +If you are hosting it yourself, it's easy enough to fix. Open your `httpd.conf` or `apache.conf` in a text editor, locate the relevant `<Directory>` section and add/change the directive: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +If your site is hosted elsewhere, check your control panel to see if you can enable `.htaccess` there. If not, contact your hosting provider to do it for you. + + +Test If `mod_rewrite` Is Enabled +-------------------------------- +If you have verified that [`.htaccess` works |#Test if .htaccess is working], you can verify that the mod_rewrite extension is enabled. Put the line `RewriteEngine On` at the beginning of the `.htaccess` file and refresh the page in your browser. +If you see an *Internal Server Error*, it means that mod_rewrite is not enabled. There are a number of ways to enable it. See Stack Overflow for various ways this may be done on different setups. + + Links Are Generated Without `https:` ------------------------------------ Nette generates links with the same protocol as the current page is using. So on the `https://foo` page, it generates links starting with `https:` and vice versa. diff --git a/nette/es/@home.texy b/nette/es/@home.texy index 5c81e00c6a..c6644ab4c5 100644 --- a/nette/es/@home.texy +++ b/nette/es/@home.texy @@ -6,11 +6,16 @@ Documentación de Nette <div> +Introducción +------------ +- [¿Por qué utilizar Nette? |www:10-reasons-why-nette] +- [Instalación |Installation] +- [Cree su primera aplicación |quickstart:] + + General ------- -- <b>[¡Cree su primera aplicación! |quickstart:]<b> - -- [Paquetes e Instalación |www:packages] +- [Lista de paquetes |www:packages] - [Mantenimiento y PHP |www:maintenance] - [Notas de la versión |https://nette.org/releases] - [Guía de actualización |migrations:en] @@ -18,6 +23,7 @@ General - [Quién crea Nette|https://nette.org/contributors] - [Historia de Nette|www:history] - [Implíquese |contributing:] +- [Desarrollo del patrocinio |https://nette.org/en/donate] - [Referencia API |https://api.nette.org/] </div> diff --git a/nette/es/installation.texy b/nette/es/installation.texy new file mode 100644 index 0000000000..2526022e69 --- /dev/null +++ b/nette/es/installation.texy @@ -0,0 +1,69 @@ +Instalación de Nette +******************** + +.[perex] +Nette es una familia de librerías avanzadas para PHP que puedes utilizar de forma completamente autónoma. Nette es también un framework completo. Esta página le muestra cómo instalar cada librería o el framework completo. + + +.[note] +¿Eres nuevo en Nette? También te recomendamos que eches un vistazo [al tutorial Crea tu primera aplicación |quickstart:]. + + +Cómo instalar paquetes .[#toc-how-to-install-packages] +------------------------------------------------------ + +Los [paquetes |www:packages] individuales de la familia Nette se instalan utilizando la herramienta [Composer |best-practices:composer]. Si no está familiarizado con ella, debería empezar a utilizarla. Es una herramienta muy útil. + +Por ejemplo, ¿necesitas recorrer el sistema de archivos en tu código? El [Finder |utils:finder], que se incluye en el paquete `nette/utils` (véase la columna de la derecha), es estupendo para ello. Puedes instalarlo desde la línea de comandos: + +```shell +composer require nette/utils +``` + +Esto también instalará todos los demás paquetes. + +Una forma alternativa es añadir todos los paquetes a la vez instalando `nette/nette`: + +```shell +composer require nette/nette +``` + + +Cómo crear un nuevo proyecto .[#toc-how-to-create-a-new-project] +---------------------------------------------------------------- + +¿Vas a crear un nuevo proyecto en Nette? La forma más fácil de empezar es descargar el esqueleto básico de aplicación web, llamado [Proyecto Web |https://github.com/nette/web-project]. + +De nuevo, Composer te ayudará a configurar tu proyecto. Busque el directorio raíz de su servidor web (por ejemplo, `/var/www` o `C:\InetPub`). Ejecute el siguiente comando en el símbolo del sistema, pero sustituya `my-project` por el nombre del directorio que desea crear: + +```shell +composer create-project nette/web-project my-project +``` + +(Si no desea utilizar Composer, [descargue el archivo |https://github.com/nette/web-project/archive/preloaded.zip], descomprímalo y cópielo en el directorio raíz del servidor web). + +Si estás desarrollando en macOS o Linux (o cualquier otro sistema basado en Unix), aún necesitarás [establecer permisos de escritura |nette:troubleshooting#setting-directory-permissions]. + +Lo bueno de Nette es que no tienes que configurar nada complicado. Llegados a este punto, la página de inicio del Proyecto Web debería estar funcionando. Puede probarlo abriendo su navegador en la siguiente URL: + +``` +http://localhost/my-project/www/ +``` + +y deberías ver la página de bienvenida de Nette Framework: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +¡Hurra, el esqueleto funciona! Borra la plantilla de bienvenida y podrás empezar a escribir una nueva aplicación. + +.[note] +Si hay algún problema, [prueba estos consejos |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Herramientas .[#toc-tools] +-------------------------- + +Recomendamos usar un [IDE de calidad y todos los plugins |best-practices:editors-and-tools] necesarios, te hará extremadamente eficiente. + + +{{leftbar: www:@menu-common}} diff --git a/nette/es/troubleshooting.texy b/nette/es/troubleshooting.texy index 6089e15492..ed935daf87 100644 --- a/nette/es/troubleshooting.texy +++ b/nette/es/troubleshooting.texy @@ -21,6 +21,11 @@ Si la sentencia `Tracy is unable to log error` no aparece en el mensaje de error Una de las razones más comunes es una caché obsoleta. Mientras que Nette inteligentemente actualiza automáticamente la caché en modo desarrollo, en modo producción se centra en maximizar el rendimiento, y limpiar la caché después de cada modificación de código depende de ti. Intente borrar `temp/cache`. +Error 404, enrutamiento no funciona .[#toc-error-404-routing-not-working] +------------------------------------------------------------------------- +Cuando todas las páginas (excepto la página de inicio) devuelven un error 404, parece un problema de configuración del servidor para [URLs bonitas |#How to Configure a Server for Nice URLs?]. + + Error `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] --------------------------------------------------------------------------------------------------------------------- Este error se produce si ha actualizado PHP a la versión 8.1 pero está utilizando Nette, que no es compatible con ella. Entonces la solución es actualizar Nette a una versión más reciente usando `composer update`. Nette es compatible con PHP 8.1 desde la versión 3.0. Si está usando una versión anterior (puede averiguarlo buscando en `composer.json`), [actualice |migrations:en] Nette o quédese con PHP 8.0. @@ -61,7 +66,7 @@ La solución **no** es "deshacerse" de la carpeta `www/` utilizando reglas en el ¿Cómo configurar un servidor para URLs agradables? .[#toc-how-to-configure-a-server-for-nice-urls] -------------------------------------------------------------------------------------------------- -**Apache**: la extensión mod_rewrite debe estar permitida y configurada en un archivo `.htaccess`. +**Apache**: debe activar y establecer reglas mod_rewrite en el archivo `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Para alterar la configuración de Apache con archivos .htaccess, la directiva AllowOverride debe estar habilitada. Este es el comportamiento por defecto de Apache. +Si tiene problemas, asegúrese de que +- el archivo `.htaccess` se encuentra en el directorio document-root (es decir, junto al archivo `index.php` ) +- [Apache está procesando los archivos .htaccess |#Test if .htaccess is working] +- [mod_rewrite está activado |#Test if mod_rewrite is enabled] + +Si está configurando la aplicación en una subcarpeta, es posible que tenga que descomentar la línea para la configuración de `RewriteBase` y establecerla en la carpeta correcta. **nginx**: la directiva `try_files` debe utilizarse en la configuración del servidor: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args ¡ES IMPORTANTE! } ``` El bloque `location` debe definirse exactamente una vez para cada ruta del sistema de ficheros en el bloque `server`. Si ya tiene un bloque `location /` en su configuración, añada la directiva `try_files` al bloque existente. +Compruebe si `.htaccess` funciona .[#toc-test-if-htaccess-is-working] +--------------------------------------------------------------------- +La forma más sencilla de probar si Apache usa o ignora su fichero `.htaccess`, es romperlo intencionadamente. Ponga la línea `Test` al principio del archivo y ahora, si actualiza la página en su navegador, debería ver un *Internal Server Error*. + +Si ve este error, ¡en realidad es bueno! Esto significa que Apache está analizando el archivo `.htaccess`, y encuentra el error que hemos puesto allí. Elimine la línea `Test`. + +Si no ve un *Error Interno del Servidor*, su configuración de Apache ignora el fichero `.htaccess`. Generalmente, Apache lo ignora porque falta la directiva de configuración `AllowOverride All`. + +Si lo aloja usted mismo, es bastante fácil de arreglar. Abra su `httpd.conf` o `apache.conf` en un editor de texto, localice la sección relevante y añada/cambie la directiva `<Directory>` y añada/cambie la directiva: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Si su sitio está alojado en otro lugar, compruebe su panel de control para ver si puede activar `.htaccess` allí. Si no es así, póngase en contacto con su proveedor de alojamiento para que lo haga por usted. + + +Compruebe si `mod_rewrite` está habilitado .[#toc-test-if-mod-rewrite-is-enabled] +--------------------------------------------------------------------------------- +Si ha comprobado que [`.htaccess` funciona |#Test if .htaccess is working], puede verificar que la extensión mod_rewrite está habilitada. Ponga la línea `RewriteEngine On` al principio del archivo `.htaccess` y actualice la página en su navegador. +Si ves un *Internal Server Error*, significa que mod_rewrite no está habilitado. Hay varias formas de activarlo. Consulte Stack Overflow para ver las distintas formas de hacerlo en diferentes configuraciones. + + Los enlaces se generan sin `https:` .[#toc-links-are-generated-without-https] ----------------------------------------------------------------------------- Nette genera enlaces con el mismo protocolo que utiliza la página actual. Así, en la página `https://foo` y viceversa. diff --git a/nette/files/qs-welcome.webp b/nette/files/qs-welcome.webp new file mode 100644 index 0000000000..13576130eb Binary files /dev/null and b/nette/files/qs-welcome.webp differ diff --git a/nette/fr/@home.texy b/nette/fr/@home.texy index 98e34a458e..76241bb9d3 100644 --- a/nette/fr/@home.texy +++ b/nette/fr/@home.texy @@ -6,12 +6,16 @@ Documentation Nette <div> -Généralités ------------ -- <b>[Créez votre première application ! |quickstart:]<b> +Introduction +------------ +- [Pourquoi utiliser Nette ? |www:10-reasons-why-nette] +- L'[installation |Installation] +- [Créez votre première application ! |quickstart:] -- [Paquets et installation |www:packages] +Généralités +----------- +- [Liste des paquets |www:packages] - [Maintenance et PHP |www:maintenance] - [Notes de version |https://nette.org/releases] - [Guide de mise à niveau |migrations:en] @@ -19,6 +23,7 @@ Généralités - [Qui crée Nette |https://nette.org/contributors] - [Histoire de Nette |www:history] - [S'impliquer |contributing:] +- [Développement du parrainage |https://nette.org/en/donate] - [Référence API |https://api.nette.org/] </div> diff --git a/nette/fr/installation.texy b/nette/fr/installation.texy new file mode 100644 index 0000000000..b5a375f25d --- /dev/null +++ b/nette/fr/installation.texy @@ -0,0 +1,69 @@ +Installation de Nette +********************* + +.[perex] +Nette est une famille de bibliothèques avancées pour PHP que vous pouvez utiliser de manière autonome. Nette est également un framework complet. Cette page vous montre comment installer chaque bibliothèque ou le framework complet. + + +.[note] +Vous êtes novice en matière de Nette ? Nous vous recommandons également de consulter [le tutoriel Créer votre première application |quickstart:]. + + +Comment installer des paquets .[#toc-how-to-install-packages] +------------------------------------------------------------- + +Les différents [paquets de |www:packages] la famille Nette sont installés à l'aide de l'outil [Composer |best-practices:composer]. Si vous n'êtes pas familier avec cet outil, vous devriez absolument commencer à l'utiliser. C'est un outil très utile. + +Par exemple, avez-vous besoin de parcourir le système de fichiers dans votre code ? Le [Finder |utils:finder], qui est inclus dans le paquetage `nette/utils` (voir colonne de droite), est parfait pour cela. Vous pouvez l'installer à partir de la ligne de commande : + +```shell +composer require nette/utils +``` + +Cela installera également tous les autres paquets. + +Une autre solution consiste à ajouter tous les paquets en une seule fois en installant `nette/nette`: + +```shell +composer require nette/nette +``` + + +Comment créer un nouveau projet .[#toc-how-to-create-a-new-project] +------------------------------------------------------------------- + +Vous allez créer un nouveau projet sur Nette ? La façon la plus simple de commencer est de télécharger le squelette d'application web de base, appelé [Projet Web. |https://github.com/nette/web-project] + +Là encore, Composer vous aidera à configurer votre projet. Trouvez le répertoire racine de votre serveur web (par exemple, `/var/www` ou `C:\InetPub`). Exécutez la commande suivante à l'invite de commande, mais remplacez `my-project` par le nom du répertoire à créer : + +```shell +composer create-project nette/web-project my-project +``` + +(Si vous ne souhaitez pas utiliser Composer, [téléchargez l'archive |https://github.com/nette/web-project/archive/preloaded.zip], décompressez-la et copiez-la dans le répertoire racine du serveur web). + +Si vous développez sous macOS ou Linux (ou tout autre système basé sur Unix), vous devrez toujours [définir des droits d'écriture |nette:troubleshooting#setting-directory-permissions]. + +L'avantage de Nette est qu'il n'est pas nécessaire de configurer ou d'installer quoi que ce soit de compliqué. À ce stade, la page d'accueil du projet Web devrait fonctionner. Vous pouvez la tester en ouvrant votre navigateur à l'URL suivante : + +``` +http://localhost/my-project/www/ +``` + +et vous devriez voir la page d'accueil du Nette Framework : + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Hourra, le squelette fonctionne ! Supprimez le modèle de bienvenue et vous pourrez commencer à écrire une nouvelle application géniale. + +.[note] +En cas de problème, [essayez ces quelques conseils |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Outils .[#toc-tools] +-------------------- + +Nous vous recommandons d'utiliser un [IDE de qualité et tous les plugins nécessaires |best-practices:editors-and-tools], cela vous rendra extrêmement efficace. + + +{{leftbar: www:@menu-common}} diff --git a/nette/fr/troubleshooting.texy b/nette/fr/troubleshooting.texy index 30346cc68f..2b51a5fb50 100644 --- a/nette/fr/troubleshooting.texy +++ b/nette/fr/troubleshooting.texy @@ -21,6 +21,11 @@ Si la phrase `Tracy is unable to log error` ne figure pas (ou plus) dans le mess L'une des raisons les plus courantes est un cache obsolète. Alors que Nette met automatiquement et intelligemment à jour le cache en mode développement, en mode production, il se concentre sur l'optimisation des performances, et c'est à vous de vider le cache après chaque modification de code. Essayez de supprimer `temp/cache`. +Erreur 404, le routage ne fonctionne pas .[#toc-error-404-routing-not-working] +------------------------------------------------------------------------------ +Lorsque toutes les pages (sauf la page d'accueil) renvoient une erreur 404, il semble qu'il y ait un problème de configuration du serveur pour les [jolies URL |#How to Configure a Server for Nice URLs?]. + + Erreur `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ---------------------------------------------------------------------------------------------------------------------- Cette erreur se produit si vous avez mis à niveau PHP vers la version 8.1 mais que vous utilisez Nette, qui n'est pas compatible avec cette version. La solution est donc de mettre à jour Nette vers une version plus récente en utilisant `composer update`. Nette supporte PHP 8.1 depuis la version 3.0. Si vous utilisez une version plus ancienne (vous pouvez le savoir en consultant `composer.json`), [mettez à jour Nette |migrations:en] ou restez avec PHP 8.0. @@ -61,7 +66,7 @@ La solution **n'est pas** de se "débarrasser" du dossier `www/` en utilisant de Comment configurer un serveur pour de belles URL ? .[#toc-how-to-configure-a-server-for-nice-urls] -------------------------------------------------------------------------------------------------- -**Apache** : l'extension mod_rewrite doit être autorisée et configurée dans un fichier `.htaccess`. +**Apache** : vous devez activer et définir les règles mod_rewrite dans le fichier `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Pour modifier la configuration d'Apache avec des fichiers .htaccess, la directive AllowOverride doit être activée. Il s'agit du comportement par défaut d'Apache. +Si vous rencontrez des problèmes, assurez-vous que +- le fichier `.htaccess` se trouve dans le répertoire document-root (c'est-à-dire à côté du fichier `index.php` ) +- [Apache traite les fichiers .htaccess |#Test if .htaccess is working] +- [mod_rewrite est activé |#Test if mod_rewrite is enabled] + +Si vous configurez l'application dans un sous-dossier, il se peut que vous deviez décommenter la ligne pour le paramètre `RewriteBase` et la définir sur le bon dossier. **nginx** : la directive `try_files` doit être utilisée dans la configuration du serveur : ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args EST IMPORTANT ! } ``` Le bloc `location` doit être défini exactement une fois pour chaque chemin du système de fichiers dans le bloc `server`. Si vous avez déjà un bloc `location /` dans votre configuration, ajoutez la directive `try_files` dans le bloc existant. +Tester si `.htaccess` fonctionne .[#toc-test-if-htaccess-is-working] +-------------------------------------------------------------------- +La manière la plus simple de tester si Apache utilise ou ignore votre fichier `.htaccess` est de le casser intentionnellement. Mettez la ligne `Test` au début du fichier et maintenant, si vous rafraîchissez la page dans votre navigateur, vous devriez voir une *Internal Server Error*. + +Si vous voyez cette erreur, c'est en fait une bonne chose ! Cela signifie qu'Apache analyse le fichier `.htaccess` et qu'il rencontre l'erreur que nous y avons placée. Supprimez la ligne `Test`. + +Si vous ne voyez pas d'erreur *Internal Server Error*, votre configuration Apache ignore le fichier `.htaccess`. En général, Apache l'ignore à cause de la directive de configuration manquante `AllowOverride All`. + +Si vous l'hébergez vous-même, c'est assez facile à résoudre. Ouvrez votre `httpd.conf` ou `apache.conf` dans un éditeur de texte, localisez la section correspondante et ajoutez/modifiez la directive. `<Directory>` et ajoutez/modifiez la directive : + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Si votre site est hébergé ailleurs, vérifiez dans votre panneau de contrôle si vous pouvez activer `.htaccess`. Si ce n'est pas le cas, contactez votre hébergeur pour qu'il le fasse pour vous. + + +Tester si `mod_rewrite` est activé .[#toc-test-if-mod-rewrite-is-enabled] +------------------------------------------------------------------------- +Si vous avez vérifié que [`.htaccess` fonctionne |#Test if .htaccess is working], vous pouvez vérifier que l'extension mod_rewrite est activée. Placez la ligne `RewriteEngine On` au début du fichier `.htaccess` et rafraîchissez la page dans votre navigateur. +Si vous voyez une *Internal Server Error*, cela signifie que l'extension mod_rewrite n'est pas activée. Il existe plusieurs façons de l'activer. Consultez Stack Overflow pour connaître les différentes façons de procéder selon les configurations. + + Les liens sont générés sans `https:` .[#toc-links-are-generated-without-https] ------------------------------------------------------------------------------ Nette génère des liens avec le même protocole que celui utilisé par la page actuelle. Ainsi, sur la page `https://foo` et vice versa. diff --git a/nette/hu/@home.texy b/nette/hu/@home.texy index e475ce3512..53d3959489 100644 --- a/nette/hu/@home.texy +++ b/nette/hu/@home.texy @@ -6,13 +6,16 @@ Nette dokumentáció <div> -Általános +Bevezetés --------- - [Miért használja a Nette-et? |www:10-reasons-why-nette] -- <b>[Készítse el első alkalmazását! |quickstart:]<b> +- [Telepítés |Installation] +- [Készítse el első alkalmazását! |quickstart:] -- [Csomagok és telepítés |www:packages] +Általános +--------- +- [Csomagok listája |www:packages] - [Karbantartás és PHP |www:maintenance] - [Kiadási megjegyzések |https://nette.org/releases] - [Frissítési útmutató |migrations:en] @@ -20,6 +23,7 @@ Nette dokumentáció - [Ki hozza létre a Nette-et |https://nette.org/contributors] - [A Nette története |www:history] - [Vegyen részt |contributing:] +- [Szponzorfejlesztés |https://nette.org/en/donate] - [API hivatkozás |https://api.nette.org/] </div> diff --git a/nette/hu/installation.texy b/nette/hu/installation.texy new file mode 100644 index 0000000000..419d2ba69c --- /dev/null +++ b/nette/hu/installation.texy @@ -0,0 +1,69 @@ +Nette telepítése +**************** + +.[perex] +A Nette egy fejlett könyvtárcsalád a PHP számára, amelyet teljesen önállóan is használhat. A Nette egyben egy full-stack keretrendszer is. Ez az oldal megmutatja, hogyan telepítheted az egyes könyvtárakat vagy a teljes keretrendszert. + + +.[note] +Új vagy a Nette-ben? Javasoljuk, hogy nézze meg [az Első alkalmazás létrehozása című bemutatót |quickstart:] is. + + +Csomagok telepítése .[#toc-how-to-install-packages] +--------------------------------------------------- + +A Nette család egyes [csomagjait |www:packages] a [Composer |best-practices:composer] eszközzel telepítheti. Ha még nem ismeri, mindenképpen ezzel kell kezdenie. Ez egy nagyon hasznos eszköz. + +Szüksége van például arra, hogy a fájlrendszert átjárja a kódjában? A [Finder |utils:finder], amely a `nette/utils` csomagban található (lásd a jobb oldali oszlopot), kiválóan alkalmas erre. A parancssorból is telepítheted: + +```shell +composer require nette/utils +``` + +Ez az összes többi csomagot is telepíti. + +Egy másik lehetőség az összes csomag egyszerre történő hozzáadása a `nette/nette` telepítésével: + +```shell +composer require nette/nette +``` + + +Új projekt létrehozása .[#toc-how-to-create-a-new-project] +---------------------------------------------------------- + +Új projektet szeretne létrehozni a Nette-en? A legegyszerűbben úgy kezdheti, ha letölti a [Web Project |https://github.com/nette/web-project] nevű alap webalkalmazás vázát. + +A Composer ismét segít a projekt beállításában. Keresse meg a webszerver gyökérkönyvtárát (pl. `/var/www` vagy `C:\InetPub`). Futtassa a parancssorban a következő parancsot, de a `my-project` helyett a létrehozandó könyvtár nevét írja be: + +```shell +composer create-project nette/web-project my-project +``` + +(Ha nem akarja használni a Composert, [töltse le az archívumot |https://github.com/nette/web-project/archive/preloaded.zip], csomagolja ki, és másolja a webkiszolgáló gyökérkönyvtárába). + +Ha macOS-en vagy Linuxon (vagy bármely más Unix-alapú rendszeren) fejleszt, akkor is [be kell állítania az írási engedélyeket |nette:troubleshooting#setting-directory-permissions]. + +A Nette nagyszerű tulajdonsága, hogy nem kell semmi bonyolultat konfigurálnod vagy beállítanod. Tehát ezen a ponton a webes projekt kezdőlapjának már működnie kell. Ezt úgy tesztelheti, hogy megnyitja a böngészőjét a következő URL címen: + +``` +http://localhost/my-project/www/ +``` + +és a Nette Framework üdvözlő oldalát kell látnia: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Hurrá, a váz működik! Töröld az üdvözlő sablont, és máris elkezdheted írni az új, nagyszerű alkalmazást. + +.[note] +Ha gond van, [próbáld ki ezt a néhány tippet |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Eszközök .[#toc-tools] +---------------------- + +Javasoljuk, hogy használjon egy [minőségi IDE-t és az összes szükséges bővítményt |best-practices:editors-and-tools], ez rendkívül hatékonnyá teszi Önt. + + +{{leftbar: www:@menu-common}} diff --git a/nette/hu/troubleshooting.texy b/nette/hu/troubleshooting.texy index 03c4e388fa..518e9032f5 100644 --- a/nette/hu/troubleshooting.texy +++ b/nette/hu/troubleshooting.texy @@ -21,6 +21,11 @@ Ha a `Tracy is unable to log error` mondat nem szerepel a hibaüzenetben (már), Az egyik leggyakoribb ok az elavult gyorsítótár. Míg a Nette okosan, automatikusan frissíti a gyorsítótárat fejlesztői módban, addig termelési módban a teljesítmény maximalizálására összpontosít, és a gyorsítótár törlése minden kódmódosítás után az Ön feladata. Próbálja meg törölni a `temp/cache`. +404-es hiba, útválasztás nem működik .[#toc-error-404-routing-not-working] +-------------------------------------------------------------------------- +Amikor minden oldal (a kezdőlap kivételével) 404-es hibát ad vissza, úgy tűnik, hogy a szerver konfigurációs problémája van a [szép URL-eknél |#How to Configure a Server for Nice URLs?]. + + Hiba `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] -------------------------------------------------------------------------------------------------------------------- Ez a hiba akkor jelentkezik, ha a PHP-t a 8.1-es verzióra frissítette, de a Nette-et használja, amely nem kompatibilis vele. A megoldás tehát a Nette újabb verzióra való frissítése a `composer update` segítségével. A Nette a 3.0-s verzió óta támogatja a PHP 8.1-es verzióját. Ha régebbi verziót használ (ezt a `composer.json` oldalon találja meg), [frissítse a Nette-et |migrations:en], vagy maradjon a PHP 8.0-nál. @@ -61,7 +66,7 @@ A megoldás **nem** a `www/` mappától való "megszabadulás" a `.htaccess` fá Hogyan konfiguráljunk egy kiszolgálót a szép URL-ekhez? .[#toc-how-to-configure-a-server-for-nice-urls] ------------------------------------------------------------------------------------------------------- -**Apache**: a mod_rewrite kiterjesztést engedélyezni kell és be kell állítani a `.htaccess` fájlban. +**Apache**: engedélyeznie és beállítania kell a mod_rewrite szabályokat a `.htaccess` fájlban: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Az Apache konfigurációjának .htaccess fájlokkal történő módosításához engedélyezni kell az AllowOverride direktívát. Ez az Apache alapértelmezett viselkedése. +Ha problémák merülnek fel, győződjön meg róla, hogy: +- a `.htaccess` fájl a document-root könyvtárban található (azaz a `index.php` fájl mellett). +- az [Apache feldolgozza a .htaccess fájlokat |#Test if .htaccess is working]. +- a [mod_rewrite engedélyezve van |#Test if mod_rewrite is enabled] + +Ha egy almappában állítod be az alkalmazást, akkor előfordulhat, hogy ki kell venned a `RewriteBase` beállításhoz tartozó sort, és a megfelelő mappára kell beállítanod. **nginx**: a `try_files` direktívát kell használni a szerverkonfigurációban: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args FONTOS! } ``` A `location` blokkot pontosan egyszer kell definiálni a `server` blokk minden egyes fájlrendszeri elérési útvonalához. Ha már van `location /` blokk a konfigurációban, akkor a `try_files` direktívát adja hozzá a meglévő blokkhoz. +Tesztelje, hogy a `.htaccess` működik-e .[#toc-test-if-htaccess-is-working] +--------------------------------------------------------------------------- +A legegyszerűbb módszer annak tesztelésére, hogy az Apache használja vagy figyelmen kívül hagyja-e a `.htaccess` fájlt, ha szándékosan megtörik. Tegye a `Test` sort a fájl elejére, és most, ha frissíti az oldalt a böngészőben, egy *Belső szerver hiba* üzenetet kell látnia. + +Ha ezt a hibát látod, az tulajdonképpen jó! Ez azt jelenti, hogy az Apache elemzi a `.htaccess` fájlt, és találkozik az általunk oda beírt hibával. Távolítsa el a `Test` sort. + +Ha nem lát *Belső szerverhibát*, akkor az Apache beállítása figyelmen kívül hagyja a `.htaccess` fájlt. Általában az Apache a hiányzó `AllowOverride All` konfigurációs utasítás miatt hagyja figyelmen kívül. + +Ha saját maga hosztolja, akkor ezt elég könnyű kijavítani. Nyissa meg a `httpd.conf` vagy a `apache.conf` címet egy szövegszerkesztő programban, keresse meg a vonatkozó `<Directory>` szakaszt, és adjuk hozzá/változtassuk meg az irányelvet: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Ha a webhelyét máshol tárolja, ellenőrizze a vezérlőpultját, hogy engedélyezheti-e ott a `.htaccess` címet. Ha nem, forduljon a tárhelyszolgáltatójához, hogy tegye ezt meg Ön helyett. + + +Tesztelje, hogy a `mod_rewrite` engedélyezve van-e .[#toc-test-if-mod-rewrite-is-enabled] +----------------------------------------------------------------------------------------- +Ha meggyőződött arról, hogy a [`.htaccess` működik |#Test if .htaccess is working], ellenőrizheti, hogy a mod_rewrite kiterjesztés engedélyezve van-e. Tegye a `RewriteEngine On` sort a `.htaccess` fájl elejére, és frissítse az oldalt a böngészőben. +Ha *Belső szerverhiba* jelenik meg, az azt jelenti, hogy a mod_rewrite nincs engedélyezve. Többféleképpen is engedélyezheted. Lásd a Stack Overflow-t, ahol különböző beállításoknál ez különböző módon történhet. + + A hivatkozások a `https:` nélkül generálódnak. .[#toc-links-are-generated-without-https] ---------------------------------------------------------------------------------------- A Nette ugyanolyan protokollal generálja a linkeket, mint amilyet az aktuális oldal használ. Tehát a `https://foo` kezdetű linkeket generálja, és fordítva. diff --git a/nette/it/@home.texy b/nette/it/@home.texy index dc9a3fb24c..73dfd0447c 100644 --- a/nette/it/@home.texy +++ b/nette/it/@home.texy @@ -6,13 +6,16 @@ Documentazione Nette <div> -Generale --------- -- [Perché usare Nette? |www:10-reasons-why-nette] -- <b>[Create la vostra prima applicazione! |quickstart:]<b> +Introduzione +------------ +- [Perché utilizzare Nette? |www:10-reasons-why-nette] +- [Installazione |Installation] +- [Create la vostra prima applicazione! |quickstart:] -- [Pacchetti e installazione |www:packages] +Generale +-------- +- [Elenco dei pacchetti |www:packages] - [Manutenzione e PHP |www:maintenance] - [Note di rilascio |https://nette.org/releases] - [Guida all'aggiornamento |migrations:en] @@ -20,6 +23,7 @@ Generale - [Chi crea Nette |https://nette.org/contributors] - [Storia di Nette |www:history] - [Partecipa |contributing:] +- [Sviluppo degli sponsor |https://nette.org/en/donate] - [Riferimento API |https://api.nette.org/] </div> diff --git a/nette/it/installation.texy b/nette/it/installation.texy new file mode 100644 index 0000000000..822ccdd6ad --- /dev/null +++ b/nette/it/installation.texy @@ -0,0 +1,69 @@ +Installazione di Nette +********************** + +.[perex] +Nette è una famiglia di librerie avanzate per PHP che possono essere utilizzate in modo completamente autonomo. Nette è anche un framework full-stack. Questa pagina mostra come installare ogni libreria o l'intero framework. + + +.[note] +Siete alle prime armi con Nette? Vi consigliamo di consultare anche [il tutorial Creare la prima applicazione |quickstart:]. + + +Come installare i pacchetti .[#toc-how-to-install-packages] +----------------------------------------------------------- + +I singoli [pacchetti |www:packages] della famiglia Nette vengono installati con lo strumento [Composer |best-practices:composer]. Se non lo conoscete, dovreste assolutamente iniziare a usarlo. È uno strumento molto utile. + +Ad esempio, avete bisogno di attraversare il file system nel vostro codice? Il [Finder |utils:finder], incluso nel pacchetto `nette/utils` (vedi colonna a destra), è ottimo per questo. È possibile installarlo dalla riga di comando: + +```shell +composer require nette/utils +``` + +Questo installerà anche tutti gli altri pacchetti. + +Un modo alternativo è quello di aggiungere tutti i pacchetti in una volta sola, installando `nette/nette`: + +```shell +composer require nette/nette +``` + + +Come creare un nuovo progetto .[#toc-how-to-create-a-new-project] +----------------------------------------------------------------- + +Avete intenzione di creare un nuovo progetto su Nette? Il modo più semplice per iniziare è scaricare lo scheletro dell'applicazione web di base, chiamato [Progetto Web |https://github.com/nette/web-project]. + +Anche in questo caso, Composer vi aiuterà a configurare il progetto. Individuate la directory principale del vostro server web (ad esempio, `/var/www` o `C:\InetPub`). Eseguite il seguente comando nel prompt dei comandi, sostituendo `my-project` con il nome della directory da creare: + +```shell +composer create-project nette/web-project my-project +``` + +(Se non si vuole usare Composer, [scaricare l'archivio |https://github.com/nette/web-project/archive/preloaded.zip], decomprimerlo e copiarlo nella directory principale del server web). + +Se si sta sviluppando su macOS o Linux (o qualsiasi altro sistema basato su Unix), è comunque necessario [impostare i permessi di scrittura |nette:troubleshooting#setting-directory-permissions]. + +Il bello di Nette è che non è necessario configurare o impostare nulla di complicato. A questo punto, la pagina iniziale del progetto Web dovrebbe funzionare. Potete verificarlo aprendo il browser al seguente URL: + +``` +http://localhost/my-project/www/ +``` + +e dovreste vedere la pagina di benvenuto di Nette Framework: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Evviva, lo scheletro funziona! Eliminate il modello di benvenuto e potrete iniziare a scrivere una nuova applicazione. + +.[note] +In caso di problemi, [provate a seguire questi suggerimenti |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Strumenti .[#toc-tools] +----------------------- + +Vi consigliamo di utilizzare un [IDE di qualità e tutti i plugin necessari |best-practices:editors-and-tools], che vi renderanno estremamente efficienti. + + +{{leftbar: www:@menu-common}} diff --git a/nette/it/troubleshooting.texy b/nette/it/troubleshooting.texy index d9832fb9ff..fafb558e90 100644 --- a/nette/it/troubleshooting.texy +++ b/nette/it/troubleshooting.texy @@ -21,6 +21,11 @@ Se la frase `Tracy is unable to log error` non compare (più) nel messaggio di e Una delle ragioni più comuni è una cache non aggiornata. Mentre Nette aggiorna automaticamente la cache in modalità di sviluppo, in modalità di produzione si concentra sulla massimizzazione delle prestazioni e la cancellazione della cache dopo ogni modifica del codice dipende da voi. Provate a cancellare `temp/cache`. +Errore 404, il routing non funziona .[#toc-error-404-routing-not-working] +------------------------------------------------------------------------- +Quando tutte le pagine (tranne la homepage) restituiscono un errore 404, sembra che ci sia un problema di configurazione del server per gli [URL più belli |#How to Configure a Server for Nice URLs?]. + + Errore `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ---------------------------------------------------------------------------------------------------------------------- Questo errore si verifica se si è aggiornato PHP alla versione 8.1 ma si sta utilizzando Nette, che non è compatibile con essa. La soluzione è aggiornare Nette a una versione più recente utilizzando `composer update`. Nette supporta PHP 8.1 dalla versione 3.0. Se si sta utilizzando una versione più vecchia (lo si può scoprire consultando `composer.json`), [aggiornare Nette |migrations:en] o rimanere con PHP 8.0. @@ -61,7 +66,7 @@ La soluzione **non** è quella di "sbarazzarsi" della cartella `www/` usando reg Come configurare un server per avere URL gradevoli? .[#toc-how-to-configure-a-server-for-nice-urls] --------------------------------------------------------------------------------------------------- -**Apache**: l'estensione mod_rewrite deve essere consentita e configurata in un file `.htaccess`. +**Apache**: è necessario abilitare e impostare le regole mod_rewrite nel file `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Per modificare la configurazione di Apache con i file .htaccess, è necessario abilitare la direttiva AllowOverride. Questo è il comportamento predefinito di Apache. +In caso di problemi, assicurarsi che: +- il file `.htaccess` si trovi nella directory document-root (cioè accanto al file `index.php` ) +- [Apache stia elaborando i file .htaccess |#Test if .htaccess is working] +- [mod_rewrite sia abilitato |#Test if mod_rewrite is enabled] + +Se si sta impostando l'applicazione in una sottocartella, potrebbe essere necessario decommentare la riga dell'impostazione `RewriteBase` e impostarla sulla cartella corretta. **nginx**: la direttiva `try_files` deve essere usata nella configurazione del server: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args È IMPORTANTE! } ``` Il blocco `location` deve essere definito esattamente una volta per ogni percorso del filesystem nel blocco `server`. Se nella configurazione è già presente un blocco `location /`, aggiungere la direttiva `try_files` al blocco esistente. +Verificare se `.htaccess` funziona .[#toc-test-if-htaccess-is-working] +---------------------------------------------------------------------- +Il modo più semplice per verificare se Apache utilizza o ignora il file `.htaccess` è quello di interromperlo intenzionalmente. Inserite la riga `Test` all'inizio del file e ora, se aggiornate la pagina nel browser, dovreste vedere un *Internal Server Error*. + +Se vedete questo errore, in realtà è un bene! Significa che Apache sta analizzando il file `.htaccess` e incontra l'errore che abbiamo inserito. Rimuovere la riga `Test`. + +Se non viene visualizzato un *Internal Server Error*, la configurazione di Apache ignora il file `.htaccess`. In genere, Apache lo ignora a causa della direttiva di configurazione mancante `AllowOverride All`. + +Se lo ospitate voi stessi, è abbastanza facile da risolvere. Aprire il file `httpd.conf` o `apache.conf` in un editor di testo, individuare la sezione pertinente `<Directory>` e aggiungere/modificare la direttiva: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Se il vostro sito è ospitato altrove, controllate il vostro pannello di controllo per vedere se potete abilitare `.htaccess` lì. In caso contrario, contattate il vostro provider di hosting affinché lo faccia per voi. + + +Verificare se `mod_rewrite` è abilitato .[#toc-test-if-mod-rewrite-is-enabled] +------------------------------------------------------------------------------ +Se si è verificato che [`.htaccess` funziona |#Test if .htaccess is working], si può verificare che l'estensione mod_rewrite sia abilitata. Inserire la riga `RewriteEngine On` all'inizio del file `.htaccess` e aggiornare la pagina nel browser. +Se viene visualizzato un *Internal Server Error*, significa che mod_rewrite non è abilitato. Ci sono diversi modi per abilitarlo. Si veda Stack Overflow per i vari modi in cui questo può essere fatto su diverse configurazioni. + + I collegamenti sono generati senza `https:` .[#toc-links-are-generated-without-https] ------------------------------------------------------------------------------------- Nette genera i link con lo stesso protocollo utilizzato dalla pagina corrente. Quindi, nella pagina `https://foo` e viceversa. diff --git a/nette/pl/@home.texy b/nette/pl/@home.texy index 9e7b4ed049..9868af29e3 100644 --- a/nette/pl/@home.texy +++ b/nette/pl/@home.texy @@ -6,13 +6,16 @@ Dokumentacja Nette <div> -Ogólne ------- -- [Dlaczego warto korzystać z Nette? |www:10-reasons-why-nette] -- <b>[Piszemy nasz pierwszy wniosek! |quickstart:]</b> +Wstęp +----- +- [Dlaczego warto używać Nette? |www:10-reasons-why-nette] +- [Instalacja |Installation] +- [Stwórz swoją pierwszą aplikację! |quickstart:] -- [Pakiety i instalacja |www:packages] +Ogólne +------ +- [Lista pakietów |www:packages] - [Konserwacja i wersja PHP |www:maintenance] - [Uwagi do wydania |https://nette.org/releases] - [Aktualizacja do nowszych wersji |migrations:en] @@ -20,6 +23,7 @@ Ogólne - [Kto tworzy Nette |https://nette.org/contributors] - [Historia Nette |www:history] - [Zaangażuj się |contributing:] +- [Rozwój sponsora |https://nette.org/en/donate] - [Odniesienia do API |https://api.nette.org/] </div> diff --git a/nette/pl/installation.texy b/nette/pl/installation.texy new file mode 100644 index 0000000000..baa288715d --- /dev/null +++ b/nette/pl/installation.texy @@ -0,0 +1,69 @@ +Instalacja Nette +**************** + +.[perex] +Nette to rodzina zaawansowanych bibliotek dla PHP, których możesz używać całkowicie samodzielnie. Nette jest również frameworkiem typu full-stack. Ta strona pokazuje jak zainstalować każdą z bibliotek lub cały framework. + + +.[note] +Jesteś początkującym użytkownikiem Nette? Polecamy również zapoznać się z [tutorialem Create Your First Application |quickstart:]. + + +Jak zainstalować pakiety .[#toc-how-to-install-packages] +-------------------------------------------------------- + +Poszczególne [pakiety |www:packages] z rodziny Nette są instalowane za pomocą narzędzia [Composer |best-practices:composer]. Jeśli nie jesteś z nim zaznajomiony, powinieneś zdecydowanie zacząć od niego. Jest to bardzo przydatne narzędzie. + +Na przykład, czy potrzebujesz przemierzać system plików w swoim kodzie? [Finder |utils:finder], który jest zawarty w pakiecie `nette/utils` (patrz prawa kolumna), jest do tego świetny. Możesz go zainstalować z linii poleceń: + +```shell +composer require nette/utils +``` + +Spowoduje to również zainstalowanie wszystkich innych pakietów. + +Alternatywnym sposobem jest dodanie wszystkich pakietów na raz poprzez instalację `nette/nette`: + +```shell +composer require nette/nette +``` + + +Jak stworzyć nowy projekt .[#toc-how-to-create-a-new-project] +------------------------------------------------------------- + +Zamierzasz zbudować nowy projekt na Nette? Najłatwiej jest zacząć od pobrania podstawowego szkieletu aplikacji internetowej o nazwie [Web Project |https://github.com/nette/web-project]. + +Również w tym przypadku Composer pomoże ci skonfigurować twój projekt. Znajdź katalog główny swojego serwera WWW (np. `/var/www` lub `C:\InetPub`). Uruchom następujące polecenie w wierszu poleceń, ale zastąp `my-project` nazwą katalogu, który ma zostać utworzony: + +```shell +composer create-project nette/web-project my-project +``` + +(Jeśli nie chcesz używać Composera, [pobierz archiwum |https://github.com/nette/web-project/archive/preloaded.zip], rozpakuj je i skopiuj do katalogu głównego serwera WWW). + +Jeśli tworzysz na macOS lub Linuxie (lub innym systemie opartym na Uniksie), nadal będziesz musiał [ustawić uprawnienia do zapisu |nette:troubleshooting#setting-directory-permissions]. + +Wspaniałą rzeczą w Nette jest to, że nie musisz konfigurować ani ustawiać niczego skomplikowanego. W tym momencie strona główna Web Project powinna już działać. Możesz to sprawdzić otwierając przeglądarkę pod następującym adresem URL: + +``` +http://localhost/my-project/www/ +``` + +i powinieneś zobaczyć stronę powitalną Nette Framework: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Hurra, szkielet działa! Usuń szablon powitalny i możesz zacząć pisać nową, wspaniałą aplikację. + +.[note] +Jeśli jest jakiś problem, [spróbuj tych kilku wskazówek |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Narzędzia .[#toc-tools] +----------------------- + +Zalecamy używanie wysokiej [jakości IDE i wszystkich niezbędnych wtyczek |best-practices:editors-and-tools], sprawi to, że będziesz niezwykle wydajny. + + +{{leftbar: www:@menu-common}} diff --git a/nette/pl/troubleshooting.texy b/nette/pl/troubleshooting.texy index 5d772a30dd..26e4e0d4b1 100644 --- a/nette/pl/troubleshooting.texy +++ b/nette/pl/troubleshooting.texy @@ -21,6 +21,11 @@ Jeśli w komunikacie o błędzie nie ma (już) frazy `Tracy is unable to log err Jedną z najczęstszych przyczyn jest nieaktualny cache. Podczas gdy Nette w trybie deweloperskim sprytnie automatycznie aktualizuje pamięć podręczną, w trybie produkcyjnym skupia się na maksymalizacji wydajności, a usunięcie pamięci podręcznej, po każdej modyfikacji kodu, zależy od Ciebie. Spróbuj usunąć `temp/cache`. +Błąd 404, routing nie działa .[#toc-error-404-routing-not-working] +------------------------------------------------------------------ +Kiedy wszystkie strony (oprócz strony głównej) zwracają błąd 404, wygląda to na problem z konfiguracją serwera dla [ładnych adresów URL |#How to Configure a Server for Nice URLs?]. + + Błąd `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] -------------------------------------------------------------------------------------------------------------------- Ten błąd pojawia się, jeśli zaktualizowałeś PHP do wersji 8.1, ale używasz Nette, która nie jest z nim kompatybilna. Rozwiązaniem jest więc aktualizacja Nette do nowszej wersji za pomocą `composer update`. Nette wspiera PHP 8.1 od wersji 3.0. Jeśli używasz starszej wersji (możesz to sprawdzić zaglądając na stronę `composer.json`), [zaktualizuj Nette |migrations:en] lub pozostań przy PHP 8.0. @@ -61,7 +66,7 @@ Rozwiązaniem **nie jest** "pozbycie się" folderu `www/` za pomocą reguł w pl Jak założyć serwer dla ładnych adresów URL? .[#toc-how-to-configure-a-server-for-nice-urls] ------------------------------------------------------------------------------------------- -**Apache**: należy włączyć i ustawić rozszerzenie `mod_rewrite` w pliku `.htaccess`: +**Apache**: należy włączyć i ustawić reguły mod_rewrite w pliku `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Aby wpłynąć na zachowanie plików Apache `.htaccess` należy mieć włączoną dyrektywę `AllowOverride`. Jest to domyślne zachowanie w Apache. +Jeśli napotkasz problemy, upewnij się, że: +- plik `.htaccess` znajduje się w katalogu document-root (tzn. obok pliku `index.php` ) +- [Apache przetwarza pliki .htaccess |#Test if .htaccess is working] +- [mod_rewrite jest włączony |#Test if mod_rewrite is enabled] + +Jeśli ustawiasz aplikację w podfolderze, być może będziesz musiał odkomentować linię dla ustawienia `RewriteBase` i ustawić ją na właściwy folder. **nginx**: należy ustawić przekierowanie za pomocą dyrektywy `try_files` wewnątrz bloku `location /` w konfiguracji serwera. ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args je důležité + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args JEST WAŻNE! } ``` Blok `location` może pojawić się tylko raz w bloku `server` dla każdej ścieżki systemu plików. Jeśli masz już w swojej konfiguracji `location /`, dodaj do niej dyrektywę `try_files`. +Sprawdź, czy `.htaccess` działa. .[#toc-test-if-htaccess-is-working] +-------------------------------------------------------------------- +Najprostszym sposobem na sprawdzenie czy Apache używa lub ignoruje twój plik `.htaccess` jest jego celowe złamanie. Umieść linię `Test` na początku pliku i teraz, jeśli odświeżysz stronę w przeglądarce, powinieneś zobaczyć *Internal Server Error*. + +Jeśli widzisz ten błąd, to właściwie dobrze! Oznacza to, że Apache przetwarza plik `.htaccess` i napotyka błąd, który tam umieściliśmy. Usuń linię `Test`. + +Jeśli nie widzisz *Internal Server Error*, oznacza to, że twoja konfiguracja Apache'a ignoruje plik `.htaccess`. Ogólnie rzecz biorąc, Apache ignoruje go z powodu brakującej dyrektywy konfiguracyjnej `AllowOverride All`. + +Jeśli sam hostujesz, łatwo to naprawić. Otwórz `httpd.conf` lub `apache.conf` w edytorze tekstu, znajdź odpowiednią sekcję `<Directory>` i dodaj/zmień dyrektywę: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Jeśli twoja witryna jest hostowana w innym miejscu, sprawdź w swoim panelu sterowania, czy możesz włączyć tam `.htaccess`. Jeśli nie, skontaktuj się z dostawcą usług hostingowych, aby zrobić to dla ciebie. + + +Sprawdź, czy `mod_rewrite` jest włączone. .[#toc-test-if-mod-rewrite-is-enabled] +-------------------------------------------------------------------------------- +Jeśli sprawdziłeś, że [`.htaccess` działa |#Test if .htaccess is working], możesz sprawdzić, czy rozszerzenie mod_rewrite jest włączone. Umieść linię `RewriteEngine On` na początku pliku `.htaccess` i odśwież stronę w przeglądarce. +Jeśli zobaczysz *Internal Server Error*, oznacza to, że mod_rewrite nie jest włączony. Istnieje wiele sposobów, aby go włączyć. Zobacz Stack Overflow, gdzie można to zrobić na różne sposoby w różnych konfiguracjach. + + Linki są generowane bez `https:` .[#toc-links-are-generated-without-https] -------------------------------------------------------------------------- Nette generuje linki z tym samym protokołem co sama strona. W ten sposób na stronie `https://foo` i odwrotnie. diff --git a/nette/pt/@home.texy b/nette/pt/@home.texy index d9a0e1f10d..6b35915287 100644 --- a/nette/pt/@home.texy +++ b/nette/pt/@home.texy @@ -6,13 +6,16 @@ Documentação Nette <div> -Geral ------ +Introdução +---------- - [Por que usar Nette? |www:10-reasons-why-nette] -- <b>[Crie sua primeira aplicação! |quickstart:]<b> +- [Instalação |Installation] +- [Crie sua primeira aplicação! |quickstart:] -- [Embalagens e instalação |www:packages] +Geral +----- +- [Lista de Pacotes |www:packages] - [Manutenção e PHP |www:maintenance] - [Notas de Lançamento |https://nette.org/releases] - [Guia de Atualização |migrations:en] @@ -20,6 +23,7 @@ Geral - [Quem Cria a Nette |https://nette.org/contributors] - [História da Nette |www:history] - [Envolva-se |contributing:] +- [Desenvolvimento do patrocinador |https://nette.org/en/donate] - [Referência API |https://api.nette.org/] </div> diff --git a/nette/pt/installation.texy b/nette/pt/installation.texy new file mode 100644 index 0000000000..e2420bd8ef --- /dev/null +++ b/nette/pt/installation.texy @@ -0,0 +1,69 @@ +Instalando Nette +**************** + +.[perex] +Nette é uma família de bibliotecas avançadas para PHP que você pode usar completamente autônoma. Nette também é uma estrutura completa. Esta página lhe mostra como instalar cada biblioteca ou toda a estrutura. + + +.[note] +Você é novo na Nette? Também recomendamos que você consulte [o tutorial Create Your First Application (Crie sua primeira aplicação |quickstart:]). + + +Como instalar os pacotes .[#toc-how-to-install-packages] +-------------------------------------------------------- + +Os [pacotes |www:packages] individuais da família Nette são instalados utilizando a ferramenta [Composer |best-practices:composer]. Se você não está familiarizado com ela, você deve definitivamente começar com ela. É uma ferramenta muito útil. + +Por exemplo, você precisa atravessar o sistema de arquivos em seu código? O [Finder |utils:finder], que está incluído no pacote `nette/utils` (ver coluna da direita), é ótimo para isso. Você pode instalá-lo a partir da linha de comando: + +```shell +composer require nette/utils +``` + +Isto também instalará todos os outros pacotes. + +Uma maneira alternativa é adicionar todos os pacotes de uma só vez, instalando `nette/nette`: + +```shell +composer require nette/nette +``` + + +Como criar um novo projeto .[#toc-how-to-create-a-new-project] +-------------------------------------------------------------- + +Você vai construir um novo projeto na Nette? A maneira mais fácil de começar é baixar o esqueleto básico da aplicação web, chamado [Projeto Web |https://github.com/nette/web-project]. + +Mais uma vez, o Composer o ajudará a montar seu projeto. Encontre o diretório raiz do seu servidor web (por exemplo, `/var/www` ou `C:\InetPub`). Execute o seguinte comando no prompt de comando, mas substitua `my-project` pelo nome do diretório a ser criado: + +```shell +composer create-project nette/web-project my-project +``` + +(Se você não quiser usar o Composer, [baixe o arquivo |https://github.com/nette/web-project/archive/preloaded.zip], descompacte-o e copie-o para o diretório raiz do servidor web). + +Se você estiver desenvolvendo em macOS ou Linux (ou qualquer outro sistema baseado em Unix), ainda precisará [definir permissões de escrita |nette:troubleshooting#setting-directory-permissions]. + +O ótimo da Nette é que você não tem que configurar ou configurar nada complicado. Portanto, até este ponto, a página inicial do Projeto Web já deve estar funcionando. Você pode testar isso abrindo seu navegador na seguinte URL: + +``` +http://localhost/my-project/www/ +``` + +e você deve ver a página de boas-vindas da Nette Framework: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Viva, o esqueleto funciona! Elimine o modelo de boas-vindas e você pode começar a escrever uma nova e excelente aplicação. + +.[note] +Se houver algum problema, [tente estas poucas dicas |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Ferramentas .[#toc-tools] +------------------------- + +Recomendamos utilizar uma [IDE de qualidade e todos os plugins necessários |best-practices:editors-and-tools], isso o tornará extremamente eficiente. + + +{{leftbar: www:@menu-common}} diff --git a/nette/pt/troubleshooting.texy b/nette/pt/troubleshooting.texy index 5c606058f6..0aed97b1cf 100644 --- a/nette/pt/troubleshooting.texy +++ b/nette/pt/troubleshooting.texy @@ -21,6 +21,11 @@ Se a frase `Tracy is unable to log error` não estiver mais na mensagem de erro Uma das razões mais comuns é um cache desatualizado. Enquanto a Nette atualiza o cache de forma inteligente e automática no modo de desenvolvimento, no modo de produção ele se concentra em maximizar o desempenho, e limpar o cache após cada modificação de código fica a seu critério. Tente excluir `temp/cache`. +Erro 404, roteamento não funciona .[#toc-error-404-routing-not-working] +----------------------------------------------------------------------- +Quando todas as páginas (exceto a página inicial) retornam um erro 404, parece um problema de configuração do servidor para [URLs bonitas |#How to Configure a Server for Nice URLs?]. + + Erro `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] -------------------------------------------------------------------------------------------------------------------- Este erro ocorre se você tiver atualizado o PHP para a versão 8.1, mas estiver usando Nette, que não é compatível com ele. Portanto, a solução é atualizar o Nette para uma versão mais recente usando `composer update`. Nette suporta o PHP 8.1 desde a versão 3.0. Se você estiver usando uma versão mais antiga (você pode descobrir procurando em `composer.json`), [atualize Nette |migrations:en] ou fique com o PHP 8.0. @@ -61,7 +66,7 @@ A solução ** não é*** para "se livrar" da pasta `www/` usando regras no arqu Como configurar um servidor para URLs legais? .[#toc-how-to-configure-a-server-for-nice-urls] --------------------------------------------------------------------------------------------- -**Apache**: extensão mod_rewrite deve ser permitida e configurada em um arquivo `.htaccess`. +**Apache**: você precisa habilitar e definir as regras mod_rewrite no arquivo `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Para alterar a configuração do Apache com arquivos .htaccess, a diretiva AllowOverride tem que ser habilitada. Este é o comportamento padrão para o Apache. +Se você tiver problemas, certifique-se disso: +- o arquivo `.htaccess` está localizado no diretório document-root (ou seja, ao lado do arquivo `index.php` ) +- [Apache está processando os arquivos .htaccess |#Test if .htaccess is working] +- [mod_rewrite está habilitado |#Test if mod_rewrite is enabled] + +Se você estiver configurando a aplicação em uma subpasta, talvez tenha que descomentar a linha para a configuração `RewriteBase` e configurá-la para a pasta correta. **nginx**: a diretiva `try_files` deve ser usada na configuração do servidor: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args$args É IMPORTANTE! } ``` O bloco `location` deve ser definido exatamente uma vez para cada caminho do sistema de arquivos no bloco `server`. Se você já tem um bloco `location /` em sua configuração, adicione a diretiva `try_files` ao bloco existente. +Teste se `.htaccess` está funcionando .[#toc-test-if-htaccess-is-working] +------------------------------------------------------------------------- +A maneira mais simples de testar se o Apache usa ou ignora seu arquivo `.htaccess`, é quebrá-lo intencionalmente. Coloque a linha `Test` no início do arquivo e agora, se você atualizar a página em seu navegador, você deve ver um erro *Internal Server Error*. + +Se você vir este erro, isso é realmente bom! Isto significa que o Apache está analisando o arquivo `.htaccess`, e encontra o erro que colocamos lá dentro. Remova a linha `Test`. + +Se você não vir um * Erro do Servidor Interno*, sua configuração do Apache ignora o arquivo `.htaccess`. Geralmente, o Apache o ignora por causa da diretiva de configuração em falta `AllowOverride All`. + +Se você mesmo o está hospedando, é fácil de consertar. Abra seu `httpd.conf` ou `apache.conf` em um editor de texto, localize o `<Directory>` e acrescentar/alterar a diretriz: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Se seu site estiver hospedado em outro lugar, verifique seu painel de controle para ver se você pode ativar `.htaccess` lá. Caso contrário, contate seu provedor de hospedagem para fazer isso por você. + + +Teste se `mod_rewrite` estiver habilitado .[#toc-test-if-mod-rewrite-is-enabled] +-------------------------------------------------------------------------------- +Se você tiver verificado que [`.htaccess` funciona |#Test if .htaccess is working], você pode verificar se a extensão mod_rewrite está habilitada. Coloque a linha `RewriteEngine On` no início do arquivo `.htaccess` e atualize a página em seu navegador. +Se você vir um erro *Internal Server Error*, significa que o mod_rewrite não está habilitado. Há várias maneiras de habilitá-lo. Veja Stack Overflow para várias maneiras de fazer isso em diferentes configurações. + + Os links são gerados sem `https:` .[#toc-links-are-generated-without-https] --------------------------------------------------------------------------- Nette gera links com o mesmo protocolo que a página atual está usando. Assim, na página `https://foo` e vice-versa. diff --git a/nette/ro/@home.texy b/nette/ro/@home.texy index 438386c504..93cb581542 100644 --- a/nette/ro/@home.texy +++ b/nette/ro/@home.texy @@ -6,13 +6,16 @@ Nette Documentație <div> -General -------- -- [De ce să folosiți Nette? |www:en:10-reasons-why-nette] -- <b>[Creați prima dumneavoastră aplicație! |quickstart:]<b> +Introducere +----------- +- [De ce să folosiți Nette? |www:10-reasons-why-nette] +- [Instalare |Installation] +- [Creați prima dumneavoastră aplicație! |quickstart:] -- [Pachete și instalare |www:packages] +General +------- +- [Lista de pachete |www:packages] - [Întreținere și PHP |www:maintenance] - [Note de lansare |https://nette.org/releases] - [Ghid de actualizare |migrations:en] @@ -20,6 +23,7 @@ General - [Cine creează Nette |https://nette.org/contributors] - [Istoria Nette |www:history] - [Implică-te |contributing:] +- [Dezvoltarea sponsorilor |https://nette.org/en/donate] - [Referință API |https://api.nette.org/] </div> diff --git a/nette/ro/installation.texy b/nette/ro/installation.texy new file mode 100644 index 0000000000..50809e5cb9 --- /dev/null +++ b/nette/ro/installation.texy @@ -0,0 +1,69 @@ +Instalarea Nette +**************** + +.[perex] +Nette este o familie de biblioteci avansate pentru PHP pe care le puteți utiliza în mod complet independent. Nette este, de asemenea, un framework complet. Această pagină vă arată cum să instalați fiecare bibliotecă sau întregul framework. + + +.[note] +Sunteți nou în Nette? Vă recomandăm, de asemenea, să consultați [tutorialul Creați prima aplicație |quickstart:]. + + +Cum se instalează pachetele .[#toc-how-to-install-packages] +----------------------------------------------------------- + + [Pachetele |www:packages] individuale din familia Nette se instalează cu ajutorul instrumentului [Composer |best-practices:composer]. Dacă nu sunteți familiarizat cu acesta, ar trebui neapărat să începeți cu el. Este un instrument foarte util. + +De exemplu, aveți nevoie să traversați sistemul de fișiere în codul dumneavoastră? [Finder-ul |utils:finder], care este inclus în pachetul `nette/utils` (a se vedea coloana din dreapta), este excelent pentru acest lucru. Îl puteți instala din linia de comandă: + +```shell +composer require nette/utils +``` + +Acest lucru va instala, de asemenea, toate celelalte pachete. + +O modalitate alternativă este de a adăuga toate pachetele deodată prin instalarea `nette/nette`: + +```shell +composer require nette/nette +``` + + +Cum se creează un proiect nou .[#toc-how-to-create-a-new-project] +----------------------------------------------------------------- + +Aveți de gând să construiți un nou proiect pe Nette? Cel mai simplu mod de a începe este să descărcați scheletul de bază al aplicației web, numit [Web Project |https://github.com/nette/web-project]. + +Din nou, Composer vă va ajuta să vă configurați proiectul. Găsiți directorul rădăcină al serverului dvs. web (de exemplu, `/var/www` sau `C:\InetPub`). Rulați următoarea comandă la promptul de comandă, dar înlocuiți `my-project` cu numele directorului care urmează să fie creat: + +```shell +composer create-project nette/web-project my-project +``` + +(Dacă nu doriți să utilizați Composer, [descărcați arhiva |https://github.com/nette/web-project/archive/preloaded.zip], descompuneți-o și copiați-o în directorul rădăcină al serverului web). + +Dacă dezvoltați pe macOS sau Linux (sau pe orice alt sistem bazat pe Unix), va trebui totuși să [setați permisiunile de scriere |nette:troubleshooting#setting-directory-permissions]. + +Lucrul grozav la Nette este că nu trebuie să configurați sau să stabiliți nimic complicat. Așadar, până în acest moment, pagina de pornire a proiectului web ar trebui să funcționeze. Puteți testa acest lucru deschizând browserul dvs. la următoarea adresă URL: + +``` +http://localhost/my-project/www/ +``` + +și ar trebui să vedeți pagina de bun venit a Nette Framework: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Ura, scheletul funcționează! Ștergeți șablonul de bun venit și puteți începe să scrieți o nouă aplicație grozavă. + +.[note] +Dacă există o problemă, [încercați aceste câteva sfaturi |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Instrumente .[#toc-tools] +------------------------- + +Vă recomandăm să folosiți un [IDE de calitate și toate plugin-urile necesare |best-practices:editors-and-tools], vă va face extrem de eficient. + + +{{leftbar: www:@menu-common}} diff --git a/nette/ro/troubleshooting.texy b/nette/ro/troubleshooting.texy index 0d941fa940..8f4e5767e7 100644 --- a/nette/ro/troubleshooting.texy +++ b/nette/ro/troubleshooting.texy @@ -21,6 +21,11 @@ Dacă propoziția `Tracy is unable to log error` nu mai apare (mai) în mesajul Unul dintre cele mai frecvente motive este un cache învechit. În timp ce Nette actualizează în mod inteligent și automat memoria cache în modul de dezvoltare, în modul de producție se concentrează pe maximizarea performanței, iar ștergerea memoriei cache după fiecare modificare de cod depinde de dumneavoastră. Încercați să ștergeți `temp/cache`. +Eroare 404, rutarea nu funcționează .[#toc-error-404-routing-not-working] +------------------------------------------------------------------------- +Atunci când toate paginile (cu excepția paginii de start) returnează o eroare 404, se pare că este vorba de o problemă de configurare a serverului pentru [URL-urile frumoase |#How to Configure a Server for Nice URLs?]. + + Eroare `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ---------------------------------------------------------------------------------------------------------------------- Această eroare apare în cazul în care ați actualizat PHP la versiunea 8.1, dar utilizați Nette, care nu este compatibil cu aceasta. Așadar, soluția este să actualizați Nette la o versiune mai nouă folosind `composer update`. Nette suportă PHP 8.1 încă de la versiunea 3.0. Dacă folosiți o versiune mai veche (puteți afla căutând în `composer.json`), [actualizați Nette |migrations:en] sau rămâneți cu PHP 8.0. @@ -61,7 +66,7 @@ Soluția **nu este** de a "scăpa" de folderul `www/` folosind reguli în fișie Cum se configurează un server pentru URL-uri frumoase? .[#toc-how-to-configure-a-server-for-nice-urls] ------------------------------------------------------------------------------------------------------ -**Apache**: extensia mod_rewrite trebuie să fie permisă și configurată într-un fișier `.htaccess`. +**Apache**: trebuie să activați și să setați regulile mod_rewrite în fișierul `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Pentru a modifica configurația Apache cu ajutorul fișierelor .htaccess, trebuie să fie activată directiva AllowOverride. Acesta este comportamentul implicit pentru Apache. +Dacă întâmpinați probleme, asigurați-vă că: +- fișierul `.htaccess` se află în directorul rădăcină al documentului (adică lângă fișierul `index.php` ) +- [Apache procesează fișierele .htaccess |#Test if .htaccess is working] +- [mod_rewrite este activat |#Test if mod_rewrite is enabled] + +Dacă configurați aplicația într-un subfolder, s-ar putea să trebuiască să decomentați linia pentru setarea `RewriteBase` și să o setați în folderul corect. **nginx**: în configurația serverului trebuie utilizată directiva `try_files`: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args ESTE IMPORTANT! } ``` Blocul `location` trebuie definit exact o singură dată pentru fiecare cale de sistem de fișiere din blocul `server`. Dacă aveți deja un bloc `location /` în configurația dumneavoastră, adăugați directiva `try_files` în blocul existent. +Testați dacă `.htaccess` funcționează .[#toc-test-if-htaccess-is-working] +------------------------------------------------------------------------- +Cel mai simplu mod de a testa dacă Apache utilizează sau ignoră fișierul `.htaccess` este să îl întrerupeți intenționat. Puneți linia `Test` la începutul fișierului și acum, dacă reîmprospătați pagina în browser, ar trebui să vedeți o *Erroră internă a serverului*. + +Dacă vedeți această eroare, este de fapt un lucru bun! Aceasta înseamnă că Apache analizează fișierul `.htaccess` și întâlnește eroarea pe care am pus-o acolo. Eliminați linia `Test`. + +Dacă nu vedeți o *Internal Server Error*, înseamnă că configurația Apache ignoră fișierul `.htaccess`. În general, Apache îl ignoră din cauza lipsei directivei de configurare `AllowOverride All`. + +Dacă îl găzduiți singur, este destul de ușor de rezolvat. Deschideți fișierul `httpd.conf` sau `apache.conf` într-un editor de text, localizați fișierul corespunzător `<Directory>` și adăugați/modificați directiva: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Dacă site-ul dvs. este găzduit în altă parte, verificați panoul de control pentru a vedea dacă puteți activa `.htaccess` acolo. În caz contrar, contactați furnizorul de servicii de găzduire pentru a face acest lucru pentru dumneavoastră. + + +Testați dacă `mod_rewrite` este activat .[#toc-test-if-mod-rewrite-is-enabled] +------------------------------------------------------------------------------ +Dacă ați verificat dacă [`.htaccess` funcționează |#Test if .htaccess is working], puteți verifica dacă extensia mod_rewrite este activată. Puneți linia `RewriteEngine On` la începutul fișierului `.htaccess` și reîmprospătați pagina în browser. +Dacă vedeți o *Internal Server Error*, înseamnă că mod_rewrite nu este activat. Există mai multe modalități de a-l activa. Consultați Stack Overflow pentru diverse moduri în care se poate face acest lucru în diferite configurații. + + Legăturile sunt generate fără `https:` .[#toc-links-are-generated-without-https] -------------------------------------------------------------------------------- Nette generează linkuri cu același protocol pe care îl folosește pagina curentă. Astfel, pe pagina `https://foo` și invers. diff --git a/nette/ru/@home.texy b/nette/ru/@home.texy index 499ac36727..fbf9ee8c63 100644 --- a/nette/ru/@home.texy +++ b/nette/ru/@home.texy @@ -6,12 +6,16 @@ <div> -Предисловие ------------ -- <b>[Создайте свое первое приложение! |quickstart:]<b> +Введение +-------- +- [Почему стоит использовать Nette? |www:10-reasons-why-nette] +- [Установка |Installation] +- [Создайте свое первое приложение! |quickstart:] -- [Пакеты и установка |www:packages] +Общие +----- +- [Список пакетов |www:packages] - [Обслуживание и PHP |www:maintenance] - [Примечания к выпуску |https://nette.org/releases] - [Руководство по обновлению |migrations:en] @@ -19,6 +23,7 @@ - [Создатели Nette |https://nette.org/contributors] - [История Nette |www:history] - [Принять участие |contributing:] +- [Развитие спонсоров |https://nette.org/en/donate] - [Справочник по API |https://api.nette.org/] </div> diff --git a/nette/ru/installation.texy b/nette/ru/installation.texy new file mode 100644 index 0000000000..658e0a1b60 --- /dev/null +++ b/nette/ru/installation.texy @@ -0,0 +1,69 @@ +Установка Nette +*************** + +.[perex] +Nette - это семейство продвинутых библиотек для PHP, которые вы можете использовать совершенно автономно. Nette также является полнофункциональным фреймворком. На этой странице показано, как установить каждую библиотеку или весь фреймворк. + + +.[note] +Вы новичок в Nette? Мы также рекомендуем вам ознакомиться с [учебником "Создание первого приложения |quickstart:]". + + +Как устанавливать пакеты .[#toc-how-to-install-packages] +-------------------------------------------------------- + +Отдельные [пакеты |www:packages] семейства Nette устанавливаются с помощью инструмента [Composer |best-practices:composer]. Если вы не знакомы с ним, вам обязательно стоит начать с него. Это очень полезный инструмент. + +Например, вам нужно перемещаться по файловой системе в вашем коде? Для этого отлично подходит [Finder |utils:finder], который входит в пакет `nette/utils` (см. правую колонку). Вы можете установить его из командной строки: + +```shell +composer require nette/utils +``` + +Это также приведет к установке всех остальных пакетов. + +Альтернативный способ - добавить все пакеты сразу, установив `nette/nette`: + +```shell +composer require nette/nette +``` + + +Как создать новый проект .[#toc-how-to-create-a-new-project] +------------------------------------------------------------ + +Вы собираетесь создать новый проект на Nette? Самый простой способ начать - загрузить базовый скелет веб-приложения под названием [Web Project |https://github.com/nette/web-project]. + +Опять же, Composer поможет вам настроить ваш проект. Найдите корневой каталог вашего веб-сервера (например, `/var/www` или `C:\InetPub`). Выполните следующую команду в командной строке, но замените `my-project` на имя создаваемого каталога: + +```shell +composer create-project nette/web-project my-project +``` + +(Если вы не хотите использовать Composer, [скачайте архив |https://github.com/nette/web-project/archive/preloaded.zip], распакуйте его и скопируйте в корневой каталог веб-сервера). + +Если вы разрабатываете на macOS или Linux (или любой другой системе на базе Unix), вам все равно придется [установить права на запись |nette:troubleshooting#setting-directory-permissions]. + +Самое замечательное в Nette то, что вам не нужно настраивать или устанавливать что-то сложное. Итак, к этому моменту домашняя страница веб-проекта должна работать. Вы можете проверить это, открыв браузер по следующему URL: + +``` +http://localhost/my-project/www/ +``` + +и вы увидите приветственную страницу Nette Framework: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Ура, скелет работает! Удалите шаблон приветствия и можете приступать к написанию нового замечательного приложения. + +.[note] +Если возникли проблемы, [попробуйте воспользоваться этими советами |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Инструменты .[#toc-tools] +------------------------- + +Мы рекомендуем использовать [качественную IDE и все необходимые плагины |best-practices:editors-and-tools], это сделает вашу работу чрезвычайно эффективной. + + +{{leftbar: www:@menu-common}} diff --git a/nette/ru/troubleshooting.texy b/nette/ru/troubleshooting.texy index 8f8efd9ddb..6a0f563c25 100644 --- a/nette/ru/troubleshooting.texy +++ b/nette/ru/troubleshooting.texy @@ -21,6 +21,11 @@ Nette не работает, отображается белая страниц Одна из наиболее распространенных причин - устаревший кэш. В то время как Nette умно автоматически обновляет кэш в режиме разработки, в производственном режиме он сосредоточен на максимизации производительности, и очистка кэша после каждой модификации кода зависит от вас. Попробуйте удалить `temp/cache`. +Ошибка 404, маршрутизация не работает .[#toc-error-404-routing-not-working] +--------------------------------------------------------------------------- +Когда все страницы (кроме домашней) выдают ошибку 404, это похоже на проблему конфигурации сервера для [красивых URL |#How to Configure a Server for Nice URLs?]. + + Ошибка `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ---------------------------------------------------------------------------------------------------------------------- Эта ошибка возникает, если вы обновили PHP до версии 8.1, но используете Nette, который не совместим с ней. Поэтому решением является обновление Nette до более новой версии с помощью `composer update`. Nette поддерживает PHP 8.1 с версии 3.0. Если вы используете более старую версию (вы можете узнать это, посмотрев в `composer.json`), [обновите Nette |migrations:en] или оставайтесь с PHP 8.0. @@ -61,7 +66,7 @@ setsebool -P httpd_can_network_connect_db on Как настроить сервер для красивых URL? .[#toc-how-to-configure-a-server-for-nice-urls] -------------------------------------------------------------------------------------- -**Apache**: Расширение mod_rewrite должно быть разрешено и настроено в файле `.htaccess`. +**Apache**: необходимо включить и установить правила mod_rewrite в файле `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Чтобы изменить конфигурацию Apache с помощью файлов .htaccess, необходимо включить директиву AllowOverride. Это поведение по умолчанию для Apache. +Если у вас возникли проблемы, убедитесь, что: +- файл `.htaccess` находится в каталоге document-root (т.е. рядом с файлом `index.php` ). +- [Apache обрабатывает файлы .htaccess |#Test if .htaccess is working] +- [включен mod_rewrite |#Test if mod_rewrite is enabled] + +Если вы устанавливаете приложение в подкаталоге, возможно, вам придется откомментировать строку для параметра `RewriteBase` и установить его в правильную папку. **nginx**: директива `try_files` должна использоваться в конфигурации сервера: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args важно + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args ВАЖНО! } ``` Блок `location` должен быть определен ровно один раз для каждого пути к файловой системе в блоке `server`. Если в вашей конфигурации уже есть блок `location /`, добавьте директиву `try_files` в существующий блок. +Проверьте, работает ли `.htaccess` .[#toc-test-if-htaccess-is-working] +---------------------------------------------------------------------- +Самый простой способ проверить, использует ли Apache или игнорирует ваш файл `.htaccess`, - это намеренно нарушить его. Поместите строку `Test` в начало файла и теперь, если вы обновите страницу в браузере, вы должны увидеть *Internal Server Error*. + +Если вы видите эту ошибку, это хорошо! Это означает, что Apache разбирает файл `.htaccess` и сталкивается с ошибкой, которую мы туда поместили. Удалите строку `Test`. + +Если вы не видите *Internal Server Error*, значит, ваша установка Apache игнорирует файл `.htaccess`. Как правило, Apache игнорирует его из-за отсутствия директивы конфигурации `AllowOverride All`. + +Если вы хоститесь сами, это достаточно легко исправить. Откройте ваш `httpd.conf` или `apache.conf` в текстовом редакторе, найдите соответствующий `<Directory>` раздел и добавьте/измените директиву: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Если ваш сайт размещен на другом хостинге, проверьте в панели управления, можете ли вы включить `.htaccess` там. Если нет, обратитесь к своему хостинг-провайдеру, чтобы он сделал это за вас. + + +Проверьте, включен ли `mod_rewrite` .[#toc-test-if-mod-rewrite-is-enabled] +-------------------------------------------------------------------------- +Если вы убедились, что [`.htaccess` работает |#Test if .htaccess is working], вы можете проверить, включено ли расширение mod_rewrite. Поместите строку `RewriteEngine On` в начало файла `.htaccess` и обновите страницу в браузере. +Если вы увидите *Internal Server Error*, это означает, что mod_rewrite не включен. Есть несколько способов включить его. Смотрите на Stack Overflow различные способы, которые можно использовать на разных установках. + + Ссылки генерируются без `https:`. .[#toc-links-are-generated-without-https] --------------------------------------------------------------------------- Nette генерирует ссылки с тем же протоколом, который использует текущая страница. Таким образом, на странице `https://foo` генерируются ссылки, начинающиеся с `https:` и наоборот. diff --git a/nette/sl/@home.texy b/nette/sl/@home.texy index 51c7224b56..bafe6b5d10 100644 --- a/nette/sl/@home.texy +++ b/nette/sl/@home.texy @@ -6,13 +6,16 @@ Nette Dokumentacija <div> -Splošno -------- -- [Zakaj uporabljati Nette |www:en:10-reasons-why-nette]? -- <b>[Ustvarite svojo prvo aplikacijo! |quickstart:]<b> +Uvod +---- +- [Zakaj uporabljati Nette |www:10-reasons-why-nette]? +- [Namestitev |Installation] +- [Ustvarite svojo prvo aplikacijo |quickstart:]! -- [Paketi in namestitev |www:packages] +Splošno +------- +- [Seznam paketov |www:packages] - [Vzdrževanje in PHP |www:maintenance] - [Opombe k izdaji |https://nette.org/releases] - [Vodnik za nadgradnjo |migrations:en] @@ -20,6 +23,7 @@ Splošno - [Kdo ustvarja Nette |https://nette.org/contributors] - [Zgodovina družbe Nette |www:history] - [Vključite se |contributing:] +- [Razvoj sponzorjev |https://nette.org/en/donate] - [Sklic na API |https://api.nette.org/] </div> diff --git a/nette/sl/installation.texy b/nette/sl/installation.texy new file mode 100644 index 0000000000..73146aa349 --- /dev/null +++ b/nette/sl/installation.texy @@ -0,0 +1,69 @@ +Namestitev sistema Nette +************************ + +.[perex] +Nette je družina naprednih knjižnic za PHP, ki jih lahko uporabljate popolnoma samostojno. Nette je tudi ogrodje za celovito gradnjo. Na tej strani je prikazano, kako namestiti posamezno knjižnico ali celotno ogrodje. + + +.[note] +Ste novi v sistemu Nette? Priporočamo vam, da si ogledate tudi [vadnico Ustvarite svojo prvo aplikacijo |quickstart:]. + + +Kako namestiti pakete .[#toc-how-to-install-packages] +----------------------------------------------------- + +Posamezni [paketi |www:packages] iz družine Nette se namestijo z orodjem [Composer |best-practices:composer]. Če ga še ne poznate, morate vsekakor začeti z njim. Je zelo uporabno orodje. + +Ali morate na primer v svoji kodi prečkati datotečni sistem? [Iskalnik |utils:finder], ki je vključen v paket `nette/utils` (glejte desni stolpec), je za to odličen. Namestite ga lahko iz ukazne vrstice: + +```shell +composer require nette/utils +``` + +To bo namestilo tudi vse druge pakete. + +Druga možnost je, da vse pakete dodate naenkrat z namestitvijo `nette/nette`: + +```shell +composer require nette/nette +``` + + +Kako ustvariti nov projekt .[#toc-how-to-create-a-new-project] +-------------------------------------------------------------- + +Boste ustvarili nov projekt na Nette? Najlažje boste začeli tako, da prenesete osnovno ogrodje spletne aplikacije, imenovano [Spletni projekt |https://github.com/nette/web-project]. + +Tudi v tem primeru vam bo program Composer pomagal pri vzpostavitvi projekta. Poiščite korenski imenik svojega spletnega strežnika (npr. `/var/www` ali `C:\InetPub`). V ukazni vrstici zaženite naslednji ukaz, vendar `my-project` zamenjajte z imenom imenika, ki ga želite ustvariti: + +```shell +composer create-project nette/web-project my-project +``` + +(Če ne želite uporabiti programa Composer, [prenesite arhiv |https://github.com/nette/web-project/archive/preloaded.zip], ga razpakirajte in kopirajte v korenski imenik spletnega strežnika.) + +Če razvijate v operacijskem sistemu MacOS ali Linux (ali katerem koli drugem sistemu, ki temelji na Unixu), boste še vedno morali [nastaviti dovoljenja za pisanje |nette:troubleshooting#setting-directory-permissions]. + +Odlična stvar pri Nette je, da vam ni treba konfigurirati ali nastavljati ničesar zapletenega. Na tej točki bi torej domača stran spletnega projekta že morala delovati. To lahko preverite tako, da odprete brskalnik na naslednjem naslovu URL: + +``` +http://localhost/my-project/www/ +``` + +in prikazana bo pozdravna stran ogrodja Nette: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Hura, okostje deluje! Izbrišite pozdravno predlogo in lahko začnete pisati odlično novo aplikacijo. + +.[note] +Če se pojavi težava, [poskusite z nekaj nasveti |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Orodja .[#toc-tools] +-------------------- + +Priporočamo uporabo [kakovostnega IDE in vseh potrebnih vtičnikov |best-practices:editors-and-tools], saj boste tako izjemno učinkoviti. + + +{{leftbar: www:@menu-common}} diff --git a/nette/sl/troubleshooting.texy b/nette/sl/troubleshooting.texy index 60a00f90a8..147e5864fb 100644 --- a/nette/sl/troubleshooting.texy +++ b/nette/sl/troubleshooting.texy @@ -21,6 +21,11 @@ Vzrok je običajno [nezadostna dovoljenja za |#Setting Directory Permissions] pi Eden najpogostejših razlogov je zastarel predpomnilnik. Medtem ko Nette v razvojnem načinu pametno samodejno posodablja predpomnilnik, se v produkcijskem načinu osredotoča na čim večjo zmogljivost, čiščenje predpomnilnika po vsaki spremembi kode pa je odvisno od vas. Poskusite izbrisati `temp/cache`. +Napaka 404, usmerjanje ne deluje .[#toc-error-404-routing-not-working] +---------------------------------------------------------------------- +Če vse strani (razen domače strani) vrnejo napako 404, je videti, da gre za težavo s konfiguracijo strežnika za [lepe naslove URL |#How to Configure a Server for Nice URLs?]. + + Napaka `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ---------------------------------------------------------------------------------------------------------------------- Ta napaka se pojavi, če ste PHP nadgradili na različico 8.1, vendar uporabljate Nette, ki z njo ni združljiv. Rešitev je, da Nette posodobite na novejšo različico z uporabo `composer update`. Nette podpira PHP 8.1 od različice 3.0. Če uporabljate starejšo različico (to lahko ugotovite z vpogledom v `composer.json`), [nadgradite Nette |migrations:en] ali pa ostanite pri PHP 8.0. @@ -61,7 +66,7 @@ Rešitev **ni**, da bi se mape `www/` "znebili" z uporabo pravil v datoteki `.ht Kako konfigurirati strežnik za lepe naslove URL? .[#toc-how-to-configure-a-server-for-nice-urls] ------------------------------------------------------------------------------------------------ -**Apache**: razširitev mod_rewrite mora biti dovoljena in konfigurirana v datoteki `.htaccess`. +**Apache**: v datoteki `.htaccess` morate omogočiti in nastaviti pravila mod_rewrite: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Če želite spremeniti konfiguracijo Apache z datotekami .htaccess, je treba omogočiti direktivo AllowOverride. To je privzeto vedenje za Apache. +Če naletite na težave, se prepričajte, da: +- se datoteka `.htaccess` nahaja v korenskem imeniku dokumenta (tj. poleg datoteke `index.php` ) +- [Apache obdeluje datoteke .htaccess |#Test if .htaccess is working] +- [je omogočen mod_rewrite |#Test if mod_rewrite is enabled] + +Če aplikacijo nastavljate v podmapi, boste morda morali odkomentirati vrstico za nastavitev `RewriteBase` in jo nastaviti na pravo mapo. **nginx**: v konfiguraciji strežnika je treba uporabiti direktivo `try_files`: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args JE POMEMBNO! } ``` Blok `location` mora biti opredeljen natanko enkrat za vsako pot do datotečnega sistema v bloku `server`. Če v konfiguraciji že imate blok `location /`, dodajte direktivo `try_files` v obstoječi blok. +Preizkusite, ali `.htaccess` deluje .[#toc-test-if-htaccess-is-working] +----------------------------------------------------------------------- +Najpreprostejši način za preverjanje, ali Apache uporablja ali ignorira vašo datoteko `.htaccess`, je, da jo namerno prekinete. Na začetek datoteke postavite vrstico `Test` in če zdaj v brskalniku osvežite stran, se bo prikazala *Internal Server Error* (notranja napaka strežnika). + +Če vidite to napako, je to pravzaprav dobro! To pomeni, da Apache analizira datoteko `.htaccess` in naleti na napako, ki smo jo vstavili vanjo. Odstranite vrstico `Test`. + +Če ne vidite *Internal Server Error*, vaša namestitev Apache ignorira datoteko `.htaccess`. Na splošno jo Apache ignorira zaradi manjkajoče konfiguracijske direktive `AllowOverride All`. + +Če gostujete sami, je to dovolj enostavno popraviti. V urejevalniku besedila odprite `httpd.conf` ali `apache.conf` in poiščite ustrezno `<Directory>` razdelek in dodajte/spremenite direktivo: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Če vaše spletno mesto gostuje drugje, preverite nadzorno ploščo in preverite, ali lahko tam omogočite `.htaccess`. Če ne, se obrnite na ponudnika gostovanja, da to stori namesto vas. + + +Preizkusite, ali je omogočena stran `mod_rewrite` .[#toc-test-if-mod-rewrite-is-enabled] +---------------------------------------------------------------------------------------- +Če ste preverili, da [`.htaccess` deluje |#Test if .htaccess is working], lahko preverite, ali je omogočena razširitev mod_rewrite. Na začetek datoteke `.htaccess` vstavite vrstico `RewriteEngine On` in osvežite stran v brskalniku. +Če se prikaže *Internal Server Error*, to pomeni, da razširitev mod_rewrite ni omogočena. Obstaja več načinov, kako ga omogočiti. V Stack Overflow si oglejte različne načine, na katere je to mogoče storiti pri različnih nastavitvah. + + Povezave se ustvarijo brez `https:` .[#toc-links-are-generated-without-https] ----------------------------------------------------------------------------- Nette generira povezave z enakim protokolom, kot ga uporablja trenutna stran. Tako na strani `https://foo`, in obratno. diff --git a/nette/tr/@home.texy b/nette/tr/@home.texy index 5588f5b6e2..095f3e9aff 100644 --- a/nette/tr/@home.texy +++ b/nette/tr/@home.texy @@ -6,13 +6,16 @@ Nette Dokümantasyon <div> -Genel +Giriş ----- -- [Neden Nette Kullanmalısınız? |www:10-reasons-why-nette] -- <b>[İlk Uygulamanızı Oluşturun! |quickstart:]<b> +- [Neden Nette Kullanılmalı? |www:10-reasons-why-nette] +- [Kurulum |Installation] +- [İlk Başvurunuzu Oluşturun! |quickstart:] -- [Paketler & Kurulum |www:packages] +Genel +----- +- [Paketlerin Listesi |www:packages] - [Bakım ve PHP |www:maintenance] - [Sürüm Notları |https://nette.org/releases] - [Yükseltme Kılavuzu |migrations:en] @@ -20,6 +23,7 @@ Genel - [Nette'i Kim Yarattı |https://nette.org/contributors] - [Nette'in Tarihçesi |www:history] - [Katılın |contributing:] +- [Sponsor geliştirme |https://nette.org/en/donate] - [API referansı |https://api.nette.org/] </div> diff --git a/nette/tr/installation.texy b/nette/tr/installation.texy new file mode 100644 index 0000000000..815c55ff9d --- /dev/null +++ b/nette/tr/installation.texy @@ -0,0 +1,69 @@ +Nette Kurulumu +************** + +.[perex] +Nette, PHP için tamamen bağımsız olarak kullanabileceğiniz gelişmiş kütüphanelerden oluşan bir ailedir. Nette aynı zamanda tam yığın bir çerçevedir. Bu sayfa size her bir kütüphaneyi veya tüm çerçeveyi nasıl kuracağınızı gösterir. + + +.[note] +Nette'te yeni misiniz? [İlk Uygulamanızı Oluşturun |quickstart:] eğitimine de göz atmanızı tavsiye ederiz. + + +Paketler Nasıl Kurulur .[#toc-how-to-install-packages] +------------------------------------------------------ + +Nette ailesindeki bireysel [paketler |www:packages] [Composer |best-practices:composer] aracı kullanılarak kurulur. Eğer bu araca aşina değilseniz, kesinlikle onunla başlamalısınız. Çok kullanışlı bir araçtır. + +Örneğin, kodunuzda dosya sistemini dolaşmanız mı gerekiyor? `nette/utils` paketine dahil olan [Finder |utils:finder](sağ sütuna bakın) bunun için harikadır. Komut satırından yükleyebilirsiniz: + +```shell +composer require nette/utils +``` + +Bu, diğer tüm paketleri de yükleyecektir. + +Alternatif bir yol da `nette/nette` adresini yükleyerek tüm paketleri bir kerede eklemektir: + +```shell +composer require nette/nette +``` + + +Yeni Bir Proje Nasıl Oluşturulur .[#toc-how-to-create-a-new-project] +-------------------------------------------------------------------- + +Nette yeni bir proje mi oluşturacaksınız? Başlamanın en kolay yolu [Web Projesi |https://github.com/nette/web-project] adı verilen temel web uygulaması iskeletini indirmektir. + +Composer yine projenizi kurmanıza yardımcı olacaktır. Web sunucunuzun kök dizinini bulun (örneğin, `/var/www` veya `C:\InetPub`). Komut isteminde aşağıdaki komutu çalıştırın, ancak `my-project` yerine oluşturulacak dizinin adını yazın: + +```shell +composer create-project nette/web-project my-project +``` + +(Composer kullanmak istemiyorsanız, [arşivi indirin |https://github.com/nette/web-project/archive/preloaded.zip], sıkıştırmayı açın ve web sunucusunun kök dizinine kopyalayın). + +MacOS veya Linux (ya da Unix tabanlı başka bir sistem) üzerinde geliştirme yapıyorsanız, yine de [yazma izinlerini ayarlamanız |nette:troubleshooting#setting-directory-permissions] gerekecektir. + +Nette'in en güzel yanı, karmaşık bir şey yapılandırmanız ya da kurmanız gerekmemesidir. Bu noktada, Web Projesi ana sayfası çalışıyor olmalıdır. Tarayıcınızı aşağıdaki URL'de açarak bunu test edebilirsiniz: + +``` +http://localhost/my-project/www/ +``` + +ve Nette Framework karşılama sayfasını görmelisiniz: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Yaşasın, iskelet çalışıyor! Karşılama şablonunu silin ve harika bir yeni uygulama yazmaya başlayabilirsiniz. + +.[note] +Bir sorun varsa, [bu birkaç ipucunu deneyin |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Araçlar .[#toc-tools] +--------------------- + + [Kaliteli |best-practices:editors-and-tools] bir [IDE ve gerekli tüm eklentileri |best-practices:editors-and-tools] kullanmanızı öneririz, bu sizi son derece verimli hale getirecektir. + + +{{leftbar: www:@menu-common}} diff --git a/nette/tr/troubleshooting.texy b/nette/tr/troubleshooting.texy index bf0c471d55..f0d9f097d5 100644 --- a/nette/tr/troubleshooting.texy +++ b/nette/tr/troubleshooting.texy @@ -21,6 +21,11 @@ Eğer `Tracy is unable to log error` cümlesi hata mesajında yer almıyorsa (ar En yaygın nedenlerden biri eski bir önbellektir. Nette, geliştirme modunda önbelleği akıllıca otomatik olarak güncellerken, üretim modunda performansı en üst düzeye çıkarmaya odaklanır ve her kod değişikliğinden sonra önbelleği temizlemek size bağlıdır. `temp/cache` adresini silmeyi deneyin. +Hata 404, yönlendirme çalışmıyor .[#toc-error-404-routing-not-working] +---------------------------------------------------------------------- +Tüm sayfalar (ana sayfa hariç) 404 hatası döndürdüğünde, [güzel URL' |#How to Configure a Server for Nice URLs?]ler için bir sunucu yapılandırma sorunu gibi görünür. + + Hata `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] -------------------------------------------------------------------------------------------------------------------- Bu hata, PHP'yi 8.1 sürümüne yükselttiyseniz ancak onunla uyumlu olmayan Nette kullanıyorsanız ortaya çıkar. Çözüm, `composer update` adresini kullanarak Nette'yi daha yeni bir sürüme güncellemektir. Nette, PHP 8.1'i 3.0 sürümünden beri desteklemektedir. Daha eski bir sürüm kullanıyorsanız ( `composer.json` adresine bakarak öğrenebilirsiniz), Nette'yi [yükseltin |migrations:en] veya PHP 8.0 ile kalın. @@ -61,7 +66,7 @@ Uygulamayı hosting üzerinde çalıştırmak için, hosting yapılandırmasınd Güzel URL'ler için Sunucu Nasıl Yapılandırılır? .[#toc-how-to-configure-a-server-for-nice-urls] ----------------------------------------------------------------------------------------------- -**Apache**: mod_rewrite uzantısına izin verilmeli ve `.htaccess` dosyasında yapılandırılmalıdır. +**Apache**: `.htaccess` dosyasında mod_rewrite kurallarını etkinleştirmeniz ve ayarlamanız gerekir: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Apache yapılandırmasını .htaccess dosyaları ile değiştirmek için AllowOverride yönergesi etkinleştirilmelidir. Bu Apache için öntanımlı davranıştır. +Sorunlarla karşılaşırsanız, şunlardan emin olun: +- `.htaccess` dosyasının document-root dizininde bulunduğundan (yani `index.php` dosyasının yanında) +- [Apache .htaccess dosyalarını işliyor |#Test if .htaccess is working] +- [mod_rewrite etkinleştirildi |#Test if mod_rewrite is enabled] + +Uygulamayı bir alt klasörde kuruyorsanız, `RewriteBase` ayarı için satırı kaldırmanız ve doğru klasöre ayarlamanız gerekebilir. **nginx**: sunucu yapılandırmasında `try_files` yönergesi kullanılmalıdır: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args is important + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args ÖNEMLİDİR! } ``` `location` bloğu, `server` bloğundaki her dosya sistemi yolu için tam olarak bir kez tanımlanmalıdır. Yapılandırmanızda zaten bir `location /` bloğu varsa, `try_files` yönergesini mevcut bloğa ekleyin. +`.htaccess` 'un çalışıp çalışmadığını test edin .[#toc-test-if-htaccess-is-working] +----------------------------------------------------------------------------------- +Apache'nin `.htaccess` dosyanızı kullanıp kullanmadığını ya da yok sayıp saymadığını test etmenin en basit yolu, dosyayı kasıtlı olarak bozmaktır. Dosyanın başına `Test` satırını koyun ve şimdi tarayıcınızda sayfayı yenilediğinizde bir *Internal Server Error* görmelisiniz. + +Eğer bu hatayı görüyorsanız, bu aslında iyi bir şey! Bu, Apache'nin `.htaccess` dosyasını çözümlediği ve oraya koyduğumuz hatayla karşılaştığı anlamına gelir. `Test` satırını kaldırın. + +Eğer bir *Dahili Sunucu Hatası* görmüyorsanız, Apache kurulumunuz `.htaccess` dosyasını görmezden geliyor demektir. Genel olarak, Apache eksik yapılandırma yönergesi `AllowOverride All` nedeniyle bunu yok sayar. + +Eğer kendiniz barındırıyorsanız, bunu düzeltmek oldukça kolaydır. `httpd.conf` veya `apache.conf` adresinizi bir metin düzenleyicide açın, ilgili `<Directory>` bölümünü açın ve yönergeyi ekleyin/değiştirin: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Siteniz başka bir yerde barındırılıyorsa, `.htaccess` adresini etkinleştirip etkinleştiremeyeceğinizi görmek için kontrol panelinizi kontrol edin. Değilse, bunu sizin için yapması için barındırma sağlayıcınızla iletişime geçin. + + +`mod_rewrite` 'un etkin olup olmadığını test edin .[#toc-test-if-mod-rewrite-is-enabled] +---------------------------------------------------------------------------------------- +[`.htaccess` 'un çalıştığını |#Test if .htaccess is working] doğruladıysanız, mod_rewrite uzantısının etkin olduğunu doğrulayabilirsiniz. `RewriteEngine On` satırını `.htaccess` dosyasının başına koyun ve tarayıcınızda sayfayı yenileyin. +Eğer bir *Internal Server Error* görürseniz, bu mod_rewrite'ın etkin olmadığı anlamına gelir. Etkinleştirmenin birkaç yolu vardır. Bunun farklı kurulumlarda yapılabileceği çeşitli yollar için Stack Overflow'a bakın. + + Bağlantılar `https:` Olmadan Oluşturulur .[#toc-links-are-generated-without-https] ---------------------------------------------------------------------------------- Nette, geçerli sayfanın kullandığı protokolle aynı protokole sahip bağlantılar oluşturur. Yani `https://foo` ile başlayan bağlantılar oluşturur ve bunun tersi de geçerlidir. diff --git a/nette/uk/@home.texy b/nette/uk/@home.texy index 38dc787e4f..22a16c5c9d 100644 --- a/nette/uk/@home.texy +++ b/nette/uk/@home.texy @@ -6,12 +6,16 @@ <div> -Передмова ---------- -- <b>[Створіть свій перший додаток! |quickstart:]<b> +Вступ +----- +- [Чому варто використовувати Nette? |www:10-reasons-why-nette] +- [Встановлення |Installation] +- [Створіть свій перший додаток! |quickstart:] -- [Пакети та встановлення|www:packages] +Загальна інформація +------------------- +- [Список пакетів |www:packages] - [Обслуговування та PHP |www:maintenance] - [Примітки до випуску |https://nette.org/releases] - [Керівництво з оновлення |migrations:en] @@ -19,6 +23,7 @@ - [Творці Nette |https://nette.org/contributors] - [Історія Nette |www:history] - [Взяти участь |contributing:] +- [Розвиток спонсорів |https://nette.org/en/donate] - [Довідник з API |https://api.nette.org/] </div> diff --git a/nette/uk/installation.texy b/nette/uk/installation.texy new file mode 100644 index 0000000000..58cb2f8e92 --- /dev/null +++ b/nette/uk/installation.texy @@ -0,0 +1,69 @@ +Встановлення Nette +****************** + +.[perex] +Nette - це сімейство розширених бібліотек для PHP, які ви можете використовувати повністю автономно. Nette також є повностековим фреймворком. На цій сторінці показано, як встановити кожну бібліотеку або весь фреймворк. + + +.[note] +Ви новачок у Nette? Ми також рекомендуємо вам ознайомитися з [навчальним посібником Створення першої програми |quickstart:]. + + +Як встановити пакети .[#toc-how-to-install-packages] +---------------------------------------------------- + +Окремі [пакунки |www:packages] сімейства Nette встановлюються за допомогою інструменту [Composer |best-practices:composer]. Якщо ви не знайомі з ним, вам обов'язково слід почати з нього. Це дуже корисний інструмент. + +Наприклад, вам потрібно пройтись по файловій системі у вашому коді? Для цього чудово підійде [Finder |utils:finder], який входить до складу пакунка `nette/utils` (див. праву колонку). Ви можете встановити його з командного рядка: + +```shell +composer require nette/utils +``` + +Це також призведе до встановлення усіх інших пакунків. + +Альтернативний спосіб - додати усі пакунки одразу, встановивши `nette/nette`: + +```shell +composer require nette/nette +``` + + +Як створити новий проект .[#toc-how-to-create-a-new-project] +------------------------------------------------------------ + +Ви збираєтеся створити новий проект на Nette? Найпростіший спосіб почати - завантажити базовий скелет веб-додатку, який називається [Веб-проект |https://github.com/nette/web-project]. + +Знову ж таки, Composer допоможе вам налаштувати ваш проект. Знайдіть кореневий каталог вашого веб-сервера (наприклад, `/var/www` або `C:\InetPub`). Запустіть у командному рядку наступну команду, але замініть `my-project` на назву каталогу, який потрібно створити: + +```shell +composer create-project nette/web-project my-project +``` + +(Якщо ви не хочете використовувати Composer, [завантажте архів |https://github.com/nette/web-project/archive/preloaded.zip], розпакуйте його і скопіюйте до кореневого каталогу веб-сервера). + +Якщо ви розробляєте на macOS або Linux (або будь-якій іншій системі на базі Unix), вам все одно потрібно буде [встановити права на запис |nette:troubleshooting#setting-directory-permissions]. + +Чудова річ у Nette полягає в тому, що вам не потрібно налаштовувати або встановлювати нічого складного. Отже, на цьому етапі домашня сторінка веб-проекту має працювати. Ви можете перевірити це, відкривши браузер за наступною адресою: + +``` +http://localhost/my-project/www/ +``` + +і ви побачите вітальну сторінку Nette Framework: + +[* qs-welcome.webp .{url: http://localhost/my-project/www/} *] + +Ура, каркас працює! Видаліть шаблон привітання і можете починати писати нову чудову програму. + +.[note] +Якщо виникли проблеми, [спробуйте скористатися цими порадами |nette:troubleshooting#Nette Is Not Working, White Page Is Displayed]. + + +Інструменти .[#toc-tools] +------------------------- + +Ми рекомендуємо використовувати [якісну IDE та всі необхідні плагіни |best-practices:editors-and-tools], це зробить вашу роботу надзвичайно ефективною. + + +{{leftbar: www:@menu-common}} diff --git a/nette/uk/troubleshooting.texy b/nette/uk/troubleshooting.texy index ed42e2e18c..3ef108144d 100644 --- a/nette/uk/troubleshooting.texy +++ b/nette/uk/troubleshooting.texy @@ -21,6 +21,11 @@ Nette не працює, відображається біла сторінка Одна з найпоширеніших причин - застарілий кеш. У той час як Nette розумно автоматично оновлює кеш у режимі розробки, у виробничому режимі він зосереджений на максимізації продуктивності, і очищення кешу після кожної модифікації коду залежить від вас. Спробуйте видалити `temp/cache`. +Помилка 404, маршрутизація не працює .[#toc-error-404-routing-not-working] +-------------------------------------------------------------------------- +Коли всі сторінки (крім головної) повертають помилку 404, це схоже на проблему конфігурації сервера для [красивих URL-адрес |#How to Configure a Server for Nice URLs?]. + + Помилка `#[\ReturnTypeWillChange] attribute should be used` .[#toc-error-returntypewillchange-attribute-should-be-used] ----------------------------------------------------------------------------------------------------------------------- Ця помилка виникає, якщо ви оновили PHP до версії 8.1, але використовуєте Nette, який не сумісний з нею. Тому рішенням є оновлення Nette до новішої версії за допомогою `composer update`. Nette підтримує PHP 8.1 з версії 3.0. Якщо ви використовуєте старішу версію (ви можете дізнатися це, подивившись у `composer.json`), [оновіть Nette |migrations:en] або залишайтеся з PHP 8.0. @@ -61,7 +66,7 @@ setsebool -P httpd_can_network_connect_db on Як налаштувати сервер для красивих URL? .[#toc-how-to-configure-a-server-for-nice-urls] --------------------------------------------------------------------------------------- -**Apache**: Розширення mod_rewrite має бути дозволено та налаштовано у файлі `.htaccess`. +**Apache**: потрібно увімкнути та встановити правила mod_rewrite у файлі `.htaccess`: ```apacheconf RewriteEngine On @@ -70,19 +75,49 @@ RewriteCond %{REQUEST_FILENAME} !-d RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz)$ index.php [L] ``` -Щоб змінити конфігурацію Apache за допомогою файлів .htaccess, необхідно включити директиву AllowOverride. Це поведінка за замовчуванням для Apache. +Якщо у вас виникли проблеми, переконайтеся, що +- файл `.htaccess` знаходиться в кореневому каталозі документа (тобто поруч з файлом `index.php` ) +- [Apache обробляє файли .htaccess |#Test if .htaccess is working] +- [увімкнено mod_rewrite |#Test if mod_rewrite is enabled] + +Якщо ви налаштовуєте додаток у підкаталозі, можливо, вам доведеться розкоментувати рядок для параметра `RewriteBase` і вказати правильну папку. **nginx**: директива `try_files` повинна використовуватися в конфігурації сервера: ```nginx location / { - try_files $uri $uri/ /index.php$is_args$args; # $is_args$args важно + try_files $uri $uri/ /index.php$is_args$args; # $is_args$args ВАЖЛИВО! } ``` Блок `location` повинен бути визначений рівно один раз для кожного шляху до файлової системи в блоці `server`. Якщо у вашій конфігурації вже є блок `location /`, додайте директиву `try_files` в існуючий блок. +Перевірте, чи працює `.htaccess` .[#toc-test-if-htaccess-is-working] +-------------------------------------------------------------------- +Найпростіший спосіб перевірити, чи використовує Apache ваш файл `.htaccess`, чи ігнорує його, - це навмисно зламати його. Помістіть рядок `Test` на початок файлу і тепер, якщо ви оновите сторінку в браузері, ви побачите *Внутрішня помилка сервера*. + +Якщо ви бачите цю помилку, це дуже добре! Це означає, що Apache аналізує файл `.htaccess`, і він зіткнувся з помилкою, яку ми туди вставили. Видаліть рядок `Test`. + +Якщо ви не побачите *Внутрішня помилка сервера*, то ваші налаштування Apache ігнорують файл `.htaccess`. Зазвичай, Apache ігнорує його через відсутність директиви конфігурації `AllowOverride All`. + +Якщо ви розміщуєте його самостійно, це досить легко виправити. Відкрийте ваш `httpd.conf` або `apache.conf` в текстовому редакторі, знайдіть відповідний розділ `<Directory>` розділ і додайте/змініть директиву: + +```apacheconf +<Directory "/var/www/htdocs"> # path to your document root + AllowOverride All + ... +``` + +Якщо ваш сайт розміщено на іншому хостингу, перевірте в панелі керування, чи можна там увімкнути `.htaccess`. Якщо ні, зверніться до хостинг-провайдера, щоб він зробив це за вас. + + +Перевірте, чи ввімкнено `mod_rewrite` .[#toc-test-if-mod-rewrite-is-enabled] +---------------------------------------------------------------------------- +Якщо ви переконалися, що [`.htaccess` працює |#Test if .htaccess is working], ви можете перевірити, чи ввімкнено розширення mod_rewrite. Додайте рядок `RewriteEngine On` на початку файлу `.htaccess` і оновіть сторінку в браузері. +Якщо ви побачите *Внутрішня помилка сервера*, це означає, що mod_rewrite не ввімкнено. Увімкнути його можна кількома способами. Див. розділ Переповнення стеку, де описано, як це можна зробити за різних налаштувань. + + Посилання генеруються без `https:`. .[#toc-links-are-generated-without-https] ----------------------------------------------------------------------------- Nette генерує посилання з тим самим протоколом, який використовує поточна сторінка. Таким чином, на сторінці `https://foo` і навпаки. diff --git a/php-generator/bg/@home.texy b/php-generator/bg/@home.texy index bf07534afe..d46d1347fd 100644 --- a/php-generator/bg/@home.texy +++ b/php-generator/bg/@home.texy @@ -5,7 +5,7 @@ - Трябва да генерирате PHP код за класове, функции, PHP файлове и др. - Поддържа всички най-нови функции на PHP като енуми, атрибути и др. - Позволява ви лесно да променяте съществуващи класове -- Изход, съвместим с PSR-12 +- Изход, съвместим с PSR-12 / PER coding style - Високо развита, стабилна и широко използвана библиотека </div> @@ -52,7 +52,6 @@ echo $class; */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -68,6 +67,7 @@ echo $printer->printClass($class); ```php $class->addConstant('ID', 123) ->setProtected() // постоянна видимост + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Той генерира: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Свойствата само за четене, въведени от PHP 8.1, могат да бъдат маркирани чрез `setReadOnly()`. +Свойствата и класовете само за четене могат да бъдат маркирани чрез `setReadOnly()`. ------ @@ -170,7 +170,7 @@ $class->addMember($methodRecount); Интерфейс или черта .[#toc-interface-or-trait] ---------------------------------------------- -Можете да създавате интерфейси и черти: +Можете да създавате интерфейси и черти (класове [InterfaceType |api:Nette\PhpGenerator\InterfaceType] и [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); @@ -194,6 +194,7 @@ echo $class; class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Енуми .[#toc-enums] ------------------- -Можете лесно да създадете енумите, които PHP 8.1 въвежда: +Можете лесно да създадете енумите, които PHP 8.1 въвежда (клас [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 +// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 +// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Принтери и съответствие с PSR .[#toc-printers-and-psr-compliance] ----------------------------------------------------------------- -PHP кодът се генерира от обекти `Printer`. Съществува `PsrPrinter`, чийто изход съответства на PSR-2 и PSR-12 и използва интервали за отстъп, и `Printer`, който използва табулатори за отстъп. +Класът [Printer |api:Nette\PhpGenerator\Printer] се използва за генериране на PHP код: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = нов Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // същото като: echo $class +``` + +Той може да генерира код за всички останали елементи, като предлага методи като `printFunction()`, `printNamespace()` и др. + +Освен това е наличен класът `PsrPrinter`, чийто изход е в съответствие със стила на кодиране PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // Отстъпление от 4 интервала +echo $printer->printClass($class); ``` -Трябва да персонализирате поведението на принтера? Създайте свой собствен, като наследите класа `Printer`. Можете да преконфигурирате тези променливи: +Трябва да настроите поведението според нуждите си? Създайте свой собствен принтер, като го наследите от класа `Printer`. Можете да преконфигурирате тези променливи: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // дължина на реда, след която редът ще се прекъсне public int $wrapLength = 120; + // символ за отстъпление, може да бъде заменен с поредица от интервали public string $indentation = "\t"; + // брой празни редове между свойствата public int $linesBetweenProperties = 0; + // Брой празни редове между методите public int $linesBetweenMethods = 2; + // брой празни редове между групите декларации за употреба на класове, функции и константи public int $linesBetweenUseTypes = 0; + // позиция на отварящата се скоба за функции и методи public bool $bracesOnNextLine = true; + // поставяне на един параметър на един ред, дори ако той има атрибут или е повишен + public bool $singleParameterOnOneLine = false; + // разделител между дясната скоба и типа за връщане на функции и методи public string $returnTypeColon = ': '; } ``` +Как и защо точно се различават стандартните `Printer` и `PsrPrinter`? Защо в пакета няма само един принтер, `PsrPrinter`, който да се различава? + +Стандартният `Printer` форматира кода така, както го правим в цялата мрежа Nette. Тъй като Nette беше създадена много по-рано от PSR, а също и защото PSR в продължение на много години не предоставяше стандартите навреме, а понякога дори с няколко години закъснение от въвеждането на нова функция в PHP, това доведе до няколко малки разлики в [стандарта за кодиране |contributing:coding-standard]. +По-голямата разлика е само в използването на табулатори вместо интервали. Знаем, че чрез използването на табулатори в нашите проекти даваме възможност за регулиране на ширината, което е [от съществено значение за хората със зрителни увреждания |contributing:coding-standard#Tabs Instead of Spaces]. +Пример за малка разлика е поставянето на къдравата скоба на отделен ред за функциите и методите и винаги. Считаме, че препоръката на PSR е нелогична и [води до намаляване на яснотата на кода |contributing:coding-standard#Wrapping and Braces]. + Типове .[#toc-types] -------------------- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // ще се опрости до A ->addTrait('Bar\AliasedClass'); // ще се опрости до AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // в коментарите се опростява ръчно +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // в коментарите опростете ръчно $method->addParameter('arg') ->setType('Bar\OtherClass'); // ще се разреши до \Bar\OtherClass echo $namespace; -// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 +// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 +// или използвайте PsrPrinter за изход, съответстващ на PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -758,7 +783,7 @@ $closure = Nette\PhpGenerator\Closure::from( ``` По подразбиране телата на функциите и методите са празни. Ако искате да ги заредите също, използвайте този начин -(той изисква да е инсталиран `nikic/php-parser` ): +(той изисква да е инсталиран `nikic/php-parser`): ```php $class = Nette\PhpGenerator\ClassType::from(Foo::class, withBodies: true); @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Зареждане от PHP файл .[#toc-loading-from-php-file] --------------------------------------------------- -Можете също така да зареждате класове и функции директно от PHP файл, който не е вече зареден, или от низ от PHP код: +Можете също така да зареждате функции, класове, интерфейси и енуми директно от низ от PHP код. Например, създаваме обект `ClassType` по този начин: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Зареждане на целия PHP файл, който може да съдържа множество класове или дори множество пространства от имена: +Когато зареждате класове от PHP код, коментарите на един ред извън тялото на метода се игнорират (например за свойства и т.н.), тъй като тази библиотека не разполага с API за работа с тях. + +Можете също така да заредите директно целия PHP файл, който може да съдържа произволен брой класове, функции или дори няколко пространства от имена: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +Първоначалният коментар на файла и декларацията `strict_types` също се зареждат. От друга страна, всички останали глобални кодове се игнорират. + Това изисква да е инсталиран `nikic/php-parser`. +.[note] +Ако трябва да манипулирате глобален код във файлове или отделни оператори в тялото на метод, по-добре е да използвате директно библиотеката `nikic/php-parser`. + Дъмпер за променливи .[#toc-variables-dumper] --------------------------------------------- diff --git a/php-generator/cs/@home.texy b/php-generator/cs/@home.texy index 921732cdda..abf2cd243f 100644 --- a/php-generator/cs/@home.texy +++ b/php-generator/cs/@home.texy @@ -5,7 +5,7 @@ Generátor PHP kódu - Potřebujete generovat kód tříd, funkcí, PHP souborů atd? - Umí všechny nejnovější vychytávky v PHP (jako enumy atd.) - Dovolí vám snadno modifikovat existující třídy -- Výstup vyhovující PSR-12 +- Výstup vyhovující PSR-12 / PER coding style - Zralá, stabilní a široce používaná knihovna </div> @@ -52,7 +52,6 @@ Vrátí následující výsledek: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -68,6 +67,7 @@ Můžeme přidat konstanty (třída [Constant |api:Nette\PhpGenerator\Constant]) ```php $class->addConstant('ID', 123) ->setProtected() // viditelnost konstant + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Vygeneruje: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Vlastnosti určené pouze pro čtení zavedené v PHP 8.1 lze označit pomocí funkce `setReadOnly()`. +Vlastnosti a třídy určené pouze pro čtení lze označit pomocí funkce `setReadOnly()`. ------ @@ -170,7 +170,7 @@ $class->addMember($methodRecount); Interface nebo traita --------------------- -Lze vytvářet rozhraní a traity: +Můžete vytvářet rozhraní a traity (třídy [InterfaceType |api:Nette\PhpGenerator\InterfaceType] a [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); @@ -194,6 +194,7 @@ Výsledek: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enums ----- -Výčty, které přináší PHP 8.1, můžete snadno vytvořit takto: +Výčty, které přináší PHP 8.1, můžete snadno vytvořit takto: (třída [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 +// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 +// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Printer a soulad s PSR ---------------------- -PHP kód generují objekty `Printer`. K je vám tiskárna `PsrPrinter`, jejíž výstup je v souladu s PSR-2 a PSR-12 a k odsazování používá mezery, a dále `Printer`, která pro odsazování používá tabulátory. +Ke generování PHP kódu slouží třída [Printer |api:Nette\PhpGenerator\Printer]: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // totéž, jako: echo $class +``` + +Umí vygenerovat kód všech dalších prvků, nabízí metody jako `printFunction()`, `printNamespace()`, atd. + +K dispozici je také třída `PsrPrinter`, jejíž výstup je v souladu s PSR-2 / PSR-12 / PER coding style: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // odsazení 4 mezerami +echo $printer->printClass($class); ``` -Potřebujete na míru upravit chování printeru? Vytvořte si vlastní poděděním třídy `Printer`. Můžete překonfigurovat tyto proměnné: +Potřebujete chování doladit na míru? Vytvořte si vlastní verzi poděděním třídy `Printer`. Lze překonfigurovat tyto proměnné: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // délka řádku, po které dojde k zalamování řádku public int $wrapLength = 120; + // znak odsazení, může být nahrazen sekvencí mezer public string $indentation = "\t"; + // počet prázdných řádků mezi properties public int $linesBetweenProperties = 0; + // počet prázdných řádků mezi metodami public int $linesBetweenMethods = 2; + // počet prázdných řádků mezi skupinami 'use statements' pro třídy, funkce a konstanty public int $linesBetweenUseTypes = 0; + // pozice otevírací složené závorky pro funkce a metody public bool $bracesOnNextLine = true; + // umístěte jeden parametr na jeden řádek, i když má atribut nebo je podporován + public bool $singleParameterOnOneLine = false; + // oddělovač mezi pravou závorkou a návratovým typem funkcí a metod public string $returnTypeColon = ': '; } ``` +Jak a proč se vlastně liší standardní `Printer` a `PsrPrinter`? Proč není v balíčku jen jeden printer, a to `PsrPrinter`? + +Standardní `Printer` formátuje kód tak, jak to děláme v celém Nette. Tím, že Nette vzniklo mnohem dřív, než PSR, a také proto, že PSR dlouhé roky nedodávalo standardy včas, ale třeba až s několikaletým zpožděním od uvedení nové featury v PHP, došlo k tomu, že [kódovací standard |contributing:coding-standard] se v několika drobnostech liší. +Větším rozdílem je jen používání tabulátorů místo mezer. Víme, že používáním tabulátorů v našich projektech umožňujeme přizpůsobení šířky, které je pro [lidi se zrakovým postižením nezbytné |contributing:coding-standard#Tabulátory místo mezer]. +Příkladem drobné odlišnosti je umístění složené závorky na samostatném řádku u funkcí a metod a to vždy. Doporučení PSR se nám jeví jako nelogické a vede k [snížení přehlednosti kódu |contributing:coding-standard#Wrapping and Braces]. + Typy ---- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // bude zjednodušen na A ->addTrait('Bar\AliasedClass'); // bude zjednodušen na AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // v komentářích zjednodušíme manuálně +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // v komentářích zjednodušíme manuálně $method->addParameter('arg') ->setType('Bar\OtherClass'); // bude přeložen na \Bar\OtherClass echo $namespace; -// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 +// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 +// nebo použijte PsrPrinter pro výstup v souladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Načítání z PHP souborů ---------------------- -Třídy nebo funkce můžete také načíst přímo ze souboru PHP, který ještě není inkludován, nebo z řetězce obsahujícího PHP kód: +Funkce, třídy, rozhraní a enumy můžete načítat také přímo z řetězce obsahujícího PHP kód. Například takto vytvoříme objekt `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,13 +808,20 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Načtení celého souboru PHP, který může obsahovat více tříd nebo dokonce více jmenných prostorů: +Při načítání tříd z kódu PHP jsou jednořádkové komentáře mimo těla metod ignorovány (např. u properties atd.), protože tato knihovna nemá API pro práci s nimi. + +Můžete také načíst přímo celý soubor PHP, který může obsahovat libovolný počet tříd, funkcí nebo dokonce jmenných prostorů: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` -Vyžaduje, aby byl nainstalován `nikic/php-parser`. +Načte se také úvodní komentář k souboru a deklarace `strict_types`. Naopak veškerý ostatní globální kód je ignorován. + +Vyžaduje se, aby byl nainstalován `nikic/php-parser`. + +.[note] +Pokud potřebujete manipulovat s globálním kódem v souborech nebo s jednotlivými příkazy v tělech metod, je lepší použít přímo knihovnu `nikic/php-parser`. Výpis proměnných diff --git a/php-generator/de/@home.texy b/php-generator/de/@home.texy index 7eccf2d03f..ea918e6b0a 100644 --- a/php-generator/de/@home.texy +++ b/php-generator/de/@home.texy @@ -52,7 +52,6 @@ Es wird dieses Ergebnis wiedergeben: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -68,6 +67,7 @@ Wir können Konstanten (Klasse [Constant |api:Nette\PhpGenerator\Constant]) und ```php $class->addConstant('ID', 123) ->setProtected() // Konstante Sichtbarkeit + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Es erzeugt: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Die mit PHP 8.1 eingeführten schreibgeschützten Eigenschaften können über `setReadOnly()` markiert werden. +Readonly-Eigenschaften und -Klassen können über `setReadOnly()` markiert werden. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Schnittstelle oder Trait .[#toc-interface-or-trait] --------------------------------------------------- -Sie können Schnittstellen und Traits erstellen: +Sie können Schnittstellen und Traits (Klassen [InterfaceType |api:Nette\PhpGenerator\InterfaceType] und [TraitType |api:Nette\PhpGenerator\TraitType]) erstellen: ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Traits verwenden: +Eigenschaften verwenden: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ Ergebnis: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enums .[#toc-enums] ------------------- -Sie können die Enums, die PHP 8.1 mitbringt, ganz einfach erstellen: +Sie können ganz einfach die Enums erstellen, die PHP 8.1 mitbringt (Klasse [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// oder PsrPrinter für PSR-2 / PSR-12-konforme Ausgabe verwenden +// oder PsrPrinter für PSR-2 / PSR-12 / PER-konforme Ausgabe verwenden // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// oder PsrPrinter für PSR-2 / PSR-12-konforme Ausgabe verwenden +// oder PsrPrinter für PSR-2 / PSR-12 / PER-konforme Ausgabe verwenden // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Drucker und PSR-Konformität .[#toc-printers-and-psr-compliance] --------------------------------------------------------------- -PHP-Code wird von `Printer` Objekten erzeugt. Es gibt eine `PsrPrinter`, deren Ausgabe mit PSR-2 und PSR-12 konform ist und Leerzeichen für die Einrückung verwendet, und eine `Printer`, die Tabulatoren für die Einrückung verwendet. +Die Klasse [Printer |api:Nette\PhpGenerator\Printer] wird verwendet, um PHP-Code zu erzeugen: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // gleich wie: echo $class +``` + +Es kann Code für alle anderen Elemente erzeugen und bietet Methoden wie `printFunction()`, `printNamespace()`, etc. + +Zusätzlich steht die Klasse `PsrPrinter` zur Verfügung, deren Ausgabe dem PSR-2 / PSR-12 / PER-Kodierungsstil entspricht: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 Leerzeichen Einrückung +echo $printer->printClass($class); ``` -Möchten Sie das Verhalten des Druckers anpassen? Erstellen Sie Ihr eigenes, indem Sie die Klasse `Printer` erben. Sie können diese Variablen neu konfigurieren: +Möchten Sie das Verhalten an Ihre Bedürfnisse anpassen? Erstellen Sie Ihren eigenen Drucker, indem Sie von der Klasse `Printer` erben. Sie können diese Variablen neu konfigurieren: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // Länge der Zeile, nach der die Zeile umgebrochen werden soll public int $wrapLength = 120; + // Einrückungszeichen, kann durch eine Folge von Leerzeichen ersetzt werden public string $indentation = "\t"; + // Anzahl der Leerzeilen zwischen den Eigenschaften public int $linesBetweenProperties = 0; + // Anzahl der Leerzeilen zwischen Methoden public int $linesBetweenMethods = 2; + // Anzahl der Leerzeilen zwischen Gruppen von Verwendungsanweisungen für Klassen, Funktionen und Konstanten public int $linesBetweenUseTypes = 0; + // Position der öffnenden Klammer für Funktionen und Methoden public bool $bracesOnNextLine = true; + // Platzierung eines Parameters in einer Zeile, auch wenn er ein Attribut hat oder promotet wird + public bool $singleParameterOnOneLine = false; + // Trennzeichen zwischen der rechten Klammer und dem Rückgabetyp von Funktionen und Methoden public string $returnTypeColon = ': '; } ``` +Wie und warum genau unterscheiden sich der Standard `Printer` und `PsrPrinter`? Warum gibt es nicht nur einen Drucker, den `PsrPrinter`, in diesem Paket? + +Der Standard `Printer` formatiert den Code so, wie wir es in ganz Nette tun. Da Nette viel früher als PSR entstanden ist, und auch weil PSR viele Jahre lang keine Standards rechtzeitig geliefert hat, sondern manchmal sogar mit mehreren Jahren Verspätung nach der Einführung einer neuen Funktion in PHP, führte dies zu einigen kleinen Unterschieden im [Codierungsstandard |contributing:coding-standard]. +Der größte Unterschied ist die Verwendung von Tabulatoren anstelle von Leerzeichen. Wir wissen, dass wir durch die Verwendung von Tabulatoren in unseren Projekten eine Breitenanpassung ermöglichen, die [für Menschen mit Sehbehinderungen wichtig |contributing:coding-standard#Tabs Instead of Spaces] ist. +Ein Beispiel für einen geringfügigen Unterschied ist die Platzierung der geschweiften Klammer in einer separaten Zeile für Funktionen und Methoden und immer. Wir halten die PSR-Empfehlung für unlogisch und [führen zu einer Verringerung der Klarheit des Codes |contributing:coding-standard#Wrapping and Braces]. + Typen .[#toc-types] ------------------- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // wird zu A vereinfacht ->addTrait('Bar\AliasedClass'); // es wird zu AliasedClass vereinfacht $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // in Kommentaren manuell vereinfachen +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in Kommentaren manuell vereinfachen $method->addParameter('arg') ->setType('Bar\OtherClass'); // es wird in \Bar\OtherClass aufgelöst echo $namespace; -// oder verwenden Sie PsrPrinter für eine PSR-2 / PSR-12 konforme Ausgabe +// oder verwenden Sie PsrPrinter für eine PSR-2 / PSR-12 / PER konforme Ausgabe // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// oder PsrPrinter für PSR-2 / PSR-12 konforme Ausgabe verwenden +// oder PsrPrinter für PSR-2 / PSR-12 / PER konforme Ausgabe verwenden // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Laden aus PHP-Datei .[#toc-loading-from-php-file] ------------------------------------------------- -Sie können Klassen und Funktionen auch direkt aus einer PHP-Datei laden, die noch nicht geladen ist, oder aus einem String von PHP-Code: +Sie können auch Funktionen, Klassen, Schnittstellen und Enums direkt aus einem String von PHP-Code laden. Zum Beispiel erstellen wir das Objekt `ClassType` auf diese Weise: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Laden der gesamten PHP-Datei, die mehrere Klassen oder sogar mehrere Namespaces enthalten kann: +Beim Laden von Klassen aus PHP-Code werden einzeilige Kommentare außerhalb von Methodenkörpern ignoriert (z. B. für Eigenschaften usw.), da diese Bibliothek keine API hat, um mit ihnen zu arbeiten. + +Sie können auch die gesamte PHP-Datei direkt laden, die eine beliebige Anzahl von Klassen, Funktionen oder sogar mehrere Namespaces enthalten kann: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +Der anfängliche Dateikommentar und die Erklärung `strict_types` werden ebenfalls geladen. Alle anderen globalen Codes werden dagegen ignoriert. + Dazu muss `nikic/php-parser` installiert sein. +.[note] +Wenn Sie globalen Code in Dateien oder einzelne Anweisungen in Methodenkörpern manipulieren müssen, ist es besser, die Bibliothek `nikic/php-parser` direkt zu verwenden. + Variablen-Dumper .[#toc-variables-dumper] ----------------------------------------- diff --git a/php-generator/el/@home.texy b/php-generator/el/@home.texy index 310cb0a887..c25dd233f1 100644 --- a/php-generator/el/@home.texy +++ b/php-generator/el/@home.texy @@ -5,7 +5,7 @@ - Χρειάζεται να δημιουργήσετε κώδικα PHP για κλάσεις, συναρτήσεις, αρχεία PHP κ.λπ. - Υποστηρίζει όλα τα τελευταία χαρακτηριστικά της PHP, όπως enums, attributes κ.λπ. - Σας επιτρέπει να τροποποιείτε εύκολα τις υπάρχουσες κλάσεις -- Έξοδος συμβατή με το PSR-12 +- Έξοδος συμβατή με το PSR-12 / PER coding style - Εξαιρετικά ώριμη, σταθερή και ευρέως χρησιμοποιούμενη βιβλιοθήκη </div> @@ -52,7 +52,6 @@ echo $class; */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -67,7 +66,8 @@ echo $printer->printClass($class); ```php $class->addConstant('ID', 123) - ->setProtected() // συνεχής ορατότητα + ->setProtected() // σταθερή ορατότητα + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Δημιουργεί: ```php -final protected const ID = 123; +final protected const int ID = 123, /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Οι ιδιότητες μόνο για ανάγνωση που εισήχθησαν από την PHP 8.1 μπορούν να επισημανθούν μέσω του `setReadOnly()`. +Οι ιδιότητες και οι κλάσεις που είναι μόνο για ανάγνωση μπορούν να επισημανθούν μέσω της διεύθυνσης `setReadOnly()`. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Διεπαφή ή γνώρισμα .[#toc-interface-or-trait] --------------------------------------------- -Μπορείτε να δημιουργήσετε διεπαφές και γνωρίσματα: +Μπορείτε να δημιουργήσετε διασυνδέσεις και γνωρίσματα (κλάσεις [InterfaceType |api:Nette\PhpGenerator\InterfaceType] και [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Χρησιμοποιώντας γνωρίσματα: +Χρήση γνωρισμάτων: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ echo $class; class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enums .[#toc-enums] ------------------- -Μπορείτε εύκολα να δημιουργήσετε τα enums που φέρνει η PHP 8.1: +Μπορείτε εύκολα να δημιουργήσετε τα enums που φέρνει η PHP 8.1 (κλάση [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// ή χρησιμοποιήστε το PsrPrinter για έξοδο που συμμορφώνεται με PSR-2 / PSR-12 +// ή χρησιμοποιήστε το PsrPrinter για έξοδο που συμμορφώνεται με PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// ή χρησιμοποιήστε το PsrPrinter για έξοδο που συμμορφώνεται με PSR-2 / PSR-12 +// ή χρησιμοποιήστε το PsrPrinter για έξοδο που συμμορφώνεται με PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Εκτυπωτές και συμμόρφωση PSR .[#toc-printers-and-psr-compliance] ---------------------------------------------------------------- -Ο κώδικας PHP παράγεται από τα αντικείμενα `Printer`. Υπάρχει ένα `PsrPrinter` του οποίου η έξοδος συμμορφώνεται με τα PSR-2 και PSR-12 και χρησιμοποιεί κενά για εσοχή, και ένα `Printer` που χρησιμοποιεί tabs για εσοχή. +Η κλάση [Printer |api:Nette\PhpGenerator\Printer] χρησιμοποιείται για τη δημιουργία κώδικα PHP: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // το ίδιο όπως: echo $class +``` + +Μπορεί να παράγει κώδικα για όλα τα άλλα στοιχεία, προσφέροντας μεθόδους όπως `printFunction()`, `printNamespace()`, κ.λπ. + +Επιπλέον, είναι διαθέσιμη η κλάση `PsrPrinter`, της οποίας η έξοδος είναι σύμφωνη με το στυλ κωδικοποίησης PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 κενά εσοχή +echo $printer->printClass($class); ``` -Θέλετε να προσαρμόσετε τη συμπεριφορά του εκτυπωτή; Δημιουργήστε το δικό σας κληρονομώντας την κλάση `Printer`. Μπορείτε να επαναδιαμορφώσετε αυτές τις μεταβλητές: +Χρειάζεται να ρυθμίσετε τη συμπεριφορά σας ανάλογα με τις ανάγκες σας; Δημιουργήστε τον δικό σας εκτυπωτή κληρονομώντας την κλάση `Printer`. Μπορείτε να ρυθμίσετε εκ νέου αυτές τις μεταβλητές: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // μήκος της γραμμής μετά το οποίο θα γίνει η διακοπή της γραμμής public int $wrapLength = 120; + // χαρακτήρας εσοχής, μπορεί να αντικατασταθεί με μια ακολουθία διαστημάτων public string $indentation = "\t"; + // αριθμός κενών γραμμών μεταξύ των ιδιοτήτων public int $linesBetweenProperties = 0; + // αριθμός κενών γραμμών μεταξύ μεθόδων public int $linesBetweenMethods = 2; + // αριθμός κενών γραμμών μεταξύ ομάδων δηλώσεων χρήσης για κλάσεις, συναρτήσεις και σταθερές public int $linesBetweenUseTypes = 0; + // θέση της εισαγωγικής αγκύλης για συναρτήσεις και μεθόδους public bool $bracesOnNextLine = true; + // τοποθέτηση μιας παραμέτρου σε μια γραμμή, ακόμη και αν έχει ένα χαρακτηριστικό ή αν προωθείται + public bool $singleParameterOnOneLine = false; + // διαχωριστικό μεταξύ της δεξιάς παρένθεσης και του τύπου επιστροφής των συναρτήσεων και των μεθόδων public string $returnTypeColon = ': '; } ``` +Πώς και γιατί ακριβώς διαφέρουν τα πρότυπα `Printer` και `PsrPrinter`; Γιατί δεν υπάρχει μόνο ένας εκτυπωτής, ο `PsrPrinter`, στο πακέτο; + +Το πρότυπο `Printer` μορφοποιεί τον κώδικα όπως τον κάνουμε σε όλη τη Nette. Δεδομένου ότι η Nette δημιουργήθηκε πολύ νωρίτερα από το PSR, και επίσης επειδή το PSR για πολλά χρόνια δεν παρέδιδε τα πρότυπα εγκαίρως, αλλά μερικές φορές ακόμη και με καθυστέρηση αρκετών ετών από την εισαγωγή ενός νέου χαρακτηριστικού στην PHP, αυτό είχε ως αποτέλεσμα μερικές μικρές διαφορές στο [πρότυπο κωδικοποίησης |contributing:coding-standard]. +Η μεγαλύτερη διαφορά είναι απλώς η χρήση των tabs αντί των κενών. Γνωρίζουμε ότι με τη χρήση tabs στα έργα μας επιτρέπουμε την προσαρμογή του πλάτους, κάτι που είναι [απαραίτητο για τα άτομα με προβλήματα όρασης |contributing:coding-standard#Tabs Instead of Spaces]. +Ένα παράδειγμα μιας μικρής διαφοράς είναι η τοποθέτηση της καμπύλης αγκύλης σε ξεχωριστή γραμμή για τις συναρτήσεις και τις μεθόδους και πάντα. Θεωρούμε ότι η σύσταση του PSR είναι παράλογη και [οδηγεί σε μείωση της σαφήνειας του κώδικα |contributing:coding-standard#Wrapping and Braces]. + Τύποι .[#toc-types] ------------------- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // θα απλοποιηθεί σε A ->addTrait('Bar\AliasedClass'); // θα απλοποιηθεί σε AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // σε σχόλια απλοποιεί χειροκίνητα +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // στα σχόλια απλοποιήστε χειροκίνητα $method->addParameter('arg') ->setType('Bar\OtherClass'); // θα επιλυθεί σε \Bar\OtherClass echo $namespace; -// ή χρήση PsrPrinter για έξοδο σύμφωνη με PSR-2 / PSR-12 +// ή χρήση PsrPrinter για έξοδο σύμφωνη με PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// ή χρήση του PsrPrinter για έξοδο σύμφωνη με PSR-2 / PSR-12 +// ή χρήση του PsrPrinter για έξοδο σύμφωνη με PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Φορτώνοντας από αρχείο PHP .[#toc-loading-from-php-file] -------------------------------------------------------- -Μπορείτε επίσης να φορτώσετε κλάσεις και συναρτήσεις απευθείας από ένα αρχείο PHP που δεν είναι ήδη φορτωμένο ή μια συμβολοσειρά κώδικα PHP: +Μπορείτε επίσης να φορτώσετε συναρτήσεις, κλάσεις, διεπαφές και enums απευθείας από μια συμβολοσειρά κώδικα PHP. Για παράδειγμα, δημιουργούμε το αντικείμενο `ClassType` με αυτόν τον τρόπο: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Φόρτωση ολόκληρου του αρχείου PHP, το οποίο μπορεί να περιέχει πολλαπλές κλάσεις ή ακόμη και πολλαπλά namespaces: +Όταν φορτώνετε κλάσεις από κώδικα PHP, τα σχόλια μιας γραμμής εκτός των σωμάτων των μεθόδων αγνοούνται (π.χ. για τις ιδιότητες κ.λπ.), επειδή αυτή η βιβλιοθήκη δεν διαθέτει API για να δουλέψει με αυτά. + +Μπορείτε επίσης να φορτώσετε απευθείας ολόκληρο το αρχείο PHP, το οποίο μπορεί να περιέχει οποιονδήποτε αριθμό κλάσεων, συναρτήσεων ή ακόμα και πολλαπλά namespaces: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +Το αρχικό σχόλιο του αρχείου και η δήλωση `strict_types` φορτώνονται επίσης. Από την άλλη πλευρά, όλος ο υπόλοιπος παγκόσμιος κώδικας αγνοείται. + Αυτό απαιτεί την εγκατάσταση του `nikic/php-parser`. +.[note] +Αν πρέπει να χειριστείτε συνολικό κώδικα σε αρχεία ή μεμονωμένες εντολές σε σώματα μεθόδων, είναι προτιμότερο να χρησιμοποιήσετε απευθείας τη βιβλιοθήκη `nikic/php-parser`. + Μεταβλητές Dumper .[#toc-variables-dumper] ------------------------------------------ diff --git a/php-generator/en/@home.texy b/php-generator/en/@home.texy index aa94e5b57b..fa938e26cf 100644 --- a/php-generator/en/@home.texy +++ b/php-generator/en/@home.texy @@ -5,7 +5,7 @@ PHP Code Generator - Need to generate PHP code for classes, functions, PHP files, etc.? - Supports all the latest PHP features like enums, attributes, etc. - Allows you to easily modify existing classes -- PSR-12 compliant output +- PSR-12 / PER coding style compliant output - Highly mature, stable, and widely used library </div> @@ -52,7 +52,6 @@ It will render this result: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -67,7 +66,8 @@ We can add constants (class [Constant |api:Nette\PhpGenerator\Constant]) and pro ```php $class->addConstant('ID', 123) - ->setProtected() // constant visibility + ->setProtected() // constant visiblity + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') It generates: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Readonly properties introduced by PHP 8.1 can be marked via `setReadOnly()`. +Readonly properties and classes can be marked via `setReadOnly()`. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Interface or Trait ------------------ -You can create interfaces and traits: +You can create interfaces and traits (classes [InterfaceType |api:Nette\PhpGenerator\InterfaceType] and [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Using Traits: +Using traits: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ Result: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enums ----- -You can easily create the enums that PHP 8.1 brings: +You can easily create the enums that PHP 8.1 brings (class [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Printers and PSR Compliance --------------------------- -PHP code is generated by `Printer` objects. There is a `PsrPrinter` whose output conforms to PSR-2 and PSR-12 and uses spaces for indentation, and a `Printer` that uses tabs for indentation. +The [Printer |api:Nette\PhpGenerator\Printer] class is used to generate PHP code: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // same as: echo $class +``` + +It can generate code for all other elements, offering methods such as `printFunction()`, `printNamespace()`, etc. + +Additionally, the `PsrPrinter` class is available, whose output is in compliance with the PSR-2 / PSR-12 / PER coding style: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spaces indentation +echo $printer->printClass($class); ``` -Need to customize printer behavior? Create your own by inheriting the `Printer` class. You can reconfigure these variables: +Need to fine-tune behavior to your needs? Create your own printer by inheriting from the `Printer` class. You can reconfigure these variables: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // length of the line after which the line will break public int $wrapLength = 120; + // indentation character, can be replaced with a sequence of spaces public string $indentation = "\t"; + // number of blank lines between properties public int $linesBetweenProperties = 0; + // number of blank lines between methods public int $linesBetweenMethods = 2; + // number of blank lines between groups of use statements for classes, functions, and constants public int $linesBetweenUseTypes = 0; + // position of the opening brace for functions and methods public bool $bracesOnNextLine = true; + // place one parameter in one line, even if it has an attribute or is promoted + public bool $singleParameterOnOneLine = false; + // separator between the right parenthesis and return type of functions and methods public string $returnTypeColon = ': '; } ``` +How and why exactly does the standard `Printer` and `PsrPrinter` differ? Why isn't there just one printer, the `PsrPrinter`, in the package? + +The standard `Printer` formats the code as we do it in all of Nette. Since Nette was created much earlier than PSR, and also because PSR for many years did not deliver standards in time, but sometimes even with several years of delay from the introduction of a new feature in PHP, this resulted in a few minor differences in the [coding standard |contributing:coding-standard]. +The bigger difference is just the use of tabs instead of spaces. We know that by using tabs in our projects we allow for width adjustment, which is [essential for people with visual impairments |contributing:coding-standard#Tabs Instead of Spaces]. +An example of a minor difference is the placement of the curly brace on a separate line for functions and methods and always. We see the PSR recommendation as illogical and [leading to a decrease in code clarity |contributing:coding-standard#Wrapping and Braces]. + Types ----- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // it will simplify to A ->addTrait('Bar\AliasedClass'); // it will simplify to AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // in comments simplify manually +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // in comments simplify manually $method->addParameter('arg') ->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass echo $namespace; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Loading from PHP File --------------------- -You can also load classes and functions directly from a PHP file that is not already loaded or string of PHP code: +You can also load functions, classes, interfaces and enums directly from a string of PHP code. For example, we create `ClassType` object this way: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Loading the entire PHP file, which may contain multiple classes or even multiple namespaces: +When loading classes from PHP code, single line comments outside of method bodies are ignored (e.g. for properties, etc.) because this library does not have an API to work with them. + +You can also load the entire PHP file directly, which can contain any number of classes, functions or even multiple namespaces: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +The initial file comment and the `strict_types` declaration are also loaded. On the other hand, all other global code is ignored. + This requires `nikic/php-parser` to be installed. +.[note] +If you need to manipulate global code in files or individual statements in method bodies, it is better to use the `nikic/php-parser` library directly. + Variables Dumper ---------------- diff --git a/php-generator/es/@home.texy b/php-generator/es/@home.texy index 5e42b8b949..71e7a364b7 100644 --- a/php-generator/es/@home.texy +++ b/php-generator/es/@home.texy @@ -5,7 +5,7 @@ Generador de código PHP - ¿Necesita generar código PHP para clases, funciones, archivos PHP, etc.? - Soporta todas las últimas características de PHP como enums, atributos, etc. - Le permite modificar fácilmente las clases existentes -- Salida compatible con PSR-12 +- Salida compatible con PSR-12 / PER coding style - Librería altamente madura, estable y ampliamente utilizada </div> @@ -52,7 +52,6 @@ Dará este resultado: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -68,6 +67,7 @@ Podemos añadir constantes (clase [Constant |api:Nette\PhpGenerator\Constant]) y ```php $class->addConstant('ID', 123) ->setProtected() // visibilidad constante + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Genera: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Las propiedades de sólo lectura introducidas por PHP 8.1 se pueden marcar a través de `setReadOnly()`. +Las propiedades y clases de sólo lectura pueden marcarse a través de `setReadOnly()`. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Interfaz o Trait .[#toc-interface-or-trait] ------------------------------------------- -Puedes crear interfaces y traits: +Puede crear interfaces y traits (clases [InterfaceType |api:Nette\PhpGenerator\InterfaceType] y [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Usando Traits: +Utilizar rasgos: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ Resultado: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enums .[#toc-enums] ------------------- -Puedes crear fácilmente los enums que trae PHP 8.1: +Puedes crear fácilmente los enums que trae PHP 8.1 (clase [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// o utilice PsrPrinter para una salida conforme a PSR-2 / PSR-12 +// o utilice PsrPrinter para una salida conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// o utilice PsrPrinter para una salida conforme a PSR-2 / PSR-12 +// o utilice PsrPrinter para una salida conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Impresoras y cumplimiento del PSR .[#toc-printers-and-psr-compliance] --------------------------------------------------------------------- -El código PHP es generado por los objetos `Printer`. Existe un `PsrPrinter` cuya salida se ajusta a PSR-2 y PSR-12 y utiliza espacios para la sangría, y un `Printer` que utiliza tabuladores para la sangría. +La clase [Printer |api:Nette\PhpGenerator\Printer] se utiliza para generar código PHP: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // igual que: echo $clase +``` + +Puede generar código para todos los demás elementos, ofreciendo métodos como `printFunction()`, `printNamespace()`, etc. + +Además, está disponible la clase `PsrPrinter`, cuya salida se ajusta al estilo de codificación PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 espacios de sangría +echo $printer->printClass($class); ``` -¿Necesita personalizar el comportamiento de la impresora? Cree la suya propia heredando la clase `Printer`. Puedes reconfigurar estas variables: +¿Necesitas ajustar el comportamiento a tus necesidades? Cree su propia impresora heredando de la clase `Printer`. Puedes reconfigurar estas variables: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // longitud de la línea después de la cual la línea se romperá public int $wrapLength = 120; + // carácter de sangría, puede ser sustituido por una secuencia de espacios public string $indentation = "\t"; + // número de líneas en blanco entre propiedades public int $linesBetweenProperties = 0; + // número de líneas en blanco entre métodos public int $linesBetweenMethods = 2; + // número de líneas en blanco entre grupos de declaraciones de uso de clases, funciones y constantes public int $linesBetweenUseTypes = 0; + // posición de la llave de apertura para funciones y métodos public bool $bracesOnNextLine = true; + // colocar un parámetro en una línea, incluso si tiene un atributo o es promocionado + public bool $singleParameterOnOneLine = false; + // separador entre el paréntesis derecho y el tipo de retorno de funciones y métodos public string $returnTypeColon = ': '; } ``` +¿Cómo y por qué difieren exactamente el estándar `Printer` y `PsrPrinter`? ¿Por qué no hay sólo una impresora, la `PsrPrinter`, en el paquete? + +El estándar `Printer` formatea el código tal y como lo hacemos en todo Nette. Dado que Nette se creó mucho antes que PSR, y también porque PSR durante muchos años no entregó los estándares a tiempo, sino a veces incluso con varios años de retraso desde la introducción de una nueva característica en PHP, esto dio lugar a algunas pequeñas diferencias en el [estándar de codificación |contributing:coding-standard]. +La mayor diferencia es simplemente el uso de tabuladores en lugar de espacios. Sabemos que utilizando tabuladores en nuestros proyectos permitimos ajustar la anchura, lo que es [esencial para las personas con deficiencias visuales |contributing:coding-standard#Tabs Instead of Spaces]. +Un ejemplo de diferencia menor es la colocación de la llave rizada en una línea separada para funciones y métodos y siempre. Consideramos que la recomendación del PSR es ilógica y [conduce a una disminución de la claridad del código |contributing:coding-standard#Wrapping and Braces]. + Tipos .[#toc-types] ------------------- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // se simplificará a A ->addTrait('Bar\AliasedClass'); // simplificará a AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // en comentarios simplificar manualmente +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // en comentarios simplificar manualmente $method->addParameter('arg') ->setType('Bar\OtherClass'); // se resolverá a \Bar\OtherClass echo $namespace; -// o utilizar PsrPrinter para una salida conforme a PSR-2 / PSR-12 +// o utilizar PsrPrinter para una salida conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// o utilice PsrPrinter para una salida conforme a PSR-2 / PSR-12 +// o utilice PsrPrinter para una salida conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Carga desde archivo PHP .[#toc-loading-from-php-file] ----------------------------------------------------- -También puede cargar clases y funciones directamente desde un archivo PHP que no esté ya cargado o una cadena de código PHP: +También puede cargar funciones, clases, interfaces y enums directamente desde una cadena de código PHP. Por ejemplo, creamos el objeto `ClassType` de esta manera: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Cargar el archivo PHP completo, que puede contener múltiples clases o incluso múltiples espacios de nombres: +Cuando se cargan clases desde código PHP, se ignoran los comentarios de una sola línea fuera de los cuerpos de los métodos (por ejemplo, para propiedades, etc.) porque esta biblioteca no tiene una API para trabajar con ellos. + +También puede cargar directamente el archivo PHP completo, que puede contener cualquier número de clases, funciones o incluso múltiples espacios de nombres: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +También se cargan el comentario inicial del archivo y la declaración `strict_types`. En cambio, el resto del código global se ignora. + Esto requiere que `nikic/php-parser` esté instalado. +.[note] +Si necesita manipular código global en archivos o sentencias individuales en cuerpos de métodos, es mejor utilizar directamente la biblioteca `nikic/php-parser`. + Volquete de variables .[#toc-variables-dumper] ---------------------------------------------- diff --git a/php-generator/fr/@home.texy b/php-generator/fr/@home.texy index 8b11395f03..f76a242a00 100644 --- a/php-generator/fr/@home.texy +++ b/php-generator/fr/@home.texy @@ -5,7 +5,7 @@ Générateur de code PHP - Vous avez besoin de générer du code PHP pour des classes, des fonctions, des fichiers PHP, etc. - Supporte toutes les dernières fonctionnalités de PHP comme les enums, les attributs, etc. - Vous permet de modifier facilement les classes existantes -- Sortie conforme à PSR-12 +- Sortie conforme à PSR-12 / PER coding style - Bibliothèque très mature, stable et largement utilisée. </div> @@ -52,7 +52,6 @@ Cela donnera le résultat suivant : */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -68,6 +67,7 @@ Nous pouvons ajouter des constantes (classe [Constant |api:Nette\PhpGenerator\Co ```php $class->addConstant('ID', 123) ->setProtected() // visibilité constante + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Il génère : ```php -final protected const ID = 123; +final protected const int ID = 123 ; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Les propriétés en lecture seule introduites par PHP 8.1 peuvent être marquées via `setReadOnly()`. +Les propriétés et les classes en lecture seule peuvent être marquées via `setReadOnly()`. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Interface ou Trait .[#toc-interface-or-trait] --------------------------------------------- -Vous pouvez créer des interfaces et des traits : +Vous pouvez créer des interfaces et des traits (classes [InterfaceType |api:Nette\PhpGenerator\InterfaceType] et [TraitType |api:Nette\PhpGenerator\TraitType]) : ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Utilisation des traits : +Utilisation des traits de caractère : ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ Résultat : class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enums .[#toc-enums] ------------------- -Vous pouvez facilement créer les enums que PHP 8.1 apporte : +Vous pouvez facilement créer les enums que PHP 8.1 apporte (classe [EnumType |api:Nette\PhpGenerator\EnumType]) : ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 +// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 +// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -467,34 +468,58 @@ function foo($a) ``` -Imprimantes et conformité aux RPS .[#toc-printers-and-psr-compliance] +Imprimantes et conformité aux PSR .[#toc-printers-and-psr-compliance] --------------------------------------------------------------------- -Le code PHP est généré par les objets `Printer`. Il existe un `PsrPrinter` dont la sortie est conforme à PSR-2 et PSR-12 et qui utilise des espaces pour l'indentation, et un `Printer` qui utilise des tabulations pour l'indentation. +La classe [Printer |api:Nette\PhpGenerator\Printer] est utilisée pour générer du code PHP : ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // same as : echo $class +``` + +Il peut générer du code pour tous les autres éléments, en proposant des méthodes telles que `printFunction()`, `printNamespace()`, etc. + +En outre, la classe `PsrPrinter` est disponible, dont la sortie est conforme au style de codage PSR-2 / PSR-12 / PER : + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // Retrait de 4 espaces +echo $printer->printClass($class); ``` -Vous avez besoin de personnaliser le comportement de l'imprimante ? Créez le vôtre en héritant de la classe `Printer`. Vous pouvez reconfigurer ces variables : +Vous souhaitez adapter le comportement de votre imprimante à vos besoins ? Créez votre propre imprimante en héritant de la classe `Printer`. Vous pouvez reconfigurer ces variables : ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // longueur de la ligne après laquelle la ligne sera coupée public int $wrapLength = 120; + // caractère d'indentation, peut être remplacé par une séquence d'espaces public string $indentation = "\t"; + // nombre de lignes vides entre les propriétés public int $linesBetweenProperties = 0; + // nombre de lignes vides entre les méthodes public int $linesBetweenMethods = 2; + // nombre de lignes vides entre les groupes de déclarations d'utilisation pour les classes, les fonctions et les constantes public int $linesBetweenUseTypes = 0; + // position de l'accolade d'ouverture pour les fonctions et les méthodes public bool $bracesOnNextLine = true; + // placer un seul paramètre sur une seule ligne, même s'il possède un attribut ou est promu + public bool $singleParameterOnOneLine = false; + // séparateur entre la parenthèse droite et le type de retour des fonctions et méthodes public string $returnTypeColon = ': '; } ``` +En quoi et pourquoi la norme `Printer` et `PsrPrinter` diffère-t-elle exactement ? Pourquoi n'y a-t-il pas qu'une seule imprimante, la `PsrPrinter`, dans le paquet ? + +La norme `Printer` formate le code comme nous le faisons dans l'ensemble de Nette. Comme Nette a été créé bien avant PSR, et aussi parce que PSR pendant de nombreuses années n'a pas fourni de normes à temps, mais parfois même avec plusieurs années de retard par rapport à l'introduction d'une nouvelle fonctionnalité dans PHP, cela a entraîné quelques différences mineures dans la [norme de codage |contributing:coding-standard]. +La différence la plus importante est l'utilisation de tabulations au lieu d'espaces. Nous savons qu'en utilisant des tabulations dans nos projets, nous permettons d'ajuster la largeur, ce qui est [essentiel pour les personnes souffrant de déficiences visuelles |contributing:coding-standard#Tabs Instead of Spaces]. +Un exemple de différence mineure est le placement de l'accolade sur une ligne séparée pour les fonctions et les méthodes et toujours. Nous considérons que la recommandation du PSR est illogique et qu'elle conduit à une [diminution de la clarté du code |contributing:coding-standard#Wrapping and Braces]. + Types .[#toc-types] ------------------- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // il sera simplifié en A ->addTrait('Bar\AliasedClass'); // il sera simplifié en AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // dans les commentaires, simplifier manuellement +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')) ; // dans les commentaires simplifier manuellement $method->addParameter('arg') ->setType('Bar\OtherClass'); // elle sera résolue en \Bar\OtherClass echo $namespace; -// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 +// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 +// ou utiliser PsrPrinter pour une sortie conforme à PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Chargement depuis le fichier PHP .[#toc-loading-from-php-file] -------------------------------------------------------------- -Vous pouvez également charger des classes et des fonctions directement depuis un fichier PHP qui n'est pas déjà chargé ou une chaîne de code PHP : +Vous pouvez également charger des fonctions, des classes, des interfaces et des enums directement à partir d'une chaîne de code PHP. Par exemple, nous créons l'objet `ClassType` de cette manière : ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Chargement de l'intégralité du fichier PHP, qui peut contenir plusieurs classes ou même plusieurs espaces de noms : +Lors du chargement de classes à partir de code PHP, les commentaires d'une seule ligne en dehors du corps des méthodes sont ignorés (par exemple pour les propriétés, etc.) car cette bibliothèque ne dispose pas d'une API pour les gérer. + +Vous pouvez également charger directement le fichier PHP entier, qui peut contenir n'importe quel nombre de classes, de fonctions ou même plusieurs espaces de noms : ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +Le commentaire initial du fichier et la déclaration `strict_types` sont également chargés. En revanche, tout autre code global est ignoré. + Cela nécessite l'installation de `nikic/php-parser`. +.[note] +Si vous devez manipuler du code global dans des fichiers ou des instructions individuelles dans des corps de méthodes, il est préférable d'utiliser directement la bibliothèque `nikic/php-parser`. + Dumper de variables .[#toc-variables-dumper] -------------------------------------------- diff --git a/php-generator/hu/@home.texy b/php-generator/hu/@home.texy index 8a2329cbe6..83133c12a4 100644 --- a/php-generator/hu/@home.texy +++ b/php-generator/hu/@home.texy @@ -5,7 +5,7 @@ PHP kód generátor - PHP kódot szeretne generálni osztályokhoz, függvényekhez, PHP fájlokhoz stb.? - Támogatja az összes legújabb PHP funkciót, mint például az enumokat, attribútumokat stb. - Lehetővé teszi a meglévő osztályok egyszerű módosítását -- PSR-12 szabványnak megfelelő kimenet +- PSR-12 / PER coding style szabványnak megfelelő kimenet - Rendkívül kiforrott, stabil és széles körben használt könyvtár </div> @@ -52,7 +52,6 @@ A következő eredményt adja ki: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -67,7 +66,8 @@ Konstanciákat (class [Constant |api:Nette\PhpGenerator\Constant]) és tulajdons ```php $class->addConstant('ID', 123) - ->setProtected() // állandó láthatóság + ->setProtected() // konstans láthatóság + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Ez generálja: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -A PHP 8.1 által bevezetett csak olvasható tulajdonságok a `setReadOnly()` oldalon keresztül jelölhetők. +A csak olvasható tulajdonságok és osztályok a `setReadOnly()` címen keresztül jelölhetők meg. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Interface vagy Trait .[#toc-interface-or-trait] ----------------------------------------------- -Létrehozhat interfészeket és vonásokat: +Interfészeket és tulajdonságokat hozhat létre ( [InterfaceType |api:Nette\PhpGenerator\InterfaceType] és [TraitType |api:Nette\PhpGenerator\TraitType] osztályok): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Traits használata: +Vonások használata: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ Eredmény: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enums .[#toc-enums] ------------------- -A PHP 8.1 által kínált enumokat könnyen létrehozhatod: +A PHP 8.1 által hozott enumokat ( [EnumType |api:Nette\PhpGenerator\EnumType] osztály) könnyen létrehozhatod: ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// vagy használja a PsrPrintert a PSR-2 / PSR-12 szabványnak megfelelő kimenethez. +// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szabványnak megfelelő kimenethez. // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// vagy használja a PsrPrintert a PSR-2 / PSR-12 szabványnak megfelelő kimenethez. +// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szabványnak megfelelő kimenethez. // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Nyomtatók és PSR-megfelelőség .[#toc-printers-and-psr-compliance] ----------------------------------------------------------------- -A PHP-kódot a `Printer` objektumok generálják. Létezik egy `PsrPrinter`, amelynek kimenete megfelel a PSR-2 és PSR-12 szabványoknak, és a behúzásnál szóközöket használ, valamint egy `Printer`, amely a behúzásnál tabulátorokat használ. +A [Printer |api:Nette\PhpGenerator\Printer] osztály PHP kód generálására szolgál: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // ugyanaz, mint: echo $class +``` + +Minden más elemhez képes kódot generálni, és olyan módszereket kínál, mint a `printFunction()`, `printNamespace()`, stb. + +Ezenkívül rendelkezésre áll a `PsrPrinter` osztály, amelynek kimenete megfelel a PSR-2 / PSR-12 / PER kódolási stílusnak: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 szóköz behúzás +echo $printer->printClass($class); ``` -Testreszabni szeretné a nyomtató viselkedését? Hozzon létre sajátot a `Printer` osztály öröklésével. Ezeket a változókat átkonfigurálhatja: +Szüksége van a viselkedés finomhangolására az Ön igényei szerint? Hozzon létre saját nyomtatót a `Printer` osztály öröklésével. Ezeket a változókat átkonfigurálhatja: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // a sor hossza, amely után a sor megszakad. public int $wrapLength = 120; + // behúzás karakter, helyettesíthető szóközökkel public string $indentation = "\t"; + // a tulajdonságok közötti üres sorok száma public int $linesBetweenProperties = 0; + // üres sorok száma a metódusok között public int $linesBetweenMethods = 2; + // üres sorok száma az osztályok, függvények és konstansok használati utasításcsoportjai között public int $linesBetweenUseTypes = 0; + // a nyitó zárójel pozíciója függvények és metódusok esetén public bool $bracesOnNextLine = true; + // egy paramétert egy sorba helyezünk, még akkor is, ha attribútummal rendelkezik vagy előléptetve van. + public bool $singleParameterOnOneLine = false; + // elválasztó a függvények és metódusok jobb oldali zárójel és a visszatérési típus között. public string $returnTypeColon = ': '; } ``` +Miben és miért különbözik pontosan a szabványos `Printer` és a `PsrPrinter`? Miért nincs csak egy nyomtató, a `PsrPrinter`, a csomagban? + +A szabványos `Printer` úgy formázza a kódot, ahogyan azt az egész Nette-ben tesszük. Mivel a Nette sokkal korábban készült, mint a PSR, és mivel a PSR sok éven át nem időben, hanem néha akár több éves késéssel szállította a szabványokat egy-egy új funkció bevezetésétől a PHP-ben, ez néhány kisebb eltérést eredményezett a [kódolási szabványban |contributing:coding-standard]. +A nagyobb különbség csupán a tabulátorok használata a szóközök helyett. Tudjuk, hogy a tabulátorok használatával a projektjeinkben lehetővé tesszük a szélesség beállítását, ami a [látássérült emberek számára elengedhetetlen |contributing:coding-standard#Tabs Instead of Spaces]. +Egy példa a kisebb különbségre a függvények és metódusok esetében a görbe zárójel külön sorba helyezése és mindig. A PSR ajánlását logikátlannak látjuk, és a [kód áttekinthetőségének csökkenéséhez vezet |contributing:coding-standard#Wrapping and Braces]. + Típusok .[#toc-types] --------------------- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // egyszerűsödik A ->addTrait('Bar\AliasedClass'); // AliasedClass-ra fog egyszerűsödni $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // a megjegyzésekben kézzel egyszerűsít +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // a megjegyzésekben manuálisan egyszerűsítünk $method->addParameter('arg') ->setType('Bar\OtherClass'); // feloldódik \Bar\OtherClass-ra echo $namespace; -// vagy használja a PsrPrintert a PSR-2 / PSR-12 szabványnak megfelelő kimenethez. +// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szabványnak megfelelő kimenethez. // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// vagy használja a PsrPrintert a PSR-2 / PSR-12 szabványnak megfelelő kimenethez. +// vagy használja a PsrPrintert a PSR-2 / PSR-12 / PER szabványnak megfelelő kimenethez. // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Betöltés PHP fájlból .[#toc-loading-from-php-file] -------------------------------------------------- -Az osztályokat és függvényeket közvetlenül egy még nem betöltött PHP-fájlból vagy PHP-kódból álló karakterláncból is betöltheti: +A függvényeket, osztályokat, interfészeket és enumokat közvetlenül egy PHP kódsorozatból is betöltheti. Például így hozzuk létre a `ClassType` objektumot: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -A teljes PHP-fájl betöltése, amely több osztályt vagy akár több névteret is tartalmazhat: +Az osztályok PHP kódból történő betöltésekor a metódus testén kívüli egysoros megjegyzéseket figyelmen kívül hagyjuk (pl. tulajdonságok stb. esetén), mivel ez a könyvtár nem rendelkezik API-val ezek kezelésére. + +A teljes PHP-fájlt közvetlenül is betöltheti, amely tetszőleges számú osztályt, függvényt vagy akár több névteret is tartalmazhat: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +A kezdeti fájlkommentár és a `strict_types` nyilatkozat is betöltődik. Másrészt minden más globális kódot figyelmen kívül hagyunk. + Ehhez telepíteni kell a `nikic/php-parser` oldalt. +.[note] +Ha fájlokban lévő globális kódot vagy a metódusok testében lévő egyes utasításokat kell manipulálnia, jobb, ha közvetlenül a `nikic/php-parser` könyvtárat használja. + Változók Dumper .[#toc-variables-dumper] ---------------------------------------- diff --git a/php-generator/it/@home.texy b/php-generator/it/@home.texy index 7d17ef178f..195ae382ef 100644 --- a/php-generator/it/@home.texy +++ b/php-generator/it/@home.texy @@ -5,7 +5,7 @@ Generatore di codice PHP - Avete bisogno di generare codice PHP per classi, funzioni, file PHP e così via? - Supporta tutte le più recenti caratteristiche di PHP, come enum, attributi, ecc. - Permette di modificare facilmente le classi esistenti -- Output conforme a PSR-12 +- Output conforme a PSR-12 / PER coding style - Libreria altamente matura, stabile e ampiamente utilizzata </div> @@ -52,7 +52,6 @@ Il risultato sarà questo: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -67,7 +66,8 @@ Possiamo aggiungere costanti (classe [Constant |api:Nette\PhpGenerator\Constant] ```php $class->addConstant('ID', 123) - ->setProtected() // visibilità costante + ->setProtected() // visiblità costante + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Genera: ```php -final protected const ID = 123; +finale protetto const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Le proprietà di sola lettura introdotte da PHP 8.1 possono essere contrassegnate tramite `setReadOnly()`. +Le proprietà e le classi di sola lettura possono essere contrassegnate tramite `setReadOnly()`. ------ @@ -170,7 +170,7 @@ $class->addMember($methodRecount); Interfaccia o tratto .[#toc-interface-or-trait] ----------------------------------------------- -È possibile creare interfacce e tratti: +È possibile creare interfacce e tratti (classi [InterfaceType |api:Nette\PhpGenerator\InterfaceType] e [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); @@ -194,6 +194,7 @@ Risultato: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enum .[#toc-enums] ------------------ -È possibile creare facilmente gli enum che PHP 8.1 introduce: +È possibile creare facilmente gli enum introdotti da PHP 8.1 (classe [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// oppure utilizzare PsrPrinter per ottenere un output conforme a PSR-2 / PSR-12 +// oppure utilizzare PsrPrinter per ottenere un output conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// oppure utilizzare PsrPrinter per un output conforme a PSR-2 / PSR-12 +// oppure utilizzare PsrPrinter per un output conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Stampanti e conformità PSR .[#toc-printers-and-psr-compliance] -------------------------------------------------------------- -Il codice PHP è generato dagli oggetti `Printer`. Esiste un `PsrPrinter` il cui output è conforme a PSR-2 e PSR-12 e utilizza gli spazi per l'indentazione e un `Printer` che utilizza le tabulazioni per l'indentazione. +La classe [Printer |api:Nette\PhpGenerator\Printer] viene utilizzata per generare codice PHP: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // come: echo $class +``` + +Può generare codice per tutti gli altri elementi, offrendo metodi come `printFunction()`, `printNamespace()`, ecc. + +Inoltre, è disponibile la classe `PsrPrinter`, il cui output è conforme allo stile di codifica PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spazi di rientro +echo $printer->printClass($class); ``` -Avete bisogno di personalizzare il comportamento della stampante? Createne una vostra ereditando la classe `Printer`. È possibile riconfigurare queste variabili: +Avete bisogno di regolare il comportamento in base alle vostre esigenze? Create la vostra stampante ereditando dalla classe `Printer`. È possibile riconfigurare queste variabili: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // lunghezza della riga dopo la quale la riga si interromperà public int $wrapLength = 120; + // carattere di indentazione, può essere sostituito da una sequenza di spazi public string $indentation = "\t"; + // numero di righe vuote tra le proprietà public int $linesBetweenProperties = 0; + // numero di righe vuote tra i metodi public int $linesBetweenMethods = 2; + // numero di righe vuote tra gruppi di dichiarazioni d'uso per classi, funzioni e costanti public int $linesBetweenUseTypes = 0; + // posizione della parentesi graffa di apertura per funzioni e metodi public bool $bracesOnNextLine = true; + // inserire un parametro in una sola riga, anche se ha un attributo o è promosso + public bool $singleParameterOnOneLine = false; + // separatore tra la parentesi destra e il tipo di ritorno di funzioni e metodi public string $returnTypeColon = ': '; } ``` +Come e perché differiscono esattamente le stampanti standard `Printer` e `PsrPrinter`? Perché non c'è una sola stampante, `PsrPrinter`, nel pacchetto? + +Lo standard `Printer` formatta il codice come lo facciamo in tutto Nette. Poiché Nette è stato creato molto prima di PSR, e anche perché PSR per molti anni non ha consegnato gli standard in tempo, ma a volte anche con diversi anni di ritardo dall'introduzione di una nuova funzionalità in PHP, questo ha portato ad alcune piccole differenze nello [standard di codifica |contributing:coding-standard]. +La differenza maggiore è solo l'uso dei tabulatori al posto degli spazi. Sappiamo che l'uso delle tabulazioni nei nostri progetti consente di regolare la larghezza, il che è [essenziale per le persone con problemi di vista |contributing:coding-standard#Tabs Instead of Spaces]. +Un esempio di differenza minore è il posizionamento della parentesi graffa su una riga separata per funzioni e metodi e sempre. Riteniamo che la raccomandazione del PSR sia illogica e [porti a una diminuzione della chiarezza del codice |contributing:coding-standard#Wrapping and Braces]. + Tipi .[#toc-types] ------------------ @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // si semplificherà in A ->addTrait('Bar\AliasedClass'); // semplificherà in AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // nei commenti semplificare manualmente +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // nei commenti semplifica manualmente $method->addParameter('arg') ->setType('Bar\OtherClass'); // si risolverà in \Bar\OtherClass echo $namespace; -// o usare PsrPrinter per un output conforme a PSR-2 / PSR-12 +// o usare PsrPrinter per un output conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// oppure utilizzare PsrPrinter per un output conforme a PSR-2 / PSR-12 +// oppure utilizzare PsrPrinter per un output conforme a PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Caricamento da file PHP .[#toc-loading-from-php-file] ----------------------------------------------------- -È anche possibile caricare classi e funzioni direttamente da un file PHP non ancora caricato o da una stringa di codice PHP: +È anche possibile caricare funzioni, classi, interfacce ed enum direttamente da una stringa di codice PHP. Ad esempio, creiamo l'oggetto `ClassType` in questo modo: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Caricare l'intero file PHP, che può contenere più classi o anche più spazi dei nomi: +Quando si caricano le classi dal codice PHP, i commenti di una sola riga al di fuori dei corpi dei metodi vengono ignorati (ad esempio per le proprietà, ecc.), perché questa libreria non dispone di un'API per gestirli. + +È anche possibile caricare direttamente l'intero file PHP, che può contenere un numero qualsiasi di classi, funzioni o anche spazi dei nomi multipli: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +Vengono caricati anche il commento iniziale del file e la dichiarazione `strict_types`. D'altra parte, tutto il resto del codice globale viene ignorato. + Questo richiede l'installazione di `nikic/php-parser`. +.[note] +Se è necessario manipolare il codice globale nei file o le singole dichiarazioni nei corpi dei metodi, è meglio usare direttamente la libreria `nikic/php-parser`. + Dumper di variabili .[#toc-variables-dumper] -------------------------------------------- diff --git a/php-generator/ja/@home.texy b/php-generator/ja/@home.texy index 0effdc30a7..847e02934c 100644 --- a/php-generator/ja/@home.texy +++ b/php-generator/ja/@home.texy @@ -52,7 +52,6 @@ echo $class; */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -67,7 +66,8 @@ echo $printer->printClass($class); ```php $class->addConstant('ID', 123) - ->setProtected() // constant visibility + ->setProtected() // 可視性を一定にする。 + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') 生成されます。 ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -PHP 8.1 で導入された Readonly プロパティは、`setReadOnly()` でマークすることができます。 +Readonlyプロパティやクラスは、`setReadOnly()` を使ってマークすることができます。 ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); インターフェイスまたはトレイト .[#toc-interface-or-trait] ------------------------------------------ -インターフェースとトレイトを作成することができます。 +インターフェースや特性(クラス[InterfaceType |api:Nette\PhpGenerator\InterfaceType]、[TraitType |api:Nette\PhpGenerator\TraitType])を作成することができます: ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Traitsを使用する。 +特性を利用する: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ echo $class; class Demo { use SmartObject; + /** @use MyTrait<Foo> */。 use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo 列挙 .[#toc-enums] ---------------- -PHP 8.1 で導入された enum を簡単に作成することができます。 +PHP 8.1がもたらすenum(クラス[EnumType |api:Nette\PhpGenerator\EnumType])を簡単に作成することができます: ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) プリンターとPSRの適合性 .[#toc-printers-and-psr-compliance] ------------------------------------------------- -PHPのコードは、`Printer` オブジェクトによって生成されます。出力が PSR-2 および PSR-12 に準拠し、インデントにスペースを使用する`PsrPrinter` と、インデントにタブを使用する`Printer` が用意されています。 +[Printer |api:Nette\PhpGenerator\Printer]クラスは、PHPコードを生成するために使用されます: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // same as: echo $class +``` + +`printFunction()`,`printNamespace()` などのメソッドを提供し、他のすべての要素のコードを生成することができます。 + +さらに、PSR-2 / PSR-12 / PERのコーディングスタイルに準拠した出力が可能な`PsrPrinter` クラスが用意されています: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spaces indentation +echo $printer->printClass($class); ``` -プリンタの動作をカスタマイズする必要がありますか?`Printer` クラスを継承して、独自のものを作成してください。これらの変数を再設定することができます。 +ニーズに合わせて動作を細かく調整する必要がある?`Printer` クラスを継承して、独自のプリンターを作成します。これらの変数を再設定することができます: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // 改行される行の長さ public int $wrapLength = 120; + // インデント文字、空白文字で置き換えることができます。 public string $indentation = "\t"; + // プロパティ間の空白行数 public int $linesBetweenProperties = 0; + // メソッド間の空白行数 public int $linesBetweenMethods = 2; + // クラス、関数、定数の使用 文のグループ間の空白行数 public int $linesBetweenUseTypes = 0; + // 関数とメソッドの開始波括弧の位置 public bool $bracesOnNextLine = true; + // パラメータが属性を持っていたり、昇格していても、1つのパラメータを1行に配置する。 + public bool $singleParameterOnOneLine = false; + // メソッドの右括弧と戻り値の関数との間の区切り文字 public string $returnTypeColon = ': '; } ``` +標準の`Printer` と`PsrPrinter` は、具体的にどのように、なぜ違うのでしょうか?なぜ、パッケージには`PsrPrinter` という1つのプリンターしかないのでしょうか? + +標準の`Printer` は、私たちがすべての Nette で行っているようにコードをフォーマットしています。Nette は PSR よりもずっと前に作られたものであり、また PSR は何年も前から標準規格が間に合わず、時には PHP の新機能の導入から数年遅れることもあったため、結果として[コーディング標準に |contributing:en:coding-standard]いくつかの小さな違いが生じました。 +より大きな違いは、スペースの代わりにタブを使用することだけです。私たちは、プロジェクトでタブを使用することで、[視覚障害者にとって必要 |contributing:en:coding-standard#Tabs Instead of Spaces]不可欠な幅の調整を可能にすることを知っています。 +細かい違いの例としては、関数やメソッドと常に別の行に中括弧を配置することが挙げられます。私たちは、PSRの勧告が非論理的であり、[コードの明瞭 |contributing:en:coding-standard#Wrapping and Braces]性を低下させるものと考えています。 + タイプ .[#toc-types] ----------------- @@ -652,14 +677,14 @@ $class = $namespace->addClass('Demo'); $class->addImplement('Foo\A') // it will simplify to A ->addTrait('Bar\AliasedClass'); // it will simplify to AliasedClass -$method = $class->addMethod('method'); +メソッド->addComment('@return ' . $namespace->simplifyType('FooD')); // コメントで手動で簡略化する $method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // in comments simplify manually $method->addParameter('arg') ->setType('Bar\OtherClass'); // it will resolve to \Bar\OtherClass echo $namespace; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// or use PsrPrinter for output conforming to PSR-2 / PSR-12 +// or use PsrPrinter for output conforming to PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` diff --git a/php-generator/pl/@home.texy b/php-generator/pl/@home.texy index 58ac15a159..c263841ce5 100644 --- a/php-generator/pl/@home.texy +++ b/php-generator/pl/@home.texy @@ -5,7 +5,7 @@ Generator kodu PHP - Potrzebujesz wygenerować kod PHP dla klas, funkcji, plików PHP, itp. - Obsługuje wszystkie najnowsze funkcje PHP, takie jak enumy, atrybuty, itp. - Pozwala na łatwą modyfikację istniejących klas -- Wyjście zgodne z PSR-12 +- Wyjście zgodne z PSR-12 / PER coding style - Wysoce dojrzała, stabilna i szeroko stosowana biblioteka </div> @@ -52,7 +52,6 @@ Wyrenderuje on taki wynik: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -68,6 +67,7 @@ Możemy dodać stałe (klasa [Constant |api:Nette\PhpGenerator\Constant]) i wła ```php $class->addConstant('ID', 123) ->setProtected() // stała widoczność + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Generuje: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Właściwości readonly wprowadzone przez PHP 8.1 mogą być oznaczone poprzez `setReadOnly()`. +Właściwości i klasy readonly mogą być oznaczone poprzez `setReadOnly()`. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Interface lub Trait .[#toc-interface-or-trait] ---------------------------------------------- -Możesz tworzyć interfejsy i cechy: +Można tworzyć interfejsy i cechy (klasy [InterfaceType |api:Nette\PhpGenerator\InterfaceType] i [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Używanie Cech: +Wykorzystanie cech: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ Wynik: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enums .[#toc-enums] ------------------- -Możesz łatwo stworzyć enumy, które przynosi PHP 8.1: +Możesz łatwo stworzyć enum, które przynosi PHP 8.1 (klasa [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 +// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 +// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Drukarki i zgodność z PSR .[#toc-printers-and-psr-compliance] ------------------------------------------------------------- -Kod PHP jest generowany przez obiekty `Printer`. Istnieje `PsrPrinter`, którego wyjście jest zgodne z PSR-2 i PSR-12 i używa spacji do wcięć, oraz `Printer`, który używa tabulacji do wcięć. +Klasa [Printer |api:Nette\PhpGenerator\Printer] służy do generowania kodu PHP: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // to samo co: echo $class +``` + +Może generować kod dla wszystkich innych elementów, oferując metody takie jak `printFunction()`, `printNamespace()`, itp. + +Dodatkowo dostępna jest klasa `PsrPrinter`, której dane wyjściowe są zgodne ze stylem kodowania PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spacje wcięcie +echo $printer->printClass($class); ``` -Potrzebujesz dostosować zachowanie drukarki? Utwórz własne, dziedzicząc po klasie `Printer`. Możesz przekonfigurować te zmienne: +Chcesz dostosować zachowanie do swoich potrzeb? Utwórz własną drukarkę, dziedzicząc z klasy `Printer`. Możesz ponownie skonfigurować te zmienne: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // długość linii, po której linia zostanie przerwana public int $wrapLength = 120; + // znak wcięcia, może być zastąpiony ciągiem spacji public string $indentation = "\t"; + // liczba pustych linii między właściwościami public int $linesBetweenProperties = 0; + // liczba pustych linii między metodami public int $linesBetweenMethods = 2; + // liczba pustych linii między grupami instrukcji użycia dla klas, funkcji i stałych public int $linesBetweenUseTypes = 0; + // pozycja nawiasu klamrowego otwierającego dla funkcji i metod public bool $bracesOnNextLine = true; + // umieszczenie jednego parametru w jednej linii, nawet jeśli ma atrybut lub jest promowany + public bool $singleParameterOnOneLine = false; + // separator między prawym nawiasem a typem zwracanej funkcji i metody public string $returnTypeColon = ': '; } ``` +Czym i dlaczego dokładnie różnią się standardowe `Printer` i `PsrPrinter`? Dlaczego w pakiecie nie ma tylko jednej drukarki, `PsrPrinter`? + +Standard `Printer` formatuje kod tak, jak robimy to we wszystkich Nette. Ponieważ Nette powstało znacznie wcześniej niż PSR, a także dlatego, że PSR przez wiele lat nie dostarczało standardów na czas, ale czasami nawet z kilkuletnim opóźnieniem od wprowadzenia nowej funkcji w PHP, spowodowało to kilka drobnych różnic w [standardzie kodowania |contributing:coding-standard]. +Większą różnicą jest właśnie używanie tabulatorów zamiast spacji. Wiemy, że stosując tabulatory w naszych projektach umożliwiamy dostosowanie szerokości, co jest [istotne dla osób z wadami wzroku |contributing:coding-standard#Tabs Instead of Spaces]. +Przykładem drobnej różnicy jest umieszczenie nawiasu klamrowego w osobnej linii dla funkcji i metod oraz zawsze. Uważamy, że zalecenie PSR jest nielogiczne i [prowadzi do zmniejszenia przejrzystości kodu |contributing:coding-standard#Wrapping and Braces]. + Typy .[#toc-types] ------------------ @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // to się uprości do A ->addTrait('Bar\AliasedClass'); // uprości się do AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // w komentarzach uprościmy ręcznie +$method->addComment('@return ' . $namespace->simplifyType('Foo')); // w komentarzach uprościć ręcznie $method->addParameter('arg') ->setType('Bar\OtherClass'); // zostanie rozwiązany do klasy \Bar\Bar\Class echo $namespace; -// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 +// lub użyć PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// lub użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 +// lub użyj PsrPrinter dla wyjścia zgodnego z PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Ładowanie z pliku PHP .[#toc-loading-from-php-file] --------------------------------------------------- -Możesz również załadować klasy i funkcje bezpośrednio z pliku PHP, który nie jest już załadowany lub z ciągu kodu PHP: +Można też ładować funkcje, klasy, interfejsy i enumy bezpośrednio z ciągu kodu PHP. Na przykład tworzymy obiekt `ClassType` w ten sposób: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Ładowanie całego pliku PHP, który może zawierać wiele klas, a nawet wiele przestrzeni nazw: +Podczas ładowania klas z kodu PHP, komentarze jednolinijkowe poza ciałami metod są ignorowane (np. dla właściwości itp.), ponieważ ta biblioteka nie posiada API do pracy z nimi. + +Możesz również załadować bezpośrednio cały plik PHP, który może zawierać dowolną liczbę klas, funkcji, a nawet wiele przestrzeni nazw: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +Ładowany jest również początkowy komentarz do pliku oraz deklaracja `strict_types`. Z drugiej strony, wszystkie inne globalne kody są ignorowane. + Wymaga to zainstalowania `nikic/php-parser`. +.[note] +Jeśli musisz manipulować globalnym kodem w plikach lub pojedynczymi stwierdzeniami w ciałach metod, lepiej jest użyć bezpośrednio biblioteki `nikic/php-parser`. + Zrzutka zmiennych .[#toc-variables-dumper] ------------------------------------------ diff --git a/php-generator/pt/@home.texy b/php-generator/pt/@home.texy index bbcac67c80..59902c2700 100644 --- a/php-generator/pt/@home.texy +++ b/php-generator/pt/@home.texy @@ -5,7 +5,7 @@ Gerador de código PHP - Necessidade de gerar código PHP para classes, funções, arquivos PHP, etc.? - Suporta todas as últimas características do PHP, como enumeração, atributos, etc. - Permite modificar facilmente as classes existentes -- Saída compatível com PSR-12 +- Saída compatível com PSR-12 / PER coding style - Biblioteca altamente madura, estável e amplamente utilizada </div> @@ -52,7 +52,6 @@ Ele renderá este resultado: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -67,7 +66,8 @@ Podemos acrescentar constantes (classe [Constante |api:Nette\PhpGenerator\Consta ```php $class->addConstant('ID', 123) - ->setProtected() // visibilidade constante + ->setProtected() // visiblidade constante + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Ele gera: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -As propriedades apenas de leitura introduzidas pelo PHP 8.1 podem ser marcadas via `setReadOnly()`. +As propriedades e classes só de leitura podem ser marcadas via `setReadOnly()`. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Interface ou Traço .[#toc-interface-or-trait] --------------------------------------------- -Você pode criar interfaces e traços: +Você pode criar interfaces e traços (classes [InterfaceType |api:Nette\PhpGenerator\InterfaceType] e [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Usando Traços: +Usando traços: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ Resultado: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enums .[#toc-enums] ------------------- -Você pode criar facilmente os enumeros que o PHP 8.1 traz: +Você pode criar facilmente os enums que o PHP 8.1 traz (classe [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// ou usar PsrPrinter para saída conforme PSR-2 / PSR-12 +// ou usar PsrPrinter para saída conforme PSR-2 / PSR-12 / PER // echo (novo Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// ou usar PsrPrinter para saída conforme PSR-2 / PSR-12 +// ou usar PsrPrinter para saída conforme PSR-2 / PSR-12 / PER // echo (novo Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Impressoras e conformidade PSR .[#toc-printers-and-psr-compliance] ------------------------------------------------------------------ -O código PHP é gerado por objetos `Printer`. Há um `PsrPrinter` cuja saída está em conformidade com PSR-2 e PSR-12 e usa espaços para recuo, e um `Printer` que usa abas para recuo. +A classe [Printer |api:Nette\PhpGenerator\Printer] é usada para gerar código PHP: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // o mesmo que: echo $class +``` + +Ele pode gerar código para todos os outros elementos, oferecendo métodos como `printFunction()`, `printNamespace()`, etc. + +Além disso, a classe `PsrPrinter` está disponível, cuja saída está em conformidade com o estilo de codificação PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 espaços de indentação +echo $printer->printClass($class); ``` -Necessidade de personalizar o comportamento da impressora? Crie seu próprio, herdando a classe `Printer`. Você pode reconfigurar estas variáveis: +Precisa ajustar o comportamento de acordo com suas necessidades? Crie sua própria impressora herdando a classe `Printer`. Você pode reconfigurar essas variáveis: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // comprimento da linha após a qual a linha será interrompida public int $wrapLength = 120; + // caractere de recuo, pode ser substituído por uma sequência de espaços public string $indentation = "\t"; + // número de linhas em branco entre as propriedades public int $linesBetweenProperties = 0; + // número de linhas em branco entre os métodos public int $linesBetweenMethods = 2; + // número de linhas em branco entre grupos de instruções de uso para classes, funções e constantes public int $linesBetweenUseTypes = 0; + // posição da chave de abertura para funções e métodos public bool $bracesOnNextLine = true; + // colocar um parâmetro em uma linha, mesmo que ele tenha um atributo ou seja promovido + public bool $singleParameterOnOneLine = false; + // separador entre o parêntese direito e o tipo de retorno de funções e métodos public string $returnTypeColon = ': '; } ``` +Como e por que exatamente o padrão `Printer` e o `PsrPrinter` diferem? Por que não há apenas uma impressora, a `PsrPrinter`, no pacote? + +O padrão `Printer` formata o código como fazemos em toda a Nette. Como a Nette foi criada muito antes da PSR, e também porque a PSR por muitos anos não forneceu padrões a tempo, mas às vezes até com vários anos de atraso da introdução de um novo recurso no PHP, isso resultou em algumas pequenas diferenças no [padrão de codificação |contributing:coding-standard]. +A maior diferença é apenas o uso de tabulações em vez de espaços. Sabemos que, ao usar tabulações em nossos projetos, permitimos o ajuste da largura, o que é [essencial para pessoas com deficiências visuais |contributing:coding-standard#Tabs Instead of Spaces]. +Um exemplo de uma diferença menor é o posicionamento da chave em uma linha separada para funções e métodos e sempre. Para nós, a recomendação do PSR é ilógica e [leva a uma diminuição da clareza do código |contributing:coding-standard#Wrapping and Braces]. + Tipos .[#toc-types] ------------------- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // simplificará para A ->addTrait('Bar\AliasedClass'); // simplificará para a AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // em comentários simplificar manualmente +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // em comentários simplifique manualmente $method->addParameter('arg') ->setType('Bar\OtherClass'); // resolverá a barrar a outra classe echo $namespace; -// ou usar PsrPrinter para saída em conformidade com PSR-2 / PSR-12 +// ou usar PsrPrinter para saída em conformidade com PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// ou usar PsrPrinter para saída em conformidade com PSR-2 / PSR-12 +// ou usar PsrPrinter para saída em conformidade com PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Carregando do arquivo PHP .[#toc-loading-from-php-file] ------------------------------------------------------- -Você também pode carregar classes e funções diretamente de um arquivo PHP que ainda não está carregado ou string de código PHP: +Você também pode carregar funções, classes, interfaces e enumeros diretamente de uma seqüência de código PHP. Por exemplo, criamos o objeto `ClassType` desta forma: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Carregando o arquivo PHP inteiro, que pode conter várias classes ou mesmo vários espaços de nomes: +Ao carregar classes do código PHP, os comentários de linha única fora dos corpos do método são ignorados (por exemplo, para propriedades, etc.) porque esta biblioteca não tem uma API para trabalhar com eles. + +Você também pode carregar o arquivo PHP inteiro diretamente, que pode conter qualquer número de classes, funções ou até mesmo vários espaços de nomes: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +O comentário inicial do arquivo e a declaração `strict_types` também são carregados. Por outro lado, todos os outros códigos globais são ignorados. + Isto requer que `nikic/php-parser` seja instalado. +.[note] +Se você precisar manipular o código global em arquivos ou declarações individuais em corpos de métodos, é melhor usar a biblioteca `nikic/php-parser` diretamente. + Variáveis Dumper .[#toc-variables-dumper] ----------------------------------------- diff --git a/php-generator/ro/@home.texy b/php-generator/ro/@home.texy index ef0433ba9a..d3aa50425e 100644 --- a/php-generator/ro/@home.texy +++ b/php-generator/ro/@home.texy @@ -5,7 +5,7 @@ Generator de coduri PHP - Aveți nevoie să generați cod PHP pentru clase, funcții, fișiere PHP, etc.? - Suportă toate cele mai recente caracteristici PHP, cum ar fi enums, atribute, etc. - Vă permite să modificați cu ușurință clasele existente -- Ieșire conformă cu PSR-12 +- Ieșire conformă cu PSR-12 / PER coding style - Bibliotecă extrem de matură, stabilă și utilizată pe scară largă </div> @@ -52,7 +52,6 @@ Acesta va reda acest rezultat: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -68,6 +67,7 @@ Putem adăuga constante (clasa [Constant |api:Nette\PhpGenerator\Constant]) și ```php $class->addConstant('ID', 123) ->setProtected() // vizibilitate constantă + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Se generează: ```php -final protected const ID = 123; +final protected const const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Proprietățile readonly introduse de PHP 8.1 pot fi marcate prin `setReadOnly()`. +Proprietățile și clasele de citire exclusivă pot fi marcate prin intermediul `setReadOnly()`. ------ @@ -170,7 +170,7 @@ $class->addMember($methodRecount); Interfață sau trăsătură .[#toc-interface-or-trait] -------------------------------------------------- -Puteți crea interfețe și trăsături: +Puteți crea interfețe și trăsături (clasele [InterfaceType |api:Nette\PhpGenerator\InterfaceType] și [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); @@ -194,6 +194,7 @@ Rezultat: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enums .[#toc-enums] ------------------- -Puteți crea cu ușurință enumerațiile pe care le aduce PHP 8.1: +Puteți crea cu ușurință enumerațiile pe care le aduce PHP 8.1 (clasa [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 +// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 +// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Imprimantele și conformitatea PSR .[#toc-printers-and-psr-compliance] --------------------------------------------------------------------- -Codul PHP este generat de obiectele `Printer`. Există un `PsrPrinter` a cărui ieșire este conformă cu PSR-2 și PSR-12 și care utilizează spații pentru indentare și un `Printer` care utilizează tabulatoare pentru indentare. +Clasa [Printer |api:Nette\PhpGenerator\Printer] este utilizată pentru a genera cod PHP: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // la fel ca: echo $class +``` + +Acesta poate genera cod pentru toate celelalte elemente, oferind metode precum `printFunction()`, `printNamespace()`, etc. + +În plus, este disponibilă clasa `PsrPrinter`, a cărei ieșire este conformă cu stilul de codare PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 spații de indentare +echo $printer->printClass($class); ``` -Aveți nevoie să personalizați comportamentul imprimantei? Creați-vă propria imprimantă moștenind clasa `Printer`. Puteți reconfigura aceste variabile: +Aveți nevoie să ajustați comportamentul la nevoile dumneavoastră? Creați-vă propria imprimantă moștenind din clasa `Printer`. Puteți reconfigura aceste variabile: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // lungimea liniei după care se va întrerupe linia public int $wrapLength = 120; + // caracter de indentare, poate fi înlocuit cu o secvență de spații public string $indentation = "\t"; + // numărul de linii goale între proprietăți public int $linesBetweenProperties = 0; + // numărul de linii goale între metode public int $linesBetweenMethods = 2; + // numărul de linii goale între grupuri de declarații de utilizare pentru clasă, funcții și constantă public int $linesBetweenUseTypes = 0; + // poziția bretonului de deschidere pentru funcții și metode public bool $bracesOnNextLine = true; + // plasează un parametru pe o singură linie, chiar dacă are un atribut sau este promovat + public bool $singleParameterOnOneLine = false; + // separator între paranteza dreaptă și tipul return al funcțiilor și metodelor public string $returnTypeColon = ': '; } ``` +Cum și de ce anume diferă standardul `Printer` și `PsrPrinter`? De ce nu există doar o singură imprimantă, `PsrPrinter`, în pachet? + +Standardul `Printer` formatează codul așa cum o facem în toată Nette. Deoarece Nette a fost creat mult mai devreme decât PSR și, de asemenea, deoarece PSR timp de mulți ani nu a livrat standardele la timp, ci uneori chiar cu o întârziere de câțiva ani de la introducerea unei noi caracteristici în PHP, acest lucru a dus la câteva diferențe minore în [standardul de codare |contributing:coding-standard]. +Cea mai mare diferență este doar utilizarea tabulatoarelor în loc de spații. Știm că, prin utilizarea tabulațiilor în proiectele noastre, permitem ajustarea lățimii, ceea ce este [esențial pentru persoanele cu deficiențe de vedere |contributing:coding-standard#Tabs Instead of Spaces]. +Un exemplu de diferență minoră este plasarea acoladei curly brace pe o linie separată pentru funcții și metode și întotdeauna. Considerăm că recomandarea PSR este ilogică și [duce la o scădere a clarității codului |contributing:coding-standard#Wrapping and Braces]. + Tipuri .[#toc-types] -------------------- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // se va simplifica la A ->addTrait('Bar\AliasedClass'); // se va simplifica în AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // în comentarii se simplifică manual +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // în comentarii simplificați manual $method->addParameter('arg') ->setType('Bar\OtherClass'); // se va rezolva în \Bar\OtherClass echo $namespace; -// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 +// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 +// sau utilizați PsrPrinter pentru o ieșire conformă cu PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Încărcare din fișierul PHP .[#toc-loading-from-php-file] -------------------------------------------------------- -De asemenea, puteți încărca clase și funcții direct dintr-un fișier PHP care nu este deja încărcat sau dintr-un șir de cod PHP: +De asemenea, puteți încărca funcții, clase, interfețe și enume direct dintr-un șir de cod PHP. De exemplu, creăm obiectul `ClassType` în acest mod: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Încărcarea întregului fișier PHP, care poate conține mai multe clase sau chiar mai multe spații de nume: +La încărcarea claselor din codul PHP, comentariile pe o singură linie din afara corpului metodelor sunt ignorate (de exemplu, pentru proprietăți etc.), deoarece această bibliotecă nu dispune de un API pentru a lucra cu acestea. + +De asemenea, puteți încărca direct întregul fișier PHP, care poate conține orice număr de clase, funcții sau chiar mai multe spații de nume: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +Se încarcă, de asemenea, comentariul inițial al fișierului și declarația `strict_types`. Pe de altă parte, toate celelalte coduri globale sunt ignorate. + Acest lucru necesită instalarea `nikic/php-parser`. +.[note] +Dacă aveți nevoie să manipulați codul global din fișiere sau declarațiile individuale din corpurile metodelor, este mai bine să utilizați direct biblioteca `nikic/php-parser`. + Descărcător de variabile .[#toc-variables-dumper] ------------------------------------------------- diff --git a/php-generator/ru/@home.texy b/php-generator/ru/@home.texy index a29929183c..5bb49c8ba3 100644 --- a/php-generator/ru/@home.texy +++ b/php-generator/ru/@home.texy @@ -5,7 +5,7 @@ - Вам нужно сгенерировать PHP-код для классов, функций, PHP-файлов и т.д.? - Поддерживает все новейшие возможности PHP, такие как перечисления, атрибуты и т.д. - Позволяет легко модифицировать существующие классы -- Выходные данные соответствуют стандарту PSR-12 +- Выходные данные соответствуют стандарту PSR-12 / PER coding style - Высокоразвитая, стабильная и широко используемая библиотека </div> @@ -52,7 +52,6 @@ echo $class; */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -68,6 +67,7 @@ echo $printer->printClass($class); ```php $class->addConstant('ID', 123) ->setProtected() // постоянная видимость + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Он генерирует: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Свойства readonly, введенные в PHP 8.1, могут быть помечены через `setReadOnly()`. +Свойства и классы, доступные для чтения, могут быть помечены с помощью `setReadOnly()`. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Интерфейс или трейты .[#toc-interface-or-trait] ----------------------------------------------- -Вы можете создавать интерфейсы и черты: +Вы можете создавать интерфейсы и трейты (классы [InterfaceType |api:Nette\PhpGenerator\InterfaceType] и [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Использование трейтов: +Использование признаков: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ echo $class; class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Энумы .[#toc-enums] ------------------- -Вы можете легко создавать перечисления, которые появились в PHP 8.1: +Вы можете легко создавать перечисления, которые появились в PHP 8.1 (класс [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// или использовать PsrPrinter для вывода, совместимого с PSR-2 / PSR-12 +// или использовать PsrPrinter для вывода, совместимого с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// или использовать PsrPrinter для вывода, совместимого с PSR-2 / PSR-12 +// или использовать PsrPrinter для вывода, совместимого с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -467,34 +468,58 @@ function foo($a) ``` -Принтеры и соответствие ПСР .[#toc-printers-and-psr-compliance] +Принтеры и соответствие PSR .[#toc-printers-and-psr-compliance] --------------------------------------------------------------- -Код PHP генерируется объектами `Printer`. Существует `PsrPrinter`, чей вывод соответствует PSR-2 и PSR-12 и использует пробелы для отступов, и `Printer`, который использует табуляцию для отступов. +Класс [Printer |api:Nette\PhpGenerator\Printer] используется для генерации PHP-кода: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // то же самое, что: echo $class +``` + +Он может генерировать код для всех остальных элементов, предлагая такие методы, как `printFunction()`, `printNamespace()` и т.д. + +Кроме того, доступен класс `PsrPrinter`, вывод которого соответствует стилю кодирования PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // отступ 4 пробела +echo $printer->printClass($class); ``` -Нужно настроить поведение принтера? Создайте свой собственный, унаследовав класс `Printer`. Вы можете изменить конфигурацию этих переменных: +Нужно точно настроить поведение под свои нужды? Создайте свой собственный принтер, унаследовав его от класса `Printer`. Вы можете изменить конфигурацию этих переменных: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // длина строки, после которой строка будет обрываться public int $wrapLength = 120; + // символ отступа, может быть заменен последовательностью пробелов public string $indentation = "\t"; + // количество пустых строк между свойствами public int $linesBetweenProperties = 0; + // количество пустых строк между методами public int $linesBetweenMethods = 2; + // количество пустых строк между группами утверждений использования для классов, функций и констант public int $linesBetweenUseTypes = 0; + // положение открывающей скобки для функций и методов размещения public bool $bracesOnNextLine = true; + // размещение одного параметра в одной строке, даже если у него есть атрибут или он продвигается + public bool $singleParameterOnOneLine = false; + // разделитель между правой круглой скобкой и возвращаемым типом функций и методов public string $returnTypeColon = ': '; } ``` +Чем и почему отличаются стандартные `Printer` и `PsrPrinter`? Почему в пакете нет только одного принтера - `PsrPrinter`? + +Стандарт `Printer` форматирует код так, как мы это делаем во всей Nette. Так как Nette был создан намного раньше, чем PSR, а также потому, что PSR в течение многих лет не поставлял стандарты вовремя, а иногда даже с задержкой в несколько лет от введения новой функции в PHP, это привело к нескольким незначительным различиям в [стандарте кодирования |contributing:coding-standard]. +Более существенным отличием является использование табуляции вместо пробелов. Мы знаем, что использование табуляции в наших проектах позволяет регулировать ширину текста, что [очень важно для людей с нарушениями зрения |contributing:coding-standard#Tabs Instead of Spaces]. +Примером незначительного отличия является размещение фигурной скобки на отдельной строке для функций и методов и всегда. Мы считаем рекомендацию PSR нелогичной и [ведущей к снижению ясности кода |contributing:coding-standard#Wrapping and Braces]. + Типы .[#toc-types] ------------------ @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // упрощается до A ->addTrait('Bar\AliasedClass'); // упрощается до AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // в комментариях упрощать вручную +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // в комментариях упрощаем вручную $method->addParameter('arg') ->setType('Bar\OtherClass'); // он будет разрешен в \Bar\OtherClass echo $namespace; -// или использовать PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 +// или использовать PsrPrinter для вывода в соответствии с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// или использовать PsrPrinter для вывода, совместимого с PSR-2 / PSR-12 +// или использовать PsrPrinter для вывода, совместимого с PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Загрузка класса из файла .[#toc-loading-from-php-file] ------------------------------------------------------ -Вы также можете загружать классы непосредственно из PHP-файла, который ещё не загружен, или строки PHP-кода: +Вы также можете загружать функции, классы, интерфейсы и перечисления непосредственно из строки PHP-кода. Например, мы создаем объект `ClassType` таким образом: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Загрузка всего файла PHP, который может содержать несколько классов или даже несколько пространств имён: +При загрузке классов из PHP-кода однострочные комментарии вне тела методов игнорируются (например, для свойств и т.д.), поскольку в этой библиотеке нет API для работы с ними. + +Вы также можете загрузить непосредственно весь PHP-файл, который может содержать любое количество классов, функций или даже несколько пространств имен: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +Начальный комментарий к файлу и объявление `strict_types` также загружаются. С другой стороны, весь остальной глобальный код игнорируется. + Для этого необходимо установить `nikic/php-parser`. +.[note] +Если вам нужно манипулировать глобальным кодом в файлах или отдельными утверждениями в телах методов, лучше использовать непосредственно библиотеку `nikic/php-parser`. + Дампинг переменных .[#toc-variables-dumper] ------------------------------------------- diff --git a/php-generator/sl/@home.texy b/php-generator/sl/@home.texy index eb46726d3c..4b47558861 100644 --- a/php-generator/sl/@home.texy +++ b/php-generator/sl/@home.texy @@ -5,7 +5,7 @@ Generator kode PHP - Potrebujete kodo PHP za razrede, funkcije, datoteke PHP itd.? - Podpira vse najnovejše funkcije PHP, kot so enumi, atributi itd. - Omogoča enostavno spreminjanje obstoječih razredov -- Izpis v skladu s standardom PSR-12 +- Izpis v skladu s standardom PSR-12 / PER coding style - Zelo zrela, stabilna in pogosto uporabljena knjižnica </div> @@ -52,7 +52,6 @@ Rezultat bo naslednji: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -67,7 +66,8 @@ Dodamo lahko konstante (razred [Constant |api:Nette\PhpGenerator\Constant]) in l ```php $class->addConstant('ID', 123) - ->setProtected() // stalna vidljivost + ->setProtected() // konstantna vidnost + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Ustvari se: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Lastnosti, namenjene samo branju, ki jih je uvedel PHP 8.1, lahko označite prek `setReadOnly()`. +Lastnosti in razrede, ki so namenjeni samo branju, lahko označite s spletno stranjo `setReadOnly()`. ------ @@ -170,7 +170,7 @@ $class->addMember($methodRecount); Vmesnik ali lastnost .[#toc-interface-or-trait] ----------------------------------------------- -Ustvarite lahko vmesnike in lastnosti: +Ustvarite lahko vmesnike in lastnosti (razreda [InterfaceType |api:Nette\PhpGenerator\InterfaceType] in [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); @@ -194,6 +194,7 @@ Rezultat: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enumi .[#toc-enums] ------------------- -Enostavno lahko ustvarite enume, ki jih prinaša PHP 8.1: +Enostavno lahko ustvarite enume, ki jih prinaša PHP 8.1 (razred [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 +// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 +// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Tiskalniki in skladnost s predpisi PSR .[#toc-printers-and-psr-compliance] -------------------------------------------------------------------------- -Koda PHP se ustvari s pomočjo predmetov `Printer`. Obstaja `PsrPrinter`, katerega izpis je skladen s PSR-2 in PSR-12 ter uporablja presledke za alineje, in `Printer`, ki za alineje uporablja tabulatorje. +Razred [Printer |api:Nette\PhpGenerator\Printer] se uporablja za ustvarjanje kode PHP: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // enako kot: echo $class +``` + +Ustvarja lahko kodo za vse druge elemente in ponuja metode, kot so `printFunction()`, `printNamespace()` itd. + +Poleg tega je na voljo razred `PsrPrinter`, katerega izpis je v skladu s slogom kodiranja PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // Odmik 4 presledki +echo $printer->printClass($class); ``` -Potrebujete prilagoditi obnašanje tiskalnika? Ustvarite svojega tako, da podedujete razred `Printer`. Te spremenljivke lahko ponovno konfigurirate: +Potrebujete natančno prilagoditi obnašanje svojim potrebam? Ustvarite svoj tiskalnik tako, da podedujete razred `Printer`. Te spremenljivke lahko ponovno konfigurirate: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // dolžina vrstice, po kateri se bo plastica prekinila public int $wrapLength = 120; + // znak za odmik, lahko se nadomesti z zaporedjem presledkov public string $indentation = "\t"; + // število praznih vrstic med zadnjimi public int $linesBetweenProperties = 0; + // število praznih vrstic med metodami public int $linesBetweenMethods = 2; + // število praznih vrstic med skupinami izjav o uporabi za razrede, funkcije in konstante public int $linesBetweenUseTypes = 0; + // položaj začetnega oklepaja za funkcije in metode public bool $bracesOnNextLine = true; + // postavi en parameter v eno vrstico, tudi če ima atribut ali je povišan + public bool $singleParameterOnOneLine = false; + // ločilo med desnim oklepajem in tipom vrnitve funkcij in metod public string $returnTypeColon = ': '; } ``` +Kako in zakaj natančno se razlikujeta standardna `Printer` in `PsrPrinter`? Zakaj v paketu ni samo enega tiskalnika, `PsrPrinter`, ki bi se razlikoval? + +Standardni `Printer` oblikuje kodo tako, kot jo oblikujemo v celotnem sistemu Nette. Ker je Nette nastal veliko prej kot PSR in tudi zato, ker PSR dolga leta ni pravočasno dostavljal standardov, temveč včasih celo z večletno zamudo od uvedbe nove funkcije v PHP, je to povzročilo nekaj manjših razlik v [standardu kodiranja |contributing:coding-standard]. +Večja razlika je le uporaba tabulatorjev namesto presledkov. Vemo, da z uporabo tabulatorjev v naših projektih omogočamo prilagajanje širine, kar je [bistvenega pomena za ljudi z okvarami vida |contributing:coding-standard#Tabs Instead of Spaces]. +Primer manjše razlike je postavitev oglatega oklepaja v ločeno vrstico za funkcije in metode ter vedno. Priporočilo PSR se nam zdi nelogično in [vodi k zmanjšanju jasnosti kode |contributing:coding-standard#Wrapping and Braces]. + Tipi .[#toc-types] ------------------ @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // se poenostavi na A ->addTrait('Bar\AliasedClass'); // se poenostavi na AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // v komentarjih poenostavi ročno +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // v komentarjih poenostavite ročno $method->addParameter('arg') ->setType('Bar\OtherClass'); // razreši se na \Bar\OtherClass echo $namespace; -// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 +// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 +// ali uporabite PsrPrinter za izpis v skladu s PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Nalaganje iz datoteke PHP .[#toc-loading-from-php-file] ------------------------------------------------------- -Razrede in funkcije lahko naložite tudi neposredno iz datoteke PHP, ki še ni naložena, ali niza kode PHP: +Funkcije, razrede, vmesnike in enume lahko naložite tudi neposredno iz niza kode PHP. Tako na primer ustvarimo objekt `ClassType`: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Nalaganje celotne datoteke PHP, ki lahko vsebuje več razredov ali celo več imenskih prostorov: +Pri nalaganju razredov iz kode PHP se enovrstični komentarji zunaj teles metod ne upoštevajo (npr. za lastnosti itd.), ker ta knjižnica nima API za delo z njimi. + +Neposredno lahko naložite tudi celotno datoteko PHP, ki lahko vsebuje poljubno število razredov, funkcij ali celo več imenskih prostorov: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +Prav tako se naložita začetni komentar datoteke in deklaracija `strict_types`. Po drugi strani pa se vsa druga globalna koda ne upošteva. + Za to je treba namestiti `nikic/php-parser`. +.[note] +Če morate upravljati globalno kodo v datotekah ali posamezne stavke v telesih metod, je bolje, da neposredno uporabite knjižnico `nikic/php-parser`. + Zbiralnik spremenljivk .[#toc-variables-dumper] ----------------------------------------------- diff --git a/php-generator/tr/@home.texy b/php-generator/tr/@home.texy index 5a67e5c966..b7b16008e2 100644 --- a/php-generator/tr/@home.texy +++ b/php-generator/tr/@home.texy @@ -5,7 +5,7 @@ PHP Kod Oluşturucu - Sınıflar, fonksiyonlar, PHP dosyaları vb. için PHP kodu oluşturmanız mı gerekiyor? - Enumlar, nitelikler vb. gibi en son PHP özelliklerini destekler. - Mevcut sınıfları kolayca değiştirmenizi sağlar -- PSR-12 uyumlu çıkış +- PSR-12 / PER coding style uyumlu çıkış - Son derece olgun, istikrarlı ve yaygın olarak kullanılan kütüphane </div> @@ -52,7 +52,6 @@ Bu sonucu verecektir: */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -67,7 +66,8 @@ Sabitleri ( [Constant |api:Nette\PhpGenerator\Constant] sınıfı) ve özellikle ```php $class->addConstant('ID', 123) - ->setProtected() // sürekli görünürlük + ->setProtected() // sabit görünürlük + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Üretiyor: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -PHP 8.1 tarafından tanıtılan salt okunur özellikler `setReadOnly()` aracılığıyla işaretlenebilir. +Readonly özellikler ve sınıflar `setReadOnly()` aracılığıyla işaretlenebilir. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Arayüz veya Özellik .[#toc-interface-or-trait] ---------------------------------------------- -Arayüzler ve özellikler oluşturabilirsiniz: +Arayüzler ve özellikler oluşturabilirsiniz ( [InterfaceType |api:Nette\PhpGenerator\InterfaceType] ve [TraitType |api:Nette\PhpGenerator\TraitType] sınıfları): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Özellikleri Kullanma: +Özellikleri kullanma: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ Sonuç: class Demo { use SmartObject; + /** @use MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Enumlar .[#toc-enums] --------------------- -PHP 8.1'in getirdiği enumları kolayca oluşturabilirsiniz: +PHP 8.1'in getirdiği [enumları (EnumType |api:Nette\PhpGenerator\EnumType] sınıfı) kolayca oluşturabilirsiniz: ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// veya PSR-2 / PSR-12'ye uygun çıktı için PsrPrinter kullanın +// veya PSR-2 / PSR-12 / PER'ye uygun çıktı için PsrPrinter kullanın // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// veya PSR-2 / PSR-12'ye uygun çıktı için PsrPrinter kullanın +// veya PSR-2 / PSR-12 / PER'ye uygun çıktı için PsrPrinter kullanın // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Yazıcılar ve PSR Uyumluluğu .[#toc-printers-and-psr-compliance] --------------------------------------------------------------- -PHP kodu `Printer` nesneleri tarafından üretilir. Çıktısı PSR-2 ve PSR-12'ye uygun olan ve girinti için boşluk kullanan bir `PsrPrinter` ve girinti için sekme kullanan bir `Printer` vardır. + [Printer |api:Nette\PhpGenerator\Printer] sınıfı PHP kodu üretmek için kullanılır: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // aynısı: echo $class +``` + +`printFunction()`, `printNamespace()` gibi yöntemler sunarak diğer tüm öğeler için kod oluşturabilir. + +Ayrıca, çıkışı PSR-2 / PSR-12 / PER kodlama stiline uygun olan `PsrPrinter` sınıfı da mevcuttur: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // 4 boşluk girinti +echo $printer->printClass($class); ``` -Yazıcı davranışını özelleştirmeniz mi gerekiyor? `Printer` sınıfını miras alarak kendi sınıfınızı oluşturun. Bu değişkenleri yeniden yapılandırabilirsiniz: +Davranışı ihtiyaçlarınıza göre ayarlamanız mı gerekiyor? `Printer` sınıfından miras alarak kendi yazıcınızı oluşturun. Bu değişkenleri yeniden yapılandırabilirsiniz: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // satırın kesileceği satır uzunluğu public int $wrapLength = 120; + // girinti karakteri, bir dizi boşluk ile değiştirilebilir public string $indentation = "\t"; + // özellikler arasındaki boş satır sayısı public int $linesBetweenProperties = 0; + // yöntemler arasındaki boş satır sayısı public int $linesBetweenMethods = 2; + // sınıflar, fonksiyonlar ve sabitler için kullanım deyimleri grupları arasındaki boş satır sayısı public int $linesBetweenUseTypes = 0; + // fonksiyonlar ve metotlar için açılış parantezinin konumu public bool $bracesOnNextLine = true; + // bir özniteliğe sahip olsa veya terfi ettirilse bile bir parametreyi tek bir satıra yerleştirir + public bool $singleParameterOnOneLine = false; + // sağ parantez ile fonksiyon ve metotların dönüş tipi arasında ayırıcı public string $returnTypeColon = ': '; } ``` +Standart `Printer` ve `PsrPrinter` tam olarak nasıl ve neden farklıdır? Pakette neden sadece tek bir yazıcı, `PsrPrinter` yok? + +Standart `Printer` kodu tüm Nette'de yaptığımız gibi biçimlendirir. Nette, PSR'den çok daha önce oluşturulduğundan ve ayrıca PSR uzun yıllar boyunca standartları zamanında teslim etmediğinden, hatta bazen PHP'de yeni bir özelliğin tanıtılmasından birkaç yıl gecikmeyle bile olsa, bu [kodlama standardında |contributing:coding-standard] birkaç küçük farklılığa neden oldu. +En büyük fark ise boşluk yerine sekme kullanılmasıdır. Projelerimizde sekme kullanarak, [görme engelli insanlar için |contributing:coding-standard#Tabs Instead of Spaces] çok önemli olan genişlik ayarlamasına izin verdiğimizi biliyoruz. +Küçük bir farka örnek olarak, küme parantezinin işlevler ve yöntemler için ve her zaman ayrı bir satıra yerleştirilmesi verilebilir. PSR tavsiyesinin mantıksız olduğunu ve [kod netliğinde azalmaya yol aç |contributing:coding-standard#Wrapping and Braces]tığını düşünüyoruz. + Türleri .[#toc-types] --------------------- @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // A'ya basitleştirilecektir ->addTrait('Bar\AliasedClass'); // AliasedClass olarak basitleştirilecektir $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // yorumlarda manuel olarak basitleştirin +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // yorumlarda manuel olarak basitleştirin $method->addParameter('arg') ->setType('Bar\OtherClass'); // \Bar\OtherClass olarak çözümlenecektir echo $namespace; -// veya PSR-2 / PSR-12'ye uygun çıktı için PsrPrinter kullanın +// veya PSR-2 / PSR-12 / PER'ye uygun çıktı için PsrPrinter kullanın // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// veya PSR-2 / PSR-12'ye uygun çıktı için PsrPrinter kullanın +// veya PSR-2 / PSR-12 / PER'ye uygun çıktı için PsrPrinter kullanın // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); PHP Dosyasından Yükleme .[#toc-loading-from-php-file] ----------------------------------------------------- -Sınıfları ve işlevleri doğrudan önceden yüklenmemiş bir PHP dosyasından veya PHP kodu dizesinden de yükleyebilirsiniz: +Fonksiyonları, sınıfları, arayüzleri ve enumları doğrudan bir PHP kodu dizisinden de yükleyebilirsiniz. Örneğin, `ClassType` nesnesini bu şekilde oluşturuyoruz: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Birden fazla sınıf ve hatta birden fazla ad alanı içerebilen PHP dosyasının tamamının yüklenmesi: +PHP kodundan sınıflar yüklenirken, yöntem gövdelerinin dışındaki tek satırlık yorumlar yok sayılır (örneğin, özellikler vb. için) çünkü bu kütüphanenin bunlarla çalışmak için bir API'si yoktur. + +Ayrıca, istediğiniz sayıda sınıf, işlev ve hatta birden fazla ad alanı içerebilen PHP dosyasının tamamını doğrudan yükleyebilirsiniz: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +İlk dosya yorumu ve `strict_types` bildirimi de yüklenir. Öte yandan, diğer tüm global kodlar yok sayılır. + Bunun için `nikic/php-parser` adresinin yüklenmesi gerekir. +.[note] +Dosyalardaki genel kodu veya yöntem gövdelerindeki tek tek ifadeleri değiştirmeniz gerekiyorsa, `nikic/php-parser` kütüphanesini doğrudan kullanmak daha iyidir. + Değişkenler Damperi .[#toc-variables-dumper] -------------------------------------------- diff --git a/php-generator/uk/@home.texy b/php-generator/uk/@home.texy index 380b40ac2b..a07c164047 100644 --- a/php-generator/uk/@home.texy +++ b/php-generator/uk/@home.texy @@ -5,7 +5,7 @@ - Потрібно згенерувати PHP-код для класів, функцій, PHP-файлів тощо? - Підтримує всі найновіші можливості PHP, такі як перерахування, атрибути тощо. - Дозволяє легко модифікувати існуючі класи -- Вихідні дані, сумісні з PSR-12 +- Вихідні дані, сумісні з PSR-12 / PER coding style - Досконала, стабільна та широко використовувана бібліотека </div> @@ -52,7 +52,6 @@ echo $class; */ final class Demo extends ParentClass implements Countable { - use Nette\SmartObject; } ``` @@ -68,6 +67,7 @@ echo $printer->printClass($class); ```php $class->addConstant('ID', 123) ->setProtected() // постійна видимість + ->setType('int') ->setFinal(); $class->addProperty('items', [1, 2, 3]) @@ -83,7 +83,7 @@ $class->addProperty('list') Це генерує: ```php -final protected const ID = 123; +final protected const int ID = 123; /** @var int[] */ private static $items = [1, 2, 3]; @@ -137,7 +137,7 @@ public function __construct( } ``` -Властивості тільки для читання, введені в PHP 8.1, можна позначити за допомогою `setReadOnly()`. +Властивості та класи, призначені лише для читання, можна позначити за допомогою `setReadOnly()`. ------ @@ -170,14 +170,14 @@ $class->addMember($methodRecount); Інтерфейс або Властивість .[#toc-interface-or-trait] ---------------------------------------------------- -Ви можете створювати інтерфейси та риси: +Ви можете створювати інтерфейси та риси (класи [InterfaceType |api:Nette\PhpGenerator\InterfaceType] та [TraitType |api:Nette\PhpGenerator\TraitType]): ```php $interface = new Nette\PhpGenerator\InterfaceType('MyInterface'); $trait = new Nette\PhpGenerator\TraitType('MyTrait'); ``` -Використання рис: +Використовуючи риси: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); @@ -194,6 +194,7 @@ echo $class; class Demo { use SmartObject; + /** @використовуйте MyTrait<Foo> */ use MyTrait { sayHello as protected; } @@ -204,7 +205,7 @@ class Demo Перерахування .[#toc-enums] --------------------------- -Ви можете легко створювати переліки, які з'явилися у PHP 8.1: +Ви можете легко створювати зчислення, які з'явилися у PHP 8.1 (клас [EnumType |api:Nette\PhpGenerator\EnumType]): ```php $enum = new Nette\PhpGenerator\EnumType('Suit'); @@ -275,7 +276,7 @@ $function->addParameter('a'); $function->addParameter('b'); echo $function; -// або використовуйте PsrPrinter для виводу відповідно до PSR-2 / PSR-12 +// або використовуйте PsrPrinter для виводу відповідно до PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFunction($function); ``` @@ -303,7 +304,7 @@ $closure->addUse('c') ->setReference(); echo $closure; -// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 +// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printClosure($closure); ``` @@ -470,31 +471,55 @@ function foo($a) Відповідність принтерів та PSR вимогам .[#toc-printers-and-psr-compliance] -------------------------------------------------------------------------- -PHP-код генерується об'єктами `Printer`. Існує об'єкт `PsrPrinter`, вивід якого відповідає стандартам PSR-2 і PSR-12 і використовує пробіли для відступів, і об'єкт `Printer`, який використовує табуляцію для відступів. +Клас [Printer |api:Nette\PhpGenerator\Printer] використовується для генерації PHP-коду: ```php $class = new Nette\PhpGenerator\ClassType('Demo'); // ... +$printer = new Nette\PhpGenerator\Printer; +echo $printer->printClass($class); // те саме що: echo $class +``` + +Він може генерувати код для всіх інших елементів, пропонуючи такі методи, як `printFunction()`, `printNamespace()` тощо. + +Крім того, доступний клас `PsrPrinter`, вихідні дані якого відповідають стилю кодування PSR-2 / PSR-12 / PER: + +```php $printer = new Nette\PhpGenerator\PsrPrinter; -echo $printer->printClass($class); // Відступ 4 пробіли +echo $printer->printClass($class); ``` -Потрібно налаштувати поведінку принтера? Створіть власний, успадкувавши клас `Printer`. Ви можете переналаштувати ці змінні: +Потрібно налаштувати поведінку відповідно до ваших потреб? Створіть власний принтер, успадкувавши клас `Printer`. Ви можете переналаштувати ці змінні: ```php class MyPrinter extends Nette\PhpGenerator\Printer { + // довжина рядка, після якої рядок буде розриватися public int $wrapLength = 120; + // символ відступу, може бути замінений послідовністю пробілів public string $indentation = "\t"; + // кількість порожніх рядків між властивостями public int $linesBetweenProperties = 0; + // кількість порожніх рядків між методами public int $linesBetweenMethods = 2; + // кількість пропусків між групами інструкцій використання класів, функцій та констант public int $linesBetweenUseTypes = 0; + // позиція відкриваючої дужки для функцій та методів public bool $bracesOnNextLine = true; + // розміщувати один параметр в одному рядку, навіть якщо він має атрибут або є розкрученим + public bool $singleParameterOnOneLine = false; + // роздільник між правою круглою дужкою та типом повернення функцій та методів public string $returnTypeColon = ': '; } ``` +Як і чим саме відрізняються стандартні `Printer` та `PsrPrinter`? Чому в комплекті немає лише одного принтера `PsrPrinter`? + +Стандартний `Printer` форматує код так само, як ми робимо це у всьому Nette. Оскільки Nette була створена набагато раніше, ніж PSR, а також тому, що PSR протягом багатьох років не постачала стандарти вчасно, а іноді навіть з кількарічним запізненням від введення нової функції в PHP, це призвело до кількох незначних відмінностей в [стандарті кодування |contributing:coding-standard]. +Більшою відмінністю є лише використання табуляції замість пробілів. Ми знаємо, що, використовуючи табуляцію в наших проектах, ми дозволяємо регулювати ширину, що дуже важливо [для людей з вадами зору |contributing:coding-standard#Tabs Instead of Spaces]. +Прикладом незначної відмінності є розміщення фігурної дужки в окремому рядку для функцій і методів і завжди. Ми вважаємо рекомендацію PSR нелогічною і такою, що призводить до [зниження зрозумілості коду |contributing:coding-standard#Wrapping and Braces]. + Типи .[#toc-types] ------------------ @@ -653,13 +678,13 @@ $class->addImplement('Foo\A') // спроститься до A ->addTrait('Bar\AliasedClass'); // спростить до AliasedClass $method = $class->addMethod('method'); -$method->addComment('@return ' . $namespace->simplifyName('Foo\D')); // у коментарях спростити вручну +$method->addComment('@return ' . $namespace->simplifyType('Foo\D')); // в коментарях спростити вручну $method->addParameter('arg') ->setType('Bar\OtherClass'); // буде спрощено до \Bar\OtherClass echo $namespace; -// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 +// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printNamespace($namespace); ``` @@ -712,7 +737,7 @@ $function = $file->addFunction('Foo\foo'); echo $file; -// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 +// або використовуйте PsrPrinter для виведення відповідно до PSR-2 / PSR-12 / PER // echo (new Nette\PhpGenerator\PsrPrinter)->printFile($file); ``` @@ -770,7 +795,7 @@ $function = Nette\PhpGenerator\GlobalFunction::from('foo', withBody: true); Завантаження з PHP-файлу .[#toc-loading-from-php-file] ------------------------------------------------------ -Ви також можете завантажити класи і функції безпосередньо з PHP-файлу, який ще не завантажено, або з рядка PHP-коду: +Ви також можете завантажувати функції, класи, інтерфейси та зчислення безпосередньо з рядка PHP-коду. Наприклад, ми створюємо об'єкт `ClassType` таким чином: ```php $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX @@ -783,14 +808,21 @@ $class = Nette\PhpGenerator\ClassType::fromCode(<<<XX XX); ``` -Завантаження всього PHP-файлу, який може містити декілька класів або навіть декілька просторів імен: +При завантаженні класів з PHP-коду ігноруються однорядкові коментарі за межами тіл методів (наприклад, для властивостей тощо), оскільки ця бібліотека не має API для роботи з ними. + +Ви також можете завантажити безпосередньо весь PHP-файл, який може містити будь-яку кількість класів, функцій або навіть декілька просторів імен: ```php $file = Nette\PhpGenerator\PhpFile::fromCode(file_get_contents('classes.php')); ``` +Також завантажуються початковий коментар до файлу та декларація `strict_types`. З іншого боку, весь інший глобальний код ігнорується. + Для цього потрібно, щоб було встановлено `nikic/php-parser`. +.[note] +Якщо вам потрібно маніпулювати глобальним кодом у файлах або окремими операторами в тілах методів, краще використовувати безпосередньо бібліотеку `nikic/php-parser`. + Дампер змінних .[#toc-variables-dumper] --------------------------------------- diff --git a/pla/cs/vytvarime-kontaktny-formular.texy b/pla/cs/vytvarime-kontaktny-formular.texy deleted file mode 100644 index 267ac29a5a..0000000000 --- a/pla/cs/vytvarime-kontaktny-formular.texy +++ /dev/null @@ -1,317 +0,0 @@ -Vytváříme kontaktní formulář -**************************** - -.[warning] -Tato stránka je již zastaralá a návod nemusí být funkční. - -.[perex] -Vytvoření kontaktního formuláře s odesláním na mail - formou **raw textu** i **html šablonou**. - -.[note] -Návod staví nad sandboxem **Nette 2.0.8** a PHP 5.3+. Stačí jej stáhnout a upravovat konkrétní soubory. - - -Jednoduše v presenteru -====================== - -Nejjednodušší a nejrychlejší přístup je vytvoření formuláře v presenteru - vytvoříme tedy komponentu `ContactForm`. Dále přidáme její zpracování třídou [Nette\Mail\Message|api:Nette\Mail\Message], která má na starosti vytváření a odesílání emailů. Více o odesílání emailů si můžete přečíst v [dokumentaci|mail:]. - - -**presenters/HomepagePresenter.php** - -```php -use Nette\Application\UI\Form; -use Nette\Mail\Message; - -class HomepagePresenter extends BasePresenter -{ - - /** - * Contact form - */ - protected function createComponentContactForm() - { - $form = new Form; - $form->addText('name', 'Jméno:') - ->addRule(Form::Filled, 'Zadejte jméno'); - $form->addText('email', 'Email:') - ->addRule(Form::Filled, 'Zadejte email') - ->addRule(Form::Email, 'Email nemá správný formát'); - $form->addTextarea('message', 'Zpráva:') - ->addRule(Form::Filled, 'Zadejte zprávu'); - $form->addSubmit('send', 'Odeslat'); - - $form->onSuccess[] = [$this, 'processContactForm']; - - return $form; - } - - - /** - * Process contact form, send message - * @param Form - */ - public function processContactForm(Form $form) - { - $values = $form->getValues(true); - - $message = new Message; - $message->addTo('test@gmail.com') - ->setFrom($values['email']) - ->setSubject('Zpráva z kontaktního formuláře') - ->setBody($values['message']) - ->send(); - - $this->flashMessage('Zpráva byla odeslána'); - $this->redirect('this'); - } - -} - -``` - - -Takto vytvořenou komponentu poté stačí přidat do šablony a můžeme vesele kontaktovat. - -**templates/Homepage/default.latte** - -```latte -{block content} - -{control contactForm} -``` - - -HTML šablona emailu -------------------- - -Pokud chceme v emailu využít HTML, musíme použít vlastní šablonu. V ní definujeme proměnné (ty jí musíme předat) a předmět v tagu `<title>`. Šablonu umístíme např. do: - -**templates/email/emailTemplate.latte** - -```latte -<html> - <head> - <title>{$title} - - - -
    -
  • - Jméno: {$values['name']} -
  • -
  • - Email: {$values['email']} -
  • -
  • - Zpráva: {$values['message']} -
  • -
- - - -``` - -Ještě upravíme metodu pro zpracování formuláře. - -**presenters/HomepagePresenter.php** - - -```php - -/** - * Process contact form, send message with custom template - * @param Form - */ -public function processContactForm(Form $form) -{ - $values = $form->getValues(true); - - $message = new Message; - $message->addTo('test@gmail.com') - ->setFrom($values['email']); - - $template = $this->createTemplate(); - $template->setFile(__DIR__ . '/../templates/emails/emailTemplate.latte'); - $template->title = 'Zpráva z kontaktního formuláře'; - $template->values = $values; - - $message->setHtmlBody($template) - ->send(); - - $this->flashMessage('Zpráva byla odeslána'); - $this->redirect('this'); -} -``` - - - - - - - - -/--comment - - -Jako komponenta -=============== - -Email s šablonou v komponentě - - -Šikovná komponenta ------------------- - -Formuláře je vhodné pro přehlednost a lepší rozšiřitelnost umisťovat do samostatné složky, např. `app/Forms`. Jelikož v tomto návodu bude potřeba vytváření šablon, využijeme k tomu samostatnou komponentu, kterou umístíme do `app/Components`. - -Zde vytvoříme složku `ContactFormControl` a v ní soubory: - -**ContactFormControl.php** - -/-php -use Nette\Application\UI\Control; -use Nette\Application\UI\Form; -use Nette\Mail\Message; - -class ContactFormComponent extends Control -{ - - protected function createComponentContactForm() - { - $form = new Form; - $form->addText('name', 'Jméno:') - ->addRule(Form::Filled, 'Zadejte jméno'); - $form->addText('email', 'Email:') - ->addRule(Form::Filled, 'Zadejte email') - ->addRule(Form::Email, 'Email nemá správný formát'); - $form->addTextarea('message', 'Zpráva:') - ->addRule(Form::Filled, 'Zadejte zprávu'); - $form->addSubmit('send', 'Odeslat'); - - $form->onSuccess[] = [$this, 'processContactForm']; - - return $form; - } - - - public function processContactForm(Form $form) - { - $settings = $this->presenter->context->parameters['email']; - $values = $form->values; - - $message = new Message; - $message->addTo($settings['to']) - ->setFrom($values['email']); - - // 1. způsob - raw text - $message->setSubject($settings['subject']) - ->setBody($values['message']); - - // 2. způsob - html - $template = $this->createTemplate(); - $template->setFile(__DIR__ . '/emailTemplate.latte'); - $template->title = $settings['subject']; - $template->values = $values; - $message->setHtmlBody($template); - - $message->send(); - - $this->presenter->flashMessage('Zpráva byla odeslána'); - $this->redirect('this'); - } - - - public function render() - { - $this->template->setFile(__DIR__ . '/ContactFormControl.latte'); - $this->template->render(); - } - -} - - - -**ContactFormControl.latte** -/-html -{control contactForm} - - -**emailTemplate.latte** -/-html - - - {$title} - - - -
    -
  • - Jméno: {$values['name']} -
  • -
  • - Email: {$values['email']} -
  • -
  • - Zpráva: {$values['message']} -
  • -
- - - -\- - - -Presenter ---------- - -Poté formulář zavoláme v presenteru a vytvoříme komponentu: - -**HomepagePresenter.php** - -```php -class HomepagePresenter extends BasePresenter -{ - - protected function createComponentContactFormControl() - { - return new ContactFormComponent; - } - - -} - -``` - - -Šablona -------- - -Komponentu přidáme do šalbony: - -**Homepage/default.latte** -```latte -{block content} - {control contactFormControl} -{/block} -``` - - -Config ------- - -**app/config/config.neon** -```neon -parameters: - email: - to: my@email.com - subject: Kontaktní formulář -``` - - -SMTP odesílání? ---------------- - -Link někam, toto už tu je... - -\-- diff --git a/quickstart/bg/@home.texy b/quickstart/bg/@home.texy index b9950b8bb4..58f142b6aa 100644 --- a/quickstart/bg/@home.texy +++ b/quickstart/bg/@home.texy @@ -4,34 +4,24 @@ .[perex] Запознайте се с рамката Nette, като създадете прост блог с коментари. Да започнем! -След първите две глави ще имате свой собствен работещ блог и ще сте готови да публикувате страхотни публикации, въпреки че след приключването на тези две глави възможностите са доста ограничени. За да го направите по-приятно за потребителите, трябва да прочетете и следващите глави и да продължите да подобрявате приложението си. +След първите две глави ще имате свой собствен работещ блог и ще сте готови да публикувате страхотни публикации, въпреки че функциите ще бъдат доста ограничени след приключването на тези две глави. За да направите нещата по-приятни за вашите потребители, трябва да прочетете и следващите глави и да продължите да подобрявате приложението си. -Можете да намерите [готовото приложение в GitHub |https://github.com/nette/tutorial-quickstart/tree/v4.0]. +.[tip] +Този урок предполага, че сте завършили документа [Инсталиране |nette:installation] и успешно сте настроили инструментите си. -Моля, инсталирайте [напълно функционален IDE и всички необходими плъгини |best-practices:editors-and-tools], което ще ви направи изключително ефективни. +Моля, използвайте PHP 8.0 или по-нова версия. Можете да намерите пълното приложение [в GitHub |https://github.com/nette-examples/quickstart/tree/v4.0]. -Това кратко ръководство е написано за рамката Nette 3.1 и PHP 8.0 или по-нова версия. -Можете да изтеглите рамката Nette ръчно, но препоръчителният начин за стартиране на нов проект е да използвате [Composer |best-practices:composer]. Ако не познавате Composer, определено трябва да започнете с него. Това е наистина прост и полезен инструмент, разгледайте [документацията му |https://getcomposer.org/doc/]. +Страницата за добре дошли .[#toc-the-welcome-page] +================================================== -С помощта на Composer можете лесно да изтеглите и инсталирате рамката за приложения, известна като Web Project, включително Nette Framework. За да направите това, намерете в командния ред вашата основна директория (например `/var/www` или `C:\InetPub`) и изпълнете следната команда: +Нека започнем със създаването на нов проект в директорията `nette-blog`: ```shell composer create-project nette/web-project nette-blog ``` -Уеб проектът ще бъде качен в директорията `nette-blog`. - -.[note] -Ако не сте успели да използвате Composer, [изтеглете |https://github.com/nette/web-project/archive/preloaded.zip] и разархивирайте архива, копирайте го в главната директория на уеб сървъра и го преименувайте на `nette-blog`. Цялата рамка е разположена в папката `vendor`. - -Ако разработвате за macOS или Linux (или друга Unix-базирана система), трябва да [конфигурирате правата за запис на |nette:troubleshooting#Setting-Directory-Permissions] уеб сървъра. - - -Страница за добре дошли .[#toc-the-welcome-page] -================================================ - -На този етап вече можете да видите началната страница на уеб проекта. Опитайте да направите това, като отворите браузъра си и отидете на следния URL адрес: +В този момент трябва да се стартира началната страница на уеб проекта. Опитайте я, като отворите браузъра си и отидете на следния URL адрес: ``` http://localhost/nette-blog/www/ @@ -77,7 +67,7 @@ http://localhost/nette-blog/www/ Почистване .[#toc-cleanup] ========================== -Уеб проектът съдържа начална страница, която можем да премахнем - не се колебайте да замените съдържанието на файла `app/Presenters/templates/Homepage/default.latte` с текста `Hello world!`. +Уеб проектът съдържа начална страница, която можем да премахнем - не се колебайте да замените съдържанието на файла `app/Presenters/templates/Home/default.latte` с текста `Hello world!`. [* qs-hello.webp .{url:-} *] @@ -86,7 +76,7 @@ http://localhost/nette-blog/www/ Tracy (дебъгер) .[#toc-tracy-debugger] ====================================== -Изключително важен инструмент за разработка е [дебъгер, наречен Tracy. |tracy:] Опитайте се да направите някои грешки във вашия файл `app/Presenters/HomepagePresenter.php` (например да премахнете къдравата скоба от дефиницията на класа HomepagePresenter) и вижте какво ще се случи. Ще се появи страница с червен екран и разбираемо описание на грешката. +Изключително важен инструмент за разработка е [дебъгер, наречен Tracy. |tracy:] Опитайте се да направите някои грешки във вашия файл `app/Presenters/HomePresenter.php` (например да премахнете къдравата скоба от дефиницията на класа HomePresenter) и вижте какво ще се случи. Ще се появи страница с червен екран и разбираемо описание на грешката. [* qs-tracy.webp .{url:-}(debugger screen) *] diff --git a/quickstart/bg/@left-menu.texy b/quickstart/bg/@left-menu.texy index 93fe4bf36e..cbc1f3b57d 100644 --- a/quickstart/bg/@left-menu.texy +++ b/quickstart/bg/@left-menu.texy @@ -1,5 +1,7 @@ +Учебник +******* - [Създайте първото си приложение |@home]! -- [Основна страница на блога |home-page] +- [Начална страница на блога |home-page] - [Страница за индивидуално вписване |single-post] - [Коментари |comments] - [Създаване и редактиране на публикации |creating-posts] diff --git a/quickstart/bg/authentication.texy b/quickstart/bg/authentication.texy index 86c1b213b3..85d904fb3c 100644 --- a/quickstart/bg/authentication.texy +++ b/quickstart/bg/authentication.texy @@ -82,7 +82,7 @@ public function signInFormSucceeded(Form $form, \stdClass $data): void { try { $this->getUser()->login($data->username, $data->password); - $this->redirect('Homepage:'); + $this->redirect('Home:'); } catch (Nette\Security\AuthenticationException $e) { $form->addError('Неправильные логин или пароль.'); @@ -117,7 +117,7 @@ public function startup(): void Скриване на връзки .[#toc-hide-links] ------------------------------------- -Неоторизиран потребител вече не може да вижда страниците за създаване и редактиране, но все още може да вижда връзките, които сочат към тях. Нека скрием и тях. Една такава връзка се намира на адрес `app/Presenters/templates/Homepage/default.latte`, като тя трябва да бъде видима само ако потребителят е влязъл в системата. +Неоторизиран потребител вече не може да вижда страниците за създаване и редактиране, но все още може да вижда връзките, които сочат към тях. Нека скрием и тях. Една такава връзка се намира на адрес `app/Presenters/templates/Home/default.latte`, като тя трябва да бъде видима само ако потребителят е влязъл в системата. Можем да го скрием, като използваме *n:атрибута*, наречен `n:if`. Ако изявлението в него е `false`, тогава целият таг `` и съдържанието му няма да бъде показано: @@ -142,7 +142,7 @@ public function startup(): void ```latte .{file:app/Presenters/templates/@layout.latte} ...