En esta última charla de Laracon Online 2017, Matt Stauffer, creador del libro Laravel Up & Running, nos explica la «magia» detrás del framework Laravel, es decir cómo funciona el contenedor Illuminate\Container y cómo se relaciona con los demás servicios y componentes de Laravel.

¿Qué es el contenedor?

El contenedor es como la espina dorsal de Laravel. Es lo que permite atar todos componentes y clases o servicios de Laravel, como por ejemplo: enrutador, middleware, solicitudes, resolución e inyección de dependencias automática, facades, etc.

El contenedor y sus alias:

Nosotros solemos referirnos al contenedor de Laravel como simplemente el contenedor (The container) pero también como:

  • La clase Application
  • El IoC Container
  • DI Container

¿Qué significan las siglas IoC y DI?

DI viene del inglés dependency injection o inyección de dependencias: se refiere a la acción de suministrar o inyectar dependencias (objetos) a través del constructor o métodos setter de otro objeto. Lo cuál suele contribuir bastante a la creación de código desacoplado y a un buen diseño orientado a objetos.

IoC viene del inglés inversión de control o inversión de control: con este principio de diseño definimos dentro del framework, una sola vez, cómo queremos que se implementen ciertos features, por ejemplo qué drivers vamos a usar para manejar la sesión o la base de datos, etc.

Puedes aprender sobre inyección de dependencias, contenedores, inversión de control y más en nuestro Curso de creación de componentes con PHP y Laravel.

El contenedor es el pegamento

Por lo tanto, Matt concluye que el contenedor es como el «pegamento» que une los diferentes componentes, servicios y clases del framework.

Cómo atar y resolver clases con el contenedor

Las dos funciones principales del contenedor son “bind” y “resolve”, es decir incluir y resolver dependencias.

Se puede acceder al contenedor de Laravel haciendo uso del helper app().

<?php

$container = app();

Lo cual no es más que una forma fácil y corta de escribir:

<?php

$container = Container::getInstance();

Esto puede parecer confuso ¿Por qué el helper se llama app pero parece que obtenemos una instancia de Container?

Luego de investigar un poco, descubrí que la clase Illuminate\Foundation\Application tiene esta línea:

// Linea 174
static::setInstance($this);

y resulta que la clase Application extiende de la clase contenedor (Illuminate\Container\Container). Lo que quiere decir que al llamar al helper app() o al ejecutar Container::getInstance() obtendremos nada más y nada menos que una instancia del objeto Application de Laravel. De allí puedes ver porqué se conoce como «Contenedor» o «Aplicación». Application no es más que una implementación de Container en Laravel.

El componente Container puede ser usado como un componente en proyectos fuera del framework Laravel.

¿Qué hace el contenedor de Laravel?

Este objeto nos permite resolver de forma automática cualquier clase, esté registrada o no dentro del contenedor:

<?php

class SomeClass
{
    Public function doSomething() {
        // do stuff
    }
}

$container = Container::getInstance();

$someClass = $container->make(SomeClass::class);

$someClass->doSomething();

// o en una sola linea:

Container::getInstance()->make(SomeClass::class)->doSomething();

Este ejemplo por supuesto no tiene ninguna «ganancia» puesto que nuestra clase SomeClass no tiene ninguna dependencia. Pero si la tuviera:

<?php

class SomeClass
{
    public function __construct(SomeDependency $dependency) {
        $this->dependency = $dependency;
    }

    //...
}

El contenedor, por lo general, será capaz de resolver e inyectar todas las dependencias de forma recursiva al momento de crear la instancia de la clase.

Dentro de Laravel, el contenedor nos permite obtener todos los servicios y clases del framework de forma muy sencilla usando:

<?php

$container = app();
$logger = $container->make(Illuminate\Log\Writer::class);

// or 

$logger = app()->make(Illuminate\Log\Writer::class);

O aún más corto, puesto que el helper app() como muchos otros permiten múltiples propósitos:

<?php

app(Illuminate\Log\Writer::class);

Aprende más sobre el uso de helpers en Laravel vs Facades e inyección de dependencias.

Autowiring

