Laravel Facade deep dive

post-thumb

The Facade component plays a crucial role in Laravel. It’s employed extensively throughout the system. Numerous packages provide facade classes, simplifying the writing and implementation process for developers.

What is a Facade?

To put it simply, a Facade can be defined as a user-friendly interface to a complex system. If you have classes that are heavily dependent on others and require several instances of those classes to be initialized, a Facade can serve as an easy-to-use entry point to interact with these components.

For instance, consider a situation where you have a Mailer class that requires numerous dependencies to be instantiated.

 1$transport = Transport::fromDsn($dsn);
 2$handler = new MessageHandler($transport);
 3
 4$bus = new MessageBus([
 5    new HandleMessageMiddleware(new HandlersLocator([
 6        SendEmailMessage::class => [$handler],
 7    ])),
 8]);
 9
10$mailer = new Mailer($transport, $bus);
11$mailer->send($email);

Facade can be configured and used to make it easy to just write a single line

1Mailer::send($email);

From Laravel docs

Laravel facades serve as “static proxies” to underlying classes in the service container

Here, “static proxies” implies that any Facade class has its methods accessed in a static manner.

So if we have Facebook class

1class Facebook {
2	public function post(string $message) {
3		// some code
4	}
5
6	public function getPostById(int $id) {
7		// some code
8	}
9}

A Facade class for Facebook would allow us to use these methods in a static way. For example:

1FacebookFacade::post('a facebook post');
2
3// or
4
5FacebookFacade::findPostById(123);

How to use a Facade in Laravel

In this example, I’ll illustrate how Facades and Laravel’s Service Container work together.

Let’s assume we have a Twitter class that accepts a token in its constructor and includes a method named tweet.

1class Twitter {
2	public function __construct(private string $token) {
3	}
4
5	public function tweet($message) {
6		// api call to twitter
7	}
8}

Additionally, we have a TwitterController that features a store method. This method is designed to save the tweet locally and also post it to Twitter.

 1class TwitterController {
 2	public function store(Request $request) {
 3		// save tweet to database
 4
 5		$twitter = new Twitter(config('services.twitter.token'));
 6		$twitter->tweet($request->message);
 7
 8		return redirect()->back()->with([
 9			'message' => 'Tweet stored and posted successfully',
10		]);
11	}
12}

While the store method accomplishes what we desire, the code could be improved. Our current controller is burdened with managing where the token is stored. Each time we need to use the Twitter class, we must load the configuration file and retrieve the token to pass to the constructor. The Laravel Container comes to our rescue here by allowing us to register a service, like the Twitter class, just once and use it anywhere. This saves us from worrying about its initialization every time.

We can register our Twitter class within the register method found in app/Providers/AppServiceProvider.php. Let’s see how to do it:

1$this->app->register('twitter', function () {
2    return new Twitter(config('services.twitter.token'));
3});

With this setup, our controller can simply call app('twitter') to retrieve an instance of the Twitter class, already initialized with the token. Hence, we can modify our code in the TwitterController as follows:

 1class TwitterController {
 2	public function store(Request $request) {
 3		// save tweet to database
 4
 5		app('twitter')->tweet($request->message);
 6
 7		return redirect()->back()->with([
 8			'message' => 'Tweet stored and posted successfully',
 9		]);
10	}
11}

What we’ve done here is register the Twitter class within the AppServiceProvider to the Container. Using app('service-name-here') subsequently retrieves the object from the Container.

However, this method requires you to remember the names of all your classes for their use, which can be a bit burdensome. Moreover, it’s not easy for your IDE to discern the type returned by app('something').

To resolve these issues, we can create a new class, which we’ll name TwitterFacade. This class would extend the Facade class.

1class TwitterFacade extends Facade
2{
3    public static function getFacadeAccessor(): string
4    {
5        return 'twitter';
6    }
7}

The getFacadeAccessor method is an override from the Facade class. This method helps Laravel in identifying the name of the service to fetch from the container.

Therefore, we can once again adjust our TwitterController to utilize the new Facade class.

 1class TwitterController {
 2	public function store(Request $request) {
 3		// save tweet to database
 4
 5		TwitterFacade::tweet($request->message);
 6
 7		return redirect()->back()->with([
 8			'message' => 'Tweet stored and posted successfully',
 9		]);
10	}
11}

So, we can deduce that:

1TwitterFacade::tweet($request->message);

Essentially translates to:

1app('twitter')->tweet($request->message);

And this is precisely how Laravel operates.

Be aware of the internal cache

Laravel’s Facade only resolves a class a single time throughout the lifespan of a request. To clarify this point further, let’s examine the following counter example

 1class Counter {
 2    public $count = 0;
 3
 4    public function increment(): void
 5    {
 6        $this->count++;
 7    }
 8
 9    public function getCount(): int
10    {
11        return $this->count;
12    }
13}
14
15class CounterFacade extends Facade
16{
17    protected static function getFacadeAccessor()
18    {
19        return 'counter';
20    }
21}

Now, when we utilize the CounterFacade:

1CounterFacade::increment();
2CounterFacade::increment();
3
4dd(CounterFacade::getCount());

Due to Laravel’s single-resolution process for the Counter class, the getCount() method would yield a result of 2.

To bypass this caching behavior, you can simply set the cache property to false in your facade class.

1class CounterFacade extends Facade
2{
3    protected static $cached = false;
4
5    protected static function getFacadeAccessor()
6    {
7        return 'counter';
8    }
9}

In this manner, the getCount() method would return 0. This is because each invocation of the facade results in a completely new instance of the Counter class.

How Does It Work Under the Hood?

PHP Implementation

First, let’s implement a Facade in plain PHP. This step will prepare us to understand Laravel’s approach better.

We’ll begin by creating a Service Container class to register and resolve our dependencies.

 1class ServiceContainer
 2{
 3    private array $services = [];
 4
 5    public function __construct()
 6    {
 7        $this->services = [];
 8    }
 9
10    public function register(string $name, closure $callback): void
11    {
12        $this->services[$name] = $callback;
13    }
14
15    public function resolve(string $name)
16    {
17        if (!isset($this->services[$name])) {
18            throw new Exception("Service $name not found");
19        }
20
21        return $this->services[$name]();
22    }
23}

Thus, we can use this ServiceContainer in a manner quite similar to how we employ the Laravel container.

1$serviceContainer = new ServiceContainer();
2
3$serviceContainer->register('twitter', function () {
4    return new Twitter($_ENV['TWITTER_TOKEN'] ?? 'token');
5});
6
7$serviceContainer->resolve('twitter')->tweet('Hello world');

Now, we need to construct an abstract Facade class, imbued with all the necessary functionalities to act as a static proxy.

In PHP, there are magic methods, one of which is _callStatic . This method gets invoked when an inaccessible static method is called.

We will utilize this feature to relay any static calls to the original class behind the Facade.

To fully implement the abstract Facade class, we need to undertake the following steps:

  • Pass a ServiceContainer instance to the Facade;
  • Implement a method to be used by custom facades;
  • Utilize __callStatic to initialize the original object and forward the call to it.

Let’s begin by implementing the first part: passing the container instance to the abstract Facade.

1abstract class Facade {
2
3    private static ServiceContainer $container;
4
5    public static function setContainer(ServiceContainer $container)
6    {
7        static::$container = $container;
8    }
9}

Next, we need to define a method similar to getFacadeAccessor. Let’s name this method getServiceName.

1protected static function getServiceName()
2{
3    throw new RuntimeException('You must implement getServiceName method.');
4}

We’ll explore the usage of this method shortly.

Next, let’s focus on the final and crucial method, __callStatic.

1public static function __callStatic($method, $args)
2{
3	$serviceName = static::getServiceName();
4
5	$instance = static::$container->resolve($serviceName);
6
7	return $instance->$method(...$args);
8}

