Want to learn object-oriented programmming? Get my free course

Designing a system: WordPress REST API endpoints

Let’s talk about the WordPress REST API. It’s been around for a little while now, and you might have even started using it in your projects. After all, a lot of us are trying to get more into JavaScript. (Zac Gordon has built a great course if you’re looking to get into it.)

But that doesn’t mean that the standard WordPress REST API will be good enough for your project. There’s a good chance that you’ll need to extend it so that you can do more with it. And that’s done using PHP even if you’re working with JavaScript. (Sorry JavaScript lovers!)

That means that this is still a great excuse to use object-oriented programming with WordPress. (Yay!) In fact, it’s an excellent opportunity to piece different object-oriented concepts together. This will let us design a system to extend the WordPress REST API!

Why not use the controller class?

If you’re already familiar with the WordPress REST API, you might know that it already has a class called WP_REST_Controller. You might be wondering why we’re not just using that class and moving on. Well, that’s a good question that’s worth discussing!

If you look at the code for the WP_REST_Controller class, you’ll notice one thing right away. It’s a really big class that does a lot! In fact, it does a lot more than we need to if we only want to add an endpoint to the REST API.

That said, this makes sense if you look at how WordPress core uses the WP_REST_Controller class. WordPress core doesn’t want to add individual REST API endpoints. It’s looking to add groups of them centred around one specific resource.

For example, you have WP_REST_Users_Controller for WordPress users. And WP_REST_Posts_Controller for WordPress posts. If you look at all the classes that extend the WP_REST_Controller class, they all centre around a specific WordPress resource.

But what do we do when we just want to add one endpoint? Or a few endpoints that we don’t revolve around a specific resource? That’s where we hit the design limitations of the WP_REST_Controller class. (Or the MVC pattern in general.) It just wasn’t meant to deal with those situations at all.

So this is why we’re not going to use the WP_REST_Controller class. We want to be able to create individual endpoints as well as groups of them. This means designing a system from the ground up around creating endpoints. (This also helps us follow the single responsibility principle.)

A brief look at the REST API

Whenever we design a system, we should always try to familiarize ourselves with the problem space. This means going over concepts and terminology. That way we have a good understanding of the problem that we’re designing the system to solve.

We’re going to do that with the WordPress REST API. Now, we’re not going to go over how it all works under the hood. (That would be a monster article in itself!) But we should have a good understanding of the terminology for the WordPress REST API. And we’ll need to go over some technical aspects of it as well.

Terminology

Let’s start with terminology. We should all be on the same page when we talk about different elements of the WordPress REST API. And, as you’ll see, some of the terms used can lead be a bit confusing because they’re used in multiple contexts.

Endpoints and routes

Alright! So we mentioned that the WP_REST_Controller class deals with groups of endpoints. That said, this isn’t the case with the WordPress REST API itself. It deals with individual endpoints.

Now, you won’t see the term “endpoint” used that much with the WordPress REST API. Instead, you’re going to see the term “route” used more. In fact, the term “route” is often a catchall for all sorts of things.

That said, a route and an endpoint are two different things. A route is a name given to an endpoint. It’s not the endpoint itself. But even then, it’s still common for people to use those two terms interchangeably.

The WordPress REST API glossary does try to help mitigate this by explaining the difference between the two. But it still calls the function to create an endpoint register_rest_route and not register_rest_endpoint. So that doesn’t help with the confusion around those two terms.

Namespaces

Namespaces is another important concept with the WordPress REST API. And before you get too excited, it’s not the PHP kind. (Sorry!) That said, they both serve a similar purpose.

Both are a mechanism used to organize your code (or endpoints to be more specific here) together. This helps prevent conflicts with code (or, in this case, endpoints) created by other developers. That’s pretty much all there is to it.

In practice, namespaces with the WordPress REST API are pretty simple. They’re just a prefix that appears at the beginning of all your endpoint URLs. The suggested format for them is slug/version.

The slug would be the slug of your plugin or theme. version is the version of the API. How a REST APIs handles versioning can become quite complicated over time. (You can read on some of the different ways to do it here.) That’s why it’s p for REST APIs to put the API version inside their URLs.

