Saturday, April 18, 2009

CakePHP 1.2 Versatile Model find('list')

Have you wanted to generate a list of options for your select but wanted the display field to be generated dynamically instead of using cakePHP find('list')?

This is the case.


MODEL
User hasOne Info

User
id
username
password
manager_id
Info
id
first_name
last_name
email
sex


I wanted to generate a list of all Users displaying the first_name, last_name and sex. And I wanted the indexes to be generated with User.id and the display field with this format "Info.last_name, Info.first_name (Info.sex)". I have developed a function to override cakePHP $model->find.

In your app_model.php, you include this function:

class AppModel extends Model {

function find($type, $options = array()) {
switch ($type) {
case 'superlist':
if(!isset($options['format'])) {
$options['format'] = '';
for ($i=0; $i $options['format'] .= "%$i ";
}
$options['format'] = trim($options['format']);
}

if (isset($options['index'])){
preg_match('/(.*?)\.(.*)/', $options['index'], $match);
$index_alias = $match[1];
$index_field = $match[2];
}else{
$index_alias = $this->alias;
$index_field = 'id';
}

$options['fields'] = array_merge(array("{$index_alias}.{$index_field}"), $options['fields']);
$list = $this->find('all', $options);

$result = array();
foreach ($list as $row){
$result[$row[$index_alias][$index_field]] = $options['format'];

foreach ($options['fields'] as $index=>$field){
preg_match('/(.*?)\.(.*)/', $field, $match);
$field_alias = $match[1];
$field_field = $match[2];

$result[$row[$index_alias][$index_field]] =
str_replace('%'.$index, $row[$field_alias][$field_field],
$result[$row[$index_alias][$index_field]]);
}
}

return $result;
break;

default:
return parent::find($type, $options);
break;
}
}

}


This is how you call the generate list function in your controller action.

// if you have not declared hasOne Info in User model, this is needed in order for User model generate Info model with it and you can pass condition on Info fields
$this->User->bindModel(
array(
'hasOne' => array('Info')
)
);

$users = $this->User->find('superlist',
array(
'format' => '%3, %2 (%1)',
'index' => 'User.id', // --> this is the default so you may not pass this option
'fields' =>
array(
'Info.sex', // --> %1
'Info.first_name', // --> %2
'Info.last_name', // --> %3
),
'order' => "Info.first_name, Info.last_name", // -- if you want to sort the option
'condition' => "User.manager_id=2 AND Info.lastname='Smith'", // --> if you need some filtering
)
);
$this->set(compact('users'));



$users array will contain the following structure:



array(
'User.id1' => 'Info.last_name1, Info.first_name1 (Info.sex1)',
'User.id2' => 'Info.last_name2, Info.first_name2 (Info.sex2)',
'User.id3' => 'Info.last_name3, Info.first_name3 (Info.sex3)',
.
.
.
'User.idN' => 'Info.last_nameN, Info.first_nameN (Info.sexN)',
)


Hope this post can help you in your future cakePHP 1.2 development.

Thanks to http://teknoid.wordpress.com/2008/09/04/findlist-with-three-or-combined-fields/ for giving me inspiration to develop this function.

6 comments:

Livetek Software said...

Thanks to share you valuable knowledge.

Kerr said...

This was a great solution, thanks for sharing! This kind of thing is such a common use case that there's no excuse for it not to have been in core from day 1.

Chris IP said...

This is really useful as at the momment building your datasets in the controller seems a little un-mvc.

Doing it the way you explain in your article is a much better method.

Ozting said...

@all
Glad this code helps you.

Outsourcing Partners said...

Great read Owen. Thanks for sharing!
PHP Programmer

Ayisha said...

This is knowledgeable site.