Introducción

El contenedor de servicios de Laravel es una herramienta poderosa para administrar dependencias de clases y realizar inyección de dependencias. La inyección de dependencias es una frase bonita para básicamente decir: las dependencias de clases son «inyectadas» en la clase mediante el constructor o, en algunos casos, métodos «setter».

Demos un vistazo a un ejemplo sencillo:

<?php

namespace App\Http\Controllers;

use App\Http\Controllers\Controller;
use App\Repositories\UserRepository;
use App\User;

class UserController extends Controller
{
    /**
    * The user repository implementation.
    *
    * @var UserRepository
    */
    protected $users;

    /**
    * Create a new controller instance.
    *
    * @param  UserRepository  $users
    * @return void
    */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
    * Show the profile for the given user.
    *
    * @param  int  $id
    * @return Response
    */
    public function show($id)
    {
        $user = $this->users->find($id);

        return view('user.profile', ['user' => $user]);
    }
}

En este ejemplo, UserController necesita retornar usuarios desde un origen de datos. Así que, inyectaremos un servicio que sea capaz de retornar los usuarios. En este contexto, nuestro UserRepository probablemente use Eloquent para retornar la información de los usuarios desde la base de datos. Sin embargo, dado que el repositorio es inyectado, seremos capaces de cambiarlo fácilmente con otra implementación. También seremos capaces de «simular» o crear una implementación de ejemplo de UserRepository al probar nuestra aplicación.

Un conocimiento profundo del contenedor de servicios de Laravel es esencial para construir aplicaciones grandes y poderosas así como también contribuir al núcleo de Laravel.

Enlaces

Fundamentos de los enlaces

La mayoría de los enlaces de tu contenedor de servicios serán registrados dentro de proveedores de servicios, así que la mayoría de estos ejemplos muestran el uso del contenedor en ese contexto.

No hay necesidad de enlazar clases al contenedor si no dependen de ninguna interfaz. El contenedor no necesita ser instruido en cómo construir esos objetos, dado que puede resolver dichos objetos automáticamente usando reflejos.

Enlaces sencillos

Dentro de un proveedor de servicios, siempre tienes acceso al contenedor mediante la propiedad $this->app. Podemos registrar un enlace usando el método bind, pasando el nombre de la clase o interfaz que deseamos registrar junto con una Closure que retorna una instancia de la clase:

$this->app->bind('HelpSpot\API', function ($app) {
    return new \HelpSpot\API($app->make('HttpClient'));
});

Observa que recibimos el contenedor como argumento. Podemos entonces usar el contenedor para resolver sub-dependencias del objeto que estamos construyendo.

Enlazando un singleton

El método singleton enlaza una clase o interfaz al contenedor que debería ser resuelto una sola vez. Una vez que el enlace de un singleton es resuelto, la misma instancia de objeto será retornada en llamadas siguientes al contenedor:

$this->app->singleton('HelpSpot\API', function ($app) {
    return new \HelpSpot\API($app->make('HttpClient'));
});

Enlazando instancias

También puedes enlazar una instancia de objeto existente al contenedor usando el método instance. La instancia dada siempre será retornada en llamadas siguientes al contenedor:

$api = new \HelpSpot\API(new HttpClient);

$this->app->instance('HelpSpot\API', $api);

Enlazando valores primitivos

Algunas veces tendrás una clase que recibe algunas clases inyectadas, pero que también necesita un valor primitivo inyectado, como un entero. Puedes fácilmente usar enlaces contextuales para inyectar cualquier valor que tu clase pueda necesitar:

$this->app->when('App\Http\Controllers\UserController')
          ->needs('$variableName')
          ->give($value);

Enlazando interfaces a implementaciones

Una característica muy poderosa del contenedor de servicios es su habilidad para enlazar una interfaz a una implementación dada. Por ejemplo, vamos a suponer que tenemos una interfaz EventPusher y una implementación RedisEventPusher. Una vez que hemos programado nuestra implementación RedisEventPusher de esta interfaz, podemos registrarla con el contenedor de servicios de la siguiente manera:

$this->app->bind(
    'App\Contracts\EventPusher',
    'App\Services\RedisEventPusher'
);

Esta sentencia le dice al contenedor que debe inyectar RedisEventPusher cuando una clase necesita una implementación de EventPusher. Ahora podemos determinar el tipo de la interfaz EventPusher en un constructor o cualquier otra ubicación donde las dependencias sean inyectadas por el contenedor de servicios:

use App\Contracts\EventPusher;

/**
* Create a new class instance.
*
* @param  EventPusher  $pusher
* @return void
*/
public function __construct(EventPusher $pusher)
{
    $this->pusher = $pusher;
}

Enlaces contextuales

