Laravel — Why you’ve been using the Repository Pattern the wrong way

Sergiu M. Neagu
6 min readMar 29, 2018

--

I have seen many GitHub repositories and tutorials presenting the Repository Pattern implementation in Laravel. Indeed, this is a great design pattern to implement within a PHP (and not only) project. But let’s dive deeper into this subject:

What is a repository?

Use a repository to separate the logic that retrieves the data and maps it to the entity model from the business logic that acts on the model. The business logic should be agnostic to the type of data that comprises the data source layer. (…) The repository mediates between the data source layer and the business layers of the application. It queries the data source for the data, maps the data from the data source to a business entity, and persists changes in the business entity to the data source. A repository separates the business logic from the interactions with the underlying data source or Web service. — mdsn.microsoft.com

Wait, what? It retrieves the data and maps it to the entity model? Doesn’t Eloquent do just that? This means an Eloquent model is a repository!

Yes, Eloquent does that, but it is not a repository. It is an ORM (Object-Relational Mapper).
An ORM is an implementation detail of a repository, it just makes it easier to access the database in an OOP way.

Basically, the main benefit of the Repository Pattern is that you can change the persistence (e.g: database) for a model by only writing a new repository, so you won’t have to touch any other code in your app.

Most implementations of the Repository Pattern I’ve seen look like this:

<?phpnamespace App\Repositories;class EloquentUserRepository implements UserRepository {    (...)    public function getAllUsers() {
return $this->model->all();
}
public function getUser($id) {
return $this->model->find($id);
}
}

What is wrong with this?

Well, first of all, I’ve made a mistake on purpose: method naming. It’s a mistake that I’ve seen in many tutorials while I was studying this topic. You should never name your methods like that! Since we already know that we’re accessing the userRepository, we’re expecting it to return some users, so the user keyword should not be in the method.

Which one do you think is simpler?

$userRepository->getAllUsers();
$userRepository->getUser($id);

or

$userRepository->all();
$userRepository->find($id);

I bet you would rather use the second one. Other than all and find, we would have where, whereIn, latest, inRandomOrder, skip, take, etc, all of which are methods that Eloquent already provides. So if that’s simpler, why wouldn’t we just use the __call magic function this way:

public function __call($method, $parameters) {
return $this->model->$method(...$parameters);
}

Well, we would not, because if that’s all we need to put inside the classes in that layer, then we don’t need that layer, right?

Since most of the GitHub repositories that present Repository Pattern implementations in Laravel have some basic examples, like the (wrong, again) getAllUsers method, another thing that developers get wrong about this design pattern is that they think they can add methods like this one:

public function getAdults()
{
$users = $this->model->where("age", >=, 18)->get();
return $users;
}

That’s wrong since it’s implementing the business logic, it has nothing to do with the database itself.

Another wrong thing about this implementation is that it returns Eloquent models. This makes your app coupled to Eloquent. So now you have a choice: you either stick to Eloquent and build your repositories like that — but then there’s no point in building a repository in the first place, since you’re only abstracting Eloquent queries and a repository is not, by definition, an abstraction of the ORM — or return a custom object and don’t use Eloquent above this layer anymore, but then you’re missing one of the key features of Laravel.

Eloquent provides support for MySQL, PostgreSQL, SQLite and SQL Server. I have been using Laravel in 90% of the projects I’ve worked on for the past 3+ years. I’ve built some of them from scratch and others were projects I’ve taken over in the middle of the development process. Some of those were implementing the Repository Pattern. Never have I met a situation where I needed to implement a persistence that Eloquent is not compatible with, so this extra-layer was just an overkill for the development process.

“Ok, so how should I keep my controllers slim if I’m not using the Repository Pattern?” you may ask. Well, if you were implementing this pattern the right way, it would not have helped you keep your controllers slim in the first place since a repository is nothing more than an adapter for a specific persistence and it should not implement any business logic nor application logic.

Simple answer: use a Service Layer.

What’s a Service Layer?

A “Service Layer” exists between the UI and the backend systems that store data and is in charge of managing the business rules of transforming and translating data between those two layers. — Cliff Gilley, Quora

Basically, a service layer sits between the presentation layer and the database layer, so this is where you would put all of your application logic. A basic Service Layer implementation in Laravel would end up having these 4 layers:

  • UI
  • Controller
  • Service
  • Database/Eloquent

A simple service with a method that stores a user would look like this:

class UserService
{
protected $user; public function __construct(User $user)
{
$this->user = $user;
} public function create(array $attributes)
{
$user = $this->user->newInstance();
$user->fill($attributes)
$user->save();
return $user;
}
}

Now, inside your controller, you can use it the same way you’ve been (probably) using the repositories, using the dependency injection feature:

class UserController extends Controller
{
protected $userService;
public function __construct(UserService $userService)
{
$this->userService = $userService;
}
public function store(CreateUserRequest $request)
{
$user = $this->userService->create(
$request->except(['_token'])
);
return redirect()->route('user.show', ['id' => $user->id]);
}
}

Note that I’m using Laravel’s php artisan make:request to validate the request inside the controller. Ideally, any validation should be done inside the service, but I like to use this feature Laravel provides, so I’m doing it inside the controller.

Now let’s say there are 3 controllers that have methods where a user can be created. Let’s consider this (stupid) situation: the business logic requires that, from now on, all of the users that have a first name which starts with the letter “A” will get “Donald” as a first name. If you were to replicate the creation code inside each of those 3 controllers’ methods, you would have to modify all of them. If you were to implement this logic inside a repository, this would break the Repository Pattern’s rule of not implementing the business/application logic.

But if you’re using the Service Layer, all you’ll need to do is go to the UserService file and modify this one method:

public function create(array $attributes)
{
$user = $this->user->newInstance();
if($attributes->firstname[0] === 'A') {
$attributes->firstname = "Donald";
}
$user->fill($attributes)
$user->save();
return $user;
}

(I hope there’s no Andrew Trump)

And yes, the UserService is where you would put the getAdults method if you’d want to.

I’m sure there are situations where a repository layer would be helpful (even though I’ve met none), but a service layer is present in all of the applications I’m building. It saves me lots of time by not having to modify multiple files because of one simple business/application logic modification.

Conclusions

On a medium/large application, I’d recommend you to always implement a service layer (I do it on smaller ones too, just in case the app will grow).

If you still think you need to use the so-called (’cause it’s not really) Repository Pattern implementation, make sure you’re building the service layer first. This will prevent you from implementing the business/application logic inside your repositories. Ex:

public function getAdults() {
$users = $this->userRepo->where('age', >=, 18)->get();
return $users;
}

Thanks for your time!

--

--

Sergiu M. Neagu
Sergiu M. Neagu

Responses (9)