Vai al contenuto principale

Cos’è il service container

Il service container di Laravel è il meccanismo con cui vengono gestite le dipendenze delle classi e viene effettuata la dependency injection. Per dependency injection si intende “iniettare” nella classe le dipendenze di cui ha bisogno, tramite il costruttore o, in alcuni casi, tramite metodi setter. Osserva l’esempio seguente.
<?php

namespace App\Http\Controllers;

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

class PodcastController extends Controller
{
    /**
     * Crea una nuova istanza del controller
     */
    public function __construct(
        protected AppleMusic $apple,
    ) {}

    /**
     * Mostra le informazioni del podcast indicato
     */
    public function show(string $id): View
    {
        return view('podcasts.show', [
            'podcast' => $this->apple->findPodcast($id)
        ]);
    }
}
In questo esempio, PodcastController deve recuperare un Podcast da una fonte dati come Apple Music. Per farlo inietta un servizio in grado di ottenerlo. Iniettando il servizio, nei test puoi sostituire facilmente AppleMusic con un mock (implementazione fittizia).
Comprendere a fondo il service container è essenziale per costruire applicazioni Laravel di grandi dimensioni. Torna utile anche per contribuire al core di Laravel.

Risoluzione zero-configuration

Se una classe dipende solo da altre classi concrete (non da interfacce), non devi indicare al container come risolverle. Ad esempio, supponi di scrivere questo codice in routes/web.php.
<?php

class Service
{
    // ...
}

Route::get('/', function (Service $service) {
    dd($service::class);
});
In questo esempio la classe è definita nel file di route, ma è a scopo dimostrativo. Nelle applicazioni reali definisci le classi di servizio nella directory app/Services.
Quando accedi alla route, Laravel risolve automaticamente la classe Service e la inietta nell’handler della route. Ottieni i benefici della dependency injection senza dover preparare file di configurazione. Molte delle classi che scrivi in un’applicazione Laravel — controller, event listener, middleware, ecc. — ricevono automaticamente le dipendenze iniettate tramite il container.

Binding

Binding di base

La maggior parte dei binding si registra all’interno dei service provider. Nei service provider puoi accedere al container tramite $this->app.

bind

Con il metodo bind registri un binding passando il nome della classe o dell’interfaccia e 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));
});
Come argomento della closure ricevi il container stesso, che puoi usare per risolvere sotto-dipendenze. Per interagire con il container al di fuori dei service provider usa la facade App.
use App\Services\Transistor;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Support\Facades\App;

App::bind(Transistor::class, function (Application $app) {
    // ...
});
Non è necessario fare il binding delle classi che non dipendono da interfacce. Il container le risolve automaticamente tramite reflection.

singleton

Il metodo singleton esegue il binding facendo in modo che la classe o l’interfaccia venga risolta una sola volta. Ogni chiamata successiva al container restituirà la stessa istanza.
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

Puoi anche fare il binding di un’istanza già esistente con il metodo instance. Ogni chiamata successiva restituirà quella stessa istanza.
use App\Services\Transistor;
use App\Services\PodcastParser;

$service = new Transistor(new PodcastParser);

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

Binding di un’interfaccia a un’implementazione

Una funzionalità potente del service container è la possibilità di legare un’interfaccia a un’implementazione specifica. Ad esempio, supponi di avere l’interfaccia EventPusher e l’implementazione RedisEventPusher.
use App\Contracts\EventPusher;
use App\Services\RedisEventPusher;

$this->app->bind(EventPusher::class, RedisEventPusher::class);
In questo modo il container inietta RedisEventPusher nelle classi che richiedono un’implementazione di EventPusher. Ti basta fare il type hint di EventPusher nel costruttore.
use App\Contracts\EventPusher;

/**
 * Crea una nuova istanza della classe
 */
public function __construct(
    protected EventPusher $pusher,
) {}
Dipendendo dall’interfaccia, cambiare implementazione non richiede modifiche al codice. Facilita test e cambiamenti futuri.

Risoluzione automatica (DI tramite type hint)

Quando il service container risolve classi come controller, event listener e middleware, guarda il type hint del costruttore e inietta automaticamente le dipendenze.
<?php

namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController extends Controller
{
    /**
     * Crea una nuova istanza del controller
     */
    public function __construct(
        protected UserRepository $users,
    ) {}
}
Se UserRepository non dipende da un’interfaccia, non è necessario registrarlo nel container. Basta accedere alla route e il container risolverà e inietterà automaticamente la dipendenza nel controller.

Risoluzione dal container

Metodo make

Con il metodo make puoi risolvere un’istanza di classe dal container.
use App\Services\Transistor;

$transistor = app()->make(Transistor::class);
Se una dipendenza non può essere risolta dal container, puoi passare argomenti aggiuntivi con makeWith.
$transistor = $this->app->makeWith(Transistor::class, ['id' => 1]);

Iniezione automatica

In pratica raramente chiamerai direttamente make. Aggiungi il type hint nel costruttore delle classi che il container risolve (controller, event listener, middleware, ecc.) e il container si occuperà dell’iniezione.

Relazione tra facade e container

Le facade di Laravel forniscono un’interfaccia statica agli oggetti presenti nel container. Ad esempio, Cache::get() internamente ottiene il servizio Cache dal container e lo chiama.
use Illuminate\Support\Facades\Cache;

// Chiamata tramite facade
Cache::get('key');

// Chiamata equivalente diretta al container
app('cache')->get('key');
Le facade sono comodi wrapper del container. Nei test puoi sostituirle con mock.
use Illuminate\Support\Facades\Cache;

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

Esempio pratico di constructor injection

Ecco un pattern tipico in un’applicazione reale.
1

Definisci l'interfaccia

<?php

namespace App\Contracts;

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

Crea la classe di implementazione

<?php

namespace App\Services;

use App\Contracts\PaymentGateway;

class StripePaymentGateway implements PaymentGateway
{
    public function charge(int $amount, string $token): bool
    {
        // Logica di pagamento tramite l'API Stripe...
        return true;
    }
}
3

Effettua il binding in un service provider

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

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

Ricevi l'iniezione nel controller

<?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 questo pattern, per passare da Stripe a un altro provider basta modificare il binding in un unico punto.

Prossimi passi

Service provider

Impara come registrare i binding usando i service provider.
Ultima modifica il 13 luglio 2026