CakePHP: Auto populating foreign key dropdown fields

Richard's picture

One of the neet features of CakePHP's scaffolding is that it automatically populates your foreign key (belongsTo, hasAndBelongsToMany) fields in your forms.

Lets take a look at a simple example to illustrate this:

app/models/article.php:

class Article extends AppModel {
	var $hasAndBelongsToMany = array(
		"Tag"
	);
}

app/models/tag.php:

class Tag extends AppModel {
 
	var $hasAndBelongsToMany = array(
		"Article"
	);
}

app/controllers/article_controller.php:

class ArticleController extends AppController {
 
	var $scaffold;
}

app/controllers/tags_controller.php:

class TagController extends AppController {
 
	var $scaffold;
}

Now, when you visit http://webroot/articles/add you'll see that CakePHP has helpfully changed the foreign key field for Tags to a multi-select list of all the Tags:

Screenshot of CakePHP foreign key lookup

Once you switch off scaffolding and start creating your own add/edit methods you'll loose this handy feature. You have to manually add the following to the add/edit actions to re-enable this feature:

function add() {
 
	// usual add code
 
	// populate the foreign key lookups
	$this->set("Tags", $this->Article->Tag->find("list");
 
}

That can be a bit of a bind to remember to do that for each foreign key and for each view that needs this functionality.

Here's an automatic method for retaining this feature in your own hand built methods.

Add the following code to your AppController (so the functionality is inherited for all your controllers):

/**
 * Populates the view with variables required for foreign key fields
 * 
 * @param $models array	list of models to fetch lookups for
 * 						if empty, the list automatically populated with the associated models
 * 
 * @return none			view vars are set for each associated model
 */
function _populateLookups($models = array()) {
 
	if (empty($models)) {
 
		// build a list of all associated models:
 
		// create a reference to the Controllers root model
		// this is the first item in the controllers $uses array
		// or the default if $uses is empty
		$rootModel = $this->{$this->modelClass};
 
		// build list of belongsTo Models
		foreach($rootModel->belongsTo as $model=>$attr) {
 
			$models[] = $model;
 
		}
 
		// build list of hasAndBelongsToMany Models
		foreach($rootModel->hasAndBelongsToMany as $model=>$attr) {
 
			$models[] = $model;
 
		}
 
	}
 
	// populate the view vars with the lookup lists for each associated model
	foreach($models as $model) {
 
		// calculate the name of the variable to populate based on the model name
		$name = Inflector::variable(Inflector::pluralize($model)); 
 
		// populate the view var
		$this->set($name, $rootModel->{$model}->find("list"));
 
	}
 
}

What this does is to automate the process off populating those foreign key view vars. If you don't pass in a list of models it will dig out the associated models automatically.

Now we need to run this function every time we display an add or edit form. Add the following code to your AppController. If you already have a beforeRender() function you can append this code to end.

/**
 * Runs before the view is rendered
 * @see cake/libs/controller/Controller#beforeRender()
 */
function beforeRender() {
 
	switch($this->action) {
 
		case "add":
		case "edit":
 
			$this->_populateLookups();
			break;
 
	}
 
}

What this does is to check which action we are performing and then calls _populateLookups() if its an add or edit action.

Comments

Richard's picture

Source code markup

I've just noticed the source code markup is being double escaped.

Lines such as:

$this->set("Tags", $this->Article->Tag->find("list");

Should read:

$this->set("Tags", $this->Article->Tag->find("list");

I'm working on a fix for this :-)

Richard's picture

Issue is fixed

Added the following three lines to sites/all/modules/geshifilter/geshi/geshi.php:

$code = str_replace(">", ">", $code);
$code = str_replace("&lt;", "<", $code);
$code = str_replace("&amp;", "&", $code);

as recommended in the comments here: http://drupal.org/node/269937

Anonymous's picture

It's not working for me.

It's not working for me. Instead of showing the labels, it's showing the ids of the fks. Can anyone help me?

Anonymous's picture

$displayField

You need to set the $displayField var.

http://book.cakephp.org/view/438/displayField

Anonymous's picture

stupid question

Hi, I hope can you help me...

I have setting trouble's with the constraint's of model's and tables, because they don't respect the foreign key when I add something wrong, like a null ID.
I read this manual http://book.cakephp.org/view/78/Associations-Linking-Models-Together ,
but I don't understand If I really need to add the constraint's in the database...¿?

For example, I have this...

User have many post

Tables.sql
users
-id (PK)
-name
-created

posts
-id (PK)
-name
-created
-user_id (int data type, not foreign key constraint in database)

Models

class User extends AppModel {
var $name = 'User';
var $hasMany = array('Post'=> array('className' => 'Post','foreignKey' => 'user_id'));
}
class Post extends AppModel {
var $name = 'User';
var $belongsTo = array('User'=> array('className' => 'User','foreignKey' => 'user_id'));
}

For ending this doubt...In my form right now, I can add a post without User id (ovbiusly I can set it)....

posts
id->1
name->This is a super post
-created->the date...
-user_id->0 ...THIS IS MY PROBLEM!!!!! :(

That's it's the entire doubt...
regards, and great post.

Anonymous's picture

thanks

awesome, thanks so much for this post. I've been using cakephp a few months, but there are some details like that that I really wasn't able to glean from reading all the documentation. I just wish I knew all the "tricks" like this!

I only had to read part of your article and "boom" I was able to populate my foreign key dropdown field.

Anonymous's picture

Thank you so much .. Reading

Thank you so much ..
Reading your blog has saved me from the redundant changes I had been making to have the foreign key mapping..

You rock !!!

Anonymous's picture

Perfect!

great post. works like a charm. thanks for sharing!

Anonymous's picture

simple and very effective

simple and very effective code. great job dude.

thanks for sharing!

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Post new comment

The content of this field is kept private and will not be shown publicly. If you have a Gravatar account, used to display your avatar.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <c>, <cpp>, <drupal5>, <drupal6>, <java>, <javascript>, <php>, <python>, <ruby>. Beside the tag style "<foo>" it is also possible to use "[foo]".

More information about formatting options