Last time we described the PHP patterns used to implement Laravel’s facade feature, and started to describe some of the “gotchas” involved. We briefly discussed how a facade introduces a second type of singleton/shared service into the system, as well as the inherent problem of hijacking/borrowing a word that’s already in use to describe another object oriented programming pattern.
This week we’re going to continue that critique of facades, with particular attention paid to typical problems you may run into implementing your facades, or using facades in a system/application you’re not intimately familiar with.
Aliases and Global Classes
One problem you’ll run into with facades is the global namespace problem. The facades classes that ship with Laravel have a proper PHP namespace, but in order to provide dead simple access to the facade class, the system creates a globally available class aliases
Class Alias: DB
Actual Facade Class: Illuminate\Support\Facades\DB
These globally available aliases are generically named (DB
, Redis
, etc.), and can lead to namespace collisions — i.e. client developers create classes (typically model or migration classes) named the same as the aliases. This, in turn, can lead to confusing situations with misleading error messages.
This is more than a hypothetical ivory tower concern. Consider this stack exchange question. Here the user has some other class named Redis
in the global namespace. Instead of loading the facade alias, PHP defers to their class. It’s not obvious that this is what PHP’s doing, and the user ends up confused and stuck.
The big trade off, as it always is with a global namespace, is ease of use. While Laravel’s system code uses namespaces, the system itself is biased towards giving client programmers the ability to ignore namespaces all together.
Consider that Laravel has global facade classes, an app/routes.php
file included in the global namespace, an app/start/*.php
files hierarchy for global startup tasks, and an autoloader and code generation tools that that ship loading and creating classes in the global namespace (from/in the app/commands
, app/controllers
, app/models
, and app/database/seeds
folders).
While this may make “modern” PHP developers cringe, it does remove the namespace learning curve barrier for people new to Laravel and new to programming. I suspect it’s one of the reasons Laravel’s such a popular framework.
One Abstraction Over the Line
Another tricky part of facades is the issue of meta-programming in general. Facades provide a great way for folks completely new to programming with a way to “do things”, but a programmer with a small bit of experience will see something like
DB::select
and assume there’s a class DB
with a method select
somewhere. In hiding the implementation details of the service container, facades also hide the actual class doing the work, which makes it harder for a developer to know what class actually implements a service.
Even as an experienced developer, I couldn’t tell you off the top of my head what the “DB
” facade’s actual class is. Whenever I need to know this, I need to
- Open up the app configuration
- Look for the
DB
alias and the actual class -
Get the service identifier (
db
) from the facade class definition -
Use
grep
orack
to search through my source code to find how the service was bound and/or extended -
Jump to that class definition
-
Then, sometimes, some extra searching if someone’s used
App:extend
to change a service definition
The same goes for all standard IDE and code completion/browsing tools — unless they’re configured to work specifically with Laravel, looking up method definitions can be a flow breaking event.
Facades make reading the client programmer’s intent easy, but at the expensive of making learning how the system works more difficult.
This might be a tenable situation if facades were the only abstraction to use static method calls. However, Laravel’s default ORM (Eloquent), also uses static method calls. Things like the following
Users::all(...);
Users::where(...)->get();
are a common site in any Laravel application. Neither all
, nor where
, are defined as static methods on an Eloquent model. Instead, Eloquent leverages the power of __callStatic
to give model objects special features.
We’ll cover the specifics of this ORM magic in another article, but the takeaway here is, in Laravel static method call syntax (::
) might mean calling a static method, might mean calling a facade, or might mean calling a magic ORM method. That is, as a programmer, you can’t just look at
Someclass::foo(...);
and know where to find the method foo
.
In the end, Laravel’s meta-programming creates a weird, two plateau learning curve. Of all the modern PHP programming frameworks it’s the easiest to get started with. However, that ease comes at the cost of a very steep learning curve when you’re ready to start digging into how the framework does its job.
In future articles article we’ll dive deep on some of Laravel’s other magic methods, including the aforementioned Eloquent ORM and Laravel’s database manager service.