Full CakePHP 1.2 App 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:

/**
 * 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);
}

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.

<?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>
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.

/**
 * 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'));
	}
}

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.

<?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>
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.

/**
 * 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();

Although CakePHP has an inbuilt slug function it doesn't deal with a few characters correctly so I've created my own:

/**
 * 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;
	}
}

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.

<?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>

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:

<?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>
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:

// 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'
	)
);

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.

/**
 * 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);
	}
}

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.

<?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>
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.

	/**
	 * 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.

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! :)

Next Article

In the next article I'm going to go through a similar process and create the Controller and View files for the Types and will reiterate the techniques that I have gone through in this article. At the end you will be able to view all the types in the application as well as add / edit and delete types from the database.

Posted on 24th April 2008
9 years, 3 months, 4 weeks, 1 day ago

comments powered by Disqus