How and why to use Facades and Service Providers in Laravel
Facades in Laravel provide a simple way to access pieces of your application but they can be a little confusing at first. When and why should you use them? We'll explore an example of separating code to make it easier to read and understand, whilst also making it easily accessible in a Laravel application.
We'll start of with some fundamental parts of Laravel to help understand why and how things are separated as they are.
The Service Container
At the core of Laravel is a component called the Service Container which you can imagine as being like the contacts list in your phone. You look up a name in your contacts and see their corresponding number. Similarly, in Laravel, you can lookup components of the framework, for example, cache
, and the service container will give you the Cache component. Easy!
Service Providers
Like adding a new friend to your contacts so you can access their phone number whenever you need, we can add new service providers to Laravel to let us access them in our applications in a number of ways.
Facades
Another way to access the service container is with Facades. These are again similar to the contacts in your phone analogy, but this time imagine assigning a nickname to each contact that you can easily remember.
Business Logic
Let's get started by making a new class to hold our new business logic. This code can be held anywhere in your codebase but a few ideas for locations include:
- Under a custom namespace in your app at app/MyBusiness/Logic.php
- Under your app namespace directly at app/BusinessLogic.php
- In a custom composer package
We'll be going with a class directly in our App namespace to keep things simple. Make a new file at app/MyCustomLogic.php
with the following code.
<?php
namespace App;
class MyCustomLogic
{
protected $baseNumber;
public function __construct($baseNumber)
{
$this->baseNumber = $baseNumber;
}
public function add($num)
{
return $this->baseNumber + $num;
}
}
We've defined a class and function with some really simple functionality. Now, let's register it with Laravel so we can start using it anywhere.
"But I can already access it anywhere simply by instantiating the class!"
― You, right now, probably.
Absolutely right, but also a simple example. The class __construct()
method needs a $baseNumber
that we probably don't want to be passing in all over our application. Let's see what we can do to improve that.
Service Provider
Let's make a Service Provider to register our new class and provide the default $baseNumber
variable. Jump onto the terminal inside your Laravel application and following along with steps below.
$ php artisan make:provider MyCustomProvider
Open app/Providers/MyCustomProvider.php
, replacing the register()
method with the below. The register method is used to bind new entries into the service container, like adding new contacts in your phone.
public function register()
{
$this->app->singleton('my-custom-logic', function () {
$baseNumber = 123;
return new \App\MyCustomLogic($baseNumber);
});
}
Let's have a look at what is happening line by line.
$this->app->singleton('my-custom-logic', function(){});
This method tells the service container that we're registering a singleton. The second parameter is a callback that will be called when Laravel needs to instantiate your class for the first time.
Singletons are only ever be instantiated once and that same instance always returned. The service container handles that for you.
$baseNumber = 123;
Inside the callback function, we're hard coding the $baseNumber
variable that will be passed to our class constructor. *
Next, add the service provider to the config/app.php file in order for it to be loaded by Laravel.
<?php
return [
...
'providers' => [
...
App\Providers\MyCustomProvider::class,
...
],
...
];
That's our service provider complete! So far we have:
- Made a class that contains some business logic.
- Created a service provider to bind our class to the service container.
- Made the service provider be loaded by Laravel.
Let's check out your new class and service provider in action.
$ php artisan tinker
This will launch an interactive shell for you application. Type the following and press enter.
>>> app('my-custom-logic');
You will see that the app()
helper returns an instance of our class. Running this function multiple times will always yield the same instance of the class because we registered it as a singleton.
>>> app('my-custom-logic')->add(4);
This will call the method on our business logic class and return the value. If the $baseNumber
variable in the service provider is set to 123
, then the output will be 127
.
Dependency Injection
A big feature of the service container is its ability to instantiate classes with dependencies. Let's say you wanted to use your new business logic in a controller. You can access it through app('my-custom-logic')
but wouldn't it be nicer to have the controller depend on our logic class being available? That's where Facades come in.
Facades can seem magical, but really they are just pointers into the service container. Let's make our own and then use it in a controller.
There is no default artisan
command to generate facades (as of writing), so copy the following into a new file at app/Facades/MyCustomFacade.php
.
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
class MyCustomFacade extends Facade
{
protected static function getFacadeAccessor()
{
return 'my-custom-logic';
}
}
You can see that the getFacadeAccessor
method returns a string, and that string is the same as the one we used to bind our singleton class MyCustomLogic
into the service container in our service provider MyCustomProvider
. This is no coincidence - the Facade is simply a pointer into the service container - like a nickname for a contact in your phone.
We again need to jump into config/app.php
and add an entry to aliases
to make Laravel load our facade. Our facade will be called CustomLogic
and is not defined by the filename, but rather the key used to register it in the array.
<?php
return [
...
'aliases' => [
...
'CustomLogic' => App\Facades\MyCustomFacade::class,
...
],
...
];
Let's try use our new facade in a controller. Make a new file at app/Http/Controllers/PageController.php
with the below content.
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class PageController extends Controller
{
protected $customLogic;
public function __construct(\CustomLogic $customLogic)
{
$this->customLogic = $customLogic;
}
public function index()
{
return $this->customLogic->add(4);
}
}
Here the __construct()
method (called by Laravel) has a dependency on our new Facade. It is prefixed with a backslash because facades are registered at the top level without a namespace as an alias by the facade service. You can also put use CustomLogic;
at the top of your file to forgo the backslash, but in my personal opinion keeping the backslash more clearly denotes the use of a facade which can improve readability.
Programs must be written for people to read, and only incidentally for machines to execute.
― Harold Abelson, Structure and Interpretation of Computer Programs
Testing
Another feature afforded by facades in Laravel is testability. They can be told to expect calls to methods, parameters and return values, making testing them easy.
CustomLogic::shouldReceive('add')
->with(4)
->once()
->andReturn(127);
Additionally, facades can be easily replaced with mock versions to return expected data instead of making calls to an API, for example.
That's it! Next time you need to separate out some business logic or better organise your code in a Laravel project take a look at service providers and facades.
Further Reading: Good Practices
* Hard coding variable like this is probably a bad idea. Instead, it would be better to store specific values like this in a config file.
Let's create a config file to store our $baseNumber
variable.
Copy the following into a new file at config/my-custom-logic.php
.
<?php
return [
'base-number' => 123
];
We can now change our hard coded value in the service provider to use Laravel's config()
helper method.
public function register()
{
$this->app->singleton('my-custom-logic', function () {
$baseNumber = config('my-custom-logic.base-number');
return new \App\MyCustomLogic($baseNumber);
});
}
If you have a secure string like a username or password, these shouldn't be stored in a config file. We can still reference our config file so our service provider doesn't need to change, but we'll move the hard-coded value 123
into Laravel's .env
file. The .env file is excluded from source control to prevent secure things like passwords being published.
Change your config/my-custom-logic.php
to the below. We'll use the env()
helper function to load in the value. This is the recommended way to store config variables from your .env
file and will cause problems if you use the env()
helper directly in your service provider.
<?php
return [
'base-number' => env('MY_CUSTOM_LOGIC_BASE_NUMBER')
];
Then simply open your .env
and add the below to the bottom of the file.
MY_CUSTOM_LOGIC_BASE_NUMBER=123
All secure!