Justkez

Trying to be a consistent blog 
Filed under

cakephp

 

CakePHP Cannot modify header information

I have just spent too long trying to discover why a controller throws a "Cannot modify header information - headers already sent by..." error message. There's no obvious reason why, and no obvious fix. The controller works fine on another installation of Apache2 and PHP, but not on my machine at home.

The problem? White space after the closing ?> PHP tag. You can have as many carriage returns as you like, but don't try a space. Turns out it was reported here 9 months ago. The team's response is perfectly acceptable, but it's quite a hard little caveat to track down and fix! Here's hoping this will be of use to someone else in distress.

Filed under  //   cakephp   php   tips  

Comments [5]

CakePHP courteous redirection after user login

There are several good user authentication/registration/login systems available for CakePHP (especially version 1.2), but I decided to roll my own. This offers a good learning exercise, in addition to the benefit of knowing where all your code is and what it's doing.

The latest little addition I've made to the code allows you to redirect the user back to where they wanted to go before they logged in. For example, I want to add a recipe into my online multi-user cookbook web application. If I click an "Add Recipe" button and get taken to a login form, I don't want to then be redirected to the front page once I'm logged in - I want to go straight back to adding a recipe.

The basic logic of the login system is

  • A function in /app/app_controller.php to check if the user is logged in. If they're not logged in, redirect them to the login page.
  • A login form to talk to a controller and model which do all the processing/checking to see if the details are correct.
  • A line of code at the end of all this to redirect the user to the front page (or members area).

By adding a tiny snippet of code to the check-if-user-is-logged-in function, and revising the redirect after login, the user will get a much more pleasant experience:

    function checkIfLoggedIn() 
{
  if(!$this -> Session -> check('user')) 
  {
    $this -> Session -> write('lastPageVisited', $this -> params['url']['url']);
    $this -> redirect('/users/login', null, true);
  }
}
  

Then when, in your controller, you decide that the user is all logged in (in this example you'd set the user variable in the session), use the following snippet:

    $this -> redirect('/' . $this -> Session -> read('lastPageVisited'), null, true);
  

Pretty rough and ready (forgive the prepending slash), but it works for my needs. If you do this, you'll probably want to check the lastPageVisited variable to ensure it exists.

Do you use CakePHP and go about this a different way? Would love to hear different methods...

Filed under  //   cakephp   development   php   programming   tutorials  

Comments [0]

CakePHP Livesearch

There was a lot of positive feedback from the last tutorial which has spurred on a newer, revised tutorial. The contents of this post relate to Cake 1.2 (still in beta, but very usable). Whilst much of the steps are the same, there are a few subtle differences, and I have tried to clean things up a little better. So, without further ado...

Overview

This is a very high level overview of what the tutorial will delve into - nothing too advance, but many people pointed out with the last tutorial that it was a bit hard to keep track of.

  • Livesearch
  • How it's implemented
  • File structure
  • Database, table and Model
  • Controller and views
  • Don't forget the layout

1. Livesearch

If you're not too sure what Livesearch is, you probably don't need to be reading this tutorial. A quick overview of a livesearch would describe how search results are presented to you before you've finished typing - no need to click submit, just type and watch the results appear. Livesearch is part of the Web 2.0 thing, and has come around thanks to the popularisation of AJAX. To see an example of a livesearch in action head on over to Google Suggest.

2. How it's implemented

If you'd rather head straight for the demo (put together by following the steps in this tutorial), then go right ahead.

We will be walking through a simple system of searching a table of first and second names - the controller we'll build later on searches both the first name and the second name field for whatever you type in. Obviously however you wish to implement a livesearch will be slightly different, but the principles are the same.

The basic process of livesearching is as follows:

  • Start typing in an input box
  • Javascript monitors what you type and periodically sends a request off in the background
  • This request is picked up by the controller, which searches the database for matches to your query
  • The controller sends the results to a small template file, which appears under the search box

The mileage is going to vary on this - results could be slow to appear because of a sluggish connection, a large database or an overloaded server. I'm never that keen on websites that implement livesearching when it doesn't add any value to the search. However, for quickly looking up a list of possible matches it is ideal.

3. File structure

I have made the entire CakePHP project tree available for download, but it would be good for you to have some understanding of what the files do before diving in.

The tutorial covers the creation of 1 model, 1 controller, 2 views for the model and 1 stylesheet. The layout is not really important in this scenario, and the one included in the project tree is very, very simple.

The model simply maps the database table (created in the next step) into CakePHP. The controller is responsible for directing the visitor to the relevant views, and of course handling the search request. It is quite surprising how little code there is in the controller.

The first view displays the search box and includes the AJAX functionality necessary to fire off the search query to the controller. It also contains a <div> element which is populated by the results. The second view is a simple template for formatting the search results. In this case it is just an unordered list.

From here on in things get very specific to the tutorial.

4. Database, table and Model

I'm assuming you have an operational CakePHP environment, and the database is all setup and ready to go. If this is not the case, you'd better get yourself over to the excellent CakePHP manual and brush up.

We are going to create one table (people) and put a few records into it:

    CREATE TABLE `people` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name_first` varchar(20) DEFAULT NULL,
`name_second` varchar(20) DEFAULT NULL,
PRIMARY KEY  (`id`)
);
  

Now we can populate it with a handful of British Prime Ministers:

    INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('7','James','Callaghan');
INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('6','Margaret','Thatcher');
INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('1','Tony','Blair');
INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('5','John','Major');
INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('8','Edward','Heath');
INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('9','Harold','Wilson');
INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('10','Alec','Douglas-Home');
INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('11','Harold','Macmillan');
INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('12','Anthony','Eden');
INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('13','Clement','Attlee');
INSERT INTO `people` (`id`,`name_first`,`name_second`) VALUES ('14','Winston','Churchill');
  

The final step is to create a model which CakePHP will know to map to the table we just created:

    <?php
/* File: /app/models/person.php */
class Person extends AppModel
{
    var $name = 'People';
}
?>
  

The next steps are where something actually starts to happen.

5. Controller and views

In order to interact with the model we just created, there has to be a controller associated with it. If you've got to this point and have no idea what a model, view or controller is, it's time you got yourself over to Wikipedia to read up. Our controller is only going to have two actions (and two associated views), so it's all pretty simple:

    <?php
/* File: /app/controllers/people_controller.php */
uses ('sanitize');
class PeopleController extends AppController
{
    var $Sanitize;
    var $name = 'People';

    function index() { }

    function beforeFilter()
    {
        $this->Sanitize = new Sanitize();
    }

}
?>
  

CakePHP's magic will automatically associate the model Person (singular) to the controller People (plural), so you don't have to worry about that.

Now we need to tell CakePHP to use the JavaScript and AJAX helpers that will give us the ability to fire off a livesearch request and do various displaying/hiding magic. Add the following line below the $name declaration:

var $helpers = array('Html', 'Javascript', 'Ajax');

It is important to also include the Html helper, as CakePHP will override the default helper inclusions with what you specify here. It's also worth noting that you can set default helper inclusions in /app/app_controller.php, but we're keeping everything wrapped up in one controller, here.

The final step of constructing the controller is to process a search request. The second view created in this step (further down) contains a form that will be used to submit data to our search action. This action will be responsible for fetching the results accordingly, setting the layout to a CakePHP-provided Ajax layout and passing the results to the search results view (created after this step):

    <?php
function search() {
if (!empty($this->params['form']['query']))
{
$query = $this -> Sanitize -> paranoid($this->params['form']['query']);
if (strlen($query) > 0)
{
    $result = $this -> Person -> findAll("name_first LIKE '%".$query."%' OR name_second LIKE '%".$query."%'");
    $this->set('result', $result);
    $this->layout = 'ajax';
}
}
}
?>
  

There are a few sanity checks here. Firstly we sanitize the query data to prevent any SQL injection style attacks - this is done through the inbuilt Sanitize library, which helps strip out any nasty characters. Secondly we want to make sure that the query field from the view is not empty, and that it is of a sensible length (greater than 1 here, just for demonstration purposes). If these checks pass, the results from the query get sent onto the results view.

The first view we'll take a look at is the one that will format the search results - we're going to call this search.ctp and it will live in /app/views/people/ - you may need to create the subdirectory if you haven't already. Copy the following into the contents of the file:

    <?php
/* File: /app/views/people/search.ctp */
if (isset($result)) {
print '<ul>';
foreach ($result as $user)
{
    $display = $user['Person']['name_first'] . ' ' . $user['Person']['name_second'];
    print '<li>';
        print '' . $display . '';
    print '';
}
print '</ul>';
}
?>
  

As you can (hopefully) deduce, this checks for the presence of a $result variable and populates an ordered (a.k.a. numbered) list with the contents, and spitting out the first and second name of each result.

The second view is somewhat more complicated, and initiates the various Javascript related functions needed for the Ajax-powered livesearching. This view is stored in index.ctp, and should look something like the following:

    <?php /* File: /app/views/people/index.ctp */?>
<form accept-charset="UNKNOWN" enctype="application/x-www-form-urlencoded" method="get">
<input id="query" maxlength="2147483647" name="query" size="20" type="text" />
<div id="loading" style="display: none; "><!--p echo $html--> image("spinner.gif") ?></div>
</form>

<?php
$options = array(
    'update' => 'view',
    'url'    => '/people/search',
    'frequency' => 1,
    'loading' => "Element.hide('view');Element.show('loading')",
    'complete' => "Element.hide('loading');Effect.Appear('view')"
);

print $ajax -> observeField('query', $options);
?>

<div id="view" class="auto_complete">
    <!-- Results will load here -->
</div>
  

The first thing to note here is the form. This is a very, very simple chunk of HTML that gives us one input box - no location to submit the form to, no submit button; just the input box. Next to that is the spinner graphic (you all know what this is...) that we want to toggle display of depending on what the Ajax handler is up to.

The $options array could well be integrated into the statement below it, but this makes things a bit more readable. The observeField is a function provided to us by the Ajax helper that will watch a form field and fire off an Ajax request when it changes. The $options array has all sorts of information about the request to make, and what to do when it's being made...

...The update field tells the Ajax handler where to display the results it receives. In this case, they are going into the DIV at the bottom of the view.

The url field tells the Ajax handler where to submit the contents of the watched field to. This is the action we created earlier.

The frequency field tells the Ajax handler how often it should re-check the watched field and submit the contents to the url specified above. This is in seconds.

The loading and complete fields tell the Ajax handler what it should do when it's waiting for results and what to do when it loads them. The values assigned to these are script.aculo.us calls. In this instance it will show the spinner graphic when results are being fetched, and will hide it again and display the results when it has got everything it needs.

Don't forget the layout

The final obstacle to having a working livesearch (from following this tutorial), is the layout. The first thing you'll need to do is download the prototype and scriptaculous libraries - you can get them from here. You'll need to extract the scriptaculous.js and prototype.js to /app/webroot/js.

I opted to use a very simple layout, so as not to distract from what was going on. You're welcome to use the following, or create your own. Whichever way you go, do not include the Javascript and stylesheet includes!

    <?php /* File: /app/views/layouts/default.ctp */
    e ($html -> css('screen'));
    e ($javascript -> link('prototype'));
    e ($javascript -> link('scriptaculous'));

    print $content_for_layout;
?>
  

The stylesheet is, again, very simple: (if you're using this, save it to /app/webroot/css/screen.css)

    * { font-family: Verdana, sans; }
a { color: #000000; }
a:hover { color: #FF0000; }
p { font-size: 12px; }
div { display: inline; font-size: 12px;}
input { padding: 4px; }
li { margin: 5px; }
div.auto_complete { display: block; }
  

That's it for the tutorial. You might want to check out the demo of everything you seen above in action. You can also download the CakePHP project tree - this contains a vanilla CakePHP 1.2 setup and everything you see above. There is also a SQL dump file in there to speed up table creation & population.

If you think I've missed anything important - explanation, technical detail or otherwise then please comment below or send me an email (address is over on the left in the sidebar). There was a good deal of feedback from the last tutorial, so thanks to all.

Filed under  //   cakephp   development   php   programming   tutorial  

Comments [3]