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.