Nette Documentation Preview

syntax
Passage des dépendances
***********************

<div class=perex>

Les arguments, ou 'dépendances' dans la terminologie DI, peuvent être passés aux classes des principales façons suivantes :

*   Injection par le constructeur
*   Injection par méthode (dite injection par setter)
*   Injection dans une propriété
*   À l'aide de la méthode `inject*()` ou de l'attribut `#[Inject]`

</div>

Illustrons chaque variante par des exemples concrets.


Injection par le constructeur
=============================

Les dépendances sont fournies comme arguments du constructeur au moment où l'objet est instancié :

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

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

$obj = new MyClass($cache);
```

Cette approche convient aux dépendances obligatoires, dont la classe a absolument besoin pour fonctionner, car sans elles l'instance ne peut pas être créée.

Depuis PHP 8.0, nous pouvons utiliser une notation plus courte ([promotion des propriétés du constructeur |https://blog.nette.org/fr/php-8-0-complete-overview-of-news#toc-constructor-property-promotion]), fonctionnellement équivalente :

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

Depuis PHP 8.1, une propriété peut être marquée du drapeau `readonly`, qui déclare que sa valeur ne changera plus après l'initialisation :

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

Le conteneur DI passe les dépendances au constructeur automatiquement grâce à l'[autowiring |autowiring]. Les arguments qui ne peuvent pas être fournis de cette façon (par exemple des chaînes, des nombres, des booléens) [sont indiqués dans la configuration |services#Arguments].


Constructor hell
----------------

Le terme *constructor hell* décrit une situation où une classe enfant hérite d'une classe parente dont le constructeur exige des dépendances, alors que la classe enfant en exige elle aussi. Elle doit alors accepter et transmettre également les dépendances du parent :

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

Le problème surgit lorsque nous voulons modifier le constructeur de `BaseClass`, par exemple quand une nouvelle dépendance s'y ajoute. Il devient alors nécessaire de modifier aussi tous les constructeurs des classes enfants. Ce qui transforme une telle modification en enfer.

Comment l'éviter ? La solution consiste à **préférer la [composition à l'héritage |faq#Pourquoi la composition est-elle préférée à l'héritage ?]**.

Nous concevons donc le code autrement. Nous éviterons les classes [abstraites |nette:introduction-to-object-oriented-programming#Classes abstraites] `Base*`. Au lieu que `MyClass` obtienne une certaine fonctionnalité en héritant de `BaseClass`, cette fonctionnalité lui sera passée comme dépendance :

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


Injection par setter
====================

Les dépendances sont fournies par l'appel d'une méthode qui les stocke dans une propriété privée. La convention de nommage habituelle de ces méthodes suit le modèle `set*()`, d'où leur nom de setters, mais elles peuvent bien sûr être nommées autrement.

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

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

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

Cette approche convient aux dépendances facultatives, qui ne sont pas indispensables au fonctionnement de la classe, car rien ne garantit que l'objet recevra effectivement la dépendance (c'est-à-dire que l'appelant invoquera la méthode).

En même temps, cette méthode permet d'appeler le setter à plusieurs reprises pour changer la dépendance. Si ce n'est pas souhaitable, ajoutez une vérification dans la méthode ou, depuis PHP 8.1, marquez la propriété `$cache` du drapeau `readonly`.

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

	public function setCache(Cache $cache): void
	{
		if (isset($this->cache)) {
			throw new RuntimeException('La dépendance a déjà été définie');
		}
		$this->cache = $cache;
	}
}
```

L'appel du setter se définit dans la configuration du conteneur DI, dans la [clé setup |services#Setup]. Là aussi, la fourniture automatique des dépendances par autowiring est utilisée :

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


Injection dans une propriété
============================

Les dépendances sont fournies par écriture directe dans une propriété de l'objet :

```php
class MyClass
{
	public Cache $cache;
}

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

Cette méthode est considérée comme inappropriée, car la propriété doit être déclarée `public`. Nous perdons donc le contrôle sur le fait que la dépendance passée est bien du type requis (c'était particulièrement vrai avant les déclarations de type des propriétés en PHP 7.4), ainsi que la possibilité de réagir à une dépendance nouvellement affectée par une logique propre, par exemple pour empêcher une modification ultérieure. En même temps, la propriété devient partie de l'API publique de la classe, ce qui n'est pas forcément voulu.

L'affectation de la propriété se définit dans la configuration du conteneur DI, dans la [section setup |services#Setup] :

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


Inject
======

Alors que les trois approches précédentes s'appliquent de façon générale dans tous les langages orientés objet, l'injection par les méthodes `inject*()` ou par l'attribut `#[Inject]` est typiquement utilisée avec les presenters Nette, où elle est activée par défaut ; tout autre service peut l'activer via [`inject: true` |services#Mode inject]. Elles sont traitées dans un [chapitre distinct |best-practices:inject-method-attribute].


Quelle méthode choisir ?
========================

