0

Facade internals — getFacadeAccessor() and __callStatic

Advanced5 min read·lv-04-002
laravel-srcframework

Concept

Understanding how Facades work internally demystifies one of Laravel's most-used features. The magic is entirely in two methods: getFacadeAccessor() and PHP's __callStatic().

The internals:

  1. A static method is called on a Facade: Cache::get('key').
  2. Since get() doesn't exist as a static method on the Cache class, PHP invokes __callStatic('get', ['key']).
  3. __callStatic calls static::getFacadeRoot().
  4. getFacadeRoot() calls static::getFacadeAccessor() to get the container key (e.g., 'cache').
  5. It resolves the binding from the container: static::resolveFacadeInstance('cache') → the CacheManager singleton.
  6. It calls $instance->get('key') and returns the result.

getFacadeAccessor(): Returns a string (the container binding key) OR a class name. Must be something the container can resolve.

resolveFacadeInstance(): Checks if the instance is already resolved (cached in static::$resolvedInstance). If not, resolves from the app container. The resolved instance is cached — subsequent calls to the same Facade method don't re-resolve from the container.

clearResolvedInstances(): Called during tests and between request cycles to allow Facades to be faked. When you call Cache::fake(), it replaces the resolved instance with a fake, and subsequent static calls use the fake.

Facade root resolution: getFacadeAccessor() can return an object directly: return new MyService(). But don't do this in production — it creates a new instance per call instead of using the container's singleton.

Code Example

php
<?php
// Facade source code — simplified from Illuminate/Support/Facades/Cache.php
namespace Illuminate\Support\Facades;

class Cache extends Facade
{
    /**
     * Get the registered name of the component.
     * Returns the container binding key.
     */
    protected static function getFacadeAccessor(): string
    {
        return 'cache'; // resolves to CacheManager from the container
    }
}

// Base Facade class — the __callStatic magic
namespace Illuminate\Support\Facades;

abstract class Facade
{
    protected static $app;           // the IoC container
    protected static $resolvedInstance = []; // cache of resolved instances

    public static function __callStatic(string $method, array $args): mixed
    {
        $instance = static::getFacadeRoot();
        return $instance->$method(...$args);
    }

    public static function getFacadeRoot(): mixed
    {
        return static::resolveFacadeInstance(static::getFacadeAccessor());
    }

    protected static function resolveFacadeInstance(string $name): mixed
    {
        if (isset(static::$resolvedInstance[$name])) {
            return static::$resolvedInstance[$name];
        }
        return static::$resolvedInstance[$name] = static::$app[$name];
    }

    // Called in tests to replace with a mock
    public static function swap(mixed $instance): void
    {
        static::$resolvedInstance[static::getFacadeAccessor()] = $instance;
        static::$app->instance(static::getFacadeAccessor(), $instance);
    }
}

// Building your own Facade
namespace App\Facades;

use Illuminate\Support\Facades\Facade;

class Analytics extends Facade
{
    protected static function getFacadeAccessor(): string
    {
        return \App\Services\Analytics::class; // registered in service provider
    }
}
// Usage: Analytics::track('page_view', ['path' => '/']);