REST in Pieces

Working with the WordPress REST API
in an Object-oriented Fashion


A talk by @thorstenfrommen

Who’s That Guy?

Thorsten Frommen

Thorsten Frommen

  • Certified PHP engineer, web development professional, and tester.
  • WordPress engineer and technical project lead at Inpsyde
  • Lead developer of MultilingualPress.
  • Maintainer of WP REST Starter.

WP REST API Logo

Looking Back

The history of the WordPress REST API:

  • idea came up at WordPress Community Summit 2012;
  • plugin (named JSON API) was started in December 2012;
  • initially planned to merge into Core with WordPress 3.6 (August 1, 2013);
  • infrastructure merged into Core with WordPress 4.4 (December 8, 2015);
  • most endpoints merged into Core with WordPress 4.7 (December 6, 2016).

Looking Ahead

A possible future of the WordPress REST API:

  • introduce more sophisticated authentication options;
  • introduce (basic) JavaScript client;
  • enhance wp/v2/users endpoint to support multisite;
  • introduce wp/v2/sites endpoint;
  • introduce wp/v2/networks endpoint.

Excursus: REST API

What is an API?

[An] Application Programming Interface (API) is a set of subroutine definitions, protocols, and tools for building application software. In general terms, it is a set of clearly defined methods of communication between various software components. Wikipedia

What is an API?

An API is the interface through which you access someone elses code or through which
							someone else's code accesses yours. In effect the public methods and properties. Stack Overflow

What is an API for Us?


For us,
an API consists of all means of communication with a specific piece of software.

What is REST?

Representational state transfer (REST) or RESTful Web services are one way of providing interoperability between computer systems on the Internet. REST-compliant Web services allow requesting systems to access and manipulate textual representations of Web resources using a uniform and predefined set of stateless operations. Wikipedia

What is REST?

REST is the underlying architectural principle of the web. The amazing thing about the web
							is the fact that clients (browsers) and servers can interact in complex ways without the
							client knowing anything beforehand about the server and the resources it hosts. The key
							constraint is that the server and client must both agree on the media used, which in the
							case of the web is HTML. An API that adheres to the principles of REST does not require the
							client to know anything about the structure of the API. Rather, the server needs to provide
							whatever information the client needs to... Stack Overflow

What is REST for Us?


For us,
REST is a client–server architecture for providing interoperability regarding the access and manipulation of resources by means of predefined stateless operations.

The WordPress REST API

Consequently, the WordPress REST API provides read/write access to (partial) data of a WordPress website, in a REST-compliant way.

Using
the WordPress REST API

List Posts

Send a GET request to the posts endpoint.


					GET /wp-json/wp/v2/posts HTTP/1.1
					Host: https://2017.berlin.wordcamp.org
				

Retrieve a Post

Send a GET request to the posts endpoint, and supply the post ID.


					GET /wp-json/wp/v2/posts/3 HTTP/1.1
					Host: https://2017.berlin.wordcamp.org
				

Delete a Post

Send a DELETE request to the posts endpoint, and supply the post ID.


					DELETE /wp-json/wp/v2/posts/3 HTTP/1.1
					Host: https://2017.berlin.wordcamp.org
				

Update a Post

Send an UPDATE request to the posts endpoint, and supply both an ID and new data.


					UPDATE /wp-json/wp/v2/posts/3 HTTP/1.1
					Host: https://2017.berlin.wordcamp.org
					Content-Type: application/json; charset=utf-8
					Content-Length: 47

					{ "title": "Welcome to WordCamp Berlin 2017!" }
				

Extending
the WordPress REST API

Writing a WordPress REST API Endpoint in 2 Minutes


					add_action( 'rest_api_init', function () {

						register_rest_route( 'tomjn/v1', 'test', [
							'callback' => function () {

								return 'moomins';
							},
						] );
					} );
				
(Or: “I don’t need no WP REST Starter.”)

The WP_REST_Posts_Controller Class

WP_REST_Posts_Controller (Or: “OK, OK. I guess it doesn’t hurt to have a look at that WP REST Starter thing.”)

Current “Best Practices”

The suggested way to develop using the WordPress REST API:

  • make use of a handful of (global, not pure) functions;
  • maybe develop a custom endpoint on top of a huge abstract controller class.

“Why Is This Bad?”

  • Following these practices results in procedural code.
  • The code is not modular.
  • Some parts of your code are redundant.
  • There is no managing layer (or instances) available.

“Can You Do Better?”

Yes!

WP REST Starter Logo

WP REST Starter

  • Composer package (i.e., no plugin).
  • Provides several interfaces for both data types and business logic.
  • Comes with straightforward implementations for the common needs.
  • Allows for proper object-oriented programming.
  • Provides one level of abstraction.

WP REST Starter is
no Replacement
for Core Infrastructure

No Replacement for Core Infrastructure

  • Internally, uses Core functions such as register_rest_route().
  • You just don’t (have to) use the infrastructure directly anymore.
  • Encapsulates logic within sensible structures.

WP REST Starter is
no Replacement
for Core Endpoints

No Replacement for Core Endpoints

  • Integrates with Core endpoints (e.g., add field to response).
  • Designed to be used for custom endpoints.
  • Follows the way Core endpoints are set up.

Getting Started

Installation

Install with Composer:


					$ composer require inpsyde/wp-rest-starter
				

Requirements

  • Minimum required PHP version:
    • PHP 7
    • PHP 5.4 (when using WP REST Starter v2)
  • Minimum required WordPress version:
    • WordPress 4.7
    • WordPress 4.4 (when defining custom endpoints only)
    • …or the WP REST API plugin

Contents of WP REST Starter

Contents of WP REST Starter

The package contains:

  • 22 interfaces (10 for data types, and 12 for business logic);
  • 14 implementations (3 for data types, and 11 for business logic);
  • 4 factory/helper classes (e.g., WP_REST_Response factory).

Using
WP REST Starter

User Story I

I want to add a custom field to the response of an endpoint.

Without WP REST Starter


					register_rest_field( 'post', 'awesomeness', $args );
				

With WP REST Starter


					$fields = new Field\Collection();

					$field = new Field\Field( 'awesomeness' );

					// TODO: Set up $field (e.g., set reader callback).

					$fields->add( 'post', $field );

					( new Field\Registry() )->register_fields( $fields );
				

Benefits

  • Separation of field definition and field registration.
  • Field definition encapsulated in a dedicated object.
  • Field registry serves as managing instance.

Looking at the Code: Field Collection


					interface Collection extends \IteratorAggregate {

						public function add(
							string $resource,
							Field $field
						): Collection;

						public function delete(
							string $resource,
							string $field_name
						): Collection;
					}
				

Looking at the Code: Field


					interface Field {

						public function definition(): array;

						public function name(): string;
					}
				

Looking at the Code: Readable Field


					interface ReadableField extends Field {

						public function set_get_callback(
							Reader $reader = null
						): ReadableField;
					}
				

Looking at the Code: Reader


					interface Reader {

						public function get_value(
							array $object,
							string $field_name,
							\WP_REST_Request $request,
							string $object_type = ''
						);
					}
				

Looking at the Code: Field Registry


					interface Registry {

						const ACTION_REGISTER = 'wp_rest_starter.register_fields';

						public function register_fields( Collection $fields );
					}
				

Looking at the Code: Field Registry Implementation


					final class Registry implements Common\Field\Registry {

						public function register_fields( Common\Field\Collection $fields ) {

							do_action( Common\Field\Registry::ACTION_REGISTER, /* args */ );

							foreach ( $fields as $resource => $resource_fields ) {
								foreach ( $resource_fields as $field_name => $field ) {
									register_rest_field(
										$resource,
										$field_name,
										$field->definition()
									);
								}
							}
						}
					}
				

User Story II

I want to register multiple custom routes (or even endpoints).

Without WP REST Starter


					register_rest_route( 'awesomeness/v1', 'foos', $args );

					register_rest_route( 'awesomeness/v1', 'foos/(?P<type>\d+)', $args );

					// ...

					register_rest_route( 'awesomeness/v1', 'bars', $args );
				

Issue: redundant (hard-coded) data.

With WP REST Starter


					$routes = new Route\Collection();

					// ...

					$routes->add( new Route\Route( 'foos', $options ) );

					$routes->add( new Route\Route( 'foos/(?P<type>\d+)', $options ) );

					// ...

					$routes->add( new Route\Route( 'bars', $options ) );

					// ...

					( new Route\Registry( 'awesomeness/v1' ) )->register_routes( $routes );
				

Benefits

  • Separation of route definition and route registration.
  • Route definition encapsulated in a dedicated object.
  • Registry serves as managing instance.
  • Registry abstracts namespace (i.e., common information).

Looking at the Code: Route Collection


					interface Collection extends \IteratorAggregate {

						public function add( Route $route ): Collection;

						public function delete( int $index ): Collection;
					}
				

Looking at the Code: Route


					interface Route {

						public function options(): array;

						public function url(): string;
					}
				

Looking at the Code: Route Registry


					interface Registry {

						const ACTION_REGISTER = 'wp_rest_starter.register_routes';

						public function register_routes( Collection $routes );
					}
				

Looking at the Code: Route Registry Implementation


					final class Registry implements Common\Route\Registry {

						// Constructor, taking the namespace.

						public function register_routes( Common\Route\Collection $routes ) {

							do_action( Common\Route\Registry::ACTION_REGISTER, /* args */ );

							foreach ( $routes as $route ) {
								register_rest_route(
									$this->namespace,
									$route->url(),
									$route->options()
								);
							}
						}
					}
				

Optional: Route Options as Dedicated Object


						$route = new Route\Route( $url, $options );
					
  • Route options can be much more complex than field definitions.
  • Define route options via a dedicated class, ~\Core\Route\Options.
  • Options can be composed of further objects, for example:
    • a request handler implementation;
    • a schema implementation.

						$options = Route\Options::from_arguments( $handler, $args, 'POST' );
						$options->set_schema( $schema );

						$route = new Route\Route( $url, $options );
					

User Story III

I want to use PSR-7 middleware in my RESTful WordPress project.

Excursus: PSR

PHP Standard Recommendations

  • PSR stands for PHP Standard Recommendations.
  • Maintained by the PHP Framework Interop Group.
  • Well-known PSRs:
    • PSR-2: Coding Style Guide;
    • PSR-4: Autoloading Standard;
    • PSR-7: HTTP Message Interfaces.

HTTP Message Interfaces

  • PSR-7 concerns itself with HTTP messages such as requests and responses.
  • Package contains interfaces for HTTP messages and related structures (e.g., URI).
  • There are quite a few PSR-7 implementations.
  • There is lots of middleware designed to work with implementors.

Implementing PSR-7 in WordPress


“Can’t be that hard, can it?”


Well, … unfortunately, it is.

PSR-7 Request and Response


					interface MessageInterface { /* ... */ }

					interface RequestInterface extends MessageInterface { /* ... */ }
					interface ServerRequestInterface extends RequestInterface { /* ... */ }

					interface ResponseInterface extends MessageInterface { /* ... */ }
				

WordPress (REST) Request and Response


					class WP_HTTP_Response { /* ... */ }

					class WP_REST_Request implements ArrayAccess { /* ... */ }

					class WP_REST_Response extends WP_HTTP_Response { /* ... */ }
				

¯\_(ツ)_/¯

WordPress get_headers() Method


					class WP_REST_Request implements ArrayAccess {

						/** @return string[][] */
						public function get_headers() { return [ 'k' => [ 'v1', 'v2' ] ]; }
					}
				

						class WP_REST_Response extends WP_HTTP_Response {

							/** @return string[] */
							public function get_headers() { return [ 'k' => 'v1, v2' ]; }
						}
					

(╯°□°)╯︵ ┻━┻

Goal


					class Request extends \WP_REST_Request
						implements Psr\Http\Message\ServerRequestInterface {

						// ...
					}

					class Response extends \WP_REST_Response
						implements Psr\Http\Message\ResponseInterface {

						// ...
					}
				

Status: work in progress… done! \o/

Creating a PSR-7-compliant REST Request

  • Instantiate a new object yourself. Unlikely, though.
  • More likely, make an existing request PSR-7-compliant:

						use Inpsyde\WPRESTStarter\Core\Request\Request;

						// ...

						$request = Request::from_wp_rest_request( $request );
					

Creating a PSR-7-compliant REST Response

  • Instantiate a new object yourself.
  • Make an existing response PSR-7-compliant:

						use Inpsyde\WPRESTStarter\Core\Response\Response;

						// ...

						$response = Response::from_wp_rest_response( $response );
					

Using the New Structures

Well, this is no big deal:

  • make the WordPress HTTP message object PSR-7-compliant;
  • then, pass it on to your desired PSR-7 middleware.

Take-aways

  • WP REST Starter…
    • …brings proper OOP to your RESTful WordPress projects.
    • …does not replace, but adds to the WordPress REST API.
    • …enables you to make use of (existing) PSR-7 middleware.
  • Please, use it, and maybe even contribute to it.
  • Also, Inpsyde is hiring! :)

References and Further Reading

Thanks!


@thorstenfrommen

slides.tfrommen.de/wp-rest-starter-2