Algunas veces tendrás dos clases que usan la misma interfaz, pero quieres inyectar diferentes implementaciones en cada clase. Por ejemplo, dos controladores pueden depender de diferentes implementaciones del contrato Illuminate\Contracts\Filesystem\Filesystem. Laravel proporciona una simple y fluida interfaz para definir este comportamiento:

use App\Http\Controllers\PhotoController;
use App\Http\Controllers\UploadController;
use App\Http\Controllers\VideoController;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Facades\Storage;

$this->app->when(PhotoController::class)
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('local');
          });

$this->app->when([VideoController::class, UploadController::class])
          ->needs(Filesystem::class)
          ->give(function () {
              return Storage::disk('s3');
          });

Etiquetado

Ocasionalmente, puedes necesitar resolver todo de una determinada «categoría» de enlaces. Por ejemplo, puede que estés construyendo un agregador de reportes que recibe un arreglo de diferentes implementaciones de la interfaz Report. Luego de registrar las implementaciones de Report, puedes asignarles una etiqueta usando el método tag:

$this->app->bind('SpeedReport', function () {
    //
});

$this->app->bind('MemoryReport', function () {
    //
});

$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');

Una vez que los servicios han sido etiquetados, puedes resolverlos fácilmente mediante el método tagged:

$this->app->bind('ReportAggregator', function ($app) {
    return new ReportAggregator($app->tagged('reports'));
});

Extendiendo enlaces

El método extend te permite modificar servicios resueltos. Por ejemplo, cuando un servicio es resuelto, puedes ejecutar código adicional para decorar o configurar el servicio. El método extend acepta un Closure, que debe retornar el servicio modificado como único argumento. La Closure recibe el servicio que está siendo resuelto y la instancia del contenedor:

$this->app->extend(Service::class, function ($service, $app) {
    return new DecoratedService($service);
});

Resolviendo

Método make

Puedes usar el método make para resolver una instancia de clase fuera del contenedor. El método make acepta el nombre de la clase o interfaz que deseas resolver:

$api = $this->app->make('HelpSpot\API');

Si estás en una ubicación de tu código que no tiene acceso a la variable $app, puedes usar el helper global resolve:

$api = resolve('HelpSpot\API');

Si algunas de las dependencias de tu clase no son resueltas mediante el contenedor, puedes inyectarlas pasándolas como un arreglo asociativo al método makeWith:

$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);

Inyección automática

Alternativamente, y de forma importante, puedes «determinar el tipo» de la dependencia en el constructor de una clase que es resuelta por el contenedor, incluyendo controladores, listeners de eventos, middleware y más. Adicionalmente, puedes «determinar el tipo» de dependencia en el método handle de las colas. En la práctica, así es como la mayoría de tus objetos deben ser resueltos por el contenedor.

Por ejemplo, puedes determinar el tipo de un repositorio definido por tu aplicación en el constructor de un controlador. El repositorio será automáticamente resuelto e inyectado en la clase:

<?php

namespace App\Http\Controllers;

use App\Users\Repository as UserRepository;

class UserController extends Controller
{
    /**
    * The user repository instance.
    */
    protected $users;

    /**
    * Create a new controller instance.
    *
    * @param  UserRepository  $users
    * @return void
    */
    public function __construct(UserRepository $users)
    {
        $this->users = $users;
    }

    /**
    * Show the user with the given ID.
    *
    * @param  int  $id
    * @return Response
    */
    public function show($id)
    {
        //
    }
}

Eventos del contenedor

El contenedor de servicios ejecuta un evento cada vez que resuelve un objeto. Puedes escuchar a este evento usando el método resolving:

$this->app->resolving(function ($object, $app) {
    // Called when container resolves object of any type...
});

$this->app->resolving(\HelpSpot\API::class, function ($api, $app) {
    // Called when container resolves objects of type "HelpSpot\API"...
});

Como puedes ver, el objeto siendo resuelto será pasado a la función de retorno (callback), permitiéndote establecer cualquier propiedad adicional en el objeto antes de que sea entregado a su consumidor.

PSR-11

El contenedor de servicios de Laravel implementa la interfaz PSR-11. Por lo tanto, puedes determinar el tipo de la interfaz de contenedor PSR-11 para obtener una instancia del contenedor de Laravel:

use Psr\Container\ContainerInterface;

Route::get('/', function (ContainerInterface $container) {
    $service = $container->get('Service');

    //
});

Una excepción es mostrada si el identificador dado no puede ser resuelto. La excepción será una instancia de Psr\Container\NotFoundExceptionInterface si el identificador nunca fue enlazado. Si el identificador fue enlazado pero ha sido incapaz de resolver, una instancia de Psr\Container\ContainerExceptionInterface será mostrada.

Regístrate hoy en Styde y obtén acceso a todo nuestro contenido.

Lección anterior Ciclo de vida de la solicitud - Documentación de Laravel 6 Lección siguiente Proveedores de Servicios - Documentación de Laravel 6