Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

pasadinhas's avatar

Repositories and Models

We can use the Repository pattern to have a more flexible way of abstracting the persistence layer of our apps. For example, we can have a general UserRepository(Interface) and then multiple implementations like DbUserRepository, FileBasedUserRepository, etc... And all that implements the interface.

That's nice! We can change the implementation and keep the API.

But we allways expect to recieve an instance of an Eloquent Model from the repository. Isn't that wrong? I mean, if we change from a Db repo to a File Based repo. Then what? What does the File Based repo return? It must have the same API as the Eloquent Model. Souldn't we use an interface (or other abstraction) for this?

So... What do we win for using repositories if our Model still extends Eloquent?

0 likes
31 replies
pasadinhas's avatar

That's and interesting arcticle, but it don't quite answers my question.

Imagine Larabook. We have a UserRepository (which will most likely became DbUserRepository, and we'll extract an interface from it). The methods of that UserRepository mostly return instances of Larabook\Users\User, a class that extends Eloquent. That User class has methods like "followers" which return an Eloquent specific relationship.

Now, if I want to change my (Db)UserRepository for a FileBasedUserRepository, I'm not going to be able to use that User class, as it extends Eloquent and I'm not using Eloquent anymore -- I'm using a file based persistence.

After all, I used a Repository, but my code is broken as soon as I change the Repository implementation to NOT use Eloquent. Because in all parts of my application I expected a Eloquent model to be returned from the Repository.

Did I make my point?

thepsion5's avatar
Level 25

This has been brought up a couple times before, and in my experience, there are basically three answers:

Screw it, Use Eloquent

  • Good: Eloquent, in all it's glory

  • Bad: Switching data sources is a big pain in the ass, especially if you liberally use the query builder for relationships

Create an Entity Interface, Have Your Eloquent Model Implement It

  • Good: Switching implementations requires much less effort, and the only wildcard is wrapping your eloquent-specific methods and including them in the interface and making sure you document the entity's properties

  • Bad: Relies on developer discipline to not leak Eloquent's implementation details into your entity, you give up on a lot of Eloquent's conveniences

Create a Separate User entity, Map Your Eloquent Model to it and Back

  • Good: You don't need to worry at all about switching repositories breaking your existing code

  • Bad: You have to implement a mapping layer to translate eloquent models to your entity classes, and will need to do so again if you implement another repository.

23 likes
psmail's avatar

Isn't this what the IoC is for?

Setup a db repo that implements Interface A. Set up a file repo that also implements Interface A. Setup a service provider to point to one of either db or file repo. Code to the interface. Switch the service provider binding to switch the repo being used.

Though, I am open to the notion I haven't understood the question.

JeffreyWay's avatar

That's the basic idea, but the issue is that, in your Eloquent-specific repo, we're returning Eloquent collections, whereas some other implementation might just return an array of DTOs.

For me, this comes down to convenience vs. purity, if that makes sense. The truth is that you will probably never switch it out. So, for me, I'm fine with with returning Eloquent, while trying to be mindful of what I use in my views. From that perspective, the repository becomes more of a clean abstraction layer for defining readable method names for my various db queries.

If you're super concerned with this, then you might consider using Doctrine instead.

15 likes
pasadinhas's avatar

Thanks for your reply @JeffreyWay! I am not super concerned, it was more a theoretical question. Just wanted to know what the alternatives are. As I said above, for my projects I'll stick to Eloquent!

thepsion5's avatar

For large projects, I think it might be worthwhile to selectively use option #3 - I'm currently refactoring my latest big project to do something similar for performance reasons. But I'd say 95% of the time you're fine using Eloquent, just go light on dynamic relationship queries and such.

1 like
JeffreyWay's avatar

If you want to experiment, then your Eloquent repos can call ->toArray() before returning.

Maybe this is the "correct" thing to do, but it comes at a big cost.

1 like
floristenhove's avatar

I'm wondering why you would use a Repository when you know you're never gonna switch out Eloquent? What other advantages are there?

I've been reading a lot about this for the last couple of days but there are so many different approaches and opinions about this that I'm a bit lost.

2 likes
pasadinhas's avatar

@floristenhove a good motive is to have a dedicated place for putting your data layer logic. You will most likely have queries that you'll want to use in multiple parts of your application. What would you do? Write the query over and over again? No! You write a method on your repository and use it anywhere you need that query.

Also, your queries won't be all like User::find($id), you will have more complex queries like User::with('profile')->orderBy('created_at')->limit(20)->get(). That's just an example of a query that is NOT readable. Extract it to a method in your repo and your code will become much more readable (if you name the method correctly, of course).

Another reason is that a repository is much more testable.

There you have three advantages of using a Repository even if you don't want to change your persistence.

9 likes
JeffreyWay's avatar

Yeah, for example, here is a snippet from the repository for this very forum.

/**
 * @param $slug
 * @return mixed
 */
public function getConversationsByChannelSlug($slug)
{
    return Channel::whereSlug($slug)
        ->firstOrFail()
        ->conversations()
        ->latest('updated_at')
        ->paginate(25);
}

You definitely don't want stuff like that in your controller or app service. Instead, throw it in a repository and give it a readable name.

10 likes
floristenhove's avatar

Makes a lot of sense and it's totally readable. Thanks for this example, Jeffrey. Helps a lot :-)

thepsion5's avatar

I really want to try working with the Data Mapper pattern but ugh, Doctrine. I really want a data mapper that isn't so verbose and config-heavy. And annotations are terrible.

1 like
marcofiset's avatar

@thepsion5 It's possible to configure your Doctrine entities with YAML files if you don't like annotations. I personnaly prefer YAML files as I don't like polluting my entity classes with database concerns.

Prullenbak's avatar

What @pasadinhas and @jeffreyway say here, makes perfect sense. Put more complex queries in a repo, to keep your code more readable and DRY-er However, where to draw the line? I don't think it makes that much sence to write a function to fetch an object by id, and then call $this->posts->getById($id) when you could easily do that with Post::find($id).

Yet the repo-thing is what I see jeffrey do here on laracasts every once in a while. Why, @jeffreyway? Is it just a matter of preference, habit, or is there a real benefit to this?

thepsion5's avatar

@Prullenbak I actually go one step further and usually have a newQuery() function in my repository. Why? Because sometimes I need to add a constraint to the query before anything else. Having a single place to create a new query helps with DRY.

JoshWilley's avatar

@Prullenbak One useful benefit of using the repository pattern that I've been doing is having an abstract repository class like this:

EloquentRepository.php

abstract class EloquentRepository implements RepositoryInterface {

    /**
     * @var Model
     */
    protected $model;

    /**
     * @param null $model
     */
    public function __construct($model = null)
    {
        $this->model = $model;
    }

    /**
     * Return all results of the given model from the database
     *
     * @param array $relationships
     * @param array $columns
     * @return mixed
     */
    public function all($relationships = [], $columns = ['*'])
    {
        $model = $this->model;
        $model = $this->relationships($model, $relationships);


        return $model->get($columns);
    }

    /**
     * Return paginated results of the given model from the database
     *
     * @param $perPage
     * @param string $orderBy
     * @param string $direction
     * @param array $relationships
     * @return mixed
     */
    public function paginate($perPage = 20, $orderBy = 'updated_at', $direction = 'desc', $relationships = [])
    {
        $model = $this->model;
        $model = $this->relationships($model, $relationships);

        return $this->orderBy($model, $orderBy, $direction)->paginate($perPage);
    }

    /**
     * Return a model by ID from the database. If relationships are provided,
     * eager load those relationships.
     *
     * @param $id
     * @param array $relationships
     * @return mixed
     */
    public function find($id, $relationships = [])
    {
        $model = $this->model;
        $model = $this->relationships($model, $relationships);
        $model = $model->findOrFail($id);

        return $model;
    }

    /**
     * Create a new Eloquent Query Builder instance
     * 
     * @return \Illuminate\Database\Eloquent\Builder|static
     */
    public function newQuery()
    {
        return $this->model->newQuery();
    }

    /**
     * Load a model's relationships
     *
     * @param $model
     * @param array $relationships
     * @return mixed
     */
    protected function relationships($model, $relationships = [])
    {
        return $model->with($relationships);
    }

    /**
     * Call Eloquent's order by method on the current model
     *
     * @param $model
     * @param $column
     * @param $direction
     * @return mixed
     */
    protected function orderBy($model, $column, $direction)
    {
        return $model->orderBy($column, $direction);
    }
}

And then, you can do things like this:

UserRepository.php

class UserRepository extends EloquentRepository {

    /**
     * @param User $model
     */
    public function __construct(User $model)
    {
        $this->model = $model;
    }
}

Notice that the abstract class adheres to a contract that provides an API for your controllers, services, commands, etc to use.

Taking it a step further, if you decide to switch from Eloquent, you can create a new abstract repository class that implements that contract, and do whatever you need to do to return the same results using your new ORM or whatever it may be.

Obviously this isn't perfect and would still take quite a lot of work to switch from Eloquent, but it does provide another step towards abstraction and making things easier if you DO ever want to swap it out.

1 like
danharper's avatar

From what @thepsion5 said, I go with the third option.

Returning an Eloquent model means you're leaking that all over your application, and personally I'm not a huge fan of ActiveRecord (I'd strongly recommend always using Collection objects, though; and Illuminate\Support\Collection is perfect).

Returning an implementation of an entity Interface sounds appealing on the face of it, until you realise you have your core business logic locked up inside an swappable class. Any future implementation must implement that logic identically which is clearly bad.

You should map your Eloquent objects to an Entity object (your mapping your Data Model to your Domain Model).

Or you may choose to wrap your data model, which is something I've been toying with recently and I have no idea if it's an established pattern or not. Here, you may define a UserDataInterface or something which you implement by your Eloquent model (containing methods like getEmail, getUsername - basically only getters/setters). Your Domain model (your User) contains all your real logic and depends on this UserDataInterface as its data source.

Prullenbak's avatar

@joshwilley and @thepsion5 Yeah, I get most of that. A bit too complex for me at the moment, but I get the idea.

But...as @jeffreyway puts it

The truth is that you will probably never switch it out.

And that's where my confusion starts :P If you want to make your app not depend on eloquent, then why build a repo that returns eloquent objects? Well, because then you have a place to put your more complex queries, right?

So, for me, I'm fine with with returning Eloquent, while trying to be mindful of what I use in my views. From that perspective, the repository becomes more of a clean abstraction layer

Ok, great.

But my question: why also the most simple queries? If your app depends on eloquent already, then I don't see why Post::find($id) is worse then $this->postRepo->find($id), when the rest of your app depends on the exact same model.

Again, I'm probably wrong, but want to understand the benefits :)

pobble's avatar

God, I wish Stackoverflow was like this place

7 likes
JoshWilley's avatar

@Prullenbak I think once you've stripped away the idea that you might switch from Eloquent, you're pretty much left with testing. It's much easier and yields a lot more flexibility to test this:

class UserController extends \BaseController {

    public function __construct(PostRepository $postRepository) 
    {
        $this->postRepository = $postRepository;
    }

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

Than this:

class UserController extends \BaseController {

    public function index()
    {
        return Post::all();
    }
}

However, like @JeffreyWay says, it's all about what's best for your application. All these techniques and patterns that we have are just tools. Sometimes the advanced patterns are overkill for a certain project, and that's perfectly fine.

1 like
xingfucoder's avatar

Hi,

When I use the repositories, I create a repository for each Entity of my Domain or each Aggregate Entity Root. The implementation goes inside the Persistence layer, and the interfaces inside the Domain layer.

The repository could have an abstract class for generic functionality of the repository, independently of the persistence method. Later you may create specific implementations of each persistence method and each entity, and using interfaces and Dependency Injection may inject the entities into the repository and the repository into the Controllers.

Hope it helps you, although the answers of the other users are very useful.

Prullenbak's avatar

@joshwilley Ok. And that's more testable, even when the all() method on the postRepository does nothing more then return the same Post::all() ?

God, I wish Stackoverflow was like this place

Jup. Learning every day here. Even when there's no new video out :)

thepsion5's avatar

And that's more testable, even when the all() method on the postRepository does nothing more then return the same Post::all() ?

Yes, because right now all your postRepository does is return Post::all(), but there's no guarantee you won't want more complex functionality in the future. For example, if you wanted to order, filter, or paginate the posts being returned. You help with DRY by containing that logic in a repository instead of repeating it anywhere you're getting multiple posts.

2 likes
JoshWilley's avatar

To add to what @thepsion5 said above, I try to avoid calling Post::all(), even in my repository. What I usually do is inject the model through the repository's constructor, so when testing, you can swap out that dependency, mock it, whatever you like.

2 likes
adamwathan's avatar

@thepsion5's first response is totally bang on.

The thing that people still haven't really mentioned is adding related models. If you are trying to return ORM-agnostic objects from your repositories, you can no longer do $user->posts()->save($post). You are basically giving up half of the features that Eloquent provides for you.

Eloquent already provides abstraction over which relational DB you are using and one config file lets you easily switch between MySQL, SQL Server, Postgres or SQLite. If you are concerned that you might switch to a completely different type of data store (MongoDB, file based, etc.) then you are going to have a ton of work to do no matter what. There is no way to easily insulate yourself against a change of that scale. Sure you can write as many interfaces as you like, but those interfaces are going to be incredibly high level and switching to a file-based storage solution is going to involve rolling your own completely custom relational system, basically rebuilding a brand new type of ORM from scratch.

I still don't have a great answer for what we should be doing in cases like this. The tradeoff is just so huge. I like the idea of hiding certain things behind a repository just so it can be injected for the sake of simplifying testing, but if you really want to take advantage of the features Eloquent provides, you are going to be leaking stuff somewhere, without question.

Situations like this make me really envious of being able to stub class methods like you can in Ruby. I would much rather give my query methods the same good names we would use in a repository but just stick them on the class itself and stub them out in my tests.

11 likes
Next

Please or to participate in this conversation.