To clarify, the first line retrieves the service name, which in our case is twitter. Then we use the service container to resolve the service name and obtain the object. The final step is to forward the call to this instance.

So, when you use:

1Twitter::tweet('something');

It actually translates to:

1$twitter = $serviceContainer->resolve('twitter');
2$twitter->tweet('something');

Now, any class can implement our abstract Facade.

1class TwitterFacade extends Facade
2{
3    public static function getServiceName(): string
4    {
5        return 'twitter';
6    }
7}

Here’s a comprehensive example in a single file:

 1abstract class Facade {
 2
 3    private static ServiceContainer $container;
 4
 5    public static function setContainer(ServiceContainer $container)
 6    {
 7        static::$container = $container;
 8    }
 9
10    protected static function getServiceName()
11    {
12        throw new RuntimeException('You must implement getServiceName method.');
13    }
14
15    public static function __callStatic($method, $args)
16    {
17        $serviceName = static::getServiceName();
18
19        $instance = static::$container->resolve($serviceName);
20
21        return $instance->$method(...$args);
22    }
23}
24
25class ServiceContainer
26{
27    private array $services = [];
28
29    public function __construct()
30    {
31        $this->services = [];
32    }
33
34    public function register(string $name, closure $callback): void
35    {
36        $this->services[$name] = $callback;
37    }
38
39    public function resolve(string $name)
40    {
41        if (!isset($this->services[$name])) {
42            throw new Exception("Service $name not found");
43        }
44
45        return $this->services[$name]();
46    }
47}
48
49class Twitter {
50    public function __construct(private string $token)
51    {
52    }
53
54    public function tweet(string $message): void
55    {
56        echo "Tweeted message: $message";
57    }
58}
59
60class TwitterFacade extends Facade
61{
62    public static function getServiceName(): string
63    {
64        return 'twitter';
65    }
66}
67
68
69
70$serviceContainer = new ServiceContainer();
71$serviceContainer->register('twitter', function () {
72    return new Twitter($_ENV['TWITTER_TOKEN'] ?? 'token');
73});
74
75Facade::setContainer($serviceContainer);
76
77TwitterFacade::tweet('Hello world');

Laravel’s Implementation

Now that you understand how a Facade component can be implemented in PHP, let’s dive deeper into Laravel’s code to understand how it works.

Open any facade class from within a Laravel project. For this example, we’ll choose the View facade (chosen without a specific reason).

1class View extends Facade
2{
3    protected static function getFacadeAccessor()
4    {
5        return 'view';
6    }
7}

Next, let’s open the Facade class and search for the __callStatic method.

 1public static function __callStatic($method, $args)
 2{
 3    $instance = static::getFacadeRoot();
 4
 5    if (! $instance) {
 6        throw new RuntimeException('A facade root has not been set.');
 7    }
 8
 9    return $instance->$method(...$args);
10}

As you can see, the method closely resembles the one we implemented ourselves (which was initially inspired by Laravel!)

Now, let’s examine the getFacadeRoot method.

1public static function getFacadeRoot()
2{
3    return static::resolveFacadeInstance(static::getFacadeAccessor());
4}

The getFacadeAccessor method is used to retrieve the service name, which in our example would be view. This name is then passed to the resolveFacadeInstance method. Let’s investigate what’s inside this method.

 1protected static function resolveFacadeInstance($name)
 2{
 3    if (isset(static::$resolvedInstance[$name])) {
 4        return static::$resolvedInstance[$name];
 5    }
 6
 7    if (static::$app) {
 8        if (static::$cached) {
 9            return static::$resolvedInstance[$name] = static::$app[$name];
10        }
11
12        return static::$app[$name];
13    }
14}

Laravel initially checks if the facade has been resolved before. If you examine the last few lines, there’s a call to static::$app[$name]. This is equivalent to app()[$name] and app($name).

This effectively covers the entire working of the Facade component in Laravel, with one final aspect remaining. That is, how does Laravel pass the container to the Facade class?