Matt también nos habló del concepto de autowiring: la capacidad que tiene un framework o componente de instanciar una clase sin recibir instrucciones explícitas de cómo hacerlo. Para lograr esto hace falta usar Reflection.

Puedes aprender cómo se logra la resolución automática de clases con Reflection en nuestro Curso de creación de componentes.

Asociar clases al contenedor de forma manual

Para casos donde requiramos más personalización, también podemos «atar» una o más clases de forma manual y específica:

app()->bind(SomeClass::class, function () {
    return new SomeClass(new SomeDependency);
});

Esto nos devolverá siempre una nueva instancia de la clase SomeClass.

Uso de «Singleton» en el contenedor

¿Qué sucede si queremos reusar una misma instancia de una clase? Para lograr esto tenemos 2 opciones:

Podemos utilizar el método singleton:

app()->singleton(SomeClass::class, function () {
    return new SomeClass(new SomeDependency);
});

Ahora se creará una instancia de SomeClass la primera vez que se llame a app(SomeClass::class) y la referencia a esta clase quedará grabada en el contenedor y será retornada la segunda, tercera, cuarta… vez que se llame a app(SomeClass::class).

Aprende más sobre el tema en nuestro Curso de creación de componentes:

También podemos utilizar el método «instance» para instanciar la clase en cualquier lugar de la aplicación y registrarla luego en el contenedor, para obtenerla y usarla en cualquier parte del proyecto:

<?php

app()->instance(SomeClass::class, new SomeClass(new SomeDependency));

Uso de alias en el contenedor

Cuando trabajamos con el contenedor no estamos obligados a usar nombres de clases, puesto que también nos permite definir «alias» los pueden crear referencias a interfaces o simples cadenas de texto, ejemplo:

<?php

app()->alias(SomeClass::class, 'some-class');

app('some-class'); // retorna la instancia de SomeClass

Todos los servicios del núcleo de Laravel tienen alias o «apodos» como podemos observar en la documentación de Laravel o en el archivo config/app.php donde están registrados.

Aprende más sobre interfaces y polimorfismo en nuestro Curso de programación orientada a objetos con PHP.

Inyección de dependencias en métodos

Como quizás habrás notado ya, Laravel también nos permite el uso de inyección de dependencias con resolución automática en métodos:

<?php

//...

public function store(Request $request)
{
    //...
}

Otros tipos de inyección de dependencias en Laravel

Matt también nos habló de otros tipos de inyección de dependencias que podemos encontrar en Laravel, como lo son:

  • Form requests: nos permite definir clases encargadas de validar datos en las peticiones de forma automática.
  • Route model binding: nos permite asociar y atara modelos a nuestras rutas de forma automática.

Luego, Matt Stauffer, continuó con dos conceptos claves de Laravel de los cuales te hemos hablado bastante en Styde:

Service Providers

Son clases que permiten atar servicios al contenedor en una ubicación central, generalmente los agrupamos por funcionalidad. Laravel es configurado dentro de Service Providers y típicamente los componentes y tu aplicación también poseen y usan service providers.

Facades

Quizás el feature más controversial de Laravel.

Las facades dan la impresión de ser clases con un montón de métodos estáticos, por ejemplo: Route::get() o View::make() o Auth::user() lo cual sería una mala práctica porque nos llevaría a tener un framework altamente acoplado como explicamos en la lección dependencias y código acoplado del curso de componentes.

Sin embargo, las facades no son más que «proxies» que nos una interfaz muy conveniente para llamar de forma «estática» a métodos no estáticos de clases registradas en el contenedor de Laravel.

Por ejemplo: View::make('mi-vista') es equivalente a escribir app(Illuminate\View\Factory::class)->make('mi-vista') o app('view')->make('mi-vista'). Es decir, con esto estaremos accediendo al contenedor con todos los beneficios que esto trae (clases desacopladas, posibilidad de reemplazar implementaciones, escribir pruebas unitarias, etc.).

Aprende más sobre facades:

Más sobre Laracon Online

Aquí puedes encontrar un resumen de cada charla del evento

Únete a nuestra comunidad en Discord y comparte con los usuarios y autores de Styde, 100% gratis.

Únete hoy

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