In Laravel, what is the difference between using the Cache facade, the cache() helper and an injected Illuminate\Contracts\Cache\Repository?
answer
- same container, three doorways
- helper calls app('cache')
- cache.store alias for the contract
- constructor makes the dependency visible
- Factory contract when you need store()
basics
~20 sAll three reach the same container-managed cache service, so behaviour matches. The facade and cache() helper call it from inside method bodies; an injected Repository contract declares the dependency in the constructor, which is explicit and framework-portable.
solid answer
~40 sThey are three routes to the same service container. `Cache::get('k')` resolves the `'cache'` binding, a `CacheManager`, and forwards to the default store. The `cache('k')` helper is a function that runs `app('cache')->get('k')`, so it is the same object without an import. Type-hinting `Illuminate\Contracts\Cache\Repository` in a constructor lets auto-wiring inject the `'cache.store'` binding, the default store's `Repository`. Data and behaviour are identical; the difference is where the dependency is declared. With the facade or helper it is hidden in a method body; with the contract it is in the constructor, so the class's needs are visible and it depends only on an `illuminate/contracts` interface. If you need to pick stores with `store('redis')`, inject `Illuminate\Contracts\Cache\Factory` instead of `Repository`.
code
php · 26 lines<?php
namespace App\Crm;
use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;
class AccountSummary
{
public function __construct(private Repository $cache) {}
public function viaContract(int $id): mixed
{
return $this->cache->get("account:{$id}");
}
public function viaFacade(int $id): mixed
{
return Cache::get("account:{$id}");
}
public function viaHelper(int $id): mixed
{
return cache("account:{$id}");
}
}go deeper
Know the three styles by sight and that they all reach the same cache service through the container.
Explain which binding each resolves: cache for the facade and helper, cache.store for the Repository contract, and when Factory is the right type-hint.
Argue when explicit constructor dependencies pay off, such as domain services and packages, and when facade brevity in controllers is acceptable.
Set a consistent convention per layer so the codebase reads uniformly, rather than letting each developer pick a style file by file.
## One service, three ways in Laravel registers its cache system in the **service container** (the object that builds and shares services). `CacheServiceProvider` binds two singletons: `'cache'`, an `Illuminate\Cache\CacheManager` that manages all configured stores, and `'cache.store'`, the `Illuminate\Cache\Repository` for the default store named in `config('cache.default')`. The skeleton's `.env.example` sets `CACHE_STORE=database`. The container also registers **aliases**, so asking for `Illuminate\Contracts\Cache\Factory` returns the `'cache'` manager and asking for `Illuminate\Contracts\Cache\Repository` returns the `'cache.store'` repository. A **contract** in Laravel is simply an interface from the `Illuminate\Contracts` namespace that describes a core service. | Style | Example | Resolves | Dependency is declared | |---|---|---|---| | Facade | `Cache::get('k')` | `'cache'` (`CacheManager`, forwarding to default store) | nowhere; only a `use` import | | Helper | `cache('k')` | `app('cache')`, then `get('k')` | nowhere; no import needed | | Injected contract | `__construct(private Repository $cache)` | `'cache.store'` (`Repository`) | in the constructor signature | ## How each one works - **Facade.** `Illuminate\Support\Facades\Cache` has no `get` method. Its inherited `__callStatic()` resolves the `'cache'` binding and calls `get()` on it; `CacheManager` forwards that to the default store. - **Helper.** `cache()` is a global function in `Illuminate/Foundation/helpers.php`. With no arguments it returns `app('cache')`; with a string it returns `app('cache')->get($key, $default)`; with an array it calls `put()` using the second argument as the TTL. Many helpers mirror a facade this way: `view()`, `config()`, `response()`. - **Injected contract.** When the container builds a controller, job, listener or service, it reads constructor type-hints and resolves each one. `Repository $cache` receives the default store's repository. ## What differs, and what does not What does **not** differ: 1. The underlying object and data. All three talk to the same singletons, so a value written with the facade is readable through the helper and the injected repository. 2. Testability in feature tests. Laravel's documentation states there is "absolutely no practical difference between facades and helper functions", and the facade's test tooling works for helper calls too because both resolve the same container binding. What **does** differ: - **Visibility of dependencies.** A constructor that lists `Repository $cache` tells a reader and a static-analysis tool exactly what the class needs. Facade and helper calls are discovered only by reading the method bodies. - **Coupling.** An injected contract ties the class to an interface in `illuminate/contracts`, which a package can depend on without the full framework. A facade ties the class to `Illuminate\Support\Facades` and to a booted Laravel application. - **Choosing a store.** The injected `Repository` is already the default store. To call `store('redis')` you need the manager, so inject `Illuminate\Contracts\Cache\Factory` (the facade's equivalent contract) instead. - **Imports and typing.** The helper needs no import at all; the facade needs one `use` line; the contract gives a real typed property your editor understands without facade docblocks. ## A common slip Type-hinting the facade class itself — `__construct(private Cache $cache)` with `Illuminate\Support\Facades\Cache` — does not inject the cache. The container builds an instance of the facade class, and calling `$this->cache->get()` fails with an undefined-method error, because facades only forward **static** calls. Inject the contract, or keep using the facade statically. A second slip is type-hinting the concrete class when a contract exists. Asking for `Illuminate\Cache\Repository` works, because the container aliases it to `'cache.store'` too, but it ties the class to the framework's implementation rather than to the interface. The contract is the more stable choice, and it is what the documentation's contract reference lists beside each facade. ## Other services follow the same pattern The cache is only one example. Most framework services have all three doorways: | Service | Facade | Helper | Contract to inject | |---|---|---|---| | Configuration | `Config::get('app.name')` | `config('app.name')` | `Illuminate\Contracts\Config\Repository` | | Views | `View::make('profile')` | `view('profile')` | `Illuminate\Contracts\View\Factory` | | Events | `Event::dispatch($e)` | `event($e)` | `Illuminate\Contracts\Events\Dispatcher` | Not every facade has a helper, and not every contract has a facade, but where both exist they normally resolve the same container binding. ## Choosing in practice Controllers, route closures and small glue code often use facades or helpers for brevity. Services with real logic, and anything shipped as a package, tend to inject contracts so their collaborators are explicit. Laravel's documentation treats this as a team preference: both styles produce working, testable applications as long as classes stay focused.
- When would you type-hint Illuminate\Contracts\Cache\Factory rather than Repository?When the class must choose between stores at runtime, for example `->store('redis')` for hot data and the default store for the rest. `Factory` resolves to the `CacheManager`, which exposes `store()`; `Repository` is already bound to one store, the default, so it cannot switch.
- Does the cache() helper create a new cache object on every call?No. It calls `app('cache')`, and `'cache'` is registered as a singleton, so the container returns the same `CacheManager` each time within the application instance. The helper is a shortcut over the container, not a separate cache implementation.
saying these in an interview costs you the question
- Helpers bypass the container and build a fresh service each call
- Facade calls cannot be covered by tests, only injected contracts can
- Injecting the Cache facade class gives you the cache repository
- The facade and the contract talk to different cache stores
- Facades are faster because static calls skip container resolution