zen of coding

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.

  • Pingback: faemino.net » » Ejecutar callbacks del modelo en funciĆ³n de acciones del controlador()

  • 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.

  • Steve

    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…

  • mark

    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 ;)

  • Ceeram

    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.

%d bloggers like this: