Dealing with calculated fields in CakePHP’s find()

Let’s say we’ve got some calculated field in our find() method, something like SUM() or COUNT() or maybe AVG().

For the sake of example let’s do something simplistic, like:

pr($this->Company->find('all', array('fields'=>array('Company.id','Company.name', 'COUNT(id) AS total_count'),
		                                     'group'=>array('Company.name'),
		                                     'recursive'=>-1)));

This is all fine and well, except our total_count field does not appear with the rest of the fields in the resulting data array, but rather in it’s own index, like here:

Array
(
    [0] => Array
        (
            [Company] => Array
                (
                    [id] => 1
                    [name] => New Company
                )

            [0] => Array
                (
                    [total_count] => 1
                )

        )

We can use Set::check() and easily reformat our array in afterFind(), so that we can reference all fields in a consistent manner.

Try something like this in your model (or app model):

function afterFind($results, $primary=false) {		
    	if($primary == true) {		   
    	   if(Set::check($results, '0.0')) {
    	      $fieldName = key($results[0][0]);
    	       foreach($results as $key=>$value) {
    	          $results[$key][$this->alias][$fieldName] = $value[0][$fieldName];
    	          unset($results[$key][0]);		          
    	       }
    	    }
    	}	
    		
    	return $results;
}

Now all fields appear where we would expect them.
Referencing all fields in a consistent manner (in the view, for example) is already a good enough benefit to use an approach similar to this one, but in addition if the change is ever made to the core in order to fix this up, your code will not be affected in any way.

Saving extra fields in the join table (for HABTM models)

An interesting question came up on IRC today, which essentially boils down to:
“How to save extra fields in the join table for HABTM models, while creating a new record for one of the involved models all at once?”

I’ve seen this question float around here and there, but do not recall any specific solutions…

So here, I propose one method which requires a temporary hasMany bind between the model which we are saving and the join table model, then using saveAll() to save the data.

Let’s take a look at an example, where we have the good ol’ Post HABTM Tag.

We are trying to save a new Post, for which we already know a Tag.id (a pretty standard HABTM save scenario), but now we also need to save an extra field ‘status’ into our join table.

//our data array
$this->data['Post']['title'] = 'Hello';
$this->data['Post']['post'] = 'My new post';
$this->data['PostsTag'][0]['tag_id'] = 15;
$this->data['PostsTag'][0]['status'] = 'disabled';

$this->Post->bindModel(array('hasMany'=>array('PostsTag')));
$this->Post->saveAll($this->data);

And that’s all there is to it, we’ve now saved our Post as well as our relation to Tag and the required extra field. Of course, in reality, your data will come from the form, but hopefully you get the gist of this idea.

SELECT … AS … in CakePHP 1.2

This is a simple hint, but hopefully will be useful to some…

You might have noticed some examples where you have:

$this->Profile->find('all', array('fields'=>array('SUM(Profile.votes) AS total_votes')));

But what if you simply needed Profile.field AS another_name?
‘fields’=>array(‘Profile.field AS another_name’) … isn’t going to work, just like that.

What you need to do is escape the fields in your ‘fields’ array, like so:

$this->Profile->find('all', array('fields'=>array('`Profile`.`name` AS `another_name`')));

(Note the backticks)

Food for thought: $this->redirect() vs $this->render()

One example, that we often see, is something along the following lines in the controller:


if($this->User->save($this->data)) {
  $this->Session->setFlash(... some stuff for the view ... );
  $this->redirect(array('action'=>'success'));	
}

If all we are doing is displaying a “success” page back to the user, do we really need to bother with a redirect()?

To me it seems a little easier to do:


if($this->User->save($this->data)) {
  $this->set('arrayOfMessages', $myPreviouslyPreparedArrayOfMessages);
  $this->render('success'); 
}

Not really a big deal one way or another, but something to consider (especially for those very high traffic servers ;)).

P.S. As some readers pointed out, the obvious draw back is that $this->render() will cause the data re-submit prompts when refreshing or going back in the browser.

Set::merge() and dynamic validation rules

Here’s another trick with Set::merge()…

Let’s say we’ve defined some basic validation rules in our Profile model, something like:

var $validate = array (
   'name' => array(
      'rule' => array('notEmpty'),
      'required' => false,
      'message' => 'Please enter a name'        	
     )

  .... and so on for all other fields ...

);

Now we’ve got an action where this particular set of rules isn’t going to fly…

Maybe the data is coming from an external (somewhat untrusted) source and in this case we need to make sure that the ‘name’ is present in the data array (i.e. ‘required’=>true) and that it’s not only ‘notEmpty’, but now it has to be ‘alphaNumeric’.

So we do an easy “update” to our default rules, which we’ve previously defined in the model:


$this->Profile->validate = Set::merge($this->Profile->validate, array(
      'name'=>array(
         'required'=>true,
         'rule'=>array('alphaNumeric')
      )
));


What happened is that now all our default rules remain as we have defined them in the model, but the ‘name’ field is now going to be validated using this new set of rules.

The theory is that you set your $validate array with some default rules that will apply in most of the situations, and use this technique when your validation rules have to change for a few fields in any given action.

To make things even cleaner you should probably add a method to AppModel called something like modifyValidate, and then you could use:

$this->Profile->modifyValidate(… array of new rules…);

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.

Use Sanitize::html() class in the views

Update (9/16/2008): As some people pointed out it’s easier to use the h() method defined in basics.php of the core, it achieves the same basic goal, but since Sanitize class has other methods and purposes, I’m gong to leave this as an informational example and food for thought. (i.e. you could completely strip-out all dangerous characters using Sanitize for one view, while keeping the data intact for other views and in the DB).

A little example for when Sanitize::html() can be quite useful in a view of your application…

Perhaps you have some Comments form, where you wouldn’t mind if users entered something like:

<script type="text/javascript">
<!-- 
I am a bad script!
// -->
</script>

(Maybe it was for educational purposes only…)

Of course the common rule is to make your data safe before you save it. However, such data is perfectly fine for saving, but very dangerous for displaying back to the user. Therefore you don’t want to convert any HTML entities (i.e. ‘<‘) to their safe alternatives, before it goes into the DB.

In the view, all you need is to call App::import('Sanitize'); then, you can easily do something like:

echo Sanitize::html($comment['Comment']['body']);

Use Set::merge() to modify the $paginate property

Let’s say you have a Posts controller, where you’ll need to paginate some posts in various actions.
You might have some “generic settings” as the manual recommends in your $paginate variable similar to this:

var $paginate = array('Post'=>array('limit'=>10, 'order'=>array('Post.modified DESC')));

Now, the above setting is probably going to work well for all of your actions where pagination is required. However, in some actions you need to also add a ‘contain’ key and maybe some ‘conditions’…

We can easily merge our main “setting” and any other desired keys we’d normally pass to $paginate, by using Set::merge()

Let’s say this is our extendedView() action of the Posts controller:


$this->paginate = Set::merge($this->paginate, 
array('Post'=>array(
     'conditions'=>array(
        'Post.forum_id'=>$this->Session->read('Forum.id')),
     'contain'=>array('Thread'=>array('Professor', 'Student'))
)));

Book review: CakePHP Application Development

Nice folks over at Packt Publishing asked me to a do a review of a book they’ve recently published, called “CakePHP Application Development“, by Ahsanul Bari and Anupom Syam. I was quite happy to oblige and not only because I got a free book, but also because I’ve been meaning to see if some of these new books about CakePHP would do a good job giving justice to such a cutting-edge framework.

I felt that it must be pretty tough for authors, editors and publishers to accomplish a book, especially the one which covers CakePHP 1.2, when the framework is constantly evolving. To my, perhaps, surprise the book actually happened to contain very up-to-date material. Overall, it accomplished the task of introducing (and advancing) one into the world of CakePHP quite well. 

What I liked…

First and foremost I enjoyed the fact that the book was very much example-driven. To me it’s just a preferred way of learning: study a functional example, try to figure out what’s doing what and finally with some luck modify and break things on my own. 

CakePHP Application Development briefly introduces MVC and CakePHP installation and takes you right to your first realistic web-app. By realistic I mean an application that actually involves and covers a good majority of the framework’s features. (There is no need to use a framework to output “Hello world”, which might’ve been a useful exercise in 1979, but certainly not today and not in this context).

