Saltar al contenido principal

¿Qué es el contenedor de servicios?

El contenedor de servicios de Laravel gestiona las dependencias de las clases y realiza la inyección de dependencias. Con “inyección de dependencias” nos referimos a “inyectar” en una clase las dependencias que necesita, ya sea a través del constructor o, según el caso, mediante métodos setter. Fíjate en este ejemplo:
<?php

namespace App\Http\Controllers;

use App\Services\AppleMusic;
use Illuminate\View\View;

class PodcastController extends Controller
{
    /**
     * Crea una nueva instancia del controlador
     */
    public function __construct(
        protected AppleMusic $apple,
    ) {}

    /**
     * Muestra la información del podcast indicado
     */
    public function show(string $id): View
    {
        return view('podcasts.show', [
            'podcast' => $this->apple->findPodcast($id)
        ]);
    }
}
Aquí PodcastController necesita obtener un podcast desde una fuente como Apple Music. Por eso inyectamos un servicio capaz de obtener podcasts. Al inyectarlo, resulta muy fácil sustituirlo por un mock durante las pruebas.
Entender bien el contenedor de servicios es imprescindible para construir aplicaciones Laravel grandes. También ayuda si vas a contribuir al propio núcleo de Laravel.

Resolución sin configuración

Si una clase solo depende de otras clases concretas (no de interfaces), no hace falta indicar al contenedor cómo resolverla. Por ejemplo, imagina este código en routes/web.php:
<?php

class Service
{
    // ...
}

Route::get('/', function (Service $service) {
    dd($service::class);
});
Este ejemplo define la clase en el archivo de rutas por motivos didácticos. En una aplicación real, define las clases de servicio en el directorio app/Services.
Al acceder a la ruta, Laravel resuelve automáticamente la clase Service y la inyecta en el manejador. Puedes disfrutar de la inyección de dependencias sin ningún archivo de configuración. Muchas de las clases que escribes en una aplicación Laravel (controladores, listeners de eventos, middleware, etc.) reciben sus dependencias por inyección automática a través del contenedor.

Bindings

Binding básico

La mayoría de bindings se registran dentro de un service provider. Dentro de él accedes al contenedor con $this->app.

bind

Con bind registras un binding pasando el nombre de la clase/interfaz y una closure.
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->bind(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});
La closure recibe el propio contenedor como argumento y puedes usarlo para resolver sub-dependencias. Si necesitas interactuar con el contenedor fuera de un service provider, usa la fachada App.
use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;

App::bind(Transistor::class, function (Application $app) {
    // ...
});
Las clases que no dependen de interfaces no necesitan ser registradas en el contenedor. El contenedor puede resolverlas automáticamente mediante reflexión.

singleton

singleton registra un binding que se resuelve una única vez. Una vez resuelto, las llamadas posteriores al contenedor devuelven la misma instancia.
use App\Services\Transistor;
use App\Services\PodcastParser;
use Illuminate\Contracts\Foundation\Application;

$this->app->singleton(Transistor::class, function (Application $app) {
    return new Transistor($app->make(PodcastParser::class));
});

instance

También puedes registrar una instancia ya existente con instance. Las llamadas posteriores devolverán siempre esa instancia.
use App\Services\Transistor;
use App\Services\PodcastParser;

$service = new Transistor(new PodcastParser);

$this->app->instance(Transistor::class, $service);

Binding de interfaces a implementaciones

Una de las capacidades más potentes del contenedor es enlazar una interfaz con una implementación concreta. Imagina que tienes la interfaz EventPusher y la implementación RedisEventPusher.
use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;

$this->app->bind(EventPusher::class, RedisEventPusher::class);
Con esto, el contenedor inyecta RedisEventPusher en las clases que necesitan una implementación de EventPusher. Solo tienes que tipar la interfaz en el constructor.
use App\Contracts\EventPusher;

/**
 * Crea una nueva instancia de la clase
 */
public function __construct(
    protected EventPusher $pusher,
) {}
Depender de interfaces te permite cambiar la implementación sin modificar el código. Facilita las pruebas y los cambios futuros.

Resolución automática (DI por type-hint)

Cuando el contenedor resuelve clases como controladores, listeners o middlewares, mira los type-hints del constructor e inyecta las dependencias automáticamente.
<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * Crea una nueva instancia del controlador
     */
    public function __construct(
        protected UserRepository $users,
    ) {}
}
Si UserRepository no depende de interfaces, no hace falta registrarlo en el contenedor. Basta con acceder a la ruta y el contenedor resolverá e inyectará las dependencias en el controlador.

Resolver desde el contenedor

El método make

Con make puedes resolver una instancia desde el contenedor.
use App\Services\Transistor;

$transistor = app()->make(Transistor::class);
Si alguna dependencia de la clase no se puede resolver desde el contenedor, usa makeWith para pasar argumentos adicionales.
$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);

Inyección automática

En la práctica, casi nunca llamarás a make directamente. Basta con tipar las dependencias en el constructor de las clases que el contenedor resuelve (controladores, listeners, middleware…) y el contenedor las inyectará automáticamente.

Relación entre facades y contenedor

Las facades de Laravel ofrecen una interfaz estática hacia objetos del contenedor. Por ejemplo, Cache::get() obtiene internamente el servicio Cache del contenedor y lo invoca.
use Illuminate\Support\Facades\Cache;

// Llamada usando la fachada
Cache::get('key');

// Llamada equivalente usando el contenedor
app('cache')->get('key');
Las facades son envoltorios cómodos alrededor del contenedor. Durante las pruebas puedes sustituir la fachada por un mock.
use Illuminate\Support\Facades\Cache;

Cache::shouldReceive('get')
    ->once()
    ->with('key')
    ->andReturn('value');

Caso práctico de inyección por constructor

Veamos un patrón habitual en aplicaciones reales.
1

Definir la interfaz

<?php

namespace App\Contracts;

interface PaymentGateway
{
    public function charge(int $amount, string $token): bool;
}
2

Crear la clase de implementación

<?php

namespace App\Services;

use App\Contracts\PaymentGateway;

class StripePaymentGateway implements PaymentGateway
{
    public function charge(int $amount, string $token): bool
    {
        // Cobro con la API de Stripe...
        return true;
    }
}
3

Registrar el binding en un service provider

use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;

$this->app->singleton(PaymentGateway::class, StripePaymentGateway::class);
4

Recibir la inyección en el controlador

<?php

namespace App\Http\Controllers;

use App\Contracts\PaymentGateway;
use Illuminate\Http\Request;

class OrderController extends Controller
{
    public function __construct(
        protected PaymentGateway $payment,
    ) {}

    public function store(Request $request)
    {
        $this->payment->charge(
            $request->amount,
            $request->payment_token
        );

        // ...
    }
}
Con este patrón, si cambias de proveedor de pago (por ejemplo, sustituyes Stripe por otro), basta con cambiar el binding en un único lugar.

Próximos pasos

Service providers

Aprende a registrar bindings usando service providers.
Última modificación el 13 de julio de 2026