WordPress REST Router
This package provides a routing library for WordPress with WordPress specific wrappers such as nonce verification and admin role checking. The backbone of this package uses FastRoute, a small and succinct route resolver. FastRoute
is also the route resolver used by the popular micro-framework Slim.
The Problem
Routing in WordPress is a pain for plugin authors. It relies solely on $_POST
object keys to resolve routes if you go with their traditional way of registering AJAX handlers via admin-ajax.php
. You could use WordPress' relatively new REST APIs, but I've foregone that option because you don't have control of when routes are resolved. This is important to developers who create extensions for other plugins where the load order is out of their control. This package also differs from WordPress' implementation in that it doesn't provide validate
and sanitize
callbacks, opting instead for generic middleware handling.
Installation
Install with composer:
$ composer require aivec/wordpress-router
If you plan on using this package in a plugin, I highly recommend namespacing it with mozart. If you don't, things may break in an impossible to debug way. You have been warned.
A Short Example
Lets add a public route:
class Routes extends Aivec\WordPress\Routing\Router {
protected function declareRoutes($r) {
$this->addPublicRoute($r, 'POST', '/brownies/{flavor}', function ($args) {
return 'I like ' . $args['flavor'] . ' brownies';
});
}
}
add_action('init', function () {
$routes = new Routes('/mynamespace/api/v1', 'nonce-key', 'nonce-name')
});
Now test the route:
$ curl -X POST my-site.com/mynamespace/api/v1/brownies/chocolate
'I like chocolate brownies'
Usage
Creating Routes
Routes can be created by overriding the declareRoutes
method of the Router
class:
class Routes extends Aivec\WordPress\Routing\Router {
protected function declareRoutes($r) {
// all routes go here
/*
* addRoute adds a route that includes nonce verification.
*
* By default, addRoute creates an AJAX route. An AJAX route expects a value
* to be returned by the callable. If the value returned is not empty,
* addRoute will die with the result:
*
* die('this is a cake')
*
* If nothing is returned or the return value is empty, die(0) will be called
*/
$this->addRoute($r, 'GET', '/getcake/withnonce', function ($args) {
return 'this is a cake';
});
/*
* addPublicRoute adds a route that can be accessed by anybody
*/
$this->addPublicRoute($r, 'POST', '/makeAjaxCake/{cakeFlavor}', function ($args) {
return 'heres a ' . $args['cakeFlavor'] . ' flavored cake';
});
/*
* POST routes can also be declared. POST routes are the same as AJAX
* routes except that the nonce verification is for POST requests and
* the handler does not do anything with the return value.
*/
$this->addPostRoute($r, ['PUT', 'POST'], '/makePostCake/withnonce', function ($args) {
// some database operation...
});
/*
* public POST routes are also declarable
*/
$this->addPublicPostRoute($r, 'POST', '/makePostCake/{cakeFlavor}', function ($args) {
// some database operation...
});
/*
* Route groups can also be added
*/
$this->addGroup($r, '/candy', function ($r) {
$this->addRoute($r, 'GET', '/bubblegum', function ($args) {
return 'Im the /candy/bubblegum route';
});
$this->addRoute($r, 'GET', '/airheads', function ($args) {
return 'Im the /candy/airheads route';
});
});
/*
* An array of middleware callables that run before and after route invokation
* can be passed in as arguments
*
* The final result is 'Im the /airheads route modified'
*
* NOTE: you can easily stop propagation in your middleware function
* by calling die()
*/
$this->addPublicRoute($r, 'GET', '/airheads', function ($args) {
return 'Im the /airheads route';
}, [
function ($args) {
// do some validation
},
],
[
function ($res, $args) {
return $res . ' modified';
},
]);
}
}
Detailed information about how routes are resolved can be found here.
After creating your routes, instantiate the class with your route namespace:
/*
* WARNING: you MUST instantiate the class sometime after WordPress core functions are
* loaded ('plugins_loaded', 'init', etc.).
*/
add_action('init', function () {
// 'routegroup' can be any route of your choice. 'nonce-key' and 'nonce-name' are also arbitrary.
$routes = new Routes('/routegroup', 'nonce-key', 'nonce-name')
});
HTML Forms
Nonce-included HTML forms can be created for a route:
$form = $routes->createPostForm(
'https://my-site.com/routegroup/makePostCake/strawberry', // the route
'<input type="hidden" name="myFormField" value="myFormValue" />', // the inner-html for the form
'post', // the form request type ('post', 'put', etc.)
'myformid' // OPTIONAL: id of form
)
Contributing
dist
directory version controlled?
Why is the Because this library packages a namespaced version of FastRoute
, and the tool for accomplishing this, mozart, cannot do it automatically.
Long answer: when this library is included as a composer dependency in another project that uses mozart
, mozart
will attempt to recursively namespace this package, as well as this packages dependencies. In this case, that dependency is FastRoute
. Even though mozart
can successfully bundle certain packages without any manual tweaks, unfortunately FastRoute
is not one such package. Because of this, we have to package an already bundled version of FastRoute
and make sure that our composer.json
does not include an autoload reference to it. Only then is it possible to require this package from another plugin/package that uses mozart
without any manual changes.