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!

Show Comments

Get the latest posts delivered right to your inbox.