Skip to content

Instantly share code, notes, and snippets.

@carlalexander
Created September 9, 2015 21:20
Show Gist options
  • Save carlalexander/ea15aea001f5cfc611d8 to your computer and use it in GitHub Desktop.
Save carlalexander/ea15aea001f5cfc611d8 to your computer and use it in GitHub Desktop.
WordPress routing system
<?php
/**
* The Processor is in charge of the interaction between the routing system and
* the rest of WordPress.
*
* @author Carl Alexander <contact@carlalexander.ca>
*/
class Processor
{
/**
* The matched route found by the router.
*
* @var Route
*/
private $matched_route;
/**
* 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);
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 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());
}
/**
* 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;
}
/**
* 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));
}
}
/**
* 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);
}
}
}
<?php
/**
* A Route describes a route and its parameters.
*
* @author Carl Alexander <contact@carlalexander.ca>
*/
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;
}
/**
* 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;
}
/**
* 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);
}
}
<?php
/**
* The Router manages routes using the WordPress rewrite API.
*
* @author Carl Alexander <contact@carlalexander.ca>
*/
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);
}
}
/**
* 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;
}
/**
* 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);
}
}
/**
* Flushes all WordPress routes.
*
* @uses flush_rewrite_rules()
*/
public function flush()
{
flush_rewrite_rules();
}
/**
* 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];
}
/**
* 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);
}
/**
* 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()), '/').'$';
}
}
<?php
require 'class-processor.php';
require 'class-route.php';
require 'class-router.php';
$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);
function my_plugin_redirect()
{
$location = '/';
if (!empty($_GET['location'])) {
$location = $_GET['location'];
}
wp_redirect($location);
}
add_action('my_plugin_redirect', 'my_plugin_redirect');
@k00ni
Copy link

k00ni commented Apr 22, 2016

Hi, could you tell me under which license this code is?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment