Full CakePHP Application Part 5


DVD Catalog CakePHP Application 


DVD Catalog CakePHP Application

Welcome to the 5th Article in the series about creating a full CakePHP 1.2 Application, last time I setup the DVD Controller and View files and I'm now able to start adding DVDs to the system. As a side note I've put the application online at dvd.jamesfairhurst.co.uk so you can have a poke around and see how the application is shaping up.
In this Article I'm going to be dealing with the Genres section of the application so I can start to add / edit / delete Genres from the database. The relationship between DVDs and Genres is a HasAndBelongsToMany (HABTM) which means that a single DVD can have multiple Genres and a Genre can have multiple DVDs. I'm going to be looking at how CakePHP deals with this type of association and how to manually create these associations when saving related data.

Genres

As well as adding, editing and deleting Genres I'm also going to allow the DVDs Controller to add a Genre if it doesn't already exist. I first saw this type of functionality in Wordpress where you can create tags on the fly by inputting all the tags separated by a comma e.g "tag 1, tag 2, tag 3". I quite like this little feature so I'm going to implement it in my admin_add and admin_edit actions in the dvds_controller so that I can simply type all the Genres in a form input and have the controller process all the information and add new ones if required.
Create the genres_controller.php with the skeleton actions along with the following view files:
  • /app/views/genres/index.ctp
  • /app/views/genres/view.ctp
  • /app/views/genres/admin_index.ctp
  • /app/views/genres/admin_add.ctp
  • /app/views/genres/admin_edit.ctp

Genres Index


The index() and admin_index() actions are going to include the same functionality as the previous index() actions that I've created. I'm going to retrieve the Genres from the database (where the status equals and order by the Genre name), save them for the view and finally display the genres in a table.

  1. // file: /app/controllers/genres_controller.php  
  2.   
  3. function index() {  
  4.     // dont get related info  
  5.     $this->Genre->recursive = 0;  
  6.     // get genres from db and save for view  
  7.     $this->set('genres'$this->Genre->findAll("Genre.status=1", null, "Genre.name"));  
  8. }  

Genres View

DVD Catalog CakePHP Application The view() action will attempt to find the Genre in the database given the Genre's slug, if one is found then the Genre is saved for the View. As you can see I've also listed the DVDs that has been assigned to that Genre, in the final application I'm not going to display them in a simple table.
  1. // file: /app/controllers/genres_controller.php  
  2.   
  3. // standard error checking has been removed  
  4. function view($slug = null) {  
  5.     // find genre in database  
  6.     $genre = $this->Genre->findBySlug($slug);  
  7.   
  8.     // if genre has been found  
  9.     if(!empty($genre)) {  
  10.         // set the genre for the view  
  11.         $this->set('genre'$genre);  
  12.     }  
  13. }  

Adding Genres

DVD Catalog CakePHP Application The admin_add() action will first check that a Genre with the same name doesn't already exist and then add the Genre to the database. This is a standard add action so I'm not going to go through the process again, if your stuck then go back through my other articles.

Editing Genres

DVD Catalog CakePHP Application The admin_edit() action will redisplay the Genre in a form and will allow the admin the change the Genre. Again the Controller will check that the new Genre name is not already taken in the database. I'm also going to display a list of all the DVDs that have been assigned to the Genre with a link to edit it. The DVDs are automatically fetched from the database when getting the Genre so I can loop through them like this:
  1. // file: /app/views/genres/admin_edit.ctp  
  2.   
  3. <?php foreach($this->data['Dvd'as $dvd): ?>  
  4. <tr>  
  5.     <td><?php echo $dvd['name']; ?></td>  
  6.     <td><?php echo $html->link('Edit'array('action'=>'admin_edit','controller'=>'dvds'$dvd['id']) );?></td>  
  7. </tr>  
  8. <?php endforeach; ?>  

DVDs and Genres

As I mentioned previously the DVD-Genre association is a hasAndBelongsToMany (HABTM) so if your new to this I suggest looking in the cookbook at the related chapter just so you have an idea of whats involved. I'm going to modify the admin_add() and admin_edit() actions of the DVDs Controller and related Views so that I can insert new Genres when creating a new DVD.
The only thing that needs adding to the View files is a new form input:
  1. echo $form->input('genres'array('label'=>'Genres:''type'=>'text'));  
CakePHP has a great way of dealing with and saving HABTM associations, and its all handled behind the scenes when a Model->save() is called. That is if your data has been formatted correctly.
  1. // sample $this->data array  
  2. Array  
  3. (  
  4.     [Dvd] => Array  
  5.         (  
  6.             [format_id] => 1  
  7.             [type_id] => 1  
  8.             [location_id] => 1  
  9.             [name] => aaa  
  10.             [...] => ...  
  11.         )  
  12.     [Genre] => Array  
  13.         (  
  14.             [Genre] => Array  
  15.                 (  
  16.                     [0] => 1  
  17.                     [1] => 2  
  18.                 )  
  19.         )  
  20. )  
Here the Genres which have an id of '1' and '2' will be associated with the newly created DVD.
In the admin_add() action of the DVDs controller I'm going to create a new private function that will process the Genres from the form, if the Genre doesn't exist create it and finally create the associations between the DVD and the Genres.
In the DVDs Controller I'm going to create a new private function that will take the Genres from the Add DVD form, split each Genre at a comma sign, check to see if the Genre exists in the database. If it doesn't then create a new one and save the insert id, if one already exists grab the id of the Genre. Finally the function will return an array of Genre Id's that will be used to associate the Genre to the DVD.
  1. // file: /app/controllers/dvds_controller.php  
  2.   
  3. /** 
  4.  * _parse_genres() 
  5.  * will parse a string of genres and get the id from the db, 
  6.  * if genre doesn't exist this function will create it 
  7.  */  
  8. function _parse_genres($genres = null) {  
  9.     // variable to save genre data array  
  10.     $data = array();  
  11.   
  12.     // explode the genres sepearated by a comma  
  13.     $explode = explode(','$genres);  
  14.   
  15.     // if the explode array is not empty  
  16.     if(!empty($explode)) {  
  17.         // loop through exploded genres  
  18.         foreach($explode as $genre) {  
  19.             // remove leading/trailing spaces  
  20.             $genre = trim($genre);  
  21.   
  22.             // if the genre is not empty  
  23.             if(!empty($genre)) {  
  24.                 // find the genre in the db  
  25.                 $db_genre = $this->Dvd->Genre->find('first'array(  
  26.                     'conditions' => array(  
  27.                         'Genre.name'    => $genre,  
  28.                         'Genre.status'  => '1'  
  29.                     )  
  30.                 ));  
  31.   
  32.                 // if a genre was found  
  33.                 if(!empty($db_genre)) {  
  34.                     // save the genre id  
  35.                     $data[] = $db_genre['Genre']['id'];  
  36.                 } else {  
  37.                     // create a new genre  
  38.                     $save = array('Genre'=>array(  
  39.                         'name'  => $genre,  
  40.                         'slug'  => $this->slug($genre),  
  41.                         'status'=> 1  
  42.                     ));  
  43.                       
  44.                     // create model  
  45.                     // has to be done when adding multiple items in a row  
  46.                     $this->Dvd->Genre->create();  
  47.   
  48.                     // save the new genre  
  49.                     $saveOK = $this->Dvd->Genre->save($save);  
  50.   
  51.                     // if save was successful  
  52.                     if($saveOK) {  
  53.                         // last insert id  
  54.                         $data[] = $this->Dvd->Genre->getLastInsertID();  
  55.                     }  
  56.                 }  
  57.             }  
  58.         }  
  59.     }  
  60.   
  61. return array('Genre' => $data);  
  62. }  
  63.   
  64. // will return an array like this  
  65. Array  
  66. (  
  67.     [Genre] => Array  
  68.         (  
  69.             [0] => 23  
  70.             [1] => 24  
  71.             [2] => 25  
  72.         )  
  73. )  
I can then use this function in my admin_add() and admin_edit() actions to add the Genre information to the form data array just before it is saved to the database:
  1. // file: /app/controllers/dvds_controller.php  
  2.   
  3. // save the genres to the form data array  
  4. $this->data['Genre'] = $this->_parse_genres($this->data['Dvd']['genres']);  
  5.   
  6. // try saving the dvd  
  7. if($this->Dvd->save($this->data)) {  
  8. // set flash and redirect  
  9. }  
I've also created another small private function to take an array of Genres from the database and return a string of genres seperated by comma's. This will be used in the view() and admin_edit() actions to display the Genres.
  1. // file: /app/controllers/dvds_controller.php  
  2.   
  3. /** 
  4.  * _create_genre_string() 
  5.  * will create a string of genres seperated by a single comma e.g. genre1, genre2, genre3,  
  6.  */  
  7. function _create_genre_string($genres) {  
  8.     // init  
  9.     $genre_string = '';  
  10.   
  11.     // if genres array not empty  
  12.     if(!empty($genres)) {  
  13.         // loop through genres and save name as a string  
  14.         foreach($genres as $g) {  
  15.             $genre_string .= $g['name'].", ";  
  16.         }  
  17.     }  
  18.   
  19. return $genre_string;  
  20. }  
Here's a screenshot of the Edit DVD page with the Genre's at the bottom:
DVD Catalog CakePHP Application

Deleting Genres

When deleting a Genre from the database I must also remove the association between any DVDs that may be attached. This is problematic because I've chosen to use a "soft delete" approach when deleting items from the database.
What I'm going to do instead is change the status of the Genre to '0' meaning it is not active and then manually delete the association using a custom SQL statement. This is quite simple as I just need to delete everything with the Id of the Genre that is being deleted.
  1. // file: /app/controllers/genres_controller.php  
  2.   
  3. /** 
  4.  * admin_delete() 
  5.  * allow an admin to delete a genre 
  6.  * url: admin/genres/delete/1 
  7.  */  
  8. function admin_delete($id = null) {  
  9.     // check genre is valid and exists  
  10.     $genre = $this->_check_genre($id);  
  11.   
  12.     // set the id of the genre  
  13.     $this->Genre->id = $id;  
  14.   
  15.     // try to change status from 1 to 0  
  16.     if ($this->Genre->saveField('status', 0)) {  
  17.         // delete the genre-dvd association from the join table  
  18.         // create the sql statement to remove association  
  19.         $sql = "DELETE FROM `dvds_genres` WHERE genre_id={$id}";  
  20.   
  21.         // run the sql query  
  22.         $this->Genre->query($sql);  
  23.   
  24.         // set flash message  
  25.         $this->Session->setFlash('The Genre was successfully deleted.''flash_good');  
  26.     } else {  
  27.         // set flash message  
  28.         $this->Session->setFlash('The Genre could not be deleted. Please try again.''flash_bad');  
  29.     }  
  30.   
  31.     // redirect  
  32.     $this->redirect(array('action'=>'index'));  
  33. }  

Wrapping Up

The application is finally taking shape with the addition of Genres, which can be added in both the Genres Controller as well as the DVDs Controller. The Has And Belongs To Many association is quite complex and difficult to get your head around if your new but keeping reading the manual and experiment with it in your applications. If you have any problems with anything in the series then drop 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