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

cristian9509's avatar

Models, Repositories, Service classes & Jobs (commands)

I need some help in understanding what part each play in a large application. I'll start first with a scenario:

  • as usual there will be User
  • an user must have a MembershipType (free, basic, business).
  • an user can have (not required) a Business (User has one or zero Business)
  • an user can have a Subscription (User has one or zero Subscription).
  • a subscription requires a SubscriptionPlan which is related to the MembershipType. So a membership type of 'basic' would have in Stripe a 'basic2015' plan; a 'business' would have a 'business2015' plan.
  • an user has Address (User many to many Address)
  • a basic user must have a 'personal' address on file while a business user must have a 'business' address on file
  • there are other requirements but this will be sufficient to get the better picture

So far I have these Models: User, MembershipType, Business, Subscription, SubscriptionPlan and Address.

Some of the business logic requires me to have at least the following methods:

  • user isBusiness()
  • user hasAddress(type) where type is (personal or business)
  • membership type getPlanForCurrentYear()
  • other things required but I'll stop here

Now, an example: John registers and is required to provide basic information (email, password, name). He signs up as a business. After this basic registration process he get's Logged In and notified that he is required to complete their profile before getting access. He is required then to provide a business information (name, website, email, phone). Also I need the business address. He then has to subscribe to the current year plan 'business2015'.

All of this I have managed to do it, but I done it in my controllers. As some suggested in other posts, first make it work and then decide if you need to extract it to repositories. Well, I have got to the point where I am sure I need to use repositories because it get's more and more complicated and I have to constantly modify my controllers. So after lot of research I am still empty on how to make my DB to UI work both ways without a mess.

My research led me to the fact that I (~~need to use Repositories, maybe Service Classes and Jobs~~) might need to use something to keep my controllers cleaner; it's just too much stuff going on there. I guess should use something like Repositories, Service Classes combined with Jobs. Can someone share some thoughts on what I will need to have a good architecture?

  1. Will I need a Repository for each Model? Have an interface and abstract repository with general methods (all, getId, create, update, delete, etc). If so how would I relate repositories between themselves?

  2. Do I need service classes (like RegisterUser, CompleteProfile) that orchestrate all the repositories involved at certain time?

  3. How should I use Jobs in this scenario? Dispatch them from the controller and inject a Repository or a Service class?

  4. Is there an example somewhere of large applications in Laravel (open source) that I can look at a understand some of the principles involved?

Thank you.

0 likes
7 replies
MikeHopley's avatar

I don't consider myself an expert on this stuff, so do treat my advice with a large dose of salt...

Consider taking a more pragmatic approach. Do you really need repositories, or is it just "following best practice"? By going down the repository road, you create complexity and indirection in your code. It takes time to set up and learn how to do it "properly".

Ask yourself what benefit you will get out of using repositories. You should have a clear, practical reason -- not just "oh well, maybe one day I might want to swap out my Eloquent models for something else."

Repositories do have uses even in smaller applications where you depend on Eloquent. In particular, they can be useful places for storing the kind of query logic that you mentioned. But consider just stuffing it all in the model for now. Okay, so you have a few extra methods on your User model. Big deal.

Avoid turning your repositories into wrappers for Eloquent. You can either have the convenience of Eloquent, or the abstraction of an "implementation neutral" data access layer -- but you cannot have both. Many developers have wasted a great deal of effort trying to square this circle. If you use Eloquent, you must accept that your code depends on it.

Even if you use repositories, don't make one for every model "for consistency". Add them for models where they are actually useful.

There is nothing wrong with having to modify your controllers. This not a compelling reason for increasing abstraction. However, what if you find yourself wanting to use the same logic in two different places -- say in two controllers, or in a controller and an Artisan command? That is a good reason for abstracting the logic into another class, which could be a service class, or a command/job.

Normally a command/job would be dispatched from a controller. The controller says, "I have received valid user credentials for a new account. Now, UserRegisterCommand, kindly do my bidding and carry out the tedious work. I care not for such petty matters." *waves hand imperiously*

I'm not saying that abstraction is "bad". What I'm trying to say is that abstraction has a cost, and much of the time it's just not worth the hassle.

My rule of thumb is that I abstract stuff when I feel "pain", not because someone somewhere said that I shouldn't have application logic in my controllers or query methods on my models. "Code purity" is not the goal. The goal is to avoid making life hard for yourself.

5 likes
t0ne's avatar

That's what i would like to learn!

martinbean's avatar

@cristian9509 Personally, I’d just re-factor the blocks of code where you’re accessing more than one model to complete an operation into service classes. If you’re sticking with Eloquent, then a repository won’t really offer that much of an advantage, other than sucking up your time to create them.

2 likes
MikeHopley's avatar

Okay, so let's take a very simplified controller:

namespace App\Http\Controllers;

use App\Models\User;

class UserController extends Controller {
    
    // This is a POST request from your registration form, for example
    public function registerUser()
    {
        // This stuff really does belong in the controller, because it's HTTP
        $username = Input::get('username');
        $password = Input::get('password');

        // This stuff is not HTTP or "directing traffic", so you **might** choose to put it somewhere else.
        $user = new User;
        $user->username = username;
        $user->password = password;
        $user->save();
    }

}

This is simplistic and silly, because (e.g.) there's no validation (or password hashing!). But even here, you have code that maybe would be better off outside the controller. You could put it wherever you want. Let's use a basic service class:

namespace App\Services;

use App\Models\User;

class UserService {

    public function registerUser($username, $password)
    {
        $user = new User;
        $user->username = $username;
        $user->password = $password;
        $user->save();
    }

}

...and now your controller looks something like this:

namespace App\Http\Controllers;

use App\Services\UserService;

class UserController extends Controller {

    private $service;
    
    public function __construct(UserService $service)
    {
        $this->userService = $service
    }
    
    public function registerUser()
    {
        $this->userService->registerUser( Input::get('username'), Input::get('password') );
    }

}

As far as I know, there's nothing really "special" about service classes. They are just classes that do things for you. You could call them whatever you want.

Alternatively you might prefer using commands (which L5.1 calls "jobs"). Commands can defer handling to a separate handler class, and this is the "pure" way to do things. But you might prefer a simple self-handling command like this:

namespace App\Commands;

use App\Models\User;

class RegisterUser extends Command implements SelfHandling {

    public function __construct($username, $password)
    {
        $this->username = $username;
        $this->password = $password;
    }
    
    public function handle()
    {
        $user = new User;
        $user->username = $this->username;
        $user->password = $this->password;
        $user->save();
    }

}

...and then your controller would look something like this:

namespace App\Http\Controllers;

use App\Commands\RegisterUser;

class UserController extends Controller {
    
    public function registerUser()
    {
        $this->dispatch(new RegisterUser( Input::get('username'), Input::get('password') ));
    }

}

In this case, you're really just using Laravel's command bus as a service class "wrapper". But that's okay if you find it helpful!

Alternatively, you might decide that commands are only appropriate for things that could potentially be queued, or decorated, or whatever. You might decide to use simple service classes and only "promote" them to commands when needed. Again, it's up to you.

5 likes
cristian9509's avatar

Thanks @MikeHopley & @martinbean . You guys made me thinking on how to approach this issue. Basically, I thought about it and I don't really think that in the near future I would have to "switch" the implementation. That is why I will not put too much focus on Repositories or now. I just think I finally got the point on what a Repo is and what it should do. In my mind I thought that a Repo acts more as a Service Class. So yes I would definitely use Service Classes or Commands/Jobs since a lot of stuff has to be reused. And based on @MikeHopley 's answer I am trying to figure out the best option between the two.

Or, would it be "inconsistent" to use both options? Like, a Service Class for more complex things and then some commands for less complex needs.

And, one more question, would it make sense to combine the two options? Like a Service Class that may call some commands to do some of the stuff. (is this too much, am I asking my wife to ask my daughter to ask me to take the garbage out instead of just taking the garbage in the first place?)

martinbean's avatar

@cristian9509 Just go with what will clean up your controllers and make development easier and faster for yourself. If you’re not comfortable with repositories, don’t just blindly add them to your application as you may find later on you could have implemented them better, and then you’re stuck with them in your app. Same for commands.

You can always go back and re-factor. So if you get to a point where you’re comfortable with the command bus and you have something where you go, “Yeah, that would be better as a command actually” then you can go back and re-factor it as such.

3 likes
MikeHopley's avatar

I think Martin's point there is really important. Go with what will make development "easier and faster for yourself". That's you. Not some other guy; not the coder you feel you ought to be; not the developer you admire the most; not that girl who gave an amazing presentation at a conference. You, here and now, with the project in front of you.

And you can always change your mind later. In most cases, the cost of changing your mind is much lower than the cost of trying to be ultra-flexible at the start.

Or, would it be "inconsistent" to use both options? Like, a Service Class for more complex things and then some commands for less complex needs.

I wouldn't worry about "inconsistency" unless it starts to confuse you or annoy you.

And, one more question, would it make sense to combine the two options? Like a Service Class that may call some commands to do some of the stuff.

I don't see a problem with that. Commands can be dispatched from anywhere that makes sense to you.

Here's a similar pattern that's often used: a command fires an event. For example, after the RegisterUser command has done its work, it fires a UserWasRegistered event. Other parts of your application can listen for this event and take further action (such as a mailer sending a welcome email).

1 like

Please or to participate in this conversation.