I want to be clear. I love the facade system in Laravel. It’s useful. It’s powerful. But it can also be a pain in the ass. Learning when to use, or not use facades and instead rely on dependency injection can not only help keep your code easier to maintain, but also easy to work with in the future.
Don’t know what facades are? Facades in Laravel are a simple wrapper to a more complex system, providing static method access to objects that have been previously configured in a service provider, either by Laravel itself or by you. It allows ease of use and rapid development, avoiding having to inject every single one of your dependencies in your code.
In Laravel 5, Taylor took this a step further and wrote wrapper functions (such as view() and redirect()) to make our code look really readable and lovely to work with. However, is there an issue with using these functions and facades? Should you be using them? The short answer is yes. The longer answer contains that horribly vague phrase “it depends”. Let’s explore.
Using and testing facades
A common use of facades invokes the view() function, as seen below:
The method above is identical to doing the following:
This is a nice piece of syntactical sugar that was introduced in Laravel 5. And it makes sense. Why not simplify those calls that you’re using everywhere, all the time?
Using facades in controllers like this I have no problem with, and will happily use them. My controllers are generally tested via acceptance tests with Behat, and my business domain tested using unit tests. However, if we start to use facades in our business layer we’ll begin to see some interesting problems.
Another pretty common use-case for facades is using App::make() or app().
There’s a couple of questions I would have with the code above:
- Why not inject the service as part of the command handler’s constructor?
- What exactly is app() doing?
When writing tests for this kind of behaviour, the developer needs to know what app is doing in order to appropriately isolate the command from external behaviour (if that is the wish). As a result a knowledge dependency is introduced. Developers need to know what app() does:
So now that we know this - we can then unit test by mocking out App::make in our tests.
But why do that? In order for that to work, you need to bootstrap the entire Laravel application just so you can write your tests (setting up facades and the like). Wouldn’t it be better to simply inject the dependency and mock it out when it comes to test? Yes, it would. But why is this any different from the example above when I said it was okay? Simple - our controllers usually aren’t unit tested. They’re tested as part of the full stack - we need the entire system running, so all dependencies will be tested anyway. We won’t be mocking out hidden dependencies.
There’s another benefit in avoiding facades in our business layer, as well. We don’t need to bootstrap the entire application in order to write a test. We can just extend a PHPUnit_Framework_TestCase and inject the dependency. This makes for much faster test runs. The same code above would look like so:
Now we can easily offer a mocked version of the service and isolate our command properly, without needing to have any prior knowledge of app(), view() or any other facade setup.
Facades are a really great tool and make a lot of sense in certain areas of your application, such as helper functions, views, or in your controllers (there’s probably a few other areas as well). However, I would recommend you keep them out of your business layer. Every facade is tied to a singleton object in Laravel and so it’s very simple to just inject the object that the facade represents. If you’re unsure which facades point to which objects, see Laravel’s facade reference.
Facades are not “bad”. They’re not “evil” as many claim them to be. They’re a tool - and a great one at that. In some cases facades are really the only way to nicely inject a dependency (such as in traits in complex systems) and so you shouldn’t refrain from using them if they make sense. Just make sure you understand when to use them and why. Overuse, just like any development pattern - can lead to problems.