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.