Nette Documentation Preview

syntax
Przekazywanie zależności
************************

<div class=perex>

Argumenty, lub "zależności" w terminologii DI, mogą być przekazywane do klas na następujące główne sposoby:

* przekazywanie przez konstruktora
* przekazywanie przez metodę (zwane setterem)
* poprzez ustawienie zmiennej
* przez metodę, adnotację lub atrybut *inject*.

</div>

Zilustrujemy teraz różne warianty na konkretnych przykładach.


Przekazywanie przez konstruktora .[#toc-constructor-injection]
==============================================================

Zależności są przekazywane jako argumenty do konstruktora w czasie tworzenia obiektu:

```php
class MyClass
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $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.

Od PHP 8.0 możemy używać krótszej formy notacji ([constructor property promotion |https://blog.nette.org/pl/php-8-0-kompletny-przeglad-nowosci#toc-constructor-property-promotion]), która jest funkcjonalnie równoważna:

```php
// PHP 8.0
class MyClass
{
	public function __construct(
		private Cache $cache,
	) {
	}
}
```

Od PHP 8.1 zmienna może być oznaczona flagą `readonly`, która deklaruje, że zawartość zmiennej nie będzie się już zmieniać:

```php
// PHP 8.1
class MyClass
{
	public function __construct(
		private readonly Cache $cache,
	) {
	}
}
```

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 **przedkładanie [kompozycji nad dziedziczenie** |faq#Why composition is preferred over inheritance].

Zaprojektujmy więc kod inaczej. Unikniemy [abstrakcyjnych |nette:introduction-to-object-oriented-programming#abstract-classes] klas `Base*`. Zamiast `MyClass` uzyskiwać pewną funkcjonalność poprzez dziedziczenie z `BaseClass`, będzie mieć 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 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 MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		$this->cache = $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ę).

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 MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		if ($this->cache) {
			throw new RuntimeException('The dependency has already been set');
		}
		$this->cache = $cache;
	}
}
```

Wywołanie setera jest zdefiniowane w konfiguracji kontenera DI w [sekcji |services#Setup] setup. Również w tym przypadku wykorzystywane jest automatyczne przekazywanie zależności przez autowiring:

```neon
services:
	-	create: MyClass
		setup:
			- setCache
```


Poprzez ustawienie zmiennej .[#toc-property-injection]
======================================================

Zależności są przekazywane przez zapis bezpośrednio do zmiennej członkowskiej:

```php
class MyClass
{
	public 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.

Ustawienia zmiennej definiujemy w konfiguracji kontenera DI w [sekcji setup |services#Setup]:

```neon
services:
	-	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]
===============================================

- Konstruktor jest odpowiedni dla obowiązkowych zależności, które klasa potrzebuje do działania
- setter jest odpowiedni dla opcjonalnych zależności lub zależności, które mogą być zmienione
- zmienne publiczne nie są odpowiednie

Przekazywanie zależności

Argumenty, lub „zależności“ w terminologii DI, mogą być przekazywane do klas na następujące główne sposoby:

  • przekazywanie przez konstruktora
  • przekazywanie przez metodę (zwane setterem)
  • poprzez ustawienie zmiennej
  • przez metodę, adnotację lub atrybut inject.

Zilustrujemy teraz różne warianty na konkretnych przykładach.

Przekazywanie przez konstruktora

Zależności są przekazywane jako argumenty do konstruktora w czasie tworzenia obiektu:

class MyClass
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $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.

Od PHP 8.0 możemy używać krótszej formy notacji (constructor property promotion), która jest funkcjonalnie równoważna:

// PHP 8.0
class MyClass
{
	public function __construct(
		private Cache $cache,
	) {
	}
}

Od PHP 8.1 zmienna może być oznaczona flagą readonly, która deklaruje, że zawartość zmiennej nie będzie się już zmieniać:

// PHP 8.1
class MyClass
{
	public function __construct(
		private readonly Cache $cache,
	) {
	}
}

Kontener DI przekazuje zależność do konstruktora automatycznie przez autowiring. Argumenty, które nie mogą być przekazane w ten sposób (np. ciągi znaków, liczby, booleans) są zapisywane w konfiguracji.

Piekło konstruktorów

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:

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 przedkładanie kompozycji nad dziedziczenie.

Zaprojektujmy więc kod inaczej. Unikniemy abstrakcyjnych klas Base*. Zamiast MyClass uzyskiwać pewną funkcjonalność poprzez dziedziczenie z BaseClass, będzie mieć tę funkcjonalność przekazaną jako zależność:

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

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.

class MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		$this->cache = $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ę).

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.

class MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		if ($this->cache) {
			throw new RuntimeException('The dependency has already been set');
		}
		$this->cache = $cache;
	}
}

Wywołanie setera jest zdefiniowane w konfiguracji kontenera DI w sekcji setup. Również w tym przypadku wykorzystywane jest automatyczne przekazywanie zależności przez autowiring:

services:
	-	create: MyClass
		setup:
			- setCache

Poprzez ustawienie zmiennej

Zależności są przekazywane przez zapis bezpośrednio do zmiennej członkowskiej:

class MyClass
{
	public 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.

Ustawienia zmiennej definiujemy w konfiguracji kontenera DI w sekcji setup:

services:
	-	create: MyClass
		setup:
			- $cache = @\Cache

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.

Jaką metodę wybrać?

  • Konstruktor jest odpowiedni dla obowiązkowych zależności, które klasa potrzebuje do działania
  • setter jest odpowiedni dla opcjonalnych zależności lub zależności, które mogą być zmienione
  • zmienne publiczne nie są odpowiednie