Skip to main content

skip to main content

developerWorks  >  Open source  >

Cook up Web sites fast with CakePHP, Part 4: Use CakePHP's Session and Request Handler components

Streamline PHP applications

developerWorks
Go to the previous pagePage 4 of 9 Go to the next page

Document options
PDF format - Fits A4 and Letter

PDF - Fits A4 and Letter
221 KB (27 pages)

Get Adobe® Reader®

Sample code


My developerWorks needs you!

Connect to your technical community


Rate this tutorial

Help us improve this content


Utilizing the Request Handler

CakePHP comes with a Request Handler that allows you to present your application in different ways, depending on the type of request being made. In the previous sections, you saw how layouts controlled the wrapper of your application, which, by default, in CakePHP is XHTML Transitional. Here, we'll examine how you can use the Request Handler to return data from your application in a different form from XHTML, depending on what is requesting the page.

Anatomy of an HTTP request

Every element on a Web page is obtained via an HTTP request, a text header that contains the resource being requested, and information on the requesting agent. A sample HTTP request is given below.


Listing 11. Sample HTTP request
                    
                              
GET / HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0 (Macintosh; U; PPC Mac OS X Mach-O; en-US; rv:1.8.1) 
Gecko/20061010 Firefox/2.0
Accept: text/xml,application/xhtml+xml,text/html
Accept-Language: en-us,en
Accept-Encoding: gzip,deflate
Accept-Charset: ISO-8859-1,utf-8
Keep-Alive: 300
Connection: keep-alive
Cache-Control: max-age=0

In Web sites of yore, it wasn't uncommon to try and parse the User-Agent string, providing completely different content to different Web browsers. For example, a company that designed its site for Internet Explorer® might find it does not look correct in other browsers, and suggest these visitors use IE.

Luckily, those days are coming to an end, and good manners on the Web mean every Web browser should get the exact same content for the exact same resources. Note that the same content does not necessarily mean it has to be presented the same way.

Instead of using the User-Agent to determine how to format content, a better method is to use the Accept header. In the example above, Firefox accepts three types of content identified by their MIME types: XML, XHTML+XML, and HTML.



Back to top


Creating an RSS feed

Now you will create an RSS feed for Tor, which is an updated list of all new products. This has changed dramatically since CakePHP V1.1.8: Now it's even easier. You need to include the Request Handler component in the Products handler, create a view that uses the RSS Helper, and tell the router to parse RSS extensions.

Giving the product list this behavior is easy when you use the Request Handler component. Since your feed will be located at /products, open the file containing the class ProductsController. To begin, include the Request Handler component.


Listing 12. Including the Request Handler component
                    
<?php
class ProductsController extends AppController
{

  var $name = 'Products';
  var $helpers = array('Html', 'Form' );
  var $components = array('Acl', 'Security', 'RequestHandler'); 
  ...
  
}
                

There is a lot more to the Request Handler than what we're going to cover here. You can use it to determine what kind of requests are accepted, or preferred, and much more. But for now, simply including the handler will suffice.

Next, you need to create a view just for the RSS feed. This view will be automagically rendered when required and makes use of the RSS Helper to get things done. Create the file app/views/products/rss/index.ctp (you may need to create that RSS directory). The view should look like what is shown below.


Listing 13. The products index RSS view
                    
<?php
     echo $rss->items($products, 'transformRSS');
     function transformRSS($products) {
           return array(
                'title'  => $products['Product']['title'],
                 'link'    => array('url'=>'/products/view/'
				       .$products['Product']['id']),
                        'description'   => $products['Product']['description'],
                );
        }
?>

Finally, add the following line to the app/config/routes.php file: Router::parseExtensions('rss');.

That's it! You can view the new RSS feed at http://localhost/products/index.rss. You may need to view the source to read the XML. And if your debug level is set to 2 or 3, the debug info will certainly invalidate the XML format. But this should get you well on the road to playing with RSS in Cake.



Back to top


Adding Ajax

Asynchronous JavaScript and XML (Ajax) is a popular method for creating interactive Web applications without sacrificing browser compatibility. In this section, we describe how to use CakePHP's Ajax helper and Request Handler components to streamline the updating of products. It is assumed that you have a general familiarity with Ajax concepts. If you need an introduction, try reading "Mastering Ajax" and "Considering Ajax" (see Resources).

