I finally feel like I’m getting to grips with Laravel’s service container. I’ve understood HOW it works and WHAT is does for a while, but it’s taken a simple, real-world example to pull it all together and make it all “stick”.
Disclaimer: I may still have all this wrong, in which case correct me in the comments. I also note that while Laravel is opinionated, sometimes there’s many ways to do a particular job, so this may not be the only way to solve my problem.
Also, while I’m pre-ambling, I find myself talking to quite a lot of WordPress developers about the benefits of Laravel so I’m thinking of doing a couple of screencasts introducing the differences and benefits. If this is of interest leave a note in the comments. If there’s demand I’ll record some “Laravel for WordPressers” tutorials and let you know when they’re up.
Example: Application stats
Here’s my simple example that I think fits really well. I have a really simple side-project application that has users and actions, and a relationship between the two: every action has a user, and every user has zero or more actions.
I want to display in this application some statistics about how many actions have been taken by all users over different time periods.
This is really simple data to get in Laravel:
$dayCount = App\Action::whereDate( Carbon\Carbon::now()->toDateString() )->count();
But I wanted to wrap this up into a class so that I could write:
$dayCount = App\Stats->dayCount;
and so that I could implement any caching that might be necessary to improve performance.
But where should I put this code within Laravel? And how do I load it and make it available when I need it?
The service container is the answer.
Where do I put the stats code?
The answer is, it doesn’t really matter all that much. I chose to just create a class in /app
and in the \App
namespace, so I have, in the file app/stats.php
:
<?php namespace App; use App\Action; use Carbon\Carbon; class Stats { /* Code */ }
And then a bunch of simple methods and properties that do all the calculations and store the results. I use the constructor of this class to run all the queries and populate the class properties with the counts. Simple.
How do I load the code?
This is the weird bit, particularly for a developer who’s so deep in WordPress and so used to just require
ing everything I need. Laravel doesn’t have a require
in sight. It’s using the composer autoloader to load PHP files and the service providers/container to create instances of classes when needed.
So this is where the service container comes in. The service container is Laravel’s way of loading stuff like this into the application and making it available.
You can think of the application as just a bunch of services. When you need a service you grab it from the container. Most Laravel code is using services all the time without you knowing it. It hides the details of what’s going on behind facades and helper functions. Auth, cookies, sessions, mail, caching and views are all provided by Laravel services. For example, when setting a session you might use the session()
helper function, like this:
session()->get( 'savedData' );
This is just getting you a (or the) session object from the service container and running the get method of it.
But how do services get into the container?
Well, you create a service provider, or add it to an existing service provider.
A service provider is a simple class that has two methods.
register()
, which places the service into the container by registering it and;boot()
, which is called after all services are registered and allows you do do some initialisation
My stats class needs the application to be set up before it runs the calculation so I’m registering it in the boot()
method, like this:
public function boot() { $this->app->singleton( Stats::class, function () { return new Stats(); }); }
And I’m registering it as a singleton so that there’s only ever one instance as the class will instantiate once and then just be referred to. (Question: Is this any better than making things static?)
I then need to register the service provider in config/app.php
, which is easy; just add this to the list of providers:
App\Providers\StatsServiceProvider::class,
So we now have the class, we’ve created the service provider to shove it into the service container for later use, and we’ve registered the service provider with Laravel. Laravel starts up, knows about the service provider, runs register()
, which does nothing, and later runs boot()
which instantiates the class instance and puts it into the service container for when it’s needed.
Nice.
I can then access the class whenever I need, either:
- explicitly from the container (which is the
App
class) by usingApp::make( 'App\Stats' )
- explicitly using the
app()
helper:app( 'App\Stats' )
- using dependency injection through type hinting (or so I believe, I’ve not tried this out!)
In my case, I actually want the stats to appear in pretty much every view that I load, and there’s a shortcut for that, which is to “share” the data with the views by adding this to the boot()
method of the service provider:
View::share('stats', app('App\Stats'));
Every view (or a component of every view) can then have access to the stats without me having to explicitly make the stats available in every controller method. So my blade templates can just do:
<strong>Today:</strong> {{ $stats->dayCount }}
Neat! (But use wisely)
Conclusion
Hopefully I did all this right and explained it well.
Yes, this is a very simple example and perhaps the service container is overkill for loading this class. But I think it’s a simple and real enough example that it helps us see how to add this kind of generic, re-usable functionality into our app.
I certainly understand the service container better having done this. Hopefully you will too.
For greater completness, most of the code files involved are shown in this Gist.