Full CakePHP Application Part 2


CakePHP 1.2 Full Application Image 1

This is the second article in the series on creating a full DVD Catalog online web application with CakePHP 1.2. Last time I went through the process of beginning a new application from scratch. I created a small brief which led to quite a few requirements that the application will need to do and from those requirements I came up with a database structure that should hold up throughout the project life cycle. Finally I sent up CakePHP on my local computer using Xampp with a Virtual Host and created the Model files for the application. If you have not yet read the first article then please go back and have a quick look so you (hopefully) know what I'm talking about.
In this article I'm going to focus on the Formats table of the application and this includes creating a controller to deal with the functionality that the application will offer such as adding, editing and deleting formats. As well as the controller I'm going to create the related views for all the Format actions and at the end of the article we will be able to start adding data to the application.

Formats

To begin we need to create a new controller file called formats_controller.php and place this in the controllers directory. If you are having trouble with the concept of a controller visit the Controllers Chapter of the CakePHP manual. Generally a Controller takes care of all the business logic of the application with each method inside the file taking care of an action that the system does.
For example each method inside a controller corresponds to a URL of the system, if a user visits http://dvdcatalog/admin/formats/add then the logic inside the admin_add() method of the Formats Controller would be processed. This makes it very easy to organise and maintain your code and is one of the main benefits of using the MVC software pattern.
When creating a new controller I like to code all of the methods I'm likely to use and for the Formats Controller I will need the following:
  • index()
  • view()
  • admin_index()
  • admin_add()
  • admin_edit()
  • admin_delete()
So far the Formats Controller looks like this:
  1. /** 
  2.  * Formats Controller 
  3.  * 
  4.  * file: /app/controllers/formats_controller.php 
  5.  */  
  6. class FormatsController extends AppController {  
  7.     // good practice to include the name variable  
  8.     var $name = 'Formats';  
  9.       
  10.     // load any helpers used in the views  
  11.     var $helpers = array('Html''Form');  
  12.   
  13.     /** 
  14.      * index() 
  15.      * main index page of the formats page 
  16.      * url: /formats/index 
  17.      */  
  18.     function index() {  
  19.   
  20.     }  
  21.   
  22.     /** 
  23.      * view() 
  24.      * displays a single format and all related dvds 
  25.      * url: /formats/view/format_slug 
  26.      */  
  27.     function view($slug = null) {  
  28.   
  29.     }  
  30.       
  31.     /** 
  32.      * admin_index() 
  33.      * main index for admin users 
  34.      * url: /admin/formats/index 
  35.      */  
  36.     function admin_index() {  
  37.   
  38.     }  
  39.   
  40.     /** 
  41.      * admin_add() 
  42.      * allows an admin to add a format 
  43.      * url: /admin/formats/add 
  44.      */  
  45.     function admin_add() {  
  46.   
  47.     }  
  48.   
  49.     /** 
  50.      * admin_edit() 
  51.      * allows an admin to edit a format 
  52.      * url: /admin/formats/edit/id 
  53.      */  
  54.     function admin_edit($id = null) {  
  55.   
  56.     }  
  57.   
  58.     /** 
  59.      * admin_delete() 
  60.      * allows an admin to delete a format 
  61.      * url: /admin/formats/delete/id 
  62.      */  
  63.     function admin_delete($id = null) {  
  64.   
  65.     }  
  66.   
  67. }  

Index Methods and Views

The index() and admin_index() methods are going to retrieve all the active formats from the database and send them to the view so that they can be displayed to the user.
  1. /** 
  2.  * index() 
  3.  * main index page of the formats page 
  4.  * url: /formats/index 
  5.  */  
  6. function index() {  
  7.     // this tells cake to ignore related format data such as dvds  
  8.     $this->Format->recursive = 0;  
  9.   
  10.     // get all formats from database where status = 1  
  11.     $formats = $this->Format->findAll("status=1");  
  12.   
  13.     // save the formats in a variable for the view  
  14.     $this->set('formats'$formats);  
  15. }  
  16.   
  17. /** 
  18.  * admin_index() 
  19.  * main index for admin users 
  20.  * url: /admin/formats/index 
  21.  */  
  22. function admin_index() {  
  23.     // this tells cake to ignore related format data such as dvds  
  24.     $this->Format->recursive = 0;  
  25.   
  26.     // get all formats from database where status = 1  
  27.     $formats = $this->Format->findAll("status=1");  
  28.   
  29.     // save the formats in a variable for the view  
  30.     $this->set('formats'$formats);  
  31. }  
