CakePHP Fat models and skinny controllers - appModel - cakephp

In an effort to follow best MVC practices I'm trying to ensure all my code follows the fat model, skinny controlller methodology therefore could someone cast their eye over the below and tell me if I'm on the right track?
Currently in my app I have
ExpenseClaims hasMany Expenses
Expenses belongsTo ExpenseClaims
In my pages/admin_index.ctp I need to get the total of all Expenses belonging to each ExpenseClaim listed.
So, the best FMSC way I can see to do this would be to load the ExpenseClaim model within the AppModel
App::uses('ExpenseClaim', 'Model');
And then have a function within the AppModel that I can use across the apps controllers (because its in the appModel) that I can pass a ExpenseClaim ID to and it will return a total of all related Expenses.
Is this the correct most MVC way of doing it rather than doing it all in the controller?
Thanks in advance

The best FMSC way is to, as you say, write the function in the model. But!! Don't do it in the AppModel, that's bad practice. Why would you put code related to two (at most) models in the AppModel?? Every model would inherit that function, that doesn't make much sense. Let's say you have a "Menu model" or an "User model", it isn't logical that they inherit a totalExpenses function, right? I understand that you want to have the function available in every controller and view if the need rises, but that's not the way to do it.
Step by step (actually, just two steps):
1) In the ExpenseClaim model, write a new function that will calculate the total of expenses
class ExpenseClaim extends AppModel {
/* definitions and validations here*/
public function totalExpenses($id) {
return $this->Expenses->find('count', array('conditions'=>
array('expense_claim_id' => $id)));
}
}
So, in the ExpenseClaimsController you can call this function with
$total = $this->ExpenseClaims->totalExpenses($the_id);
2) Now, it's logical to have the function that counts the total in the expenses claim model, and therefore available in the respective controller, but you said you wanted to use it in pages/admin_index, and let's imagine pages has absolutely no connection with the claim model. Well, then you can either do
ClassRegistry::init("ExpenseClaims")->totalExpenses($the_id);
or
$this->loadModel("ExpenseClaims");
$this->ExpenseClaims->totalExpenses($the_id);
(both in the controller) and you'll have that value available without putting that function in the AppModel.
(Btw, the code I wrote should work, but you need to fine tune the controllers and models names or close a parenthesis here and there, I haven't tested it).
Now, that's general best practice. Works in most cases, with more complicated functions. But for your case in specific, you may want to take a look at cake's counterCache, it keeps count of stuff without you having to do much.

Related

Cakephp Standard - Is it the Proper standard to write things in controller

Sorry for this basic question. The following are the codes presented in my Userscontroller.php
public function register()
{
//Setting some data
$this->User->create();
$this->User->save($this->request->data)
}
public function edit()
{
//Setting some data
$this->User->save($this->request->data)
}
public function admin_add()
{
//Setting some data
$this->User->create();
$this->User->save($this->request->data)
}
public function admin_edit()
{
//Setting some data
$this->User->save($this->request->data)
}
One of my senior reviewed this code and said that the above code not met the CAKEPHP Standard..Business logic need to move to model
like as follows in model
<?php
class User extends AppModel {
//Validation parts
public functions savingData($data =array(), $id=false)
{
if($id == false)
{
$this->create();
}
$this->User->save($data)
}
}?>
And in controllers he is asking to call this savingData function for Create and update options
in MVC, business rules goes to model.
but in your example there is no any business logic so your code is correct to be in controller.
I suggest to use cakephp console to create controller and model and you can get it by yourself.
Usually any method which has validations or save data to other tables or any extra functionality that makes the actions bigger than one page you need to consider it (break the function to smaller functions) and you can put this function base on your need and access into a Model or Component. I can say fat model concept and Component is used for this kind of situation.
I believe that methods shouldn't be more than one page this make the application maintainable for the next developer.
Also any static methods can be inside Utility as a class which can be called from controllers or views ... easily.
Bear in mind that Cake has lots of model behaviours which contained in all models. like beforeSave(), afterSave(), initiate(), before and after delete() ,...
At the end it is entirely up to you how you implement OOP, tidy and maintainable application.
Other example imagine a Car.
All the engine and complected stuff is inside box where you can't see it you can call it Model .
Controller stuff is in front of your hand so you can Controller it. Controller
Hope it helps if any one can add more option to this answer for others to learn.
Generally speaking your boss is correct in that the goal is fat models - skinny controllers. The reason for this is that if at your work they are doing Unit testing ( which I hope they are ) this makes things MUCH easier on you. Testing in the controller is a pain in the rear.
With that being said though the code in your controller is negligible so don't know if what he is asking is "neccessary" exactly, just technically not consistent with current standards ( which will change with time anyway ). Bottom line if you are bothered that he/she made this criticism, try not to be. Things like this are not written in stone and are left up to interpretation for the most part.

Cakephp how should you manage controllers and views

I am completely new to CakePHP, therefore I am fairly confused on how the management of different controls/views/elements works.
The Problem:
I am attempting to create my home page, which has PageController and view home.ctp. Now, I have another controller, IdeaController, which contains function to load all the ideas from the DB.
public function index() {
$this->set('title_for_layout', 'Ideas');
$this->set('ideas', $this->Idea->find('all'));
}
Now, I need to access this Idea Controller from multiple(up to 3) different views. What is the best way to go about doing so?
Thank-you
You're going the other way around...
Views don't "have access" to the controller. The controller receives a request, tells the models what info it need from them, and passes all necessary data to the views. Views are like little dolls you can put clothes on, but nothing more, they don't speak or play or ask random questions to you.
Generally speaking, there's just one view per action in a controller. If you need to "access" the controller from different views, you may be a little confused with cake.
Without knowing the exact reason you need to do this, I'm going to assume you want to "access" the controller from 3 different views only to get all ideas from the BD. If that's the case, the standard way is:
1) Create a function in the appropriate model:
//Idea Model
public function getAll() {
return $this->Idea->find('all');
}
Well, you don't actually need to do that function, since it's so simple, but for more complex cases where you want to get only the ideas of the logged in user and in a certain order, encapsulating that in the model is a mentally-sane way to go.
2) In every action in the controller that the view will need that data, you do
public function randomAction() {
$this->set('ideas', $this->Idea->getAll());
}
3) In every view you receive the data as
pr($ideas);
And that's it.
The views never "access" the controller, so your question turns out to be a bit confusing. Maybe you meant having 3 views associated to the same action, but there's little reason to do that also... Or maybe you meant to have a "view" in a lot of other "views", like the "info" bar on stackoverflow, that repeats everywhere so you need to have it in a lot of views. If that was the case, then you have to do that in an element to reutilize that "view"-code inside other views.
If you clarify a little more what you want, I can probably answer a lot better, but I hope at least I gave you some direction on where to look or how to go about what you want to do.
Each View has to have it's own method in the Controller.
Say, if you want an ideas/index, ideas/view/x and ideas/add pages, you need to create the following methods:
class IdeasController extends AppController
{
public function index()
{
/* index stuff */
}
public function view($id)
{
/* view stuff */
}
public function add()
{
/* add stuff */
}
}
For the sake of understanding the framework, is advisible to do the blog tutorial, as you after that will be very familiar with the core concepts: http://book.cakephp.org/2.0/en/getting-started.html#blog-tutorial
Good luck!

