Hydrogen Router: An In-depth guide
Router? But I already got one from Verizon!
Router is the preferred method of matching URL requests to Controller objects, making aggressive use of caching to stay very quick, regardless of the number or complexity of rules defined. Many rules can be added to a Router instance without incurring any extra processing or lookup time.
Router runs by looking at the PATH_INFO
Not familiar? PATH_INFO is super easy to understand. Let's say you have a PHP script in the root of your domain called index.php. You might load this in your web browser:
http://example.com/index.php
But what's cool is that you can actually continue the path past the .php, and everything you type there will be available to the Hydrogen Router. For example:
http://example.com/index.php/archive/2011/01/24
If you request the above URL, your PATH_INFO in PHP will be
/archive/2011/01/24, and the Router can look at that and determine which controller should handle the request.
Basic Router rules
Each "rule" in Router is simply a test to see if the request's URL (specifically, the PATH_INFO) matches a specific pattern, and if so, calling the appropriate Controller class and function to handle that request. Router rules can match all request types, or can be set to respond only to DELETE, POST, PUT, or GET HTTP requests.
The main goal of any routing rule is to end up with a variable named 'controller' that defines which class should be loaded, and a variable named 'function' that defines which function to call within the Controller class. This is a simple rule to direct requests to the root of the app ('/') to the welcome Controller's greeting() function:
$router = new Router();
$router->request('/', array(
'controller' => 'welcome',
'function' => 'greeting'
));
$router->start();
The supplied array of variables are the "defaults". These can easily be overridden by specifying that these variables should come from the URL:
$router->request('/:controller/:function');
The :variable format intercepts that specific piece of the request URL and assigns it to the name of the variable. In the example above, the Router rule will match any URL with two path "segments" (like /welcome/greeting) but ONLY if the specified controller and function actually exist. If not, Router will move on to the next rule.
Parts of a URL can be made optional by putting them in parentheses. The following rule will match a direct request to the root, or allow the URL to override the default controller and function:
$router->request('/(:controller(/:function))', array(
'controller' => 'welcome',
'function' => 'greeting'
));
With the above rule, a request to /login would look for a Controller class named 'login' and call its 'greeting' function.
Consider, though, the likelihood that the controller classes are namespaced and probably start with a capital first letter. To achieve this, overrides can be used. Just as variables in URLs take precedence over the 'defaults' array, overrides are applied before any controller function is called.
$router->request('/(:controller(/:function))', array(
'controller' => 'welcome',
'function' => 'greeting'
), array(
'controller' => '\myapp\controllers\%{controller|capfirst}Controller'
));
Using a second array of overrides, it's easy to transform a value of 'welcome' for the controller into '\myapp\controllers\WelcomeController'. In the array of overrides, existing variables can be referenced with %variable or %{variable}. Additionally, it can be appended with a list of pipe-separated filters to affect a change on the variable contents. Legal filters:
- "capfirst" makes the first letter uppercase
- "upper" makes the entire string uppercase
- "lower" will lowercase the entire string.
Filters are executed in the order they are listed. The following override array will turn wELcOMe into Welcome:
array('controller' => '%{controller|lower|capfirst});
Additional arguments aside from 'controller' and 'function' can also be collected, and sent into the Controller's function as parameters. The following rule allows an optional "id" variable to be collected:
$router->request('/blog/:function(/:id)', array(
'controller' => '\myapp\controllers\BlogController',
'id' => 1
));
With the above rule, if /blog/page/4 were requested, Router would call
\myapp\controllers\BlogController::page('4');
--assuming, of course, that BlogController contains a function called 'page'. Otherwise, Router would try any following rules.
Collecting a variable number of arguments with variable groups
Groups of variables can also be captured by using the :*variable format. Consider the following rule:
$router->request('/(:controller(/:function(/:*args)))', array(
'controller' => 'home',
'function' => 'index'
), array(
'controller' => '\myapp\controllers\%{controller|capfirst}Controller',
));
In the above case, if /blog/archive/2011/sept/10 is requested, Router would make the following call:
\myapp\controllers\BlogController::archive("2011/sept/10");
It's unlikely that the argument would be useful without being separated into each of its pieces, though, so Router provides two special overrides for this case. Overriding 'args' with
Router::EXPAND_ARRAYlike this...
$router->request('/(:controller(/:function(/:*args)))', array(
'controller' => 'home',
'function' => 'index'
), array(
'controller' => '\myapp\controllers\%{controller|capfirst}Controller',
'args' => Router::EXPAND_ARRAY
));
would cause this call to be made:
\myapp\controllers\BlogController::archive(array('2011', 'sept', '10'));
Or, override args with
Router::EXPAND_PARAMSfor this call:
\myapp\controllers\BlogController::archive('2011', 'sept', '10');
Variable restrictions
Sometimes it's useful for a rule to only match if a certain collected variable is restricted in some way. For example, consider the following rule:
$router->post('/:section(/:controller(/:function))', array(
'controller' => 'home',
'function' => 'index'
), array(
'controller' => '\myapp\controllers\%{controller|capfirst}Controller',
));
If we only want :section to match either 'blog' or 'wiki', we can add an array that restricts :section to those items:
$router->post('/:section(/:controller(/:function))', array(
'controller' => 'home',
'function' => 'index'
), array(
'controller' => '\myapp\controllers\%section\%{controller|capfirst}Controller',
), array(
'section' => array('blog', 'wiki')
));
Now, if the first element of the path isn't 'blog' or 'wiki', the entire rule fails to match and Router moves on. Restrictions aren't limited to whole words, though. Advanced users familiar with regular expressions can, instead, pass a regular expression string:
$router->post('/:section(/:controller(/:function))', array(
'controller' => 'home',
'function' => 'index'
), array(
'controller' => '\myapp\controllers\%section\%{controller|capfirst}Controller',
), array(
'section' => '[^0-9/]'
));
Now, :section cannot contain a digit or a slash. It's important to prevent your variables from matching slashes, because they might swallow later variables in your rule!
Controlling argument order
Router's purpose is to manipulate requests in a way that any Controller function can be called. You shouldn't have to change your controller functions to make them work with your router! So the next argument that each rule types allows for is an array that determines in which order arguments should be passed to functions. Consider this rule:
$router->get('/:controller/:function(/:forumID(/:topicID))', array(
'forumID' => '1',
'topicID' => '1'
), array(
'controller' => '\myapp\controllers\%{controller|capfirst}Controller',
), array(
'forumID' => '[0-9]+',
'topicID' => '[0-9]+'
));
That will call the specified function with the arguments 'forumID' and 'topicID', whatever they happen to match in the URL. But what if the function requires the topic ID first, and then the forum's? The next array argument will let you specify the order:
$router->get('/:controller/:function(/:forumID(/:topicID))', array(
'forumID' => '1',
'topicID' => '1'
), array(
'controller' => '\myapp\controllers\%{controller|capfirst}Controller',
), array(
'forumID' => '[0-9]+',
'topicID' => '[0-9]+'
), array('topicID', 'forumID'));
Now the topicID is first. Note that NO additional arguments will be sent to the function that aren't specified in that array. For example, only putting 'topicID' in that array without 'forumID' would cause the function to be called with only one argument. This can be useful if you capture any variables to be used in the Controller namespace, but don't want them passed on to the function.
Arguments: Served up in the format you want them. Fresh and Juicy.
Router supports sending each variable to the matched function as separate parameters (the default behavior), or as an associative array. Referring to the example above, if you'd prefer this type of call to be made:
\myapp\controllers\HomeController::index(array(
'topicID' => 6,
'forumID' => 2
));Then simply pass in 'true' after the argument order array:
$router->get('/:controller/:function(/:forumID(/:topicID))', array(
'forumID' => '1',
'topicID' => '1'
), array(
'controller' => '\myapp\controllers\%{controller|capfirst}Controller',
), array(
'forumID' => '[0-9]+',
'topicID' => '[0-9]+'
), array('topicID', 'forumID'), true);
Done! Now your function will be called with an array as its only argument.
The CatchAll rule: because you gotta show something!
If none of the rules that you've set so far match the requested URL, you'll often want to show an error page, send the 404 header, or maybe even just redirect to your homepage. This is where the catchAll rules comes in:
$router->catchAll(array(
'controller': 'error',
'function': 'error404'
));
That's it! Now, if none of the previously-set rules match, the user will be directed to the error page. Note that
catchAll()takes the exact same arguments as every other rule type, except for the URL. So if you'd like to specify some arguments to be passed along, change their order, send them as an associative array, or whatever you like, you can follow the same format as the other rules.
One more tiny feature: Global Overrides
Often, when setting up your rules, your Overrides array will contain a lot of the same things for each rule. So save yourself some time and set those overrides before you set any rules:
$router->setGlobalOverrides(array(
'controller' => '\myapp\controllers\%{controller|capfirst}Controller',
'allArgs' => Router::EXPAND_PARAMS
));Now, you can leave the overrides array out of your future rules, and these will still apply. If you do decide to add in per-rule overrides, they'll be merged with what you put here -- so you can always count on your 'controller' override being there, even if you don't redefine it.
A Final Example!
This is an example ruleset that could run a real application. If you picked up Hydrogen in its early stages, you might be familiar with the old Dispatcher class (which Router replaces), and complex rules like the HomeMatch rule and PathInfoAutoMap rule. Those looking to upgrade to the Router class will probably use a ruleset exactly like this one:
$router = new Router();
$router->setGlobalOverrides(array(
'controller' => '\myapp\controllers\%{controller|capfirst}Controller',
'args' => Router::EXPAND_PARAMS
));
$router->request('/(:controller(/:function(/:*args)))', array(
'controller' => 'home',
'function' => 'index'
));
$router->catchAll(array(
'controller' => 'error',
'function' => 'error404'
));
$router->start();
That's it! Beginning to end, that will route all requests to their appropriate destination.
One more thing!
Note that Router will expect ALL controllers to be autoloaded! The Router is not responsible for including your php files for you. Thankfully, Hydrogen's Autoloader class makes this very easy. Check out its documentation, or hang out for the upcoming Autoloader guide. Or, just look at the Hydrogen MVC Starter for a simple example.
Now you know everything!
Congratulations






Cartoon Clouds
Mountains
Sunrise
Clouds
Green Clouds
None






a hoopy frood who really knows where his towel is. ~~~ 












Help