Execute code in model callbacks based on controller actions
I think it happens pretty often, that you need to run some code in a given model’s callback, but only on certain controller actions.
For example, we have a User model and it has an afterFind() method, which massages the results array in some manner. However, we only want for this to happen when our controller action is called “test”.
Well, it’s as easy as 1, 2, 3…
1. Add this to your App Model:
var $controllerAction = null;
function setControllerAction( $action = null ) {
if($action) {
$this->controllerAction = $action;
}
}
2. Now in your User model, you can do something like this:
function afterFind($results, $primary = false) {
if($this->controllerAction == 'test') {
// run some code such as $this->_reformatTestData($results);
}
}
3. And this is how you would set things up in the controller:
$this->User->setControllerAction('test');
$this->User->find('all');
Done.
And now you have to set the controller action every time before you perform a find… and you have to redundantly write out the action name. what’s so great about this?
I thought you can get the current action name in any controllers by calling: $this->action ?
@Jonah Turnquist
No, you specify the action only when you need this. For any other actions you proceed as always and don’t have to write anything at all. What’s great about this, is that you can easily specify when to run the code in afterFind() based on a given action name.
@Kien Pham
Sure, but not in the model.
But you can do something like:
$this->User->setControllerAction($this->action);
Either way it’s just an example.
“I thought you can get the current action name in any controllers by calling: $this->action”
You can, but this code gives you the action in the model.
An alternative is to create an action property in your AppModel ( var $action) and set it in the beforeFilter() of your AppController ( $this->Model->action = $this->Action )
@Richard@Home
You can definitely set the property directly, but as a good practice it is recommended to have wrapper methods for setting Object properties.
Wouldnt it be “smarter” to use custom finds for stuff like that??
$this->find(‘my-handler’) wich just wraps a find(‘all’) and applies your callbacks to the result before its returned?
@Jippi
I’m not sure how you’d apply that, since all callbacks are fired with each find() operation… also that means that for each action where you need certain callbacks applied you’d need to define a new find type.
It will get a little overwhelming if you’ve got about a 100 of those for various models.
If those of you who wanted all callbacks to return the same thing, you should be looking in to creating/using a behavior, not teknoid’s neat little trick here. :)
@Brendon Kozlowski
Very good point.
[...] Una de las cosas que me he encontrado al desarrollar con CakePHP es el poder ejectuar cierto código en las callbacks (afterSave, beforeSave, afterFind, etc.) del modelo en función de la acción realizada desde el controlador. Ciertamente no había pensado en la solución que plantea teknoid al respecto: [...]
Hello!
How about using Configure::write() to save the controller and action names in the AppController::beforefilter()? it would be available for all models, or maybe using Router in the model.
Regards,
Inside
@Inside
Router should certainly not be used in the Model.
However, how do you propose doing the Configure::read/write?
Can you share (bin) your proposed sample code somewhere?
Thanks.
Maybe i’m making a big mistake, but wouldnt Router::getParam(‘action’, true) work fine in this case? instead of using a method to pass the action name. Please, correct me if i’m thinking in a bad way (breaking MVC or something like that).
About configure i was thinking in doing this in the app controller:
Configure::write(array(‘Controller’ => array(‘action’ => $this->action, ‘name’ => $this->name)));
and then in the model use Configure::read(‘Controller.action’)
Regards.
@Inside
Using Router in the model, is definitely breaking MVC, imo.
Regarding using Configure, that could be interesting, but I’d need to look into that in more detail. Thanks for sharing.
Why not have Model::setControllerAction($this->action); in AppController ?
@Steve
You certainly could, but imagine this is not going to be needed by every model…
nice idea
but you should define your variable inside the class (otherwise it throws warnings if not passed by controller and called in the callback):
class AppModel extends Model{
var $actsAs = …;
var $controllerAction = null;
…
@mark
It is defined, 1st line of code ;)
I had something similar, i dealed with it something like this:
Put in your model:
var $callback;
function beforeFind($queryData){
if(isset($queryData['callback']) && $queryData['callback']==’test’){
$this->callback = ‘test’;
//do you beforefind things when action is test
} else {
// Do you normal beforeFind things
}
}
function afterFind($results, $primary){
if($this->callback==’test’){
//do your special afterFind logic for test method
}
}
Then you can do the find from the controller like:
$this->Model->find(‘all’,array(‘conditions’=>array, ‘callback’=>’test’));
@Ceeram
Nicely done. Thanks for sharing.