The Death of Models???
In the late 2010s, many frameworks offer the super Model class that allows you to pass all the logic from the controller without asking you to do anything at the model, except for some easy works like... telling the superclass what is the table name, or the PK field name.
But is that a good idea?
Cool. That's a good question.
Despite adding some logic to the models, we still need to pass the parameters from the controller anyway, because controllers handle all input and generate the output for the views. Hence, for the simple features like sorting and filtering, we can just simply send the parameter from the controller to the model class, right away. Offering the superclass that includes these highly-frequently used is very useful and made programming much easier.
Here's the bad news.
We don't always have only simple logic. Sometimes the logic is very complicated and frequently used everywhere. Sometimes it requires another table(s) hence it requires the JOIN with the keys to match. And although we are working on simple logic, for so many times we need to filter, sort, and paginate the data, hence we need to fill in the following SQL:
WHERE ??? = ??? ORDER BY ??? LIMIT ???, ???. I understand that it could be easier to pass all these parameters from the controller, but imagine the following conditions:
- We are filtering from multiple columns (
$model->where_like('customer_name', ?)->or_where_like('customer_email', ?)) and one day, we add the phone in the list of searchable fields, and this search appears in multiple controllers. Hence we need to add
->or_where_like('customer_tel', ?)to every single controller. That's a very tough job. And if the programmer forgets to change in one controller, then the search result will not be consistent already. Using the inconsistent system could be troublesome for the users.
- We have multiple tables that require sorted, paginated, searchable data, so we always need the following structure:
$model->where_like(?)->or_where_like(?)->or_where_like(?)->order_by(?, asc|desc)->limit(?, ?). Isn't it better if we have a method in the model for this structure so we can call this method in the controller with all the parameters, and let the model takes all of them into the right format for the query:
$model->get_paginated_data(offset, limit, [where], [order_by]). The list of columns to be searched for will appear only in the model, and the controller can be cleaner.
So, to sum up, I still prefer to write long logic in the model, not the controller, for 2 simple reasons: (1) keep the controller clean of the complex model logic, and (2) for consistency of the data retrieval.
Hence, the model cannot die.
Some people argue that by putting the logic in the model would make it very hard to maintain. Due to the fact that people create the function for each and every kind of query, hence the name could be meaningless, not useful, and hence reduce the reusability anyway, so it could be better to place the logic in the controller right away.
The way to fix the above problem is to create a little bit more flexible functions that would be reusable, the easiest way is just to take more parameters and use the if/else blocks to determine how to generate the query for this particular use case. And also, we have to index them well by writing and maintaining good documentation.
If you say that by having a very fat model would make it harder to maintain, then how could you describe having a very fat controller is very easy to maintain? Writing logic in the controller would make it even longer because of the lack of reusability, hence fatter, and harder to maintain in my personal point of view.
To limit the logic in the model is to reduce the number of reusable functions. And when the number of reusable functions is low (because the logic is written purely in the controller), we will eventually forget to do the regression testing. We will forget that, for shared functions, we have to search throughout the system and make sure the new change will never cause unwanted side effects, just because we don't encounter shared functions a lot in this controller-heavy way of coding.
Some people could argue which kind of logic should be put in the model, I would say "data" only. We can use the model to retrieve the data from the database. For the singular data, we can use the model as the entity class, too, so we can work in the popular object-oriented way.
Anyway, recently I heard someone said that the model is the class of shared, reusable functions that can be used anywhere in the system, and the features like upload and email, which requires the use of the database, should be placed in the model, too. I would never agree with this method. Instinctively, people would go for the helper or library for that kind of feature, and we just call the model from the helper or library. By placing everything in the model would obscure the reason for the existence of helpers and libraries.
Moreover, the public libraries are also available for download, and most people would use composer or npm to manage external libraries that anyone could just use to speedup the environment. So it could be easy to just create a simple helper to call the public library so we can add our own specific configurations. For example, we can create the email helper to call the external email library, with the organization’s header and footer already stored in the helper so it can be shared across every function that needs to send emails.