Facades are a common and convenient practice in Laravel development.
No need to import services manually — just call Cache::get()
and go.
But do you really know what’s happening behind the scenes?
In this post, we’ll break it down:
1. How Facades Work Under the Hood
A facade is just a static wrapper around a service in the Laravel Service Container.
Cache::get('key');
is essentially the same as:
app('cache')->get('key'); // or via dependency injection: $cache->get('key');
So, facades aren’t really static — they resolve real objects from the container dynamically.
2. What’s the Catch?
Facades are convenient, but:
- Harder to test — mocking with
Cache::shouldReceive()
orMail::fake()
can be unintuitive; - Hidden dependencies — classes don’t explicitly declare what they rely on;
- Weak IoC principles — swapping implementations becomes more difficult.
3. When Should You Use Dependency Injection Instead?
Prefer DI when you:
- Want testable, flexible code;
- Work with multiple implementations of a contract;
- Need to clearly express a class’s dependencies.
use Illuminate\Contracts\Cache\Repository as Cache; class UserService { public function __construct(private Cache $cache) {} public function getUser(int $id) { return $this->cache->remember("user:$id", 60, fn() => User::find($id)); } }
4. Refactoring from Facade to Dependency Injection
Before:
class UserService { public function getUser(int $id) { return Cache::get("user:$id"); } }
After:
use Illuminate\Contracts\Cache\Repository as Cache; class UserService { public function __construct(private Cache $cache) {} public function getUser(int $id) { return $this->cache->get("user:$id"); } }
TL;DR
Facades are fast and easy to use.
But for scalable, testable, and maintainable architecture — consider dependency injection instead.
Do you use facades in your Laravel projects? Share your thoughts in the comments!