Search Forms in CakePHP

In this CakePHP tutorial I would like to show you how I handle search forms, while preserving pagination.

The basic principal is to read the posted variables, and redirect the user to a page with the appropriate filters in the URL.

Add the Search Form

Lets add a search form on the index page to search through the records.

index.ctp
<?php echo $form->create('Post',array('action'=>'search'));?>
        <fieldset>
                <legend><?php __('Post Search');?></legend>
        <?php
                echo $form->input('Search.keywords');
                echo $form->input('Search.id');
                echo $form->input('Search.name',array('after'=>__('wildcard is *',true)));
                echo $form->input('Search.body',array('after'=>__('wildcard is *',true)));
                echo $form->input('Search.active',array('empty'=>__('Any',true),'options'=>array(0=>__('Inactive',true),1=>__('Active',true))));
                echo $form->input('Search.category_id');
                echo $form->input('Search.tag');
                echo $form->input('Search.tag_id');
                echo $form->submit('Search');
        ?>
        </fieldset>
<?php echo $form->end();?>

Controller Method

The search action will handle reading the posted variables, and redirecting back to the index url.

The index action will handle setting up the pagination options.

controllers/posts_controller.php
class PostsController extends AppController {
        var $name = 'Posts';

        function search() {
                // the page we will redirect to
                $url['action'] = 'index';
               
                // build a URL will all the search elements in it
                // the resulting URL will be
                // example.com/cake/posts/index/Search.keywords:mykeyword/Search.tag_id:3
                foreach ($this->data as $k=>$v){
                        foreach ($this->data as $kk=>$vv){
                                $url[$kk.'.'.$k]=$v;
                        }
                }

                // redirect the user to the url
                $this->redirect($url, null, true);
        }
        function index() {
                // the elements from the url we set above are read  
                // automagically by cake into $this->passedArgs[]
                // eg:
                // $passedArgs['Search.keywords'] = mykeyword
                // $passedArgs['Search.tag_id'] = 3

                // required if you are using Containable
                // requires Post to have the Containable behaviour
                //$contain = array();  

                // we want to set a title containing all of the
                // search criteria used (not required)         
                $title = array();
               
                //
                // filter by id
                //
                if(isset($this->passedArgs['id'])) {

                        // set the conditions
                        $this->paginate['conditions'][]['Post.id'] = $this->passedArgs['id'];

                        // set the Search data, so the form remembers the option
                        $this->data['Search']['id'] = $this->passedArgs['id'];

                        // set the Page Title (not required)
                        $title[] = __('ID',true).': '.$this->passedArgs['id'];
                }

                //
                // filter by keywords
                //
                if(isset($this->passedArgs['Search.keywords'])) {
                        $keywords = $this->passedArgs['Search.keywords'];
                        $this->paginate['conditions'][] = array(
                                'OR' => array(
                                        'Post.name LIKE' => "%$keywords%",
                                        'Post.body LIKE' => "%$keywords%",
                                )
                        );
                        $this->data['Search']['keywords'] = $keywords;
                        $title[] = __('Keywords',true).': '.$keywords;
                }

                //
                // filter by name
                //
                if(isset($this->passedArgs['Search.name'])) {
                        $this->paginate['conditions'][]['Post.name LIKE'] = str_replace('*','%',$this->passedArgs['Search.name']);
                        $this->data['Search']['name'] = $this->passedArgs['Search.name'];
                        $title[] = __('Name',true).': '.$this->passedArgs['Search.name'];
                }

                //
                // filter by body
                //
                if(isset($this->passedArgs['Search.body'])) {
                        $this->paginate['conditions'][]['Post.body LIKE'] = str_replace('*','%',$this->passedArgs['Search.body']);
                        $this->data['Search']['body'] = $this->passedArgs['Search.body'];
                        $title[] = __('Body',true).': '.$this->passedArgs['Search.body'];
                }

                //
                // filter by active
                //
                if(isset($this->passedArgs['Search.active'])) {
                        $this->paginate['conditions'][]['Post.active'] = ($this->passedArgs['Search.active'])?1:0;
                        $this->data['Search']['active'] = $this->passedArgs['Search.active'];
                        $title[] = ($this->passedArgs['Search.active']) ? __('Active Posts',true) : __('Inactive Posts',true);
                }
               
                //
                // filter by category_id, including all children
                //
                if (isset($this->passedArgs['Search.category_id'])) {

                        // get all children
                        $category_ids = array($this->passedArgs['Search.category_id']);
                        $children = $this->Post->Category->children($this->passedArgs['Search.category_id']);
                        foreach ($children as $child) {
                                $category_ids[] = $child['Category']['id'];
                        }

                        // set the conditions - SELECT ... WHERE field IN(1,2,3)
                        $this->paginate['conditions'][]['Post.category_id'] = $category_ids;

                        // set the Search data, so the form remembers the option
                        $this->data['Search']['category_id'] = $this->passedArgs['Search.category_id'];

                        // set the Page Title to the Category Name
                        $title[] = __('Category',true).': '.$this->Post->Category->field('name',array('Category.id'=>$this->passedArgs['Search.category_id']));
                }
               
                //
                // filter by tag (habtm) - name or id
                //
                if (isset($this->passedArgs['Search.tag']) || isset($this->passedArgs['Search.tag_id'])) {

                        // if we were given the tag name, get the id
                        if (isset($this->passedArgs['Search.tag'])) {
                                $this->passedArgs['Search.tag_id'] = $this->Post->Tag->field('id',array('Tag.name'=>$this->passedArgs['Search.tag']));
                        }

                        // bind the model as a hasOne so we can use the rows as conditions
                        $this->Post->bindModel(array(
                                'hasOne' => array(
                                        'PostTag' => array(
                                                'className' => 'PostTag',
                                                'foreign_key' => 'post_id',
                                        ),
                                ),
                        ),false);
                        //$contain[] = 'PostTag'; // required if you are using Containable
                       
                        // set the conditions
                        $this->paginate['conditions'][]['PostTag.tag_id'] = $this->passedArgs['Search.tag_id'];

                        // set the Search data, so the form remembers the option
                        $this->data['Search']['tag_id'] = $this->passedArgs['Search.tag_id'];

                        // set the Page Title to the Tag Name
                        $title[] = __('Tag',true).': '.$this->Post->Tag->field('name',array('Tag.id'=>$this->passedArgs['Search.tag_id']));
                }

                // get posts           
                //$this->Post->contain($contain); // required if you are using Containable
                //$this->paginate['reset']=false; // required if you are using Containable
                $posts = $this->paginate();
               
                // set the category path of each post (not required, just an example)
                // requires Category to have the Tree behaviour
                //
                // you can use this to add anything you want to the $posts array
                // before it is sent to the view
                foreach($posts as $k=>$post) {
                        $posts[$k]['CategoryPath'] = $this->Post->Category->getPath($post['Post']['category_id']);
                        $posts[$k]['CategoryPath'] = $posts[$k]['CategoryPath']?$posts[$k]['CategoryPath']:array();
                }

                // set title
                $title = implode(' | ',$title);
                $title = (isset($title)&&$title)?$title:'All Videos';
               
                // set related data
                $tags = $this->Post->Tag->find('list');
                $this->set(compact('posts','tags','title'));
        }
       

}

Getting Pagination to Remember Search Options

A quick trick to get pagination to remember the search options.

views/posts/index.php
<?php $paginator->options(array('url' => $this->passedArgs)); ?>

Hiding the Search Form

Here is some JavaScript that will allow you to hide the search form when it's not needed.

This Javascript requires jQuery.

views/posts/index.php
<?php echo $html->link(__('Search', true), 'javascript:void(0)', array('class'=>'search-toggle')); ?>
$(document).ready(function(){
        // toggle the search form
        $('.search-toggle').click(function(){
                $('#PostSearchForm').toggle();
        });
        $('#PostSearchForm').hide();
});

The Final Product

Here is an example of how the form will look.


Reply

  • Allowed HTML tags: <b> <br> <p> <a> <strong> <cite> <em> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
  • You may use [img:xx] tags to display uploaded files or images inline.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <css>, <diff>, <drupal5>, <html>, <javascript>, <php>. Beside the tag style "<foo>" it is also possible to use "[foo]". PHP source code can also be enclosed in <?php ... ?> or <% ... %>.

More information about formatting options