Simple Repositories in Laravel

laravel
Table of Contents

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.

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.

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;    
    }
}

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! 👋

Enjoyed this post or found it useful? Please consider sharing it on Twitter.