Lesson 3 - Router
In the previous lesson, .htaccess, autoloader and the common controller, we configured the .htaccess, created the index.php and an abstract controller that we're going to use as the parent class for all controllers. In today's tutorial, we'll create a router, which will route a user to the right controller based on the URL he/she entered.
Router
The router receives a URL address, processes it and calls the appropriate controller based on the given information. As always, there are several possible ways of implementing the router, out of which I've chosen the simplest one. The main idea behind a router remarkably evokes that of a controller. We'll take advantage of that, and make the router a controller.
We'll start by creating a RouterController.php file in the controllers folder, and declare a class with the same name in said file, which will inherit from the abstract controller we made in the last lesson. One of the main things here will be to implement the process method().
The router will determine which controller needs to be called based on the URL address. It creates an instance of said controller and stores it in the $controller property, which we'll declare in the class as well.
<?php class RouterController extends Controller { protected $controller; public function process($params) { } }
You may find it a bit confusing that we have 2 nested controllers, but this way of doing things is neater in this case. The RouterController controller receives a URL from the user, processes it and calls the nested controller based on the given information (e.g. the ArticleController). Both of these controllers will have a view, whereas the Router will have a template with the website layout (header, menu, etc.). The nested controller will have a template based on the type of controller that it is (article, contact form, login screen, and so on).
Parsing the URL address
The first thing we'll do is parse the URL address in the index. We'll receive the address as a string, which will look just like it does in the browser's address bar. Now, let's set up a format for the URL address:
http://www.domain.com/controller/parameter2/parameter3
In real life, it would look something more along the lines of this:
http://www.domain.com/article/article-name
Here, we're saying that we need the ArticleController controller since passing "article-name" to it. The controller will retrieve the given article from the model and pass it to the view, which will render it to the user.
Take this URL, for example:
http://www.domain.com/controller/parameter2/parameter3
For this to work, we'd have to get the array like this:
Array ( [0] => controller [1] => parameter2 [2] => parameter3 )
Let's add a parseUrl() method to the class:
private function parseUrl($url) { }
PHP provides the parse_url() function, which simply parses URL addresses. It won't do all the work for us, but it'll help us keep the protocol and domain path separate from the one with parameters. The function returns an associative array, so we'll get the "/controller/parameter2/parameter3" part from the "path" index. Keep in mind that part of the string could potentially start with a forward slash "/", which we'd have to remove using the ltrim() function. We'll also remove spaces around the URL, for which we'd use the trim() function.
$parsedUrl = parse_url($url); $parsedUrl["path"] = ltrim($parsedUrl["path"], "/"); $parsedUrl["path"] = trim($parsedUrl["path"]);
Now all that's left to do is to split the resulting string with a slash to get an array of the individual parameters:
$explodedUrl = explode("/", $parsedUrl["path"]);
Once all of that's been done, we return the result. The URL parsing function is now ready:
return $explodedUrl;
Calling the nested controller
Let's move to the process() method which will be called at the start of the request. We expect an array containing the URL address in the $params argument's 1st index. We'll parse it using the method we just now finished up:
$parsedUrl = $this->parseUrl($params[0]);
We won't handle cases when the URL is incomplete or non-existent quite yet.
Retrieving the class name
Our first task will be to determine the controller class' name. We already know that the controller is the 1st parameter in the parsed URL address. However, we use different naming conventions when writing URL addresses. That's why the controller name comes to us in the "controller-name" format, and after which we have to convert it to the "ControllerNameController" format. For example, we'd convert the "article" string to the "ArticleController" and the "user-list" to the "UserListController". We'll create a method for this type of conversion and name it dashesToCamel() method:
private function dashesToCamel($text) { }
We'll use PHP's ucwords() function which will convert the first letter of each word in a sentence to uppercase. The function is not UTF-safe, but it won't matter in this case. We'll convert the parameter to an actual sentence first by replacing hyphens with spaces:
$text = str_replace('-', ' ', $text);
Then, we'll capitalize the words and remove whitespaces:
$text = ucwords($text); $text = str_replace(' ', '', $text);
That's all. The code could be on a single line, but I've written it in two to be clear. Once the methods convert the string, e.g. the "list-users" to ListUsers, we'll return the result:
return $text;
Let's get back to the process() method and determine the controller's name with the help of our new method:
$controllerClass = $this->dashesToCamel(array_shift($parsedUrl)) . 'Controller';
Now, we have obtained the first parameter of the parsed URL using the array_shift() function and removed it from the original array since we no longer need it there.
We'll print the controller class' name just as a visual confirmation of what is going on:
echo($controllerClass);
We'll also print the rest of the URL address' array:
echo('<br />'); print_r($parsedUrl);
Calling the router
Let's proceed to index.php and create a router instance. We'll make it process the URL address found in the superglobal $_SERVER array under the REQUEST_URI key. We'll also have to insert the aforementioned value into an array since the process() method expects it:
$router = new RouterController(); $router->process(array($_SERVER['REQUEST_URI']));
Thanks to the autoloader, the one we set up in the previous lesson, the RouterController class is required automatically. To test the entire process, enter the following URL address:
localhost/article/article-name/another-parameter
You should get the following result:
Our router is working as expected. It determined the controller's name as well as its parameters based on the URL address. That's been enough for today, we'll make it fully functional in the next lesson - Wiring controllers and views.
Did you have a problem with anything? Download the sample application below and compare it with your project, you will find the error easily.
Download
By downloading the following file, you agree to the license terms
Downloaded 493x (7.64 kB)
Application includes source codes in language PHP