Skip to content

Using The Repository Pattern in Laravel

Published: at 05:00 PM

Why Use the Repository Pattern?

At its core, the repository pattern is a structural design pattern which serves as an abstraction layer between the data access logic and the business logic within our application. This ensures that these concerns are kept seperate.

This level of seperation not only makes your codebase easier to maintain and understand, but I believe it also promotes code reusability and significantly enhances your ability to conduct unit testing within your application regardless of framework.

Benefits of Using the Repository Pattern in Laravel

Utilising the repository pattern in Laravel gives us several advantages. It decouples models from our controllers, it streamlines data access logic and it makes the codebase more robust and adaptable to changes.

It also promotes better testing practices by allowing developers to mock the repositories in unit tests ensuring that our business logic is tested independently of data access logic.

Implementing the Repository Pattern in Laravel

Implementing the repository pattern in Laravel involves a few key steps. First, we must create a repository interface that defines the methods to access data sources and then, an implementation class that encapsulates the logic of accessing the data source. This is then used within our Laravel application wheverever we would directly call our database, whether that be using Eloquent or the good ol’ DB facade.

In your Laravel application, create a new folder under app/Repositories. Inside the newly created Repositories folder, create an interface corresponding for the model/table you would like to create a repository for (in our case, User so we will call this UserRepositoryInterface and create some methods.

namespace App\Repositories;

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

interface UserRepositoryInterface
{
    public function getAllUsers(): Collection;
    public function getUserById($id): ?User;
    public function deleteUser($id): void;
    // Add more methods as needed
}

Now we have our interface, we can focus on our implementation. You could have an implementation for every different ORM, or API etc. In our case, lets create an implementation for Eloquent, the default Laravel ORM. Let’s create a subfolder called Eloquent inside our app/Repositories folder.

Inside the Eloquent folder, create a new class that implements the interface you defined for example, UserRepository.

namespace App\Repositories\Eloquent;

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

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

    public function getUserById($id): ?User
    {
        return User::find($id);
    }

    public function deleteUser($id): void
    {
        User::destroy($id);
    }

    // Implement the rest of the methods defined in the interface
}

Now we have our interface, and our implementation class, we now need to register the repository within a service provider so we can a) harness the power of dependency injection and b) easily switch out our repository. I prefer creating a new service provider for this purpose. This can be done by running php artisan make:provider RepositoryServiceProvider. This will generate us a new service provider located in app/Providers. In this class, let’s bind our interface to our implementation.

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use App\Repositories\UserRepositoryInterface;
use App\Repositories\Eloquent\UserRepository;

class RepositoryServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->bind(UserRepositoryInterface::class, UserRepository::class);
    }
}

Don’t forget, you will need to register your new service provider in the providers array located in config/app.php. Now we can go ahead and utilise our new repository. Here is a really basic example of a controller which utilises our new repository:

namespace App\Http\Controllers;

use App\Repositories\UserRepositoryInterface;
use Illuminate\View\View;

class UserController extends Controller
{
    public function __construct(
      protected UserRepositoryInterface $userRepository
    ) {}

    public function index(): View
    {
        $users = $this->userRepository->getAllUsers();
        return view('users.index', compact('users'));
    }
}

Some Considerations

The more advanced readers here may have noticed that our repository is returning Eloquent Models and Laravel Collections. Whilst this is okay for most projects where you’re not going to be wanting to swap out the underlying ORM/data access layer, there is an argument that we should be casting our Models into Data Transfer Objects to be used in our application as if we wanted to switch our ORM to say Doctrine, we would have to go through our application and update a lot of code.

Common Pitfalls of Using the Repository Pattern in Laravel

Ultimatley, using this pattern can create some complexity and can increase the time it takes for new developers to get up to speed on your codebase, but, the benefits in terms of clear seperation of business logic from data access logic and the ease of testing it creates is way worth it in my opinion.

You should also make a concious effort to not stray too far from Laravel’s conventions.

Conclusion

The repository pattern offers a compelling appraoch to improving the architecture of your Laravel application by providing cleaner seperation of concerns, enhanced mtaintability and superior testing capabilities.

I will say that, like everything in the world of software development, people do tend to make their own opinions on whether they see patterns like this as beneficial to I do urge you to test this, play about with it and work out if you believe it to be as great as I do.