Categories


Archives


Recent Posts


Categories


Unraveling Laravel Facades

astorm

Frustrated by Magento? Then you’ll love Commerce Bug, the must have debugging extension for anyone using Magento. Whether you’re just starting out or you’re a seasoned pro, Commerce Bug will save you and your team hours everyday. Grab a copy and start working with Magento instead of against it.

Updated for Magento 2! No Frills Magento Layout is the only Magento front end book you'll ever need. Get your copy today!

So far in this series, whenever we’ve wanted to use a service, we’ve fetched Laravel’s application/service-container object with the global app function

$app = app();

and then used either make or ArrayAccess syntax to fetch a service

$service = $app['db'];
$service = $app->make('db');    

While this is a perfectly valid use of the framework, it’s not ideal for all situations and all developers. This is especially true for people just getting into Laravel and PHP programming.

PHP’s ArrayAccess feature, while useful, is advanced functionality that can confuse programmers just learning PHP. Most meta-programming falls under this category, as most new programmers start by assuming their programming language follows a strict set of rules. Changing the rules under them tends to confuse and frustrate people until they make the “programming the programming language” connection.

Calling make is also problematic. It’s not that calling make itself is incorrect, it’s that there are numerous other methods on the application object that can instantiate or return an object/service. Here’s some off the top of my head.

$app = app();
$object = $app->make(...);
$object = $app->register(...);
$object = $app->getRegistered(...);    
$object = $app->resolveProviderClass(...);        
$object = $app->extend(...);        
$object = $app->db;
$object = $app->offsetGet(...);
...

There’s also methods like $app->singleton and $app->instance which might look like they’re for instantiating objects and services, but aren’t (or aren’t directly)

Despite their public access level, many of these methods are meant to be used by the application object’s internal logic, (or programmers familiar with that logic). One of the disadvantages of combining the global application object and the service container object is it becomes a lot less obvious which methods client programmers use to interact with the service container, and which methods are meant for internal framework use.

This is where Laravel facades enter the picture.

Laravel Facades

Let’s jump in with some code. Calling a service method with a Laravel facade looks like this

DB::select('SELECT * FROM table');

The above is equivalent to

$app = app();
$app->make('db')->select('SELECT * FROM table');

How it’s equivalent is not a straightforward story, but it’s the story we’re going to tell today.

First, let’s define a facade’s job in Laravel

A Laravel facade provides access to a shared/singleton service without requiring the end client-programmer to use the application container.

That is, the point of facades are to provide access to services without the container. A Laravel application is still structured around using services, but for many applications a client programmer may be completely unaware of the application service container, and will only access services through facades.

A facade looks like a static method call (and ultimately, behind the scenes, is a static method call). A less experienced PHP programmer might look at

DB::select('SELECT * FROM table');

and assume there’s a class in the global namespace named DB, and that this class has a static method named select. However, you can search your Laravel code base up, down, left, and right, and you won’t find a global class DB anywhere in the system.

Facades are a classic example of meta-programming. Roughly defined, meta-programming is changing a language/platform’s behavior to build new features into the language. Meta-programming allows system developers to come up with new language constructs and ideas without needing to change the underlying language implementation. Meta-programming knowledge is an important part of any modern day developer’s toolkit, but it’s like ginger in cooking. A little goes a long way.

Today we’re going to unravel how Laravel facades work. One important warning: If you’re a classically trained object oriented programmer, Laravel’s facades are not the Gang of Four’s facades. We’ll get to that eventually, but for now assume you’re learning a whole new design pattern.

Facade Classes are Aliases

Let’s consider our previous facade call

DB::select('SELECT * FROM table');

This looks like a call to the static method select on the class DB. Earlier we said Laravel has no class DB defined. Lets use the Reflection API to figure out where PHP thinks this class is. As in previous articles, we’ll drop our code in a testbed route.

Route::get('testbed', function(){
    $r = new ReflectionClass('DB');
    var_dump(
        $r->getFilename()
    );

    var_dump(
        $r->getName()
    );
});

Above we’ve used the Reflection API to lookup both the class’s definition file, and the class’s name. If you run the above by loading the route testbed in your browser

