Let’s Learn: WordPress REST API

Tim Roberts
Let’s Learn:
Published in
9 min readDec 4, 2016

Github Repo of Bearded-Routing WordPress plugin: https://github.com/beardedtim/bearded-routing

I hate PHP. There’s no real reason why, if we’re being honest with each other. It isn’t any better or worse than JavaScript and I’m sure that in many aspects it is a better language. Regardless, I am always looking for ways to make it look and act more like JavaScript does. With WordPress’s REST API becoming stable, I think it’s time that we come up with a silly abstraction of it to make it look a tad more like the servers I am used to building.

Why?

I find it beneficial to build silly abstractions on top of a new technology. It helps me understand the API I am interacting with along with new edges and concepts of the language I am working in. Finding the leaks in my own abstraction helps me find the leaks in the one I am building on top of.

This is definitely not trying to be a tool that others use. There is no promise that this is a good idea or that this works how I think it does. This is just me telling my future self what we know about how the WordPress REST API works now and how we are interacting with it.

Goal

Our goal is to create a simple API for creating REST endpoints. We want it to handle and react similarly to how Express works because that is the tool we use most often in JavaScript and will be the foundation for how we build REST endpoints in WordPress. This means that instead of /(?P<param>.*?) being how we declare a endpoint param, we will use /:param instead. Let’s see what I mean.

In Express, we declare endpoints like so:

app.get(‘/api’,(request,response)=>{
response.send(‘data’)
})

While inside of WordPress we create it like so:

function callable($request){
return rest_ensure_response('data');
}
register_rest_route('base-url','/api,[
'methods'=>GET,
'callback'=>'callable'
]);

My compromise of these will be something like this:

$router->add_route('/api','api_get_handler');
$router->add_route('/api',['post'=>'api_post_handler']);
$router->add_route('/api/:id','api_with_param_handler');

and our handlers will get a request and route parameters like so:

function api_get_handler($request,$route){
return $route->send('get for "/api"');
}

Notice that our handler has to return a value. This is because WordPress makes you have to do that. It’s weird but we give for what we get. Let’s see how we create this.

Steps:

In order to do this, we are going to have to do a few things:

  1. Ensure that our input is good
  2. Take an Express-ish route and turn it into a WordPress route
  3. Keep track of what routes are registered so we can add methods to already declared routes.
  4. Call the correct callback when a route is hit.

Easy peasy, right? Let’s start. We need a class to hold all of our things, so let’s create our Router class:

if(!class_exists(‘BeardedRouter’){  class BeardedRouter{

public $root;
function __construct($root = 'bearded-family'){ $this->root=$root; } }}

Alright, so we created a class that takes a namespace/REST root endpoint. This way we don’t pollute the other REST endpoints. We set up a default in case we don’t set this but in practice, each group of REST endpoints will have their own root/namespace. Now we have the root set up, let’s create a method on this class to add a new route

public function add_route($route,$callbacks){    if(!$callbacks){     return new WP_Error('bad_route_added','You have not given add_route a callback',$callbacks);

}
}

Now we have a function that will take a route as a string and either a single callback or an array of callbacks. We also check to make sure that we have a callback to fire on this endpoint. If we forget to give it a callback, let’s return an error so that we are reminded of how this whole thing works.

The next challenge we will meet is what if we pass in a single callback instead of an array? Well, let’s make a rule: Any single method will be treated as a GET method. That sounds good. How can we tell our worker to create a single method as a ‘get’ callback?

if(is_string($callbacks)){
$callbacks = [
'get'=>$callbacks
];
}

We just set our $callbacks box to equal an array styled how our callbacks array would look, just with a single key called ‘get’, assigning the value as the single method string.

Now we need to create the route in a way that WordPress will understand. There are a few ways we could handle this but the way that I will be able to refactor later looks something like this:

public function create_wordpress_route($str){

return $route;
}

A function that takes in a string and returns a WordPress valid route. Since we are taking in Express-ish strings and not WordPress-ish strings, we need a way to create the WordPress-ish strings from the Express-ish ones. To do that, we can split the string at the ‘/’ character ( assuming that our routes will be in the format ‘/text/text/:param/text’ ), map over that value and transform any params into WordPress params, and then join them back together with the ‘/’ character:

public function create_wordpress_route($str){
$split = explode('/',$str);
$made = array_map(function($val){
if($this->_is_param($val)){
return $this->_create_param($val);
}else {
return $val;
}
},$split);
$endpoint = implode('/',$made);
return $endpoint;
}

In the transformation function, we take in a string and check if it is a param. If it is, we transform that value into a WordPress param and return the newly created string. If it is not, we just return the string unchanged. Let’s see how we check if something is a param:

public function _is_param($str){
return strpos($str,':') === 0;
}

We can assume that any time we have ‘:sometext’ we are telling our worker that we want that text to be a parameter for that route. So if a colon is found at position 0, it is a parameter. If we know it is a parameter, we have to create it in the WordPress format of ‘(?P<sometext>.*?)’. Which looks like all we need to do is take out the colon from the string and place the result in the middle of another string:

public function _create_param($str){
return '(?P<'.substr($str, 1).'>.*?)';
}

So now, if our create_wordpress_route worker is met with a string that looks like ‘/albums/:id’, it returns a string of ‘/albums/(?P<id>.*?)’. Which is how WordPress seems to need it. And that knocks out half of our todo list for this API.

At this point in time, this function has an array of verb(s) and callback(s) and the WordPress route string. Now we need to either create the route if we haven’t before or we need to update that handler to have the new methods. This is where I got confused.

The Struggle

The idea of having a string to check against so I can update routes and be able to read/modify them later was a simple enough solution and I really wanted to keep that interface. I wanted to be able to update and keep track of the route through an array of handlers and be able to query that by the string that create_wordpress_route creates. The problem came in with the WordPress API’s register_rest_route function.

This worked for non-param routes such as ‘/albums’ and ‘/users’ but when we added in the params, we ran into an issue because we are taking in Express style strings and not Wordpress style. What that meant was that we had no idea what route ‘/albums/album-name/songs’ matched against even though we told our users that we could handle that route.

I spent a long time trying to craft the perfect regex solution to this, trying to match custom input against the current route off of the request object. It was a waste of a few hours of my life that I will never get back all because I don’t know how to create higher-order anonymous functions in PHP that can hold some state that I can pass around. It took me a long time to stop trying to hammer a nail with a wrench.

The register_rest_route function took an array that has a callback key. This we want to be the function that our user passed to us. However if we try to call this from our BeardedRouter object, it will not be able to easily tell what callback to fire because PHP regex is stupid hard to understand. Instead, what we can do is create a new class called BeardedRoute that houses all of this information and tell WordPress to call that class and its handle_route instead of trying to use the BeardedRouter class. Let’s see what I’m talking about:

$route_handler = new BeardedRoute($cleaned_route,$callbacks);
// We register this endpoint with WordPress
// giving all of the methods as possible endpoints
// because in our $route_handler->handle_route function
// we throw an error if a handler was not set
register_rest_route($this->root,$cleaned_route,[
'methods'=>ALL_OF_THEM,
'callback'=>[$route_handler,'handle_route']
]);

// Then we add this new handler to our route_handlers
// array
$this->route_handlers[$cleaned_route] = $route_handler;

We create a property on the BeardedRouter class to house an array of WordPress routes and their corresponding BeardedRoute objects. I am hoping that these are passed by reference so when we interact with them later we are actually changing stuff and not just recreating the class and calling its methods. Why in the world is OOP so difficult? Why does PHP have to not be exactly like JavaScript?! I digress.

A lot of stuff is happening at this step so let’s break it down.

  1. Our worker creates a new BeardedRoute object, giving it the WordPress route string and the callbacks array from before.
  2. It registers the WordPress route string as a route on the BeardedRouter’s root property.
  3. During the registration, it attaches to ALL of the methods. This is a super bad way to do this but it works. On all of these verbs, we attach a single the handle_route method on the BeardedRoute object that we created.
  4. We add the BeardedRoute object to the BeardedRouter’s route_handlers array so that if we want to see what routes are attached to it we can just query this object.

Eventually I got two classes built to work as a Route and a Router.

Our BeardedRoute class looks like:

And the finished BeardedRouter looks like:

The Flow Of Data

The best way I know how to grasp a tool is to understand the flow of data between its parts. So let’s walk through what happens when we use these two classes:

  1. I have 0 custom routes created and need to create some

We need to hook into the action rest_api_init with the function that creates our routes. Which looks something like this:

This starts off our process. When we create a new instance of BeardedRouter, we get returned an object that has some state set. It has a root property set to ‘/api’. Then we use that object to call add_route which takes in two strings.

Our worker then checks the second string. It sees that it is a string and turns it into an array of [‘get’=>’callback’]. Then we create a the correct version of our WordPress route.

Once we have our callbacks array and the route, we check to see if we have already registered a handler for the route. If we have not, we create a new instance of BeardedRoute and give it our route and our callback array.

When we create the new BeardedRoute, it takes the route and callback array and sets it as its internal state. Then we use the route and the BeardedRoute object and tell WordPress to call that object’s handle_route method. WordPress will pass that function the request object.

We then add the BeardedRoute as the handler inside of our BeardedRouter handlers array so that we can reference that handler later if we need to.

If we have already created the route, we just call the handler’s add_methods method which updates the handler’s methods state.

At this point, we have successfully create routes and pointed them at a handler object along with kept reference to those handlers for later. We are onto the next step in this flow.

2. Someone sends a REST request to one of the endpoints we created in the above process

WordPress does a lot of things here that I am not even going to worry about right now. What is important to know is that WordPress will call the above handler’s handle_route method and pass it a request object.

At this point, our worker knows the request along with its own registered verbs and callbacks. What we now do is get the HTTP verb from the request by using its get_method method. We then check to see if we have a registered callback for that verb. If we do not ( because we registered ALL verbs in the beginning, we need to have an error for when we try to reach an endpoint with a fallback. This should be refactored later. Probably won’t be but it should be. ), we return an error.

If it does have a callback for that verb, we call the callback, passing it the request object along with the handler itself.

At this point, we are inside of the function that we registered and are in charge of returning something to the user. We handle the route logic here and do whatever it is the user is asking from us: get users, post a product, etc. If you do not return something from this function, I believe that WordPress returns null. Whatever you return here is what WordPress sends to the client. It might/probably does some sort of cleansing and stuff though.

And there we have it! We have a simple wrapper of the WordPress REST API classes and a way to attach callbacks to our just endpoints!

--

--

Tim Roberts
Let’s Learn:

dev kid who likes to write in english instead of code