Skip to content

Instantly share code, notes, and snippets.

@rumur
Last active April 9, 2019 13:37
Show Gist options
  • Save rumur/0681f882eafd40acc43424e10e94b057 to your computer and use it in GitHub Desktop.
Save rumur/0681f882eafd40acc43424e10e94b057 to your computer and use it in GitHub Desktop.
WordPress Custom HTTP Endpoints

How to Use

1. Register Routes that you want to use

/**
 * Registers Custom HTTP routes.
 *
 * @author rumur
 */
add_action( 'init', function () {
    /**
     * @Note if you passed params, they will be injected to your action
     * automatically.
     */
    Http_Router::make('ep')->addRoute( 'team', [
        'callback' => 'Team_Controller@show',
    ])->addRoute('team/{member_id}', [
        'callback' => 'Team_Controller@show',
    ])->addRoute('profile/{member_id}', [
        'name'   => 'team.profile',
        'regexp' => [ 'member_id' => '([0-9]{1,})' ],
        'callback' => 'Team_Profile_Controller@show',
    ])->registerRoutes();
});

2. Create Controller

// class-team-controller.php
class Team_Controller
{
    // ...
    
    /**
     * Prints the Member Template.
     * 
     * @param int $member_id
     */
    public function show($member_id)
    {
        $member = get_post($member_id);
        
        // ...
    }
}

3. Do not forget to reset Permalinks

Tips


If you need to get the route url in the theme

  • Give a route a name
[
  //...
  "name" => "team.profile",
  //...
]
  • Call the route function within the theme files
    echo route('team.profile', ['member_id' => 777]); // -> http://your-site.com/profile/777
