• Home
  • Engineering
  • Business
  • Travel

DeMar.is

DeMar.is

Monthly Archives: January 2008

Pretty URLs – htaccess friendly url-to-action mapping

15 Tuesday Jan 2008

Posted by Justin DeMaris in Engineering

≈ Leave a comment

Thanks to djahandarie for pointing out a code error: PHP_SELF has been replaced with REQUEST_URI.

While I may not be a huge fan of Code Igniter, their approach to URLs is absolutely incredible. I honestly never knew before I started using it that it was even possible to append a bunch of stuff after a filename in the URL without using the question mark. For those of you who don’t know what I’m talking about, these two URLs will load the same file:

http://www.fugitivethought.com/index.php

http://www.fugitivethought.com/index.php/foo/bar

If you don’t believe me, try it out right now! What’s so great about this? Well first of all, it (at least mildly) looks like you are browsing folders. There are no ampersands or question marks mucking up the URL. The second cool thing is that a very simple mod_rewrite rule allows you to remove the index.php from there, to create a very pretty URL that would look like this:

http://www.fugitivethought.com/foo/bar

The .htaccess file that will allow this is taken directly from the Code Igniter user guide (http://codeigniter.com/user_guide/general/urls.html):

RewriteEngine on
RewriteCond $1 !^(index\.php|images|robots\.txt)
RewriteRule ^(.*)$ /index.php/$1 [L]

If you don’t get the importance of pretty, human interpretable URLs then you can read more about it here:http://www.aardvarkmedia.co.uk/about/articles/007.html

Now in order to make this approach to URLs useful we need a parser inside of index.php with a very simple map that will allow us to tell it what class and function to call based on the different parts of the URL. While looking to implement the best parts of Code Igniter for a personal project, I wrote a script file that does exactly this.

Basically, you keep a set of “Controller” classes inside of a directory named Controllers. You can change the name of this directory by editing line 34 in the code below (35 in the downloaded file). The map variable (lines 11 to 18) is simply a mapping of what class and function to call based on the first part of the URL (in our above entries, this would be the “foo” portion). If there is no entry on the list to match this first step, then it will use the entry labeled “default”. For each entry it calls, it pops the top entry (first entry) off the stack of ‘/’ separated portions of the URL and passes the rest of them to the function. So if we visit http://hostname/foo/bar/cat, the function will figure out which function to call for “foo”, and then call it passing an array containing “bar” and “cat”. Any further redirecting of the URL can be done inside the function that is called.

  1. // Define the path root for the application.
  2. // Example: A site rooted at http://foo.com/bar/index.php would put bar/index.php here
  3. define(‘_PAGE_ROOT_’, ‘bar/index.php’);
  4. // Parse the URL
  5. $uri = substr($_SERVER[‘REQUEST_URI’], strlen(_PAGE_ROOT_) + 2);
  6. $pgs = explode(‘/’, $uri);
  7. // Describe the URI map, further mapping can be done at each controller
  8. // inside of the map method.
  9. // default – load views.php and the Views class and call the home() method
  10. $map[‘default’]    = array(‘views’,   ‘home’);
  11. $map[‘year’]       = array(‘views’,   ‘year’);
  12. $map[‘month’]      = array(‘views’,   ‘month’);
  13. // note the use of “Map” here
  14. $map[‘control’]    = array(‘control’, ‘map’);
  15. $map[‘request’]    = array(‘actions’, ‘request’);
  16. $map[‘csv’]        = array(‘feeds’,   ‘csv’);
  17. // Map the first section of the URI to a controller and action
  18. if ( isset($map[$pgs[0]]) ) {
  19.     $action = $map[$pgs[0]];
  20.     array_shift($pgs);
  21. } else {
  22.     $action = $map[‘default’];
  23. }
  24. // Cleaner format
  25. $controller_class  =  ucfirst($action[0]);
  26. $controller_action =  $action[1];
  27. $controller_params =& $pgs;
  28. // Load the controller
  29. require ‘Controllers/’.$action[0].‘.php’;
  30. // Instantiate the controller
  31. $controller = new $controller_class;
  32. // Call the action
  33. $controller->$controller_action($controller_params);

or you can click here to download a local copy for perusal.

In the example code in the provided file, the map calls a function named “map” in the “control” class if the first string is “control”. The map function in the control class looks at the next entry in the stack of URL nibblets and calls a particular function for that. For example, it could call the “bar” method of the control class and pass it whatever it deems necessary.

One major advantage of using this over the Code Igniter code is that I am not artificially blocking using GET in the URL. If you want to have a URL like:

http://hostname/foo/bar/?blah=moo&moreblah=moremoo

then you are perfectly welcome to do so. The controller functions will be called always and be able to use the built in $_GET global in PHP as any normal PHP application would. And you still get to have pretty URLs.

Prado Benchmark

13 Sunday Jan 2008

Posted by Justin DeMaris in Engineering

≈ Leave a comment

As you may see from previous posts, I have been spending a lot of time lately in search of the ultimate framework for PHP. I have been involved with a team developing an open source component oriented framework since the beginning of last summer because the only equivalent framework we could find was Prado, and the team lead was very dissatisfied with the load and performance times on it. He tested it when version 3 had just been released, so we were unsure if performance increasing patches would be applied later. The tests were run on RedHat Enterprise Server and Suse, both on dual Xeon 2.0GHz servers with 4GB RAM and 15k RPM SCSI drives, and the load time for the blog app provided with Prado (not a very complicated app) was between .3 and .4 seconds per page. This is not horrible, but scale to a large user base, and it becomes impracticable.

Since this benchmark was a couple of years out of date, I decided to re-benchmark it using the latest Prado version 3.1.1r2290. This benchmark is run in Windows Vista on a dual core Intel Centrino with 3GB of RAM. Using the same application, the load time was on average .5 seconds (ranging between .48 and .53). Compare this with the FugitiveThought home page load time of .02 seconds (without caching), and we see a massive discrepancy in speeds.

With Prado, the index.php file that handles the loading of the framework and instantiation of the application has a run time of .085 seconds on average (between .08 and .10 seconds). This means that even before we get the application itself, Prado takes up more time than a directly written page. While these times can be brushed over by the proper use of caching, this only applies to some pages. Often times, the rich, interactive applications that you would be using Prado and its components for will not be cachable.

Smarty Caching in Code Igniter

10 Thursday Jan 2008

Posted by Justin DeMaris in Engineering

≈ Leave a comment

My review of Code Igniter drew in a lot of email requests for the Smarty caching integration code, so I’ve made an article specifically to address that issue. First of all, my claim of a factor of 15 on the performance gained is not something pulled out of thin air. I monitored Apache’s use of the processor during page load requests. Each page load was for a single Code Igniter driven page which ran a single MySQL query. The request bumped Apache up from 0% processor usage to 3.0% usage (as monitored using top from the console). With caching enabled, a cache miss still only went up to 3.0% processor usage, so there was only negligible overhead for the cache checking, and the cache generation. A cache hit on the same page bumped Apache up to only 0.2% processor usage. Doing the math of 3.0 / 0.2 gives me the factor of 15 that I claimed in the original article.

In order to make the caching the most effective, we want the cache hit to reduce PHP run time to a bare minimum. To this end, I have the cache mechanism running off of a pre_system hook in Code Igniter. My management class is named CacheManager, so my hooks.php file in /system/application/config has in it:

  1. $hook[‘pre_system’] = array(
  2.     ‘class’ => ‘CacheManager’,
  3.     ‘function’ => ‘do_cache’,
  4.     ‘filename’ => ‘CacheManager.php’,
  5.     ‘filepath’ => ‘hooks’,
  6.     ‘params’ => array()
  7. );

The template for the cache manager itself is shown below, and is saved inside of CacheManager.php in the /system/application/hooks directory.

  1. class CacheManager {
  2.     function do_cache() { }
  3. }

do_cache is the method that gets called at the beginning when the system is loaded. So far this is pretty basic and can be taken directly from the Code Igniter manual entry on hooks. The issue we have here is that since Code Igniter itself is not yet created or prepared, we cannot use any of its handy library loading functions to load Smarty and check for the existance of a cached version of the requested page. In fact, we can’t even use Code Igniter to parse the URL and figure out which which page we are checking for the cache on. If I had written a generic cache manager that could apply to all web sites automatically, I could have just provided the file and would have no need for this explanation, so unfortunately, you have to read this tutorial and then build your own cache manager for your particular website.

For this example, we are going to assume a basic blog application. We have a front page accessible through http://myserver/, we have individual blog entries accessible through http://myserver/story/The_Title_Of_The_Article, and we have an admin section where we post and edit stuff accessible from http://myserver/admin. We want the blog entries that other people can see to be cached, so that if we have a popular story, it does not bring our web server to its knees. The admin section will not be cached, because (a) we want only ourselves to see it when we log in, and (b) it is not high traffic enough to warrant the complexity of caching.

The first thing we need to do is load up the Smarty library itself. Since we cannot use Code Igniters methods, we have to load it directly. If you followed the instructions from here for using Smarty with Code Igniter, then the following code will work to load Smarty. It may need tweaking if you have a different method for using Smarty.

  1. $file = dirname(__FILE__).‘/../libraries/Smarty-2.6.18/libs/Smarty.class.php’;
  2. require $file;
  3. if ( !defined(‘BASEPATH’) )
  4.     define(‘BASEPATH’, dirname(__FILE__).‘/../../’;
  5. class CacheManager {
  6.     function do_cache() {
  7.         // Turn on Smarty
  8.         $s = new Smarty();
  9.         $s->caching = true;
  10.         $s->cache_dir = BASEPATH . ‘cache/smcache/’;
  11.         $s->template_dir = BASEPATH . ‘application/views/’;
  12.         $s->compile_dir = BASEPATH . ‘cache/’;
  13.     }
  14. }

Code Igniter already has directory for some caching, but it is limitted to compiling PHP and Smarty templates and such, so I created a directory inside it to hold the results of static HTML caching. So we now have a web server writable directory in /system/cache/smcache/. The above code loads Smarty and sets up all of the proper paths (the defaults used by the previously mentioned Smarty library for Code Igniter). It also turns on caching. Next we need to parse the URL. Since all of this work will be done behind the rewrite rules, we are guaranteed to have an index.php somewhere in the URL, and we can use this to locate ourselves in the URL. We add code to do_cache to make it look like this:

  1. $file = dirname(__FILE__).‘/../libraries/Smarty-2.6.18/libs/Smarty.class.php’;
  2. require $file;
  3. if ( !defined(‘BASEPATH’) )
  4.     define(‘BASEPATH’, dirname(__FILE__).‘/../../’;
  5. class CacheManager {
  6.     function do_cache() {
  7.         // Turn on Smarty
  8.         $s = new Smarty();
  9.         $s->caching = true;
  10.         $s->cache_dir = BASEPATH . ‘cache/smcache/’;
  11.         $s->template_dir = BASEPATH . ‘application/views/’;
  12.         $s->compile_dir = BASEPATH . ‘cache/’;
  13.         // Parse the URL into sections.
  14.         $fields = explode(‘/’, PHP_SELF);
  15.         $base = array_search(‘index.php’, $fields) + 1;
  16.     }
  17. }

This breaks the URL down into sections, finds index.php and then starts looking one step ahead of it. If our base URL is http://myserver/index.php, we will then have whatever comes after index.php be the base. For the home page (/), the rest of the array will be empty. For an individual story, we will have two more entries, one with “story” and one with the title of the story. For the admin page, we will have one or more entries, with the first entry being “admin”. By our schema, if we are looking at “admin”, then we are not caching at all. If we are looking at an empty entry, then we know we are on the front page and it is a basic cache. If we have “story” as the entry, we are caching each story individually and the unique ID for the cache entry will be the title itself. With this as the basis, we will have the following code:

  1. $file = dirname(__FILE__).‘/../libraries/Smarty-2.6.18/libs/Smarty.class.php’;
  2. require $file;
  3. if ( !defined(‘BASEPATH’) )
  4.     define(‘BASEPATH’, dirname(__FILE__).‘/../../’;
  5. class CacheManager {
  6.     function do_cache() {
  7.         // Turn on Smarty
  8.         $s = new Smarty();
  9.         $s->caching = true;
  10.         $s->cache_dir = BASEPATH . ‘cache/smcache/’;
  11.         $s->template_dir = BASEPATH . ‘application/views/’;
  12.         $s->compile_dir = BASEPATH . ‘cache/’;
  13.         // Parse the URL into sections.
  14.         $fields = explode(‘/’, $_SERVER[‘PHP_SELF’]);
  15.         $base = array_search(‘index.php’, $fields) + 1;
  16.         // Base case assuming that nothing beyond index.php is provided
  17.         if ( count($fields) <= $base )
  18.             $fields[$base] = ”;
  19.         // Ignore these entries, since they are not cached
  20.         if ( in_array($fields[$base], array(‘admin’)) )
  21.             return;
  22.         // If we have nothing, then this entry is cached with no cache id
  23.         if ( $fields[$base] == ” ) {
  24.             $page = ‘home.tpl’;
  25.             $id = NULL;
  26.         }
  27.         // If we are looking at a story, then entry is cached with a cache id
  28.         if ( $fields[$base] == ‘story’ ) {
  29.             $page = ‘story.tpl’;
  30.             $id = $fields[$base + 1];
  31.         }
  32.         // Check if we have the entry cached
  33.         if ( $page && $s->is_cached($page, $id) ) {
  34.             $s->display($page, $id);
  35.             exit;
  36.         }
  37.     }
  38. }

From this code we basically have the entirety of the schema. It figures out which template and cache id to use and then checks if it is already cached. If it is, then it displays the cache and exits immediately. If it is not cached, or we do not try to cache it, then it simply returns and Code Igniter continues loading and operating as normal. There are two more parts to make the caching work. We need to make sure caching is enabled in the Smarty object that Code Igniter will use, and we need to generate the cache files. Cache file generation is exactly the same as it normally is with Smarty; the cache is generated when you call $smarty->display(‘page.tpl’, ‘cache_id’), and the id of the cache entry is optional. For a more in depth read on this last part, you can read the Smarty documentation. As for turning on caching in Smarty itself, we need to modify the Mysmarty library that is included from Code Igniter. If you followed the instructions from http://devcha.blogspot.com/2007/12/smarty-as-template-engine-in-code.html for installing Smarty with Code Igniter, then open up /system/application/libraries/Mysmarty.php and below the $this->compile_dir declarations, add in the following lines:

  1. $this->caching = false;
  2. $this->cache_dir = (!emptyempty($config[‘smarty_cache_dir’]) ?
  3.                            $config[‘smarty_cache_dir’]
  4.                          : BASEPATH . ‘cache/smcache/’);

This will configure the caching to use the same directories as described above. If you have used a different method for combining Smarty with Code Igniter, then you are basically on your own, although the same changes will apply.

Code Igniter Review

07 Monday Jan 2008

Posted by Justin DeMaris in Engineering

≈ Leave a comment

Code Igniter Review

Code Igniter is a framework for developing PHP applications. As a framework, it is designed to take a lot of the monotony and repetition out of coding by providing pre-packaged solutions to a lot of common problems. Code Igniter is designed around the MVC (Model-View-Controller) design pattern. The first step in learning Code Igniter is to realize that your programs no longer go directly to the PHP file you write, but rather they go to the frameworks loading file (index.php), which then parses the rest of the URL, figures out which controller is supposed to handle the request, and then loads that controller PHP file that you wrote and passes it all of the information to handle the request.

The default URLs for a Code Igniter driven application are of the format http://your-server/the-site/index.php/foo/bar. Through a trick of mod_rewrite, you can use the cleaner URL of http://your-server/the-site/foo/bar. This is one point that is a definite plus for Code Igniter; the URLs it produces are extremely clean and pretty. There are some difficulties to this practice, however, since I have had issues when trying to have some controllers get redirected using the internal _remap function and leave others to their defaults. Another major issue I had was that Code Igniter no longer allows the use of GET parameter passing. Not only does it prohibit the use of a question mark in an URL (it errors out and displays a friendly message saying that your URL has invalid characters in it if you try), but it also calls unset on $_GET inside the framework before it gets to your controller.

There is a config option to re-enable the use of GET, but for some reason this automatically disables all of those pretty URLs and forces you into using index.php?c=foo&m=bar instead of the original /foo/bar. This is terribly aggravating for anything that you actually need to use GET for when you still hope to have pretty URLs. I used Code Igniter on a fairly large project which had six different parameters to filter pages by, each of them optional. Using GET variables this is fairly easy since the presence or absence of the variable indicates whether the parameter is being used. Rewriting this into a URL schema without GET is much more difficult. I understand that disabling GET is a design decision to help keep the URLs pretty and enforce good practices by not allowing people to actually upload data through GET, but this particular instance was a perfectly valid use of GET.

Code Igniter provides a method inside of routes.php that allows you to use regular expressions to redirect requests in the URLs to a specific controller and method. They don’t provide much help to explain how to do it, so I assume that using it is a fairly non-standard practice. Some of the rewrites that should have been incredibly easy were not. For example, a rule like

$route[‘year’] = “events/year”;
$route[‘year/(.*)’] = “events/year/$1”;

Is needed to handle a URL like /year if there can be anything else coming after year. If you have only the first rule and you go to http://your-server/the-site/year/2007, then the server will have the default controller and default method handle the request instead of events->year(). If you have the second rule only, it will have http://your-server/the-site/year/ be handled by the default controller/method. This last issue does not make sense to me at all, since the .* should mean that it will handle something OR nothing.

Code Igniter tries to be very light weight, and to some degree it does succeed. But given a lot of the other features that have been included, I was frankly amazed that they did not integrate a decent templating engine. Thankfully, some nice third parties have created Smarty libraries that you can add to your Code Igniter project. I highly recommend using it, especially if you work now or intend to work with a separate designer in the future.

Another nice feature of Code Igniter is their use Active-Record style database queries. You can write your own complete queries, or use a lot of abstraction that they included so that your application can be reasonably database independent and porting to other database servers in the future will not be difficult. Their abstraction system has almost all of the features that I would want in an abstraction layer, and the way it handles results is quite clean. More details on that can be found in the Database help in the Code Igniter user manual.

Speaking of the User Manual, this is one area where Code Igniter is pretty much unbeatable. The user manual itself is very well written and organized, I am a very big fan of the Table Of Contents drop down on every page, and they have a plethora of examples on almost every topic. The wiki helps out some too, although it is much harder to navigate.

Code Igniter is supposed to be expandable and it provides hooks into the different sections of the framework while it runs to allow you to perform actions at different times. This is very useful, but the first time I had occassion to use a hook, I found that it did not have a hook at the point I needed, and I ended up having to make an ugly work around to handle it. I was trying to integrate Smarty’s caching system into the rest of the application, since the Code Igniters caching is poorly documented and does not seem to be as customizable. Since the cache needs to know which page is going to displayed before it can check for that cache file, I needed a hook after the controller and method had been selected and the controller instantiated, but before the method was called. Since this was not provided, I ended up having to write a hook into the earliest part of the framework (before the framework itself is even loaded). In retrospect, this probably ended up better after all, since the caching mechanism kicked in faster and significantly reduced the PHP run time and the server load time (by a factor of 15!).

Overall, I give Code Igniter an 7.5 out of 10. It has a lot of good features, it documents it well and it does fairly well at enforcing good practices. For small projects and websites with fairly simple to organize data, you can have a full blown website with administration sections and database driven page generation in very little time. For more complicated data, Code Igniter seems to break down and I ended up having a little too much bloat. As Code Igniter gets more use, I am sure a lot of these wrinkles will get ironed out. I would rate it lower, but the fact that the internal code is clean and that the documentation is so good are both signs that it is a well organized project and as such, improvements are virtually guaranteed to come.

Subscribe

  • Entries (RSS)
  • Comments (RSS)

Archives

  • April 2015
  • March 2015
  • July 2012
  • June 2012
  • January 2012
  • December 2011
  • November 2011
  • March 2010
  • January 2009
  • July 2008
  • March 2008
  • February 2008
  • January 2008
  • August 2007
  • June 2007
  • May 2007
  • April 2007
  • February 2007
  • January 2007
  • November 2006
  • June 2006
  • February 2006
  • January 2006
  • December 2005
  • November 2005
  • October 2005
  • July 2005
  • June 2005

Categories

  • Business
  • Engineering
  • Travel
  • Uncategorized

Meta

  • Register
  • Log in

Blog at WordPress.com.

  • Follow Following
    • DeMar.is
    • Already have a WordPress.com account? Log in now.
    • DeMar.is
    • Customize
    • Follow Following
    • Sign up
    • Log in
    • Report this content
    • View site in Reader
    • Manage subscriptions
    • Collapse this bar