Laravel Tip: Write more controllers
While listening to FullStack Radio, I heard Taylor Otwell give a great piece of advice about how to name controllers and their respective methods. I thought it deserved its own write up as it's proven seriously useful in keeping my controllers readable.
When working in a team, you should not be able to tell who’s written a piece of code. Every developer codes to the same guidelines and coding style.
While this goal is often hard to reach in reality, we can take steps to get closer to fulfilling it. Some common techniques include: linting your code or following a documented coding style (like PSR2). However, due to everybody having different vocabularies, naming is the hardest thing to keep consistent within a team. Creating conventions for common solutions is a great way to improve this situation.
Here, I’ll be talking about how you should name your controllers and their respective methods. Doing so will keep your controllers smaller, easier to understand and consistent within the team. I first heard about this from Taylor Otwell, who was inspired by a great talk by David Heinemeier.
The convention consists of 2 key concepts:
- Write more controllers
- Only use 7 REST verbs in your controllers
Write more controllers
People seem to be adverse to having too many controllers, and tend to favour a fewer number of controllers with more methods on. For example, you could have the following:
A BasketController
with the methods:
createBasket()
getBasket()
addProduct()
updateProduct()
removeProduct()
The problem here is that this violates the Single Responsibility Principle. Your controller cares about 2 things: the basket itself and products in the basket.
The simple solution here is to have 2 controllers:
A BasketController
with the methods:
createBasket()
getBasket()
A BasketProductController
with the methods:
addProduct()
updateProduct()
removeProduct()
Now each controller only has one responsibility and as a result are much smaller and more focused. But how do you know when to apply this? That leads me onto the second concept…
Only use the 7 REST verbs in your controllers
These are: index
, create
, store
, show
, edit
, update
, destroy
.
Laravel has some great documentation on when each action should be used depending on the url and verb:
Using these, we can refactor our original controller:
Our BasketController
:
createBasket()
becomescreate()
getBasket()
becomesshow()
addProduct()
cannot use one of those verbsupdateProduct()
cannot use one of those verbsremoveProduct()
cannot use one of those verbs
Here you can see that it’s impossible to name any of the product related methods using only the 7 verbs – as they’ll clash with the basket. The solution? You guessed it, create a new controller:
Add a BasketProductController
and change the methods to:
addProduct()
becomesstore()
updateProduct()
becomesupdate()
removeProduct()
becomesdestroy()
Now we have 2 controllers, where all the methods exclusively use one of the seven REST verbs. We now have something that looks like this:
A BasketController
with the methods:
create()
show()
A BasketProductController
with the methods:
store()
update()
destroy()
What happens if something doesn’t fit into this model?
It happens. Sometimes you are doing something that doesn’t follow the standard CRUD model. I often run into this when adding RPC style endpoints alongside a REST api.
For example, I will need to convert this basket to an order. So I allow the following URL:
POST /api/baskets/1/actions/convert-to-order
There are several solutions we could apply here. Pick one or come up with our own, but stick with one convention.
Create a BasketOrderController
with a convert
method on.
Personally I don’t like this method as it’s often difficult to name the new controller. And here, is it the order or the basket that is being converted? It’s difficult to tell by the name alone.
Add a convertToOrder
method on the BasketController
This is fine. A small number of additional methods on the controller for non-CRUD operations is a good compromise. But sometimes it can still get a bit out of hand, especially as you’ll likely have some helper methods alongside it, bloating your controller.
Stick related things together together: e.g. BasketActionController
This is what I am currently doing. It keeps my CRUD controllers clean and simple and I can keep the potential mess contained to one place. If you’re dealing with reports for a basket, you could create a BasketReportController
. It doesn’t always fit, but it helps keep my other controllers clean.
Wrapping up
Whether you agree or disagree with points in this post, the most important thing is that you work out what you like and stick with it. The project I’m working on at the moment has 38 controllers, each with an average of 4-5 methods on. Out of those, I have 7 of these <thing>ActionController
s. It feels much cleaner and easier to navigate and I’d highly recommend it.
Bonus tip: Always use singular names for controllers. BasketController
not BasketsController
. BasketProductController
not BasketsProductsController
or BasketProductsController
. It gets real confusing knowing when to apply the plural or not and leads to inconsistencies.