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()
- /**
- * Formats Controller
- *
- * file: /app/controllers/formats_controller.php
- */
- class FormatsController extends AppController {
- // good practice to include the name variable
- var $name = 'Formats';
- // load any helpers used in the views
- var $helpers = array('Html', 'Form');
- /**
- * index()
- * main index page of the formats page
- * url: /formats/index
- */
- function index() {
- }
- /**
- * view()
- * displays a single format and all related dvds
- * url: /formats/view/format_slug
- */
- function view($slug = null) {
- }
- /**
- * admin_index()
- * main index for admin users
- * url: /admin/formats/index
- */
- function admin_index() {
- }
- /**
- * admin_add()
- * allows an admin to add a format
- * url: /admin/formats/add
- */
- function admin_add() {
- }
- /**
- * admin_edit()
- * allows an admin to edit a format
- * url: /admin/formats/edit/id
- */
- function admin_edit($id = null) {
- }
- /**
- * admin_delete()
- * allows an admin to delete a format
- * url: /admin/formats/delete/id
- */
- function admin_delete($id = null) {
- }
- }
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.- /**
- * index()
- * main index page of the formats page
- * url: /formats/index
- */
- function index() {
- // this tells cake to ignore related format data such as dvds
- $this->Format->recursive = 0;
- // get all formats from database where status = 1
- $formats = $this->Format->findAll("status=1");
- // save the formats in a variable for the view
- $this->set('formats', $formats);
- }
- /**
- * admin_index()
- * main index for admin users
- * url: /admin/formats/index
- */
- function admin_index() {
- // this tells cake to ignore related format data such as dvds
- $this->Format->recursive = 0;
- // get all formats from database where status = 1
- $formats = $this->Format->findAll("status=1");
- // save the formats in a variable for the view
- $this->set('formats', $formats);
- }
- <?php
- // file: /app/views/formats/admin_index.ctp
- ?>
- <div class="formats index">
- <h2>Formats Admin Index</h2>
- <p>Currently displaying all formats in the application.</p>
- <?php
- // check $formats variable exists and is not empty
- if(isset($formats) && !empty($formats)) :
- ?>
- <table>
- <thead>
- <tr>
- <th>Id</th>
- <th>Name</th>
- <th>Slug</th>
- <th>Description</th>
- <th>Created</th>
- <th>Modified</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- <?php
- // initialise a counter for striping the table
- $count = 0;
- // loop through and display format
- foreach($formats as $format):
- // stripes the table by adding a class to every other row
- $class = ( ($count % 2) ? " class='altrow'": '' );
- // increment count
- $count++;
- ?>
- <tr<?php echo $class; ?>>
- <td><?php echo $format['Format']['id']; ?></td>
- <td><?php echo $format['Format']['name']; ?></td>
- <td><?php echo $format['Format']['slug']; ?></td>
- <td><?php echo $format['Format']['desc']; ?></td>
- <td><?php echo $format['Format']['created']; ?></td>
- <td><?php echo $format['Format']['modified']; ?></td>
- <td>
- <?php echo $html->link('Edit', array('action'=>'admin_edit', $format['Format']['id']) );?>
- <?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']));?>
- </td>
- </tr>
- <?php endforeach; ?>
- </tbody>
- </table>
- <?php
- else:
- echo 'There are currently no Formats in the database.';
- endif;
- ?>
- <ul class="actions">
- <li><?php echo $html->link('Add a Format', array('action'=>'add')); ?></li>
- </ul>
- </div>
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.- /**
- * view()
- * displays a single format and all related dvds
- * url: /formats/view/format_slug
- */
- function view($slug = null) {
- // if slug is null
- if(!$slug) {
- // set a flash message
- $this->Session->setFlash('Invalid id for Format', 'flash_bad');
- // redirect user
- $this->redirect(array('action'=>'index'));
- }
- // find format in database
- $format = $this->Format->findBySlug($slug);
- // if format has been found
- if(!empty($format)) {
- // set the format for the view
- $this->set('format', $format);
- } else {
- // set a flash message
- $this->Session->setFlash('Invalid id for Format', 'flash_bad');
- // redirect user
- $this->redirect(array('action'=>'index'));
- }
- }
- <?php
- // page: /app/views/formats/view.ctp
- ?>
- <div class="formats view">
- <h2>Viewing Format: <?php echo $format['Format']['name']; ?></h2>
- <dl>
- <dt>Name:</dt>
- <dd><?php echo $format['Format']['name']; ?></dd>
- <dt>Description:</dt>
- <dd><?php echo $format['Format']['desc']; ?></dd>
- <dt>Created</dt>
- <dd><?php echo $format['Format']['created']; ?></dd>
- </dl>
- <?php if(!empty($format['Dvd'])): ?>
- <div class="related">
- <h3>DVDs with this Format</h3>
- <table>
- <thead>
- <tr>
- <th>Name</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- <?php foreach($format['Dvd'] as $dvd): ?>
- <tr>
- <td><?php echo $dvd['name']; ?></td>
- <td><?php echo $html->link('View', '/dvds/view/'.$dvd['slug']);?></td>
- </tr>
- <?php endforeach; ?>
- </tbody>
- </table>
- </div>
- <?php endif; ?>
- <ul class="actions">
- <li><?php echo $html->link('List Formats', array('action'=>'index'));?></li>
- </ul>
- </div>
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.- /**
- * admin_add()
- * allows an admin to add a format
- * url: /admin/formats/add
- */
- function admin_add() {
- // if the form data is not empty
- if (!empty($this->data)) {
- // initialise the format model
- $this->Format->create();
- // create the slug
- $this->data['Format']['slug'] = $this->slug($this->data['Format']['name']);
- // try saving the format
- if ($this->Format->save($this->data)) {
- // set a flash message
- $this->Session->setFlash('The Format has been saved', 'flash_good');
- // redirect
- $this->redirect(array('action'=>'index'));
- } else {
- // set a flash message
- $this->Session->setFlash('The Format could not be saved. Please, try again.', 'flash_bad');
- }
- }
- }
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:- $this->methodName();
- /**
- * App Controller
- *
- * file: /app/app_controller.php
- */
- class AppController extends Controller {
- /**
- * slug()
- * creates a slug from a string
- */
- function slug($str) {
- // replace spaces with underscore, all to lowercase
- $str = strtolower(str_replace(' ', '_', $str));
- // create regex pattern
- $pattern = "/[^a-zA-Z0-9_]/";
- // replace non alphanumeric characters
- $str = preg_replace($pattern, '', $str);
- return $str;
- }
- }
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.
- <?php
- // file: /app/views/layouts/flash_good.ctp
- ?>
- <div class="flash_good">
- <?php echo $content_for_layout; ?>
- </div>
- <?php
- // file: /app/views/layouts/flash_bad.ctp
- ?>
- <div class="flash_bad">
- <?php echo $content_for_layout; ?>
- </div>
- <?php
- // file: /app/views/formats/admin_add.ctp
- ?>
- <div class="formats form">
- <?php echo $form->create('Format');?>
- <fieldset>
- <legend>Add a Format</legend>
- <?php
- // create the form inputs
- echo $form->input('name', array('label'=>'Name: *'));
- echo $form->input('desc', array('label'=>'Description:', 'type'=>'textarea'));
- ?>
- </fieldset>
- <?php echo $form->end('Add');?>
- </div>
- <ul class="actions">
- <li><?php echo $html->link('List Formats', array('action'=>'index'));?></li>
- </ul>
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:- // file: /app/models/format.php
- // setup form validation for formats
- var $validate = array(
- // name field
- 'name' => array(
- // must not be empty
- 'rule' => VALID_NOT_EMPTY,
- // error message to display
- 'message' => 'Please enter a Format Name'
- )
- );
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.
- /**
- * admin_edit()
- * allows an admin to edit a format
- * url: /admin/formats/edit/1
- */
- function admin_edit($id = null) {
- // if the id is null and the form data empty
- if (!$id && empty($this->data)) {
- // set a flash message
- $this->Session->setFlash('Invalid Format', 'flash_bad');
- // redirect the user
- $this->redirect(array('action'=>'index'));
- }
- // if the form data is empty
- if (!empty($this->data)) {
- // create the slug
- $this->data['Format']['slug'] = $this->slug($this->data['Format']['name']);
- // try saving the form data
- if ($this->Format->save($this->data)) {
- // set a flash message
- $this->Session->setFlash('The Format has been saved', 'flash_good');
- // redirect
- $this->redirect(array('action'=>'index'));
- } else {
- // set a flash message
- $this->Session->setFlash('The Format could not be saved. Please, try again.', 'flash_bad');
- }
- }
- // if form has not been submitted
- if (empty($this->data)) {
- // find the format from the database and populate the form data
- $this->data = $this->Format->read(null, $id);
- }
- }
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.
- <?php
- // file: /app/views/formats/admin_edit.ctp
- ?>
- <div class="formats form">
- <?php echo $form->create('Format');?>
- <fieldset>
- <legend>Edit Format</legend>
- <?php
- // a hidden form input field, required in a edit form
- echo $form->input('id', array('type'=>'hidden'));
- // create the form inputs
- echo $form->input('name', array('label'=>'Name: *'));
- echo $form->input('desc', array('label'=>'Description:', 'type'=>'textarea'));
- ?>
- </fieldset>
- <?php echo $form->end('Edit');?>
- </div>
- <?php if(!empty($this->data['Dvd'])): ?>
- <div class="related">
- <h3>DVDs with this Format</h3>
- <table>
- <thead>
- <tr>
- <th>Id</th>
- <th>Name</th>
- <th>Actions</th>
- </tr>
- </thead>
- <tbody>
- <?php foreach($this->data['Dvd'] as $dvd): ?>
- <tr>
- <td><?php echo $dvd['id']; ?></td>
- <td><?php echo $dvd['name']; ?></td>
- <td><?php echo $html->link('Edit', '/admin/dvds/edit/'.$dvd['id']);?></td>
- </tr>
- <?php endforeach; ?>
- </tbody>
- </table>
- </div>
- <?php endif; ?>
- <ul class="actions">
- <li><?php echo $html->link('List Formats', array('action'=>'index'));?></li>
- </ul>
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.
- /**
- * admin_delete()
- * allows an admin to delete a format
- * url: /admin/formats/delete/1
- */
- function admin_delete($id = null) {
- // if the id is null
- if (!$id) {
- // set flash message
- $this->Session->setFlash('Invalid id for Format', 'flash_bad');
- // redirect
- $this->redirect(array('action'=>'index'));
- }
- // set the id of the format
- $this->Format->id = $id;
- // try to change status from 1 to 0
- if ($this->Format->saveField('status', 0)) {
- // set flash message
- $this->Session->setFlash('The Format was successfully deleted.', 'flash_good');
- } else {
- // set flash message
- $this->Session->setFlash('The Format could not be deleted. Please try again.', 'flash_bad');
- }
- // redirect
- $this->redirect(array('action'=>'index'));
- }
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.