Recall that Ajax is a series of asynchronous requests from the client to the server, generally initiated by user interaction. Since CakePHP separates the back-end and front-end plug-ins into components and helpers, respectively, it means that all of your JavaScript functions are going to be implemented by the Ajax helper, while all of the back-end functionality will be supported by the Request Handler component.

The Request Handler component effectively allows you to reuse the non-Ajax code you created. It has an isajax method, which returns true if the request was made by an XMLHttpRequest call. Within your controller, you can use this method to check the nature of the request and tweak the output slightly, depending on whether Ajax is involved.

script.aculo.us

CakePHP's Ajax helper requires Prototype for Ajax and uses script.aculo.us for effects. Both are released under the MIT license. (The MIT license is the same as CakePHP and basically says you can use the software for any means with few restrictions). The script.aculo.us library includes a copy of Prototype, so there is no need to download both. You should begin by downloading the latest copy of script.aculo.us (see Resources). You don't really need to know much about script.aculo.us, except that you need to include its JavaScript files on your site.

After you've downloaded and extracted script.aculo.us, you should see a folder called src with a number of JavaScript files in it. You won't be using all of them, but you might as well copy all of them into the app/webroot/js folder of your site. Also copy the file lib/prototype.js to app/webroot/js.

Now that you have script.aculo.us, the JavaScript needs to be included on every page. Open the file app/views/layouts/default.ctp and add the JavaScript-related lines shown below.


Listing 14. Changing the header
                    
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<?php
if ( isset($javascript) ) {
  echo $javascript->link('prototype.js');
  echo $javascript->link('scriptaculous.js?load=effects');
  echo $javascript->link('controls.js');
}
?>
...
                

That's pretty much all you're going to have to do with the script.aculo.us library. Everything from here on out only involves objects in CakePHP.



Back to top


Adding a list of favorites

Recall from Part 2 that you were able to link up two models with the hasMany and belongsTo relationships. In particular, you linked up Dealers with Product. There is another relationship, hasAndBelongsToMany, which is particularly useful when you are linking many models.

To illustrate, imagine you created a user's favorite items by using the hasMany relationship. Then each product must "belongTo" a particular user, meaning that no other user could mark it as a favorite. That would not be a good thing. The resulting database structure is shown in Figure 2.


Figure 2. Database schema for hasMany and belongsTo relationship
Database schema for hasMany and belongsTo relationship

Notice that a product may belong to only one user. Instead, the hasAndBelongsToMany relationship uses a "join table," which is a way to ensure that a user hasMany products, without the products "belongingTo" a single user. A join table simply keeps track of a list of pairs — a user and a product — to signify that the two are linked. The resulting database structure is shown below.


Figure 3. Database schema for hasAndBelongsToMany relationship
Database schema for hasAndBelongsToMany relationship

Any number of connections can be made between the two tables. If you set up a join table properly, CakePHP will automatically create the proper links for you. To ensure that CakePHP can find your join table, there are a number of name conventions you should follow:

  • The name of the table should be the names of the two models, pluralized, in alphabetical order. For example, if you were linking a user and group object, the name of the table should be groups_users.
  • The table should have two foreign keys, each named the same as the belongsTo relationship. For example, a users/groups join table would have the fields user_id and group_id.
  • For optimization, the two keys should form a primary key on the join table.

In our case, we are linking products to users, so the SQL query will create a table that meets these standards.


Listing 15. Create a table to link products to users
                    
CREATE TABLE products_users (
  product_id int(5) NOT NULL,
  user_id int(5) NOT NULL,
  PRIMARY KEY (product_id,user_id)
) ENGINE=MyISAM;
                

To link up the two models, we simply add the hasAndBelongsToMany relationship to the product and user models.


Listing 16. Add hasAndBelongsToMany relationship to product and user models
                    
//In app/models/user.php:
<?php
  class User extends AppModel
  {
    ...
    
    var $hasAndBelongsToMany = array( 'Product' =>
        array(
            'className' => 'Product'
        )
    );
}
?>

//In app/models/product.php:
<?php
  class Product extends AppModel
  {
    ...
    
    var $hasAndBelongsToMany = array( 'User' =>
        array(
            'className' => 'User'
        )
    );
}
?>
                

Now, any time a product or user is read from the database, it will check the products_users table to see if there are any links that should be made. If it finds applicable rows, it will load up the corresponding objects. Create a new action in the users_controller.php called favorites.