Next we need to create new View files called index.ctp and admin_index.ctp in the /app/views/formats folder. The views files are pretty basic and will output the formats in a table.
  1. <?php  
  2.   
  3. // file: /app/views/formats/admin_index.ctp  
  4.   
  5. ?>  
  6. <div class="formats index">  
  7.   
  8.     <h2>Formats Admin Index</h2>  
  9.     <p>Currently displaying all formats in the application.</p>  
  10.   
  11.     <?php  
  12.     // check $formats variable exists and is not empty  
  13.     if(isset($formats) && !empty($formats)) :  
  14.     ?>  
  15.   
  16.     <table>  
  17.         <thead>  
  18.             <tr>  
  19.                 <th>Id</th>  
  20.                 <th>Name</th>  
  21.                 <th>Slug</th>  
  22.                 <th>Description</th>  
  23.                 <th>Created</th>  
  24.                 <th>Modified</th>  
  25.                 <th>Actions</th>  
  26.             </tr>  
  27.         </thead>  
  28.         <tbody>  
  29.             <?php  
  30.             // initialise a counter for striping the table  
  31.             $count = 0;  
  32.   
  33.             // loop through and display format  
  34.             foreach($formats as $format):  
  35.                 // stripes the table by adding a class to every other row  
  36.                 $class = ( ($count % 2) ? " class='altrow'"'' );  
  37.                 // increment count  
  38.                 $count++;  
  39.             ?>  
  40.             <tr<?php echo $class; ?>>  
  41.                 <td><?php echo $format['Format']['id']; ?></td>  
  42.                 <td><?php echo $format['Format']['name']; ?></td>  
  43.                 <td><?php echo $format['Format']['slug']; ?></td>  
  44.                 <td><?php echo $format['Format']['desc']; ?></td>  
  45.                 <td><?php echo $format['Format']['created']; ?></td>  
  46.                 <td><?php echo $format['Format']['modified']; ?></td>  
  47.                 <td>  
  48.                 <?php echo $html->link('Edit'array('action'=>'admin_edit'$format['Format']['id']) );?>  
  49.                 <?php echo $html->link('Delete'array('action'=>'admin_delete'$format['Format']['id']), null, sprintf('Are you sure you want to delete Format: %s?'$format['Format']['name']));?>  
  50.                 </td>  
  51.             </tr>  
  52.             <?php endforeach; ?>  
  53.         </tbody>  
  54.     </table>  
  55.       
  56.     <?php  
  57.     else:  
  58.         echo 'There are currently no Formats in the database.';  
  59.     endif;  
  60.     ?>  
  61.       
  62.     <ul class="actions">  
  63.         <li><?php echo $html->link('Add a Format'array('action'=>'add')); ?></li>  
  64.     </ul>  
  65.   
  66. </div>  
CakePHP 1.2 Full Application Image 1

View Method and View

The view() method will be used by a user to list all the DVDs that belong to a particular format, the method will first check that the slug isn't null and will then retrieve the slug from the database using one of CakePHP's magic findByFieldName() methods. If the format has been found then I will save it for the View.
  1. /** 
  2.  * view() 
  3.  * displays a single format and all related dvds 
  4.  * url: /formats/view/format_slug 
  5.  */  
  6. function view($slug = null) {  
  7.     // if slug is null  
  8.     if(!$slug) {  
  9.         // set a flash message  
  10.         $this->Session->setFlash('Invalid id for Format''flash_bad');  
  11.         // redirect user  
  12.         $this->redirect(array('action'=>'index'));  
  13.     }  
  14.       
  15.     // find format in database  
  16.     $format = $this->Format->findBySlug($slug);  
  17.       
  18.     // if format has been found  
  19.     if(!empty($format)) {  
  20.         // set the format for the view  
  21.         $this->set('format'$format);  
  22.     } else {  
  23.         // set a flash message  
  24.         $this->Session->setFlash('Invalid id for Format''flash_bad');  
  25.         // redirect user  
  26.         $this->redirect(array('action'=>'index'));  
  27.     }  
  28. }  
