Laravel’s service container is a powerful tool for managing class dependencies and performing dependency injection. Dependency injection means that class dependencies are “injected” into the class through its constructor or, in some cases, setter methods.Consider this example:
<?phpnamespace App\Http\Controllers;use App\Services\AppleMusic;use Illuminate\View\View;class PodcastController extends Controller{ public function __construct( protected AppleMusic $apple, ) {} public function show(string $id): View { return view('podcasts.show', [ 'podcast' => $this->apple->findPodcast($id) ]); }}
The PodcastController needs to retrieve podcasts from a data source. By injecting an AppleMusic service, you can swap in a mock during tests without changing the controller code.
A deep understanding of the service container is essential for building large Laravel applications and contributing to the Laravel core.
When a class has no dependencies on interfaces—only concrete classes—you don’t need to tell the container how to resolve it. For example:
<?phpclass Service{ // ...}Route::get('/', function (Service $service) { dd($service::class);});
Laravel automatically resolves the Service class and injects it into the route handler. No configuration needed.Most classes you write in a Laravel application—controllers, event listeners, middleware—have their dependencies automatically injected by the container.
Use bind to register a class or interface with a resolver 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));});
The closure receives the container itself, so you can resolve sub-dependencies from within it.To interact with the container outside a service provider, use the App facade:
use App\Services\Transistor;use Illuminate\Contracts\Foundation\Application;use Illuminate\Support\Facades\App;App::bind(Transistor::class, function (Application $app) { // ...});
You only need to bind a class if it depends on an interface. Classes that depend only on concrete types are resolved automatically through reflection.
Use singleton to bind a class or interface that should only be resolved once. Subsequent calls to the container return the same instance:
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));});
Bind an existing object instance to the container. Every subsequent resolution returns the exact same object:
use App\Services\Transistor;use App\Services\PodcastParser;$service = new Transistor(new PodcastParser);$this->app->instance(Transistor::class, $service);
One of the container’s most powerful features is binding an interface to a concrete implementation:
use App\Contracts\EventPusher;use App\Services\RedisEventPusher;$this->app->bind(EventPusher::class, RedisEventPusher::class);
Now whenever the container needs an EventPusher, it injects a RedisEventPusher. Type-hint the interface in your constructor and the container handles the rest:
use App\Contracts\EventPusher;public function __construct( protected EventPusher $pusher,) {}
Depending on interfaces rather than concrete classes means you can swap implementations without touching the consumer code—a huge win for testability.
In practice, you rarely call make directly. Instead, type-hint dependencies in your constructors and let the container inject them automatically when it resolves controllers, jobs, listeners, and other framework-managed classes.
Here’s a complete example showing how to wire up a payment gateway with the container:
1
Define an interface
<?phpnamespace App\Contracts;interface PaymentGateway{ public function charge(int $amount, string $token): bool;}
2
Create a concrete implementation
<?phpnamespace App\Services;use App\Contracts\PaymentGateway;class StripePaymentGateway implements PaymentGateway{ public function charge(int $amount, string $token): bool { // Call the Stripe API... return true; }}
3
Bind in a service provider
use App\Contracts\PaymentGateway;use App\Services\StripePaymentGateway;$this->app->singleton(PaymentGateway::class, StripePaymentGateway::class);
4
Inject into a controller
<?phpnamespace 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 ); // ... }}
To switch from Stripe to a different provider, change the binding in one place. Every class that depends on PaymentGateway gets the new implementation automatically.
The container fires an event each time it resolves an object. Listen to this event using resolving:
use App\Services\Transistor;use Illuminate\Contracts\Foundation\Application;$this->app->resolving(Transistor::class, function (Transistor $transistor, Application $app) { // Called whenever the container resolves a Transistor instance...});
You can also listen to all resolved objects:
$this->app->resolving(function (mixed $object, Application $app) { // Called whenever the container resolves any type...});
Laravel’s facades provide a static interface to objects held in the container. For example, Cache::get() internally retrieves the Cache service from the container:
use Illuminate\Support\Facades\Cache;// Via facadeCache::get('key');// Equivalent direct container callapp('cache')->get('key');
Facades are convenient wrappers around the container. During tests, you can replace them with mocks:
use Illuminate\Support\Facades\Cache;Cache::shouldReceive('get') ->once() ->with('key') ->andReturn('value');
Facades
Learn how facades work under the hood and when to use them.