API Project - Part 4 - The Router

Open a browser and navigate to: http://localhost/api/users/ Even though the 'users' folder does not exist, you'll get a response from the server, which might lead you to believe that you are looking at the default page for the users folder.

This is the magic of URL rewriting, which is configured in the .htaccess file. In order to do URL rewriting, your Apache server must have the mod_rewrite module installed, which we did when we set up our Ubuntu VMs.

Don't worry about the specifics of the .htaccess file, but note that it takes all requests for resources within the api folder and redirect them to index.php. And when it does this it appends a URL paramater named url_path and sets it to the path that was requested. So the request to http://localhost/api/users/ is translated to http://localhost/api/index.php?url_path=users. But the amazing thing is that the URL bar in the browser continues to show http://localhost/api/users/.

Our code in index.php can access the url_path parameter by using the $_GET super global array.

Take a look at the code in index.php, it's nice and short right now (but we'll be adding a lot to it soon). The code currently just extracts the url_path parameter and echos it into the page (along with the request method).

In this step, we are going to make use of the Router class. Don't worry about the code inside the Router class, it's confusing and it's not important to understand exactly how it works. But what you should begin to understand is how you can use it.

The Router class allows you to define routes (URLs) that the app will respond to. Currently, our app will respond any URL that starts with http://localhost/api by simply echoing the request method and the path requested (relative to the api folder). When building a REST API (web service), you define specific URLs that your app will respond to. The Router allows you to do this, and will determine what code runs in order to create the HTTP response.

Routers can work in different ways, but I designed this one to work like it does in an ASP.NET MVC application (which you will learn about in the next semester). The Router will analyze the incomming request to see if it has a route that matches it. If it does have a mathcing route, it will do two things:

  1. Instantiate a class (this is known as a Controller class)
  2. Invoke a specific method of that class (this is known as an Action method)

Comment out the die() call in index.php (the last line of code) and add this code (note that the Router class is already included in index.php):

$routes = [
	"users/" => ["controller" => "UserController", "action" => "handleUsers"],
	"users/:id" => ["controller" => "UserController", "action" => "handleSingleUser"],
	"roles/" => ["controller" => "RoleController", "action" => "handleRoles"],
	"login/" =>	["controller" => "LoginController", "action" => "handleLogin"],
	"logout/" => ["controller" => "LoginController", "action" => "handleLogout"]
];

$router = new Router($routes);
$route = $router->getController($url_path);

if($route){
	echo("Instantiate this controller: <b>{$route['controller']}</b> and invoke this method: <b>{$route['action']}</b>");
}else{
	header("HTTP/1.1 404 Not Found");
	echo("We don't have a route that matches " . $url_path);
}


die();

Let's walk through this code step by step:

  1. We define an associative array named $routes whose keys are the URLs that our app will respond to. The value for each key is another associative array that has two keys, controller and action (sound familiar?)
  2. We pass the $routes array into the Router's constructor. By doing this, we have defined the routes that our app will respond to.
  3. We call the getController() by passing in the actual URL being requested.
  4. If there is a route that matches $url_path then getController() will return an associative array that specifies the controller class to instantiate, and the action method to invoke on the controller
  5. If there is NOT a match, we simply respond with a 404 status code (page not found) and echo a message saying there is no matching route.

Now, try browsing to this URL:

http://localhost/api/users/

You should see a message saying that the UserController should be instantiated and it's handleUsers method should be invoked If we were to write this code ourselves, it would look something like this:

// DON'T ADD THIS CODE, IT'S JUST TO SHOW YOU WHAT OUR CODE MIGHT LOOK LIKE:
$controller = new UserController();
$controller->handleUsers();

Now browse to this URL:

http://localhost/api/users/7

This will invoke the same controller class (UserController), but will invoke a different action method (handleSingleUser). When we defined the $routes array, one of the routes included :id, which can be replaced with a number (or some other type of id). This is very similar to what you've seen when creating routes for NodeJS Express apps.

Now browse to this URL:

http://localhost/api/foo

You should see a message saying there is no matching route.

Let's replace the IF statement with the final code that we'll be using, which is this:

if($route){
	$className = $route['controller'];
	$methodName = $route['action'];
	//die("Instantiate $className and invoke $methodName");

	// Dynamically import a class, instantiate it, and call a method on it
	include_once("includes/controllers/$className.inc.php"); // Imports the controller
	$controller = new $className(get_link()); // Instantiates the controller
	call_user_func(array($controller, $methodName)); // Invokes the action method

}else{
	header('HTTP/1.1 404 Route Not Found');
}

Now, this might be some of the craziest code you've ever seen! And, it won't even work right now because we have not defined any controller classes. We can go over this code together in class, just ask.

In the next step, we'll start creating controller classes. But before we do, we'll need create a development database for our project. We currently have a test database, that allows us to run our unit tests, but remember that this database gets destroyed and recreated every time we run tests. So we need a separate database which will not be overwritten when we run tests.

Create a Development Database

Open PHPMyAdmin in the browser and create a database named api_dev_db.

I have created a PHP script/file that will create the tables for the database. In the future, you will have to add SQL to this file that creates the tables that you will add to your final project. But don't worry about that for now.

To create the tables for the dev database, navigate to this URL in your browser:

http://localhost/api/tests/create-dev-db.php

Now we're ready to start creating controller classes.