Lets look at how Facades work in Laravel 4 by investigating the flow of one of the facaded classes: URL. As in <?=URL::route('news')?>
.
As you'll see in the summary, this isn't exactly described in the procedural order your app is executed. But I think it serves to explain what's going on.
-
The app config has an
aliases
array. In there is:'URL' => 'Illuminate\Support\Facades\URL'
. If you look up that class, you'll see it just has this:protected static function getFacadeAccessor() { return 'url'; }
and that it inherits fromIlluminate\Support\Facades\Facade
. We'll get back to this later. -
Lets now turn to how the app boots up. The /vendor/laravel/framework/src/Illuminate/Foundation/start.php bootstrap file calls
registerAliasLoader()
on an instance ofIlluminate\Foundation\Application
. -
This, in turn, hands off the work to
Illuminate\Foundation\AliasLoader->getInstance()
, passing it all the aliases. Then it callsprependToLoaderStack
which uses PHP'sspl_autoload_register()
function to registerIlluminate\Foundation\AliasLoader->load()
as an autoloader. That autoloader looks up an alias from the config array that is was passed and uses PHP'sclass_alias
to, on the fly, make a class_alias if needed. -
So now our facade will get invoked whenever someone trys to execute a class with the same name as the key in the aliases array. If you take a look at
Illuminate\Support\Facades\Facade
you'll see that it has a__callStatic
magic method that takes static method calls and executes them on the singleton instance of the class. That singleton instance is expected to have been already created, as you'll read inresolveFacadeInstance()
, it pulls it fromstatic::$app[$name]
. In other words, the main app container should have already had an instance of the facaded class 'shared' with it. In our case, it is shared to the application class with the $name that we saw inIlluminate\Support\Facades\URL
: "url". The facade is just pointing at that instance. -
So when did the instance in the application container get instantiated? There's no one way to know this; it's different for various classes. For our example case, by digging around I found that in the constructor for the main application (
Illuminate\Foundation\Application
), this is called$this->register(new RoutingServiceProvider($this));
.register()
takes a passed service provider and calls IT'Sregister()
function; sort of the bootstrap for the service provider. -
Illuminate\Routing\RoutingServiceProvider
'sregister()
calls$this->registerUrlGenerator();
. And this function looks like this:$this->app['url'] = $this->app->share(function($app) { // The URL generator needs the route collection that exists on the router. // Keep in mind this is an object, so we're passing by references here // and all the registered routes will be available to the generator. $routes = $app['router']->getRoutes(); return new UrlGenerator($routes, $app['request']); });
It basically does some dependency injection on UrlGenerator and then saves a singleton instance (that's the result of the
share()
) of that to the main app container as "url".
So, the ServiceProvider for a class/package is responsible for registering a facade-able class on the app container through an array type accessor (i.e. $this->app['whatever]
). It's assumed that this work will be done before you try to use the facade. Then, your class needs to have an accompanying facade class that inherits from Illuminate\Support\Facades\Facade
but defines it's own getFacadeAccessor()
. This class will do the magic work of resolving your static calls to the instance stored in $app
. The getFacadeAccessor()
just needs to return the same string that you saved your instance to $app
under and it needs to be listed in the app config's aliases
.
Nice post. I wish I saw this before I had to traverse through framework in order to understand how it does all that. Don't you think that this is overcomplicated a bit?