Skip to content

Commit

Permalink
inject injection: improved
Browse files Browse the repository at this point in the history
  • Loading branch information
dg committed May 29, 2023
1 parent 2106763 commit 71729a1
Show file tree
Hide file tree
Showing 50 changed files with 306 additions and 786 deletions.
64 changes: 17 additions & 47 deletions best-practices/bg/inject-method-attribute.texy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
{
Expand All @@ -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: Най-добри практики}}
64 changes: 17 additions & 47 deletions best-practices/cs/inject-method-attribute.texy
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
{
Expand All @@ -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}}
Loading

0 comments on commit 71729a1

Please sign in to comment.