Routes

Related Files and Videos

Phreeze Training Video

Overview

In a simple PHP application you might have a stand-alone PHP
file for each page such as /customer.php, /product.php, etc and each
script is executed when it's requested by name in the URL. With a Phreeze
application (like many modern web apps) the URLs of the web app do not
necessarily relate to a specific PHP file with the same name. Instead the
URLs are "virtual" and all requests go through one
PHP file (usually index.php). This entry-point script analyzes the URL
and then decides what functions to execute. In order to get all requests
to go through one single file, you utilize the "rewrite" feature
of your web server. For example, Apache can be configured using an
.htaccess file.

For these virtual URLs to work, our index.php file has to know
which function to call when a particular URL is requested. In other words,
we need to map public URLs to PHP classes, methods or functions in our app.
In a Phreeze application the variable GlobalConfig::ROUTE_MAP
holds this information. By default this is configured in the
_app_config.php file.

The route map is really just a specially formatted array of key/value pairs.
The key is a URL pattern and the value is the 'route' to execute. Let's take a look
at a simple route map:

This route map has exactly two routes and they each are in the following format:

'[VERB]:[URL]' => array('route' => '[CONTROLLER].[METHOD]')

The first route in the above example 'GET:customers' => array('route' => 'Customer.View')
might look like this in your browser: http://localhost/customers. When Phreeze encounters this
URL it will instantiate the 'CustomerController' class and fire a method called View() on that class.

The second route in the example 'POST:product' => array('route' => 'Customer.Insert')
is slightly different. Notice that it begins with 'POST' instead of 'GET'. This tells Phreeze that
this route only matches POST requests from the browser. So, simply typing the URL
http://localhost/product into your browser will not trigger the route. POST requests are
usually the result of either submitting a form, or an AJAX request.
In this example, a POST request to http://localhost/product would fire ProductController.Insert().
The common HTTP verbs used in web apps are GET, POST, PUT and DELETE and in your route map you can handle
them all separately.

URL Parameters

The previous example allowed you to map a URL to a controller class so long as there was an exact match.
However in a typical application you will have parameters as part of your URL, for example:
http://localhost/api/sales/customer/25. Most likely this URL would have something to do
with a customer record with an ID of 25. But, how would we get the route to respond to any ID number
such as 25, 26 27, etc? This is done using wildcard patterns in the route map like so:

Notice that (:num) is on the end of the URL. This tells Phreeze to map any URL that matches
the pattern http://localhost/api/sales/customer/(:num) where (:num) is a numerical value.

This solves the problem of routing the URL to the appropriate controller, but we now have another
issue. From within our controller code, we need to get the value of that
parameter. In other words, we need to get the ID for the customer from the URL so that our controller
method knows which Customer object is being requested. Let's add some code to the route map:

We've added a second key called 'params' to the route array. Before we look at that,
let's take a moment to analyze the URL from the persepective of the Router.
Using the forward slash / character as a delimiter, the URL
http://localhost/api/sales/customer/25 would be split into 4 parts:

0 = api
1 = sales
2 = customer
3 = 25

Given the URL above, our controller would likely be interested in obtaining the
value '25' without without manually parsing the URL. Lets take another look at the 'params'
key: 'params' => array('customerId' => 3) What this tells Phreeze is that
the item of the exploded URL at position 3 is going to be assigned a name of 'customerId'.
Notice that this is a zero-based array, so the count starts at 0 instead of 1.

To make things more clear, let's take a look at how we access that from within the Controller:

The controller is able to get the value '25' only using the assigned name of 'customerId'
so it doesn't need to know anything about the format of the URL.

Why use all of this abstraction and not just access the URL directly from within the controller?
The reason is so that the Controller is not tightly coupled with the URL. This allows us
flexibility to later change URLs without re-writing controller code. The Router is the only
class that has to understand the URLs and route map and can provide information to the controller
in a more abstract manner. This strategy also makes unit testing easier because we can test our
controller methods from the command-line and use a mock router to provide information to the controller.
The controller won't know or care whether it is running in a web environment or from the command line.

Wildcard Patterns

In the previous example we used the pattern (:num) as a placeholder for any valid number in the
URL. What if the ID we want is not a numerical value? We can also use (:any) to match
any character in the URL for example:

All of the following URLs would match in the above example: http://localhost/customer/aaa, http://localhost/customer/123, http://localhost/customer/zzz

Order of Operations

One word of caution about using the (:any) pattern is that Phreeze will return the first match
that it finds. In the example below, the 2nd route will never be hit because the pattern
above it will be matched by the same URL. When two routes match the same URL, Phreeze will
always use whichever one is first.

If you were to reverse the order of the two routes above, then the 'update' route would be hit and the (:any)
route would be hit for any other match.

More Information

The route map array is process by a class in the Phreeze library names "GenericRouter" This class is an
implementation of the IRouter interface. You can write your own implementation of IRouter to process your
own specialized routes.