
Alright, I’ll be honest: when I first saw someone using dependency injection in a Closure-based Artisan command in Laravel, I thought,
“Wait… that works?”
I’d always assumed that kind of magic was reserved for full-blown command classes. You know, the ones you generate with php artisan make:command
. Turns out, Closure commands are way more powerful than I gave them credit for.
So if you’re like me — someone who enjoys keeping things simple and snappy when possible — here’s how you can use Dependency Injection in Laravel Closure Commands, and why I now use it more than I expected.
First: What Are Closure Commands Again?
Quick recap if you’re new to the term.
Closure commands are those artisan commands you define directly inside routes/console.php
. No need to create a separate command class unless your logic is super complex.
Example:
Artisan::command('say:hi {name}', function ($name) {
$this->info("Hey there, $name!");
});
Super handy for quick scripts, batch jobs, or tools you don’t plan to reuse across multiple apps.
But here’s what blew my mind: you can inject services into them, just like controller methods or job constructors.
Okay, But Why Use Dependency Injection Here?
Because it’s clean.
Because it saves you from calling app(MyService::class)
manually.
And because Laravel already gives you a world-class container — might as well use it, right?
Here’s what I mean:
Artisan::command('reports:generate', function (ReportService $service) {
$service->generateMonthly();
$this->info("Monthly report generated.");
});
Boom. Laravel automatically resolves ReportService
from the container and hands it to your closure.
No extra code, no mess.
Real Example: Notifying Users (The Dirty Way vs. Clean Way)
Let’s say you’re building a command to notify all active users.
The Dirty Way (No DI)
Artisan::command('users:notify', function () {
$service = new \App\Services\NotificationService();
$users = \App\Models\User::where('active', 1)->get();
foreach ($users as $user) {
$service->send($user);
}
$this->info("Notified all active users.");
});
The Clean Way (With DI)
use App\Services\NotificationService;
Artisan::command('users:notify', function (NotificationService $notifier) {
$users = \App\Models\User::where('active', 1)->get();
foreach ($users as $user) {
$notifier->send($user);
}
$this->info("All users have been notified.");
});
Cleaner. More testable. Easier to maintain if your service ever needs to change.
How Laravel Makes This Work
Laravel’s container (a.k.a. the Service Container) is the one doing the magic here.
It sees the type-hinted parameters in your closure and says, “Oh, I know how to build that,” — as long as it’s registered in the container (which most of your app services already are).
You can even inject built-in stuff like:
Illuminate\Console\Scheduling\Schedule
Illuminate\Contracts\Bus\Dispatcher
- or even your config repository.
Can You Inject Multiple Dependencies? Yup.
Here’s a sample I actually used:
use App\Services\SyncService;
use Illuminate\Contracts\Cache\Repository as Cache;
Artisan::command('sync:run', function (SyncService $sync, Cache $cache) {
if ($cache->get('sync:running')) {
$this->warn("Sync already running...");
return;
}
$sync->run();
$this->info("Sync complete.");
});
No boilerplate. Laravel figures it out.
Honestly, it still feels kinda magical every time it works.
Gotchas You Should Know
So yeah, this is cool — but here are a few things to watch out for:
1. Don’t Go Overboard
If your Closure command starts ballooning with logic, maybe it’s time to turn it into a real class.
I use a personal rule of thumb:
“If the command is more than 40 lines, it gets its own file.”
2. No Return Types
Unlike controllers or service classes, you can’t really return stuff from these closures (not in a useful way).
So keep them procedural: log things, trigger events, queue jobs, etc.
3. Service Not Resolving?
Sometimes, Laravel will throw a “target class does not exist” error.
That usually means:
- You typo’d the class name
- You forgot to bind it in the container
- You injected an interface without telling Laravel how to build it
You can fix that in a service provider:
$this->app->bind(
App\Contracts\MyContract::class,
App\Services\MyConcreteService::class
);
Final Thoughts
To be honest, I used to think Closure commands were only for “quick-and-dirty” scripts. You know, the kind you write once, run once, then forget.
But with dependency injection, they’ve become something I reach for way more often.
They’re fast to write, readable, and work surprisingly well for all kinds of scheduled jobs or cron-based scripts.
So if you haven’t tried injecting services into your Laravel Closure commands — give it a go.
Feels like a mini power-up for something that used to be basic.
Pro Tip:
Don’t forget you can still use things like @inject
in Blade or service injection in Jobs, Listeners, etc. Laravel’s container is everywhere — Closure commands are just another way to take advantage of it.