MVC: Correct pattern to reference objects from a different model

I'm using CakePHP2.3 and my app has many associations between models. It's very common that a controller action will involve manipulating data from another model. So I start to write a method in the model class to keep the controllers skinny... But in these situations, I'm never sure which model the method should go in?
Here's an example. Say I have two models: Book and Author. Author hasMany Book. In the /books/add view I might want to show a drop-down list of popular authors for the user to select as associated with that book. So I need to write a method in one of the two models. Should I...
A. Write a method in the Author model class and call that method from inside the BooksController::add() action...
$this->Author->get_popular_authors()
B. Write a method in the Book model class that instantiates the other model and uses it's find functions... Ex:
//Inside Book::get_popular_authors()
$Author = new Author();
$populars = $Author->find('all', $options);
return $populars;
I think my question is the same as asking "what is the best practice for writing model methods that primarily deal with associations between another model?" How best to decide which model that method should belong to? Thanks in advance.
PS: I'm not interested in hearing whether you thinking CakePHP sucks or isn't "true" MVC. This question is about MVC design pattern, not framework(s).
IMHO the function should be in the model that most closely matches the data you're trying to retrieve. Models are the "data layer".
So if you're fetching "popular authors", the function should be in the Author model, and so on.
Sometimes a function won't fit any model "cleanly", so you just pick one and continue. There are much more productive design decisions to concern yourself with. :)
BTW, in Cake, related models can be accessed without fetching "other" the model object. So if Book is related to Author:
//BooksController
$this->Book->Author->get_popular_authors();
//Book Model
$this->Author->get_popular_authors();
ref: http://book.cakephp.org/2.0/en/models/associations-linking-models-together.html#relationship-types
Follow the coding standards: get_popular_authors() this should be camel cased getPopularAuthors().
My guess is further that you want to display a list of popular authors. I would implement this using an element and cache that element and fetching the data in that element using requestAction() to fetch the data from the Authors controller (the action calls the model method).
This way the code is in the "right" place, your element is cached (performance bonus) and reuseable within any place.
That brings me back to
"what is the best practice for writing model methods that primarily
deal with associations between another model?"
In theory you can stuff your code into any model and call it through the assocs. I would say common sense applies here: Your method should be implement in the model/controller it matches the most. Is it user related? User model/controller. Is it a book that belongs to an user? Book model/controller.
I would always try to keep the coupling low and put the code into a specific domain. See also separation of concerns.
I think the key point to answer your question is defined by your specifications: "... popular authors for the user to select as associated with that book.".
That, in addition to the fact that you fetch all the authors, makes me ask:
What is the criteria that you will use to determine which authors are popular?
I doubt it, but if that depends on the current book being added, or some previous fields the user entered, there's some sense in adopting solution B and write the logic inside the Book model.
More likely solution A is the correct one because your case needs the code to find popular authors only in the add action of the Book controller. It is a "feature" of the add action only and so it should be coded inside the Author model to retrieve the list and called by the add action when preparing the "empty" form to pass the list to the view.
Furthermore, it would make sense to write some similar code inside the Book model if you wanted, e.g., to display all the other books from the same author.
In this case you seem to want popular authors (those with more books ?), so this clearly is an "extra feature" of the Author model (That you could even code as a custom find method).
In any case, as stated by others as well, there's no need to re-load the Author model as it is automatically loaded via its association with Books.
Look out for Premature Optimization. Just build your project till it works. You can always optimize your code or mvc patterns after you do a review of your code. And most important after your project is done most of the time you will see a more clear or better way to do it faster/smarter and better than you did before.
You can't and never will build a perfect mvc or project in one time. You need to find yourself a way of working you like or prefer and in time you'll learn how to improve your coding.
See for more information about Premature Optimization

