Repository Pattern in Laravel, it's worth?
Tags: laravel, php, testing
Published: 2021-03-30 15:46:11

The repository pattern consists of adding a layer of classes that are in charge of accessing the data source and obtaining the different data models.

> **[We can work together!](https://developerjoy.co/)**

They have methods such as `find`, `findAll`, `create` or `update` among others and are very common in frameworks like Symfony but not so much in Laravel.

There are articles, videos and even [libraries](https://github.com/drbarzaga/laravel-repository-pattern) to implement this pattern in Laravel, but does it make sense?

## Video: repository pattern in Laravel (in Spanish 🇪🇸)

{% youtube uZp2HPbibA8 %}

## In Laravel we use Active Record

In Laravel we have [Eloquent ORM](https://laravel.com/docs/8.x/eloquent) and it is based on the Active Record pattern. Otherwise, Doctrine, from Symfony, is based on the repository pattern.

In the active record pattern, each model corresponds to a table in our database and this model itself is our way of accessing this table. **We will be able to search, create or update records in the table directly using the model.**

```php
<?php

// Get user with id = 1
User::find(1);

// Search users or create a new one
User::all();
User::where('email', '=', 'hola@victorfalcon.es')->first();
User::create([ ... ]);
```

This has led many people to qualify active record as an anti-pattern. Specifically, it breaks with the Single Responsabily Principle of SOLID, since each model is responsible for both interacting with the database and its relationships and also, being a model that also contains some domain/business logic.

## Repository pattern in Laravel

From the beginning, it seems as a bad idea to implement a repository pattern with Eloquent ORM. We are making an active record based library work as a repository based ORM.

But it doesn't matter, we want our Laravel application to have repositories. Let's go for it.

### Creating a repository

We are going to create a `UserRepository` interface and his implementation with loquent:

```php
<?php

namespace App\Repositories;

interface UserRepository
{
    public function all();
    public function create(array  $data);
    public function update(array $data, $id);
    public function delete($id);
    public function find($id);
}
```

```php
<?php

namespace App\Repositories;

use App\Model\User;
use Illuminate\Database\Eloquent\ModelNotFoundException;

class EloquentUserRepository implements UserRepository
{
    protected $model;

    public function __construct(User $user)
    {
        $this->model = $user;
    }

    public function all()
    {
        return $this->model->all();
    }

    public function create(array $data)
    {
        return $this->model->create($data);
    }

    public function update(array $data, $id)
    {
        return $this->model->where('id', $id)
            ->update($data);
    }

    public function delete($id)
    {
        return $this->model->destroy($id);
    }

    public function find($id)
    {
        if (null == $user = $this->model->find($id)) {
            throw new ModelNotFoundException("User not found");
        }

        return $user;
    }
}
```

Lastly, we need to bind our repository with the implementation in a service provider:

```php
public function register()
{
    $this->app->bind(
        'App\Repositories\UserRepositoryInterface',
        'App\Repositories\UserRepository'
    );
}
```

As we can see now we have encapsulated all the data access in a specific class, and we can stop using the model for this although, in reality, our model still has the same methods and the repository depends directly on it and that it has this inheritance with Eloquent.

And the question is:

**Me—** Have we gained anything, is there any difference between doing `User::all()` or doing `$this->repository->all()`?
**Symfony dev—** Well, even though we're increasing complexity and duplicating code, we can now mock the repository and run tests without accessing the database, and that's cool!
**Me—** True, but if that was your problem, you should have said earlier. In Laravel we have some good options to fix that.

### Why it has no sense, IMO

Most of the time, when someone rejects active record is because it is not testable. You can't test a simple class without having to mount a database, because there is no way to change or replace the model that is part of our domain code. And this would be a big problem if, in Laravel, it was not so easy to test with the database.

As we see in the following test, Laravel comes prepared to do this kind of automatic testing in a simple and clear way and, with [Laravel Sail, we don't even have to worry about Docker containers](https://dev.to/victoor/laravel-sail-the-best-way-to-start-developing-with-laravel-l6d).

```php
use DatabaseMigrations;

class UserCreatorTest extends TestCase
{
    use DatabaseMigrations;

    private $service;

    protected function setUp(): void
    {
        $this->service = new UserCreator();
    }

    public function test_it_creates_an_user(): void
    {
        $data = [
            'name' => 'Víctor',
            'email' => 'hola@victorfalcon.es',
        ];

        ($this->service)($data);

        $this->assertDatabaseHas('users', $data);
    }
}
```

In addition, the latest versions of Laravel are even prepared to launch these tests in parallel making them run faster, so time is not an issue.

And finally, these tests bring us much more value than unit tests without infrastructure and, in most cases, even if we do unit tests we will also have to do functional tests with database to make sure that everything goes as expected so, in this case, we just make one test only.

## To long; Didn't read
In short, we have to assume that unit tests in Laravel are not common, but doing functional tests is easy and quick. This why we can adopt the active record without any issue.