Paginating search results in CakePHP

This post describes my endeavors with paging search results in a recent project. I tend to be very explicit in my posts, so if you prefer you can follow the fast track and jump to the solution

A friend of mine is working on a website for a local realty agent. Since the website will display data about properties that the realtor is handling, I suggested we build it on CakePHP and get some cool features as a bonus. As a bonus he’d learn some of the CakePHP internals. After having set up the database, models, controllers and admin routing and various UI-related matters, we were faced with a very interesting problem:

How do you paginate the results of a search query?

I am sure this has been addressed many times, but I wanted to give a personal taste to the answer. So I read and thought a lot bout this. The real problem I was facing is that the application won’t know about the search query in the subsequent pages of results. This is of course unless you make use of HTTP GET for your search requests. CakePHP also provides nice SEO friendly URL and I didn’t want to give that up. What’s more, HTTP GET is not the right solution because:

Good web development practices demand that HTTP GET requests always present the same data and never alter them.

Search results change with time, so there is no guarantee that the data is the same, even for the same search query. Just imagine for a moment going back to a blog and performing a search for some article’s keywords. You will get that article in your search results, but there may be other (newer) articles containing the same term (let alone a web app where a real estate agent will be updating realties each day). This is why using HTTP GET is considered Bad Practice™ and was out of our line of action.

Since HTTP is a stateless protocol and we cannot or better, choose not to use GET parameters to store the search query, the only way for us to persist data during subsequent requests (different pages of search results) is to use the PHP Session to store the search query.

The Session

The session is a mechanism that wants to make up for HTTP’s stateless nature. A session is unique for every user or, better put, every browser that requests the web pages. Every web programming language offers tools to store, retrieve and delete data relative to a user’s (i.e. browser’s session).

Our plan is to store search query data in the session. CakePHP offers a Session Component that can be used the CakePHP-way. You can even store and retrieve arrays in the session, which does came in handy, as we will see.

The Solution

To make the long story short (or is it to late for that?) the adapted solution is the following:

  1. On first request (POST action), use Session component to store search conditions and then show the first page of search results.
  2. On subsequent requests (GET action), use Session component to retrieve search conditions and hand them over to the paginator.

Before going into the code, I’d like to shed some light into the application structure itself. The controller that’s going to handle the search requests is PropertiesController. PropertyModel is the relative Model.

The search form is simplified to include the following criteria:

  • City of property
  • Type of property
  • Room count
  • Floor count

Data coming from the POST request is stored into Session variables using dot notation. Dot notation (i.e. Properties.floor_count will allow us to retrieve an associative array from the Session (look at line 41). In lines 45-47 we manipulate the associative array retrieved from the session and pass it as a $conditions argument to the paginator (line 52).

<?php
class PropertiesController extends AppController{

	// Session is already included in your AppController
	// this is for illustration purposes only
	$uses = array( "Session", /*...*/ );

	function search(){
		// Make sure some data has been posted
		if( ! empty( $this->data ) ){
			// Delete what was previously stored in the session,
			// this is a new query
			$this->Session->delete('Property');

			// Save search criteria to session
			$city_id = $this->data[ 'Property' ][ 'city_id' ];
			if( $city_id )
				$this->Session->write('Property.city_id', $city_id);

			$type = $this->data[ 'Property' ][ 'type_id' ];
			if ( $type != 0 )
				$this->Session->write( 'Property.type_id', $type );

			$floor_count = $this->data[ 'Property' ][ 'floor_count' ];
			if( $floor_count )
				$this->Session->write( 'Property.floor_count', $floor_count );

			$room_count = $this->data[ 'Property' ][ 'room_count' ];
			if ( $room_count )
				$this->Session->write( 'Property.room_count', $room_count );
		}

		if(  ! $this->Session->check( 'Property' ) ){
			// If no search conditions have been provided
			// set a flash message and redirect to homepage
			$this->Session->setFlash('Provide some search criteria!', true);
			$this->redirect(array('controller'=>'pages', 'action'=>'home'));
		}

		// Now, we are sure to have some search criteria
		$conditions = $this->Session->read( 'Property' );

		// fill up conditions_array
		$conditions_array = array();
		foreach( $conditions as $key => $val ){
			// dot notation for: Model.attribute
			$conditions_array[ 'Property.' . $key ] = $val;
		}

		// Now we provide the search conditions and call the paginator
		$this->paginate  = array(
			'conditions' => $conditions_array,
			'limit' => '2'
		);
		$properties = $this->paginate('Property');
		$this->set(compact('properties'));
		$this->render('search', 'public');
	}
}

After having gone through all this, it seems pretty straightforward. There is one catch: for the purposes of this post, I have not included any additional data sanitizing apart from that provided by CakePHP’s ORM. If you intend to use this code in any real web applications, please read through Data Sanitizing chapter of the CakePHP Book to understand what is sanitized and what’s not.

As a final note, there are also free benefits coming out of this solution, like: Search History and/or Search suggestions for the user, but another post is needed to talk about that.


Cakephp on Site5 subdomains

I have developed different web applications using CakePHP and I’m liking the framework so far. Especially it’s ability to automatically generate (or better bake) CRUD operations for every defined model. Recently I was asked to develop two different applications that would live on two subdomains of the same site5 hosted domain. As I had little


Hello and welcome!

This is an attempt to keep track of solutions to everyday programming problems I come across in my daily work. Development is a hobby of mine, so expect to find various guides, links and roundups. Besides Java and C, I am currently exploring CakePHP – a web application framework in PHP. We have to adapt