Earlier today, I gave a talk at A Day of REST about unlocking the potential of the WP REST API at the command line — by creating a more RESTful WP-CLI. Check out the project on Github, and stay tuned for the v0.1.0 release. Read on for my (loosely edited) annotated slides from the presentation.
A more RESTful WP-CLI is about unlocking the potential of the WP REST API at the command line. But what does this actually mean? Let me begin with a story…
Migrations used to really suck.
Back in the olden days, there was a web interface for mapping users before import. Imagine having dozens of files to import, each with hundreds of users to map. This nightmare is how I started out.
At the beginning of my career I helped run CoPress, a hosting company which helped move student newspaper websites to WordPress. Intelligently, we had a Python conversion script to transform College Publisher archives to WordPress WXR. Not very intelligently, we uploaded those WXR files through the web. CM Life was the largest import I had to work on.
"Hey Brian, about a third of your archives have been imported into your site. To finish this process, however, we’ll need to take the site down for up to a couple of hours, or turn off comments and have you not publish anything at least. The technical explanation is that the import script is being shut down by Apache halfway through the import process because it’s taking a longer time to check the incoming content against the database."
Fast-forward a few years later. When I joined Automattic’s WordPress.com VIP team, they had a directory full of really useful bin scripts with really inconsistent usage instructions. One of those bin scripts was Thorsten Ott’s CLI wrapper for the WordPress importer.
Thorsten also introduced me to WP-CLI. Driven by the desire to have a consistent interface for the bin scripts, I contributed his CLI wrapper upstream to WP-CLI. And thus, what used to take hours through the web, suddenly took minutes or seconds at the command line.
But migrations still kinda suck.
The ideal WXR import requires a fresh, empty site and script execution without errors. If you import into an existing site, or your script fatals, your data can be broken without you realizing. Furthermore, WXR doesn’t handle custom database tables or site configuration.
Let’s leave this problem of migrations for a bit and get back to a more RESTful WP-CLI.
WP-CLI is a command line interface for anything you might do in the WordPress admin (create, edit, and delete posts, comments and users). It also provides useful WordPress-related commands, for instance:
- wp site empty empties your site of posts, comments, and terms while leaving options and users. Useful if you want to reset the content in your database, without resetting site configuration.
- wp search-replace is a serialization-friendly search-replace.
WP-API also provides commands (endpoints) for managing WordPress. Given these similarities, which I see as an opportunity, last fall I ran a Kickstarter for “A more RESTful WP-CLI.”
A more RESTful WP-CLI is a WP-CLI that’s backed by the WP REST API. In this model, WP-CLI is both a client and an application. As a client, the functionality is in the REST API endpoints, and WP-CLI exists as a user-friendly wrapper over the API. As an application, the functionality is in WP-CLI, built on top of the WP REST API.
More specifically, the RESTful WP-CLI will comprise: endpoint discovery, command registration, authentication, and meta operations. It’s important to note I’m only 18% of the way through the project, though. Some points are concrete, but a lot are still hypothetical.
Let’s look at some code!
Conveniently for us, WP-API describes itself in the index with context=help. This is sufficient description to create WP-CLI commands based on what the site supports. But how do we register WP-CLI commands?
Historically, a WP-CLI command is a class method passed $args and $assoc args. $args represents positional arguments. $assoc_args represent associative arguments. With those supplied values, the method then performs some procedural logic.
Metadata about the command is included in a special PHPDoc format. Short description is at the top. The synopsis defines accepted parameters, which can be positional or associative. Both types can be optional. Lastly, a description can be included for each argument.
WP-CLI then parses the PHPDoc to turn it into synopsis it can understand.
Last year, I set about building a prototype of commands based on the WP REST API. If defining a class is the only way to register a WP-CLI command, then creating dynamic WP-CLI commands required defining a new class at runtime. Super ugly.
It would be much nicer to be able to register an arbitrary callback as a command. This callback can be a closure, function, or method of an instantiated class. Include your description and synopsis as secondary arguments. And this feature is coming in v0.23.0!
With our new functionality in place, we can much more easily register commands dynamically. Much nicer than the eval hack! RESTCommand class supports list_items, create_item, etc.
Boom. Totally dynamically generated from the API for wordpress-develop.dev
It even works for remote sites too! That’s the whole point. Include –http= to access rest endpoints from an arbitrary remote site.
Here’s how you can list tags locally.
I can create tags locally too, through the REST API. But, there’s new behavior: requires authentication and authorization locally. WP-CLI currently offers unfettered access to the database. In the future, we’ll likely have a sudo mode.
Documentation populates from the schema – dynamically! This will become more detailed over time.
Because WP-CLI knows how to interpret the index, parameters are validated before the request is made – dynamically!
But, remote authentication hasn’t been worked out yet.
The goal for authentication is to make it as easy as possible. Authenticate once, and reuse (cache) that authentication until it expires. Switch between multiple authorizations.
For the project, there are a couple of options I’ll be exploring: http and ssh.
http auth would include basic, oAuth 1 and 2, and API key – whatever the remote site supports. http is advantageous because it’s more accessible, but runs the limitation of web server requests.
We could also proxy over SSH, when it’s available. SSH auth -> discovery of the remote wp install -> discovery of endpoints -> perform a command.
Two problems: don’t want to type long commands, each site will support different endpoints.
To address these problems, I’m considering ways to introduce aliases, as internal shortcuts for additional global params. Ideally, WP-CLI should only expose commands supported on a specified WordPress instance. Aliases provide UX for this feature, but backwards compatibility is a concern.
All of the work up to this point is foundational, though. WP-CLI as an application means meta operations, or commands to manipulate core resources. For instance, wp diff could compare two JSON blobs. The diffing mechanism is easy, and we can now know they’re equivalent blobs because WP-API defines canonical routes.
Why do all of this? Short of a mind to WordPress connection, WP-CLI’s goal is to be the fastest interface for WordPress. Anything you might want to do to WordPress, WP-CLI should always be, quantatively, the fastest way to perform the task. Furthermore, creating a common interface for disparate features means you can develop mental muscle memory for getting faster with WP-CLI over time.