Advertisement
  1. Code
  2. Laravel

Profundizando en el Contenedor IoC de Laravel

Scroll to top
Read Time: 11 min

() translation by (you can also view the original English article)

Inversión de control, o IoC, es una técnica que permite invertir el control en comparación con el código de procedimiento clásico. La forma más prominente de IoC es, por supuesto, la Inyección de Dependencia, o DI. El contenedor IoC de Laravel es una de las características más usadas de Laravel, aunque probablemente sea el menos comprendido.

He aquí un ejemplo muy rápido de la utilización de la inyección de dependencia para lograr la inversión de control.

1
<?php
2
3
class JeepWrangler
4
{
5
    public function __construct(Petrol $fuel)
6
    {
7
        $this->fuel = $fuel;
8
    }
9
  
10
    public function refuel($litres)
11
    {
12
	    return $litres * $this->fuel->getPrice();
13
    }
14
}
15
16
class Petrol
17
{
18
    public function getPrice()
19
    {
20
    	return 130.7;
21
    }
22
}
23
24
$petrol = new Petrol;
25
$car = new JeepWrangler($petrol);
26
27
$cost = $car->refuel(60);

Mediante el uso de inyección constructor hemos delegado la creación de nuestra instancia Petrol de nuevo a la persona que llama, logrando así la inversión de control. Nuestro JeepWrangler no necesita saber de dónde proviene Petrol, siempre que lo consiga.

Entonces, ¿qué tiene que ver todo esto con Laravel? Bastante, en realidad. Laravel, si no lo sabías, en realidad es un contenedor de IoC. Un contenedor es un objeto que, como es de esperar, contiene cosas. El contenedor IoC de Laravel se usa para contener muchos, muchos enlaces diferentes. Todo lo que haces en Laravel tendrá en algún momento una interacción con el contenedor de IoC. Esta interacción es generalmente en forma de una unión que se resuelve.

Si abre alguno de los proveedores de servicios existentes de Laravel, lo más probable es que vea algo como esto en el método de registro register (el ejemplo se ha simplificado mucho).

1
$this->app['router'] = $this->app->share(function($app) {
2
    return new Router;
3
});

Es un enlace muy, muy básico. Consiste en el nombre de la encuadernación (router) y un resolver (el cierre). Cuando el enlace se resuelve desde el contenedor, obtendremos una instancia de Router devuelta.    

Laravel normalmente agrupa nombres de enlace similares, como session y session.store.

Para resolver el enlace podemos simplemente llamar a un método directamente de él, o utilizar el método make en el contenedor.

1
$router = $this->app->make('router');

Eso es lo que hace el contenedor en su forma más básica. Pero, al igual que la mayoría de las cosas de Laravel, hay mucho más que limitarse a limitar y resolver las clases.

Fijaciones compartidas y no compartidas

Si ha examinado varios de los proveedores de servicios de Laravel, notará que la mayoría de los enlaces se definen como el ejemplo anterior. Aquí está de nuevo:

1
$this->app['router'] = $this->app->share(function($app) {
2
    return new Router;
3
});

Esta vinculación utiliza el método share compartido en el contenedor.  Laravel utiliza una variable estática para almacenar un valor resuelto anterior y simplemente reutilizará ese valor cuando un enlace se resuelva de nuevo. Esto es básicamente lo que hace el método share hace.

1
$this->app['router'] = function($app) {
2
    static $router;
3
     
4
    if (is_null($router)) {
5
        $router = new Router;
6
    }
7
     
8
    return $router;
9
     
10
};

Otra forma de escribir esto sería utilizar el método bindShared.

1
$this->app->bindShared('router', function($app) {
2
    return new Router;
3
});

También puede utilizar los métodos singleton y instance para lograr un enlace compartido. Por lo tanto, si todos logran lo mismo, ¿cuál es la diferencia? No mucho, en realidad. Yo personalmente prefiero usar el método bindShared.

Enlace condicional

Puede haber ocasiones en las que quiera unir algo al contenedor, pero sólo cuando no haya sido enlazado antes. Hay algunas maneras que usted podría ir sobre esto, pero el más fácil es utilizar el método bindIf.

1
$this->app->bindIf('router', function($app) {
2
    return new ImprovedRouter;
3
});

Esto sólo se enlazará al contenedor si la vinculación del enrutador router ya no existe.  Lo único que hay que tener en cuenta aquí es cómo compartir una vinculación condicional. Para ello, debe proporcionar un tercer parámetro al método bindIf con un valor de true.

Resolución de dependencia automática

Una de las características más utilizadas del contenedor IoC es su capacidad para resolver automáticamente las dependencias de las clases que no están enlazadas. ¿Que significa exactamente? En primer lugar, en realidad no es necesario vincular algo al contenedor para resolver una instancia. Podemos simplemente make una instancia de casi cualquier clase.

1
class Petrol
2
{
3
    public function getPrice()
4
    {
5
        return 130.7;
6
    }
7
}
8
9
// In our service provider...

10
$petrol = $this->app->make('Petrol');

El contenedor instanciará nuestra clase Petrol para nosotros. La mejor parte de esto es que también resolverá las dependencias del constructor para nosotros.

1
class JeepWrangler
2
{
3
    public function __construct(Petrol $fuel)
4
    {
5
        $this->fuel = $fuel;
6
    }
7
	
8
    public function refuel($litres)
9
    {
10
        return $litres * $this->fuel->getPrice();
11
    }
12
    
13
}
14
15
// In our service provider...

16
$car = $this->app->make('JeepWrangler');

Lo primero que hace el contenedor es inspeccionar las dependencias de la clase JeepWrangler. A continuación, intentará resolver esas dependencias. Por lo tanto, debido a que nuestro JeepWrangler tipo de pistas de la clase Petrol, el contenedor automáticamente resolver e inyectar como una dependencia.

El contenedor no puede inyectar automáticamente dependencias que no sean de tipo insinuado. Así que si una de sus dependencias es una matriz, entonces necesitará instanciarla manualmente o darle al parámetro un valor predeterminado.

Implementaciones vinculantes

Tener Laravel resolver automáticamente sus dependencias es grande y simplifica el proceso de instanciar las clases manualmente. Sin embargo, hay momentos en los que se desea inyectar una implementación específica, especialmente cuando se utilizan interfaces. Esto se logra fácilmente utilizando el nombre completo de la clase como enlace. Para demostrar esto usaremos una nueva interfaz llamada Fuel.

1
interface Fuel
2
{
3
    public function getPrice();
4
}

Ahora nuestra clase JeepWrangler puede teclear la interfaz, y nos aseguraremos de que nuestra clase Petrol implemente la interfaz.

1
class JeepWrangler
2
{
3
    public function __construct(Fuel $fuel)
4
    {
5
        $this->fuel = $fuel;
6
    }
7
	
8
    public function refuel($litres)
9
    {
10
        return $litres * $this->fuel->getPrice();
11
    }
12
}
13
14
class Petrol implements Fuel
15
{
16
    public function getPrice()
17
    {
18
        return 130.7;
19
    }
20
}

Ahora podemos unir nuestra interfaz de Fuel al contenedor y hacer que resuelva una nueva instancia de Petrol.

1
$this->app->bind('Fuel', 'Petrol');
2
3
// Or, we could instantiate it ourselves.

4
$this->app->bind('Fuel', function ($app) {
5
    return new Petrol;
6
});

Ahora cuando hacemos una nueva instancia de nuestro JeepWrangler, el contenedor verá que pide Fuel, y sabrá inyectar automáticamente una instancia Petrol.

Esto también hace que sea realmente fácil intercambiar una implementación, ya que simplemente podemos cambiar el enlace en el contenedor. Para demostrarlo, podríamos empezar a recargar nuestro coche con gasolina premium, que es un poco más caro.

1
class PremiumPetrol implements Fuel
2
{
3
    public function getPrice()
4
    {
5
        return 144.3;
6
    }
7
}
8
9
// In our service provider...

10
$this->app->bind('Fuel', 'PremiumPetrol');

Vinculaciones contextuales

Tenga en cuenta que los enlaces contextuales sólo están disponibles en Laravel 5.

Una vinculación contextual le permite enlazar una implementación (como lo hicimos anteriormente) a una clase específica.

1
abstract class Car
2
{
3
    public function __construct(Fuel $fuel)
4
    {
5
        $this->fuel = $fuel;
6
    }
7
8
    public function refuel($litres)
9
    {
10
        return $litres * $this->fuel->getPrice();
11
    }
12
}

A continuación, crearemos una nueva clase NissanPatrol que amplíe la clase abstracta y actualizaremos nuestro JeepWrangler para extenderlo también.

1
class JeepWrangler extends Car
2
{
3
    //

4
}
5
6
class NissanPatrol extends Car
7
{
8
    //

9
}

Por último crearemos una nueva clase Diesel que implementa la interfaz Fuel.

1
class Diesel implements Fuel
2
{
3
    public function getPrice()
4
    {
5
        return 135.3;
6
    }
7
}

Ahora, nuestro Jeep Wrangler se reposta con gasolina y nuestro Nissan Patrol se reposta con diesel. Si tratamos de usar el mismo método que antes vinculando una implementación a la interfaz, entonces ambos coches tendrían el mismo tipo de combustible, que no es lo que queremos.

Así que para asegurarse de que cada coche se rellena con el combustible correcto, podemos informarle al contenedor qué implementación utilizar en cada contexto.

1
$this->app->when('JeepWrangler')->needs('Fuel')->give('Petrol');
2
$this->app->when('NissanPatrol')->needs('Fuel')->give('Diesel');

Etiquetado

Tenga en cuenta que el etiquetado sólo está disponible en Laravel 5.

Ser capaz de resolver enlaces desde el contenedor es muy importante. Normalmente sólo podemos resolver algo si sabemos cómo se ha ligado al contenedor. Con Laravel 5, ahora somos capaces de etiquetar nuestros enlaces para que los desarrolladores puedan resolver fácilmente todos los enlaces que tengan la misma etiqueta.

