Facades ... in Realtime?

Laravel 5.4 Wizardry

Posted by lagbox on March 13, 2017 laravel tricks

Realtime Facades

Laravel 5.4 has brought a new feature to the table, Realtime Facades. This allows you to use any class as a Facade with out having to actually create a Facade class first. Using this feature only requires you to reference your class in a particular way and you will instantly have a Facade to use for your class.

I will assume you already know what Facades are and how they work as static proxies.

Follow the Yellow Brick Road

Lets use the following class as our example class to start our journey:

namespace App\States;

class Kansas
{
    public function weather()
    {
        echo "tornado";
    }
}

We're Off to See the Wizard

To get a realtime Facade you just have to reference your class by its FQCN as belonging in the Facades namespace. (Prepend Facades\ to your FQCN)

use Facades\App\States\Kansas;

The Release notes for Laravel 5.4 use a new PHP 7 Feature in their example that may confuse those not familiar with PHP 7, as Laravel 5.4 does not require PHP 7:

use Facades\ {
    App\Services\PaymentGateway
};

PHP 7.0 - New Features - Group use declarations

The Great and Powerful Oz

To see the wizard with some perspective lets call our original class's method weather statically, then call our "realtime" Facade.

use Facades\App\States\Kansas;
	 
\App\States\Kansas::weather();
// PHP error:  Non-static method ... should not be called statically ...
	
Kansas::weather();
// "tornado"
	
Kansas::getFacadeRoot();
// => App\States\Kansas {#686}

This is obviously some type of wizardry going on. Some how we have a working Facade and we did nothing but reference a class in the Facades namespace which does not exist. What crazy spell is this?

Pay No Attention to That Man Behind the Curtain

As there is no 'magic' and Laravel doesn't change PHP, there has to be a simple answer for what is happening with in the language. What Toto has unveiled for us is Laravel plugging into the autoload system in PHP and writing to disk. An actual class file is being written to disk so there is something to require.

Laravel has registered a particular callable with the autoload system via spl_autoload_register that points to AliasLoader@load to hook into the autoload system. When PHP encounters a class that hasn't been loaded that it needs it will start hitting the autoload queue and fire the callbacks.

AliasLoader

The lovely AliasLoader (Illuminate\Foundation\AliasLoader) provides our 'alias' support we configure in config/app.php. This is the reason why a class that is defined in a random namespace can be aliased to Cache for instance. As AliasLoader@load is an autoloader callback already, it is a good place to add in what is needed for this new feature of "realtime" Facades.

Brain

The AliasLoader@load method is going to check if the class name that PHP is looking for starts with Facades or what ever the value of AliasLoader::$facadeNamespace is. If this is the case we get a call to $this->loadFacade($class).

Courage

The AliasLoader@loadFacade method is responsible for actually loading our class for PHP. It gets the path to this file to require from a call to $this->ensureFacadeExists($class)

Heart

AliasLoader@ensureFacadeExists ends up doing most of the needed work. This method is responsible for finding or creating the needed class file for us. Since the PHP autoloader needs a file to load and we need the ability to create a class that returns the correct value from Facade@getFacadeAccessor we need a class file created.

Here is the flow:

  • Generate a path to a "realtime" Facade file in storage based on the scheme of storage_path('framework/cache/facade-'. sha1($class) .'.php').
  • If a file exists at that path.
    • Return that path.
  • Create a Facade class from the stub (Illuminate/Foundation/stubs/facade.stub) and save it to the path we checked with the correct namespace, class name and Facade accessor for the class we want a Facade for.
  • Return that path.

The Wizard is Gone

This is the file that ends up being cached to disk to yourapp/storage/framework/cache/... for us by the framework after the first call to our "realtime" Facade:

namespace Facades\App\States;

use Illuminate\Support\Facades\Facade;

/**
 * @see \App\States\Kansas
 */
class Kansas extends Facade
{
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'App\States\Kansas';
    }
}

Back home

This cool feature is a 'trick' of the framework hooking into the autoload system. Laravel is creating a Facade class file for you, writing it to disk and loading it. Since these files aren't created in a location that we have composer autoloading and they are also named with a hash, the AliasLoader is responsible for these classes being loaded when needed.

Well, now you know its not magic, welcome back to Kansas.