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