133

While creating an app in Laravel 4 after reading T. Otwell's book on good design patterns in Laravel I found myself creating repositories for every table on the application.

I ended up with the following table structure:

  • Students: id, name
  • Courses: id, name, teacher_id
  • Teachers: id, name
  • Assignments: id, name, course_id
  • Scores (acts as a pivot between students and assignments): student_id, assignment_id, scores

I have repository classes with find, create, update and delete methods for all of these tables. Each repository has an Eloquent model which interacts with the database. Relationships are defined in the model per Laravel's documentation: http://laravel.com/docs/eloquent#relationships.

When creating a new course, all I do is calling the create method on the Course Repository. That course has assignments, so when creating one, I also want to create an entry in the score's table for each student in the course. I do this through the Assignment Repository. This implies the assignment repository communicates with two Eloquent models, with the Assignment and Student model.

My question is: as this app will probably grow in size and more relationships will be introduced, is it good practice to communicate with different Eloquent models in repositories or should this be done using other repositories instead (I mean calling other repositories from the Assignment repository) or should it be done in the Eloquent models all together?

Also, is it good practice to use the scores table as a pivot between assignments and students or should it be done somewhere else?

Antonio Carlos Ribeiro
  • 86,191
  • 22
  • 213
  • 204
ehp
  • 1,689
  • 3
  • 14
  • 20

4 Answers4

278

I am finishing up a large project using Laravel 4 and had to answer all of the questions you are asking right now. After reading all of the available Laravel books over at Leanpub, and tons of Googling, I came up with the following structure.

  1. One Eloquent Model class per datable table
  2. One Repository class per Eloquent Model
  3. A Service class that may communicate between multiple Repository classes.

So let's say I'm building a movie database. I would have at least the following following Eloquent Model classes:

  • Movie
  • Studio
  • Director
  • Actor
  • Review

A repository class would encapsulate each Eloquent Model class and be responsible for CRUD operations on the database. The repository classes might look like this:

  • MovieRepository
  • StudioRepository
  • DirectorRepository
  • ActorRepository
  • ReviewRepository

Each repository class would extend a BaseRepository class which implements the following interface:

interface BaseRepositoryInterface
{
    public function errors();

    public function all(array $related = null);

    public function get($id, array $related = null);

    public function getWhere($column, $value, array $related = null);

    public function getRecent($limit, array $related = null);

    public function create(array $data);

    public function update(array $data);

    public function delete($id);

    public function deleteWhere($column, $value);
}

A Service class is used to glue multiple repositories together and contains the real "business logic" of the application. Controllers only communicate with Service classes for Create, Update and Delete actions.

So when I want to create a new Movie record in the database, my MovieController class might have the following methods:

public function __construct(MovieRepositoryInterface $movieRepository, MovieServiceInterface $movieService)
{
    $this->movieRepository = $movieRepository;
    $this->movieService = $movieService;
}

public function postCreate()
{
    if( ! $this->movieService->create(Input::all()))
    {
        return Redirect::back()->withErrors($this->movieService->errors())->withInput();
    }

    // New movie was saved successfully. Do whatever you need to do here.
}

It's up to you to determine how you POST data to your controllers, but let's say the data returned by Input::all() in the postCreate() method looks something like this:

$data = array(
    'movie' => array(
        'title'    => 'Iron Eagle',
        'year'     => '1986',
        'synopsis' => 'When Doug\'s father, an Air Force Pilot, is shot down by MiGs belonging to a radical Middle Eastern state, no one seems able to get him out. Doug finds Chappy, an Air Force Colonel who is intrigued by the idea of sending in two fighters piloted by himself and Doug to rescue Doug\'s father after bombing the MiG base.'
    ),
    'actors' => array(
        0 => 'Louis Gossett Jr.',
        1 => 'Jason Gedrick',
        2 => 'Larry B. Scott'
    ),
    'director' => 'Sidney J. Furie',
    'studio' => 'TriStar Pictures'
)

Since the MovieRepository shouldn't know how to create Actor, Director or Studio records in the database, we'll use our MovieService class, which might look something like this:

public function __construct(MovieRepositoryInterface $movieRepository, ActorRepositoryInterface $actorRepository, DirectorRepositoryInterface $directorRepository, StudioRepositoryInterface $studioRepository)
{
    $this->movieRepository = $movieRepository;
    $this->actorRepository = $actorRepository;
    $this->directorRepository = $directorRepository;
    $this->studioRepository = $studioRepository;
}

