Pasar dependencias

Los argumentos, o “dependencias” en la terminología DI, se pueden pasar a las clases de las siguientes formas principales:

  • pasando por constructor
  • pasando por método (llamado setter)
  • estableciendo una propiedad
  • por método, anotación o atributo inject.

Los tres primeros métodos se aplican en general en todos los lenguajes orientados a objetos, el cuarto es específico de los presentadores Nette, por lo que se trata en capítulo aparte. A continuación veremos más detenidamente cada una de estas opciones y las mostraremos con ejemplos concretos.

Inyección de constructor

Las dependencias se pasan como argumentos al constructor cuando se crea el objeto:

class MyService
{
	public function __construct(
		private Cache $cache,
	) {
	}
}

$service = new MyService($cache);

Esta forma es útil para dependencias obligatorias que la clase necesita absolutamente para funcionar, ya que sin ellas la instancia no puede ser creada.

Desde PHP 8.0, podemos usar una forma más corta de notación que es funcionalmente equivalente:

// PHP 8.0
class MyService
{
	public function __construct(
		private Cache $service,
	) {
	}
}

A partir de PHP 8.1, una propiedad puede ser marcada con una bandera readonly que declara que el contenido de la propiedad no cambiará:

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

El contenedor DI pasa dependencias al constructor automáticamente usando autowiring. Argumentos que no se pueden pasar de esta forma (por ejemplo cadenas, números, booleanos) escribir en configuración.

Inyección de Setter

Las dependencias se pasan llamando a un método que las almacena en una propiedad privada. La convención de nomenclatura habitual para estos métodos es de la forma set*(), por lo que se denominan setters.

class MyService
{
	private Cache $cache;

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

$service = new MyService;
$service->setCache($cache);

Este método es útil para dependencias opcionales que no son necesarias para la función de la clase, ya que no se garantiza que el objeto las reciba realmente (es decir, que el usuario llame al método).

Al mismo tiempo, este método permite llamar al setter repetidamente para cambiar la dependencia. Si esto no es deseable, añada una comprobación al método, o a partir de PHP 8.1, marque la propiedad $cache con la bandera readonly.

class MyService
{
	private Cache $cache;

	public function setCache(Cache $service): void
	{
		if ($this->cache) {
			throw new RuntimeException('La dependencia ya se ha establecido');
		}
		$this->cache = $service;
	}
}

La llamada al setter se define en la configuración del contenedor DI en sección setup. También aquí se utiliza el paso automático de dependencias mediante autowiring:

services:
	-
		create: MyService
		setup:
			- setCache

Inyección de propiedades

Las dependencias se pasan directamente a la propiedad:

class MyService
{
	public Cache $cache;
}

$service = new MyService;
$service->cache = $cache;

Este método se considera inapropiado porque la propiedad debe declararse como public. Por lo tanto, no tenemos control sobre si la dependencia pasada será realmente del tipo especificado (esto era cierto antes de PHP 7.4) y perdemos la capacidad de reaccionar a la dependencia recién asignada con nuestro propio código, por ejemplo para evitar cambios posteriores. Al mismo tiempo, la propiedad pasa a formar parte de la interfaz pública de la clase, lo que puede no ser deseable.

La configuración de la variable se define en la configuración del contenedor DI en sección setup:

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

¿Qué camino elegir?

  • el constructor es adecuado para dependencias obligatorias que la clase necesita para funcionar.
  • el setter, por otro lado, es adecuado para dependencias opcionales, o dependencias que se pueden cambiar.
  • las variables públicas no son recomendables.