Cakephp how should you manage controllers and views - cakephp

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!

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 to re-use code in different controller

I have some controller, It use same code idea, only different when call model
ex:
In bookscontroller:
public function abc(){ $this->Book->find(....); }
In categoriescontroller:
public function abc(){ $this->Category->find(....); }
I think it too waste. So, please teach me how to write only one function, but it can call in any controller and load same model.
Thank a lot :)
If you think you need to DRY (dont repeat yourself) that, use components.
They can share code between controllers.
In your case you might also want to look into the crud plugin which takes basic CRUD one step further.
I think it too waste. So, please teach me how to write only one
function, but it can call in any controller and load same model.
You answered half of your question already: DON'T put the code in a controller. Everything related to data manipulating and fetching should go into a model, not a controller.
Assuming you are in your books controller and book belongs to category you can simply call from there:
$this->set('someCategoryData', $this->Book->Category->yourCustomMethod());
$this->set('categories', $this->Book->Category->find('list'));
Need a book from the categories controller?
$this->set('book', $this->Category->Book->view($bookId));
You should fetch the data through the associations and have model methods for your data retrival and manipulation tasks.
A component that calls 1000 models with the logic inside the component is clearly the wrong approach. Read about SoC.

CakePHP Fat models and skinny controllers - appModel

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.

Calendar throughout CakePHP site, but CakeBook says to not use a Model in a Component

I have a small calendar widget-type thing on many pages throughout my site. The goal is for it to retrieve events from X category that fall between Y and Z dates.
My assumption (I'm new to CakePHP) was that I should create a component, and have it do the query. Something like this:
class CalendarComponent extends Object {
var $uses = array('Event');
function getEvents($category = null, $date = null, $limit = null) {
$events = $this->Event->find('list', //conditions to get correct events
return $events;
}
}
BUT, according to this CakeBook page:
To access/use a model in a component
is not generally recommended
So - where would I store this logic / model call if not in a component? I've admittedly not used a component yet (or not created one anyway) due to lack of really understanding how I should use them - any snippet of advice on this is also VERY welcome.
Great question in my opinion and I imagine one that comes up quite often. I was actually dealing with a similar problem where I wanted some site-wide data gathering or functionality shoved into a component.
The first thing to keep in mind:
The book is a guideline.
These 'rules' aren't rules. If there's a good reason for breaking the rule and you understand why the rule is being broken then break the damn thing! Cake itself breaks this rule quite often.
Core components that require/use models:
Acl
Auth
Sessions (fairly positive you can save session data to a model)
So, clearly there are use cases where you need to use a model inside a component. How do you do it though?
Well, there's a couple different ways. What I wound up going with? Something like this:
<?php
ModelLoadingComponent extends Object {
public function startup($controller) {
$controller->loadModel('Model');
$this->Model = $controller->Model;
}
}
?>
That's it! Your component is now setup to use $this->Model...just like you would in a controller.
Edit:
Sorry, to clarify: no, you don't have to setup a new component to load models. I was showing an example for how you could load a component in any model. The startup function I used is a component-specific callback, there's a whole slew of them http://book.cakephp.org/view/998/MVC-Class-Access-Within-Components. These callback methods make components a lot easier to work with. I highly recommend looking at this part of the components tutorial if nothing else.
If you were working inside an AppController object you could call $this->loadModel() but we aren't working an AppController! We're working with a component, really an Object. There is no Object::loadModel() so we have to come up with a different way to get that model. This is where $controller comes into play in our startup callback.
When the startup method is invoked by Cake it will pass the $controller object it's working with on this dispatch as the first parameter. With this we're able to access controller methods...like loadModel().
Why do we do it this way?
Well, we could use ClassRegistry::init('Model') in each of our component methods that need to use the model. If you have 10 methods in your component and only 1 of them uses the model this might work. However, what if you have 10 methods in your component and all 10 of them use the model? Well, you'd be calling ClassRegistry::init('Model') 10 times! That's a lot of overhead when what you really want is just 1 model object. With this method the component is creating one model object. The one we create in startup.
I hope this clarifies your questions and provides some insight into why I use this method for models in components.
Edit: Added a code clarification after I did some experimenting.
I think writing a component is overkill in this case and it would be cleaner to put the getEvents method into the Event model.

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