The introduction to CakePHP concepts is gradual, but to the point. The explanations were clear and overall the book was a quick and easy read (which is good because I’m lazy and have a hard time with over-the-top technical reading). The “What just happened?” approach gives a perfect feel to leading by example. 

The progression of the book is pretty logical, but you have to make sure to really understand each chapter (and each part thereof) to be prepared to deal with the next, usually more complex topic.

A few gripes…

As mentioned I think the authors did a very good job of introducing the framework and a vast majority of the features. There are a few things that I wish would be improved (maybe in the next edition, if one is ever planned). 

No information about Behaviors. I feel that model behaviors deserve special attention, since they can become a very intrigual part of an application and business logic can often be handled very elegantly with behaviors.

No coverege of unit testing. I wish it would just be at least introduced or briefly touched upon. Granted, testing is somewhat outside the scope of the framework itself, but it’s a topic that must be brought up time and time again especially when it’s so readily available in CakePHP.

Too much time spent on AJAX. Sure, it’s important and no book on a modern web development framework would be completely web-2.0-ish without it, but I have a feeling that CakePHP will be abandoning its dependency on Prototype (JS framework). It gets things done, but it doesn’t always get things done the right way. 

Code formatting and spelling mistakes. Having to follow code is hard enough, but it’s even harder to follow it from the black and white pages of a book. Proper indentations and formatting are necessary (with logical bolding of some elements) and very crucial to a technical/coding book such as this one.  While in the beginning the code looks mostly nicely formatted, for some reason it seems to fall apart later on. There were also some misspellings, etc. (which, I guess, are good to discourage blind copying, but really should be addressed more carefully). 

Bottom line…

I definitely recommend this book as a first reading material for anyone who is just starting out with CakePHP. I simply see no way how one could be disappointed in the amount of information, its overall presentation and hopefully the ultimate result once the reader is finished and has tried out all of the examples. So to sum things up, on a scale from one to ten, it deserves a solid eight. Had it covered behaviors (with some solid examples), I’d probably give it a nine and I don’t think I can ever imagine a perfect technical book, so a 10 would be a helluva hard mark to reach :)

Example of CakePHP’s Containable for deep model bindings

Here’s an example of using the Containble behavior when you’ve got deep and complex model relationships.

Let’s consider the following model associations…

User->Profile
User->Account->AccountSummary
User->Post->PostAttachment->PostAttachmentHistory->HistoryNotes
User->Post->Tag

It’s very possible that each of those models has other associations, so we need to use Containable to retrieve just the models we need (we’ll also specify fields and conditions for some models just to make it more “fun”). Remember, that Containable behavior has to be attached to all of the models in our relationship, therefore to make your life easier you should probably attach the behavior to all models in App Model…

Our find() call:

$this->User->find('all', array(
   'contain'=>array(
      'Profile',
      'Account'=>array('AccountSummary'),
      'Post'=>array(
         'PostAttachment'=>array(
            'fields'=>array('id','name'),
               'PostAttachmentHistory'=>array(
                  'HistoryNotes'=>array(
                     'fields'=>array('id', 'note')
                   )
               )
         ),
         'Tag'=>array('conditions'=>array('Tag.name LIKE'=>'%happy%'))
       )
    )
));

Crazy, huh?

Some things to keep in mind:

  • ‘contain’ key is only used once in the main model, you don’t use ‘contain’ again for related models
  • Deep binding is done by using the ‘contain’=>array(‘ModelA’=>array(‘ModelB’=>array(‘ModelC’…
  • Each model can have it’s own set of find() options, by using ‘contain’=>array(‘ModelA’=>array(‘fields’=>…, ‘conditions’=> …, ‘ModelB’=>array(‘fields’=>…etc.
  • As rafaelbandeira3 pointed out, ‘fields’ and ‘conditions’ are the only keys that will be of use. ‘limit’, ‘recursive’, ‘group’ and ‘order’ will not produce desired results for any of the “contained” models.
  • Of course you can still apply them to the main model (i.e. User). Well, except ‘recursive’, which is not needed since Containable handles the associations for you