Skip to content

Instantly share code, notes, and snippets.

@Arifursdev
Last active June 29, 2026 17:16
Show Gist options
  • Select an option

  • Save Arifursdev/4d64f2385f932a4ab50d0d2251e70d2d to your computer and use it in GitHub Desktop.

Select an option

Save Arifursdev/4d64f2385f932a4ab50d0d2251e70d2d to your computer and use it in GitHub Desktop.
WordPress Router Class

https://example.com/ https://example.com/author/123 https://example.com/create https://example.com/update/123 https://example.com/delete/123

// Define routes
WPRoutes::namespace( 'app' )
    ->get( '/', function( $request ) {
        return [ 'message' => 'Root' ];
    } )
    ->get( 'author/{id}', function( $request ) {
        return [ 'author_id' => $request['params']['id'] ];
    } )
    ->post( 'create', function( $request ) {
        return [ 'created' => $request['body'] ];
    } )
    ->put( 'update/{id}', function( $request ) {
        return [ 'updated' => $request['params']['id'], 'data' => $request['body'] ];
    } )
    ->delete( 'delete/{id}', function( $request ) {
        return [ 'deleted' => $request['params']['id'] ];
    } );

// Flush once after defining routes once
WPRoutes::flush();

PUT/DELETE Requests (use one of these methods):

// 1. HTTP Header (recommended)
fetch('/app/update/123', {
    method: 'POST',
    headers: { 'X-HTTP-Method-Override': 'PUT' },
    body: JSON.stringify({ name: 'Updated' })
});

// 2. _method in body
fetch('/app/update/123', {
    method: 'POST',
    body: JSON.stringify({ _method: 'PUT', name: 'Updated' })
});

Request Object:

$request = [
    'params' => [ 'id' => '123' ],      // URL parameters
    'query'  => [ 'page' => '1' ],      // Query string
    'body'   => [ 'name' => 'John' ],   // POST/PUT/DELETE body
    'method' => 'PUT'                   // Actual method
];
<?php
class WPRoutes {
/**
* @var $this[]
*/
private static $instances = [];
/**
* @var array
*/
private static $routes = [];
/**
* @var bool
*/
private static $initialized = false;
/**
* @var string
*/
private $namespace;
/**
* @var string|null
*/
private static $rawInput = null;
/**
* Constructor
*
* @param string $namespace
*/
private function __construct( $namespace ) {
$this->namespace = trim( $namespace, '/' );
if ( !self::$initialized ) {
add_action( 'init', [self::class, 'register_rewrite_rules'] );
add_filter( 'query_vars', [self::class, 'query_vars'] );
add_action( 'parse_request', [self::class, 'handle_request'] );
self::$initialized = true;
}
}
/**
* Get or create a WPRoutes instance for a specific namespace
*
* @param string $namespace
* @return $this
*/
public static function namespace( $namespace ) {
if ( !isset( self::$instances[$namespace] ) ) {
self::$instances[$namespace] = new self( $namespace );
}
return self::$instances[$namespace];
}
/**
* Define a GET route
*
* @param string $path
* @param callable $callback
* @return $this
*/
public function get( $path, $callback ) {
return $this->add_route( 'GET', $path, $callback );
}
/**
* Define a POST route
*
* @param string $path
* @param callable $callback
* @return $this
*/
public function post( $path, $callback ) {
return $this->add_route( 'POST', $path, $callback );
}
/**
* Define a PUT route
*
* @param string $path
* @param callable $callback
* @return $this
*/
public function put( $path, $callback ) {
return $this->add_route( 'PUT', $path, $callback );
}
/**
* Define a DELETE route
*
* @param string $path
* @param callable $callback
* @return $this
*/
public function delete( $path, $callback ) {
return $this->add_route( 'DELETE', $path, $callback );
}
/**
* Add a route to the routing table
*
* @param string $method
* @param string $path
* @param callable $callback
* @return $this
*/
private function add_route( $method, $path, $callback ) {
$path = '/' . trim( $path, '/' );
$pattern = '#^' . preg_replace( '/\{([^}]+)\}/', '([^\/]+)', $path ) . '$#';
self::$routes[$method][] = [
'namespace' => $this->namespace,
'callback' => $callback,
'pattern' => $pattern,
'params' => $this->extract_param_names( $path ),
];
return $this;
}
/**
* Extract parameter names from a route path
*
* @param string $path
* @return array
*/
private function extract_param_names( $path ) {
preg_match_all( '/\{([^}]+)\}/', $path, $matches );
return $matches[1] ?? [];
}
/**
* Register rewrite rules for all defined routes
*/
public static function register_rewrite_rules() {
if ( empty( self::$routes ) ) {
return;
}
$namespaces = [];
foreach ( self::$routes as $method => $routes ) {
foreach ( $routes as $route ) {
$namespaces[] = $route['namespace'];
}
}
$namespaces = array_unique( $namespaces );
foreach ( $namespaces as $ns ) {
add_rewrite_tag( "%{$ns}_route%", '(.+)' );
add_rewrite_rule( "^{$ns}/(.+)?", "index.php?{$ns}_route=\$matches[1]", 'top' );
add_rewrite_rule( "^{$ns}/?$", "index.php?{$ns}_route=", 'top' );
}
}
/**
* Add query vars to WordPress
*
* @param array $vars
* @return array
*/
public static function query_vars( $vars ) {
if ( empty( self::$routes ) ) {
return $vars;
}
$namespaces = [];
foreach ( self::$routes as $method => $routes ) {
foreach ( $routes as $route ) {
$namespaces[] = $route['namespace'];
}
}
$namespaces = array_unique( $namespaces );
foreach ( $namespaces as $ns ) {
$vars[] = "{$ns}_route";
}
return $vars;
}
/**
* Handle incoming requests and dispatch to the appropriate route callback
*
* @param WP $wp
*/
public static function handle_request( $wp ) {
$method = self::get_method();
if ( !isset( self::$routes[$method] ) ) {
return;
}
foreach ( self::$routes[$method] as $route ) {
$ns_key = $route['namespace'] . '_route';
if ( !isset( $wp->query_vars[$ns_key] ) ) {
continue;
}
$requestPath = '/' . trim( $wp->query_vars[$ns_key], '/' );
if ( preg_match( $route['pattern'], $requestPath, $matches ) ) {
array_shift( $matches );
$params = array_combine( $route['params'], $matches );
$request = self::build_request( $params, $method );
$response = call_user_func( $route['callback'], $request );
self::send_response( $response );
exit;
}
}
}
/**
* Get the HTTP method, considering method spoofing
*
* @return string
*/
private static function get_method() {
$method = strtoupper( $_SERVER['REQUEST_METHOD'] ?? 'GET' );
if ( $method === 'POST' ) {
// Check header first
$spoofed = strtoupper( $_SERVER['HTTP_X_HTTP_METHOD_OVERRIDE'] ?? '' );
if ( in_array( $spoofed, ['PUT', 'DELETE', 'PATCH'], true ) ) {
return $spoofed;
}
// Check raw input for _method parameter
$rawInput = self::get_raw_input();
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
if ( stripos( $contentType, 'application/json' ) !== false ) {
$decoded = json_decode( $rawInput, true );
if ( isset( $decoded['_method'] ) ) {
$spoofed = strtoupper( $decoded['_method'] );
if ( in_array( $spoofed, ['PUT', 'DELETE', 'PATCH'], true ) ) {
return $spoofed;
}
}
} else {
// Check $_POST or parse raw input
if ( isset( $_POST['_method'] ) ) {
$spoofed = strtoupper( $_POST['_method'] );
} else {
parse_str( $rawInput, $parsed );
$spoofed = strtoupper( $parsed['_method'] ?? '' );
}
if ( in_array( $spoofed, ['PUT', 'DELETE', 'PATCH'], true ) ) {
return $spoofed;
}
}
}
return $method;
}
/**
* Get and cache raw input from php://input
*
* @return string
*/
private static function get_raw_input() {
if ( self::$rawInput === null ) {
self::$rawInput = file_get_contents( 'php://input' );
}
return self::$rawInput;
}
/**
* Build a request array
*
* @param array $params
* @param string $method
* @return array
*/
private static function build_request( $params, $method ) {
$body = [];
if ( in_array( $method, ['POST', 'PUT', 'PATCH', 'DELETE'] ) ) {
$contentType = $_SERVER['CONTENT_TYPE'] ?? '';
$rawInput = self::get_raw_input();
if ( stripos( $contentType, 'application/json' ) !== false ) {
$decoded = json_decode( $rawInput, true );
$body = is_array( $decoded ) ? $decoded : [];
// Remove _method from body if present
unset( $body['_method'] );
} elseif ( stripos( $contentType, 'multipart/form-data' ) !== false ) {
$body = array_merge( $_POST, $_FILES );
unset( $body['_method'] );
} elseif ( stripos( $contentType, 'application/x-www-form-urlencoded' ) !== false || !empty( $_POST ) ) {
$body = $_POST;
unset( $body['_method'] );
} else {
// Fallback: try to parse raw input as query string
parse_str( $rawInput, $parsed );
$body = is_array( $parsed ) ? $parsed : [];
unset( $body['_method'] );
}
}
return [
'params' => array_map( 'sanitize_text_field', $params ),
'query' => array_map( 'sanitize_text_field', $_GET ),
'body' => $body,
'method' => $method,
];
}
/**
* Send the response to the client
*
* @param mixed $response
*/
private static function send_response( $response ) {
if ( is_array( $response ) || is_object( $response ) ) {
wp_send_json( $response );
} else {
echo $response;
}
}
/**
* Flush rewrite rules
*/
public static function flush() {
flush_rewrite_rules();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment