Tag: cakephp forms

Improved form handling in CakePHP 1.3

Here is a typical, simple form done with cake’s form helpers:

[code language="php"]
echo $this->Form->create('Article', array('action' => 'test'));

echo $this->Form->input('Article.title');
echo $this->Form->input('Article.body');
echo $this->Form->input('Article.user_id', array('type' => 'hidden'));

echo $this->Form->end('Add Aricle with Tags and Comment');
[/cc]

Which outputs the following HTML:

[code language="html"]




[/cc]

This is fine and all, but one thing worth noting is that default behavior is wrapping each element in a div, and in some cases this may not be desirable. Either a legacy CSS or a front-end developer preference, might require the form to be structured (wrapped) differently.

Let's see how we can set some defaults for our form with CakePHP 1.3., while simplifying the code a little at the same time by using the inputs() method...

[code language="php"]
echo $this->Form->create('Article', array('action' => 'test',
'inputDefaults' => array(
'div' => array('tag' => 'p'),
'before' => '-- goes before the label --',
'after' => '-- goes after the input --')));

echo $this->Form->inputs(array('Article.title',
'Article.body',
'Article.user_id' => array('type' => 'hidden')),
array('legend' => 'Article submit'));

echo $this->Form->end('Add Aricle');
[/cc]

Which produces:

[code language="php"]


New Article

-- goes before the label --


-- goes after the input --

-- goes before the label --


-- goes after the input --


[/cc]

Of course for a simple form this may not be a very significant improvement, but having the ability to set defaults for the entire form and relying on some automagic for the rest, certainly makes one's life easier.

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:

$this->load(‘MyModel’);
As soon as this line executed you’ll have an instance of MyModel available in $this->MyModel (pr($this->MyModel); to see your freshly loaded object.

Another option is to use this approach:
$MyModel = ClassRegistry::init(‘MyModel’);

Which is nice, because it allows you to do things like this:

$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

Use app controller to keep static list data

In the past I’ve often kept some data such as a list of states or countries in the DB and used that to populate my forms. After some random thinking I’ve found no good reason to utilize the DB for such a thing. This kind of a list is not very long and the number and names of countries and states is not likely to change very often, so I’ve decided to just move it to an array in my app controller.

So, for example, I’ve created a method called getStateList(), which returns an array of all the states (in the USA, that is).

Now if I need to prepare (set) this array for the view variable. I simply do $this->set(‘stateList’, $this->getStateList()); in my controller. Now I have a $stateList variable available in the view.

To build a drop-down box:
$form->select(‘SomeModel.state’, array($stateList), null, null, false);

Nice and clean and no need to be bothered with DB calls.

I would only recommend this approach if you know that your list data isn’t likely to change, not very large (for example I wouldn’t use this for 42,000+ US zip codes) and has to be accessed by multiple form select elements in various views.