Skip to content

Instantly share code, notes, and snippets.

@leepeterson
Forked from carlalexander/class-processor.php
Created August 18, 2020 01:13
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leepeterson/d1c0298431e131c4188669650f406bb5 to your computer and use it in GitHub Desktop.
Save leepeterson/d1c0298431e131c4188669650f406bb5 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');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment