It’s no secret that I am a fan of the WordPress REST API. I use it all the time on new projects, but adding REST API support to my main project — Caldera Forms — has been difficult. Modern WordPress development requires writing PHP code that allows for the same actions to be performed in response to a traditional admin/ front-end HTTP request, a REST API request or WP-CLI request, using the same internal PHP API.
In the past, I don’t think following this philosophy mattered much. Settings were changed on admin pages and content was displayed on the front-end. This led to lots of people writing very successful plugins that were strongly tied to incoming HTTP request and the global WP_Query object assuming that request was coming from a browser clicking things in WordPress.
Having a single PHP API for all CRUD operations, at a minimum has become the new standard. It leads to better, more manageable code, and allows REST APIs, WP-CLI, and whatever comes next — maybe GraphQL — to easily perform the same function, via different interfaces.
For example, WooCommerce recently added CRUD classes to the core plugin. Having everything running through this one API will allow them to modify their database structure, and make their code more testable. But, and this is a huge but, there are a ton of WooCommerce add-ons out there and even more site-specific code that aren’t using those CRUD classes. This is going to present a massive backward compatibility challenge, but it is worth it for the future of their platform.
I’d like to share some of the challenges I’ve dealt with adding a REST API, and standardizing the underlying PHP APIs in my plugin Caldera Forms. Hopefully, this will make the case to you to think about these things when you are working on a new plugin and help you, if you’re in a similar situation.
Before You Get To REST API
The Caldera Forms REST API is something that was actually added to the plugin in late 2016, but I had been laying the groundwork for a long time. The first thing I did was introduce a PHP API for form settings.
Since form configurations are stored in the options table, a call to get_option() is technically sufficient to get a form configuration. Of course, that wasn’t obvious to other developers. Adding a PHP class called Caldera_Forms_Forms with methods like get_form(), get_forms(), create_form(), save_form() makes that more visible. Inside of those methods are the standard filters, and configuration caching that should always be used.
With that API in place, I was able to standardize loading of forms and was prepared to create a REST API endpoint for form configurations. Also, having all of calls for form configurations running through a single class will make it very simple to change the way forms are stored if we do decide to change the storage location of forms or introduce a form object class.
In addition to form configuration, the Caldera Forms REST API also needed to interact with form entries. This required a similar process. Instead of using WPDB to read and write entry data, as needed to custom tables, I wrote an entry CRUD abstraction that could then be wrapped around a REST API.
Structuring Your REST API Code
I don’t want to go into too many details about how I structured the Caldera Forms REST API. I can’t remember if the example code for the article I wrote for Torque on using an object-oriented approach to creating REST APIs is based on the structure for Caldera Forms’ REST API, or if it is the other way around.
I used as much class inheritance as possible, this makes it very easy to add new endpoints, which I have done for add-ons and when I realized I needed different data for our front-end entry viewer. The other important thing I did was created a class for loading REST API endpoints.
All of our endpoint classes implement the Caldera_Forms_API_Route interface. This interface has one method, add_routes() which takes one argument, $namespace. That method does exactly what it says it does — add the routes defined by that class. It also means that the API namespace is defined once, and passed into each route.
The class that loads these endpoints uses method dependency injection — passing dependencies in this case the route classes into the class through a public method, instead of constructor dependency injection — passing dependencies into the object when it is instantiated. This makes it easier to add routes from add-on plugins.
Because of this, I was able to instantiate the class that loads the API, expose the object on action for add-ons to use, and then actually add the routes. You can see the code here.
We actually shipped the infrastructure for the REST API in version 1.4.4 of Caldera Forms, but didn’t add any endpoints until version 1.5. This allowed for add-ons to add endpoints sooner, which was very useful and helped me test the system.
Making your REST API for your plugin as extensible as WordPress is supposed to be is essential for any plugin that has add-ons or might have add-ons or just wants to be easy for other developers to work with.
Simple Clean Callbacks
I think that one way to measure success in PHP coding for WordPress is a clean and simple REST API callback that uses the plugin’s standard systems. For example, here is the callback for the Caldera Forms REST API endpoint for getting an entry:
<?php //this is excerpted from Caldera_Forms_API_Entries class //https://github.com/CalderaWP/Caldera-Forms/blob/master/classes/api/entries.php /** * Get an entry * * GET /cf-api/v2/entries/form-id/entry-id * * @since 1.5.0 * * @param WP_REST_Request $request * * @return Caldera_Forms_API_Error|Caldera_Forms_API_Response */ public function get_item( WP_REST_Request $request ) { $form_id = $request[ 'form_id' ]; $form = Caldera_Forms_Forms::get_form( $form_id ); if( ! is_array( $form ) ){ return Caldera_Forms_API_Response_Factory::error_form_not_found(); } $entry = new Caldera_Forms_Entry( $form, $request[ 'entry_id' ] ); if( null == $entry->get_entry() ){ return Caldera_Forms_API_Response_Factory::error_entry_not_found(); } $data = $this->add_entry_to_response( $entry, array() ); $data = $data[ $request[ 'entry_id' ] ]; return Caldera_Forms_API_Response_Factory::entry_data( $data, 1, 1 ); } This function loads the form the entry using the standard PHP API for forms. If the form isn't found, the factory for responses is used to return an error. If the form does exist, the form configuration is passed to the standard object for form entries along with the entry ID from the request.
If that entry is not found, the response factory is used to return an error. If the entry is found, another method of the class, which is reused in other callbacks, is used to prepare the entry for the response. Then the response factory is used to provide a standard response object using a class that extends WP_REST_Response.
All of the internal logic of interacting with the database is happening in other classes. This REST API controller class is simply for responding to API requests with the right data or appropriate error, mission accomplished.
REST API-First Is About More Than REST APIs
When starting a new WordPress plugin or app, thinking about how everything is going to be consumed by the REST API is very important. And this isn’t just because your plugin and app needs a REST API. It’s because the approach I and many people have been advocating for leads to clean, maintainable code.
If a code base can be consumed equally well by two interfaces — for example the WordPress admin and a REST API — adding WP-CLI support, or other data interchange technology that might not even exist yet, should be easy.
For legacy projects, this can be a challenge, but it’s a challenge I enjoy. More importantly, it will help keep your plugin or app relevant and improve your code.
No Comments