Simple Repositories in Laravel

Abstracting common queries in your Laravel applications can be done in many ways. Let's take a look at the simplest way using the "Repository pattern".

Published 2 months ago | Updated 2 months ago
Laravel

The "Repository pattern" is essentially another level of abstraction on top of your database abstraction layer. In the context of Laravel, these are simple classes with methods that call more complex chains of methods on your model classes.

Generally, each repository is responsible for one entity within your application. For example, a UserRepository should only be responsible for retrieving User records.

Abstract implementation

I've seen various people use traits to implement repositories directly inside of their models. Personally I think this gives the model too much responsibility, especially since models in Laravel are essentially "God classes" already.

Instead, I'll create an abstract class that all of the repository classes will extend:

<?php
  
namespace App\Repositories;

abstract class Repository
{
    //
}

All of the repository classes will live inside of the app/Repositories folder and are namespaced accordingly. If you're following a domain-driven design, you could put this class inside of a "shared" domain.

Each repository needs to have some model-based context, so we'll add a static property to our base class.

<?php
  
namespace App\Repositories;

abstract class Repository
{
    protected static string $model;
}

I've chosen to make this protected since I don't need to access it externally, but I still need to have access to it / overwrite it in the child class. You could make it public if you wanted to do some conditional logic based on the value.

This $model property should contain the fully-qualified namespace of a model class, for example User::class.

The reason this implementation of the repository pattern is so simple is that all method calls from inside of the class will be delegated to an instance of $model.

The missing piece of the puzzle is the all-mighty magic __call() method.

<?php
  
namespace App\Repositories;

use Illuminate\Support\Facades\App;

abstract class Repository
{
    protected static string $model;

    public function __call(string $name, array $arguments)
    {
        return App::make(static::$model)->{$name}(...$arguments);
    }
}

Now whenever we call a method that doesn't exist on your repository class, it will instead be delegated / deferred to an instance of the underlying $model.

An example

A quick example would be creating a UserRepository that has some useful methods for finding a User by email, name and id.

<?php
  
namespace App\Repositories;

use App\User;

class UserRepository extends Repository
{
    protected static string $model = User::class;
  
    public function findByName(string $name): ?User
    {
        return $this->where('name', $name)->first();
    }
  
    public function findByEmail(string $email): ?User
    {
        return $this->where('email', $email)->first();
    }
  
    public function findById(int $id): ?User
    {
        return $this->find($id);
    }
}

Then, inside of a controller you could pull the repository in using dependency injection:

<?php
  
namespace App\Http\Controllers;

use App\Repositories\UserRepository;

class UserController
{
    protected $users;
  
    public function __construct(UserRepository $users)
    {
        $this->users = $users;    
    }
}

Reusing the class

You could copy and paste the class into each of your projects, but I don't like doing that. If I fix or implement something in one place, I'll have to go back and do it everywhere.

Instead I've packaged this Repository class up into a little package that you can install in your projects:

composer require ryangjchandler/repository

It's a stupidly small package, but copy-paste is bad. Install a package instead.

Sign off

I'd love to know if anyone else has used this pattern in their applications and how they implemented it. Comment below or tweet me if you have.

See ya! 👋