where to add queries ? in a model or a controller in Cakephp

i am working on a Cakephp 2.x.. i have watched many tutorials and read documentation too ... but i didnt get one thing .. i have seen that in some tutorials they write queries in a model and some of them write every thing in controller and model remains clean except they add validation rules etc ... how i am coding right now is like that
class ContactsController extends AppController{
public function index(){
$this->layout='default';
$id = $this->Auth->user('idUser');
$data= $this->Contact->find('all',array(
'conditions' => array('Contact.User_id' => $id)));
$this->set('contacts',$data);
}
}
I wrote the find function here in controller..i have seen in some tutorials they have written this in model class and then call it in Controller .. so i want to ask what would be the best way or what is actually the standard way ? should i have to code everything in controller and write just validation rules in model ? how should i code so that my code looks clean and easily manageable
There are several reasons why you want to do queries in a model:
Data manipulation and fetching is a clear model task in the MVC pattern
You can not re-use queries if done in a controller, simple example is that you want to do something from a shell AND a controller.
Models are a lot easier to unit test than controllers
If you need to change a query you do it in ONE model, not 10 controllers that might do the same query or method.
For an example check the view() method of the controller and model here
Controller https://github.com/CakeDC/users/blob/master/Controller/UsersController.php#L218
Model: https://github.com/CakeDC/users/blob/master/Model/User.php#L536
Same for other methods. Just check how much code is in the model and how much in the controller and try to get an understanding why.
What others stated here is wrong: It is clearly no standard to do queries in the controller.
I disagree with Arkheart. I think you should put it in the model. Your model then becomes essentially a data API for your controllers. You have much cleaner separation of concerns. And to what downside? Your models will have more methods in them. So what.

Is using the RequestHandlerComponent in a model possible?

I'm new to PHP and decided to use the cakePHP framework to help me get started.
I can't figure out one thing though, I want to call methods on the RequestHandlerComponent class to update a users last used IP address and other information, I figured the best place to put this would be in the beforeSave() method on the User model.
I can't figure out how to call the getClientIP method though.
The normal code that would otherwise go in the controller doesn't work. Is there another way to call this class if you're in the model and not the controller?
Class Level:
var $components = array('RequestHandler');
And in the function:
$this->data['User']['lastActiveIP'] = $this->RequestHandler->getClientIP();
Gives:
Undefined property: User::$RequestHandler
Call to a member function getClientIP() on a non-object
Components, by design, aren't available to models (without bypassing MVC convention - which you can do, of course). If you chose to force it to be available, look into ClassRegistry::init(). A better solution, I think, would be to employ the RequestHandler component in your controller (where it's meant to be used), set the lastActiveIp value in the controller (exactly as you've shown in your own example code) and pass the entire data array along to the model.
Now your component is being used where it should be and the model gets to remain ignorant about where it gets its data. At the risk of oversimplification, all the model should know is what to do with the data once it arrives; let the controller worry about collecting and packaging the data.
In addition to Rob's answer, maybe it's enough to put a bit of code together yourself that uses the general env('REMOTE_ADDR') or similar variables. Look at the RequestHandler code, it's not doing anything terrifically complicated.
You may even be able to call the component statically, which is slightly better than instantiating it in the model (still in violation of MVC though). Untested, but should work:
App::import('Component', 'RequestHandler');
RequestHandlerComponent::getClientIp();

Resources