When I started developing applications in CakePHP with the help of the one and only Cookbook, I was very happy and satisfied with my first ever Blog application. A Blog application in framework is analogous to the "Hello World" program for the novice programmers, as it helps to clarify the basic communication among the objects in MVC architecture
.
When I compared my Blog application with the MVC design pattern, I found that some controllers in my application ended up with hundreds of lines. The models were too skinny and just had the relationships with other models followed by some lines of code containing validations. Code in the fat controllers looked something like this:
-
/**
-
* Action to display the listing of posts
-
*/
-
public function index()
-
{
-
$authorId = $this->Auth->user('id');
-
//If author is logged in, display his posts
-
'contain' => FALSE,
-
'id',
-
'title',
-
'created',
-
),
-
));
-
//Else display posts from all the authors
-
} else {
-
'contain' => FALSE,
-
'id',
-
'title',
-
'created',
-
),
-
));
-
}
-
}//end index()
In the above code, you can see that the controller is overloaded with the excessive code. Considering the fact that the controller is responsible for handling and routing requests made by the client, it should be light in weight and agile in nature.
As far as the model is concerned, it is the one which has a direct access to the centralized database. Hence, all the enterprise logic or business logic must be written in the model. A model can be seen as an object with the help of which, you can write and call some generalized methods dealing with your database. It makes the task much simpler for the controller.
After following the "Fat Models and skinny controllers" approach in my application, I re-wrote and optimized the code in my controller as follows:
-
/**
-
* Action to display the listing of posts
-
*/
-
public function index()
-
{
-
$authorId = $this->Auth->user('id');
-
//If author is logged in, display his posts
-
$posts = $this->Post->getAuthorPosts($authorId);
-
//Else display posts from all the authors
-
} else {
-
$posts = $this->Post->getAllPosts();
-
}
-
}//end index()
Now, let us see how the model class will look alike:
-
class Post extends AppModel
-
{
-
var $name = 'Post';
-
-
var $belongsTo = 'User';
-
-
);
-
-
/**
-
* Action to display the listing of posts by the specific Author
-
*/
-
public function getAuthorPosts($authorId)
-
{
-
'contain' => FALSE,
-
'id',
-
'title',
-
'created',
-
),
-
));
-
}//end getAuthorPosts()
-
-
-
/**
-
* Action to display the listing of all the posts
-
*/
-
public function getAllPosts()
-
{
-
'contain' => FALSE,
-
'id',
-
'title',
-
'created',
-
),
-
));
-
}//end getAllPosts()
-
-
}//end class
The above code looks pretty self documented due to the proper action names and the comments. In this way, I have moved my controller actions to model to make the controller skinny and to make the model fatty. Also, note the find() called in the model without referring the model name i.e. $this->find(...) instead of $this->Post->find(...). You'll see this technique more advantageous when you'll need such actions repeatedly in your code as the generic actions.
I know that this concept is not new but what I see is very slow adoption of this technique. I feel developers should emphasize on creating model behaviors rather than creating controller components. Obviously if you are needing certain actions repeatedly in your controller then you should go ahead and create a component. The Amazon S3 Upload Behavior is a good example of a generalized behavior which can be used efficiently to store files using Amazon Simple Storage Service (S3).
So, beyond this, whenever you try to write a web application in CakePHP, take an oath to try hard to follow these concepts. Repeat these lines after me:
- Keep minimal logic in the controllers
- Keep all your business logic in the models (try to write generalized methods whenever possible)
- Call those generalized methods from controller by passing the required parameters
- Use proper and self explanatory method names and comments
- Models should not talk to the views directly and vice-versa
- It is absolutely fine if the view contains some PHP code which deals with the presentation logic
Your ideas, views or clarifications regarding this concept are most welcome
.
UPDATED:
- Added code for the model class and its description
perhaps you could post the model too, so we can see the implementation of find
Thanks for the suggestion fufu. I've added code for the model class and its description.
sometimes it is too easy to get carried away with skinny controllers/fat models. certain functions are only available inside controllers, and as I see, there is no way to use them in a model without re initializing a controller.
I think the best approach is to create logic in the models when it will be shared outside of a controller, but a simple find call like in your example seems like overkill for some projects.
you may spend more time abstracting functions into the model than actually getting work done.
The find was just an example here! If you are finding the need to call controller functions inside a model then you are obviously doing something wrong and need to re-examine your app
@jblotus It is obvious to keep the controller specific code or functions in the controller itself. With the help of find(), I've just tried to explain not to overload your controllers when these things or some generic functions can be written in the model to achieve re-usability and follow MVC best practices.
Aren't thus rules about keeping the model "fat" and the controlers "skinny" true in any case ?
I mean, it's the same even not using CakePHP... It's pretty much universal, isn't it ?
@JB Yes, keeping your models "fat" and controllers "skinny" is true and applicable for every MVC based application and it is always a good practice to implement.
Good article – definitely knowledge that needs to be spread wide and far. This stuff really makes your code better.
@bhimrao Thanks for the appreciation
.
I think much of the point of the article is that a lot of the documentation that gets new Cake developers started leads them to use fat controllers and skinny models, rather than the other way around. This is probably unintentional but true nonetheless. I have found myself trying to do the same thing (moving excessive controller code into the model) and it does simplify things a bit as well as adhering more closely to the MVC ideology, which mandates that business logic should be done in the model.
@Paul Exactly.
Fat models and skinny controllers is of course very good practice, but I've always problem to decide, which model to choose to put the logic. Of course the problem is when there are some joined models.
For example shops and orders. Should I put method getShopOrders() in Shop model or getOrdersFromShop in Order model?
@red As far as model associations are concerned, then we need to consider what business logic demands for. For now, let us consider that Shop has many Orders relationship. And now if you are writing the code for some action in ShopsController, then you'll need to make a call like $this->Shop->Order->getShopOrders(). Definition of getShopOrders() method will reside in the Order model. This is because you are asking the query (writing the real find() in) to your Order model. This will bring the required results for you and that's why Model associations are considered as one of the most wonderful and powerful features of CakePHP.
@Rohan thanks for your answer - so you recommend to put the logic "closest" to the model? Referring to the example, when returning orders put to Order model and peroid? Hm, this actually sounds very reasonably for me
@red Yes, the logic must be closest to the respective and responsible models. Glad to know that the explanation helped you.
Good Article - but one question. What about the logic that is not related with any model?
For example, I need a set of functions to connect with an Adserver. These functions are not related with any model. Where should be these functions/class?
thanks
JoseA: They should go in a component and if its for purely display purposes then helper would be a better place for those functions.
Very clear,
congratulations