Using mod_rewrite And Zend Framework To Display Dynamic sitemap.xml

3rd April 2009

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.

  1. class SitemapController extends Zend_Controller_Action
  2. {
  3. public function indexAction()
  4. {
  5. $this->_helper->layout()->disableLayout();
  6. $urls = array(array('loc'=>'http://www.hashbangcode.com/', 'lastmod'=>'2009-04-02T11:34:48+00:00', 'changefreq'=>'daily', 'priority'=>'1.0'));
  7.  
  8. $this->view->urls = $urls;
  9. }
  10. }

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.

  1. partialLoop('sitemap/_urlItem.phtml',$this->urls); ?>

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

  1. <url>
  2. <loc><?php echo $this-?>loc; ?></loc>
  3. <lastmod><?php echo $this-?>lastmod; ?></lastmod>
  4. <changefreq><?php echo $this-?>changefreq; ?></changefreq>
  5. <priority><?php echo $this-?>priority; ?></priority>
  6. </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.

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

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

Comments

Permalink
Nice article. Just starting out with ZF, so very grateful to find accessible resources like this. At first, my thinking was the same as yours: doesn't seem to me like it would be necessary for the rewrite rule to do an actual [R] redirect. Servicing the request under the requested URL sitemap.xml should have been fine, right? But, on further reflection, it makes sense that ZF is still seeing the requested URL as the sitemap.xml. Two questions: 1. Once you've set up the new Route, then you no longer need the RewriteRule, right? 2. I'm sure that setting up these new Routes in the bootstrap would work, but I'd hate to clutter my bootstrap with each custom Route I might over time need to create. I imagine that there is a more elegant solution, something like a Routing Plugin or some custom Router class that could handle them all? Sure, it could be instantiated and called in the bootstrap, but at least the bootstrap is a little cleaner. Whaddya think? Again, thanks for a great article. Cheers,

David (Wed, 09/02/2009 - 08:25)

Permalink
Thanks for the feedback David. In answer to your questions. 1. Good point. You might be onto something there! 2. I'm sure you can create a router plugin that will allow you to set up multiple routes outside of your bootstrap file with ease. Use something like this to add a plugin to your Bootstrap.
  1. $frontController = Zend_Controller_Front::getInstance();
  2. $frontController->registerPlugin(new App_Controller_Plugin_RouterPlugin());
I might write another post about plugins as they are quite useful, especially when abstracting or creating reusable code.

philipnorton42 (Wed, 09/02/2009 - 08:51)

Add new comment

The content of this field is kept private and will not be shown publicly.