- Le constructeur convient aux dépendances obligatoires, dont la classe a absolument besoin pour fonctionner.
- Le setter, à l'inverse, convient aux dépendances facultatives, ou à celles qu'il faudra peut-être changer par la suite.
- Les propriétés publiques ne sont généralement pas recommandées.

Passage des dépendances

Les arguments, ou ‚dépendances‘ dans la terminologie DI, peuvent être passés aux classes des principales façons suivantes :

  • Injection par le constructeur
  • Injection par méthode (dite injection par setter)
  • Injection dans une propriété
  • À l'aide de la méthode inject*() ou de l'attribut #[Inject]

Illustrons chaque variante par des exemples concrets.

Injection par le constructeur

Les dépendances sont fournies comme arguments du constructeur au moment où l'objet est instancié :

class MyClass
{
	private Cache $cache;

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

$obj = new MyClass($cache);

Cette approche convient aux dépendances obligatoires, dont la classe a absolument besoin pour fonctionner, car sans elles l'instance ne peut pas être créée.

Depuis PHP 8.0, nous pouvons utiliser une notation plus courte (promotion des propriétés du constructeur), fonctionnellement équivalente :

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

Depuis PHP 8.1, une propriété peut être marquée du drapeau readonly, qui déclare que sa valeur ne changera plus après l'initialisation :

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

Le conteneur DI passe les dépendances au constructeur automatiquement grâce à l'autowiring. Les arguments qui ne peuvent pas être fournis de cette façon (par exemple des chaînes, des nombres, des booléens) sont indiqués dans la configuration.

Constructor hell

Le terme constructor hell décrit une situation où une classe enfant hérite d'une classe parente dont le constructeur exige des dépendances, alors que la classe enfant en exige elle aussi. Elle doit alors accepter et transmettre également les dépendances du parent :

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

Le problème surgit lorsque nous voulons modifier le constructeur de BaseClass, par exemple quand une nouvelle dépendance s'y ajoute. Il devient alors nécessaire de modifier aussi tous les constructeurs des classes enfants. Ce qui transforme une telle modification en enfer.

Comment l'éviter ? La solution consiste à préférer la composition à l'héritage.

Nous concevons donc le code autrement. Nous éviterons les classes abstraites Base*. Au lieu que MyClass obtienne une certaine fonctionnalité en héritant de BaseClass, cette fonctionnalité lui sera passée comme dépendance :

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

Injection par setter

Les dépendances sont fournies par l'appel d'une méthode qui les stocke dans une propriété privée. La convention de nommage habituelle de ces méthodes suit le modèle set*(), d'où leur nom de setters, mais elles peuvent bien sûr être nommées autrement.

class MyClass
{
	private Cache $cache;

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

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

Cette approche convient aux dépendances facultatives, qui ne sont pas indispensables au fonctionnement de la classe, car rien ne garantit que l'objet recevra effectivement la dépendance (c'est-à-dire que l'appelant invoquera la méthode).

En même temps, cette méthode permet d'appeler le setter à plusieurs reprises pour changer la dépendance. Si ce n'est pas souhaitable, ajoutez une vérification dans la méthode ou, depuis PHP 8.1, marquez la propriété $cache du drapeau readonly.

class MyClass
{
	private Cache $cache;

	public function setCache(Cache $cache): void
	{
		if (isset($this->cache)) {
			throw new RuntimeException('La dépendance a déjà été définie');
		}
		$this->cache = $cache;
	}
}

L'appel du setter se définit dans la configuration du conteneur DI, dans la clé setup. Là aussi, la fourniture automatique des dépendances par autowiring est utilisée :

services:
	-	create: MyClass
		setup:
			- setCache

Injection dans une propriété

Les dépendances sont fournies par écriture directe dans une propriété de l'objet :

class MyClass
{
	public Cache $cache;
}

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

Cette méthode est considérée comme inappropriée, car la propriété doit être déclarée public. Nous perdons donc le contrôle sur le fait que la dépendance passée est bien du type requis (c'était particulièrement vrai avant les déclarations de type des propriétés en PHP 7.4), ainsi que la possibilité de réagir à une dépendance nouvellement affectée par une logique propre, par exemple pour empêcher une modification ultérieure. En même temps, la propriété devient partie de l'API publique de la classe, ce qui n'est pas forcément voulu.

L'affectation de la propriété se définit dans la configuration du conteneur DI, dans la section setup :

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

Inject

Alors que les trois approches précédentes s'appliquent de façon générale dans tous les langages orientés objet, l'injection par les méthodes inject*() ou par l'attribut #[Inject] est typiquement utilisée avec les presenters Nette, où elle est activée par défaut ; tout autre service peut l'activer via inject: true. Elles sont traitées dans un chapitre distinct.

Quelle méthode choisir ?

  • Le constructeur convient aux dépendances obligatoires, dont la classe a absolument besoin pour fonctionner.
  • Le setter, à l'inverse, convient aux dépendances facultatives, ou à celles qu'il faudra peut-être changer par la suite.
  • Les propriétés publiques ne sont généralement pas recommandées.