Full CakePHP Application Part 11

Wow, just looked back at my last post date and realised its been a while. Anyway here's the latest post on creating a full application in CakePHP. Last time I finished off the header and this time I'm going to finish the footer. Check out the online version of the app cos I've been busy adding Dvds and its looking good.
From the view file I'm going to use the $this->requestAction() method to get data from the Dvd Controller. The requestAction() method is used to call a Controller's action from anywhere and can be extremely useful in our view files for requesting specific chunks of data.

Creating the Controller Actions

In the Dvds Controller I'm going to create 2 new methods, footer() will take care of the "top rated", "recently added", and "most active" lists. The second method top_genres() will process the Genres.
I had a quick look in the bakery and came across this article on creating Reusable Elements. Using version 1.2 I can use the paginate() method to get Dvds from the database and also pass parameters which will automatically sort our results.
This is a fantastic feature of version 1.2 and will cut down on some code. The top_genres() method is a little more involved and requires grabbing all the Genres from the database along with related Dvds. I then loop through the genres and count how many Dvds each Genre contains, I then sort the Genres into descending order and finally return the top 5 Genres with the slug as an index and the number of Dvds as the value. The code for that is included below.
In both methods below I first check that $this->params['requested'] is true. This makes sure that the requestAction method was used to get the data and redirects users to the index if they came through a URL e.g. /dvdcatalog/dvds/footer
  1. // file: /app/controllers/dvds_controller.php  
  2.   
  3. /** 
  4.  * footer() 
  5.  * gets dvds from db with passed url options 
  6.  */  
  7. function footer() {  
  8.     // if the data has been requested  
  9.     if(isset($this->params['requested'])) {  
  10.         // only get from dvd table, no joins  
  11.         $this->Dvd->recursive = -1;  
  12.         // return the dvds  
  13.         return $this->paginate();  
  14.     } else {  
  15.         // redirect to index if called directly from url  
  16.         $this->redirect(array('action'=>'index'));  
  17.     }  
  18. }  
  19.   
  20. /** 
  21.  * top_genres() 
  22.  * gets the most active genres from the db 
  23.  */  
  24. function top_genres() {  
  25.     // if the data has been requested  
  26.     if(isset($this->params['requested'])) {  
  27.         // get all genres  
  28.         $genres = $this->Dvd->Genre->find('all');  
  29.         // init  
  30.         $sorted = array();  
  31.         // loop through  
  32.         foreach($genres as $key=>$g) {  
  33.             $sorted[$g['Genre']['slug']] = count($g['Dvd']);  
  34.         }  
  35.         // sort the array  
  36.         arsort($sorted);  
  37.   
  38.         return array_slice($sorted, 0, 5);  
  39.     } else {  
  40.         // redirect to index  
  41.         $this->redirect(array('action'=>'index'));  
  42.     }  
  43. }  

Requesting the data

From the footer view file I can use the $this->requestAction() method to run any controller action and because we're using paginate() to retrieve data from the database we can pass parameters to sort and limit the data.
In the Top Rated list for example I'm getting all the Dvds from the database, sorting them by the rating field, supplying the order in which to order them which is descending and limiting the returned result to 5 dvds. All this is done using the url being passed. How good is that!
  1. // file: /app/views/elements/index_footer.ctp  
  2.   
  3. <h2>Top Rated</h2>  
  4. <ol>  
  5. <?php  
  6. // get top rated dvds  
  7. // i'm using the paginator so i can specifiy sql statements here  
  8. $top_rated = $this->requestAction('dvds/footer/sort:rating/direction:desc/limit:5');  
  9. // loop through dvds  
  10. foreach($top_rated as $dvd) {  
  11.     echo "<li><a href='/dvds/view/".$dvd['Dvd']['slug']."'>".$dvd['Dvd']['name']."</a></li>";  
  12. }  
  13. ?>  
  14. </ol>  
Further reading on paginate() can be viewed here, here and here

Most Active Dvds

I was running out of lists to display so I created a "most active" list that requires some minor changes to the database. Each time a DVD is viewed I'm going to increment a "view" count so that I can order Dvds by the number of times they have been viewed. Here's the SQL for this change:
  1. ALTER TABLE `dvds` ADD `views` INT NOT NULL AFTER `episodes` ;  
A minor change is also required in the view() action of the Dvds Controller. I'm simply going to save the incremented value of the "views" field and this is done every time a Dvd is viewed.
  1. // if dvd has been found  
  2. if(!empty($dvd)) {  
  3.     // save genres string  
  4.     $dvd['Dvd']['genres'] = $this->_create_genre_string($dvd['Genre']);  
  5.     // set the dvd for the view  
  6.     $this->set('dvd'$dvd);  
  7.   
  8.     // increment the number of items the dvd was viewed  
  9.     $this->Dvd->save(array(  
  10.         'Dvd' => array(  
  11.             'id' => $dvd['Dvd']['id'],  
  12.             'views' => ($dvd['Dvd']['views'] + 1)  
  13.         )  
  14.     ));  
  15. }  

Create a Reusable Element

Although the way I've done things is all fine and well, we can improve things some more by creating reusuable elements incase I want to use these lists in other places. The first to do in create 4 new files in the /app/views/elements folder, a file for each of the lists.
Once the code has been extracted to the 4 different files I can include them by calling the $this->element() method, now whenever I want to display these lists I can simply call the method with the name of the file like below.
  1. // file: /app/views/elements/index_footer.ctp  
  2.   
  3. <h2>Top Rated</h2>  
  4. <?php echo $this->element('top_rated');?>  
  1. // file: /app/views/elements/top_rated.ctp  
  2.   
  3. <ol>  
  4. <?php  
  5. // get top rated dvds  
  6. // i'm using the paginator so i can specifiy sql statements here  
  7. $top_rated = $this->requestAction('dvds/footer/sort:rating/direction:desc/limit:5');  
  8. // loop through dvds  
  9. foreach($top_rated as $dvd) {  
  10.     echo "<li><a href='/dvds/view/".$dvd['Dvd']['slug']."'>".$dvd['Dvd']['name']."</a></li>";  
  11. }  
  12. ?>  
  13. </ol>  

Improving Performance

Given that there are quite a few SQL queries being performed on the database we can improve performance by caching the results by a set time period. This is really easy todo by supplying a time period when including the element like this:
  1. // file: /app/views/elements/index_footer.ctp  
  2.   
  3. <h2>Recently Added</h2>  
  4. <?php echo $this->element('recently_added'array('cache'=>'+1 hour'));?>   
Just a quick note make sure your /app/tmp folder is writable with the correct permissions as your file wont be cached.

Wrapping Up

Ok so I've finally got round to completing the front page of the application. I've covered the paginate() method and the process of creating a reusable element that is avaliable througout the application. I've also covered using the requestAction() method for retrieving data from a controller action whilst in a view.

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