Controller Routing

In my framework, I have a routes.xml file which defines certain routes based on patterns, eg {string} or {int}. The entry would then define the Controller and Action, and any parameters to be passed to them. I also make it responsible for redirects (which is an option in the routes.xml file) and also switching to a secure server or not, so these are handled pretty early on in the lifecycle of the request.

Example file:


<routes>
  <index>
    <execute controller="HomeController" action="index" secure="no" />
  </index>

  <notfound>
    <execute controller="ErrorController" action="Error404" />
  </notfound>

  <route match="test1">
    <execute controller="TestController" />
  </route>

 <route match="test2">
  <execute controller="TestController" action="TestAction" />
 </route>

 <route match="test3">
  <execute controller="TestController" action="TestAction2" secure="yes" />

  <param>Chris Emerson</param>
 </route>

 <route match="test3/{string}">
  <execute controller="TestController" action="TestAction2" />

  <param>&#37;1</param>
 </route>

 <route match="test4">
  <redirect route="test1" secure="no" />
 </route>

 <route match="test5">
  <redirect uri="http://www.example.com" />
 </route>
</routes>

If no action is defined, index is used as default.

It’s currently limited a bit, in that ‘excess’ url fragments aren’t catered for, but I can easily add this I think with another token - {url} or something - or another attribute of the route which will pass the rest of the url on to the controller/action.

The dispatcher is called with the url string by index php, it parses this file, finds what it should do and loads the controller from there, or redirects, or whatever it needs to do.

It’s not perfect, but then my whole framework is a work in progress!

…and how many websites do you code and then put online with just some text that says ‘Hello World’?

I agree its all about the right tool for the job, but it’s not as bad as you make out really.

Thanks for the example however all the example does is include a file. It does not route a request.

I’ll download a few PHP Frameworks and see what I can come up with.

Logicearth is correct in saying that mysql_escape_string is being used incorrectly. The name of the functions kind of gives away the purpose of the function.

Thanks for the help anyway.

I already have a dispatcher - it was always a part of the equation, so it’s not something that is being added here. The router parses the URL into parts, and the dispatcher just figures out which controller to load and action to run based on the given params from the router. It’s actually pretty simple.

Yup dispatchers are useful outside of routing. They potentially allow loading of MVC triads from anywhere in the application using a common interface. The router is just one part of the application which can use it.

The entire Alloy Framework as-is right now is 66KB zipped. Just because it’s a framework doesn’t mean it can’t be lightweight.

I find it amusing that you’re able to quantify lightweightedness using zipped filesize.

Firstly, zipping text files has a high rate of compression. Secondly, I don’t think lightweight means filesize in this case, it means memory footprint and the number of steps/complexity required to reach a given point.

Rename all your variables $a, $b, $c and your classes/methods the same, remove all that unnecessary type hinting and redundant “public” declarations, along with the comments and whitespace and you can probably get it down to under 20kb how lightweight is that? :wink:

I prefer pattern mapping over binding the request to the controller by some naming convention. Delegating routing via patterns or callbacks is much more flexible and decouples the routing and controller logic. It is a little more complex, but leaves a lot of room to modify urls as desired by a client and easily map “friendly urls” to their “non-friendly” counter part. Czaries example is a pretty good that allows for flexibility in terms of dynamic and static routing.

I was working on this earlier today. I’ll go over the psuedo code of what I’m trying.

Routing requests is the responsibility of the framework’s core in my setup. The system is event based, the event is encoded as the “php file” at the end of the url. “index.php” is the default event (any extension is allowed, but the presence of the extension marks the member as a event).

The path is exploded on / and then each element is checked against the tree. If the path goes down a nonexistent branch then the last found path entry is used. The controller responsible for that path is started and it makes the decision on what to do with the extra path, though the default action is to throw 404.

An examples might help. Consider the path /articles/November/23/richard’s_column.html

We have a controller named articles so core will find it second (it finds “home” first), but the core can’t find anything for the rest of the path, so it starts articles and tells it the remaining path is /November/23 and the event is “richard’s_column.html”

The articles Page controller knows what to do from there.

