Archive

Posts Tagged ‘controller’

Using mod_rewrite And Zend Framework To Display Dynamic sitemap.xml

April 3rd, 2009 1 comment

Whilst creating a site the other day I thought about how I would manage the sitemap.xml file. This file is basically a XML file containing a list of URLs. Most major search engines understand (and look for) this file, so having it present on a site is a definite must.

I have been down the route before of having a sitemap.xml file created by the application every time a new record or something was added, but as this was a high traffic, multi-user site this approach just had to many problems. The main problem (aside from the potential performance hit) was that I would have to spend hours tying the calls to the sitemap.xml creation file into my application.

I then hit upon the idea of using a RewriteRule that would mask a controller as the sitemap.xml file. This would mean that the sitemap.xml controls could be kept away from all other parts of the application (so I could use the same template again), but I could also use Zend_Cache to cache the sitemap.xml file daily and therefore save on processing time.

First I needed to create a RewriteRule that would redirect a call to sitemap.xml to the Sitemap controller.

RewriteRule ^(.*)sitemap.xml$ /sitemap/index [L]

Next I created the Sitemap controller and made sure that the index action did not show the layout. The URLs are passed as an array to the view.

class SitemapController extends Zend_Controller_Action
{
    public function indexAction()
    {
        $this->_helper->layout()->disableLayout();
        $urls = array(array('loc'=>'http://www.talkincode.com/', 'lastmod'=>'2009-04-02T11:34:48+00:00', 'changefreq'=>'daily', 'priority'=>'1.0'));
        
        $this->view->urls = $urls;
    }
}

In order for the Sitemap controller to display anything it needs to have a view to render. This creates the basic outline of the file and uses the partialLoop() function to print out the array of URLs.

<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
    <?php echo $this->partialLoop('sitemap/_urlItem.phtml',$this->urls); ?>
</urlset>

Here is the file _urlItem.phtml, which gets rendered for every item in the $this->urls array.

<url> 
    <loc><?php echo $this->loc; ?></loc> 
    <lastmod><?php echo $this->lastmod; ?></lastmod> 
    <changefreq><?php echo $this->changefreq; ?></changefreq> 
    <priority><?php echo $this->priority; ?></priority> 
</url>

I assumed that everything would be working nicely now, but when I went to a browser and tried to find sitemap.xml I was presented with a message that said sitemap.xml was an invalid controller.

Just to test I added a redirect to the end of the RewriteRule to make sure that the rule worked.

RewriteRule ^(.*)sitemap.xml$ /sitemap/index [R=301, L]

This redirected to the correct place, so it must have been Zend Framework that was causing the error to occur. After a bit of thinking I realised that I could create a route that would reroute the call to the missing sitemap.xml controller to the existing Sitemap controller. Here is the rule I created, just add this to your bootstrap file.

$router = $frontController->getRouter();
$router->addRoute(
    'manageSitemap',
    new Zend_Controller_Router_Route('sitemap.xml', array('controller'=>'sitemap','action'=>'index'))
);

Navigating to sitemap.xml now shows me the output of the Sitemap controller.

A Useful Error Controller Class For Zend Framework Applications

December 23rd, 2008 7 comments

One useful function of any application is to report on any errors that occurred. Zend Framework comes with a nice error controller system that you can activate by creating an ErrorController class.

The following is an ErrorController class that I use. It detects what sort of error occurred and displays a message to the user. It will also email a detailed report of the error to the server admins.

class ErrorController extends Zend_Controller_Action
{
  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');
 
    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';
        $this->view->code  = 404;
        if ($errors->type == Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_CONTROLLER) {
          $this->view->info = sprintf(
                      'Unable to find controller "%s" in module "%s"',
                      $errors->request->getControllerName(),
                      $errors->request->getModuleName()
                    );
        }
        if ($errors->type == Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION) {
          $this->view->info = sprintf(
                      'Unable to find action "%s" in controller "%s" in module "%s"',
                      $errors->request->getActionName(),
                      $errors->request->getControllerName(),
                      $errors->request->getModuleName()
                    );
        }
        break;
      case Zend_Controller_Plugin_ErrorHandler::EXCEPTION_NO_ACTION:
      default:
        // application error
        $this->getResponse()->setHttpResponseCode(500);
        $this->view->message = 'Application error';
        $this->view->code  = 500;
        $this->view->info  = $errors->exception;
        break;
    }
 
    // send a mail to let someone know that there was a problem!
    $config = Zend_Registry::get('configuration');
    $tr = new Zend_Mail_Transport_Smtp($config->smtp);
    Zend_Mail::setDefaultTransport($tr);
 
    $mail = new Zend_Mail();
    $emailBody = "An error occurred in the system. The error message contained the following output:\n\n";
    $emailBody .= $this->view->message." (".$this->view->code.")\n\n";
    $emailBody .= "Zend Error Type: ".$errors->type."\n\n";
    $emailBody .= "REQUEST_URI: ".$_SERVER['REQUEST_URI']."\n\n";
    if ( isset($_SERVER['HTTP_REFERER']) ) {
      $emailBody .= "HTTP_REFERER: ".$_SERVER['HTTP_REFERER']."\n\n";
    }
    $emailBody .= "Stack trace: \n\n". $errors->exception->getTraceAsString()."\n\n";
 
    // find the user to blame!
    $username = Zend_Auth::getInstance()->getIdentity();
    $emailBody .= "This error was created by ".$username.".";
 
    $mail->setBodyText($emailBody);
    $mail->setFrom('anAddress@example.com', '');
    $mail->addTo($config->adminEmail, $config->adminName);
    $mail->setSubject('An Error Occured');
    // Email
    $mail->send();
 
    $this->view->title = 'Error!';
    $this->view->heading = 'Error!';
 
    // 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;
  }

This error controller relies on certain options that have been set up in your configuration file. Add the following options to your configuration file to get these options working.

smtp = my.smtpserver.com
adminEmail = admin@example.com
adminName = 'Your name'

To enable this just call the throwExceptions() function of the Zend_Controller_Front and pass it a value of false. This will cause the framework to look for an error controller rather than try to print out things.

$frontController = Zend_Controller_Front::getInstance();
$frontController->throwExceptions(false);

Finally, you need to add a view so that your errors will be displayed nicely. This will display more if the environment is set to 'test'.

<h3>Error has happened. Inside this application. Sorry about that.</h3>
<p><?=$this->message ?> (<?=$this->code ?>) </p>
<?
if( $this->env == 'test' ) {
 if ( isset($this->info ) ) { ?>
<? if ( 404 == $this->code ) { ?>
  <p><b>Reason:</b> <?= $this->info ?></p>
<? } elseif (500 == $this->code) { ?>
  <p>Bad server, naughty server!<br />No donut for you!</p>
  <p><img src="<?=$this->baseUrl();?>/images/donut.jpg" /></p>
  <h4>Exception information:</h4>
 <p><b>Message:</b> <?= $this->info->getMessage() ?></p>
 <h4>Stack trace:</h4>
 <pre><?= $this->info->getTraceAsString() ?></pre>
<? } ?>
<? } ?>
<? } ?>