Skip to content

Instantly share code, notes, and snippets.

@weotch
Last active December 15, 2019 01:55
Show Gist options
  • Save weotch/5365355 to your computer and use it in GitHub Desktop.
Save weotch/5365355 to your computer and use it in GitHub Desktop.
Laravel 4: Service Providers and Facades

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')?>.

The logic flow

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.

  1. 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 from Illuminate\Support\Facades\Facade. We'll get back to this later.

  2. 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 of Illuminate\Foundation\Application.

  3. This, in turn, hands off the work to Illuminate\Foundation\AliasLoader->getInstance(), passing it all the aliases. Then it calls prependToLoaderStack which uses PHP's spl_autoload_register() function to register Illuminate\Foundation\AliasLoader->load() as an autoloader. That autoloader looks up an alias from the config array that is was passed and uses PHP's class_alias to, on the fly, make a class_alias if needed.

  4. 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 in resolveFacadeInstance(), it pulls it from static::$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 in Illuminate\Support\Facades\URL: "url". The facade is just pointing at that instance.

  5. 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'S register() function; sort of the bootstrap for the service provider.

  6. Illuminate\Routing\RoutingServiceProvider's register() 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".

Summary

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.

@sergeylukin
Copy link

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?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment