Welcome to the 10th article in my series about creating a full CakePHP application from start to finish, cant believe I've managed to fill this much space with a single application but its still going and finally taking some shape and its looking pretty, pretty, pretty good (Curb your Enthusiasm Reference - Great Show).
In this article I'm going to be finishing off the header and footer of the main DVD page by adding some functionality to the form and creating some re-usable elements which grabs data from the database. I'm also going to be cleaning a few things up as and when I come across them.
That should be it, I've had a quick check to make sure that everything is working and I didn't come across any errors. Upgrading most frameworks is a very simple process but you must be careful and check that your application doesn't throw any errors or that anything being used was not depreciated.
You can do some pretty advanced stuff with the routes file but it boils down to re-mapping urls to certain Controllers and Actions. In my case I want the default Controller/Action to be /dvds/index, so when the application loads via http://dvdcatalog/ my index() action in the dvds_controller.php will be run.
To do this I need to comment out the first "Router" line and create a new one, this will tell CakePHP to run the appropriate controller action depending on the url being requested. In this case the url will be the very top level e.g. / and will be re-routed to the dvds controller.
First I need to get all the options from the database in list format ready to be used in a form select. CakePHP has this covered by using the $this->find('list') method. I'm going to add a few conditions so the options are in alphabetical order and have not been deleted. I then need to pass these to the view using the $this->set() method.
Just a quick aside about the compact() function, this is a standard PHP function and will try to find any variable being passed and creates an array with the variable as a key. This is great for passing multiple variables to the view.
Now that we have our variables in the view I need to use the form helper to create the select boxes. Using the $form->input() we can pass in the options from the controller and it will automatically create the drop down form element.
In order to save the selected form data and redisplay it once the form has been submitted I've used the selected option, this means that I need to set the variables in the $this->data array if the form has not been submitted to prevent any errors.
Once the form has been submitted I'm going to loop through all the filter options and build up a url which I can use to save the filter terms. The url will consist of /filter_term/selected_option and if more than one filter is selcted they will be added to the url string like this /format/dvd/genre/action. Once complete I'm going to redirect the page using the url so that the filter terms can be saved.
Now that the filters have been set I need to create some more logic in the index action that will parse the filters and create some SQL conditions that will be used when retrieving Dvds from the database.
If any filters are active in the URL they will be stored in the $this->params['pass'] array so I need to check that this is not empty. Then I'm going to loop through the filters, grab the selected value by getting the next item in the array and then perform a switch statement based on the active filter.
In the example below I've done the format example, I've retrieved the Format from the database based on the slug in the filter and then created a SQL where statement to select only the formats in the filter. Once all the SQL has been created in the $conditions array all the Dvds are found in the db based on said conditions.
Creating these conditions are quite simple for formats, types, locations, and searches because all the information is stored in the dvds table however this is not the case for Genres because this is a HABTM association and I'm unable to filter Dvds using SQL alone.
To filter by Genre I'm going to retrieve the Genre from the database and save it for later. Once all the Dvds have been found I need a little processing to remove the Dvds that dont have the Genre selected in the Filter. This seems to be a long winded way of doing things and your right, but SQL does have its limitations and when using a HABTM association its very difficult and often impossible to use the where clause to filter your selection.
In this article I'm going to be finishing off the header and footer of the main DVD page by adding some functionality to the form and creating some re-usable elements which grabs data from the database. I'm also going to be cleaning a few things up as and when I come across them.
Upgrading CakePHP
There have been quite a few updates to CakePHP since starting this application so its about time to upgrade. Download the latest version from the main website and overwrite the cake directory with the latest version.That should be it, I've had a quick check to make sure that everything is working and I didn't come across any errors. Upgrading most frameworks is a very simple process but you must be careful and check that your application doesn't throw any errors or that anything being used was not depreciated.
Defining a Default Start Page
When you first load up a CakePHP application you will be met with a standard page but with most (if not all) applications you develop you will want to change the default starting page. You can do this quite easily by modifying the /app/config/routes.php file, but first have a quick read of the Routes Chapter in the Cookbook.You can do some pretty advanced stuff with the routes file but it boils down to re-mapping urls to certain Controllers and Actions. In my case I want the default Controller/Action to be /dvds/index, so when the application loads via http://dvdcatalog/ my index() action in the dvds_controller.php will be run.
To do this I need to comment out the first "Router" line and create a new one, this will tell CakePHP to run the appropriate controller action depending on the url being requested. In this case the url will be the very top level e.g. / and will be re-routed to the dvds controller.
- // file: /app/config/routes.php
- // from this
- Router::connect('/', array('controller' => 'pages', 'action' => 'display', 'home'));
- // to this
- Router::connect('/', array('controller' => 'dvds', 'action' => 'index'));
Header
Ok so its finally time to implement some functionality into the header form, this is going to quite complex but I'll break it down so that things make sense. I want to be able to filter the dvds by all the options in the form e.g. by Type and Genre and i'm going to make the searches bookmarkable by creating a url to show all the filters being used.First I need to get all the options from the database in list format ready to be used in a form select. CakePHP has this covered by using the $this->find('list') method. I'm going to add a few conditions so the options are in alphabetical order and have not been deleted. I then need to pass these to the view using the $this->set() method.
- // file: app/controllers/dvds_controller.php
- // get all options for form
- $formats = $this->Dvd->Format->find('list', array(
- 'fields' => 'id, name',
- 'order'=>'Format.name',
- 'conditions'=> array(
- 'Format.status'=>'1'
- )
- ));
- $types = $this->Dvd->Type->find('list', array(
- 'fields'=>'id, name',
- 'order'=>'Type.name',
- 'conditions'=> array(
- 'Type.status'=>'1'
- )
- ));
- $locations = $this->Dvd->Location->find('list', array(
- 'fields'=>'id, name',
- 'order'=>'Location.name',
- 'conditions'=> array(
- 'Location.status'=>'1'
- )
- ));
- $genres = $this->Dvd->Genre->find('list', array(
- 'fields'=>'id, name',
- 'order'=>'Genre.name',
- 'conditions'=> array(
- 'Genre.status'=>'1'
- )
- ));
- // add name to option
- $formats = array(''=>'Formats') + $formats;
- $types = array(''=>'Types') + $types;
- $locations = array(''=>'Locations') + $locations;
- $genres = array(''=>'Genres') + $genres;
- // save the dvds in a variable for the view
- $this->set(compact('formats', 'types', 'locations', 'genres', 'dvds'));
Now that we have our variables in the view I need to use the form helper to create the select boxes. Using the $form->input() we can pass in the options from the controller and it will automatically create the drop down form element.
- // file: app/views/elements/index_header.ctp
- <?php echo $form->input('format', array(
- 'label' => '',
- 'type' => 'select',
- 'options' => $formats,
- 'selected' => $this->data['format']
- )); ?>
- // file: app/controllers/dvds_controller.php
- // if form submitted
- if (!empty($this->data)) {
- } else {
- // set form options
- $this->data['format'] = '';
- $this->data['type'] = '';
- $this->data['location'] = '';
- $this->data['genre'] = '';
- $this->data['search'] = '';
- }
- // file: app/controllers/dvds_controller.php
- // if form submitted
- if (!empty($this->data)) {
- // if reset button pressed redirect to index page
- if(isset($this->data['reset'])) {
- $this->redirect(array('action'=>'index'));
- }
- // init
- $url = '';
- // remove search key if not set
- if($this->data['search'] == '') {
- unset($this->data['search']);
- }
- // loop through filters
- foreach($this->data as $key=>$filter) {
- // ignore submit button
- if($key != 'filter') {
- // init
- $selected = '';
- switch($key) {
- case 'format':
- $selected = $formats[$filter];
- break;
- case 'type':
- $selected = $types[$filter];
- break;
- case 'location':
- $selected = $locations[$filter];
- break;
- case 'genre':
- $selected = $genres[$filter];
- break;
- case 'search':
- $selected = $filter;
- break;
- }
- // if filter value is not empty
- if(!empty($filter)) {
- $selected = $this->slug($selected);
- $url .= "/$key/$selected";
- }
- }
- }
- // redirect
- $this->redirect('/dvds/index/'.$url);
- } else {
- }
If any filters are active in the URL they will be stored in the $this->params['pass'] array so I need to check that this is not empty. Then I'm going to loop through the filters, grab the selected value by getting the next item in the array and then perform a switch statement based on the active filter.
In the example below I've done the format example, I've retrieved the Format from the database based on the slug in the filter and then created a SQL where statement to select only the formats in the filter. Once all the SQL has been created in the $conditions array all the Dvds are found in the db based on said conditions.
- // file: app/controllers/dvds_controller.php
- // if any parameters have been passed
- if(!empty($this->params['pass'])) {
- // only select active dvds
- $conditions = array('Dvd.status'=>1);
- // get params
- $params = $this->params['pass'];
- // loop
- foreach($params as $key=>$param) {
- // get the filter value
- if(isset($params[$key+1])) {
- $value = $params[$key+1];
- }
- // switch based on filter is use
- switch($param)
- {
- case 'format':
- // get format
- $format = $this->Dvd->Format->find('first', array(
- 'recursive' => 0,
- 'conditions' => array(
- 'Format.slug'=>$value
- )
- ));
- // set where clause
- $conditions['Dvd.format_id'] = $format['Format']['id'];
- // save value for form
- $this->data['format'] = $format['Format']['id'];
- break;
- case 'type':
- break;
- case 'location':
- break;
- case 'genre':
- break;
- case 'search':
- break;
- }
- // get all dvds with param conditions
- $dvds = $this->Dvd->find('all', array(
- 'order' => 'Dvd.name',
- 'conditions' => $conditions
- ));
- }
- }
To filter by Genre I'm going to retrieve the Genre from the database and save it for later. Once all the Dvds have been found I need a little processing to remove the Dvds that dont have the Genre selected in the Filter. This seems to be a long winded way of doing things and your right, but SQL does have its limitations and when using a HABTM association its very difficult and often impossible to use the where clause to filter your selection.
- // file: app/controllers/dvds_controller.php
- // get the selected genre from thr database
- case 'genre':
- // get genre
- $genre = $this->Dvd->Genre->find('first', array(
- 'recursive' => 0,
- 'conditions' => array(
- 'Genre.slug'=>$value
- )
- ));
- // save value for form
- $this->data['genre'] = $genre['Genre']['id'];
- break;
- // get all dvds with param conditions
- $dvds = $this->Dvd->find('all', array(
- 'order' => 'Dvd.name',
- 'conditions' => $conditions
- ));
- // if genre filter has been set loop through all dvds and remove if the genre
- // doesn't the one we have selected in the filter array
- if(isset($genre)) {
- // loop through dvds
- foreach($dvds as $key=>$dvd) {
- // init
- $found = FALSE;
- // loop through genres
- foreach($dvd['Genre'] as $k=>$g) {
- // if the genre id matches the filter genre no need to continue
- if($g['id'] == $genre['Genre']['id']) {
- $found = TRUE;
- break;
- }
- }
- // if the genre was not found in dvds
- if(!$found) {
- // remove from list
- unset($dvds[$key]);
- }
- }
- }