So let’s say that we were creating the first version of our plugin’s REST API. The namespace used by its endpoints would be myplugin/v1. In fact, that’s the namespace that we’re going to use for the rest of this article. (How convenient!)

register_rest_route function

We mentioned the register_rest_route function earlier when we discussed the WP_REST_Controller class. We’re going to come back and look at it now because it’s a vital function. In fact, it’s the only WordPress REST API function that we’ll need for this entire article. (Woah!)

That’s because the register_rest_route function is the function used to create WordPress REST API endpoints. That makes the function an essential part of the system that we’re designing. So it’s a good idea to go over how it works!

Parameters

The register_rest_route has four parameters: namespace, route, args and override. Both namespace and route are mandatory parameters. Meanwhile,args and override are optional.

You should have a good idea of what namespace is after we went over the concept earlier. It’s the namespace of the endpoint that we’re adding. For us, that’ll be myplugin/v1.

namespace will stay the same most of the time. (The only time that it’ll change is if you’re building more than one unique APIs.) The parameter that’ll change from endpoint to endpoint is the route parameter. This is the parameter that the register_rest_route function uses to create the unique part of the full endpoint URL.

We’re going to skip over args and talk about override for a second. override is also a pretty self-explanatory parameter. You use it to tell whether you want to override an existing endpoint if there’s one already using the same namespace and route as yours.

The “args” parameter explained

So there’s a reason why we skipped over the args parameter. While the three parameters that we saw were quite easy to understand from their name, this one isn’t. Its name doesn’t really tell us what it does. args is the parameter used to pass the configuration options of the endpoint.

The register_rest_route function uses a multidimensional array to represent these configuration options. It’s what makes this parameter the most complicated part of the register_rest_route function. (And the WordPress REST API to an extent.) That’s why we’re going to take a moment to go over it all.

The first level of the args array has four possible configuration options. Those are args, callback, methods and permission_callback. It expects those to be keys inside the array.

Three “args” parameters

It’s worth pointing out that args here isn’t the same args as the parameter that we’re looking at. args is itself a multidimensional configuration array. (Yes, it’s confusing. And that’s why naming is important!) That’s why we’re also going to skip over it for now and get back to it at the end.

callback is the callback that the WordPress REST API will use when it determines that a request is for your endpoint. The WordPress REST API expects callback to be a callable value. For most of us, that means a string with the function name that we want the WordPress REST API to call.

methods is the HTTP request methods (e.g. POST or GET) that the endpoint responds to. You can assign it either a string or an array of strings. A string is when if you want it to respond to a single method and the array is for when you want it to respond to several.

permission_callback is another callback that you can assign to an endpoint. The WordPress REST API uses it to determine if the incoming request can access the endpoint. The callback can return either true, false or a WP_Error object. It’s also important to note that, if the permission_callback doesn’t return true, the WordPress REST API won’t call our callback.

The other “args”

Alright, now that we saw the three straightforward args array options. We can move on to the other args in the register_rest_route function. That’s the args option in the args array.

You use it to configure what arguments (thus the name args) the WordPress REST API expects to receive for the endpoint that you’re registering. You also use it to tell the WordPress REST API how to process these arguments when it receives them. And, like its parent array, the args array is also an associative array.

Each key of the array is a parameter that your endpoint can accept. You can then configure that parameter using four configuration options. They are default, required, sanitize_callback and validate_callback.

default and required are quite easy to understand. default is the default value of the parameter if the endpoint doesn’t receive a value for it. required is a boolean value that determines whether the parameter is mandatory or not. That said if you set a default value, required won’t do anything since there will always be a value.

The last two options are sanitize_callback and validate_callback. As you can guess from their names, both of them are callbacks similar to the ones we saw earlier. You use sanitize_callback to sanitize the value received by the endpoint. Meanwhile, validate_callback is the callback used to determine whether the value is valid or not.

Concerning the order of the callbacks, the WordPress REST API always calls validate_callback before sanitize_callback. If validate_callback returns false, the WordPress REST API won’t do a call to the sanitize_callback callable. So that’s always something to keep in mind when working with both callbacks.

Building our system

Alright, so this was quite a bit of explanation of the register_rest_route function! But it’s important that you understand its inner workings well. We’re going to make use of this information throughout the design process.

