Nette Documentation Preview

syntax
Podajanje odvisnosti
********************

<div class=perex>

Argumente ali "odvisnosti" v terminologiji DI lahko razredom posredujete na naslednje glavne načine:

* posredovanje s konstruktorjem
* posredovanje z metodo (imenovano setter)
* z nastavitvijo lastnosti
* z metodo, anotacijo ali atributom *inject*

</div>

Različne različice bomo zdaj ponazorili s konkretnimi primeri.


Vbrizgavanje konstruktorjev .[#toc-constructor-injection]
=========================================================

Odvisnosti se konstruktorju ob ustvarjanju predmeta posredujejo kot argumenti:

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

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

Od različice PHP 8.0 lahko uporabimo krajšo obliko zapisa ([constructor property promotion |https://blog.nette.org/sl/php-8-0-popoln-pregled-novic#toc-constructor-property-promotion]), ki je funkcionalno enakovredna:

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

Od PHP 8.1 lahko lastnost označimo z zastavico `readonly`, ki izjavlja, da se vsebina lastnosti ne bo spremenila:

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

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 dajete prednost [sestavi pred dedovanjem** |faq#Why composition is preferred over inheritance].

Zato kodo oblikujmo drugače. Izognili se bomo [abstraktnim |nette:introduction-to-object-oriented-programming#abstract-classes] `Base*` razredom. Namesto da bi razred `MyClass` pridobil določeno 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 so te metode poimenovane v obliki `set*()`, zato se imenujejo setterji, seveda pa se lahko imenujejo tudi kako drugače.

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

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

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 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;
	}
}
```

Klic setterja je opredeljen v konfiguraciji vsebnika DI v [razdelku setup |services#Setup]. Tudi tu se uporablja samodejno posredovanje odvisnosti s pomočjo funkcije autowiring:

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


Vbrizgavanje lastnosti .[#toc-property-injection]
=================================================

Odvisnosti se posredujejo neposredno lastnosti:

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

Nastavitev spremenljivke je opredeljena v konfiguraciji vsebnika DI v [razdelku nastavitev |services#Setup]:

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

- konstruktor je primeren za obvezne odvisnosti, ki jih razred potrebuje za delovanje
- setter pa je po drugi strani primeren za neobvezne odvisnosti ali odvisnosti, ki jih je mogoče spremeniti
- javne spremenljivke niso priporočljive

Podajanje odvisnosti

Argumente ali „odvisnosti“ v terminologiji DI lahko razredom posredujete na naslednje glavne načine:

  • posredovanje s konstruktorjem
  • posredovanje z metodo (imenovano setter)
  • z nastavitvijo lastnosti
  • z metodo, anotacijo ali atributom inject

Različne različice bomo zdaj ponazorili s konkretnimi primeri.

Vbrizgavanje konstruktorjev

Odvisnosti se konstruktorju ob ustvarjanju predmeta posredujejo kot argumenti:

class MyClass
{
	private Cache $cache;

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

Od različice PHP 8.0 lahko uporabimo krajšo obliko zapisa (constructor property promotion), ki je funkcionalno enakovredna:

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

Od PHP 8.1 lahko lastnost označimo z zastavico readonly, ki izjavlja, da se vsebina lastnosti ne bo spremenila:

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

Kontejner DI samodejno posreduje odvisnosti konstruktorju z uporabo samodejnega povezovanja. Argumente, ki jih ni mogoče posredovati na ta način (npr. nize, številke, logične vrednosti), zapiše v konfiguracijo.

Konstruktorski pekel

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:

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 dajete prednost sestavi pred dedovanjem**.

Zato kodo oblikujmo drugače. Izognili se bomo abstraktnim Base* razredom. Namesto da bi razred MyClass pridobil določeno funkcionalnost s podedovanjem od razreda BaseClass, mu bo ta funkcionalnost posredovana kot odvisnost:

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

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.

class MyClass
{
	private Cache $cache;

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

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.

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;
	}
}

Klic setterja je opredeljen v konfiguraciji vsebnika DI v razdelku setup. Tudi tu se uporablja samodejno posredovanje odvisnosti s pomočjo funkcije autowiring:

services:
	-	create: MyClass
		setup:
			- setCache

Vbrizgavanje lastnosti

Odvisnosti se posredujejo neposredno lastnosti:

class MyClass
{
	public Cache $cache;
}

$obj = new MyClass;
$obj->cache = $cache;

publicTako 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.

Nastavitev spremenljivke je opredeljena v konfiguraciji vsebnika DI v razdelku nastavitev:

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

Vbrizgajte

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.

Kateri način izbrati?

  • konstruktor je primeren za obvezne odvisnosti, ki jih razred potrebuje za delovanje
  • setter pa je po drugi strani primeren za neobvezne odvisnosti ali odvisnosti, ki jih je mogoče spremeniti
  • javne spremenljivke niso priporočljive