Url Routing with PHP - Part One

Most PHP frameworks use some variation of the front controller pattern to centralize common code and logic. There are advantages and disadvantages to this. I am going to ignore those for now. In fact the first part of this series will explore a simple procedural URL routing method that contains many of the disadvantages. In later articles we will build upon this basis and address the disadvantages.

Requirements

Redirecting with mod_rewrite

This article doesn't discuss mod_rewrite and the .htaccess in detail, the following .htaccess file is sufficient for the needs of our simple front controller. The .htaccess file should be placed in the same directory as the front controller script. All requests will be sent to index.php while requests for a file or directory that does exist will bypass the mod_rewrite rules and be served directly by the web server.

file: .htaccess

  1. Options +FollowSymLinks
  2. IndexIgnore */*
  3. # Turn on the RewriteEngine
  4. RewriteEngine On
  5. #  Rules
  6. RewriteCond %{REQUEST_FILENAME} !-f
  7. RewriteCond %{REQUEST_FILENAME} !-d
  8. RewriteRule . index.php

The Entry Point

I chose a simple format for the incoming URL, a command followed by its parameters separated by forward slashes "/".

For example:
www.example.com/command/parameter1/parameter2/

  1. $requestURI = explode('/', $_SERVER['REQUEST_URI']);

in this example $requestURI would contain.

  1. Array ( [0] => [1] => command [2] => parameter1 [3] => parameter2 [4] => )

This works fine except in the case where the front controller is not in the root directory.

For example:
www.example.com/myapps/app1/command/parameter1/parameter2/

in this case $requestURI would contain.

  1. Array ( [0] => [1] => myapps [2] => app1 [3] => command [4] => parameter1 [5] => parameter2  [6] => )

To filter out the path to the front controller we will need to use the $_SERVER['SCRIPT_NAME'] variable.

  1. $requestURI = explode('/', $_SERVER['REQUEST_URI']);
  2. $scriptName = explode('/',$_SERVER['SCRIPT_NAME']);

In the above example $scriptName would contain.

Array ( [0] => [1] => myapps[2] => app1 [3] => index.php )

Which can be used to remove the path and script name from the URI.
Note: As you can see the explode method leaves an empty array member at the first position. This will be fixed by the following code.

  1. $requestURI = explode('/', $_SERVER['REQUEST_URI']);
  2. $scriptName = explode('/',$_SERVER['SCRIPT_NAME']);
  3.  
  4. for($i= 0;$i < sizeof($scriptName);$i++)
  5.         {
  6.       if ($requestURI[$i]     == $scriptName[$i])
  7.               {
  8.                 unset($requestURI[$i]);
  9.             }
  10.       }
  11.  
  12. $command = array_values($requestURI);

After the code above $command would contain.

Array ( [0] => command [1] => parameter1 [2] => parameter2  [3] => )

Dispatching the command

Now that we have a command with its parameters stored in an array it is trivial to handle them via a switch statement. The following is a short example.

  1. switch($command[0])
  2.       {
  3.  
  4.       case 'commandOne' :
  5.                 echo 'You entered command: '.$command[0];
  6.                 break;
  7.  
  8.       case 'commandTwo' :
  9.                 echo 'You entered command: '.$command[0];
  10.                 break;
  11.  
  12.       default:
  13.                 echo 'That command does not exist.';
  14.                   break;
  15.       }

Try it out

You can visit examples.phpaddiction.com/urlrouter/part_1/ to see it in action and download the sample code.

Next up

The next part in the series will focus on moving the URL router into a class and out of the global namespace.

Update I've updated the .htaccess per the comment made by DrBacchus.

Comments

21 Responses to “Url Routing with PHP - Part One”

  1. Tim on April 2nd, 2007 9:58 am
    Gravatar

    that's sweet ! Can't wait to read part-2

  2. Diego Anzlin on April 2nd, 2007 2:11 pm
    Gravatar

    Oi Friend,

    for script to function correctly is necessary to add the following line before "FOR"

    — LINE TO ADD —-
    $scriptNameArray = array_values($scriptName);
    — LINE TO ADD —-

    for($i= 0; $i

  3. Doug Hill on April 2nd, 2007 2:29 pm
    Gravatar

    Copy and paste seems to have bit me! I've fixed the code now Diego. Thanks for the catch.
    Doug

  4. Matthew Turland on April 6th, 2007 2:59 pm
    Gravatar

    For users of PHP 4.3.0+ I believe lines 4-10 in the second source code example can be simplified by making use of the array_diff_assoc function like so:

    $command = array_diff_assoc($requestURI, $scriptName);

    Untested, so I could be wrong, but just a thought. Nice tutorial!

  5. Doug Hill on April 7th, 2007 12:46 am
    Gravatar

    array_diff_assoc() may work I haven't tried it either. I picked the longer for loop to make what was happening in the sample code clearer. I'll try it out though and see.

    Update:
    $commandArray = array_diff_assoc($requestURI,$scriptName);
    $commandArray = array_values($commandArray);

    The above works fine. The article isn't really about how you parse the URLs though, off hand I can think of dozens of schemes. The point is actually being able to translate some arbitrary format into a command array.. or in later articles a command object.

  6. DrBacchus on April 23rd, 2007 6:22 pm
    Gravatar

    Your rewrite conditions are slightly inaccurate:

    RewriteCond %{REQUEST_FILENAME} !-f [OR]
    RewriteCond %{REQUEST_FILENAME} !-d

    Because this is an OR condition, the rewrite rule will always be run. (A OR B) is TRUE when either A or B is true. Any argument will always be either (not a file) or (not a directory). You need to remove the [OR] there, otherwise a request for index.php will cause a loop.

    What you want is "if it's not a file AND it's not a directory":

    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d

  7. Doug Hill on April 23rd, 2007 11:43 pm
    Gravatar

    You are correct, I need to edit the article, in later versions of the sample code I removed the [OR].

  8. Peter Mansen on June 2nd, 2007 5:14 pm
    Gravatar

    How do you use mod_rewrite on the sam server? Thank you!

  9. IndridCold on July 16th, 2007 6:24 pm
    Gravatar

    Why not use something simple like:

    substr($_SERVER['REQUEST_URI'], strlen($_SERVER['SCRIPT_NAME']));

    to distinguish between the script name and the parameters, then explode on that?

    Just an idea.

  10. IndridCold on July 16th, 2007 6:47 pm
    Gravatar

    You might also want to filter out multiple slashes in anticipation of exploding on the '/' as your separator.

    $rawParams = preg_replace('@[\/]{2,}@', '/', $rawParams);

  11. Jesús Flores on September 11th, 2007 6:42 am
    Gravatar

    It's simply awesome ! :) Ty

  12. What is a URL? on November 23rd, 2007 11:51 pm
    Gravatar

    What is the advantage to doing all this? Why not have the PHP script parse the path instead of Apache? Why learn two (possibly conflicting) languages?

  13. Doug Hill on November 24th, 2007 12:33 am
    Gravatar

    I'm not sure I understand your question, The point of the artice is to have the PHP script parse the path.

  14. Peter on December 18th, 2007 8:52 pm
    Gravatar

    re: What is the advantage to doing all this?

    If you're referring to the usage of mod_rewrite, the point is (IMHO) to hide index.php from the URL so that instead of http://example.com/index.php?action=foo/bar or http://example.com/?action=foo/bar you can use http://example.com/foo/bar

    If you're looking for motivation, read http://www.w3.org/Provider/Style/URI

  15. Rauli on December 30th, 2007 1:28 pm
    Gravatar

    With this method, is it possible to get error404 message at all for non-existent 'pages'?

  16. Alessandro Motta on March 21st, 2008 4:32 am
    Gravatar

    Hi,
    nice tutorial you wrote here. I built a similar class for PHP, that works fine. But now, there's a problem:

    I'd like to route urls like the MediaWiki uses. That means urls like http://localhost/user:Test/param1/param2 But when I call these addresses i receive the message (from Apache) that the access has been denied. Do you know any solutions to this problem?

    Thanks for the tutorial and greetings

  17. SMartcoder on March 26th, 2008 2:48 am
    Gravatar

    Nice and brilliant script.
    So simple.

    ANother helpful url is found here.

    Routing in php
    Php routing

  18. jay on April 16th, 2008 10:25 am
    Gravatar

    Hi guys,

    I'm newbie to all this stuff. I am wondering if i could do the following WITHOUT CHANGING THE URL

    http://example.com/foo/bar
    to call
    http://example.com/foo?article_name="bar"

    Thanks in advance :)

  19. Vitou on June 10th, 2008 10:40 pm
    Gravatar

    Hi all,
    I am wonder whether your Rewrite Rull works with Apache on Window plateform or not.

  20. Donatello on June 13th, 2008 11:44 pm
    Gravatar

    Here's My Version!

    .htaccess

    RewriteEngine on
    RewriteCond $1 !^(index\.php|img|css|js)
    RewriteRule ^(.*)$ /index.php/$1 [L]

    .php


    abstract class URI {

    protected $pathArray = array();

    protected $path;

    public function __construct() {

    global $HTTP_SERVER_VARS;

    $this->pathArray = explode("/",$HTTP_SERVER_VARS["REQUEST_URI"]);
    }

    public function getPathByNumber($pathNumber){

    if (substr($this->pathArray[1], -4, 4) == '.php') {
    $this->path = mysql_escape_string($this->pathArray[$pathNumber + 1]);
    }else {
    $this->path = mysql_escape_string($this->pathArray[$pathNumber]);
    }

    return $this->path;
    }

    } //ends URI class

  21. Cakka on July 29th, 2008 1:12 am
    Gravatar

    Thanks for your tutorial.

Leave a Reply




XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>