views:

1215

answers:

8

What's the best way to implement a URL interpreter / dispatcher, such as found in Django and RoR, in PHP?

It should be able to interpret a query string as follows:

  • /users/show/4 maps to
    • area = Users
    • action = show
    • Id = 4
  • /contents/list/20/10 maps to
    • area = Contents
    • action = list
    • Start = 20
    • Count = 10
  • /toggle/projects/10/active maps to
    • action = toggle
    • area = Projects
    • id = 10
    • field = active

Where the query string can be a specified GET / POST variable, or a string passed to the interpreter.

Edit: I'd prefer an implementation that does not use mod_rewrite.

Edit: This question is not about clean urls, but about interpreting a URL. Drupal uses mod_rewrite to redirect requests such as http://host/node/5 to http://host/?q=node/5. It then interprets the value of $_REQUEST['q']. I'm interested in the interpreting part.

+2  A: 

If appropriate, you can use one that already exists in an MVC framework.

Check out projects such as -- in no particular order -- Zend Framework, CakePHP, Symfony, Code Ignitor, Kohana, Solar and Akelos.

Cheekysoft
A: 

Just to second @Cheekysoft's suggestion, check out the Zend_Controller component of the Zend Framework. It is an implementation of the Front Controller pattern that can be used independently of the rest of the framework (assuming you would rather not use a complete MVC framework).

And obviously, CakePHP is the most similar to the RoR style.

leek
+1  A: 

have a look at the cakephp implementation as an example:

https://trac.cakephp.org/browser/trunk/cake/1.2.x.x/cake/dispatcher.php

https://trac.cakephp.org/browser/trunk/cake/1.2.x.x/cake/libs/router.php

You could also do something with mod_rewrite:

<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} -f
    RewriteRule ^(.*)$ $1 [L]
    RewriteRule ^([a-z]{2})/(.*)$ $2?lang=$1 [QSA,L]
    RewriteRule ^(.*)$ index.php?url=$1 [QSA,L]
</IfModule>

This would catch urls like /en/foo /de/foo and pass them to index.php with GET parameters 'lang' amd 'url'. Something similar can be done for 'projects', 'actions' etc

adnam
+1  A: 

Why specifically would you prefer not to use mod_rewrite? RoR uses mod_rewrite. I'm not sure how Django does this, but mod_php defaults to mapping URLs to files, so unless you create a system that writes a separate PHPfile for every possible URL (a maintenance nightmare), you'll need to use mod_rewrite for clean URLs.

Scott Reynen
By using PHP, you know it's there and working. So it's basically the If mod_rewrite.c that bothers me. Django uses python, and I know that Drupal uses PHP.
Jrgns
Drupal uses mod_rewrite to get the type of clean URLs you're seeking, as does every PHP CMS. Python has its own version of mod_rewrite, URLconf. The IfModule is there to provide an ugly URL fallback where mod_rewrite is unavailable. But it's widely available. You should really try it out.
Scott Reynen
The most Drupal does with mod_rewrite is to redirect /drupal/node/5 to /drupal/index.php?q=node/5 and to check if the browser is trying to access other files. It then interprets the $_REQUEST['q'], where it checks for aliases, and breaks it up in different parts.
Jrgns
Oh, and I have tried it out, but I prefer the flexibility / familiarity of PHP's string handling functions. I do use mod_rewrite to create clean urls the same way drupal does.
Jrgns
A: 

The way that I do this is very simple.
I use wordpress' .htaccess file:

<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.php [L]

What this .htaccess does is when something returns a 404, it sends the user to index.php.

In the above, /index.php is the "interpreter" for the URL.
In index.php, I have something along the lines of:

$req = $_SERVER['REQUEST_URI'];
$req = explode("/",$req);

The second line splits up the URL into sections based on "/". You can have

$area = $req['0'];
$action= $req['1'];
$id = $req['2'];

What I end up doing is:

function get_page($offset) {//offset is the chunk of URL we want to look at
    $req = $_SERVER['REQUEST_URI'];
    $req = explode("/",$req);
    $page = $req[$offset];
    return $page;
}
$area   = get_page(0);
$action = get_page(1);
$id     = get_page(2);

Hope this helps!

James Anderson
Getting the different chunks is easy. I want the code to be able to spot that it's a area/action/id url, or a action/area/id/field url, or someting totally different.
Jrgns
+1  A: 

What you are describing in your question should actually be the URL mapper part. For that, you could use a PEAR package called Net_URL_Mapper. For some information on how to use that class, have a look at this unit test.

hangy
A: 

I'm doing a PHP framework that does just what you are describing - taking what Django is doing, and bringing it to PHP, and here's how I'm solving this at the moment:

To get the nice and clean RESTful URLs that Django have (http://example.com/do/this/and/that/), you are unfortunately required to have mod_rewrite. But everything isn't as glum as it would seem, because you can achieve almost the same thing with a URI that contains the script's filename (http://example.com/index.php/do/this/and/that/). My mod_rewrite just forwards all calls to that format, so it's almost as usable as without the mod_rewrite trick.

To be truthful, I'm currently doing the latter method by GET (http://example.com/index.php?do/this/and/that/), and fixing stuff in case there are some genuine GET variables passed around. But my initial research says that using the direct slash after the filename should be even easier. You can dig it out with a certain $_SERVER superglobal index and doesn't require any Apache configuration. Can't remember the exact index off-hand, but you can trivially do a phpinfo() testpage to see how stuff look like under the hood.

Hope this helps.

Henrik Paul
I'm not sure I understand what you're trying to say?
Jrgns
A: 

Check how it's done in Pluf (an analogue of Django for PHP).

Forever Child