A facade is not a static class. Cache::get('catalog') lands in Facade::__callStatic(), which takes the string from the facade's protected getFacadeAccessor(), resolves it from the container, remembers the object and forwards the call. A bound closure reads each accessor here:
use Illuminate\Support\Facades\{Cache, DB, Event, Storage};
foreach ([Cache::class, DB::class, Event::class, Storage::class] as $f) {
$accessor = (fn () => static::getFacadeAccessor())->bindTo(null, $f)();
printf("%-8s %-10s %s\n", class_basename($f), $accessor, $f::getFacadeRoot()::class);
}
var_dump(Event::getFacadeRoot() === app('events'));
Event::fake();
echo Event::getFacadeRoot()::class, "\n";Output
Cache cache Illuminate\Cache\CacheManager DB db Illuminate\Database\DatabaseManager Event events Illuminate\Events\Dispatcher Storage filesystem Illuminate\Filesystem\FilesystemManager bool(true) Illuminate\Support\Testing\Fakes\EventFake
The facade and app('events') are one object, and Event::fake() swaps the container entry, so later Event:: calls reach the fake (Mocks and Fakes). Prefixing any class with Facades\ makes a real-time facade: Facades\App\Reports\SalesReport::line() worked, generating storage/framework/cache/facade-<hash>.php. Facades hide the dependency that an injected LoggerInterface shows, so inject in domain services and keep facades for glue code.