Learning Laravel – Observations, part 1: The service container
With excerpts from the documentation
I have worked with symfony 1, Symfony 2+, Zend Framework 1, Zend Expressive, but never with Laravel. Until now. My partner was looking for a way to learn PHP and building web applications with it. Most of my own framework knowledge is related to Symfony, so my initial plan was to find a Symfony course for her. However, Jeffrey Way's Laracasts also came to mind, and I thought it would be an interesting learning experience for us both if Laravel would be the framework of choice in this matter. It turned out to be a good idea. My partner is making good progress, and I get to see what Laravel is all about. We have a lot of fun together, finding out how it works, or what you're supposed to be doing with it.
As a side-project, I've been reading the official Laravel documentation. Being a human with framework habits, I couldn't help but compare the Laravel approach to the Symfony approach. I've also compared some of the suggestions from the documentation with what I think are best practices for any web application, regardless the framework that you choose. Something to keep in mind when reading this article is that my approach to web application architecture is to keep the framework and other infrastructural concerns far from the code that represent my application's use cases and the domain models it contains. See also the series I wrote about this approach earlier (part 1, part 2 and part 3). I'm usually not looking for a way to develop something as quick as possible, or make development as convenient as possible. I try to find ways to protect its future against external influences, like changes in the language, the framework, a desire to switch to a different database, queueing system, etc. This will certainly color some of my observations in this and following articles, including the advice I give here on which feature to use, and which ones to ignore.
I also want to make clear that I'm absolutely not here to bash Laravel or anything. Quite the contrary actually, I think its builders have made some great decisions. They have also added things that I guess might nudge people in the wrong direction in terms of object design, but keep in mind: this is all my opinion. Of course, I try to give proper arguments, but in the end I'm not here to say: use Symfony, ditch Laravel. If anything, my message when it comes to frameworks would be: use them to your advantage, but only in the parts of your code base where it makes sense. Don't let them determine your overall design (or architecture), make sure you can swap parts of them out when you want or need to. If Laravel/Symfony/Zend/etc. suits your needs today, use it today, but prepare for the day when you don't want to use it anymore.
With all disclaimers out of the way, let's go!
The service container
Let's start with a discussion about the service container. After I wrote the article on Hand-written service containers someone pointed out to me that my solution wasn't the only one that has the advantage of being easy to refactor; the classes, interfaces, and methods that you use in Laravel service definitions are also indexable by your IDE and are therefore easy to rename, move, etc. That's totally correct. This is what a service definition looks like:
// Defining a service inside a closure:
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
// Using it somewhere else:
$this->app->make('Helpspot\Api');
// Note: you can use `Api::class` instead of spelling out the full class name
However, most services don't even need all this work, because you can give the container instructions. E.g. instead of binding an interface with a closure, you can also bind it with a class name, so that, whenever an object requires an instance of the interface, it will get an instance of that class injected:
$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);
Shared versus singleton services
I think it's very interesting that by default the services that you define using bind()
are not shared, that is, the next time we'd call $this->app->make()
, you will get a
Truncated by Planet PHP, read more at the original (another 15717 bytes)