http://laravel.example.com/index.php/testbed

You’ll see the following

string '/path/to/laravel/vendor/laravel/framework/src/Illuminate/Support/Facades/DB.php' (length=115)

string 'Illuminate\Support\Facades\DB' (length=29)    

For some reason, PHP thinks the class’s name is Illuminate\Support\Facades\DB. If you’ve read last week’s article on PHP’s class aliases, you probably know what’s going on. The class DB is actually a class_alias. This alias allows a service developer to provide a short, easy to remember-and-type facade name for their service.

It’s beyond the scope of this article, but if you’re curious, these aliases are setup automatically by the Laravel autoloader

Illuminate\Foundation\AliasLoader::alias()

This autoloader class reads through the alises defined in app/config/app.php. You can see the DB alias here

#File: app/config/app.php
'aliases' => array(
    //...
    'DB'              => 'Illuminate\Support\Facades\DB',
    /...

By using an autoloader to define an alias, the Laravel core team ensures no aliases are loaded until they’re actually needed.

Facade Classes

Now that we know the real class Laravel uses when we type DB we can go look for the select method. However, if we look at the Illuminate/Support/Facades/DB class, we’ll be disappointed.

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/DB.php 
class DB extends Facade {
    protected static function getFacadeAccessor() { 
        return 'db'; 
    }    
}

There’s no select method defined. You might think there’s a select method in the parent Illuminate\Support\Facades\Facade class (defined in vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php), but it’s not there either. What we do have is a getFacadeAccessor method that returns a string — db.

Unless you’re already familiar with some of PHP’s meta-programming features, this entire state of affairs is pretty confusing and confounding. However, even if you don’t know about things like __callStatic, think back to how we defined a facade’s responsibilities

A Laravel facade provides access to a shared/singleton service without requiring the end client-programmer to use the application container.

A facade class has one responsibility — and that’s to define a getFacadeAccessor method, and have this method return a service identifier. In the DB facade above that identifier is the string db. This means a call to

DB::select(...)

Is equivalent to each of the following

app()->make('db')->select(...)
app()->db->select(...)
app()['db']->select(...)

If the accessor had been written

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/DB.php
protected static function getFacadeAccessor() { 
    return 'my_crazy_service'; 
}    

Then the static call DB::select would have been equivalent to

app()->make('my_crazy_service')->select(...)
app()->my_crazy_service->select(...)
app()['my_crazy_service']->select(...)

Of course, you’re probably wondering how a static call is somehow transformed into a method call to a service. To understand that, we’ll need to look more deeply into the definition of the Facade base class — the abstract class which all Laravel facades inherit from.

Facades and Magic Methods

If you read last week’s article, you already know about __callStatic. In brief, if you call a method that doesn’t exist on a PHP object, PHP will issue a fatal error unless that PHP object has a magic __call or __callStatic method defined. So, when a programmer calls

DB:select(...)

PHP says

Hmm, no select method is defined on DB or its parent classes, I better crash with a fata— wait, there’s a __callStatic on the parent Facade class, I’ll call that instead.

Let’s take a look at __callStatic

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot();

    switch (count($args))
    {
        case 0:
            return $instance->$method();
        #... snipped for brevity ...#
        default:
            return call_user_func_array(array($instance, $method), $args);
    }
}

What __callStatic does is get an instance of an object with the getFacadeRoot method, and then pass on the static method name (passed in as $method) as a call to a regular method on the object.

It’s worth repeating that step by step. First, we have a call to a static method

DB::select('SELECT * FROM table');

PHP can’t find a select method to call, so instead PHP calls the __callStatic method, passing in the method name and arguments as parameters.

DB::__callStatic('select', array('SELECT * FROM table'));

In __callStatic, the Laravel framework code fetches a service object with a call to getFacadeRoot

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
public static function __callStatic($method, $args)
{
    $instance = static::getFacadeRoot('select', array('SELECT * FROM table'));
    //...
}

Finally, __callStatic passes the method call on to the object in $instance using PHP’s dynamic method calling feature, and returns the value

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php    
return $instance->select('SELECT * FROM table');