The default handling though is to check the events table, see if the event is allowed on that page (all pages must have an index event though since it is default). Next it checks to see if the event is requested properly - all events are get or post. It is however possible in the framework to have two events with the same name but different request methods - for example the GET edit method returns the form to be entered out, the POST edit method actually saves the changes to the database. Next it determines if the event is javascript initiated and chooses the responder object based on that fact. There are a few methods exclusive to js or html but I try to keep these to a minimum and only where they make sense. For example, the method POST blurValidate sends a field name and its current value to PHP and PHP returns boolean true or false if it passes server side validation. The php response is a javascript object, and there’s little to no point in allowing it to be accessed without js except in debug mode when testing.

The database holds the pages and lets the user choose the setup pretty much however they want. When I’m finished pages won’t have to have a class defined for them, they can instead inherit this from their parent. Same with blocks and templates. So in the above the articles page can inherit some or all of the blocks off the home page.

These URLs would be matched by routes

/:year/:producer/:car
/:year/:producer
/:year

Router should return controller Cars with these parameters. I see no issue here.

Oh, so we’re adding a dispatcher to the mix as well?

When you need to write 4MB of PHP script with 60 files loaded to say “Hello World”, you’re doing something wrong. That’s the problem with most frameworks - so married are they to concepts like decoupling and design patterns that they forget the reasons for those rules in the first place, to encourage code reuse. But if you go too far down that road you end up writing pages and pages of boilerplate code which serves no useful purpose except perhaps as ego massaging for the original designer and migraine headache induction for everyone else.

It is not enough to know a rule or concept and how to follow it, it’s also necessary to know when it doesn’t apply to the situation and when to break it.

Agreed, but the router still must reach the search demon. Also there’s no reason for some of the data to be split as long as the issue isn’t being forced.

Using your product search example the user might start a search from a page that is two categories down from root - say, computer accessories, input devices. So their current URL is

/accessories/input_devices/index.html

The router’s information about those categories is relevant to the search, especially if the widget gives the user a checkbox on whether to search this section or the whole site. Hence the search URL should still be

/accessories/input_devices/index.html?param=1&foo=3&bar=mice

rather than

search.php?group=accessories&category=input_devices&param=1…

In this case I would use javascript to rewrite the form action depending on the checked status of the search “this section” or “whole site” so that if the user searches the whole site the action is appropriate to that for bookmarking. If js is off though the search will still resolve, but php should to send a 300 redirect to make sure the browser bookmarks the results page as a sitewide search page and not a section page.

@scallioXTX : well i provided previous code supposing that $_GET[‘do’] will be only ‘access’ and mysql_escape_string … besides it doesn’t only work with db … it’s useful to escape strings and check for slashes or quotes … you think it’s not needed?

well … i can agree escaping is not the best practice here … i need to validated paths, but i belive that mysql_escape_string is generally for escaping strings … no need to b used only wiz mysql queries !

wish you luck @L4DD13

I’m also aiming the framework to be an idea step 2 for beginning programmers - step 1 being general syntax and control flow mastery. I think I can accomplish these goals but we’ll see. If the framework starts getting to difficult for beginners I’ll make a simplified version because, currently, there is no good beginner’s framework which is sad.

CodeIgnitor?

It sacrafices SoC and optimal design for the sake of simplicity, which is why (I think) new-ish developers lean towards it. Plus, it has excellent documentation and promotes a “fast” attitude.

Here is the thing (IMO). While Zend is complex, it does promote good practice at the cost of a bit more complexity, having to understand what components wire togather, and how. That being said, any framework can be as simple as CodeIgnitor through the implementation of facades.

Cheers,
Alex

Whoa Michael, crank the rant machine down a few notches. I think you’re stuck on 11.

A few quick points as a response:

  1. I already have a dispatcher - it was always a part of the equation, so it’s not something that is being added here. The router parses the URL into parts, and the dispatcher just figures out which controller to load and action to run based on the given params from the router. It’s actually pretty simple.
  2. The dispatcher in question is two small functions on the kernel object - one to handle external requests (dispatchRequest) and one to handle internal and direct HMVC requests (dispatch) [source].
  3. The entire Alloy Framework as-is right now is 66KB zipped. Just because it’s a framework doesn’t mean it can’t be lightweight.

