Benjamin Franklin once said —
A place for everything, everything in its place.
This applies to software development as well. Understanding which portion of the code goes where is the key to a maintainable code base.
Don’t get me wrong. It’s a no brainer that controllers go inside the
controllers directory, no confusions whatsoever. The thing people often confuse themselves with is, what to write in a controller and what not to.
Table of Content
- Project Codes
- The Scenario
- Understanding Business Logic
- Service Classes to the Rescue
- Action Re-usability
- Closing Thoughts
You can find an implementation of the service discussed in this article in the following repository:
This is a simple real-time shopping cart implementation powered by Laravel, Livewire and TailwindCSS projects. This…
Take the following piece of code for example:
This is a controller from an imaginary e-commerce project, responsible for managing the shopping cart. Although this is a perfectly valid piece of code, there are some problems.
The controller also knows too much. Look at the
index method for example. It doesn't need to know whether the cart content comes from the session or database. Neither does it need to know how to calculate the total price of the cart items.
A controller should be only responsible for transporting requests and responses. The inner details aka the business logic should be handled by other classes in the server.
Understanding Business Logic
As explained in this thread:
Business logic or domain logic is that part of the program which encodes the real-world business rules that determine how data can be created, stored, and changed. It prescribes how business objects interact with one another, and enforces the routes and the methods by which business objects are accessed and updated.
In case of a simple shopping cart system, the business logic behind adding an item to cart can be described as follows:
- Take necessary product information (id, name, price, quantity) as input.
- Validate the input data.
- Form a new cart item.
- Check if the item already exists in the cart.
- If yes, update it’s quantity and if no, add the newly formed item to cart.
Now lets have a look at the
As you can see, the business logic translates to code pretty accurately. Now the problem is, controllers are not meant to contain business logic.
Service Classes to the Rescue
According to the very popular alexeymezenin/laravel-best-practices repository:
The idea of service classes is not something built into the framework or documented in the official docs. As a result, different people refer to them differently. At the end of the day, service classes are plain classes responsible for holding the business logic.
A service class for holding the shopping cart related business logic can be as follows:
As I’ve already said, service classes are not something built into the framework hence there is no
artisan make command for creating a service class. You keep the classes anywhere you like. I'm keeping my classes inside
CartService.php file contains both
protected methods. The public methods named
clear() are responsible for adding item to cart, removing item from cart, updating cart item quantity and clearing the cart respectively.
The other public methods are
total() responsible for returning the cart content and total price of added items respectively.
Finally the protected methods
createCartItem() are responsible for returning cart content within the class methods and forming a new cart item from the received parameters.
Now that the service class is ready, you need to use it inside the controller. To utilize the service class inside the
CartItemController.php file, the code needs to be updated as follows:
Thanks to zero configuration resolution, the service container resolves any class that doesn’t depend on any interface automatically. Hence simply injecting the
CartService in the controller constructor does the trick:
Now an instance of the
CartService class becomes available within the controller and can be accessed as
$this->cartService property. Rest of the controller action have been updated to make use of the service and as you can see, the controller has become much cleaner now.
Apart from making the controller cleaner, you also get the benefit of the shopping cart related actions being accessible from anywhere. Consider the following livewire component for example:
You can add, remove, update or clear the shopping cart from anywhere. Prior to the service class implementation, the only way to manage the cart was through HTTP requests. Now you can even manage the cart through
The concept of service classes discussed in this article is nothing concrete and I’m not claiming it to be a silver bullet. It’s something that I’ve used in the past and have had no problem whatsoever. As long as you’re thoughtful about using these classes and not overdoing it, you should be good to go.