Routes Tutorial

The Hummingbird router allows you to define custom routes to process information or execute other tasks such as downloading a file, signing-out the current user, etc.

In this tutorial you'll learn how to leverage the custom-route support and process request data with the Request and Response objects.

About routes

Routes provide a way to define custom URLs for your application.

Say you want to add a simple catalog to your site, you'll need a place where you can show all your items (the archive) and a place to show an individual item (the single).

If you were to use just PHP, you'll likely end up with some routes like /catalog.php and /detail.php?id=10.

You may also use pages, but you should still pass additional query parameters like /detail?id=10.

But with custom routes, you may generate pretty-URLs which are even more semantical and give better ranking to your page on search engines, for example /detail/large-ceramic-vase.

Route syntax

Hummingbird route syntax is similar to the one used by Backbone.js.

You can specify route components separated with a forward slash /:

  • /clients - Will match exactly /clients
  • /users/sign-up - Will match exactly /users/sign-up

You may also specify parameters, by prefixing them with a colon :. To make them optional, enclose the parameter within parentheses:

  • /products/:section - Will match /products/men, /products/women, /products/children, etc. The parameter is treated as required.
  • /products/:section(/:subsection) - Will match /products/men, /products/women as well as /products/men/watches, /products/women/watches, etc. The first parameter is required, the second one is optional.
  • /products/:section/p:page - Will match /products/men/p1, /products/women/p5, /products/children/p11, etc. Both parameters will be required but the last one will pass only the number to the handler function.