These issues aside, I do know where you’re coming from, and I too feel that many frameworks are too heavy, try to do too much, or hide too many things from you. That’s exactly why I made Alloy. We’re in the same boat here, believe it or not. The key is to find a balance and always focus on real-world usage. Framework projects that don’t do that are obvious to spot, so just avoid those particular ones or just don’t use a framework at all. There is no basis for flared emotional response like the one you gave. Please check your emotions at the door and come prepared for a level-headed and facts-based discussion.

All that said, I believe the thread op has indicated they have the answer and the direction they were looking for, so I won’t continue back and forth arguments about which approach is better. Both approaches “work”, and both have pros and cons. I think there is enough information in this thread for someone who is conflicted to make a decision on which approach to choose.

You’ll still end up delegating routing to specific controllers when you can’t determine the controller to use from the route alone (unless you want to hard code specific routes)

e.g.

  1. /some/category/subcategory Needs to go to the category controller
  2. /some/category/product Needs to go to the product controller

On a somewhat interesting and related note, it’s not always possible to route everything. A good edge case is dealing with a searchable list.

Say I have a list (of products/orders/users, anything really) with a sort option and a couple of text filters on various fields.

You could do /user/list/[filter1]/[filter2]/[sortoption] (e.g. /user/list/tom/2010-01-01/1 ) but this requires the user to supply something for each field (or it might be a checkbox which if unchecked wont have a value so you’d end up with/user/list///1 which is undesirable). Imho, it’s better to revert to GET. /user/list/?filter1=tom&filter2=2010-01-01&sort=1 the other alternative is to mimic it (e.g. /user/list/filter1=tom;filter2=;sort=1 but then you cant use simple GET forms.

static routing??

If you’re routing static pages, you’re doing something wrong.

Most routers are based on some kind of regular expression dialect. For example Zend Framework s Router. The inspiration comes a lot from Ruby on Rails. If you’re looking for inspiration, reading the [url=http://guides.rubyonrails.org/routing.html]documentation for Rails may be useful for you too.

As an alternative to specifying the routes at a central point, you can delegate it to a hierarchy of controllers. This is a core concept of Konstrukt.

For routing there are pretty much 2 options:

  1. A routing table where each route is defined with the components it will initiate.

  2. A convention over configuration approach where routing is based on the filesystem and/or whether classes/methods exist.

Both have their advantages but I prefer the latter as it means far, far less set up.

For example,

/admin/user/edit/12

What could happen here is the admin controller would initiate the user controller and call the action “edit” with the first parameter as 12. All this would need a fairly complex routing table and script to deal with it.

If you’re doing guesswork, it’s easy for the application to apply these rules based on what files/classes/actions exist. E.g. if the controller “Admin” existed but there was no subcontroller called “user” it could call admin’s default action with 3 parameters.

You are using the wrong method then. mysql_escape if for using with SQL queries. Not general escaping, in any case escaping is not something you should be doing here. You should validate and filter. Accepting paths from unknown sources is the worst thing you can do on a web application.

Not really, in that case it would be caught as parameters to the default action on the default controller, e.g.


class MyDefaultController extends Controller {
	public $defaultAction = 'main';

	public function main($year = null, $make = null, $model = null) {
		//...	
	}

}

the default controller would be defined in the general project configuration.

If it really is an edge case where that doesn’t work (e.g. it may be putting too much work in the controller) then there should still be room to override it. However, I’ve found the best match approach works in 95% of cases.

Edit: How would you handle categorisation where you don’t know what level the end point is?

e.g. On a site I deal with we have categories e.g.

e.g.
/accessories/bags/alpinestars/
which has a product e.g.
/accessories/bags/alpinestars/alpinestars_ripper_backpack
or
/gloves/ducati/ducati_corse_gloves

The product (end point) could be N levels down the chain depending how it’s been categorised and you don’t know whether any given URI is a category or a product