The Repository Service Pattern in your Laravel application

The Repository Service Pattern in your Laravel application

Written by Mattia Toselli on Nov 21st, 2022 Views Report Post

Why the repository service pattern is a good idea

As the definition says:

"The repository is a layer between the domain and data layers of your application with an interface to perform create, read, update and delete CRUD operations".

One may argue that the model is an abstraction by itself, and these probably makes sense in some way, but an abstraction has the task of decoupling application from dependencies and application logic, and as we know, models leverage on the Eloquent ORM.

Let's say for example that we would like to use Doctrine or that we need to use two different databases for the same model for different conditions, or that we want to use the cache massively. That would cause a massive list of updates in the code, risk of introducing bugs and a generous refactor of our controllers. If the client were to propose changing the data structure, it would be difficult to impossible to implement such a change if the code is not written correctly. Interfaces should be the basis for every implementation so that in the event of changes, only the class implementing the interface needs to be changed instead of the code in the entire application. What would we do if the client needs to use a Dataabase tha Eloquent does not support? A tipical example could be:

class UsersController extends Controller
{
   public function index()
   {
       //using the typical Laravel Enjoyer logic...
       $users = User::all();
       //method logic...
   }
} 

But as we know, this is a bad practice (well, probably not that bad, there are cases in which you could use it, and we'll see when at the end).

Create a service for User Model abstraction

Let's jump directly to the code. First, let us create a new folder in App named Repository, then in a new App/Repository/UserRepository.php file:

<?php
namespace App\Repository;

use App\Models\User;
use Illuminate\Support\Collection;

class UserRepository implements UserRepositoryInterface
{
    public function all() : Collection
    {
        return User::all();
    }

    public function find(String $id) : ?User
    {
        return User::find($id);
    }
    
    public function insert(Array $attributes) : Bool
    {
        $user = User::insert($attributes);
        return $user;
    }
    
    public function update(String $id, Array $attributes) : Bool
    {
        $user = User::find($id);
        return $user->update($attributes);
    }
    
    public function delete(String $id) : Bool
    {
        $user = User::find($id);
        return $user->delete();
    }
}

and in the same folder let's code the interface, in a new file named App/Repository/UserRepositoryInterface:

<?php
namespace App\Repository;

use App\Models\User;
use Illuminate\Support\Collection;

interface UserRepositoryInterface
{
    function all() : Collection;
    function find(String $id) : ?User;
    function insert(Array $attributes) : Bool;
    function update(String $id, Array $attributes) : Bool;
    function delete(String $id) : Bool;
}

We created an interface that wraps all the basics operations of the User model. Now, let's create a service and see how we can call it.

Register a service and use it

Open a terminal and use the Artisan command:

php artisan make:provider RepositoryServiceProvider

In the newly generated file, register the service:

<?php 

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Repository\UserRepositoryInterface; 
use App\Repository\UserRepository; 

/** 
* Class RepositoryServiceProvider 
* @package App\Providers 
*/ 
class RepositoryServiceProvider extends ServiceProvider 
{ 
   /** 
    * Register services. 
    * 
    * @return void  
    */ 
   public function register() 
   { 
       $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
   }
}

Now, in config/app.php, register the Service in the providers:

'providers' => [

        /*
         * Laravel Framework Service Providers...
         */
        Illuminate\Auth\AuthServiceProvider::class,
        Illuminate\Broadcasting\BroadcastServiceProvider::class,
        .
        .
        .//other services....
        .
        App\Providers\RepositoryServiceProvider::class,
    ],

We can now call these methods by injecting the right service into the controllers.

<?php

namespace App\Http\Controllers;

use App\Repository\UserRepositoryInterface;

class UserController extends Controller
{
    protected $UserRepository;
    
    public function __construct(UserRepositoryInterface $UserRepository)
    {
        $this->UserRepository = $UserRepository;
    }
    
    public function list(Request $request)
    {
        return $this->UserRepository->all();
    }
}

Some tips and caveats

  • Each repository should have its own interface! Never create a repository without its own interface!
  • Inject the model in a constructor, don’t use a static class.
  • These kind of logic is useful in great projects that are going to live many years, with many features and updates, there's not anything against this pattern in small projects, but the time cost could be big since the model pattern provided out of the box could be axactly what fits in your project without further tweaks. Do your own choices!

Comments (1)