Bulk record update in Cakephp

I’m sure there are many ways of doing this, but recently I finally got round to figuring out how to update multiple records at a time.  When I started out all I wanted was to give all the selected records the same value for one database field, so the first thing we need to know is which records need to be updated and which field. To do this all you need is a list with your records (I’m using portfolios) inside a form with some check boxes and a select box with a list of actions.
<?php echo $form->create('Portfolio',array('action'=>'process')); ?>

<?php echo $exPaginator->sort(__('Name',true),'name');?>
<?php echo $exPaginator->sort(__('Type',true),'type');?>
<?php echo $exPaginator->sort(__('Publish',true),'publish');?>
<?php echo $exPaginator->sort(__('Front Page',true),'front');?>
<?php echo $exPaginator->sort(__('Featured',true),'featured');?>
<?php __('Select') ?>

<?php
foreach ($portfolios as $portfolio):
?>

<?php echo $html->link($portfolio['Portfolio']['name'], array('action'=>'view', $portfolio['Portfolio']['id'])) ?>
"><?php echo $general->ajaxTC($portfolio['Portfolio']['publish'],$portfolio['Portfolio']['id'],'publish'); ?>
"><?php echo $general->ajaxTC($portfolio['Portfolio']['front'],$portfolio['Portfolio']['id'],'front'); ?>
"><?php echo $general->ajaxTC($portfolio['Portfolio']['featured'],$portfolio['Portfolio']['id'],'featured'); ?>
<?php echo $form->checkbox('Portfolio.id.'.$portfolio['Portfolio']['id']); ?>

<?php
endforeach;
?>

<?php
echo $form->input('action', array('options'=>array('Publish','Front Page','Featured','Delete')));
echo $form->end('Go -->');
?>

Note: The $general->ajaxTC is from a custom helper I use to show an image of a tick or a cross and using YUI changing the value using ajax. $exPaginator->sort is also a custom helper that I have used to extend the standard paginator.
The above code will produce a form which when submitted will send the id of the record and it’s checked value (1 or 0) and the value of the action( in this case 0 = Publish, 1 = Front Page, 2 = Featured and 3 = Delete). The important bits are, $form->checkbox(Portfolio.id.’.$portfolio['Portfolio']['id']) which creates the checkbox for each record and $form->input(action’, array(options’=>array(Publish’,'Front Page’,'Featured’,'Delete’))) which creates the selectbox of actions.
To make use of this data, a new function is needed in the controller. I’ve called it “process”. The first thing your new function needs to do is create an array of ids that were checked, because cakephp will add a hidden field for every check box (see the cook book for details) we need every id that has a value of “1″.
function admin_process(){
/**
* Process form input to delete, publish etc. selected portfolios
*/
if (!empty($this->data)) {
foreach($this->data['Portfolio']['id'] as $id => $value){
if($value == 1){
$ids[] = $id;
}
}
}
}
Now $ids will only be those that were checked. To make use of this array I use a switch statement to cycle through each of the actions and use either updateAll or deleteAll. The code is pretty straight forward in each case, if you are not sure refer to the cook book or if you get really stuck add a comment below.
function admin_process(){
/**
* Process form input to delete, publish etc. selected portfolios
* TODO: Confirm before delete.
*/
$this->autoRender = false;
if (!empty($this->data)) {
foreach($this->data['Portfolio']['id'] as $id => $value){
if($value == 1){
$ids[] = $id;
}
}
switch($this->data['Portfolio']['action']){
case 0:
//Publish
if($this->Portfolio->updateAll(array('Portfolio.publish'=>1), array('Portfolio.id'=>$ids))){
$this->Session->setFlash(__('Portfolios published', true));
$this->redirect(array('action'=>'index'));
}
break;
case 1:
//Front Page
if($this->Portfolio->updateAll(array('Portfolio.front'=>1), array('Portfolio.id'=>$ids))){
$this->Session->setFlash(__('Portfolios published to the Front Page', true));
$this->redirect(array('action'=>'index'));
}
break;
case 2:
//Featured
if($this->Portfolio->updateAll(array('Portfolio.featured'=>1), array('Portfolio.id'=>$ids))){
$this->Session->setFlash(__('Portfolios published to featured area', true));
$this->redirect(array('action'=>'index'));
}
break;
case 3:
//Delete
if($this->Portfolio->deleteAll(array('Portfolio.id'=>$ids))){
$this->Session->setFlash(__('Portfolios deleted', true));
$this->redirect(array('action'=>'index'));
}
break;
default:
//D'oh something went wrong!!
$this->Session->setFlash(__('Something went wrong, please try again.', true));
$this->redirect(array('action'=>'index'));
}
}

}
As this action does not need a view file, because it just redirects back to the index page I have added $this->autoRender = false; which will prevent the normal cakephp action of looking for a view file.
I added the default action at the end in case anything goes wrong or if someone tries doing something they shouldn’t be. This example could be extended to as many actions as required, just remember that if your array of options is not a value=>key array, the values start from 0 (zero) for the first, 1 for the second and so on.
Happy baking, comments and retweets are always welcome.