Listing 17. Retrieving a list of favorites
                    
   function favorites () {
       $username = $this->Session->read('user');
       $favorites = array();
       
       if ($username)
       {
         $this->User->recursive = 2;
         $results = $this->User->findByUsername($username);
         
         foreach($results['Product'] as $product) {
           $favorites[] = array('Product' => $product, 'Dealer' => 
$product['Dealer']);
         }
         
         $this->set('products', $favorites);

      } else {

         $this->redirect(array('controller' => 'users', 'action' => 'login'));
       }
   }
                



Back to top


The addToFavorites action

From an interface standpoint, we want the user to add a product to their favorites. Therefore, we need an addToFavorites action in the ProductsController.

Open the file app/controllers/products_controller.php and add the addToFavorites action, shown below.


Listing 18. Adding addToFavorites action
                    
 function addToFavorites($id) {
    $product = $this->Product->read(null, $id);
    $username = $this->Session->read('user');
    $success = false;
         if ($this->Acl->check($username, $id.'-'.$product['Product']
		                            ['title'], 'read')) {
              $result = $this->Product->User->findByUsername($username);
               $product['User'] = array( 'User' =>
                   array($result['User']['id'])
               );
               $this->Product->save($product);
               $success = true;
         }
         if ( $this->RequestHandler->isAjax() ) {
               $this->layout = 'ajax';
               if ( $success ) {
                     echo 'Added product to favorites';
               } else {
                     echo 'Access denied';
                      }
exit;

         } else {
                if ( $success ) {
                     $this->Session->setFlash('Added product to favorites');
                     $this->redirect(array('controller' => 'users', 'action' 
					               => 'favorites'), null, true);
                 } else {
                     $this->Session->setFlash('Access denied');
                     $this->redirect(array('action' => 'index'), null, true);
                 }
         }
   }

Let's go over what this action does. First, it does a quick ACL check to see if the user is actually able to read the product. Next, it loads up the user object based on value stored in the user session variable. Then it formats the data for saving and saves the item in the favorites list.

Finally, Request Handler is called to see if the action was an Ajax request. If it was, an informational message is returned. Otherwise, the user is redirected to the appropriate page.

You can now go to /products to try out the action, giving the result shown in Figure 4. View the list of products and pick one by ID, such as product 8. Then go to http://localhost/products/addToFavorites/8 and see the results. Even though you have no evidence of it other than the output message, the link has been made. Connect to your database and look at the products_users table, and you should see a single row linking your user_id with the selected product_id.


Figure 4. Added product successful
The completed favorites page



Back to top


Linking to the addToFavorites action

Finally, you should provide a link to the addToFavorites action on the view products page. You'll utilize the Ajax helper's link method to create the link. The method, $ajax->link, acts much like the $html->link method. It takes the following parameters:

  • $title — A string that will be used for the title of the link — in this case, "Delete"
  • $href — The action the link will perform
  • $options — An array of options, the most important of which is update, the ID of the element that the response text should feed into — in this case, we are updating the entire table
  • $confirm — A JavaScript alert that will pop up confirming the action

This method outputs all the JavaScript needed to make a full Ajax request. On the back end, we only need to update the products controller to account for a new type of request. Utilizing the Request Handler component, we change the behavior if the action is part of an Ajax call, as shown in Figure 18. You'll also need to add a test in the products View view to see if Ajax is enabled and, if so, display an Ajax link. This link will be looking to update a DOM element called favMessage. You will want to create that somewhere in the view, like at the end of the product div.


Listing 19. Adding to favorites links in the products view
                    
<?php
if ( isset($ajax) ) {
      echo $ajax->link('Add to Favorites', array('action' => 'addToFavorites/' . 
         $product['Product']['id']), array('update' => 'favMessage'));
} else {
      echo $html->link('Add to Favorites', array('action' => 'addToFavorites/' . 
         $product['Product']['id']));
}
?>

...

<span id='favMessage'> </span>
...

For this to work, you need to add the JavaScript and Ajax helpers to the Products controller like so: var $helpers = array('Html', 'Form', 'Javascript', 'Ajax' );.

By using CakePHP's Ajax helper, we've added Ajax functionality without having to write a single line of JavaScript.



Back to top


Filling in the gaps

Rather than give you code to display the favorites table, you should try doing it on your own. You need to add the favorites described earlier method to the users controller and a users/favorites view.

When you are done with that, try adding a "Remove from favorites" link to the products table. Set it up so that the user is shown the "Add/Remove" link based on whether the product is already in favorites.



Back to top



Go to the previous pagePage 4 of 9 Go to the next page