# Designing a system: WordPress routing

Developers use WordPress to build all sorts of solutions. They can range from a small website to large application platforms. The larger the project gets, the more common it is to have the need for WordPress to handle custom URLs. You might want to map a custom URL to a new template, a specific hook or both. These situations get more and more common as you work on larger WordPress projects. This type of problem is a bit of a growing up pain with WordPress. In framework land, there's a tool that helps you with that problem. It's called the routing system. It's a critical component of most frameworks. It lets you map URLs with different parts of your application. It's a tough problem to solve, but a good example of [object-oriented design](https://en.wikipedia.org/wiki/Object-oriented_design). That's why we're going to build one. It'll show you how [object-oriented programming](http://carlalexander.ca/object-oriented-programming-next-step-wordpress-developer/) helps you solve harder problems.

## But isn't this the job of the rewrite API?

Not quite. (But excellent question!) The [rewrite API](https://carlalexander.ca/wordpress-adventurous-rewrite-api/) allows WordPress to understand human-readable URLs. There's no question that it's an important task. That said, a routing system is a large enough problem that it's made up of smaller sub-problems. Solving these sub-problems is necessary for the routing system to do its job. And, as you'll see next, the rewrite API itself only takes care of one of these sub-problems. # The responsibilities of a routing system

To handle these problems, we're going to break the routing system into more than one class. We'll keep each class focused by limiting them to a [single responsibility](https://carlalexander.ca/single-responsibility-principle-wordpress/). Let's look at what these are. ## Map custom URLs

The most basic need of a routing system is the ability to map custom URLs. It needs a way to know what you expect it to do when it receives a matching URL. This is what we call a route. Routes are responsible for mapping the relationships between custom URLs and WordPress. They tell the routing system what to do when it matches a custom URL. Each route represents a single relationship and answers specific questions such as: - What hook do you want to call?
- What template do you want to load?

## Match a request to a route

The next thing the routing system needs to do is match a request to a route. This lets the routing system know which route it needs to process. The router is in charge of that job. You can register routes with it. It'll take a request that WordPress received and attempt to match it to one of them. But to do that, it needs to understand the request that WordPress received. This is where the rewrite API becomes interesting. Its job is to transform a URL into something that WordPress can understand. The router also needs to recognize URLs to do its job. Could the rewrite API help the router do that then? Of course, it could! We'd have to design the router as a bridge between the routing system and the rewrite API. On one side, it would have to transform the registered routes so that the rewrite API could use them. On the other, it would have to take the information from the rewrite API and use it to match a route. ![Router-Graph](//carlalexander.ca/wp-content/uploads/2015/09/Router-Graph.png)## Process a route

Once the router matched a request to a route, the routing system needs to process it. This comes down to looking at the questions answered by the matched route. - Does it want to call a hook?
- Does it want to load a template?

This is how the routing system will process the matched route. It'll do it based on how it answered these questions. So let's say that the router matched a route. If it wants to call a hook, the routing system will call it. If it wants to load a template, the routing system will pass it to the WordPress template loader. # Limitations of the system

Routing isn't an easy problem to solve. That said, we can make it easier by limiting what the routing system can do. Even with these limitations, the routing system is quite useful. ## No route requirements

Route requirements let you create routes that are more granular. For example, you can create a route and restrict it to a specific HTTP method. It's a powerful feature for sure. That said, supporting route requirements demands more out of the routing system. It can't just match a route to a URL anymore. It also has to make sure that the request matches the other requirements as well. ## No path variables

Path variables let you define routes with dynamic URLs like `/product/{id}`. When the routing system matches these routes, it extracts the variables from the URL. These variables then get sent to our hook as arguments or our template as variables. There's no doubt that this is an important feature. It's even mandatory in some cases. But it also adds a tremendous amount of complexity to the routing system. That's why we won't support them here. # Building our routing system

Alright, we laid some initial groundwork for our routing system. It's time to start building it! Now, you might be wondering, "Where do I start?". That's a valid question for you to have. ## Finding a starting point

Building a routing system is a large task. It's like looking up at a mountain that you have to climb. It's normal to feel intimidated by it. You don't always know what's a good starting point. And, once you found one, how do you build out the entire system? A good way to get over these issues is to try to start with the most isolated component in the system. You then work your way up from there. So what's the most isolated component in the routing system? It's the route. A route just stores the mapping information for the routing system. It has [no dependencies](https://carlalexander.ca/coupling-cohesion-wordpress/) on other parts of the routing system. So that's where we'll start. ## Route class

The job of our `Route` class is simple. We want it to be able to answer the two questions we saw earlier for a given URL. These were: - Do you want to call a hook? If so which one?
- Do you want to load a template? If so which one?

That's it! It's not any more complicated than that. We just need to build it now. ### Constructing a Route object

Let's look at the constructor first as it [sets the initial state](https://carlalexander.ca/designing-class-wordpress-hooks/#the-role-of-the-constructor) of our `Route` object. What does the `__construct` method need to create a `Route` object? ```
class Route
{
    /**
     * The hook called when this route is matched.
     *
     * @var string
     */
    private $hook;

    /**
     * The URL path that the route needs to match.
     *
     * @var string
     */
    private $path;

    /**
     * The template that the route wants to load.
     *
     * @var string
     */
    private $template;

    /**
     * Constructor.
     *
     * @param string $path
     * @param string $hook
     * @param string $template
     */
    public function __construct($path, $hook = '', $template = '')
    {
        $this->hook = $hook;
        $this->path = $path;
        $this->template = $template;
    }
}
```

As you can see above, the `__construct` takes three parameters: `path`, `hook` and `template`. The only required parameter is `path`. It represents the URL path that we want the route to match. We can't have a route without it. `hook` and `template` are optional. That's an intentional design decision. These two parameters represent the two questions we want our route to answer. We just don't want to force a route to answer either question by default. The method itself doesn't do much. It assigns each parameter to internal variables with the same name. And each of these internal variables is private. ### Getting information out of our Route object

Making our internal variables private has a (good) side-effect. Others can't see what's inside our `Route` objects by default. We need to create ways for them to see what's inside them. ```
class Route
{
    // ...

    /**
     * Get the hook called when this route is matched.
     *
     * @return string
     */
    public function get_hook()
    {
        return $this->hook;
    }

    /**
     * Get the URL path that the route needs to match.
     *
     * @return string
     */
    public function get_path()
    {
        return $this->path;
    }

    /**
     * Get the template that the route wants to load.
     *
     * @return string
     */
    public function get_template()
    {
        return $this->template;
    }
}
```

That's the job of these [getter methods](https://en.wikipedia.org/wiki/Mutator_method). They give others read access to the variables inside our `Route` class. Each method starts with `get_` and ends with the variable name. As a note, we won't be creating ways for others to change the variables inside our class. This is another design decision. We don't want to allow others to change a `Route` object once created. We call these types of objects: [immutable objects](https://en.wikipedia.org/wiki/Immutable_object). ### Does a route answer a question?

Now that we have getter methods, there's a way to get the route information out of our `Route` objects. That said, there are situations where you don't need to the answer to the question. Rather, you only want to know if it answers the question or not. This isn't hard to do. We know that the defaults for `hook` and `template` is an empty string. You can just use [`empty`](https://secure.php.net/manual/en/function.empty.php) function to check. So you could just use the get method and do the empty check yourself. You'd have to do it every time you want to know if the `Route` object answers the question not. But why should you have to do that? You shouldn't have to. ```
class Route
{
    // ...

    /**
     * Checks if this route want to call a hook when matched.
     *
     * @return bool
     */
    public function has_hook()
    {
        return !empty($this->hook);
    }

    /**
     * Checks if this route want to load a template when matched.
     *
     * @return bool
     */
    public function has_template()
    {
        return !empty($this->template);
    }
}
```

That's why `has_hook` and `has_template` are good helper methods to have. You remove code that you have to copy/paste everywhere. This improves the usability of our `Route` class. This improves the quality of life of anyone that wants to use our class. ## Router Class

Now that we have our `Route` class, it's time to move on to the largest piece of the puzzle: the `Router` class. As we saw earlier, it has to do a lot of work for the routing system. It needs to: - Manage the `Route` objects
- Act as a bridge between the rewrite API and the routing system
- Match requests received by WordPress to a `Route` object

Handling these tasks is what gives it its size. They're also a good way to break down the work we need to do to build the `Router` class. But enough talking about it, let's start building it! ### Managing routes

The basic need of the `Router` class is to manage `Route` objects. Lucky for us, we don't have to make it complicated. We can just do it by managing an internal array variable that contains our `Route` objects. ```
class Router
{
    /**
     * All registered routes.
     *
     * @var array
     */
    private $routes;

    /**
     * Constructor.
     *
     * @param array $routes
     */
    public function __construct(array $routes = array())
    {
        $this->routes = $routes;
    }
}
```

As you can see, there's nothing fancy with our constructor. As you can see, our constructor reflects this basic need. It has a `routes` parameter that lets you pass an initial set of routes. We assign these initial routes to the internal `routes` variable. That said, we shouldn't limit adding routes to the initial `Router` object construction. We might want to add more `Route` objects to it later on. That's a reasonable scenario to expect. ```
class Router
{
    // ...

    /**
     * Add a route to the router.
     *
     * @param Route $route
     */
    public function add_route(Route $route)
    {
        $this->routes[] = $route;
    }
}
```

The `add_route` method fixes the issue. It takes a `Route` object as a parameter and then appends it to the `routes` array. We can also use our `add_route` method in our constructor to make it safer. ```
class Router
{
    /**
     * All registered routes.
     *
     * @var Route[]
     */
    private $routes;

    /**
     * Constructor.
     *
     * @param Route[] $routes
     */
    public function __construct(array $routes = array())
    {
        $this->routes = array();

        foreach ($routes as $name => $route) {
            $this->add_route($name, $route);
        }
    }

    // ...
}
```

This is our updated `__construct` method. The role of `for` loop is to act as an extra validation step. It passes each element of the `routes` array to the `add_route` method. Because `add_route` uses [type hinting](https://secure.php.net/manual/en/language.oop5.typehinting.php), we're now confident that `routes` only contains `Route` objects. We can also update the [PHPDoc](https://en.wikipedia.org/wiki/PHPDoc) to reflect this by changing it to `Route[]`. This implies that [we expect an array](http://www.phpdoc.org/docs/latest/guides/types.html#arrays) of `Route` objects as a parameter. ### Bridging the rewrite API

So how do you use the rewrite API with a routing system? We've been dodging the question so far. The answer is that we want to use rewrite rules. With the help of rewrite rules, the router can know when it should try to match a request to a `Route` object. But how can the router know that? The trick is to add a unique name to each route. ```
add_rewrite_rule('^my-plugin$', 'index.php?route_name=my_plugin_index');
```

Here's a simple example of a rewrite rule for a route with the path `/my-plugin`. `^my-plugin$` is the regular expression that matches the path of our route. `index.php?route_name=my_plugin_index` is the URL that WordPress redirects our route to. The important part is `route_name=my_plugin_index`. It's what tells the router, "Hey! I'm a URL you should care about. LOOK AT ME." In this case, it wants the router to look (Yes yes, we're looking at you. Happy!?) for a route named `my_plugin_index`. #### Converting routes to rewrite rules

So now that we know what the rewrite API secret sauce is, we need our router to use it. First, we want to transform our registered `Route` objects into rewrite rules. Let's create a method to do that. ```
class Router
{
    // ...

    /**
     * Adds a new WordPress rewrite rule for the given Route.
     *
     * @param string $name
     * @param Route  $route
     */
    private function add_rule($name, Route $route)
    {
        // ...
    }
}
```

Here's our first pass at the `add_rule` method. We have two parameters: `name` and `route`. `name` is the route name that we want to give it like `my_plugin_index`. We're going to leave the inside empty for now. We need to lay down a bit of groundwork first. So let's take care of that. #### Assigning a name to a route

So far, we haven't coded anything to track the name of a route for `add_rule`. This is the first thing that we're going to fix. The simplest way to do this is to change the structure of our routes array. We'll make it an [associative array](https://en.wikipedia.org/wiki/Associative_array) where the name of the route is the key. ```
class Router
{
    // ...

    /**
     * Add a route to the router. Overwrites a route if it shares the same name as an already registered one.
     *
     * @param string $name
     * @param Route  $route
     */
    public function add_route($name, Route $route)
    {
        $this->routes[$name] = $route;
    }

    // ...
}
```

We updated our `add_route` method to reflect this change. It now takes an extra `name` parameter. `name` is then used as the key when it adds `route` to the `routes` array. ```
class Router
{
    /**
     * All registered routes.
     *
     * @var Route[]
     */
    private $routes;

    /**
     * Constructor.
     *
     * @param Route[] $routes
     */
    public function __construct(array $routes = array())
    {
        $this->routes = array();

        foreach ($routes as $name => $route) {
            $this->add_route($name, $route);
        }
    }

    // ...
}
```

We also need to update our constructor. It now assumes that the given `routes` array is an associative array with the route name as the key. The [foreach loop](https://secure.php.net/manual/en/control-structures.foreach.php) now passes the `name` key to `add_route`. #### Defining a route variable

As you saw earlier, `route_name=` is what lets the rewrite rule identify a route. It's not a good idea to hardcode the route variable name. So let's give someone the option to override it. ```
class Router
{
    /**
     * All registered routes.
     *
     * @var Route[]
     */
    private $routes;

    /**
     * Query variable used to identify routes.
     *
     * @var string
     */
    private $route_variable;

    /**
     * Constructor.
     *
     * @param string  $route_variable
     * @param Route[] $routes
     */
    public function __construct($route_variable = 'route_name', array $routes = array())
    {
        $this->routes = array();
        $this->route_variable = $route_variable;

        foreach ($routes as $name => $route) {
            $this->add_route($name, $route);
        }
    }

    // ...
}
```

This brings us back to the `__construct` method again. We're going to add `route_variable` as a method parameter. We'll keep the `route_name` as the default. `route_variable` also gets assigned to an internal variable with the same name. #### Converting our route path into a regular expression

We also need a way to transform our route path to a regular expression. This lets us turn `/my-plugin` into `^my-plugin$`. ```
class Router
{
    // ...

    /**
     * Generates the regex for the WordPress rewrite API for the given route.
     *
     * @param Route $route
     *
     * @return string
     */
    private function generate_route_regex(Route $route)
    {
        return '^'.ltrim(trim($route->get_path()), '/').'$';
    }
}
```

`generate_route_regex` takes a `Route` object as a parameter. It uses that `Route` object and gets the route path using `get_path`. It then makes it all pretty for the rewrite API by applying a few trims here and there on it. To do that, it starts by using [`trim`](https://secure.php.net/manual/en/function.trim.php) to remove all spaces from both sides of the route path. It also uses [`ltrim`](https://secure.php.net/manual/en/function.ltrim.php) to remove any `/` at the beginning of the path. The rewrite API doesn't want its regular expressions starting with them. It finishes off by adding `^` to the beginning of the string and `$` at the end. These are special regular expression characters. `^` only matches the beginning of a string while `$` only matches at the end. We add them to prevent matching errors. They force the rewrite API to do an exact match to our URL paths. #### Going back to the add\_rule method

So, let's recap what we've done so far. We started tracking the name of every registered route. We added the internal `route_variable` variable to let someone change the route variable name. We also created `generate_route_regex` to convert a route path into a regular expression. Now, we'll use all these pieces to complete our `add_rule` method. ```
class Router
{
    // ...

    /**
     * Adds a new WordPress rewrite rule for the given Route.
     *
     * @param string $name
     * @param Route  $route
     * @param string $position
     */
    private function add_rule($name, Route $route, $position = 'top')
    {
        add_rewrite_rule($this->generate_route_regex($route), 'index.php?'.$this->route_variable.'='.$name, $position);
    }

   // ...
}
```

The first you might have noticed is that we added `position` as a third parameter. This lets us tweak the default position where the rewrite API add the rule for a route. By default, it would get appended to the all other rewrite rules. We'd rather have the rewrite API prepend it so that it can process it first. That's why the default value is `top`. Looking at the completed `add_rule` method, it's just a wrapper around the `add_rewrite_rule` function. It converts our three parameters (`name`, `route` and `position`) into three arguments for `add_rewrite_rule`. The first argument of the `add_rewrite_rule` is the regular expression of the new rule. So that's where we'll use the output of the `generate_route_regex` method. The second argument is the redirect URL that WordPress will process. We create that redirect URL by concatenating `route_variable` and `name` inside a string. The third argument is the `position` parameter described earlier. #### Registering everything with the rewrite API

So far, we've seen how to convert a single `Route` object into a rewrite rule. We still need a method that puts everything together. That's the last piece of the puzzle. ```
class Router
{
    // ...

    /**
     * Compiles the router into WordPress rewrite rules.
     */
    public function compile()
    {
        foreach ($this->routes as $name => $route) {
            $this->add_rule($name, $route);
        }
    }

    // ...
}
```

That's the goal of the `compile` method. It compiles everything in the `Router` object into rewrite rules. It passes every registered route and its name in the router to `add_rule`. Right now, the router still wouldn't work with the rewrite API. That's because we didn't register the `route_variable` with it. Let's fix that. ```
class Router
{
    // ...

    /**
     * Compiles the router into WordPress rewrite rules.
     */
    public function compile()
    {
        add_rewrite_tag('%'.$this->route_variable.'%', '(.+)');

        foreach ($this->routes as $name => $route) {
            $this->add_rule($name, $route);
        }
    }

    // ...
}
```

We added a call to `add_rewrite_tag` to register `route_variable` as a rewrite tag. We pass it the `route_variable` prepended and appended with `%`. We use `(.+)` as the regular expression. This tells it to match any character 1 or more times. ### Matching a route

At this point, we've handled everything bridging our routing system to the rewrite API. But that's only one direction! We still need to handle converting information from the rewrite API to a route. Lucky for us, this isn't as complicated as what we've done so far. All the information that we need is in the internal [WordPress query variables](https://codex.wordpress.org/WordPress_Query_Vars). We just need to look over them. ```
class Router
{
    // ...

    /**
     * Tries to find a matching route using the given query variables. Returns the matching route
     * or a WP_Error.
     *
     * @param array $query_variables
     *
     * @return Route|WP_Error
     */
    public function match(array $query_variables)
    {
        if (empty($query_variables[$this->route_variable])) {
            return new WP_Error('missing_route_variable');
        }

        $route_name = $query_variables[$this->route_variable];

        if (!isset($this->routes[$route_name])) {
            return new WP_Error('route_not_found');
        }

        return $this->routes[$route_name];
    }

    // ...
}
```

And that's exactly what the `match` method does. It takes the array of query variables as a parameter. It then tries to find a matching route or returns a `WP_Error` object with a relevant error code. The first step is to check if there's even a route name stored inside `query_variables`. To do that, we do an empty check on `query_variables` using `route_variable` as the key. If it's not empty, it'll contain the name of the route we're looking for. Otherwise, we send back a `WP_Error` with `missing_route_variable` as the error code. Now that we have a route name, we just need to check if there's a route registered with that name. We can do that using the [`isset`](https://secure.php.net/manual/en/function.isset.php) function. It lets us verify that there's a `Route` object stored in our `routes` array. that using on our `routes`. If there's a route stored at `route_name` location, we pass it back. Otherwise, we return another `WP_Error` with the `route_not_found` error code this time. ## Processor Class

So far, we've stayed pretty far away from WordPress. Sure, we created a bridge between the routing system and the rewrite API, but that's it. We still need to take care of the larger WordPress picture next. What's involved in doing that? Well, we need to find out if there's a route that matches the current request. Using that route, we have to call a specific hook, load a specific template or do both. There's also the question of compiling our router and flushing the rewrite rules. That's still quite a bit of work left to do. This will all be the job of our `Processor` class. It'll be in charge of wiring the routing system with the rest of WordPress using the [plugin API](https://carlalexander.ca/wordpress-adventurous-plugin-api). Let's get to it! ### Constructor and the plugin API

Since we'll be using the plugin API, we have to [design our class around that](https://carlalexander.ca/designing-class-wordpress-hooks). You can pick the option that you prefer here. But for this example, we'll use a custom constructor. ```
class Processor
{
    /**
     * The router.
     *
     * @var Router
     */
    private $router;

    /**
     * Constructor.
     *
     * @param Router $router
     */
    public function __construct(Router $router)
    {
        $this->router = $router;
    }

    /**
     * Initialize processor with WordPress.
     *
     * @param Router $router
     */
    public static function init(Router $router)
    {
        $self = new self($router);
    }
}
```

This shows the initial setup for the `Processor` class. There isn't much to see there yet, but that's intentional. We'll fill the rest out as we add functionality to our class. We have our `__construct` method and `init` as the custom constructor. Both take a `Router` object as a parameter. That way the `__construct` method can assign it to the `router` internal variable. ```
$router = new Router('my_plugin_route_name');

Processor::init($router);
```

`Processor::init($router);` initializes everything with WordPress. We create a `Router` object outside the class and pass it to the constructor. This is just meant to serve as an example. The `router` variable could come from anywhere in your plugin. The important is that you pass it to the constructor. That way we don't create a [tight coupling](https://en.wikipedia.org/wiki/Coupling_(computer_programming)) between the two classes. We don't want to create the router inside the `Processor` class. ### Loading routes

We have the skeleton of our `Processor` class, but it's still pretty empty. The next thing we want to do is add routes to it. That way we can load them into the router. ```
class Processor
{
    /**
     * The router.
     *
     * @var Router
     */
    private $router;

    /**
     * The routes we want to register with WordPress.
     *
     * @var Route[]
     */
    private $routes;

    /**
     * Constructor.
     *
     * @param Router  $router
     * @param Route[] $routes
     */
    public function __construct(Router $router, array $routes = array())
    {
        $this->router = $router;
        $this->routes = $routes;
    }

    /**
     * Initialize processor with WordPress.
     *
     * @param Router  $router
     * @param Route[] $routes
     */
    public static function init(Router $router, array $routes = array())
    {
        $self = new self($router, $routes);
    }
}
```

The first thing to do is to change our existing `__construct` and `init` methods. They now have `routes` as a second parameter with an empty array as the default value. It's an array of routes that we want to register with WordPress. ```
$router = new Router('my_plugin_route_name');
$routes = array(
    'my_plugin_index' => new Route('/my-plugin'),
);

Processor::init($router, $routes);
```

We also updated our external call to `Processor::init`. We created an external array of routes with one `Route` object in it. Our route has `my_plugin_index` as its name and `/my-plugin` as its path. Why don't we pass the routes into our `Router` object right away? That's a valid question to ask yourself. And there's a good reason for that! (isn't there always?) We want to delay when our `Processor` class added the routes to the router. That way any plugin or theme can filter them. This lets them either add or remove routes before they get registered with WordPress. ```
class Processor
{
    // ...

    /**
     * Initialize processor with WordPress.
     *
     * @param Router  $router
     * @param Route[] $routes
     */
    public static function init(Router $router, array $routes = array())
    {
        $self = new self($router, $routes);

        add_action('init', array($self, 'register_routes'));
    }

    /**
     * Register all our routes into WordPress.
     */
    public function register_routes()
    {
        $routes = apply_filters('my_plugin_routes', $this->routes);

        foreach ($routes as $name => $route) {
            $this->router->add_route($name, $route);
        }
    }
}
```

All this happens through the `register_routes` method. We attached it to the `init` hook in our `init` method (a decision approved by the redundancy department of redundancy). This pushes the route registration to the [last possible moment](https://carlalexander.ca/wordpress-adventurous-loading/#init) during loading. Any plugin or theme can then use the `my_plugin_routes` filter to change the array of routes. Once the routes filtered, the method loops through them and adds them to the router. ### Compiling and flushing routes

Now that all our routes are in our router, there's still the question of compiling it into rewrite rules. The good news is that we've already done all the work for it. We just need to call our `compile` method in `register_routes`. ```
class Processor
{
    // ...

    /**
     * Register all our routes into WordPress.
     */
    public function register_routes()
    {
        $routes = apply_filters('my_plugin_routes', $this->routes);

        foreach ($routes as $name => $route) {
            $this->router->add_route($name, $route);
        }

        $this->router->compile();
    }
}
```

So that was good news, but there's also some bad news. Adding rewrite rules is just one of two steps when using the rewrite API. These rewrite rules won't take effect until we flush them using [`flush_rewrite_rules`](http://codex.wordpress.org/Function_Reference/flush_rewrite_rules). The problem is that `flush_rewrite_rules` is an expensive function. We can't always call it at the end of our `compile` method. We need to add some logic to only do it when needed. ```
class Processor
{
    // ...

    /**
     * Register all our routes into WordPress.
     */
    public function register_routes()
    {
        $routes = apply_filters('my_plugin_routes', $this->routes);

        foreach ($routes as $name => $route) {
            $this->router->add_route($name, $route);
        }

        $this->router->compile();

        $routes_hash = md5(serialize($routes));

        if ($routes_hash != get_option('my_plugin_routes_hash')) {
            flush_rewrite_rules();
            update_option('my_plugin_routes_hash', $routes_hash);
        }
    }
}
```

The solution is to keep track of the routes that we registered the last time we flushed them. To do that, we need to hash our routes into a unique string. We start by serializing our routes into a string using the [`serialize`](https://secure.php.net/manual/en/function.serialize.php) function. We then hash that string with the [`md5`](https://secure.php.net/manual/en/function.md5.php) function. This gives us a unique string to represent our routes. We store this string in an option called `my_plugin_routes_hash`. `register_routes` compares that option to the value it gets from hashing our routes. If the value is different, it calls `flush_rewrite_rules`. It also saves the `routes_hash` as our new `my_plugin_routes_hash` option. ### Matching a request to a route

We're almost at the finish line! We've done most of the heavy lifting. The rewrite API has all the information it needs to process our routes. We just need to check if it found anything. Lucky for us, there's an action hook designed just for that purpose: `parse_request`. It fires when the WordPress finishes parsing the query variables of the incoming request. It passes the current WordPress environment as an argument. ```
class Processor
{
    // ...

    /**
     * Initialize processor with WordPress.
     *
     * @param Router  $router
     * @param Route[] $routes
     */
    public static function init(Router $router, array $routes = array())
    {
        $self = new self($router, $routes);

        add_action('init', array($self, 'register_routes'));
        add_action('parse_request', array($self, 'match_request'));;
    }

    /**
     * Attempts to match the current request to a route.
     *
     * @param WP $environment
     */
    public function match_request(WP $environment)
    {
        $matched_route = $this->router->match($environment->query_vars);

        if ($matched_route instanceof Route) {
            $this->matched_route = $matched_route;
        }
    }

    // ...
}
```

`match_request` is the method that we'll hook to `parse_request`. We updated our `init` method to register it. It takes a `WP` object as a parameter. That's the WordPress environment class. The method itself is pretty simple. It calls the router `match` method. It'll return either a `Route` or a `WP_Error`. If we have a `Route` object, we store it in the `matched_route` internal variable. This is how the `Processor` class knows if there's a matched route or not. ```
class Processor
{
    /**
     * Attempts to match the current request to a route.
     *
     * @param WP $environment
     */
    public function match_request(WP $environment)
    {
        $matched_route = $this->router->match($environment->query_vars);

        if ($matched_route instanceof Route) {
            $this->matched_route = $matched_route;
        }

        if ($matched_route instanceof \WP_Error && in_array('route_not_found', $matched_route->get_error_codes())) {
            wp_die($matched_route, 'Route Not Found', array('response' => 404));
        }
    }
}
```

This last little bit is an optional step so feel free to ignore it. It's an example of what you can do with the `WP_Error` objects sent back by the router. In this case, we want to stop the WordPress process with a 404 error when we can't find a route. Why do that? It's just a way to prevent WordPress from doing something we don't expect. We know from the `match` method that it found our route variable. It just couldn't find a route using it. We're not sure how WordPress would handle it so we stop the process. ### Processing the matched route

Let's say that the `match_request` method found something. We need to check which questions it answers. Does it want to call a hook, load a template or both? #### Calling the route hook

```
class Processor
{
    // ...

    /**
     * Initialize processor with WordPress.
     *
     * @param Router  $router
     * @param Route[] $routes
     */
    public static function init(Router $router, array $routes = array())
    {
        $self = new self($router, $routes);

        add_action('init', array($self, 'register_routes'));
        add_action('parse_request', array($self, 'match_request'));
        add_action('template_redirect', array($self, 'call_route_hook'));
    }

    /**
     * Checks to see if a route was found. If there's one, it calls the route hook.
     */
    public function call_route_hook()
    {
        // ...
    }

    // ...
}
```

`call_route_hook` handles whether the matched route wants to call a hook or not. The tricky question is "When do we want to call it?" While we could use any hook, `template_redirect` is (in my opinion) the best one for the job. Why? Well, it's the last hook before the WordPress templating system goes into high gear. At that point, the WordPress process has done everything, but generate HTML. It's the perfect location to create custom responses outside the templating system. ```
class Processor
{
    // ...

    /**
     * Checks to see if a route was found. If there's one, it calls the route hook.
     */
    public function call_route_hook()
    {
        if (!$this->matched_route instanceof Route || !$this->matched_route->has_hook()) {
            return;
        }

        do_action($this->matched_route->get_hook());
    }

    // ...
}
```

Now, let's look at the `call_route_hook` method. The first thing it does is to check if the router even found a route. We use an [`instanceof`](https://secure.php.net/manual/en/internals2.opcodes.instanceof.php) check on `matched_route` to see if it's a `Route` object. You also want to check if it even has a hook to call. If `matched_route` passes both these tests, we call the route hook using [`do_action`](https://codex.wordpress.org/Function_Reference/do_action). #### Loading the route template

```
class Processor
{
    // ...

    /**
     * Initialize processor with WordPress.
     *
     * @param Router  $router
     * @param Route[] $routes
     */
    public static function init(Router $router, array $routes = array())
    {
        $self = new self($router, $routes);

        add_action('init', array($self, 'register_routes'));
        add_action('parse_request', array($self, 'match_request'));
        add_action('template_include', array($self, 'load_route_template'));
        add_action('template_redirect', array($self, 'call_route_hook'));
    }

    /**
     * Checks to see if a route was found. If there's one, it loads the route template.
     *
     * @param string $template
     *
     * @return string
     */
    public function load_route_template($template)
    {
        if (!$this->matched_route instanceof Route || !$this->matched_route->has_template()) {
            return $template;
        }

        $route_template = get_query_template($this->matched_route->get_template());

        if (!empty($route_template)) {
            $template = $route_template;
        }

        return $template;
    }

    // ...
}
```

`load_route_template` looks at whether the matched route wants to load a template or not. Unlike `call_route_hook`, there's only one good hook for this method. It's the `template_include` hook. It lets you filter the path of the template that the templating system wants to load. We start by making similar tests as `call_route_hook`. We check if we have matched route and if it has a template. If the tests fail, we return the `template` value and stop there. Now, if it does pass the test, we could just return the value from the `get_template` method. But the truth is that it's not a great solution. Instead, we'll use [`get_query_template`](https://codex.wordpress.org/Function_Reference/get_query_template) to locate it. That way, the route only needs to pass a filename without the extension. The function will take care of the rest. If `get_query_template` locates a template, we replace the value of `template` with it. # The routing system at work

To finish things off, let's see what our hard work looks like in practice. It shows two different situations where you can use the routing system. Each of them will have their own route. ```
$router = new Router('my_plugin_route_name');
$routes = array(
    'my_plugin_index' => new Route('/my-plugin', '', 'my-plugin-index'),
    'my_plugin_redirect' => new Route('/my-plugin/redirect', 'my_plugin_redirect'),
);

Processor::init($router, $routes);
```

The `my_plugin_index` route shows how to route to an index page. The routing system will use the route when URL path matches `/my-plugin`. It doesn't want to call any hook. Instead, it wants to load a template with the `my-plugin-index.php` filename. The `my_plugin_redirect` route is a template-less route. It only wants the routing system to call the `my_plugin_redirect` hook. It expects the hook to take care of generating a response. ```
function my_plugin_redirect()
{
    $location = '/';

    if (!empty($_GET['location'])) {
        $location = $_GET['location'];
    }

    wp_redirect($location);
}
add_action('my_plugin_redirect', 'my_plugin_redirect');
```

That's what the `my_plugin_redirect` function (I know, original name) does. We use `/` as the default path. It then checks if the request had a location value. If it does, it redirects to that. # Looking at the big picture

This is a lot to digest. It's not a walk in the park to build something like a routing system. Even more so if you're struggling with object-oriented programming. It might even feel a bit insane. It's the catch-22 of object-oriented programming. It's with these types of large systems where it shines brightest. Except that you need to feel comfortable with it to build them. The good news is that our routing system is reusable. You can [find all the code on GitHub](https://gist.github.com/carlalexander/ea15aea001f5cfc611d8). It's all ready for you to use it on your own projects. All that you need to do is create your own `Route` objects. You don't need to build anything else. You're good to go!