<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Notes to self</title>
	<atom:link href="http://oerd.cukalla.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://oerd.cukalla.com</link>
	<description>(intelligent tagline here)</description>
	<lastBuildDate>Sat, 09 Apr 2011 18:52:43 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Paginating search results in CakePHP</title>
		<link>http://oerd.cukalla.com/cakephp/paginating-search-results-in-cakephp/</link>
		<comments>http://oerd.cukalla.com/cakephp/paginating-search-results-in-cakephp/#comments</comments>
		<pubDate>Tue, 01 Jun 2010 17:52:41 +0000</pubDate>
		<dc:creator>oerd</dc:creator>
				<category><![CDATA[cakephp]]></category>
		<category><![CDATA[paginator]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[search]]></category>

		<guid isPermaLink="false">http://oerd.cukalla.com/?p=64</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p><em>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 <a title="My solution to paging search results with CakePHP" href="#solution">solution</a></em></p>
<p>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 <a title="CakePHP Project" href="http://www.cakephp.org">CakePHP</a> and get some cool features as a bonus. As a bonus he&#8217;d learn some of the CakePHP internals. After having set up the database, models, controllers and <a href="http://book.cakephp.org/view/46/Routes-Configuration#Prefix-Routing-544">admin routing</a> and various UI-related matters, we were faced with a very interesting problem:</p>
<h4>How do you paginate the results of a search query?</h4>
<p>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&#8217;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&#8217;t want to give that up. What&#8217;s more, HTTP GET is not the right solution because:</p>
<blockquote><p>Good web development practices demand that HTTP GET requests always present the same data and never alter them.</p></blockquote>
<p>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&#8217;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.</p>
<p>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.</p>
<h4>The Session</h4>
<p>The session is a mechanism that wants to make up for HTTP&#8217;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&#8217;s (i.e. browser&#8217;s session).</p>
<p>Our plan is to store search query data in the session. CakePHP offers a <code>Session Component</code> 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.</p>
<h4><a id="solution" name="solution"></a>The Solution</h4>
<p>To make the long story short (or is it to late for that?) the adapted solution is the following:</p>
<ol>
<li>On first request (POST action), use Session component to store search conditions and then show the first page of search results.</li>
<li>On subsequent requests (GET action), use Session component to retrieve search conditions and hand them over to the paginator.</li>
</ol>
<p>Before going into the code, I&#8217;d like to shed some light into the application structure itself. The controller that&#8217;s going to handle the search requests is <code>PropertiesController</code>. <code>PropertyModel</code> is the relative Model.</p>
<p>The search form is simplified to include the following criteria:</p>
<ul>
<li>City of property</li>
<li>Type of property</li>
<li>Room count</li>
<li>Floor count</li>
</ul>
<p>Data coming from the POST request is stored into Session variables using dot notation. Dot notation (i.e. <code>Properties.floor_count</code> 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 <code>$conditions</code> argument to the paginator (line 52).</p>
<pre class="brush: php; title: ; notranslate">
&lt;?php
class PropertiesController extends AppController{

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

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

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

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

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

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

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

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

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

		// Now we provide the search conditions and call the paginator
		$this-&gt;paginate  = array(
			'conditions' =&gt; $conditions_array,
			'limit' =&gt; '2'
		);
		$properties = $this-&gt;paginate('Property');
		$this-&gt;set(compact('properties'));
		$this-&gt;render('search', 'public');
	}
}
</pre>
<p>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&#8217;s ORM. If you intend to use this code in any real web applications, please read through <a title="Data Sanitization - CakePHP Book" href="http://book.cakephp.org/complete/153/Data-Sanitization">Data Sanitizing</a> chapter of the CakePHP Book to understand what is sanitized and what&#8217;s not.</p>
<p>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.</p>
]]></content:encoded>
			<wfw:commentRss>http://oerd.cukalla.com/cakephp/paginating-search-results-in-cakephp/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>Cakephp on Site5 subdomains</title>
		<link>http://oerd.cukalla.com/php/cakephp-on-site5-subdomains/</link>
		<comments>http://oerd.cukalla.com/php/cakephp-on-site5-subdomains/#comments</comments>
		<pubDate>Tue, 16 Jun 2009 09:47:42 +0000</pubDate>
		<dc:creator>oerd</dc:creator>
				<category><![CDATA[php]]></category>
		<category><![CDATA[cakephp]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[site5]]></category>

		<guid isPermaLink="false">http://oerd.cukalla.com/?p=11</guid>
		<description><![CDATA[I have developed different web applications using CakePHP and I&#8217;m liking the framework so far. Especially it&#8217;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 [...]]]></description>
			<content:encoded><![CDATA[<p>I have developed different web applications using <a href="http://www.cakephp.org">CakePHP</a> and I&#8217;m liking the framework so far. Especially it&#8217;s ability to automatically generate (or better <em>bake</em>) <acronym title="Create Request Update Delete">CRUD</acronym> operations for every defined model. Recently I was asked to develop two different applications that would live on two subdomains of the same <a href="http://www.site5.com">site5</a> hosted domain.</p>
<p>As I had little experience with <a href="http://www.site5.com">site5</a> hosting at the time, I went on to search the web (as well as their user forums) for guides on working with <a href="http://www.cakephp.org">CakePHP</a>. As strange as it may seem, different users were complaining that they were not able to install CakePHP on a site5 subdomain. </p>
<p>I can only assume here, but it may be related with the default cPanel way to add a subdomain which is to just add a redirect to <tt>~/public_html/subdomain</tt> for <tt>http://subdomain.mydomain.com</tt>, which is the same thing as <tt>http://mydomain.com/subdomain</tt>. Now, this is not the way I configure my subdomains, I hate cluttering of the <tt>public_html</tt> directory, so I did not try this at all. Instead I create a <tt>webdir</tt> directory on my home and host all my add-on domains and subdomains under that directory, explicitly telling cPanel where the root location of every subdomain is.</p>
<h4>The Fast-track</h4>
<ul>
<li>
	On Site5 cPanel:</p>
<ul>
<li>create subdomain</li>
<li>create DB for cakephp (remember user/pass)</li>
</ul>
</li>
<li>
	On Site5 shell</p>
<ul>
<li>Checkout CakePHP from https://trac.cakephp.org/</li>
<li>Move <tt>app</tt> directory to <tt>webdir/cake</tt></li>
<li>Move <tt>.htaccess</tt> file to <tt>webdir/cake</tt></li>
<li>fix <tt>webroot/index.php</tt> so it knows where <tt>cake</tt> and your <tt>app</tt> directories are located</li>
</ul>
</li>
</ul>
<h4>Step-by-step guide</h4>
<p>First things first, log in to cPanel for your site5 account and go to <em>Subdomains</em>. Create a subdomain:</p>
<pre class="brush: plain; title: ; notranslate">
Subdomain: cake
Directory Root: web/cake
</pre>
<p>This will create a new directory <tt>~/web/cake</tt> on your site5 home and configure the Apache web server to serve files from this directory for every <tt>http://cake.yourdomain.com</tt> request. Keep in mind that <tt>~/web/cake</tt> will be the <em>working directory</em> for your CakePHP application.</p>
<p>The next step is to create a database (MySQL in this case) for CakePHP and configure a user for that database. Return to the &#8220;Home&#8221; view of your cPanel administration interface and then go to &#8220;MySQL database wizard&#8221;. Enter</p>
<pre>
New Database: cakedb
</pre>
<p>You can also take a look at my screenshot for <a href="http://oerd.cukalla.com/blog/wp-content/uploads/2009/06/cakephp_cpanel_db_name.jpeg">cPanel DB name selection</a> and then click Next. On the next screen enter</p>
<pre>
Username: cakeusr
Password: cakepass
Password (Again): cakepass
</pre>
<p>Again there is a <a href="http://oerd.cukalla.com/blog/wp-content/uploads/2009/06/cakephp_cpanel_user_pass.jpeg">screenshot for cPanel DB username/password selection</a>. </p>
<p>Before clicking &#8220;Next Step&#8221; remember that this is a <em>guide</em>, you should replace the default values that I am providing with proper username and password values. Choose a strong password, or use the &#8220;Generate Password&#8221; button to generate a strong password. This is crucial to the security of your web app! After having considered this, click on &#8220;Next Step&#8221;. </p>
<p>On the following screen select all that apply or, if you want to give <tt>cakeusr</tt> full access to the database click on &#8220;ALL PRIVILEGES&#8221;. Here is another screenshot for <a href="http://oerd.cukalla.com/blog/wp-content/uploads/2009/06/cakephp_cpanel_privileges.jpeg">cPanel DB privileges selection</a>, where I have given all privileges to the database user. When you are done click on &#8216;Next Step&#8217;.</p>
<p>Ta-Da! We&#8217;re done with the cPanel, you will see a screen like:</p>
<div style="text-align:center;"><img src="http://oerd.cukalla.com/blog/wp-content/uploads/2009/06/cakephp_cpanel_ok.jpeg" alt="cakephp_cpanel_ok.jpeg" border="0" width="520" height="239" /></div>
<p>Now log in to the site5 console via your <acronym title="Secure Shell">SSH</acronym> client of choice. I simply use the terminal on Unix like systems, but if you are using Windows, <a href="http://www.chiark.greenend.org.uk/~sgtatham/putty/">PuTTY</a> seems to be the preferred choice.</p>
<p>Once you are logged in your account you should get a copy of CakePHP. I tend to <em>check out</em> a copy from their <acronym title="Subversion">SVN</acronym> repository, because it allows simple updating. If you prefer to just download CakePHP, you can get a copy from the <a href="http://www.cakephp.org">CakePHP website</a>.</p>
<p>For the subversion checkout:</p>
<pre name="code">
mkdir -p ~/lib/php/ #  create a directory for php libraries
cd ~/lib/php/
svn co https://svn.cakephp.org/repo/trunk/cake/1.2.x.x cake
</pre>
<p>With this we are creating a a directory for php libraries where we are also storing the CakePHP framework checked out from the subversion repository. If you downloaded the zip distribution, unzip it and store it on <tt>~/lib/php/</tt> to follow with this guide.</p>
<p>A CakePHP application is served from the <tt>app</tt> directory, so we need to put that particular directory (and the <tt>.htaccess</tt> file) under the webroot of our subdomain (<tt>~/web/cake</tt>) so it can be served by Apache.</p>
<pre>
mv ~/lib/php/cake/app ~/web/cake/
mv ~/lib/php/cake/.htaccess ~/web/cake/
</pre>
<p>Now, all we need to do is tell the CakePHP application where the cake distribution is. To do that we have to edit the <tt>~/web/cake/app/webroot/index.php</tt> file.</p>
<pre>
cd ~/web/cake/app/webroot
vim index.php
</pre>
<p>Here is my <tt>index.php</tt> file, don&#8217;t forget to change the 3rd line:</p>
<pre class="brush: php; title: ; notranslate">
&amp;lt;?php
//TODO: change the following to your Site5 user
$site5user = 'YOUR_SITE5_USERNAME';

/**
 * Use the DS to separate the directories in other defines
 */
	if (!defined('DS')) {
		define('DS', DIRECTORY_SEPARATOR);
	}
/**
 * These defines should only be edited if you have cake installed in
 * a directory layout other than the way it is distributed.
 * When using custom settings be sure to use the DS and do not add a trailing DS.
 */

/**
 * The full path to the directory which holds &quot;app&quot;, WITHOUT a trailing DS.
 */
	if (!defined('ROOT')) {
		define('ROOT', DS.'home'.DS.$site5user.DS.'web'.DS.'cake');
	}
/**
 * The actual directory name for the &quot;app&quot;.
 */
	if (!defined('APP_DIR')) {
		define('APP_DIR', 'app');
	}
/**
 * The absolute path to the &quot;cake&quot; directory, WITHOUT a trailing DS.
 */
	if (!defined('CAKE_CORE_INCLUDE_PATH')) {
		define('CAKE_CORE_INCLUDE_PATH', DS.'home'.DS.$site5user.DS.'lib'.DS.'php'.DS.'cake');
	}

/**
 * Editing below this line should NOT be necessary.
 * Change at your own risk.
 */
	if (!defined('WEBROOT_DIR')) {
		define('WEBROOT_DIR', basename(dirname(__FILE__)));
	}
	if (!defined('WWW_ROOT')) {
		define('WWW_ROOT', dirname(__FILE__) . DS);
	}
	if (!defined('CORE_PATH')) {
		if (function_exists('ini_set') &amp;&amp; ini_set('include_path', CAKE_CORE_INCLUDE_PATH . PATH_SEPARATOR . ROOT . DS . APP_DIR . DS . PATH_SEPARATOR . ini_get('include_path'))) {
			define('APP_PATH', null);
			define('CORE_PATH', null);
		} else {
			define('APP_PATH', ROOT . DS . APP_DIR . DS);
			define('CORE_PATH', CAKE_CORE_INCLUDE_PATH . DS);
		}
	}
	if (!include(CORE_PATH . 'cake' . DS . 'bootstrap.php')) {
		trigger_error(&quot;CakePHP core could not be found.  Check the value of CAKE_CORE_INCLUDE_PATH in APP/webroot/index.php.  It should point to the directory containing your &quot; . DS . &quot;cake core directory and your &quot; . DS . &quot;vendors root directory.&quot;, E_USER_ERROR);
	}
	if (isset($_GET['url']) &amp;&amp; $_GET['url'] === 'favicon.ico') {
		return;
	} else {
		$Dispatcher = new Dispatcher();
		$Dispatcher-&gt;dispatch($url);
	}
	if (Configure::read() &gt; 0) {
		echo &quot;&amp;lt;!-- &quot; . round(getMicrotime() - $TIME_START, 4) . &quot;s --&amp;gt;&quot;;
	}
?&amp;gt;
</pre>
<p>We are almost done. Now all there is left to do is add your username/password for the database and of course change the security salt, but you should know how to do these if you&#8217;ve already developed with CakePHP.</p>
]]></content:encoded>
			<wfw:commentRss>http://oerd.cukalla.com/php/cakephp-on-site5-subdomains/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Hello and welcome!</title>
		<link>http://oerd.cukalla.com/writing/hello-world/</link>
		<comments>http://oerd.cukalla.com/writing/hello-world/#comments</comments>
		<pubDate>Wed, 03 Jun 2009 09:20:08 +0000</pubDate>
		<dc:creator>oerd</dc:creator>
				<category><![CDATA[writing]]></category>

		<guid isPermaLink="false">http://oerd.cukalla.com/blog/?p=1</guid>
		<description><![CDATA[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 [...]]]></description>
			<content:encoded><![CDATA[<p>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 <a href="http://cakephp.org">CakePHP</a> – a web application framework in PHP. </p>
<p>We have to adapt to a market where managers more often than not are deciding to move their applications to the web. They obtain (almost) ubiquitous access, and we have new technologies to adapt to. <acronym title="(Extended) Hypertext Markup Language">(X)HTML</acronym>, <acronym title="Cascading Style Sheet">CSS</acronym> and Javascript are part of the daily code-writing process, so expect some of that, too. </p>
]]></content:encoded>
			<wfw:commentRss>http://oerd.cukalla.com/writing/hello-world/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>