Finally, you may also define splats with an asterisk, which will match any number of parameters:

  • /products/*stuff - Will match /products/men, /products/women, /products/men/watches, /products/men/watches/analog, etc.

In any case, the parameters will be passed to the handler function as its first parameter, in an associative array that contains:

  • The full matched route, /products/men
  • The first parameter, products
  • The second parameter, men
  • ...and so on

Creating a custom route

Adding custom routes is as easy as adding a page.

First locate your routes.inc.php file, which is located in the /external folder.

TBD: Add image

There you can see a sample route named route_test. We will create our new route just below it, to do so, add the following code:

function route_catalog() {
    global $site;
    $request = $site->getRequest();
    $response = $site->getResponse();
    # Use the render function to show a template
    $site->render('page-catalog');
    # Return the response to the browser
    return $response->respond();
}
$site->getRouter()->addRoute('/catalog', 'route_catalog', true);

What we're doing there is using the render() function to display the page-catalog template. Please see the pages tutorial to learn how to create templates and where to place the template files.

Note: In this case we don't need to register the page first, since we are rendering the template directly.

Now, as you may have guessed, we are only displaying an static page; in this particular case, what we need to do is pass the actual catalog data to the page so it can actually show its items.

To do so, we will pass a second argument to the render() function, an associative array, with the variables we want to pass to the template:

function route_catalog() {
    global $site;
    $request = $site->getRequest();
    $response = $site->getResponse();
    # Fetch the catalog items from database
    $items = Catalog::getItems(); // Dummy code, this class doesn't exist
    # Use the render function to show a template
    $data = array();
    $data['items'] = $items;
    $site->render('page-catalog', $data);
    # Return the response to the browser
    return $response->respond();
}
$site->getRouter()->addRoute('/catalog', 'route_catalog', true);

The $data variable will be expanded on the template, generating a variable for each of its contents; in this case you will be able to use the $items variable directly on the template:

<div class="margins">
    <div class="the-content">
        <h1 class="section-title">Catalog</h1>
        <h3>Our collection of items</h3>
        <div class="items">
            <?php
                if ($items):
                    foreach ($items as $item):
            ?>
                <div class="item">
                    <img src="<?php echo $item->image; ?>" alt="">
                    <h3><?php echo $item->name; ?></h3>
                    <a href="<?php $site->urlTo("/detail/{$item->slug}", true); ?>">More details...</a>
                </div>
            <?php
                    endforeach;
                endif;
            ?>
        </div>
    </div>
</div>

As you can see, we are using the $items variable directly, in our example, it represents an array of objects retrieved from the database, each object hasan image, name and slug associated as well as some additional fields for the details page.

We're also using the urlTo() function to generate the link to the detail page (passing the slug as a path component). The second boolean argument (set to true) determines whether te function will output the link or not.

Using parameters

Now that we have the catalog page we need to create the detail one.

In the last section, we used the urlTo() function to build the link to the detail page (also called permalink in some content management systems).

We are passing the slug in the link, so we will end up wi th links as /detail/some-item-name or /detail/another-item-name. This way, the URL is more descriptive about the contents and actually passes the data required to retrieve the item from the database.

These kind of links are known as pretty URLs and are very important for search engine optimization processes.

But for now, we will focus on the processing of the request, to be able to get the argument from the query, fetch the item and display the detail page template.

To do so, we will create a new template, so please refer to the pages tutorial if you re not sure about the procedure to create a template.

We've created a new template named page-detail and added the following markup inside the section element:

<div class="margins">
    <div class="the-content">
        <h1 class="section-title"><?php echo $item->name; ?></h1>
        <h3>This is the detail page</h3>
        <img src="<?php echo $item->image; ?>" alt="">
        <p><?php echo $item->description; ?></p>
        <p>
            <a href="<?php $site->urlTo('/catalog', true); ?>">Go back</a>
        </p>
    </div>
</div>

If you look closely at the template code, you will notice that we're using an $item variable; that's the variable that will contain our item details and we will pass it to the template from the route handler function.

So, we need to create a new function to handle the /detail route:

function route_detail($params) {
    global $site;
    $request = $site->getRequest();
    $response = $site->getResponse();
    # Use the render function to show a template
    $site->render('page-detail');
    # Return the response to the browser
    return $response->respond();
}
$site->getRouter()->addRoute('/detail/:slug', 'route_detail', true);

You may have noticed that there is a new parameter on the handler function, $parameters. This is an associative array that will receive the parameters declared on the route.

For this example, we'll extract the slug to fetch the item from the database and display the detail page:

function route_detail($params) {
    global $site;
    $request = $site->getRequest();
    $response = $site->getResponse();
    # Use the get_item function to extract the slug from the array
    $slug = get_item($params, 1); // Remember, it's the first item
    $item = Catalog::getItemDetails($slug); // Dummy code, this class doesn't exist
    if ($item) {
        $data = array();
        $data['item'] = $item;
        # Use the render function to show a template
        $site->render('page-detail', $data);
    } else {
        # If the item wasn't found, we redirect the user to the error page
        $site->redirectTo( $site->urlTo('/error') );
    }
    # Return the response to the browser
    return $response->respond();
}
$site->getRouter()->addRoute('/detail/:slug', 'route_detail', true);

Nothing new here, except the redirectTo() call, but, as its name implies, it only generates an HTTP redirection header to the specified page.

Next up, you'll learn another practial application for the custom routes.

Processing request data

Having pretty-URLs is nice, but it's not the only way to leverage the use of custom routes.

Remember when we created a contact page on the pages tutorial?

Well, that form needs some extra coding to actually send emails.

In common practice, you set the action of your form to a PHP file and put the messaging logic there.

But what if you could display the page and send the mail within a single route?

To do so, you will need to add a new route:

function route_contact() {
    global $site;
    $request = $site->getRequest();
    $response = $site->getResponse();
    # Use the render function to show a template
    $site->render('page-contact');
    # Return the response to the browser
    return $response->respond();
}
$site->getRouter()->addRoute('/contact', 'route_contact', true);

This time though, we will use the $request object to check the request type:

function route_contact() {
    global $site;
    $request = $site->getRequest();
    $response = $site->getResponse();
    # Check request type
    switch ($request->type) {
        case 'get':
            # Use the render function to show a template
            $site->render('page-contact');
        break;
        case 'post':
        break;
    }
    # Return the response to the browser
    return $response->respond();
}
$site->getRouter()->addRoute('/contact', 'route_contact', true);

And in the case of a GET request we will render the template. Remember, if you render directly a template there's no need to register it as a page, in fact if you have registered it previously you must remove that from your functions.inc.php file.

Now for the POST type:

function route_contact() {
    global $site;
    $request = $site->getRequest();
    $response = $site->getResponse();
    # Check request type
    switch ($request->type) {
        case 'get':
            # Use the render function to show a template
            $site->render('page-contact');
        break;
        case 'post':
            # Use the `$request` object to get the POST fields
            $name = $request->post('name');
            $email = $request->post('email');
            $subject = $request->post('subject');
            $message = $request->post('message');
            # --------------------------------------------
            # Your awesome email sending logic goes here
            # --------------------------------------------
            # Redirect to the contact page with a success message
            $site->redirectTo( $site->urlTo('/contact?message=success') );
        break;
    }
    # Return the response to the browser
    return $response->respond();
}
$site->getRouter()->addRoute('/contact', 'route_contact', true);

We simply have to check for the request type and run the required logic for each case. As simple as that.

The $request object has some handy methods to retrieve values from the $_GET, $_POST, $_REQUEST and $_FILES arrays.

Generating a JSON response

Now you know how to render a page and process request data on a custom route.

But with custom routes you can also respond to AJAX requests in JSON format. This is specially helpful when building modern web applications but you may also improve your user's experience by adding AJAX to your contact forms for example.

And that is exactly what we're gona do for this example.

TBD: Javascript tutorial

...

Once you've set-up the javascript part, let's proceed with the route.

We will start by revisiting the contact route. The form uses the POST method, so we will modify just that method.

function route_contact() {
    global $site;
    $request = $site->getRequest();
    $response = $site->getResponse();
    # Check request type
    switch ($request->type) {
        case 'get':
            # Use the render function to show a template
            $site->render('page-contact');
        break;
        case 'post':
            # Create the AJAX response variables
            $result = 'error';
            $data = array();
            $message = '';
            # Use the `$request` object to get the POST fields
            $name = $request->post('name');
            $email = $request->post('email');
            $subject = $request->post('subject');
            $message = $request->post('message');
            $sent = false;
            # --------------------------------------------
            # Your awesome email sending logic goes here
            # --------------------------------------------
            if ($sent) {
                # Set the result to 'success'
                $result = 'success';
                $message = 'Thanks for your comments.';
            } else {
                $message = 'An error has ocurred while sending your message.';
            }
            if ( $request->isAjax() ) {
                # For AJAX, respond with a JSON payload
                $response->ajaxRespond($result, $data, $message);
            } else {
                # Redirect to the contact page with a success message
                $site->redirectTo( $site->urlTo('/contact?message=success') );
            }
        break;
    }
    # Return the response to the browser
    return $response->respond();
}
$site->getRouter()->addRoute('/contact', 'route_contact', true);
Go back to previous page