views:

2235

answers:

6

So I'm writing a framework on which I want to base a few apps that I'm working on (the framework is there so I have an environment to work with, and a system that will let me, for example, use a single sign-on)

I want to make this framework, and the apps it has use a Resource Oriented Architecture.

Now, I want to create a URL routing class that is expandable by APP writers (and possibly also by CMS App users, but that's WAYYYY ahead in the future) and I'm trying to figure out the best way to do it by looking at how other apps do it.

I'll post some answers myself, and hopefully, the voting will help me decide whether these are good ideas or not, but essentially, I want to work out what the best way of routing multiple applications in PHP would be.

Feel free to suggest methods aswell as voting any I add. If It's someone elses method I end up using, I'll accept that answer.

+1  A: 

Use a list of Regexs to match which object I should be using

For example

^/users/[\w-]+/bookmarks/(.+)/$
^/users/[\w-]+/bookmarks/$
^/users/[\w-]+/$

Pros: Nice and simple, lets me define routes directly Cons: Would have to be ordered, not making it easy to add new things in (very error prone)

This is, afaik, how Django does it

Mez
A: 

Try taking look at MVC pattern.
Zend Framework uses it for example, but also CakePHP, CodeIgniter, ...

Me personally don't like the MVC model, but it's most of the time implemented as "View for web" component.

The decision pretty much depends on preference...

michal kralik
I am using an MVC thing. I'm talking of the technical side or URL routing . This isnt an answer to my question
Mez
A: 

I think a lot of frameworks use a combination of Apache's mod_rewrite and a front controller. With mod_rewrite, you can turn a URL like this: /people/get/3 into this: index.php?controller=people&method=get&id=3. Index.php would implement your front controller which routes the page request based on the parameters given.

Unlabeled Meat
So, obviously ignoring your non ROA urls, it's the same as my answer above, but using mod_rewrite with specific rules, and doing the "routing" through specific paraameters, rather than through the code?
Mez
A: 

Zend's MVC framework by default uses a structure like

/router/controller/action/key1/value1/key2/value2

where router is the router file (mapped via mod_rewrite, controller is from a controller action handler which is defined by a class that derives from Zend_Controller_Action and action references a method in the controller, named actionAction. The key/value pairs can go in any order and are available to the action method as an associative array.

I've used something similar in the past in my own code, and so far it's worked fairly well.

Michael Johnson
actions dont apply to a ROA system, only to an RPC style system
Mez
You're absolutely right... I misread the question. That, and ROA threw me off. I've always called it REST.
Michael Johnson
+3  A: 

I prefer to use reg ex over making my own format since it is common knowledge. I wrote a small class that I use which allows me to nest these reg ex routing tables. I use to use something similar that was implemented by inheritance but it didn't need inheritance so I rewrote it.

I do a reg ex on a key and map to my own control string. Take the below example. I visit /api/related/joe and my router class creates a new object ApiController and calls it's method relatedDocuments(array('tags' => 'joe'));

$index = urldecode(substr($_SERVER["REQUEST_URI"], 12));  // the 12 strips the subdirectory my app is running in

Route::process($index, array(
 "#^api/related/(.*)$#Di"    => "ApiController/relatedDocuments/tags",

 "#^thread/(.*)/post$#Di"    => "ThreadController/post/title",
 "#^thread/(.*)/reply$#Di"   => "ThreadController/reply/title",
 "#^thread/(.*)$#Di"         => "ThreadController/thread/title",

 "#^ajax/tag/(.*)/(.*)$#Di"  => "TagController/add/id/tags",
 "#^ajax/reply/(.*)/post$#Di"=> "ThreadController/ajaxPost/id",
 "#^ajax/reply/(.*)$#Di"     => "ArticleController/newReply/id",
 "#^ajax/toggle/(.*)$#Di"    => "ApiController/toggle/toggle",

 "#^$#Di"                    => "HomeController",
));

In order to keep errors down and simplicity up you can subdivide your table. This way you can put the routing table into the class that it controls. Taking the above example you can combine the three thread calls into a single one.

Route::process($index, array(
 "#^api/related/(.*)$#Di"    => "ApiController/relatedDocuments/tags",

 "#^thread/(.*)$#Di"         => "ThreadController/route/uri",

 "#^ajax/tag/(.*)/(.*)$#Di"  => "TagController/add/id/tags",
 "#^ajax/reply/(.*)/post$#Di"=> "ThreadController/ajaxPost/id",
 "#^ajax/reply/(.*)$#Di"     => "ArticleController/newReply/id",
 "#^ajax/toggle/(.*)$#Di"    => "ApiController/toggle/toggle",

 "#^$#Di"                    => "HomeController",
));

Then you define ThreadController::route to be like this.

function route($args) {
    Route::process($args['uri'], array(
  "#^(.*)/post$#Di"    => "ThreadController/post/title",
  "#^(.*)/reply$#Di"   => "ThreadController/reply/title",
  "#^(.*)$#Di"         => "ThreadController/thread/title",
 ));
}

Also you can define whatever defaults you want for your routing string on the right. Just don't forget to document them or you will confuse people. I'm currently calling index if you don't include a function name on the right. Here is my current code. You may want to change it to handle errors how you like and or default actions.

gradbot
The second example is very much what Ive actually been thinking of doing... (RoR style) - I'll have a look through your code.
Mez
+1  A: 

Yet another framework? -- anyway...

The trick is with routing is to pass it all over to your routing controller.

You'd probably want to use something similar to what I've documented here:

http://www.hm2k.com/posts/friendly-urls

The second solution allows you to use URLs similar to Zend Framework.