All of this explains the mystery of how a static looking method call is actually a call to an instance method on a service object, but it doesn’t explain how the facade class knows which service to instantiate.

This is where getFacadeAccessor comes into the picture.

The Facade Accessor

If we take a look at the method call that fetches the service object

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
$instance = static::getFacadeRoot('select', array('SELECT * FROM

We see the special static keyword. The static keyword is similar to self. Both are a way to refer to the current class without without using the full classname. You can read more in the PHP manual’s Late Static Binding section, but for now all you need to know is getFacadeRoot is defined on the same base Facade class. (Also, the keyword static is something completely different if used in a function context, so Drupal developers beware)

If we take a look at the getFacadeRoot method, we’ll see the following

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
public static function getFacadeRoot()
{
    return static::resolveFacadeInstance(
        static::getFacadeAccessor()
    );
}

Ah ha! Here’s that getFacadeAccesssor method we saw defined in the specific Facade class. You’ll remember that looks like this

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/DB.php
protected static function getFacadeAccessor() { 
    return 'db'; 
}

So, putting our x-ray variable specs back on, for our method call, getFacadeRoot actually looks like

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
return static::resolveFacadeInstance(
    'db'
);

This means our next stop is the resolveFacadeInstance method

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
protected static function resolveFacadeInstance($name)
{
    if (is_object($name)) return $name;

    if (isset(static::$resolvedInstance[$name]))
    {
        return static::$resolvedInstance[$name];
    }

    return static::$resolvedInstance[$name] = static::$app[$name];
}

This short method is the final key to understanding a facade. It’s the last line that’s most important

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php
return static::$resolvedInstance[$name] = static::$app[$name];

Here the facade code refers to the static property $app. The static property $app contains a reference to the application container object. In other words, the above could be rewritten to

//get an instance of the global appliaction objcet
$app = app();

//set a copy of the service we fetch on the $resolvedInstance property
static::$resolvedInstance[$name] = $app['db'];

//return the instance
return static::$resolvedInstance[$name];

The Facade is, ultimately, using the same code to fetch a service reference as a normal Laravel developer would. There’s one small wrinkle to be aware of, and that’s the conditional

#File: vendor/laravel/framework/src/Illuminate/Support/Facades/Facade.php    
if (isset(static::$resolvedInstance[$name]))
{
    return static::$resolvedInstance[$name];
}

If the facade has already fetched (or “resolved”) an instance of the service, it will return that instance. You’ll remember from our definition

A Laravel facade provides access to a shared/singleton service without requiring the end client-programmer to use the application container.

that a facade is meant to provide access to a shared/singleton service — well it turns out facades will work with unshared services as well, but because of the above conditional, theses unshared services will be treated as shared/singleton by the facade.

This might give an object system developer reason to pause, as it introduces a second level of “singleton” into the system. A facade might not catch a shared service that’s changed in the application object at runtime.

This (admittedly rare) edge case must have seemed like an acceptable tradeoff to the Laravel core team, and of all the complaints about facades this is a relatively minor one.

Facade Controversy

If you’ve spent anytime lurking in various programmer communities, you’ve probably gotten a whiff of the displeasure some programmers have for Laravel’s facades. Among these complaints

I can only speak for myself, but I’ve found it more useful to not have strong opinions about these sorts of things. It’s good to know and understand programming/computer science theory, and the story of how we got here. It’s also good to be able to drop yourself into the mindset of a programmer who does have strong opinions, and be able to talk coherently about your own decisions, but ultimately it’s a lot easier to swim with the flow of a framework’s design. The ability to work with multiple programming systems, warts and all, is the most important skill a 21st century developer needs.

That said, I have my own personal list of gripes about facades, which we’ll cover next time in the facade troubleshooting article.

Originally published October 8, 2014
Series Navigation<< PHP Magic Methods and Class AliasesLaravel Facade Troubleshooting >>

Copyright © Alana Storm 1975 – 2023 All Rights Reserved

Originally Posted: 8th October 2014

email hidden; JavaScript is required