public function create(array $input)
{
    $movieData    = $input['movie'];
    $actorsData   = $input['actors'];
    $directorData = $input['director'];
    $studioData   = $input['studio'];

    // In a more complete example you would probably want to implement database transactions and perform input validation using the Laravel Validator class here.

    // Create the new movie record
    $movie = $this->movieRepository->create($movieData);

    // Create the new actor records and associate them with the movie record
    foreach($actors as $actor)
    {
        $actorModel = $this->actorRepository->create($actor);
        $movie->actors()->save($actorModel);
    }

    // Create the director record and associate it with the movie record
    $director = $this->directorRepository->create($directorData);
    $director->movies()->associate($movie);

    // Create the studio record and associate it with the movie record
    $studio = $this->studioRepository->create($studioData);
    $studio->movies()->associate($movie);

    // Assume everything worked. In the real world you'll need to implement checks.
    return true;
}

So what we're left with is a nice, sensible separation of concerns. Repositories are only aware of the Eloquent model they insert and retrieve from the database. Controllers don't care about repositories, they just hand off the data they collect from the user and pass it to the appropriate service. The service doesn't care how the data it receives is saved to the database, it just hands off the relevant data it was given by the controller to the appropriate repositories.

Jeff
  • 6,643
  • 3
  • 25
  • 35
Kyle Noland
  • 4,788
  • 6
  • 30
  • 33
  • 11
    This comment is by far the cleaner, more scalable and maintainable approach. – Andreas Feb 26 '14 at 07:23
  • 4
    +1! That will help me a lot, thank you for sharing with us! Wondering how you managed to validate things inside services, if possible, could you briefly explain what you did? Thank you anyway! :) – Paulo Freitas Mar 24 '14 at 00:18
  • 6
    Like @PauloFreitas said, it'd be interesting to see how you handle the validation part, and I'd be interested in the exceptions part as well (do you use exception, events or you just handle this as you seem to suggest in your controller via a boolean return in your services?). Thanks! – Nicolas Apr 04 '14 at 13:01
  • 1
    Great explanation and code examples of how to approach such a situation! I have been working on a relatively large application and began with a similar setup. I wish I could have kept it as clean as intended. It turned out that taking on too many new concepts and building a quickly growing application which depends on them at the same time was a bit too much to bite off. Needless to say I've fallen back to an Eloquent-heavy codebase, learned a lot, and grown a ton as a developer during the process. I'll be sure to bookmark this for my next shot at implementing a repository pattern. – Erik Aybar Jun 24 '14 at 22:57
  • 13
    Good write up, although I'm not sure why you're injecting movieRepository into MovieController as the controller shouldn't be doing anything directly with the repository, nor is your postCreate method using the movieRepository, so I assume you left it in by mistake? – davidnknight Sep 07 '14 at 14:28
  • 4
    Great, but shouldn't Repository returns eloquent model dependent results (i.e.: array)? Then why in the MovieService, there is some code operates the eloquent models directly ($director->movies()->associate($movie);). – Mengdi Gao Oct 29 '14 at 02:03
  • Can you give a little info how you are using $related parameter? – gandra404 Nov 08 '14 at 17:16
  • 25
    Question about this: why are you using repositories in this example? This is an honest question - to me, it looks like you're using repositories but at least in this example the repository isn't really doing anything but providing the same interface as Eloquent, and in the end you are still tied to Eloquent because your service class is using eloquent directly in it (```$studio->movies()->associate($movie);```). – Kevin Mitchell Nov 24 '14 at 07:33
  • Well explained and really clean! I have some large Laravel apps and they all follow a similar structure. However, I have different folder and namespace structure for all of them. I don't think any of them is wrong, as there are different approaches, but I still think "what would be the best way to organize it?". So I'm wondering if you could share your folder/namespace structure just to compare :) – Jesús Jun 12 '15 at 10:16
  • 1
    Why not do service oriented repos instead of model oriented repos that need yet another abstraction layer (service classes). Say, you have user content (blog posts, messages, comments etc.). You create ContentUserRepo and deal with posts, messages, comments right there using different models. You will have to combine models directly, or indirectly at some point to get the job done anyway. Be it in repo, or service class. Service oriented repos allow to drop one abstraction layer - service classes. But it is all down to project and preference ... I guess. – Jeffz Jun 19 '17 at 14:16
  • 1
    I don't like to create such a big interfaces because it is against SOLID, it is better to create separate interfaces with more specific task. – Miljan Rakita Aug 26 '19 at 01:38
  • Oh what a difference a few years make. The new hotness now is Domain Driven Design. So that pattern would be the top answer for this now. The top answer here has already separated out the Domains. – fred Jun 28 '20 at 23:56
  • 1
    @KevinMitchell I agree either dont use repositories - or do all DB stuff in the repositories. In this case, it should perhaps do something like `$actorModel = $this->actorRepository->create($movie, $actor);` and remove `$movie->actors()->save($actorModel);` from the service class. – Adam Oct 12 '20 at 09:34
  • @fred I'd say that this pattern incorporates domain driven design with the service layer – Jonathan P Apr 18 '22 at 05:37
