Learning Laravel - Observations, part 2: Service providers and façades
Matthias Noback
- Laravel draft: true
See for an introduction to this series part 1: The service container. Let’s continue with a discussion about service providers.
Service providers
When you want to hook into the Symfony container build process, you have to write classes that inherit from Extension
. Laravel calls them ServiceProvider
and that makes sense: they will provide services, or basically service definitions, to the service container. This seems like a perfectly reasonable thing to do. I wonder if Laravel developers are also encouraged to combine all service definitions for the entire application in one service provider, e.g. AppServiceProvider
. Defining services looks like we’ve discussed in the first part of this series: by binding things, or setting up singletons:
class RiakServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* @return void
*/
public function register()
{
$this->app->singleton(Connection::class, function ($app) {
return new Connection(config('riak'));
});
}
}
What I find slightly confusing is this sentence in the documentation: “If your service provider registers many simple bindings, you may wish to use the bindings
and singletons
properties instead of manually registering each container binding.” Of course, it makes sense to simplify things, but it changes the control structure a bit. Instead of explicitly providing $this->app
with service definitions, you expect something to inspect your $bindings
and $singletons
properties. Not a big problem per se, but then there’s another sentence: “If your provider is only registering bindings in the service container, you may choose to defer its registration until one of the registered bindings is actually needed.” Instead of leaving this decision to the user (they have to think about it, have knowledge about this), maybe there is a way to change the design in such a way that developers will automatically do the right thing. E.g. make all services deferred by default, and warn the user as soon as they do something that would require them to mark it as “not deferred”. Another problem I see with the service providers that implement DeferrableProvider
is that there is now some duplication; the Connection::class
singleton is being defined by that name in the register()
method, but the name can also be found in the provides()
method:
class RiakServiceProvider extends ServiceProvider implements DeferrableProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->singleton(Connection::class, function ($app) {
return new Connection($app['config']['riak']);
});
}
/**
* Get the services provided by the provider.
*
* @return array
*/
public function provides()
{
return [Connection::class];
}
}
It’s like returning the keys of an array by returning a hard-coded list of the keys instead of using array_keys()
. Maybe it’s not a big problem, but still something that has to become part of your workflow: do you change something in the register()
method, also update it in the provides()
method.
A similar design issue is described in the following sentence: “You should never attempt to register any event listeners, routes, or any other piece of functionality within the register
method. Otherwise, you may accidentally use a service that is provided by a service provider which has not loaded yet.” This makes it feel like we can accidentally break a service provider by doing the wrong thing in its register()
method. I think there should be a way to keep the developer from making the mistake described here. For instance, you could rewrite the register()
method to not have access to the entire public interface of $app
(including make()
), but only to the methods that the developer is allowed to call there (e.g. bind()
and singleton()
):
interface PreBootApp
{
public function bind(/* ... */): void;
public function singleton(/* ... */): void
}
interface ServiceProvider
{
public function register(PreBootApp $app): void
{
// You can only call $app->bind() or $app->singleton() here
}
public function boot(BootingApp $app): void
{
// Here you can also call $app->make() if you want
}
}
Another option would be to simply throw an exception when a user tries to access a service inside the register()
method and tell them that they can’t do it, because the container hasn’t been fully booted yet.
I think when you’re talking about object design, you should always look for ways to make it easy to use an object in the right way, and impossible, or very hard to use it in the wrong way. And if a client tries to use it in the wrong way, to be very explicit about that.
The boot()
method is already provided by Laravel and is reminiscent of Symfony’s Bundle::boot()
method. The documentation uses a so-called view composer as an example for something you might want to register in a service provider’s book method: “So, what if we need to register a view composer within our service provider? This should be done within the boot
method. This method is called after all other service providers have been registered, meaning you have access to all other services that have been registered by the framework”. I think a view composer would be a really good case for tagged services: every developer needs to define their view composer as a service, tag is as “ViewComposer”, after which and then Laravel can simply take all those “ViewComposer” services and inject them as a constructor argument of the view service.
Conclusion
All in all I think that Laravel service providers are easy to use and work with. And although I think the design could be improved in subtle ways, I think they achieve with great simplicity what Symfony container extensions don’t. I think the complicating factor there is that a container extension is basically a way to hook into the container’s compilation process. This is a complicated process, so writing something that hooks into it is bound to be complicated too. Of course, most of what people will do in a service provider’s register()
method is what Symfony users will do in their .yml
files (or .xml
if you prefer those). Plus, a lot of the tricks are no longer necessary, because Symfony can now also resolve many services all by itself.
Façades
Instead of using an injected dependency, Laravel allows you to skip the injection part and use the service directly, by calling a static method on a so-called façade class. For example:
// With dependency injection, using a cache would look like this:
final class Foo
{
public function __construct(CacheService $cache)
{
$this->cache = $cache;
}
public function foo(): string
{
if ($this->cache->has('foo')) {
return $this->cache->get('foo');
}
// ...
}
}
// Using a façade, it would look like this:
final class Foo
{
public function __construct()
{
}
public function foo(): string
{
if (Cache::has('foo')) {
return Cache::get('foo');
}
// ...
}
}
A lot has been said about façades. In my experience, mentioning façades amongst developers will either generate rage or laughter. It has become a very sensitive topic, to the point where I find that people will be hesitant to say they love them and use them, or to say that they wouldn’t let them pass through in a code review. This makes façades a touchy subject. Since this is a series where I’m making a few observations from my personal standpoint, I’ll still give you my opinion here. Anyway, to be very honest, any topic that is a touchy topic and ends up not being discussed, is something worth discussing. We’re talking about ways of writing code here, we’re not putting entire lifestyle’s or personalities up for debate.
So here’s my view on façades: I don’t think there’s an actual need for using them. They are really workarounds for something we’ve been fighting hard for: the right to use dependency injection, and the option to inject all of a service’s dependencies as constructor arguments. For quite some time, this wasn’t even an option. A framework would instantiate our classes for us without even offering the option to pass in any constructor argument (like Zend Framework 1 instantiated our controllers, then called an init()
method on them), or a framework would allow services to be retrieved from a singleton (like symfony 1’s infamous sfContext::getInstance()
). With façades, we are looking at yet another trick, which doesn’t rely on a single singleton class like sfContext
, but offers multiple static classes which still work more or less in the same way.
Traditionally, the reason against using globally available static methods or functions to access service dependencies was that the classes that did this couldn’t be easily unit-tested. This is why the Laravel documentation adds the following paragraph: “Typically, it would not be possible to mock or stub a truly static class method. However, since facades use dynamic methods to proxy method calls to objects resolved from the service container, we actually can test facades just as we would test an injected class instance.” In code, this is what that looks like:
Cache::shouldReceive('get')
->with('key')
->andReturn('value');
This is a pragmatic solution, which I think is actually quite okay, besides the fact that apparently this allows mocking of services in production code. That may be quite confusing. However, there are more important issues with using a façade in your code:
- Implicit dependencies.
- Lack of abstraction.
- Framework coupling.
Regarding 1 (“implicit dependencies”); the documentation covers this problem in an excellent way: “However, some care must be taken when using facades. The primary danger of facades is class scope creep. Since facades are so easy to use and do not require injection, it can be easy to let your classes continue to grow and use many facades in a single class. Using dependency injection, this potential is mitigated by the visual feedback a large constructor gives you that your class is growing too large. So, when using facades, pay special attention to the size of your class so that its scope of responsibility stays narrow.”
As I already pointed out, I think a framework should help developers do the right thing. Of course, any code can easily end up being too complex to handle anymore, but offering a tool like façades makes this easier to happen, as described in the documentation itself. The developer won’t receive feedback from the code that a class might may be doing too much, as they would when they would use dependency injection.
Regarding 2 (“lack of abstraction”); there is also the problem of Dependency inversion, or the fact that depending on a façade means that you’re depending on a concretion (even though it is represented by an interface). There’s dependency injection, which means you’ll inject all of a service’s dependencies as constructor arguments. But then there’s another option, which is to introduce your own abstraction (I’ve written about that in more detail in When to add an interface to a class). When using a façade, there’s no way to add your own abstraction for what that façade does; you’re stuck to whatever its current implementation is.
Regarding 3 (“framework coupling”); this is related to the previous point. I wouldn’t want my domain code, and application logic (a.k.a. use cases) to use façades directly, as it would make dependencies go from high-level to low-level code, or from abstract to concrete. There’s one important exception to this rule: code that is already coupled to the framework, and is supposed to be coupled to it, could still use façades freely. If you want; I would personally not prefer it, but it doesn’t do any damage there, since you’re not looking for decoupling there anyway. You’re also not looking for abstractions there, nor would you need to worry that classes are ending up with too many dependencies. So where in your code would it be totally fine to use façades? Examples would be web controllers/actions, console commands/handlers, or framework-specific add-ons/plugins, like maybe the service providers we saw earlier, or framework hooks/event listeners of some sorts. As long as you keep your core code free of framework involvement.
Conclusion
As with any framework-specific tools, façades should be used only when you’re already in the framework’s realm. When you’re working on domain/business-specific code, don’t use them at all. Introduce your own abstractions, and add concrete framework or library-specific implementations, and make sure you inject the abstractions as dependencies, not the concretions.
That said, I really think that it’s worth the extra effort to learn a bit about Laravel’s services and to inject them, instead of falling back on façades (or helper functions). Getting into the habit of using dependency injection everywhe