Here's another issue I couldn't find a simple answer to. I'm building a website in PHP, and I want a simple front controller. In other words, I want every bit of traffic to run through a central script that farms out the traffic by parsing the URL. And I want it to be silent – I don't want the user to know the redirects are happening. Now, I could do this with query strings – that's like
http://mysite.com/index.php?page=foo&action=bar But that's ugly, and it gets complicated fast.
Instead, I want to do it in the user-friendly, semantically meaningful, SEO-ok way, like:
http://mysite.com/foo/bar I want to use the path structure instead of the query string. This is a common pattern for Ruby and Python, but maybe less so in PHP. So, here's how I did it.
(Note: This isn't rocket science. Yet it was still hard to find. And I'm sure there are 1000 ways this could be done better, cleaner, faster, more. But I'm a functional coder. I don't care if it's the most elegant, I care if it works. And this works.)
There are two parts to the controller. The first is the .htaccess file. I'm using XAMPP on Windows Vista as my development environment. If you need help getting .htaccess to work there, check out my earlier post.
RewriteCond $1 !^(index\.php|lib|parts|pages|images|robots\.txt)
RewriteRule ^(.*)$ /your/webroot/index.php/$1 [L]
Here's what this does. (Full disclosure: I adapted this from CodeIgniter's model…) The first line enables mod_rewrite. It's a must. The second line sets the conditions for the rewrite. It says, rewrite everything except what's listed in the parenthesis. If you have additional directories that you keep images, css files, or other things in, you just add them to the list in parens with a '|' between. Finally, the last line sends everything through the main controller, which is index.php, but without actually changing the URL in the address bar. This part is what makes the whole thing transparent to the user. Good stuff.
Ok, so now, we can type something like 'http://mysite.com/foo/bar' and it will silently redirect to 'http://mysite/com/index.php/foo/bar'. So now all we have to do is set up index.php to handle the incoming request. Now, there are lots of more object oriented ways to do this, but for my purposes, the simple procedural way works best: a switch statement.
//A file with all the common configuration, like web roots, security,
//database, language, etc.
//Your header, the same regardless of the page
//This is the content area that will change based on the URL.
//My div is called 'textarea'. Yours might be called something else.
//The switch statement
$url = substr($_SERVER['REQUEST_URI'], strlen(URLROOT));
//the default is an error!
//End your content div here
//Your footer, the same regardless of the page
That's it. No mystery. In that first line of PHP ($url = …), I get the path string that's in the address bar – so this will show what the user typed before we did the redirect. Then, in my config.php I've set a global variable called 'URLROOT' that corresponds to the path in my local environment. Using substr, I snip out only the part of the path that comes after the root, and feed that to my switch statement.
Like I said, I'm sure a more experienced coder has 1000 better ways to do this. But, give it a try. It worked for me!
Sat 4 Oct 2008
Recently I was trying to get .htaccess control working under Windows and XAMPP. I found it surprisingly hard to find the answer, even though it's a simple one. You've got to make two changes to the httpd.conf file, which for me was found in C:/xampp/apache/conf.
- Search the httpd.conf file for 'mod_rewrite'. You'll find the statement that loads that module is commented out. Remove the '#' at the start of the line to un-comment it.
- By default, XAMPP on Windows does not allow .htaccess files to override the httpd.conf file. Search the httpd.conf file for 'AllowOverride'. You should find one or more statements that say 'AllowOverride None'. Wherever you find it (or more selectively, if you're savvy like that), change it to 'AllowOverride All'.
Don't forget to restart Apache, and you're done!