Si está desarrollando una aplicación que permite a otros desarrolladores crear complementos y desea poder resolver fácilmente todos esos complementos, las etiquetas serán extremadamente útiles.

1
$this->app->tag('awesome.plugin', 'plugin');
2
3
// Or an array of tags.

4
5
$tags = ['plugin', 'theme'];
6
7
$this->app->tag('awesome.plugin', $tags);

Ahora, para resolver todos los enlaces de una etiqueta dada, podemos usar el método etiquetado tagged .

1
$plugins = $this->app->tagged('plugin');
2
3
foreach ($plugins as $plugin) {
4
    $plugin->doSomethingFunky();
5
}

Rebotes y Rebinding

Cuando se enlaza algo al contenedor con el mismo nombre más de una vez, se llama rebinding. Laravel se dará cuenta de que algo vinculante de nuevo y provocará un rebound.

El mayor beneficio aquí es cuando se está desarrollando un paquete que permite a otros desarrolladores para ampliarlo por volver a conectar los componentes en el contenedor. Para usarlo, tendremos que implementar la inyección de setter Car en nuestro abstract.

1
abstract class Car
2
{
3
    public function __construct(Fuel $fuel)
4
    {
5
        $this->fuel = $fuel;
6
    }
7
8
    public function refuel($litres)
9
    {
10
        return $litres * $this->fuel->getPrice();
11
    }
12
    
13
    public function setFuel(Fuel $fuel)
14
    {
15
        $this->fuel = $fuel;
16
    }
17
    
18
}

Supongamos que estamos vinculando nuestro JeepWrangler al contenedor como tal.

1
$this->app->bindShared('fuel', function ($app) {
2
    return new Petrol;
3
});
4
5
$this->app->bindShared('car', function ($app) {
6
	return new JeepWrangler($app['fuel']);
7
});

Esto es perfectamente bien, pero digamos que otro desarrollador viene a lo largo y quiere extender esto y utilizar gasolina premium en el coche. Así que utilizan el método setFuel para inyectar su nuevo combustible en el coche.

1
$this->app['car']->setFuel(new PremiumPetrol);

En la mayoría de los casos, esto podría ser todo lo que se necesita; Sin embargo, ¿qué pasa si nuestro paquete se vuelve más complicado y el fuel vinculante se inyecta en varias otras clases? Esto haría que el otro desarrollador tuviera que establecer su nueva instancia un montón de veces. Por lo tanto, para resolver esto, podemos hacer uso de rebinding:

1
$this->app->bindShared('car', function ($app) {
2
    return new JeepWrangler($app->rebinding('fuel', function ($app, $fuel) {
3
		$app['car']->setFuel($fuel);
4
	}));
5
});

El método de rebinding nos devolverá inmediatamente la instancia ya enlazada para que podamos usarla en el constructor de nuestro JeepWrangler. El cierre dado al método de rebinding recibe dos parámetros, siendo el primero el contenedor IoC y el segundo el nuevo binding. A continuación, podemos utilizar el método setFuel nosotros mismos para inyectar el nuevo enlace en nuestra instancia JeepWrangler.

Todo lo que queda es que otro desarrollador simplemente vuelva a conectar combustible fuel en el contenedor.  Su proveedor de servicios podría ser así:

1
$this->app->bindShared('fuel', function () {
2
    return new PremiumPetrol;
3
});

Una vez que la unión se rebote en el contenedor, Laravel activará automáticamente los cierres asociados closures. En nuestro caso, la nueva instancia de PremiumPetrol se establecerá en nuestra instancia de JeepWrangler.

Extensión

Si desea inyectar una dependencia en uno de los enlaces principales o un enlace creado por un paquete, el método extend en el contenedor es una de las formas más sencillas de hacerlo.

Este método resolverá el enlace del contenedor y ejecutará un cierre con el contenedor y la instancia resuelta como parámetros. Esto le permite resolver fácilmente e inyectar sus propios enlaces, o simplemente instanciar una nueva clase e inyectarla.

1
$this->app->extend('car', function ($app, $car) {
2
    $car->setFuel(new PremiumPetrol);
3
});

A diferencia de rebinding, esto sólo establecerá la dependencia de un solo enlace.

Uso fuera de Laravel

Al igual que muchos de los componentes de iluminación que componen el marco de Laravel, el contenedor se puede utilizar fuera de Laravel en una aplicación independiente. Para ello, primero debe solicitarlo como una dependencia en su archivo composer.json.

1
{
2
    "require": {
3
        "illuminate/container": "4.2.*"
4
   }
5
}

Esto instalará la versión 4.2 más reciente del contenedor. Ahora, todo lo que queda por hacer es instanciar un nuevo contenedor.

1
require 'vendor/autoload.php';
2
3
$app = new Illuminate\Container\Container;
4
5
$app->bindShared('car', function () {
6
    return new JeepWrangler;
7
});

De todos los componentes, este es uno de los más fáciles de usar dondequiera que necesite un contenedor IoC flexible y destacado.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.