Nette Documentation Preview

syntax
Passaggio delle dipendenze
**************************
הההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההההה
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Passaggio delle dipendenze

Gli argomenti, o nella terminologia DI „dipendenze“, possono essere passati alle classi nei seguenti modi principali:

  • passaggio tramite costruttore
  • passaggio tramite metodo (cosiddetto setter)
  • impostazione di una variabile
  • tramite metodo, annotazione o attributo inject

Ora mostreremo le singole varianti con esempi concreti.

Passaggio tramite costruttore

Le dipendenze vengono passate al momento della creazione dell'oggetto come argomenti del costruttore:

class MyClass
{
	private Cache $cache;

	public function __construct(Cache $cache)
	{
		$this->cache = $cache;
	}
}

$obj = new MyClass($cache);

Questa forma è adatta per le dipendenze obbligatorie di cui la classe ha necessariamente bisogno per funzionare, poiché senza di esse non sarà possibile creare l'istanza.

Da PHP 8.0 possiamo usare una forma di scrittura più breve (constructor property promotion), che è funzionalmente equivalente:

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

Da PHP 8.1 è possibile contrassegnare la variabile con il flag readonly, che dichiara che il contenuto della variabile non cambierà più:

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

Il container DI passa automaticamente le dipendenze al costruttore tramite autowiring. Gli argomenti che non possono essere passati in questo modo (es. stringhe, numeri, booleani) li scriviamo nella configurazione.

Constructor hell

Il termine constructor hell indica la situazione in cui un figlio eredita da una classe genitore il cui costruttore richiede dipendenze, e allo stesso tempo il figlio richiede dipendenze. Deve quindi ricevere e passare anche quelle del genitore:

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 sorge nel momento in cui vogliamo modificare il costruttore della classe BaseClass, ad esempio quando viene aggiunta una nuova dipendenza. Allora è necessario modificare anche tutti i costruttori dei figli. Il che rende una tale modifica un inferno.

Come prevenirlo? La soluzione è dare la preferenza alla composizione rispetto all'ereditarietà.

Quindi progetteremo il codice diversamente. Eviteremo le classi astratte Base*. Invece che MyClass ottenga una certa funzionalità ereditando da BaseClass, si farà passare questa funzionalità come dipendenza:

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

Passaggio tramite setter

Le dipendenze vengono passate chiamando un metodo che le salva in una variabile privata. La convenzione usuale per nominare questi metodi è la forma set*(), per questo vengono chiamati setter, ma possono ovviamente chiamarsi in qualsiasi altro modo.

class MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		$this->cache = $cache;
	}
}

$obj = new MyClass;
$obj->setCache($cache);

Questo metodo è adatto per le dipendenze opzionali che non sono indispensabili per la funzione della classe, poiché non è garantito che l'oggetto riceva effettivamente la dipendenza (cioè che l'utente chiami il metodo).

Allo stesso tempo, questo metodo consente di chiamare il setter ripetutamente e quindi modificare la dipendenza. Se ciò non è desiderato, aggiungiamo un controllo nel metodo, o da PHP 8.1 contrassegniamo la property $cache con il flag readonly.

class MyClass
{
	private Cache $cache;

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

La chiamata del setter viene definita nella configurazione del container DI nella chiave setup. Anche qui si utilizza il passaggio automatico delle dipendenze tramite autowiring:

services:
	-	create: MyClass
		setup:
			- setCache

Impostazione di una variabile

Le dipendenze vengono passate scrivendo direttamente nella variabile membro:

class MyClass
{
	public Cache $cache;
}

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

Questo metodo è considerato inappropriato, poiché la variabile membro deve essere dichiarata come public. E quindi non abbiamo controllo sul fatto che la dipendenza passata sia effettivamente del tipo specificato (valido prima di PHP 7.4) e perdiamo la possibilità di reagire alla dipendenza appena assegnata con codice personalizzato, ad esempio impedendo modifiche successive. Allo stesso tempo, la variabile diventa parte dell'interfaccia pubblica della classe, il che potrebbe non essere desiderabile.

L'impostazione della variabile viene definita nella configurazione del container DI nella sezione setup:

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

Inject

Mentre i tre metodi precedenti valgono in generale in tutti i linguaggi orientati agli oggetti, l'iniezione tramite metodo, annotazione o attributo inject è specifica esclusivamente per i presenter in Nette. Ne parla un capitolo separato.

Quale metodo scegliere?

  • il costruttore è adatto per le dipendenze obbligatorie di cui la classe ha necessariamente bisogno per funzionare
  • il setter è invece adatto per le dipendenze opzionali, o dipendenze che si desidera poter modificare ulteriormente
  • le variabili pubbliche non sono adatte