The endpoint contract

The first thing that we want to do is define a contract that all our endpoints will have to follow. This contract should act as a bridge between our endpoint classes and the WordPress REST API. This would allow us to use these classes with the register_rest_route function.

The easiest way to build such a contract is to create methods for each of the register_rest_route function parameters. That said, this isn’t an ideal way to approach building our contract in this particular case. That’s because, as we saw earlier, the register_rest_route function parameters aren’t that straightforward.

Creating contract around the “args” array

Instead, we’re going to focus a lot more on building a contract around the last parameter of the register_rest_route function. That was the args array parameter. We’re going to create methods for each of the keys in the associative array.

/**
 * A WordPress REST API endpoint.
 */
interface MyPlugin_EndpointInterface
{
    /**
     * Get the expected arguments for the REST API endpoint.
     *
     * @return array
     */
    public function get_arguments();

    /**
     * Get the callback used by the REST API endpoint.
     *
     * @return callable
     */
    public function get_callback();

    /**
     * Get the callback used to validate a request to the REST API endpoint.
     *
     * @return callable
     */
    public function get_permission_callback();

    /**
     * Get the HTTP methods that the REST API endpoint responds to.
     *
     * @return mixed
     */
    public function get_methods();
}

So here’s the first iteration of the MyPlugin_EndpointInterface interface. It has four methods. One for each key of the args associative array as we just discussed.

The first method is get_arguments. It returns that the second args associative array that we saw earlier. (And it’s still The one that you use to configure the arguments that your WordPress REST API endpoint expects.

The second and third methods are get_callback and get_permission_callback. These are the two callables used by the endpoint. They map to the callback and permission_callback array keys of the args parameter associative array.

The last method is get_methods. You use it to return the HTTP request methods that your endpoint responds to. You can have it return either a string with a single HTTP request method or an array with several.

An incomplete contract

Now, let’s try to use our MyPlugin_EndpointInterface interface. To do that, we’re going to imagine a function that we have to build a function that uses it. We’ll call that function myplugin_register_endpoint. Given the contract that we’ve created so far, this function could look something like this:

/**
 * Register an endpoint with the WordPress REST API.
 *
 * @param MyPlugin_EndpointInterface $endpoint
 */
function myplugin_register_endpoint(MyPlugin_EndpointInterface $endpoint)
{
    register_rest_route($namespace, $route, array(
        'args' => $endpoint->get_arguments(),
        'callback' => $endpoint->get_callback(),
        'methods' => $endpoint->get_methods(),
        'permission_callback' => $endpoint->get_permission_callback(),
    ));
}

As an additional constraint, the myplugin_register_endpoint function only has a single parameter. It only accepts an object that implements our MyPlugin_EndpointInterface interface as an argument. We use type hinting to enforce this requirement.

The function itself is just a wrapper around the register_rest_route function. We use the four methods from our MyPlugin_EndpointInterface interface with the last function argument. As we discussed earlier, this argument is an associative array. And we use the return value of each of the interface method as a value for one of the keys inside that associative array.

Filling the gaps

But what about the first two parameters of the register_rest_route function? Right now, they only have placeholder variables in them. In general, most of us will always want to register an endpoint using the same namespace. So we could change our myplugin_register_endpoint function to take that into consideration like this:

/**
 * Register an endpoint with the WordPress REST API.
 *
 * @param MyPlugin_EndpointInterface $endpoint
 */
function myplugin_register_endpoint(MyPlugin_EndpointInterface $endpoint)
{
    register_rest_route('myplugin/v1', $route, array(
        'args' => $endpoint->get_arguments(),
        'callback' => $endpoint->get_callback(),
        'methods' => $endpoint->get_methods(),
        'permission_callback' => $endpoint->get_permission_callback(),
    ));
}

Above is our modified myplugin_register_endpoint function. We replaced the namespace placeholder variable with the myplugin/v1 string. This is the namespace that we decided to use for this article earlier.

Adding the endpoint route

This leaves us with the route placeholder variable. While we know that the namespace has to be the same for all our endpoints, that’s not the case with the route parameter. Each endpoint needs to have a unique route.

This means that we need to make changes to our MyPlugin_EndpointInterface interface. It should have a method that returns the value that we pass to the route parameter. So let’s add it:

/**
 * A WordPress REST API endpoint.
 */
interface MyPlugin_EndpointInterface
{
    // ...

    /**
     * Get the path pattern of the REST API endpoint.
     *
     * @return string
     */
    public function get_path();

    // ...
}

Here’s our MyPlugin_EndpointInterface interface with its new method. You probably noticed that we called it get_path instead of get_route. We did that to use the same terminology as URLs. The method should return the URL path of our endpoint without the namespace.

/**
 * Register an endpoint with the WordPress REST API.
 *
 * @param MyPlugin_EndpointInterface $endpoint
 */
function myplugin_register_endpoint(MyPlugin_EndpointInterface $endpoint)
{
    register_rest_route('myplugin/v1', $endpoint->get_path(), array(
        'args' => $endpoint->get_arguments(),
        'callback' => $endpoint->get_callback(),
        'methods' => $endpoint->get_methods(),
        'permission_callback' => $endpoint->get_permission_callback(),
    ));
}

Now all that we have left to do is update our myplugin_register_endpoint function. This is what we’ve done above. We replaced the route placeholder variable with a call to the get_path method.

Registering our plugin endpoints

At this point, we have our MyPlugin_EndpointInterface interface and our working myplugin_register_endpoint function. These two components let us both build an endpoint and register it with the WordPress REST API. The only thing left to do is to create a function to register all our plugin’s endpoints.

/**
 * Register all our endpoints with the WordPress REST API.
 */
function myplugin_register_endpoints()
{
    $endpoints = apply_filters('myplugin_endpoints', array(
        // ...
    ));
    
    foreach ($endpoints as $endpoint) {
        myplugin_register_endpoint($endpoint);
    }
}
add_action('rest_api_init', 'myplugin_register_endpoints');

That’s what the myplugin_register_endpoints function above does. We use the add_action function to register it to the rest_api_init action hook. That’s the standard hook to use if you want to use the register_rest_route function.

The myplugin_register_endpoints function itself is pretty simple. It takes an array of endpoints and passes them one at a time to our myplugin_register_endpoint function. Before assigning the array to the endpoints variable, we pass it to the apply_filters function so that others can modify it. But you can skip this step if you don’t want to allow others to modify the endpoints that your plugin is registering.

Improving our system

So this is all that you need to build and use your own endpoint classes with the WordPress REST API. If you wanted, you could stop reading now. That said, we’re still not done from a design perspective.

Our system still has some rough edges to it though. Let’s look at some of the ways we can improve what we’ve built so far. That’s what we’ll focus on for the rest of the article.

Overcoming the limitations of our interface

A good place to start is by thinking about what we want an endpoint class to do. That’s because our MyPlugin_EndpointInterface interface doesn’t really help us on that front. It’s just a contract for mapping our endpoint class to the register_rest_route function.

The most glaring of this disconnect is that the MyPlugin_EndpointInterface interface has a get_callback method. But our endpoint classes are going to need to have a method for that callback. We really need a way to express that requirement in our design.

That said, this isn’t something that you can express well using an interface. And it’s not what they’re meant for either. Having an existing method for the get_callback method to return isn’t part of the contract with the register_rest_route function.

Using an abstract class

But that doesn’t mean that there aren’t other tools that we can use to do it. In fact, a great way to solve this problem is by using an abstract class. We can use it to build a base class for all our endpoints that can also take this need into consideration.

/**
 * Base class for WordPress REST API endpoints.
 */
abstract class MyPlugin_AbstractEndpoint implements MyPlugin_EndpointInterface
{
    /**
     * Get the callback used by the REST API endpoint.
     *
     * @return callable
     */
    final public function get_callback()
    {
        return array($this, 'respond');
    }

    /**
     * Respond to a request to the REST API endpoint.
     *
     * @param WP_REST_Request $request
     *
     * @return mixed
     */
    abstract public function respond(WP_REST_Request $request);
}

The MyPlugin_AbstractEndpoint class above is our abstract base class. It implements our MyPlugin_EndpointInterface interface. But it only implements the get_callback method from it. We’re leaving it to the classes that extend our MyPlugin_AbstractEndpoint class to implement the remaining interface methods.

Let’s look at the get_callback method now. It returns an array with an instance of itself and the string respond. This array is the callable that represents a call to our new respond abstract method.

The respond abstract method will always receive a WP_REST_Request object from the WordPress REST API. It expects you to return the data that you want to send back as a response. That data can come in different formats. Or you can return a WP_Error object if there was an error.

If you’re not familiar with an abstract method, it’s pretty much the same as an interface method. It’s a method that any class that extends our MyPlugin_AbstractEndpoint class will have to implement. So instead of having to implement the get_callback method, a class now needs to implement the respond method.

Why does this fix our disconnect?

This might seem like a trivial change to you. We just replaced one method to implement with another. How does this change anything?

From an object-oriented design perspective, this small change makes all difference. This fixes the disconnect between our MyPlugin_EndpointInterface interface and what we want our endpoint classes to do. How does it do that?

As we mentioned earlier, the get_callback method is a bit different from the other MyPlugin_EndpointInterface interface methods. It’s not a method that defines the behaviour of an endpoint. It’s a method that only exists because of the mapping between the MyPlugin_EndpointInterface interface and the register_rest_route function.

But that’s not the case with the respond method. This is the method that generates the response to a request to our endpoint. Unlike the get_callback method, it’s a method that needs to be unique for each endpoint.

A note on the use of the final keyword

It’s worth taking a moment to discuss a specific design decision in our MyPlugin_AbstractEndpoint class. It’s the use of the final keyword with the get_callback method. This might seem like a small and insignificant detail, but it’s quite important.

That’s because some developers find the use of the final keyword excessive. In fact, some developers advocate never to use it at all. That said, in this case, it makes a lot of sense with our MyPlugin_AbstractEndpoint class.

The whole reason why it exists is so that the get_callback method can return a callable pointing to our new respond abstract method. If someone overrides the get_callback method to point to another method, our MyPlugin_AbstractEndpoint class loses its purpose. That’s why it makes sense to make the get_callback method final.

Creating HTTP status code response classes

The respond and get_callback methods are a good way to bring up the next improvement to our system. We mentioned in the previous section that the respond method would return either data of a mixed type or a WP_Error object. This is also what any callback that you returned with the get_callback method would have to do as well.

The problem is that we’re leaving out an important aspect of REST API response generation. That’s the use of the proper HTTP status code for our responses. We need a way to tell the WordPress REST API what status code we want it to send with our response.

To do this, we’re going to need to build two sets of classes. One set for when we want to respond with data or any type of valid response. And another when you want to respond with an error.

Valid response classes

Let’s start with the response classes that we want to use when we have a valid response. So far, we’ve said that when we have any data to send back, we can have our callback return it. The WordPress REST API will then take that data and convert it to an HTTP response for you.

But behind the scenes, the WordPress REST API is converting your data to a WP_REST_Response object. That’s why we’re going to use it as our base class for our valid response classes. Let’s look at what this looks like in practice:

/**
 * A REST API response for a "200 Ok" response.
 */
class MyPlugin_OkResponse extends WP_REST_Response
{
    /**
     * Constructor.
     *
     * @param mixed $data
     * @param array $headers
     */
    public function __construct($data = null, array $headers = array())
    {
        parent::__construct($data, 200, $headers);
    }
}

Above, we have the MyPlugin_OkResponse class. It is one of the many response classes that you could create. This one represents a WordPress REST API response with the 200 Ok status code.

From a design perspective, the MyPlugin_OkResponse class itself is pretty simple. All that we did is extend the WP_REST_Response class that we just talked about. We then overrode its constructor.

By default, the constructor of the WP_REST_Response class has three parameters: data, status and headers. The goal of our MyPlugin_OkResponse class is to enforce the status of the response. So our new constructor removes status as a parameter. And instead, we pass 200 to the parent constructor.

That’s all there is to it! Let’s say that you wanted to create a response class for the 201 Created status code. You’d create a class called MyPlugin_CreatedResponse. And, in the constructor, you’d pass 201 to the parent constructor instead of 200 like this:

/**
 * A REST API response for a "201 Created" response.
 */
class MyPlugin_CreatedResponse extends WP_REST_Response
{
    /**
     * Constructor.
     *
     * @param mixed $data
     * @param array $headers
     */
    public function __construct($data = null, array $headers = array())
    {
        parent::__construct($data, 201, $headers);
    }
}

Error response classes

With responses to errors, you can also do what we just did and extend the WP_REST_Response class. That said, we mentioned earlier that the WordPress REST API also supports using the WP_Error class. The WP_REST_Server converts that WP_Error object into a WP_REST_Response object. (The error_to_response method is the method that does it.)

So which one should we use? Well, a WP_Error object is a common (if not even a standard) way to represent an error. It would make more sense that we extend that class so that we stay consistent with the rest of WordPress.

Now, the next thing that we have to tackle is the HTTP status code for our error response. Where do we store that information with the WP_Error class? The WordPress REST API expects us to store it as part of the error_data for our error like this:

/**
 * A WordPress error representing a "404 Not Found" REST API response.
 */
class MyPlugin_NotFoundError extends WP_Error
{
    /**
     * Constructor.
     *
     * @param string $code
     * @param string $message
     */
    public function __construct($code = '', $message = '')
    {
        parent::__construct($code, $message, array('status' => 404));
    }
}

The MyPlugin_NotFoundError class above represents an error response with a 404 Not Found status code. Like the MyPlugin_OkResponse class, the MyPlugin_NotFoundError class is also not that complicated. We extended the WP_Error class and overrode its constructor.

Our constructor only has two parameters: code and message. We removed the data parameter. That’s because we hardcoded the data argument that we’ll pass to the parent constructor. It’s an array with the key status containing the value 404 for our 404 Not Found status code.

/**
 * A WordPress error representing a "404 Not Found" REST API response.
 */
class NotFoundError extends WP_Error
{
    /**
     * Constructor.
     *
     * @param string $code
     * @param string $message
     * @param mixed  $data
     */
    public function __construct($code = '', $message = '', $data = '')
    {
        if (!is_array($data)) {
            $data = (array) $data;
        }

        $data = array_merge($data, array('status' => 404));

        parent::__construct($code, $message, $data);
    }
}

If you wanted to keep all three constructor parameters, you could do something like what we did above. We started by using a guard clause to check if the data argument that we received was an array. If it’s not, we cast it as an array.

We then merge our status key-value pair using the array_merge function. It’s worth noting that the order of the arguments with the array_merge function is important. We want to pass our array with our HTTP status code as the second argument to the array_merge function. That’s because it’ll overwrite the status value in our data array (if there’s one) ensuring that it always has 404 as the status value.

Using this system with the event management system

The last improvement that we’re going to look at revolves another system that we’ve designed: the event management system. If you’re not familiar with this system, you should definitely read about it! It’s a very important system for using object-oriented programming with WordPress.

It’s because of that fact that we’re going to look at how we can integrate the REST API endpoint system with it. But, if you’re not familiar with the system and don’t have time to look it up now, you can skip this section. You can always come back to it at another time!

Why is the event management system needed?

With this out of the way, let’s look at why we need the event management system. What are we trying to improve with it? It goes back to the code that we had earlier to register the endpoints.

/**
 * Register an endpoint with the WordPress REST API.
 *
 * @param MyPlugin_EndpointInterface $endpoint
 */
function myplugin_register_endpoint(MyPlugin_EndpointInterface $endpoint)
{
    register_rest_route('myplugin/v1', $endpoint->get_path(), array(
        'args' => $endpoint->get_arguments(),
        'callback' => $endpoint->get_callback(),
        'methods' => $endpoint->get_methods(),
        'permission_callback' => $endpoint->get_permission_callback(),
    ));
}

/**
 * Register all our endpoints with the WordPress REST API.
 */
function myplugin_register_endpoints()
{
    $endpoints = apply_filters('myplugin_endpoints', array(
        // ...
    ));
    
    foreach ($endpoints as $endpoint) {
        myplugin_register_endpoint($endpoint);
    }
}
add_action('rest_api_init', 'myplugin_register_endpoints');

This code doesn’t fit with what we’ve been trying to do with our system. It isn’t object-oriented (it’s procedural) and that’s the reason why we built this system in the first place. But with the event management system, we can move all this code into an event subscriber class.

/**
 * Subscriber that manages the integration with the WordPress REST API.
 */
class MyPlugin_RestApiSubscriber implements MyPlugin_SubscriberInterface
{
    /**
     * The WordPress REST API endpoints used by the plugin.
     *
     * @var MyPlugin_EndpointInterface[]
     */
    private $endpoints;

    /**
     * The namespace of the WordPress REST API endpoints managed by the subscriber.
     *
     * @var string
     */
    private $namespace;

    /**
     * Constructor.
     *
     * @param string                       $namespace;
     * @param MyPlugin_EndpointInterface[] $endpoints
     */
    public function __construct($namespace, array $endpoints = array())
    {
        $this->endpoints = array();
        $this->namespace = trim($namespace, '/');

        foreach ($endpoints as $endpoint) {
            $this->add_endpoint($endpoint);
        }
    }

    /**
     * Returns an array of events that this subscriber wants to listen to.
     *
     * The array key is the event name. The value can be:
     *
     *  * The method name
     *  * An array with the method name and priority
     *  * An array with the method name, priority and number of accepted arguments
     *
     * For instance:
     *
     *  * array('event_name' => 'method_name')
     *  * array('event_name' => array('method_name', $priority))
     *  * array('event_name' => array('method_name', $priority, $accepted_args))
     *
     * @return array
     */
    public static function get_subscribed_events()
    {
        return array(
            'rest_api_init' => 'register_endpoints',
        );
    }

    /**
     * Register endpoints with the WordPress REST API.
     */
    public function register_endpoints()
    {
        foreach ($this->endpoints as $endpoint) {
            $this->register_endpoint($endpoint);
        }
    }

    /**
     * Add a new WordPress REST API endpoint to the subscriber.
     *
     * @param MyPlugin_EndpointInterface $endpoint
     */
    private function add_endpoint(MyPlugin_EndpointInterface $endpoint)
    {
        $this->endpoints[] = $endpoint;
    }

    /**
     * Get the arguments used to configure the endpoint with the WordPress REST API server.
     *
     * @param MyPlugin_EndpointInterface $endpoint
     *
     * @return array
     */
    private function get_arguments(MyPlugin_EndpointInterface $endpoint)
    {
        return array(
            'args' => $endpoint->get_arguments(),
            'callback' => $endpoint->get_callback(),
            'methods' => $endpoint->get_methods(),
            'permission_callback' => $endpoint->get_permission_callback(),
        );
    }

    /**
     * Register the given endpoint with the WordPress REST API.
     *
     * @param MyPlugin_EndpointInterface $endpoint
     */
    private function register_endpoint(MyPlugin_EndpointInterface $endpoint)
    {
        register_rest_route($this->namespace, $endpoint->get_path(), $this->get_arguments($endpoint));
    }
}

Here’s our MyPlugin_RestApiSubscriber class. It implements the MyPlugin_SubscriberInterface from the event management system. This interface requires that our class have the get_subscribed_events static method.

The get_subscribed_events static method is the point of interaction between our class and the plugin API. We use it to register our register_endpoints class method with the rest_api_init hook. This is pretty much the same thing that our procedural code did earlier.

Constructor

Where things change is inside the class. Let’s start with the MyPlugin_RestApiSubscriber class constructor which you can find below. That constructor has two parameters: namespace and endpoints.

/**
 * Subscriber that manages the integration with the WordPress REST API.
 */
class MyPlugin_RestApiSubscriber implements MyPlugin_SubscriberInterface
{
    /**
     * The WordPress REST API endpoints used by the plugin.
     *
     * @var MyPlugin_EndpointInterface[]
     */
    private $endpoints;

    /**
     * The namespace of the WordPress REST API endpoints managed by the subscriber.
     *
     * @var string
     */
    private $namespace;

    /**
     * Constructor.
     *
     * @param string                       $namespace;
     * @param MyPlugin_EndpointInterface[] $endpoints
     */
    public function __construct($namespace, array $endpoints = array())
    {
        $this->endpoints = array();
        $this->namespace = trim($namespace, '/');

        foreach ($endpoints as $endpoint) {
            $this->add_endpoint($endpoint);
        }
    }

    // ...

    /**
     * Add a new WordPress REST API endpoint to the subscriber.
     *
     * @param MyPlugin_EndpointInterface $endpoint
     */
    private function add_endpoint(MyPlugin_EndpointInterface $endpoint)
    {
        $this->endpoints[] = $endpoint;
    }

    // ...
}

namespace is the namespace that we’re going to use to register the REST API endpoints. This would replace the myplugin/v1 namespace string that we were using earlier. Rather than having it hardcoded, we would pass it as an argument.

endpoints is an array of MyPlugin_EndpointInterface objects that we want to register. You’ll notice that we don’t assign it to the endpoints internal property right away. Instead, we just initialize it as an empty array.

After doing that, we loop through the endpoints argument. We pass each endpoint in that array to the add_endpoint method. This ensures that all the elements of the endpoints array implement the MyPlugin_EndpointInterface interface.

Registering our endpoints

The rest of the class focuses on registering the endpoints. As we said earlier, the get_subscribed_events static method registers the register_endpoints method with the plugin API. The plugin API will call that method when the WordPress REST API calls the rest_api_init hook.

/**
 * Subscriber that manages the integration with the WordPress REST API.
 */
class MyPlugin_RestApiSubscriber implements MyPlugin_SubscriberInterface
{
    // ...

    /**
     * Returns an array of events that this subscriber wants to listen to.
     *
     * The array key is the event name. The value can be:
     *
     *  * The method name
     *  * An array with the method name and priority
     *  * An array with the method name, priority and number of accepted arguments
     *
     * For instance:
     *
     *  * array('event_name' => 'method_name')
     *  * array('event_name' => array('method_name', $priority))
     *  * array('event_name' => array('method_name', $priority, $accepted_args))
     *
     * @return array
     */
    public static function get_subscribed_events()
    {
        return array(
            'rest_api_init' => 'register_endpoints',
        );
    }

    /**
     * Register endpoints with the WordPress REST API.
     */
    public function register_endpoints()
    {
        foreach ($this->endpoints as $endpoint) {
            $this->register_endpoint($endpoint);
        }
    }

    // ...

    /**
     * Get the arguments used to configure the endpoint with the WordPress REST API server.
     *
     * @param MyPlugin_EndpointInterface $endpoint
     *
     * @return array
     */
    private function get_arguments(MyPlugin_EndpointInterface $endpoint)
    {
        return array(
            'args' => $endpoint->get_arguments(),
            'callback' => $endpoint->get_callback(),
            'methods' => $endpoint->get_methods(),
            'permission_callback' => $endpoint->get_permission_callback(),
        );
    }

    /**
     * Register the given endpoint with the WordPress REST API.
     *
     * @param MyPlugin_EndpointInterface $endpoint
     */
    private function register_endpoint(MyPlugin_EndpointInterface $endpoint)
    {
        register_rest_route($this->namespace, $endpoint->get_path(), $this->get_arguments($endpoint));
    }
}

Meanwhile, the register_endpoints method is pretty much like our myplugin_register_endpoints function. The only thing missing is the call to apply_filters. This is a bit trickier to do with the event management system, so we’re skipping it for simplicity.

The register_endpoints method itself loops through the endpoints in our endpoints internal property. We pass each endpoint to the register_endpoint method. This method is also almost identical to the myplugin_register_endpoint function earlier.

The only difference is that we created a new method for the arguments parameter of the register_rest_route function. The get_arguments method generates the arguments array that we pass to the register_rest_route function. The method itself just returns an array by calling the relevant MyPlugin_EndpointInterface interface methods for its values.

And there you have it!

This is all that you need to create a system for managing WordPress REST API endpoints. While we saw a lot of ways to improve the system, most of it hinges on a single thing: the MyPlugin_EndpointInterface interface. That’s it.

This interface is the bridge between our endpoint classes and the WordPress REST API. We use it to pass arguments to the register_rest_route function. We saw a few different approaches to doing it, but the purpose of the interface never changed.

That’s because that’s what design is about. We had a specific problem to solve. (How to create classes for WordPress REST API endpoints.) But how we get there varies based on our needs and imagination.

Photo Credit: Arnaud Mesureur

Creative Commons License