zen of coding

Nice trick to toggle your Model field in CakePHP

This little trick will allow you toggle any field, for example ‘status’ which can be either 0 or 1…

Add this little function to your model or better yet, app model:

Update 1: Thanks to Kalt for the sound improvement to the original method
Update 2: And thanks to rafaelbandeira3 for improving it even further

function toggleField($field, $id=null) {
   if(empty($id)) {
      $id = $this->id;
   }

   $field = $this->escapeField($field);
   return $this->updateAll(array($field => '1 -' . $field),
                             array($this->escapeField()  => $id)
                           );
}

(Daniel Hofstetter pointed out that instead of ‘1-‘ you could use the ‘NOT’ operator)

Now in your controller you could do:

$this->User->toggleField('status', $id);

Of course, we assume that we are dealing with only 0 or 1 as status types.

  • I would use the NOT operator, it makes it easier to understand your snippet.

  • Why don’t you use Model::escapeField ?

    $this->updateAll(
    array($this->escapeField($field) => ‘1 – ‘.$this->escapeField($field)),
    array($this->escapeField() => $id)
    );

  • teknoid

    @Daniel Hofstetter, Kalt

    Not sure where I would use NOT operator :)

    I will follow Kalt’s suggestion to simplify and improve the method… Thanks!

  • Instead of ‘1-‘ you would use ‘NOT ‘.

  • teknoid

    @Daniel Hofstetter

    Ah, thanks. Well, honestly to me it’s more intuitive to use ‘1-‘.
    I did add to the post that NOT could be used instead.

  • Pingback: links for 2008-09-17 « Richard@Home()

  • rafaelbandeira3

    The method is fine. But for design purposes, not doing a thing if nothing is passed is not very smart, as it is mandatory to use a field name in the method it should issue a warning if you don’t, and you don’t even need to do that, just don’t set a default value to $field. Now going even further, your method should be read as ‘toggle field status’, so use $id first is a little bit confusing – params should be toggleField($field, $id).

    Last but not least, you could follow Model class desing and use $this->id as default for id when it is needed – Model::read(), Model::del(), Model::saveField()… so I really liked your approach but I think this way would be better:

    function toggleField($field, $id=null) {
    if(empty($id)) {
    $id = $this->id;
    }
    $field = $this->escapeField($field);
    return $this->updateAll(
    array($field => ‘1 -‘ . $field),
    array($this->primaryKey => $id)
    );
    }

  • teknoid

    @rafaelbandeira3

    Excellent suggestions, I’ve updated the post to include your improvements.

  • Thanks for the post. I was thinking about this myself, but coming from the opposite direction in a way and wrote a generic function to place in app_controller – but I came across this and thought it would make sense to use that too – the eventual idea being to have a helper that displays an icon that you can then just toggle via ajax.

    function admin_toggle($field, $id) {
    if(empty($field) || empty($id)){
    $this->flash(__(‘Error.’, true), array(‘action’=>’index’));
    } else {
    if($this->{$this->modelClass}->toggleField($field, $id)) {
    $this->flash(__(‘Updated.’, true), array(‘action’=>’index’));
    } else {
    $this->flash(__(‘Unable to Update.’, true), array(‘action’=>’index’));
    }
    }

    With my function obviously you would need to ensure that a user had permission to update this field in
    this model or you’ve got big security hole…

    I did make one change: array($this->name.’.’.$this->primaryKey => $id) to specify the Table Alias in the Where clause as I was getting an SQL error of an ambiguous Id in the Where clause – I guess because I hadn’t unbound a related Model?

    Anyway I never would have thought of toggle-ing the values with 1- it’s a neat idea.

    Cheers

  • teknoid

    @John

    Thanks for your comment. This is a nice addition to the method.
    Regarding $this->name, it is best to use $this->escapeField(), which will automatically escape the field and add a model name. Also you have $this->alias, if needed, which is still a more accurate representation of the model name in most cases.

  • @Technoid

    Thanks for the tip, much appreciated.

  • teknoid

    @John

    No problem

  • Pingback: Inverser (toggle) la valeur booléenne d’un champ - Pierre MARTIN()

  • Ben

    @John
    Excellent piece of code. Interestingly enough, that’s exactly what I have been using in my apps. It is very mind lifting to see someone else come up with the same idea as you. :)
    I also have another method which looks the same but is used to increment fields such as ‘download_count’.

    A minor gotcha: You forgot to update your usage example to reflect the changes you made to the method…

    This:

    $this->User->toggleField($id, ‘status’);

    becomes this:

    $this->User->toggleField(‘status’, $id);

  • @Ben

    Thanks for the correction. I’ve updated the post.

  • Be aware that the updateAll method does not trigger the callbacks (beforeSave, afterSave). If you need too trigger them, use the saveField method as follow :

    function toggleField($field = ‘status’, $id = null) {
    if(!empty($id)) {
    $this->id = $id;
    }

    if(!$this->id or !$this->hasField($field)) return false;

    return $this->saveField($field, ‘1-‘ . $this->escapeField($field));
    }

  • @Kalt

    Excellent. Thanks for sharing.

  • My solution does not work anymore with 1.2.1.x. I do now :
    $this->saveField($field, !$this->field($field));
    It generates an additionnal query but this is the only way I found to trigger the model callbacks.

  • Thanks for this. Been looking to do this but couldn’t find as simple a solution as this.

    T

  • @PolyMe

    No problem, I’m glad it helped.

  • Ankit

    The code worked for me & would like to thank you buddy… :)

  • @Ankit

    Nice, no problem ;)

  • I’ve turned this into something of a behavior. Someone in IRC asked me to take a look at their Toggle Behavior which spurred me into developing this (I also had need for something working in this respect). AD7six goaded me into giving it mounds of features. The last thing I need to add is support for model callbacks, for which I was going to dive into the core, but that is for another day :) . It takes into account Rafael Bandeira’s comment about core conventions, Kalt’s $model->escapeField() tip, Daniel Hofstetter’s remark about using ‘NOT’ instead of ‘-1’, as well as core conventions regarding the actual writing of code. You could use it as a slight replacement for the SoftDeletableBehavior by Mariano Iglesias, as long as you add in a condition in your Model::beforeFind() to not find anything toggled off :)

    I’ll push it to github.com, but for the moment you can find it at http://bin.cakephp.org/saved/50826 .

    Enjoy everyone!

  • @Jose Diaz-Gonzalez

    Nicely done. Thanks for sharing! ;)

  • Vaughany

    Is there any chance of you updating this for CakePHP 3?

%d bloggers like this: