WP-CLI: command and conquer!

Presented at WordCamp Kansas City 2016

I use WP-CLI just about every day. It’s become a central part of my daily life as a WordPress programmer. And it’s not just useful for managing core, there’s a growing list of plugins that have WP-CLI commands baked-in, too.

In this article, we won’t be talking about what WP-CLI is or how to use it — we’ve previously covered how to use WP-CLI. Instead, we will look at how to create custom WP-CLI commands inside our plugins. That’s right, it’s time to get our hands dirty and build something awesome!

Loading our command

The developer API for adding custom commands depends on WP-CLI being loaded, so we’ll want to make sure it’s running when we attempt to register our custom command.

Additionally, if we are in CLI mode then there are likely many additional files in the plugin that don’t need to be loaded at all (such as admin screens).

When I build a plugin, I often put all of the code for my custom command inside its own file. Then in my main plugin file, I identify the point where everything that my CLI command requires has been loaded, then require my custom command file only when WP-CLI is running and return early to avoid loading anything else.

Example

WP-CLI Custom Command Example

Let’s break down the example above:

  1. We are always loading the API and Settings classes (presumably our CLI command must need these too).
  2. We are only loading our custom command when the WP_CLI constant is defined, which is a handy way to know we are running WordPress in CLI mode.
  3. We are returning early in the construct, which stops execution and prevents the class-admin.php file from loading. We are in CLI mode after all, so there’s no need to load anything related to the admin area.

Registering our command

Now that we have properly loaded our command, let’s open up class-cli.php and start working with the WP-CLI developer API to register our command.

It’s as easy as creating a class, and using WP_CLI::add_command() to register it.

Example

Register Command Example

Let’s break down the example above:

  1. We are registering a custom command called my-plugin using WP_CLI::add_command on the last line.
  2. The custom command points to the My_Plugin_Command class.
  3. Since greet() is a public method, a sub-command called greet is automatically registered.
  4. Since get_year() is a private method, we can only call it inside our class.

Alright, let’s try it out in the terminal:

WP-CLI Register Command Terminal

We just wrote a custom command. Pretty cool!

Pro tip: It’s important to register a command that is unique to your plugin or project. Think of it as the namespace that all of your sub-commands will live under.

Understanding args

WP-CLI command methods accept two types of arguments: positional and associative.

Let’s go a little deeper with our greet sub-command, and allow the user to control who the greeting is for using positional arguments.

Example

WP-CLI Positional Arg Example

Let’s break down the example above:

  1. Positional arguments are passed in the $args array.
  2. Each argument can be accessed using its numeric index, starting with zero.

Alright, let’s try it out in the terminal:

WP-CLI Positional Arg Terminal

Let’s say we want displaying the year to be optional. After all, most people already know what year it is. We can easily do this using an associative argument.

Example

WP-CLI Associative Arg Example

Let’s break down the example above:

  1. Associative arguments are passed in the $args_assoc array.
  2. Each argument can be accessed via its associative index (e.g. show-year).
  3. If we see show-year in the index, we will display the year after the greeting.

Alright, let’s try it out in the terminal:

WP-CLI Associative Arg Terminal

Great. Everything works. But there is something about our code that is kind of messy. Can you tell? That isset() check just seems like it could be cleaner. Luckily, in WP-CLI there is a way to easily clean things up!

Introducing WP_CLI\Utils\get_flag_value()

Example

WP-CLI Get Flag Example

Let’s break down the example above:

  1. The get_flag_value() method helps us easily grab a value from the associative index.
  2. In the example above, we don’t need the value, we only need to check if it exists.
  3. If the index does not exist, the default value will be null.
  4. We can optionally pass a third parameter to set the default to be anything we want.
  5. This is a clean and handy alternative to doing isset() checks everywhere.

Bonus: By using get_flag_value() we also have automatic support for –no-show-year. This is a feature built-in to WP-CLI that allows a boolean option flag to be negated.

Using PHPDoc

Now the real magic begins. You might think that PHPDoc blocks are nothing more than a developer best-practice. Well in WP-CLI, they aren’t just a nice-to-have description of our methods, they are actually a functional part of our command!

Let me explain…

In our examples above, you may have noticed we did a big “no-no” in PHP. We didn’t use isset() to check if the numeric indexes existed before trying to reference them. We are printing $args[0] and $args[1] without any checks whatsoever. What were we thinking?! We all know what this will lead to: Undefined Index notices. Super. Annoying.

Well, this is where PHPDoc in WP-CLI actually saves the day (and a bunch of lines of code).

Example

WP-CLI PHPDoc Example

Let’s break down the example above:

  1. By using WP-CLI’s magic PHPDoc syntax, we can make sure certain arguments are required.
  2. The first-name and last-name arguments will always be required.
  3. The –show-year associative argument flag will be optional.
  4. If our users are having trouble remembering how to use the command, they can simply use the global –help flag to display this PHPDoc in their terminal!

Alright, let’s try it out in the terminal:

WP-CLI PHPDoc Terminal

Output: Strings

The truth is, the usefulness of our custom commands relies a lot on how we output them. Thankfully, WP-CLI has once again done all of the heavy lifting for us, we just have to properly use the output methods available to us.

The most common type of output in commands are going to be the strings we print to the terminal.

The greeting message in our example above can be displayed a number of different ways using WP-CLI helper methods, each one tailored to a different context.

Example

WP-CLI Output Strings Example

Output: Global options

When considering the output of our command, it’s also important to be familiar with the global options that are built-in to WP-CLI.

As you can see in the previous string examples, some methods behave differently depending on what global options might be in use.

For instance, if a user runs a command using the –quiet global option, all output messages will be suppressed except when using WP_CLI::line(). It stands to reason that this method should be used sparingly as it will always be printed to the terminal. We should use WP_CLI::log() instead.

Imagine the frustration of a user trying to use the –quiet flag only to see dozens of lines appear! Let’s be developers who pay attention to those kinds of UX details in our output. It’s important, even in the terminal.

Formatting arrays

There will be times when we want to display an array of information to our user, and give them options to display it the way they want. Never fear, WP-CLI::format_items() is here!

Let’s say that we have a list of different types of greetings in our plugin, and we want the user to be able to list them all the terminal.

Example

WP-CLI Format Arrays Example

Let’s break down the example above:

  1. Our greetings are stored in an array of arrays called $items.
  2. Each greeting has a type and a text key.
  3. We are using the get_flag_value() method to get the –format option value.
  4. If –format is not specified, we will display a table by default.
  5. We pass our $format, $items, and key names to the WP_CLI::format_items() method.

Alright, let’s try it out in the terminal:

WP-CLI Format Arrays Terminal

Nice!

With just a few lines a code we’ve given our users the ability to not only view an array of data, but easily format it the way they want using a –format option.

The WP_CLI::format_items() method accepts table, json, csv, yaml, ids, and count as valid format types, which we can describe in the PHPDoc for this new greetings sub-command.

Conclusion

As you can see, there are a ton of useful tools in the WP-CLI developer API. This is great news for us developers as we look to level-up our plugins, and provide our advanced users with powerful ways to use them in the terminal.

I would be remiss to not point you to the official WP-CLI developer resources at this point. They contain all of the technical information about helper methods, the arguments available in them, and many great examples.

    • Commands Cookbook:

http://wp-cli.org/docs/commands-cookbook/

    • Internal API:

http://wp-cli.org/docs/internal-api/

In this article we’ve only scratched the surface of what we can do with custom commands. Was it helpful for you? Did you end up writing a custom command for your plugin? Let me know in the comments, or join the discussion in the community.

Frankie Jarrett is a full-stack developer from Missouri who has been building PHP web applications and WordPress products since 2007. He is Senior WordPress Product Engineer at GoDaddy and loves the fact that he gets to work with WordPress every single day. From launching his own theme shop in 2011, to building large-scale enterprise websites, authoring dozens of plugins, and now creating amazing product experiences on GoDaddy's managed hosting platform — WordPress has always been at the center of Frankie's career, and he plans to keep it that way!