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.
- // file: /app/controllers/genres_controller.php
- function index() {
- // dont get related info
- $this->Genre->recursive = 0;
- // get genres from db and save for view
- $this->set('genres', $this->Genre->findAll("Genre.status=1", null, "Genre.name"));
- }
Genres View
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.- // file: /app/controllers/genres_controller.php
- // standard error checking has been removed
- function view($slug = null) {
- // find genre in database
- $genre = $this->Genre->findBySlug($slug);
- // if genre has been found
- if(!empty($genre)) {
- // set the genre for the view
- $this->set('genre', $genre);
- }
- }
Adding Genres
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
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:- // file: /app/views/genres/admin_edit.ctp
- <?php foreach($this->data['Dvd'] as $dvd): ?>
- <tr>
- <td><?php echo $dvd['name']; ?></td>
- <td><?php echo $html->link('Edit', array('action'=>'admin_edit','controller'=>'dvds', $dvd['id']) );?></td>
- </tr>
- <?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:
- echo $form->input('genres', array('label'=>'Genres:', 'type'=>'text'));
- // sample $this->data array
- Array
- (
- [Dvd] => Array
- (
- [format_id] => 1
- [type_id] => 1
- [location_id] => 1
- [name] => aaa
- [...] => ...
- )
- [Genre] => Array
- (
- [Genre] => Array
- (
- [0] => 1
- [1] => 2
- )
- )
- )
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.
- // file: /app/controllers/dvds_controller.php
- /**
- * _parse_genres()
- * will parse a string of genres and get the id from the db,
- * if genre doesn't exist this function will create it
- */
- function _parse_genres($genres = null) {
- // variable to save genre data array
- $data = array();
- // explode the genres sepearated by a comma
- $explode = explode(',', $genres);
- // if the explode array is not empty
- if(!empty($explode)) {
- // loop through exploded genres
- foreach($explode as $genre) {
- // remove leading/trailing spaces
- $genre = trim($genre);
- // if the genre is not empty
- if(!empty($genre)) {
- // find the genre in the db
- $db_genre = $this->Dvd->Genre->find('first', array(
- 'conditions' => array(
- 'Genre.name' => $genre,
- 'Genre.status' => '1'
- )
- ));
- // if a genre was found
- if(!empty($db_genre)) {
- // save the genre id
- $data[] = $db_genre['Genre']['id'];
- } else {
- // create a new genre
- $save = array('Genre'=>array(
- 'name' => $genre,
- 'slug' => $this->slug($genre),
- 'status'=> 1
- ));
- // create model
- // has to be done when adding multiple items in a row
- $this->Dvd->Genre->create();
- // save the new genre
- $saveOK = $this->Dvd->Genre->save($save);
- // if save was successful
- if($saveOK) {
- // last insert id
- $data[] = $this->Dvd->Genre->getLastInsertID();
- }
- }
- }
- }
- }
- return array('Genre' => $data);
- }
- // will return an array like this
- Array
- (
- [Genre] => Array
- (
- [0] => 23
- [1] => 24
- [2] => 25
- )
- )
- // file: /app/controllers/dvds_controller.php
- // save the genres to the form data array
- $this->data['Genre'] = $this->_parse_genres($this->data['Dvd']['genres']);
- // try saving the dvd
- if($this->Dvd->save($this->data)) {
- // set flash and redirect
- }
- // file: /app/controllers/dvds_controller.php
- /**
- * _create_genre_string()
- * will create a string of genres seperated by a single comma e.g. genre1, genre2, genre3,
- */
- function _create_genre_string($genres) {
- // init
- $genre_string = '';
- // if genres array not empty
- if(!empty($genres)) {
- // loop through genres and save name as a string
- foreach($genres as $g) {
- $genre_string .= $g['name'].", ";
- }
- }
- return $genre_string;
- }
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.
- // file: /app/controllers/genres_controller.php
- /**
- * admin_delete()
- * allow an admin to delete a genre
- * url: admin/genres/delete/1
- */
- function admin_delete($id = null) {
- // check genre is valid and exists
- $genre = $this->_check_genre($id);
- // set the id of the genre
- $this->Genre->id = $id;
- // try to change status from 1 to 0
- if ($this->Genre->saveField('status', 0)) {
- // delete the genre-dvd association from the join table
- // create the sql statement to remove association
- $sql = "DELETE FROM `dvds_genres` WHERE genre_id={$id}";
- // run the sql query
- $this->Genre->query($sql);
- // set flash message
- $this->Session->setFlash('The Genre was successfully deleted.', 'flash_good');
- } else {
- // set flash message
- $this->Session->setFlash('The Genre could not be deleted. Please try again.', 'flash_bad');
- }
- // redirect
- $this->redirect(array('action'=>'index'));
- }