25

To avoid code duplication in Laravel, I want to have a method that is used by multiple Controllers, it inserts some rows in database and also updates some data in another table.

I thought of using Repository, but I read somewhere that Repository is better used for retrieving data and shouldn't be used for inserting.

So I'm going to use Traits now. but I'm a bit confused...

Could some one please explain in a simple way what is the best usage for each of these (Repository/Service/Trait) and how are they different?

J. Doe
  • 812
  • 1
  • 15
  • 33

1 Answers1

83

Traits

Are an alternative approach to inheritance that solves some limitations of single class inheritance, which PHP uses. Since all Eloquent models extends Model it can not extend another class. This is commonly used to share similar logic across objects. Let's imagine a couple of models have a Company relationship.

trait HasCompany {
   public function company() {
       return $this->belongsTo(Company::class);
   }
}

Now the user can easily share code from the trait, by the keyword using. This is an example and most often a more complex use case would be needed for it to make sense.

class User extends Model {
   use HasCompany;
}

Repositories

Repositories is a design pattern to abstract data layers from the application. Your logic should not care about how you store data, so if you wanted to change from Mysql to Mongodb, you would only swap out the repository and not have to change business logic.

Very opinionated here, but this is not a fitting design pattern for Laravel. Laravel has Eloquent and the database layer is therefor already abstracted. Repositories is sometimes used for Laravel applications, but rather an outlier, than a common sight. One of the main reasons for repositories, is to be data implementation agnostic, which already exists and you can swap between SQL servers flawlessly. Also Eloquents features like ::find(), scopes etc. feels like a replacement for Repositories and is quirky to use at the same time.

If you use Doctrine as the ORM, which you can in Laravel, it is the core of their architecture and should be used.

It sometimes occur that the repository pattern is used to encapsulate business logic, with a similar approach to a service approach or action pattern.

Services

Is commonly used for a place to store business logic or the building blocks of your actions in your application. In traditional MVC design, Controllers should only handle input. Normally you would put your logic in Models, but they get "fat" very quickly, when this happens services is a common place to put business logic. Sometimes also named actions or commands, which is similar but a little bit different approaches.

One of the core things it solves, is to make your business logic reusable. Imaging filtering all users by an active flag, when you retrieve it in its controller.

public function all() {
    return User::where('active', true)->get();
}

Now you have your business logic, that enforces that you only work on active users, later you want to notify all active users with an email, by notifications using a command.

class NotifyUsers extends Command {
    public function handle() {
        foreach (User::where('active', true)->get() as $user) {
            $user->notify();
        }
    }
}

Now you manually have to keep business logic up to date. Next time you add a second condition or change the logic, you have to change the code in two places. In a big application where this code block is used often, it can make it quite hard to maintain the conditions without forgetting one of the places. If you make a service with this logic, you can easily utilize the same business logic across the application. While one have one place to change the code, if this logic had to change.

class UserService {
    public function all() {
        return User::where('active', true)->get();
    }
}

Everywhere you want to use this business logic getting active users, you can use the service. Therefor only having one place to maintain the logic. A call can be as simply as resolve(UserService::class)->all(). Example of the updated logic with services would be.

// controller
public function all(UserService $userService) {
    return $userService->all();
}

// command
class NotifyUsers extends Command {
    public function handle(UserService $userService) {
        $userService->all()->each->notify();
    }
}

Conclusion

The world is not black and white, you have to figure out your own approach. My advice is, do not spend time on Repositories, Laravel has a lot of features to handle data related operations scopes, getters setters etc. that conflicts with the Repository design pattern. See if a service like design approach suits you and you can utilize em. Traits is not as much an architectural design pattern, as it is a Class inheritance alternative, simply to share logic between classes.

mrhn
  • 17,961
  • 4
  • 27
  • 46
  • 13
    That was perfect, more like an article than an answer actually, thank you very much! – J. Doe Feb 03 '20 at 01:14
  • 2
    Glad you liked it, this is a very fluffy subject very opinion and religion matters more than facts :) – mrhn Feb 03 '20 at 01:21
  • Would service layer be preferable for pagination and its logic ? – Shulz Nov 03 '21 at 10:40
  • Hmm... In general i don't see the correlation with pagination and these design patterns. As these are already made in Laravel on the query builder, i usually just utilize those and transform the query builder meta information on the response. – mrhn Nov 03 '21 at 11:57
  • This answer cleared my ambiguity. Thanks @mrhn! – eldorjon Mar 14 '22 at 12:28
  • 1
    I will kindly add to the discussion on Repositories vs Eloquent models. Repositories in Laravel come very handy in large projects with: **1)** Abstracting complex caching mechanisms. **2)** Composing complex and dynamic datasets based on authenticated user, environment, data state etc. **3)** Abstracting transactions with multi-table complex data inserting/updating logic. – Umur Karagöz Apr 27 '22 at 20:33
  • I agree, but i think that services in this context is more about creating a repository like design pattern, that blends business logic and repository logic together. Instead of having it more traditional separated between business logic and repositories. These services would solve transactions and caching. Even thou i would say i worked on some large projects, i still have not been at a magnitude, where a service didn't solve these problems :) – mrhn Apr 27 '22 at 21:38