There is a method within the Facade class named setFacadeApplication , which takes care of passing the container to the Facade class.

1public static function setFacadeApplication($app)
2{
3    static::$app = $app;
4}

Now, let’s see where this method is used. You’ll find it in the RegisterFacades class.

 1class RegisterFacades
 2{
 3    public function bootstrap(Application $app)
 4    {
 5        Facade::clearResolvedInstances();
 6
 7        Facade::setFacadeApplication($app);
 8
 9        AliasLoader::getInstance(array_merge(
10            $app->make('config')->get('app.aliases', []),
11            $app->make(PackageManifest::class)->aliases()
12        ))->register();
13    }
14}

Indeed, we find the use of setFacadeApplication in this context. The RegisterFacades class is part of the bootstrap array in both HTTP and console kernels, as you can see here in the HttpKernel .

1protected $bootstrappers = [
2\Illuminate\Foundation\Bootstrap\LoadEnvironmentVariables::class,
3\Illuminate\Foundation\Bootstrap\LoadConfiguration::class,
4\Illuminate\Foundation\Bootstrap\HandleExceptions::class,
5\Illuminate\Foundation\Bootstrap\RegisterFacades::class,
6\Illuminate\Foundation\Bootstrap\RegisterProviders::class,
7\Illuminate\Foundation\Bootstrap\BootProviders::class,
8];

And that essentially sums up how the Facade component operates in Laravel.

Testing a Facade

Given that static calls to the Facade class are essentially a static proxy forwarding to an actual instance, any Facade can be tested or mocked. Laravel facilitates this out of the box with the shouldReceive method.

An example would look something like this:

1Route::get('/cache', function () {
2    return Cache::get('key');
3});

It can be tested as follows:

 1public function test_basic_example(): void
 2{
 3    Cache::shouldReceive('get')
 4         ->with('key')
 5         ->andReturn('value');
 6
 7    $response = $this->get('/cache');
 8
 9    $response->assertSee('value');
10}

Surely, you might already have a guess on how this operates behind the scenes. Let’s revisit the Facade class and explore the shouldReceive function to better understand its functionality.

 1public static function shouldReceive()
 2{
 3    $name = static::getFacadeAccessor();
 4
 5    $mock = static::isMock()
 6        ? static::$resolvedInstance[$name]
 7        : static::createFreshMockInstance();
 8
 9    return $mock->shouldReceive(...func_get_args());
10}

If you’re unfamiliar with Mockery, it’s a versatile tool used for testing. It enables us to mock objects, set expectations, and then assert that they match.

In this particular snippet of code, the method we are interested in is static::createFreshMockInstance().

1protected static function createFreshMockInstance()
2{
3    return tap(static::createMock(), function ($mock) {
4        static::swap($mock);
5
6        $mock->shouldAllowMockingProtectedMethods();
7    });
8}

In the createFreshMockInstance method, there are two key sub-methods: createMock and swap. The createMock method is responsible for creating a mock object from the original instance.

 1protected static function createMock()
 2{
 3    $class = static::getMockableClass();
 4
 5    return $class ? Mockery::mock($class) : Mockery::mock();
 6}
 7
 8protected static function getMockableClass()
 9{
10    if ($root = static::getFacadeRoot()) {
11        return get_class($root);
12    }
13}

Following this, the swap method comes into play, replacing the original object in the cached facades and the container with the newly created mock object.

1public static function swap($instance)
2{
3    static::$resolvedInstance[static::getFacadeAccessor()] = $instance;
4
5    if (isset(static::$app)) {
6        static::$app->instance(static::getFacadeAccessor(), $instance);
7    }
8}

With the use of the shouldReceive method, all subsequent calls to that facade class will be directed to the mocked instance.

Now that you understand how Laravel’s Facades work under the hood and how they can be tested, you can use them more effectively and confidently in your applications.

That’s it. Happy coding!

comments powered by Disqus

You May Also Like