In Laravel, how does a call like Cache::get('key') reach a real object when the Cache facade class defines no static get method?
answer
- no static get on the class
- PHP magic for missing static calls
- accessor string names a container binding
- getFacadeRoot, then a stored instance
- CacheManager forwards to the default store
basics
~10 sCache extends Illuminate\Support\Facades\Facade, whose __callStatic() catches the missing static call, resolves the container binding named by getFacadeAccessor() ('cache', a CacheManager) and calls get() on that object.
solid answer
~40 sA facade is a thin class extending `Illuminate\Support\Facades\Facade`. The `Cache` facade only overrides `getFacadeAccessor()`, which returns the string `'cache'`. When you write `Cache::get('key')`, PHP finds no accessible static `get`, so it calls the inherited `__callStatic($method, $args)`. That calls `getFacadeRoot()`, which resolves the `'cache'` binding from the application container — the `CacheManager` singleton — and keeps it in a static resolved-instance array. Then it runs `$instance->get(...$args)`. `CacheManager` has no `get` either; its own `__call` forwards to the default store's `Repository`. If no application has been set on the facade, `__callStatic` throws a `RuntimeException` ("A facade root has not been set."). So the syntax is static, but the work is an ordinary instance call on a container-managed object.
code
php · 12 lines<?php
// What the Cache facade call does, written out by hand.
use Illuminate\Support\Facades\Cache;
$viaFacade = Cache::get('user:42');
$root = app('cache'); // Illuminate\Cache\CacheManager
$byHand = $root->get('user:42'); // __call forwards to $root->store()->get()
// getFacadeRoot() exposes the object the facade uses
var_dump(Cache::getFacadeRoot() === $root); // bool(true)go deeper
Recall that a facade forwards a static-looking call to an object from the container, and that getFacadeAccessor names which binding it uses.
Walk through __callStatic, getFacadeRoot and resolveFacadeInstance in order, and explain that CacheManager forwards again to the default store's Repository.
Show you can debug a facade: find its root with getFacadeRoot, explain the 'facade root has not been set' error and when it appears outside a booted application.
Frame facades as syntax over the container rather than a separate architecture, so team debates focus on dependency visibility, not on imagined static state.
## What a Laravel facade actually is A **facade** in Laravel is a small class that gives static-looking access to an object that lives in the **service container** (the application's registry of how to build and share services). Every framework facade, and every custom one you write, extends `Illuminate\Support\Facades\Facade`. The facade class itself holds almost no code: `Illuminate\Support\Facades\Cache` defines only a protected static `getFacadeAccessor()` that returns `'cache'`, plus `@method static` docblocks so editors can autocomplete. The object that does the work is called the **facade root**. For `Cache` it is the `Illuminate\Cache\CacheManager` registered as a singleton under the `'cache'` key by `CacheServiceProvider`. | Piece | Where it lives | Job | |---|---|---| | `getFacadeAccessor()` | each facade class | returns the container key or class name to resolve | | `__callStatic($method, $args)` | `Facade` base class | PHP calls it for a missing or inaccessible static method | | `getFacadeRoot()` | `Facade` base class | resolves the accessor through `resolveFacadeInstance()` | | `static $resolvedInstance` | `Facade` base class | remembers the resolved object per accessor | | `static $app` | `Facade` base class | the application instance, set at bootstrap | ## The call, step by step When a controller runs `Cache::get('user:42')`: 1. PHP looks for a static method `get` on `Illuminate\Support\Facades\Cache` and its parents. None is declared, so PHP invokes the magic `__callStatic('get', ['user:42'])` inherited from `Facade`. 2. `__callStatic()` calls `static::getFacadeRoot()`. 3. `getFacadeRoot()` calls `static::getFacadeAccessor()`, which returns `'cache'`, and passes it to `resolveFacadeInstance()`. 4. `resolveFacadeInstance()` first checks the static `$resolvedInstance` map. On a miss it reads `static::$app['cache']` — array access on the container, which is `make('cache')` — and, because `static::$cached` is `true` by default, stores the result for later calls. 5. If no instance came back, `__callStatic()` throws `RuntimeException('A facade root has not been set.')`. 6. Otherwise it returns `$instance->get(...$args)`. `CacheManager` does not have a `get` method either. Its `__call()` forwards to `$this->store()`, the `Illuminate\Cache\Repository` for the store named in `config('cache.default')` (the skeleton's `.env.example` sets `CACHE_STORE=database`). So one facade call passes through two layers of PHP magic before it reaches real code. ## What the accessor may return - **A string alias**: `Cache` returns `'cache'`, `Log` returns `'log'`. The container maps these aliases to classes and contracts. - **A class or interface name**: `Gate` returns `Illuminate\Contracts\Auth\Access\Gate::class`; `Http` returns `Illuminate\Http\Client\Factory::class`. - Whatever it returns must be something the container can build, or the first static call fails with the container's resolution error. ## Where it is wired and where it breaks - The `RegisterFacades` bootstrapper calls `Facade::clearResolvedInstances()` and `Facade::setFacadeApplication($app)` during application bootstrap. Code that runs before that, or outside a booted app (a plain `PHPUnit\Framework\TestCase`), hits the `RuntimeException` above. - A facade that forgets to override `getFacadeAccessor()` inherits the base version, which throws `RuntimeException('Facade does not implement getFacadeAccessor method.')`. - PHP only falls back to `__callStatic()` for methods it cannot find, so a **public static method declared on the facade class** runs directly. `Schema::connection($name)` is such a method. - `Cache::getFacadeRoot()` is public: it returns the resolved `CacheManager`, which is handy in Tinker or when debugging which object a facade is really using. ## Writing a custom facade A custom facade is the same two-line override, pointing at a class the container can build: ```php <?php namespace App\Support\Facades; use App\Crm\LeadScorer; use Illuminate\Support\Facades\Facade; class Scorer extends Facade { protected static function getFacadeAccessor(): string { return LeadScorer::class; } } ``` `Scorer::score($lead)` then resolves `LeadScorer` through the container (auto-wiring its constructor) and calls `score()` on it. Nothing about the facade is static state in the service itself: the only static data is the application reference and the map of resolved instances. The service is an ordinary object, which is why the container can hand a different one to the facade when a binding changes before first use. ## Common misreadings Interviewers probe the model with a few recurring wrong answers: - **"A facade is a static class."** Only the entry point is static. The object doing the work is an instance with its own constructor, dependencies and configuration, built by the container. - **"The `@method` docblocks make it work."** The `@method static mixed get(...)` lines on `Cache` exist for editors and static analysers. PHP never reads them; delete them and the calls still run through `__callStatic()`. - **"Every call resolves a new object."** By default the first resolution is stored in `$resolvedInstance`, so later calls reuse it until the map is cleared. - **"Facades skip the container."** The opposite is true: a facade is a shortcut *to* the container, which is why any binding you register decides what the facade calls. Knowing these four lets you explain facades in one sentence: static syntax, container-resolved instance, method forwarded with `__callStatic()`.
- What happens if a facade class declares its own public static method with the same name as a method on its root?PHP only invokes `__callStatic()` when it cannot find an accessible static method, so the facade's own method runs and the root's method is never reached through that name. Laravel uses this deliberately: the `Schema` facade declares `connection($name)` as a real static method that returns a schema builder for a named connection.
- How do you get at the actual object behind a facade when debugging?Call `getFacadeRoot()` on the facade, for example `Cache::getFacadeRoot()`, which returns the resolved `CacheManager`. Resolving the same key yourself with `app('cache')` returns the same singleton, so you can inspect its class, its default store and its configuration directly.
- Why does the Cache facade reach a Repository even though its accessor resolves a CacheManager?The accessor `'cache'` is the `CacheManager`, which manages many stores. It has no `get`, `put` or `remember` of its own; its `__call()` forwards every unknown method to `$this->store()`, the `Repository` for the default store in `config('cache.default')`. Calling `Cache::store('redis')` selects a different repository explicitly.
saying these in an interview costs you the question
- Facades are static classes that keep the service's data in static properties
- Laravel generates a static method for every facade method at compile time
- The @method docblocks on a facade are what make the static calls work
- A facade call bypasses the service container entirely
- Cache::get() is a static method defined on the Cache facade class