74

Keep in mind you're asking for opinions :D

Here's mine:

TL;DR: Yes, that's fine.

You're doing fine!

I do exactly what you are doing often and find it works great.

I often, however, organize repositories around business logic instead of having a repo-per-table. This is useful as it's a point of view centered around how your application should solve your "business problem".

A Course is a "entity", with attributes (title, id, etc) and even other entities (Assignments, which have their own attributes and possibly entities).

Your "Course" repository should be able to return a Course and the Courses' attributes/Assignments (including Assignment).

You can accomplish that with Eloquent, luckily.

(I often end up with a repository per table, but some repositories are used much more than others, and so have many more methods. Your "courses" repository may be much more full-featured than your Assignments repository, for instance, if your application centers more around Courses and less about a Courses' collection of Assignments).

The tricky part

I often use repositories inside of my repositories in order to do some database actions.

Any repository which implements Eloquent in order to handle data will likely return Eloquent models. In that light, it's fine if your Course model uses built-in relationships in order to retrieve or save Assignments (or any other use case). Our "implementation" is built around Eloquent.

From a practical point of view, this makes sense. We're unlikely to change data sources to something Eloquent can't handle (to a non-sql data source).

ORMS

The trickiest part of this setup, for me at least, is determing if Eloquent is actually helping or harming us. ORMs are a tricky subject, because while they help us greatly from a practical point of view, they also couple your "business logic entities" code with the code doing the data retrieval.

This sort of muddles up whether your repository's responsibility is actually for handling data or handling the retrieval / update of entities (business domain entities).

Furthermore, they act as the very objects you pass to your views. If you later have to get away from using Eloquent models in a repository, you'll need to make sure the variables passed to your views behave in the same way or have the same methods available, otherwise changing your data sources will roll into changing your views, and you've (partially) lost the purpose of abstracting your logic out to repositories in the first place - the maintainability of your project goes down as.

Anyway, these are somewhat incomplete thoughts. They are, as stated, merely my opinion, which happens to be the result of reading Domain Driven Design and watching videos like "uncle bob's" keynote at Ruby Midwest within the last year.

fideloper
  • 12,213
  • 1
  • 41
  • 38
  • 1
    In your opinion, would it be a good alternative if repositories return data transfer objects instead of eloquent objects? Of course this would imply an extra conversion from eloquent to dto's but this way, at least, you isolate your controllers/views from the current orm implementation. – federivo Sep 16 '13 at 14:14
  • 1
    I've [experimented with that myself](https://github.com/fideloper/Laravel-Architecture/tree/develop/app/Arch) a bit and found it a little on the impractical side. That being said, I do like that idea in the abstract. However, Illuminate's database Collection objects act just like arrays and Model objects act just like StdClass objects enough so that we can, practically speaking , stick with Eloquent and still use arrays/objects in the future should we need to. – fideloper Sep 16 '13 at 15:08
  • 4
    @fideloper I am feeling that if I use repositories I lose the whole beauty of ORM that Eloquent provides. When retrieving an account object via my repository method `$a = $this->account->getById(1)` i cannot simply chain methods like `$a->getActiveUsers()`. Okay, I could use `$a->users->...`, but then I am returning an Eloquent collection and no stdClass object and am tied to Eloquent again. What's the solution to this? Declaring another method in the user repository like `$user->getActiveUsersByAccount($a->id);`? Would love to hear how you solve this... – santacruz Jan 16 '14 at 21:00
  • 1
    ORMs are terrible for Enterprise(ish)-level architecture because they cause issues like this. **In the end, you have to decide what makes the most sense for your application.** Personally when using repositories with Eloquent (90% of the time!) I use Eloquent and try my hardest to treat models & collections like stdClasses & Arrays (because you can!) so if I need to, switching to something else is possible. – fideloper Jan 17 '14 at 00:14
  • Thanks for your input @fideloper! I am trying my best to adhere to this not making use of lazy loaded Eloquent models outside of my Eloquent implementation of the repository. I do have a really hard time though figuring out how to deal with repositories in combination with state. What if a user can be related to multiple accounts and has different roles depending on which account he logs into? What's the best way to deal with that? – santacruz Jan 17 '14 at 14:40
  • 6
    Go ahead and use lazy-loaded models. You can make real domain models work like that if you ever skip out on using Eloquent. But seriously, are you *gonna* switch out Eloquent ever? In for a penny, in for a pound! (Don't go overboard trying to stick to "the rules"! I break all of mine all the time). – fideloper Jan 17 '14 at 22:17
  • 1
    Having developed quite a few websites using Laravel I've been thinking about this quite a bit. I have actually decided that the repository pattern is an "antipattern" if you're using it with an active record ORM such as Eloquent. Where I think the repository pattern makes sense is in entity mapping frameworks such as Doctrine. I'm thinking of writing a post about it - because I feel like Taylors recommendation of the repository pattern is actually counter productive in the context of Laravel. –  Aug 19 '14 at 21:53
  • I agree. But after having the same mindset, I eventually decided that repositories can still be very practical when used with a AR ORM as well. I end up compromising before I spin too far off and end up in DDD/CQRS/Event-Sourcing land! – fideloper Aug 19 '14 at 23:41
  • The problem with this approach is that your business layer becomes extremely intimate with your returned models. Ie. $course->assignments().etc. it knows a lot about your entities. If you have to swap out the repository for whatever reason (say you're moving to doctrine or a filesystem db), your code is screwed. It's far better to not have these things available, but instead, constantly request the repository for new data. For example: $courses = $courseRepository->getAll(); – Oddman Oct 21 '14 at 11:52
7

I like to think of it in terms of what my code is doing and what it is responsible for, rather than "right or wrong". This is how I break apart my responsibilities:

  • Controllers are the HTTP layer and route requests through to the underlying apis (aka, it controls the flow)
  • Models represent the database schema, and tell the application what the data looks like, what relationships it may have, as well as any global attributes that may be necessary (such as a name method for returning a concatenated first and last name)
  • Repositories represent the more complex queries and interactions with the models (I don't do any queries on model methods).
  • Search engines - classes that help me build complex search queries.

With this in mind, it makes sense every time to use a repository (whether you create interfaces.etc. is a whole other topic). I like this approach, because it means I know exactly where to go when I'm needing to do certain work.

I also tend to build a base repository, usually an abstract class which defines the main defaults - basically CRUD operations, and then each child can just extend and add methods as necessary, or overload the defaults. Injecting your model also helps this pattern to be quite robust.

Oddman
  • 3,715
  • 1
  • 16
  • 13
  • Can you show your implementation of your BaseRepository? I actually do this too and I'm curious what you did. – Odyssee Jan 03 '19 at 10:48
  • Think getById, getByName, getByTitle, save type methods.etc. - generally methods that apply to all repositories within various domains. – Oddman Jan 04 '19 at 11:04
5

Think of Repositories as a consistent filing cabinet of your data (not just your ORMs). The idea is that you want to grab data in a consistent simple to use API.

If you find yourself just doing Model::all(), Model::find(), Model::create() you probably won't benefit much from abstracting away a repository. On the other hand, if you want to do a bit more business logic to your queries or actions, you may want to create a repository to make an easier to use API for dealing with data.

I think you were asking if a repository would be the best way to deal with some of the more verbose syntax required to connect related models. Depending on the situation, there are a few things I may do:

  1. Hanging a new child model off of a parent model (one-one or one-many), I would add a method to the child repository something like createWithParent($attributes, $parentModelInstance) and this would just add the $parentModelInstance->id into the parent_id field of the attributes and call create.

  2. Attaching a many-many relationship, I actually create functions on the models so that I can run $instance->attachChild($childInstance). Note that this requires existing elements on both side.

  3. Creating related models in one run, I create something that I call a Gateway (it may be a bit off from Fowler's definitions). Way I can call $gateway->createParentAndChild($parentAttributes, $childAttributes) instead of a bunch of logic that may change or that would complicate the logic that I have in a controller or command.

Ryan Tablada
  • 440
  • 4
  • 11