Realtime Facades in Laravel

post-thumb

We’ve previously explored the Facade pattern and its functioning within Laravel. However, Laravel doesn’t simply offer a single way to create a Facade; it also introduces the option of a realtime facade.

What is a Realtime Facade?

To utilize a Facade in Laravel, you’ll need to generate a class that acts as the facade for your existing class.

Logger class:

1namespace App\Util;
2
3class Logger
4{
5    public function log(string $message): void
6    {
7        echo $message;
8    }
9}

Facade class for logger:

 1namespace App\Util;
 2
 3use Illuminate\Support\Facades\Facade;
 4
 5class LoggerFacade extends Facade
 6{
 7    protected static function getFacadeAccessor()
 8    {
 9        return Logger::class;
10    }
11}

With realtime facades, you no longer need to create a concrete facade class. Simply prefix the namespace of the class with Facades, and Laravel will automatically generate the facade class for you during the initial run.

Instead of:

1App\Util\LoggerFacade::log('hello')

You can write:

1Facades\App\Util\Logger::log('hello')

Here’s a complete example:

 1namespace App\Util;
 2
 3class Logger
 4{
 5    public function log(string $message): void
 6    {
 7        echo $message;
 8    }
 9}
10
11\Facades\App\Util\Logger::log('hello');

Please note that if your class has any dependencies, you need to use the app container to register a resolve function. This way, the dependencies can be properly handled by the realtime facades.

An Example

Let’s adjust our Logger class by adding a file path to the constructor, which will enable us to log messages to a specified file.

 1namespace App\Util;
 2
 3class Logger
 4{
 5
 6    public function __construct(private string $path)
 7    {
 8        //
 9    }
10
11    public function log(string $message): void
12    {
13        echo $message;
14    }
15}

Now, if we try to call our Logger using a realtime facade, it will throw a BindingResolutionException.

Illuminate\Contracts\Container\BindingResolutionException  Unresolvable dependency resolving [Parameter #0 [ <required> string $path ]] in class App\Util\Logger.

To address this, we simply need to register a binding in AppServiceProvider.

1class AppServiceProvider extends ServiceProvider
2{
3    public function register(): void
4    {
5        $this->app->bind(Logger::class, fn () => new Logger(storage_path('logs/log.txt')));
6    }
7}

Next, let’s dive into how realtime facades function behind the scenes.

How Does It Work?

To understand how this works, we first need to identify where Laravel uses the ‘Facades\’ namespace string. With the help of PHPStorm, I found this in the Illuminate\Foundation\AliasLoader class:

 1namespace Illuminate\Foundation;
 2
 3class AliasLoader
 4{
 5    /**
 6     * The namespace for all real-time facades.
 7     *
 8     * @var string
 9     */
10    protected static $facadeNamespace = 'Facades\\';
11}

This implies that you can use AliasLoader::setFacadeNamespace to alter the namespace prefix to anything you desire, instead of Facades\.

Now, let’s check where this static property is used. One of the methods where it is used is the load method:

 1/**
 2 * Load a class alias if it is registered.
 3 *
 4 * @param  string  $alias
 5 * @return bool|null
 6 */
 7public function load($alias)
 8{
 9    if (static::$facadeNamespace && str_starts_with($alias, static::$facadeNamespace)) {
10        $this->loadFacade($alias);
11
12        return true;
13    }
14
15    if (isset($this->aliases[$alias])) {
16        return class_alias($this->aliases[$alias], $alias);
17    }
18}

The method first checks if the alias class starts with the facades namespace. If so, it will call loadFacade for this alias. Let’s delve into that:

1protected function loadFacade($alias)
2{
3    require $this->ensureFacadeExists($alias);
4}

Continuing, let’s investigate ensureFacadeExists:

 1/**
 2 * Ensure that the given alias has an existing real-time facade class.
 3 *
 4 * @param  string  $alias
 5 * @return string
 6 */
 7protected function ensureFacadeExists($alias)
 8{
 9    if (is_file($path = storage_path('framework/cache/facade-'.sha1($alias).'.php'))) {
10        return $path;
11    }
12
13    file_put_contents($path, $this->formatFacadeStub(
14        $alias, file_get_contents(__DIR__.'/stubs/facade.stub')
15    ));
16
17    return $path;
18}

This method checks if the facade file exists in the cache (which would be false for the first run on a new class). If not, it loads a template and replaces all placeholders with actual values.

Here is the template:

 1namespace DummyNamespace;
 2
 3use Illuminate\Support\Facades\Facade;
 4
 5/**
 6 * @see \DummyTarget
 7 */
 8class DummyClass extends Facade
 9{
10    /**
11     * Get the registered name of the component.
12     */
13    protected static function getFacadeAccessor(): string
14    {
15        return 'DummyTarget';
16    }
17}

And this is a practical example of our Logger realtime facade class:

 1namespace Facades\App\Util;
 2
 3use Illuminate\Support\Facades\Facade;
 4
 5/**
 6 * @see \App\Util\Logger
 7 */
 8class Logger extends Facade
 9{
10    /**
11     * Get the registered name of the component.
12     */
13    protected static function getFacadeAccessor(): string
14    {
15        return 'App\Util\Logger';
16    }
17}

That’s all regarding how realtime facades operate.

What is AliasLoader?

We’ve just seen that the logic for realtime facades resides within the AliasLoader class. Let’s examine who calls the load method.

1protected function prependToLoaderStack()
2{
3    spl_autoload_register([$this, 'load'], true, true);
4}

In the AliasLoader, the prependToLoaderStack method utilizes spl_autoload_register. This means that PHP will invoke the load method every time a class is needed during your project’s code execution.

Happy Coding!

comments powered by Disqus

You May Also Like