Interfaces in Laravel: Simple Singleton Example of Two Services

When do you need to create interfaces in PHP/Laravel? Suppose you have a method with different implementations depending on some condition. In that case, it may be a candidate for an interface, with different classes implementing that interface method differently, depending on that condition. Let me show an example.

Let's look at the question from the Laracasts forum:

"Where I should put a simple code that needs to be used in a few places. This code just fetched certain $data about the user based on the environment"

$env = config('app.env');
if ($env === 'test') {
$data = // get from one place;
} else if ($env === 'prod') {
$data = // get from another place
}

Or, let's try to transform it to a more realistic example: you want to load the list of cities into your application, but the production environment should work with real city names. In contrast, other environments would work with a "fake" list.

if (app()->isProduction()) {
$cities = City::all()->toArray();
} else {
$cities = config('app.fake_cities');
}

You could solve it in various ways, but it sounds like one method to get the data from different sources. And the main thing is that the source is decided not by some method parameter but by the global environment.

One of the solutions is this:

  1. Create an interface with a method getList() that should return the array
  2. Create two Service classes with different logic for that method: one for "real data" and one for "fake data".
  3. When calling that Service from Controller, you call the interface everywhere instead.
  4. In the AppServiceProvider, you resolve the Interface with one of the Service classes, depending on the environment.

Let's see it in action, in the code.


Step 1. Create Interface

app/Interfaces/CityListInterface.php:

namespace App\Interfaces;
 
interface CityListInterface {
 
public function getList(): array;
 
}

Step 2. Create Two Service Classes

Both classes should implement that interface which requires them to have a method getList() with identical parameters and return types.

app/Services/RealCityService.php:

namespace App\Services;
 
use App\Interfaces\CityListInterface;
use App\Models\City;
 
class RealCityService implements CityListInterface {
 
public function getList(): array
{
return City::all()->toArray();
}
 
}

app/Services/FakeCityService.php:

namespace App\Services;
 
use App\Interfaces\CityListInterface;
 
class FakeCityService implements CityListInterface {
 
public function getList(): array
{
return config('app.fake_cities');
}
 
}

Step 3. In Controller, Type-Hint the Interface

Whenever you need to get the list of cities in Controller or elsewhere, type-hint the Interface, not a specific service.

If you need that in a particular method, do this:

use App\Interfaces\CityListInterface;
 