The View file will display a few details of the format along with all the DVDs that belong to the format. A simple table is used to loop through the DVDs with a view link for each one.
  1. <?php  
  2.   
  3. // page: /app/views/formats/view.ctp  
  4.   
  5. ?>  
  6.   
  7. <div class="formats view">  
  8.     <h2>Viewing Format: <?php echo $format['Format']['name']; ?></h2>  
  9.       
  10.     <dl>  
  11.         <dt>Name:</dt>  
  12.         <dd><?php echo $format['Format']['name']; ?></dd>  
  13.           
  14.         <dt>Description:</dt>  
  15.         <dd><?php echo $format['Format']['desc']; ?></dd>  
  16.           
  17.         <dt>Created</dt>  
  18.         <dd><?php echo $format['Format']['created']; ?></dd>  
  19.     </dl>  
  20.       
  21.     <?php if(!empty($format['Dvd'])): ?>  
  22.       
  23.     <div class="related">  
  24.         <h3>DVDs with this Format</h3>  
  25.         <table>  
  26.             <thead>  
  27.                 <tr>  
  28.                     <th>Name</th>  
  29.                     <th>Actions</th>  
  30.                 </tr>  
  31.             </thead>  
  32.             <tbody>  
  33.                 <?php foreach($format['Dvd'as $dvd): ?>  
  34.                 <tr>  
  35.                     <td><?php echo $dvd['name']; ?></td>  
  36.                     <td><?php echo $html->link('View''/dvds/view/'.$dvd['slug']);?></td>  
  37.                 </tr>  
  38.                 <?php endforeach; ?>  
  39.             </tbody>  
  40.         </table>  
  41.     </div>  
  42.       
  43.     <?php endif; ?>  
  44.       
  45.     <ul class="actions">  
  46.         <li><?php echo $html->link('List Formats'array('action'=>'index'));?></li>  
  47.     </ul>  
  48. </div>  
CakePHP 1.2 Full Application Image 2

Admin Add Method and View

The admin_add() method will process the form data and try to add the information to the database. The method first checks that the form has been submitted, creates a slug from the name of the format and finally adds the format to the database. If the save() was successful then the user is redirected to the index page with a "success" flash message and if not the application displays a "failure" flash message.
  1. /** 
  2.  * admin_add() 
  3.  * allows an admin to add a format 
  4.  * url: /admin/formats/add 
  5.  */  
  6. function admin_add() {  
  7.     // if the form data is not empty  
  8.     if (!empty($this->data)) {  
  9.         // initialise the format model  
  10.         $this->Format->create();  
  11.   
  12.         // create the slug  
  13.         $this->data['Format']['slug'] = $this->slug($this->data['Format']['name']);  
  14.   
  15.         // try saving the format  
  16.         if ($this->Format->save($this->data)) {  
  17.             // set a flash message  
  18.             $this->Session->setFlash('The Format has been saved''flash_good');  
  19.             // redirect  
  20.             $this->redirect(array('action'=>'index'));  
  21.         } else {  
  22.             // set a flash message  
  23.             $this->Session->setFlash('The Format could not be saved. Please, try again.''flash_bad');  
  24.         }  
  25.     }  
  26. }  

Slugs

I have covered the creation of slugs in a previous article but I'll quickly go through it here as well. The first thing to do is create a new app_controller.php file located in the /app folder of the application. This file is useful as any methods you create in here will be available to every controller by calling:
  1. $this->methodName();  
Although CakePHP has an inbuilt slug function it doesn't deal with a few characters correctly so I've created my own:
  1. /** 
  2.  * App Controller 
  3.  * 
  4.  * file: /app/app_controller.php 
  5.  */  
  6. class AppController extends Controller {  
  7.   
  8.     /** 
  9.      * slug() 
  10.      * creates a slug from a string 
  11.      */  
  12.     function slug($str) {  
  13.         // replace spaces with underscore, all to lowercase  
  14.         $str = strtolower(str_replace(' ''_'$str));  
  15.   
  16.         // create regex pattern  
  17.         $pattern = "/[^a-zA-Z0-9_]/";  
  18.   
  19.         // replace non alphanumeric characters  
  20.         $str = preg_replace($pattern''$str);  
  21.   
  22.     return $str;  
  23.     }  
  24. }  
The function first replaces all spaces with underscores, converts all the characters to lower case and finally uses a regular expression to remove all characters that are not letters, numbers and underscores. In the admin_add() method I use this function to turn the name of the format into a slug before the save() takes place so that it is added to the database.

Flash Messages

In a previous article I came up with a way to display custom flash messages depending on the success/failure of the action. This is useful to show feedback to the user about the system such as a green box for a success action and a red box for a failure action.
Looking at the setFlash() function in more detail in the API I found that this functionality is built in by changing the layout that the flash message is displayed in. To make use of this I created two new layouts that will wrap the message in a div to indicate success or failure.
  1. <?php  
  2. // file: /app/views/layouts/flash_good.ctp  
  3. ?>  
  4.   
  5. <div class="flash_good">  
  6. <?php echo $content_for_layout; ?>  
  7. </div>  
  8.   
  9. <?php  
  10. // file: /app/views/layouts/flash_bad.ctp  
  11. ?>  
  12.   
  13. <div class="flash_bad">  
  14. <?php echo $content_for_layout; ?>  
  15. </div>  
I can now use CSS to style these flash messages accordingly. Next up is the view for the admin_add() method, create a new file called admin_add.ctp in the formats view folder (/app/views/formats). The view file contains the form that will be displayed when an admin visits http://dvdcatalog/admin/formats/add and looks like this:
  1. <?php  
  2.   
  3. // file: /app/views/formats/admin_add.ctp  
  4.   
  5. ?>  
  6. <div class="formats form">  
  7. <?php echo $form->create('Format');?>  
  8.     <fieldset>  
  9.         <legend>Add a Format</legend>  
  10.         <?php  
  11.         // create the form inputs  
  12.         echo $form->input('name'array('label'=>'Name: *'));  
  13.         echo $form->input('desc'array('label'=>'Description:''type'=>'textarea'));  
  14.         ?>  
  15.     </fieldset>  
  16. <?php echo $form->end('Add');?>  
  17. </div>  
  18.   
  19. <ul class="actions">  
  20.     <li><?php echo $html->link('List Formats'array('action'=>'index'));?></li>  
  21. </ul>  
CakePHP 1.2 Full Application Image 3 I've used the form helper to quickly create a form that contains two inputs, one for the title and one for the description. Initially I had a bit of trouble understanding how the form helper works in the CakePHP 1.2 due to the lack of official documentation but looking in the API gave me a clearer picture.

Form Validation

One of the many things that has been severely beefed up in the new version of Cake is the built in data validation and gives us loads of options for some pretty advanced validation. For the Add Format form I want the Name of the format to be a mandatory field with an error message displayed if the name is not present. To do this I need to add a rule to the Format Model:
  1. // file: /app/models/format.php  
  2.   
  3. // setup form validation for formats  
  4. var $validate = array(  
  5.     // name field  
  6.     'name' => array(  
  7.         // must not be empty  
  8.         'rule'      => VALID_NOT_EMPTY,  
  9.         // error message to display  
  10.         'message'   => 'Please enter a Format Name'  
  11.     )  
  12. );  
These rules come into play when the save() method is run in the controller, if the form successully validates then the data is inputted to the database if not the form is displayed again with the error messages defined in the Model.
CakePHP 1.2 Full Application Image 4

Admin Edit Method and View

The admin_edit() method is very simiar to the admin_add(), the only difference is that we need to find the format in the database and save it for the View so that the form will be pre populated with data so that we can edit the information.
The method takes an id as a parameter and this is first checked to see if it is null or empty, if it is the admin is redirected to the index page. If the form has been submitted we need to create a new slug and save the data to the database. This is the same as the admin_add() method. Finally if the form has not been submitted we need to find the Format from the database using the id that was passed as an argument.
  1. /** 
  2.  * admin_edit() 
  3.  * allows an admin to edit a format 
  4.  * url: /admin/formats/edit/1 
  5.  */  
  6. function admin_edit($id = null) {  
  7.     // if the id is null and the form data empty  
  8.     if (!$id && empty($this->data)) {  
  9.         // set a flash message  
  10.         $this->Session->setFlash('Invalid Format''flash_bad');  
  11.         // redirect the user  
  12.         $this->redirect(array('action'=>'index'));  
  13.     }  
  14.   
  15.     // if the form data is empty  
  16.     if (!empty($this->data)) {  
  17.         // create the slug  
  18.         $this->data['Format']['slug'] = $this->slug($this->data['Format']['name']);  
  19.   
  20.         // try saving the form data  
  21.         if ($this->Format->save($this->data)) {  
  22.             // set a flash message  
  23.             $this->Session->setFlash('The Format has been saved''flash_good');  
  24.             // redirect  
  25.             $this->redirect(array('action'=>'index'));  
  26.         } else {  
  27.             // set a flash message  
  28.             $this->Session->setFlash('The Format could not be saved. Please, try again.''flash_bad');  
  29.         }  
  30.     }  
  31.   
  32.     // if form has not been submitted  
  33.     if (empty($this->data)) {  
  34.         // find the format from the database and populate the form data  
  35.         $this->data = $this->Format->read(null, $id);  
  36.     }  
  37. }  
A corresponding admin_edit.ctp View file needs to be created like before and this will show a populated form to allow the data to edited. In order for the edit to work correctly I must include a hidden form field of the id of the format so that CakePHP knows this is an edit form and it will populate the form correctly.
I'm also going to display all the related DVDs that belong to a Format, the DVDs are automatically retrieved from the database when the $this->Format->read() was made so I'm going to loop through them all and display them in a table. This allows the DVD to viewed and edited directly from here to make things easier for the admin, although and this stage there are no DVDs in the database.
  1. <?php  
  2.   
  3. // file: /app/views/formats/admin_edit.ctp  
  4.   
  5. ?>  
  6. <div class="formats form">  
  7. <?php echo $form->create('Format');?>  
  8.     <fieldset>  
  9.         <legend>Edit Format</legend>  
  10.         <?php  
  11.         // a hidden form input field, required in a edit form  
  12.         echo $form->input('id'array('type'=>'hidden'));  
  13.         // create the form inputs  
  14.         echo $form->input('name'array('label'=>'Name: *'));  
  15.         echo $form->input('desc'array('label'=>'Description:''type'=>'textarea'));  
  16.         ?>  
  17.     </fieldset>  
  18. <?php echo $form->end('Edit');?>  
  19. </div>  
  20.   
  21. <?php if(!empty($this->data['Dvd'])): ?>  
  22.   
  23. <div class="related">  
  24.     <h3>DVDs with this Format</h3>  
  25.     <table>  
  26.         <thead>  
  27.             <tr>  
  28.                 <th>Id</th>  
  29.                 <th>Name</th>  
  30.                 <th>Actions</th>  
  31.             </tr>  
  32.         </thead>  
  33.         <tbody>  
  34.             <?php foreach($this->data['Dvd'as $dvd): ?>  
  35.             <tr>  
  36.                 <td><?php echo $dvd['id']; ?></td>  
  37.                 <td><?php echo $dvd['name']; ?></td>  
  38.                 <td><?php echo $html->link('Edit''/admin/dvds/edit/'.$dvd['id']);?></td>  
  39.             </tr>  
  40.             <?php endforeach; ?>  
  41.         </tbody>  
  42.     </table>  
  43. </div>  
  44.   
  45. <?php endif; ?>  
  46.   
  47. <ul class="actions">  
  48.     <li><?php echo $html->link('List Formats'array('action'=>'index'));?></li>  
  49. </ul>  
CakePHP 1.2 Full Application Image 5

Delete Method

The delete() method instead of deleting an item completely from the database will change the status of the Format from "1" to "0". This is just a security precaution so that no data is actually deleted from the database and if you do accidently delete something you can easily restore the data using PhpMyAdmin or an SQL statement.
The method first checks that the id is valid and then saves the id of the Format to the current Format model. This ensures that when the saveField() is ran CakePHP only performs the operation on the Format with the specified id. If the delete was a success the user is redirected to the index page with a flash message.
  1. /** 
  2.  * admin_delete() 
  3.  * allows an admin to delete a format 
  4.  * url: /admin/formats/delete/1 
  5.  */  
  6. function admin_delete($id = null) {  
  7.     // if the id is null  
  8.     if (!$id) {  
  9.         // set flash message  
  10.         $this->Session->setFlash('Invalid id for Format''flash_bad');  
  11.         // redirect  
  12.         $this->redirect(array('action'=>'index'));  
  13.     }  
  14.   
  15.     // set the id of the format  
  16.     $this->Format->id = $id;  
  17.   
  18.     // try to change status from 1 to 0  
  19.     if ($this->Format->saveField('status', 0)) {  
  20.         // set flash message  
  21.         $this->Session->setFlash('The Format was successfully deleted.''flash_good');  
  22.     } else {  
  23.         // set flash message  
  24.         $this->Session->setFlash('The Format could not be deleted. Please try again.''flash_bad');  
  25.     }  
  26.       
  27.     // redirect  
  28.     $this->redirect(array('action'=>'index'));  
  29. }  

Wrapping Up

Now that the Formats Controller and Views have been created we can start to populate the database with data. All of the Methods and Views that I've gone through are pretty much the building blocks of any CakePHP Application. The Create Read Update Delete (CRUD) functionality is a standard way to interact with the database and if you fully understand how this works most of the application can be completed without too many problems.
I've covered quite a few topics in this article. These include Controllers, Models, Views, Form Validation, Flash Messages, Layouts and slug creation. If you do get stuck then check out the official manual or send me an email.

Source Code

The source code for this article can be downloaded using this link. If these articles are helping you out why not consider donating I can always use a beer! :)

This entry was posted in . Bookmark the permalink.

Leave a Reply