Difference between revisions of "Workshop - week 02"

From mi-linux
Jump to navigationJump to search
m
 
(12 intermediate revisions by 5 users not shown)
Line 1: Line 1:
[[Main Page]] >> [[Web Frameworks]] >> [[Web Frameworks - Workbook]] >> Workshop - week 02
+
[[Main Page]] >> [[CP2132|Web Frameworks]] >> [[Web Frameworks - Workbook|Workbook]] >> Workshop - week 02
  
===Setting Up Our Default Controller and View===
+
===Setting Up Our Default Controller===
  
Next we need to set up our [http://framework.zend.com/docs/quickstart/create-an-action-controller-and-view default controller and view]. As we are using the Zend Framework defaults, the controller class only requires skeleton code.
+
====Create an Action Controller====
  
===Error Controller and View===
+
In Zend Framework's MVC implementation, both the default action controller and the default action are named 'index'. You should therefore set up the default action controller by creating application/controllers/IndexController.php with the following contents:
  
Because of the change we made by making all requests return the default view, essentially we remove the need for the ErrorHandler that is handling EXCEPTION_NO_CONTROLLER. However, it is good practice to set up the respective error handlers, and so we will continue to [http://framework.zend.com/docs/quickstart/create-an-error-controller-and-view follow the tutorial].
+
<pre>
 +
<?php
 +
// application/controllers/IndexController.php
  
===4. Creating the Layout===
+
/**
 +
* IndexController is the default controller for this application
 +
*
 +
* Notice that we do not have to require 'Zend/Controller/Action.php', this
 +
* is because our application is using "autoloading" in the bootstrap.
 +
*
 +
* @see http://framework.zend.com/manual/en/zend.loader.html#zend.loader.load.autoload
 +
*/
 +
class IndexController extends Zend_Controller_Action
 +
{
 +
    /**
 +
    * The "index" action is the default action for all controllers. This
 +
    * will be the landing page of your application.
 +
    *
 +
    * Assuming the default route and default router, this action is dispatched
 +
    * via the following urls:
 +
    *  /
 +
    *  /index/
 +
    *  /index/index
 +
    *
 +
    * @return void
 +
    */
 +
    public function indexAction()
 +
    {
 +
        /*
 +
          There is nothing inside this action, but it will still attempt to
 +
          render a view.  This is because by default, the front controller
 +
          uses the ViewRenderer action helper to handle auto rendering
 +
          In the MVC grand scheme of things, the ViewRenderer allows us to
 +
          draw the line between the C and V in the MVC.  Also note this action
 +
          helper is optional, but on default.
 +
        */
 +
    }
 +
}
 +
</pre>
  
We can think of the [http://framework.zend.com/docs/quickstart/create-a-layout layout page] as a template that defines a consistent look and feel for all pages within the MVC application. The controllers provide views that fill in what is unique about a particular page. Combined together, they provide us with a complete HTML/XHTML page. As the tutorial suggests, this is the reason why the layout is a good place to set up headers and footers that we want to remain the same for all our pages.
+
Because we're using the standard router, requests for http://mi-linux.wlv.ac.uk/~YourStudentNumber/QuickStart/public will be routed to the indexAction method on the IndexController action controller, as will requests to http://http://mi-linux.wlv.ac.uk/~YourStudentNumber/QuickStart/public/index and http://mi-linux.wlv.ac.uk/~YourStudentNumber/QuickStart/public/index/index.
  
===5. Configuration and Registry===
+
For further information on Zend routes and routers, check out the following: http://framework.zend.com/manual/en/zend.controller.router.html
  
In older web programming methodologies, programmers would often create a file to be 'included' by all pages that needed access to database connections. This had many disadvantages, such as providing all the code for database connectivity often to every page, even when a database connection was not required for the page. There was also the security considerations of having a file with all the database connection details in a location where it may be accessible by anonymous users.
+
====Controller and Action Name Conventions====
  
A more modern take on the concept is to have a [http://framework.zend.com/docs/quickstart/create-a-configuration-and-registry registry / configuration file], and classes to provide access to the data. It works much like an .ini file for desktop applications - providing a place to store application-wide settings, such as database connection strings. It works much like a hashtable, where data can be retrieved by key name. [http://framework.zend.com/docs/quickstart/create-a-configuration-and-registry We need to create] our app.ini file, and modify the boostrap.php file to include a reference to the app.ini. In the case of our QuickStart application, we will then be able to access the database settings from any page we choose.
+
To work correctly with the standard router, action method names should follow the lowerCamelCase convention with the suffix 'Action'. Action controller classes, on the other hand, should follow the UpperCamelCase convention with the suffix 'Controller'. While the standard router should work for almost all applications, routing in Zend Framework is completely customizable. See the Reference Guide for details.
  
===6. Model and Database===
+
It may seem like indexAction() is doing nothing at all, but again Zend Framework is doing useful things for you behind the scenes. In this case it is routing the request to the appropriate view to return the correct response. Unless otherwise configured, the view it will look for is views/scripts/index/index.phtml, where the views directory is contained in the same folder as the controllers directory you passed to Zend_Controller_Front::run(). The file extension for views is yet another best practice recommended for Zend Framework applications; since view templates look suspiciously like PHP scripts in ZF, it is easier to distinguish view templates from other PHP files if you use the .phtml extension instead of .php.
  
See [http://framework.zend.com/docs/quickstart/create-a-model-and-database-table here].
+
Let's create the view script now with the following contents:
  
====Database====
+
<pre>
 +
<? // application/views/scripts/index/index.phtml ?>
 +
<h1 align="center">
 +
    Hello, Zend Framework MVC!
 +
</h1>
 +
</pre>
 +
 
 +
====Create an Error Controller & View====
  
=====Database Connection=====
+
Because of the change we made by making all requests return the default view, essentially we remove the need for the ErrorHandler that is handling EXCEPTION_NO_CONTROLLER. However, it is good practice to set up the respective error handlers, and so we will continue.
  
Next, we need somewhere to store our data. We modify the app.ini file to include the database connection details (We are setting up a SQLite database - it is not a database you will necessarily be familiar with. Rather than having a database program running on a server, SQLite can have an entire database stored within a file - like using an XML file, but with all the benefits of SQL queries.)
+
The last things you want to display to an end user of your site are either a blank page or exceptions. Zend Framework's MVC provides an ErrorHandler plugin that will detect exceptions and dispatch an ErrorController so that you can easily display appropriate content to your end user.
  
We then need to modify the bootstrap.php file, to include the new connection details to the database we are about to create.
+
Additionally, you need some way to handle 404 errors- i.e., situations when the action or controller are not found.
  
=====Database Schema=====
+
In development, you do want to see exceptions, however. Fortunately, ZF MVC can accomodate both situations.
  
We then create two SQL script files - one that defines the structure of the database that we want to set up, and another with some initial test data to be inserted into the new database.
+
The ErrorHandler plugin by default will dispatch the errorAction() of the ErrorController. Let's build our controller.
  
=====Setup Script=====
+
<pre>
 +
<?php
 +
// application/controllers/ErrorController.php
  
This PHP file will be run just once, and will create our database by executing the first script, and fill it with data as outlined by our second script. Once scripts/load.sqlite.php has been executed successfully, the file can be deleted - but don't do so until you have a fully working application, in case you need to execute the database setup again.
+
/**
 +
* ErrorController
 +
*/
 +
class ErrorController extends Zend_Controller_Action
 +
{
 +
    /**
 +
    * errorAction() is the action that will be called by the "ErrorHandler"
 +
    * plugin. When an error/exception has been encountered
 +
    * in a ZF MVC application (assuming the ErrorHandler has not been disabled
 +
    * in your bootstrap) - the Errorhandler will set the next dispatchable
 +
    * action to come here.  This is the "default" module, "error" controller,
 +
    * specifically, the "error" action.  These options are configurable.
 +
    *
 +
    * @see http://framework.zend.com/manual/en/zend.controller.plugins.html
 +
    *
 +
    * @return void
 +
    */
 +
    public function errorAction()
 +
    {
 +
        // Ensure the default view suffix is used so we always return good
 +
        // content
 +
        $this->_helper->viewRenderer->setViewSuffix('phtml');
  
'''Pro Tip:''' Remember that the directories that the database is being set up within - data/db/ - need to have the correct permissions set. At this stage, CHMOD it to 777 (read, write and execute for all) to help get the application running.
+
        // Grab the error object from the request
 +
        $errors = $this->_getParam('error_handler');
  
====Coding====
+
        // $errors will be an object set as a parameter of the request object,
 +
        // type is a property
 +
        switch ($errors->type) {
 +
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER:
 +
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
  
=====Data Connector=====
+
                // 404 error -- controller or action not found
 +
                $this->getResponse()->setHttpResponseCode(404);
 +
                $this->view->message = 'Page not found';
 +
                break;
 +
            default:
 +
                // application error
 +
                $this->getResponse()->setHttpResponseCode(500);
 +
                $this->view->message = 'Application error';
 +
                break;
 +
        }
  
Now we need a small class to define how our normal database queries (update, insert, delete) should be handled. In our system, we only require an insert method, as we'll be only allowing inserts into the guestbook. However, to follow best practice we include an update method, but set it so that it will throw an exception error if the update is called. Our insert method with automatically add the date and time of the insert to the database.
+
        // pass the environment to the view script so we can conditionally
 +
        // display more/less information
 +
        $this->view->env      = $this->getInvokeArg('env');
 +
       
 +
        // pass the actual exception object to the view
 +
        $this->view->exception = $errors->exception;
 +
       
 +
        // pass the request to the view
 +
        $this->view->request  = $errors->request;
 +
    }
 +
}
 +
</pre>
  
 +
The above may seem a bit esoteric.
  
=====Set-up Model File=====
+
First, the ErrorHandler plugin stores error information- including the error type, exception caught, and the request that produced it- within a token that is passed in the request. We store that in the $errors variable.
  
The model is what establishes business rules within our application. All methods relating to how our page does what it does should be defined here. We need to be able to retrive records from our database, so that the guestbook can display entries. It includes a function to return one entry, all entries - as well as a function that uses the insert we just set up to save new entries to the database.
+
Second, we do some switching based on the exception type. There are two types of exceptions that represent 404 errors: controller not found and action not found. For these, we change the HTTP response code to 404 to indicate this. All other exceptions are considered application exceptions, and we'll return the appropriate HTTP response code for these- 500.
  
We have one more line that needs changing in here:
+
Third, remember the env variable you passed to the front controller in the bootstrap file? We're going to get that from the front controller and pass it to the view. More on this later.
  
<pre>    protected function _getGuestbookForm()
+
Finally, we pass the exception and request to the view.
    {
 
        require_once APPLICATION_PATH . '/forms/GuestBook.php';
 
        $form = new Form_GuestBook();
 
        $form->setAction($this->_helper->url('sign'));
 
        return $form;
 
    }
 
</pre>
 
  
Should become:
+
Let's look at the related view script, which we'll place in application/views/scripts/error/error.phtml.
  
 
<pre>
 
<pre>
    protected function _getGuestbookForm()
+
<? // application/views/scripts/error/error.phtml ?>
    {
+
 
        require_once APPLICATION_PATH . '/forms/GuestBook.php';
+
<h1>An error occurred</h1>
        $form = new Form_GuestBook();
+
<h2><?= $this->message ?></h2>
        $form->setAction($this->_request->getBaseUrl() . $this->_helper->url('sign'));
+
 
        return $form;
+
<? if ('development' == $this->env): ?>
     }
+
    <h3>Exception information:</h3>
</pre>
+
    <p>
 +
        <b>Message:</b> <?= $this->exception->getMessage() ?>
 +
     </p>  
  
=====Guestbook Controller=====
+
    <h3>Stack trace:</h3>
 +
    <?= $this->exception->getTraceAsString() ?>
  
[http://framework.zend.com/docs/quickstart/create-a-form In our final steps], we need to create a form through which entries can be inserted.
+
    <h3>Request Parameters:</h3>
To tie all the functionality together we have just implemented, we need our guestbook controller. This will call functions in the data connector class we just completed, so that data to be saved can be passed to it, and retrieved records can be displayed through our view.
+
    <? var_dump($this->request->getParams()) ?>
 +
<? endif ?>
 +
</pre>
  
=====Guestbook View=====
+
We're now using the env variable in the view script to determine whether or not to display exception information. In development, you'll get all the information for debugging. Changing the value in your bootstrap.php file will prevent this sensitive information from being displayed, while providing a reasonable error message to your end user.
  
And as we have our guestbook controller, we need a guestbook view. This will be used to display our guestbook records, as well as offering a link to set our guestbook action to sign, so that a record can be inserted.
+
==References==
 
  
===7. Form===
+
All work relating to the QuickStart tutorial is © 2006 - 2009 by Zend Technologies Ltd. All rights reserved.
  
We now [http://framework.zend.com/docs/quickstart/create-a-form need a form]; somewhere our users can actually insert their entry. We create a function to initialize the class, which includes all the controls needed for the form, such as the text boxes and the captcha. As can be seen, a captcha control exists within the Zend Framework - we simply state what kind we want, how many letters should be displayed, and what the timeout is before it becomes invalid.
+
http://framework.zend.com/docs/quickstart/
  
We then modify our GuestbookController to include a sign action; providing users with a means of signing our guestbook.
+
http://www.johnmee.com/2008/11/zend-framework-quickstart-tutorial-deploy-to-a-subdirectory-instead-of-web-root/
  
And as we know by know - new code in the Controller giving us something new to process = new view, so we create a new view for our signing action.
+
== Ready to move on? ==
  
And with the signing view complete, we should have a fully working GuestBook application. Load it up and test it out.
+
When you're happy you understand what you've done here, take a look at the [[Workshop_-_week_03|Week 3 Workshop]]

Latest revision as of 08:13, 23 July 2009

Main Page >> Web Frameworks >> Workbook >> Workshop - week 02

Setting Up Our Default Controller

Create an Action Controller

In Zend Framework's MVC implementation, both the default action controller and the default action are named 'index'. You should therefore set up the default action controller by creating application/controllers/IndexController.php with the following contents:

<?php
// application/controllers/IndexController.php

/**
 * IndexController is the default controller for this application
 * 
 * Notice that we do not have to require 'Zend/Controller/Action.php', this
 * is because our application is using "autoloading" in the bootstrap.
 *
 * @see http://framework.zend.com/manual/en/zend.loader.html#zend.loader.load.autoload
 */
class IndexController extends Zend_Controller_Action 
{
    /**
     * The "index" action is the default action for all controllers. This 
     * will be the landing page of your application.
     *
     * Assuming the default route and default router, this action is dispatched 
     * via the following urls:
     *   /
     *   /index/
     *   /index/index
     *
     * @return void
     */
    public function indexAction() 
    {
        /*
           There is nothing inside this action, but it will still attempt to 
           render a view.  This is because by default, the front controller 
           uses the ViewRenderer action helper to handle auto rendering
           In the MVC grand scheme of things, the ViewRenderer allows us to 
           draw the line between the C and V in the MVC.  Also note this action
           helper is optional, but on default.
        */ 
    }
}

Because we're using the standard router, requests for http://mi-linux.wlv.ac.uk/~YourStudentNumber/QuickStart/public will be routed to the indexAction method on the IndexController action controller, as will requests to http://http://mi-linux.wlv.ac.uk/~YourStudentNumber/QuickStart/public/index and http://mi-linux.wlv.ac.uk/~YourStudentNumber/QuickStart/public/index/index.

For further information on Zend routes and routers, check out the following: http://framework.zend.com/manual/en/zend.controller.router.html

Controller and Action Name Conventions

To work correctly with the standard router, action method names should follow the lowerCamelCase convention with the suffix 'Action'. Action controller classes, on the other hand, should follow the UpperCamelCase convention with the suffix 'Controller'. While the standard router should work for almost all applications, routing in Zend Framework is completely customizable. See the Reference Guide for details.

It may seem like indexAction() is doing nothing at all, but again Zend Framework is doing useful things for you behind the scenes. In this case it is routing the request to the appropriate view to return the correct response. Unless otherwise configured, the view it will look for is views/scripts/index/index.phtml, where the views directory is contained in the same folder as the controllers directory you passed to Zend_Controller_Front::run(). The file extension for views is yet another best practice recommended for Zend Framework applications; since view templates look suspiciously like PHP scripts in ZF, it is easier to distinguish view templates from other PHP files if you use the .phtml extension instead of .php.

Let's create the view script now with the following contents:

<? // application/views/scripts/index/index.phtml ?>
<h1 align="center">
    Hello, Zend Framework MVC!
</h1>

Create an Error Controller & View

Because of the change we made by making all requests return the default view, essentially we remove the need for the ErrorHandler that is handling EXCEPTION_NO_CONTROLLER. However, it is good practice to set up the respective error handlers, and so we will continue.

The last things you want to display to an end user of your site are either a blank page or exceptions. Zend Framework's MVC provides an ErrorHandler plugin that will detect exceptions and dispatch an ErrorController so that you can easily display appropriate content to your end user.

Additionally, you need some way to handle 404 errors- i.e., situations when the action or controller are not found.

In development, you do want to see exceptions, however. Fortunately, ZF MVC can accomodate both situations.

The ErrorHandler plugin by default will dispatch the errorAction() of the ErrorController. Let's build our controller.

<?php 
// application/controllers/ErrorController.php

/**
 * ErrorController
 */ 
class ErrorController extends Zend_Controller_Action 
{ 
    /**
     * errorAction() is the action that will be called by the "ErrorHandler" 
     * plugin.  When an error/exception has been encountered
     * in a ZF MVC application (assuming the ErrorHandler has not been disabled
     * in your bootstrap) - the Errorhandler will set the next dispatchable 
     * action to come here.  This is the "default" module, "error" controller, 
     * specifically, the "error" action.  These options are configurable. 
     * 
     * @see http://framework.zend.com/manual/en/zend.controller.plugins.html
     *
     * @return void
     */
    public function errorAction() 
    { 
        // Ensure the default view suffix is used so we always return good 
        // content
        $this->_helper->viewRenderer->setViewSuffix('phtml');

        // Grab the error object from the request
        $errors = $this->_getParam('error_handler'); 

        // $errors will be an object set as a parameter of the request object, 
        // type is a property
        switch ($errors->type) { 
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER: 
            case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION: 

                // 404 error -- controller or action not found 
                $this->getResponse()->setHttpResponseCode(404); 
                $this->view->message = 'Page not found'; 
                break; 
            default: 
                // application error 
                $this->getResponse()->setHttpResponseCode(500); 
                $this->view->message = 'Application error'; 
                break; 
        } 

        // pass the environment to the view script so we can conditionally 
        // display more/less information
        $this->view->env       = $this->getInvokeArg('env'); 
        
        // pass the actual exception object to the view
        $this->view->exception = $errors->exception; 
        
        // pass the request to the view
        $this->view->request   = $errors->request; 
    } 
}

The above may seem a bit esoteric.

First, the ErrorHandler plugin stores error information- including the error type, exception caught, and the request that produced it- within a token that is passed in the request. We store that in the $errors variable.

Second, we do some switching based on the exception type. There are two types of exceptions that represent 404 errors: controller not found and action not found. For these, we change the HTTP response code to 404 to indicate this. All other exceptions are considered application exceptions, and we'll return the appropriate HTTP response code for these- 500.

Third, remember the env variable you passed to the front controller in the bootstrap file? We're going to get that from the front controller and pass it to the view. More on this later.

Finally, we pass the exception and request to the view.

Let's look at the related view script, which we'll place in application/views/scripts/error/error.phtml.

<? // application/views/scripts/error/error.phtml ?>

<h1>An error occurred</h1> 
<h2><?= $this->message ?></h2> 

<? if ('development' == $this->env): ?> 
    <h3>Exception information:</h3> 
    <p> 
        <b>Message:</b> <?= $this->exception->getMessage() ?> 
    </p> 

    <h3>Stack trace:</h3> 
    <?= $this->exception->getTraceAsString() ?>

    <h3>Request Parameters:</h3> 
    <? var_dump($this->request->getParams()) ?>
<? endif ?>

We're now using the env variable in the view script to determine whether or not to display exception information. In development, you'll get all the information for debugging. Changing the value in your bootstrap.php file will prevent this sensitive information from being displayed, while providing a reasonable error message to your end user.

References

All work relating to the QuickStart tutorial is © 2006 - 2009 by Zend Technologies Ltd. All rights reserved.

http://framework.zend.com/docs/quickstart/

http://www.johnmee.com/2008/11/zend-framework-quickstart-tutorial-deploy-to-a-subdirectory-instead-of-web-root/

Ready to move on?

When you're happy you understand what you've done here, take a look at the Week 3 Workshop