BETWEEN AND SQL syntax in CakePHP

This is how you’d write a BETWEEN… AND… SQL query in a cake-like way.

The example should be pretty much self explanatory:

$this->Post->find(‘all’, array(‘conditions’=>array(‘Post.id BETWEEN ? AND ?’ => array(1, 10))));

Note, that CakePHP will quote the numeric values depending on the field type in your DB.

CakePHP and save() security

An interesting point came up on IRC…

What happens if someone submits data to your application via a fake form?

How can you ensure that a malicious user will not simply save some unwanted data by filling your $this->data array with things you don’t want there? For example, by sending an “extra” field, one could post $this->data[‘User’][‘id’] = 5; and trigger an update instead of save… well you can use your imagination to come up with some other evil tricks.

A simple solution is to ensure that you pass a third parameter to your save() method. If you take a look at the API, you’ll see that save() will allow you to specify a list of fields, which you know should be saved, the rest will be ignored. 

P.S. Additional security for your forms (and ultimately your data) can be achieved with the Security component.

Retaining a search string in the URL

As you know, for a good reason, CakePHP will use the POST method by default to submit form data. However, there is a case where this could present a problem…

Let’s say you are building a little search tool for all of your articles. It would be nice to allow people to bookmark or copy/paste the search URL, which would look like this:

http://mysite.com/articles/search/Hippopotamus

If you have a form that does the POST of the search term “Hippopotamus” that’s not going to work just like that.

Instead, create a form that will POST to searchRedirect() action, which would look something like:

 
function searchRedirect() {         
  if(!empty($this->data)) {             
    $this->redirect(array('controller'=>'articles', 'action'=>'search', $this->data['Article']['searchField']));       
  }     
} 

Now you can build your search() action to read the value from the URL (which was passed via the redirect) and do whatever it has to display the results… and of course you now have the search term visible in the URL.

The example above is simplistic and you probably want to make sure to use urlencode() urldecode() or some other method to ensure that you read safe data from the URL param. Better yet, you might want to store the search term in the session (and read it from there), so that if someone tampers with the URL, it won’t cause any problems in your app.

P.S. FYI, this can hardly be called a hack as it is actually a common enough practice in web development http://en.wikipedia.org/wiki/Post/Redirect/Get

read() vs find()

What exactly is the differednce between $this->User->read() and $this->User->find()?

Seemingly, they both retrieve the data (and possibly associated data) for the User model. Yet, there is a an important distinction: read() will also set the data for your model object, while find() will simply return the resultset array.

What’s the big deal?
None, really. However, if you are not planning to manipulate the object in any way, what exactly is the point of populating it with any data?
If your goal is simply to get some data and pass it to the view for display, you should stick with using find(). As a matter of fact read() will call find() internally anyways.

That beings said, read() can probably be useful if you need to manipulate your model’s data and then save it. Surely the same can be done with find(), but read() will provide a cleaner syntax, if that’s what you are after.

I’m not going to address any issues with performance, because if you are running into bottlenecks due to the performance of read() vs find(), I’m sure you’ve got more pressing issues at hand to deal with (like saving your DB or purchasing a better server).

Changes to database config keys in RC1

There are a few important changes in the database configuration, which I wanted to quickly point out:

1. ‘connect’ key is gone.
For example, if you had ‘connect’ => ‘mysql_connect’ you will need to replace it with ‘driver’=>’mysql’

2. New key: ‘persistent’=>false
This one is very important and you definitely want to add it to your configuration file.
It had caused me some pain, before I noticed this ticket:
https://trac.cakephp.org/ticket/4873

 

Validating a checkbox in CakePHP 1.2

In a few simple words: use the ‘comparison’ rule to validate a checkbox.

To give you an example, let’s say a user needs to agree to the terms of service when registering a new account. In your User model you setup a rule for the checkbox as follows:

 'agree' => array(
            	'rule' => array('comparison', '!=', 0),
            	'required' => true,
            	'message' => 'You must agree to the terms of use',
       	        'on' => 'create'
       )

In the view you create a field by doing something like this:
<? echo $form->checkbox(‘User.agree’); ?> I agree to the terms of service
<? echo $form->error(‘User.agree’); ?>

Note, I’m using $form->checkbox(), therefore I need to have $form->error(). Otherwise you could use $form->input().

As you can probably guess from the code the empty checkbox will send a default value of zero, and that is what your validation rule is going catch. The ‘on’ key will ensure that the rule is only enforced on account creation and not when the User is editing an existing account.

15 Essential CakePHP Tips

1. Save() does not work!
Sometimes it happens that save() fails without any obvious reason. Your data array looks fine and you’ve build the form correctly, etc., etc., but no query was executed. It is very possible that save had failed due to validation errors. Maybe you are updating some model and while the current fields in the form pass the validation, there is a chance that some “other ones” are causing the validation rules to fail. An easy (and helpful) way to see what’s going on with validation is to do pr($this->validationErrors); in your view. By employing this method you’ll see exactly what’s happening with your model’s validation. The other option is to pass false as a second parameter to save(); in order to disable the validation. However, the latter method does not give much of hint and should not be used to fix a failing problem, but rather to purposely avoid validation.

2. Save() still does not work!
Do you have beforeSave(); in your model or app model? Always double-check for this method, and even more importantly, ensure that it returns true.

3. Validating on create or update
CakePHP has an 'on' key to be used in your $validate array. It allows you to specify whether the rule should be enforced during a new record creation or during an update of an existing record. For example, if you only want to check for a unique email address when you are creating a new User account, you’d add 'on' => 'create' to your $validate array. Therefore, this rule will be ignored when you are updating/editing some user.

4. Mind your cache
You’ve made some changes to your tables, but you app “ignores” them… You’ve moved some things around, bur your app doesn’t seem to “notice” that…
Clear the cache. Delete the files from your app/tmp/cache. Be sure to only delete the files and not the directory structure.
Basically if you notice some strange behavior in your app, just keep in mind that it could be caused by the cache.

5. I’m losing the extra URL parameters when paginating
You need to retain some extra URL parameters during pagination. For example the URL is something like /products/view/45. Yet, when you build the pagination the ID (45) is lost… Well, all you need to do is add this line of code to your view: $paginator->options(array('url' => $this->passedArgs));

Update: this is likely fixed in the recent builds of CakePHP, but still good to be aware of.

6. Using afterFind()
Model::afterFind() allows you to perform some data manipulation after the Model’s find method. Here’s a sample usage in the model:

function afterFind($results, $primary=false) {
  if($primary == true) {
	// do stuff to the $results
  }

  return $results;
}

$primary will be set to true after your find() method is executed and some results are returned. After you modify the $results you will return them back to your app (controller).

7. I need to know the basic information about my table
Try this: pr($this->ModelName->schema())

(Thanks, gwoo, for the hint)

8. How do I check for a non-empty field in CakePHP 1.2?
Update: cake core now has a 'notEmpty' rule built-in, so definitely use it instead.

For historic purposes only:
The old VALID_NOT_EMPTY constant is now deprecated and there does not seem to be a rule to replace it… Well, it’s easy enough by using: 'rule' => array('minLength', '1')

Martin Bavio pointed out that having space characters (only) as your field data will make the validation rule pass and this is probably not a desirable effect. Using this simple regex, instead, will catch an empty string with space characters: 'rule' => array('custom', '/\S+/')

9. Avoid using the $uses array
You’ve got two completely unrelated models, but you need info from one in the controller of another. The first idea is to add them to the $uses array. Hey, it’s easy and gets the job done. Well, to make the long story short, it’s bad practice. Think about your model bindings and ensure that models are really not related to one another. Sometimes your User model is definitely not related to CommentRating, but you absolutely need it in your users controller. Well, just by chance it appears that User->Post->Comment->CommentRating. It’s a deep binding that may not be obvious at first, but by employing such a chain of models you can easily avoid using the $uses array, when it’s really not necessary.

Update: if you truly need to load some random (and definitely unrelated) model in your controller, do it like so in the given action:

$MyModel = ClassRegistry::init('MyModel');

or even:

$someData = ClassRegistry::init('MyModel')->find('all', array('conditions'=>array('MyModel.name LIKE'=>'%'.$name.'%'))); (the latter, chaining, example is PHP5 only)

10. Clean-up ugly HTML

It’s no secret that CakePHP often outputs some very ugly and hard to read HTML. This little trick will make it a lot cleaner.
Create a file named app_helper.php in your app’s root directory.

Next add this function to it:


function output($string) {
return parent::output($string . "\n");
}

Take a look at your HTML now… much better.

(Thanks, TommyO, for the hint)

11. How can I access session data in the view?
Very easy. You have the $session helper available.
Just try it: pr($session)
Update: if you have already written some variables to the session, then try pr($session->read());

12. I need to save data from multiple models
Don’t even think about using loops and other trickery. There is a very nice (but not well documented) method saveAll() available. It will let you save data from multiple models at once. Granted, it takes a little time to figure it out, but once you get it working it is really a time saver. Try using it with some dummy data, and make sure that your data array is properly formatted and associations are setup correctly.

13. Static pages and routes
I really don’t like having /pages/ as part of the URL for my static pages. Well, they are static… so let’s make them .html instead (at the same time we sprinkle just a little of security by obscurity mantra).
If you had links pointing to www.example.com/pages/myPage/ they should now point to www.example.com/myPage.html and add this to your routes:

Router::connect('/(.*).html', array('controller' => 'pages', 'action' => 'display'));

Sweet.

14. The fastest way to build a form in CakePHP


echo $form->create();
echo $form->inputs();
echo $form->end();

It is just a step beyond scaffolding, but really this is how easy it is to build a form in CakePHP. Just give it a go.

15. Get to know CakePHP by using Bug tracker
Update: recently CakePHP has switched to another system from Trac.

CakePHP is constantly evolving and if you are serious about developing on top of this framework it is very important to keep up with the latest and greatest and to get familiar with some of the new and upcoming features. code.cakephp.org is a great place for this:

a. You can download nightly builds from here.
b. Keep an eye on the Wiki to see current status of CakePHP project and helpful hints.
c. Use Timeline to see the latest code updates. Pick “commits” to filter the updates.
d. Take a look at the code of test files to see how a certain feature should be used
e. Submit bugs and requests for enhancements, but read this first: http://book.cakephp.org/view/759/Bugreport

Set variable in the view to be used in the layout

I’m not sure that you should really be doing this, but I recall running into one situation where I wanted to set some variable in the view and have it available in the layout. Here’s how…

In the view:
$this->viewVars['myVar'] = 'something';

In the layout:
echo $myVar;

Nate pointed out that it can be even easier with just: $this->set('myVar', 'something');
As $this->set(); works just as well in the view as in the controller.

Remember about MVC and double check if what you are doing is a correct approach to begin with.

New way to GROUP BY in CakePHP

Just recently ( end of May, 2008 ) there was an addition to CakePHP’s find method to easily build a GROUP BY in your SQL.

If you’ve been using something like $this->Product->find(‘all’… etc., etc. Now you can add a new key ‘group’ to your find method, like so:

$this->Product->find(‘all’,array(‘fields’=>array(‘Product.type’,’MIN(Product.price) as price’), ‘group’=> ‘Product.type’));

Of course you will have to upgrade to a nightly SVN core for this to work.