class RestaurantController extends Controller
{
public function create(CityListInterface $cityList)
{
$cities = $cityList->getList();

If you need to use the service in multiple methods of the class, use it in Constructor:

use App\Interfaces\CityListInterface;
 
class RestaurantController extends Controller
{
public function __construct(public CityListInterface $cityList) { }
 
public function create()
{
$cities = $this->cityList->getList();
 
// ...
}
 
public function edit(Restaurant $restaurant)
{
$cities = $this->cityList->getList();
 
// ...
}

Step 4. Resolve Interface with Service

In the AppServiceProvider or any ServiceProvider (Laravel default one or your custom one), you need to add this into the register() method.

app/Providers/AppServiceProvider.php:

namespace App\Providers;
 
use App\Interfaces\CityListInterface;
use App\Services\FakeCityService;
use App\Services\RealCityService;
use Illuminate\Support\ServiceProvider;
 
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
// This means .env file APP_ENV=production
if (app()->isProduction()) {
$this->app->singleton(CityListInterface::class, RealCityService::class);
} else {
$this->app->singleton(CityListInterface::class, FakeCityService::class);
}
}

Here you have the logic that creates a specific object of your specific Service class for all your application based on a global environment. So, you define it once here and "forget" it.


I've also seen different implementation syntax using boot() method of the ServiceProvider, and bind() instead of singleton():

class AppServiceProvider extends ServiceProvider
{
public function boot(): void
{
if (app()->isProduction()) {
$this->app->bind(CityListInterface::class, RealCityService::class);
} else {
$this->app->bind(CityListInterface::class, FakeCityService::class);
}
}

While practically you wouldn't notice much difference, this bind() method would create a new Service object whenever called. The singleton() way creates the object once and reuses it, saving memory.

You can find more examples of interfaces and different patterns of their implementations in my 2-hour course SOLID Code in Laravel

avatar

After all, we got back to the same ugly if .. else but placed somewhere else, moreover Service Provider is now coupled with environment on which we run app - which is in contradiction to SOLID principle.

Intoducing CitySelectionStrategyService that would use simple match(app()->isProduction()) in it's contstructor, to pick one of the strategies (RealCityService or FakeCityService) is the way to go for above example.

All 3: CitySelectionStrategyService, RealCityService and FakeCityService should implement same interface, Service provider should resolve abstract to CitySelectionStrategyService.

Doing it this way code follows SOLID principle.

https://refactoring.guru/design-patterns/strategy/php/example

avatar

Thanks for the valuable comment. Yes I agree that Strategy pattern is another good solution, but in my personal experience, it is more complicated to understand than using the more-or-less standard Laravel way that I described and I saw many developers using.

What you're talking about is more PHP and framework agnostic way.

Developers have a choice which to use, both are ok, in my opinion.

avatar

@jakub Can you elaborate on why using env() or config() at Service Provider is not following SOLID? Thanks.

avatar

@quyle92

ServiceContainer is a pattern that has just one responsibility - (when is it asked) it resolves an abstract implmentation to a concrete implementation. It's behavior should not vary on any conditions and only possible mapping should A to B, but not A to B but sometimes C and on 2nd Tuesday of a month D.

Pattern that returns instances of different objects depending on some codition is Strategy, and above is mix of container and strategy.

avatar

thank you so much for this clarification. now suppose that we have more complex case which is the folowing : we are trying to implement CQRS so we have this interface interface CommandBus { public function execute(); } we want the command bus to be implemented depends on where it called, in other words lets suppose that we have invokable controller called CreatePostController, and we want to bind CreatePostCommand to CommandBus in the constructor of this controller to implement this we will use this in the register on AppServiceProvider $this->app ->when(CreatePostController::class) ->needs(CommandBusInterface::class) ->give(function (Application $app) { return $app->make(CreatePostCommand::class); }); but the problem is that CreatePostCommand has a constructor which should be constructed from the data came from controller, here binding can also pass the data to the constructor of the CreatePostCommand?

avatar

Command bus is not an interface, CQRS has nothing to do with ServiceContainer or ServiceProviders, it may have something to do with DDD and/or Event Sourcing.

Before going this route I would consider all pros and cons. It's almost one way ticket - going back may be cumbersome if you go to far before realising that it does not work that great as you thought that it would be.

read here and read here 2

Perhaps combining actions, services and repositories will work better. If you are only concerned about read/write performance then laravel can do this for you read here

If you are stubborn then, good starting point would be: CQRS in laravel part 1 and CQRS in laravel part 2

avatar

First of all thanks for your replay that is not related to my origin question, i mentioned CommandBus an example and FYI the command bus could be an interface, back to the orgin question if you have answer the case i mentioned please answer me.

avatar

Command Bus can't be an interface - it has very specific taks to perofrm. I assume that you reffer to this or similiar exampe

If I create interfacte like this :

interface Framework { public function __construct(); }

Does this means that frameworks are interfaces ?

avatar

Hello Jakub, first to answer your question about CommandBus please take a look here example and if i consider Framework is an interface then no if it has constuctor and yes if interface Framework { public function handle(); }

avatar

Jakub we are going out of the scope of this great article. to get back to the road and if we want to ask the question in other words. can you handle nested binding with laravel containers.

avatar

I know Explicit Architecture and I implement it in laravel. In my first answer I gave you links to some implementation of CommandBus pattern (it's a design pattern that solves particular problem and not an interface) - last two links. We got to the point of discussion where I just drop off. Good luck with implementation.

avatar

thanks dude :)

Like our articles?

Become a Premium Member for $129/year or $29/month
What else you will get:
  • 59 courses (1057 lessons, total 42 h 44 min)
  • 78 long-form tutorials (one new every week)
  • access to project repositories
  • access to private Discord

Recent Premium Tutorials