<?php
/**
* Class Http_Router
*
* @author rumur
*/
class Http_Router
{
/** @var string */
protected $namespace;
/** @var array */
protected $routes = [];
/**
* Http_Router constructor.
*
* @param $namespace
*/
public function __construct($namespace)
{
$this->namespace = sanitize_title($namespace);
}
/**
* Factory
*
* @param $namespace
*
* @return Http_Router
*
* @author rumur
*/
static public function make($namespace)
{
$self = new static($namespace);
$self->registerNamespace();
$self->registerResolver();
return $self;
}
/**
* @param string $route
* @param array $args
*
* @return $this
*
* @author rumur
*/
public function addRoute($route, array $args)
{
$this->routes[$this->makeRouteId($route)] = $this->parseArgs($route, $args);
return $this;
}
public function getNamespace()
{
return $this->namespace;
}
/**
* Prefix for variables that will occur within the $wp_query.
*
* @return string
*/
public function paramPrefix()
{
return trim($this->getNamespace(), '_') . '_';
}
/**
* Register all routes.
*
* @author rumur
*/
public function registerRoutes()
{
foreach ($this->routes as $route => $args) {
$this->registerRoute($this->getNamespace(), $route, $args);
}
return $this;
}
/**
* @param string $route
* @param array $args
* @return array
*/
protected function parseArgs($route, array $args = [])
{
$args = wp_parse_args($args, [
'rewrite_rule' => [],
'name' => false,
'regexp' => $default_regexp = '(.+?)',
'params' => [],
'callback' => function () {
return __('Default view');
},
]);
$args = array_merge($args, [
'params_raw' => [],
'route_raw' => $route,
'route_url' => site_url($route),
'route_id' => $this->makeRouteId($route)
]);
$rewrite_rule_query = [];
$rewrite_rule_regexp = '';
preg_match_all('/{(\w*)}/', $route, $regexp);
if (!empty($regexp)) {
$params = array_combine($regexp[1], $regexp[0]);
$params_with_prefix = [];
foreach ($params as $param => $raw) {
$params_with_prefix["{$this->paramPrefix()}{$param}"] = $raw;
}
$args['params_raw'] = $params;
$args['params'] = array_keys($params_with_prefix);
if (is_array($args['regexp'])) {
// Could be if the regexp not filled for all params
if ($missed_params_in_regexp = array_diff_key($params, $args['regexp'])) {
// Fill missed regexp with default one.
$missed_params_in_regexp = array_fill_keys(array_keys($missed_params_in_regexp), $default_regexp);
// Merge it back for further use.
$args['regexp'] = array_merge($args['regexp'], $missed_params_in_regexp);
}
// Populate params with prefix;
$args_regexp = $args['regexp'];
$args['regexp'] = [];
foreach ($args_regexp as $param => $regexp) {
$args['regexp']["{$this->paramPrefix()}{$param}"] = $regexp;
}
}
$rewrite_rule_regexp = str_replace($params, $args['regexp'], $route);
$matches_counter = 1;
foreach ($params_with_prefix as $param => $raw_param) {
array_push($rewrite_rule_query, sprintf('%s=$matches[%d]', $param, $matches_counter++));
}
}
$args['rewrite_rule'] = [
'regexp' => sprintf('^%s/?$', $rewrite_rule_regexp),
'query' => sprintf('index.php?is_%s_route=%s&%s',
$this->getNamespace(), $args['route_id'], join('&', $rewrite_rule_query)
),
];
if ($args['callback'] instanceof Closure) {
$handler = $args['callback'];
} else {
$handler = explode('@', $args['callback']);
}
$args['callback'] = function () use ($handler) {
try {
echo call_user_func_array($handler, func_get_args());
} // Catch Exceptions
catch (Throwable $e) {
echo $e->getMessage();
error_log($e->getMessage());
}
};
$this->saveRouteByName($args);
return $args;
}
/**
* @param $route
* @return string
*/
protected function makeRouteId($route)
{
return md5($route);
}
/**
* @param $route_id
* @return bool
*/
protected function has($route_id)
{
return isset($this->routes[$route_id]);
}
/**
* Gets routes args
*
* @param string $route_id.
*
* @return array routes args
*/
protected function get($route_id)
{
return $this->has($route_id)
? $this->routes[$route_id]
: [];
}
/**
* Registers Namespace
*/
protected function registerNamespace()
{
\add_rewrite_tag('%is_' . $this->getNamespace() . '_route%', '(\w+)');
}
/**
* Resolver of the route content.
*/
protected function registerResolver()
{
\add_filter('template_include', function ($template) {
if ($route_id = get_query_var("is_{$this->getNamespace()}_route", false)) {
if ($this->has($route_id)) {
$routes_args = $this->get($route_id);
$params = array_map(function ($param) {
return get_query_var($param);
}, $routes_args['params']);
$template = $routes_args['callback'](...$params);
}
}
return $template;
});
}
/**
* @param string $namespace
* @param string $route
* @param array $args
*/
protected function registerRoute($namespace, $route, array $args)
{
add_rewrite_rule($args['rewrite_rule']['regexp'], $args['rewrite_rule']['query'], 'top');
//add_rewrite_rule('^'.$namespace.'/(.*)?', $query, 'top');
// (?:\/(?:.*)?|\/?$) // The end of regexp for the whole route
foreach ($args['params'] as $param) {
add_rewrite_tag("%{$param}%", $args['regexp'][$param] ?? $args['regexp']);
}
add_rewrite_tag("%route_id%", '(\w+)');
}
/**
* @param array $args
*/
protected function saveRouteByName(array $args)
{
if ($args['name']) {
$current_route_data = [ $args['name'] => $args ];
if ($cached = wp_cache_get('http_router', 'ep')) {
$data = array_merge($cached, $current_route_data);
wp_cache_replace('http_router', $data, 'ep');
} else {
wp_cache_set('http_router', $current_route_data, 'ep');
}
}
}
/**
* @param $name
* @return array
*/
public static function getRouteByName($name)
{
if ($cached = wp_cache_get('http_router', 'ep')) {
if (isset($cached[$name])) {
return $cached[$name];
}
}
return [];
}
}
<?php
if (!function_exists('route')) {
/**
* Helper to determine the Route URL.
*
* Example:
* - echo route('profile', ['user_name' => 'rumur']); Will return => `https://siteurl.com/profile/rumur`
*
* @param string $name The name of the route that was passed to the route args.
* @param array $params Params that would be passed to the URL if the route has ones.
*
* @return string Url to the route.
*
* @author rumur
*/
function route($name, array $params = [])
{
if ($route_args = Http_Router::getRouteByName($name)) {
$params = array_filter($params);
if ($params && $route_args['params_raw']) {
return str_replace($route_args['params_raw'], $params, $route_args['route_url']);
}
return esc_url($route_args['route_url']);
}
return home_url('/');
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment