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