Anatomy of a WP-CLI Command: wp database import

Server cables - How does wp db import work?

To some, WP-CLI might seem like a nice but optional convenience for a WordPress developer who’s comfortable with the command line. For developers who’ve dealt with time-out issues in PHP processes, or who administer lots of websites, WP-CLI is more than that: it’s a time-saver and power-tool.

Today we’re going to look hard at a single WP-CLI command to find out how it works. In doing so we’ll learn a lot more about how WP-CLI commands work, and what we’d need to do to make our own.

This article assumes a fair amount of comfort and knowledge with the command line generally and WP-CLI in particular. We’re not really going to learn about how to pilot it, but how it works internally. Whatever your familiarity with WP-CLI, stay tuned through the next section—we’ve got lots of other free content that can help you.

Helpful Reading Before Learning About Writing WP-CLI Commands

Learning the basics of WP-CLI itself is the place to start. This article will work from that basis.

If you’d like a refresher on what a “CLI” is, maybe start with this:

What is the Command Line? CLIs from First Principles

If you’ve got that part under control, but need an introduction to WP-CLI, start here:

The What, How and Why of WP-CLI: WordPress in Your Terminal

We also have a smattering of other articles, Quick Guides, and more that have videos which can help you see some of this stuff in action. Here are a few:

What is a WP-CLI Command?

Unlike much of WordPress core and the code WordPress developers write, WP-CLI makes a lot of things happen with PHP objects. (If you’d like an introduction to object-oriented programming in PHP, read these two articles: Core Concepts of OOP and Diving Deeper into Object Orientation.)

A WP-CLI command is a single action or set of actions that are registered with WP-CLI via a hook. That’s the same as most of WordPress. However, the difference is that in addition to registering a single command with a single call to that hook, with WP-CLI you can hook an entire PHP object onto your call. In that case, all of the public methods on your object become executable CLI commands (or subcommands, if you prefer).

This is a really cool and powerful choice, but it isn’t something that every WordPress developer will likely expect to happen by default. The first time I heard this, I actually subtly doubted that I was understanding correctly. WordPress doesn’t contain much such wizardry.

The Command We’ll Study and Why: wp db import

The way that WP-CLI (the core project) is actually organized today is in a series of distinct repositories for many of the commands it can act on out of the box. So the plugin components are each completely distinct from the core, and on release the process uses Composer to pull in all the commands that should be in the core set before the final packaging. This is another very cool example of modularity and forward thinking, but it may trip some people up.

Some of the commands, plugin and theme for example, are in the wp-cli/wp-cli repo, but most others are outside of it. And I think the ones outside are useful for someone thinking of writing their own commands. After all, if you were able to add your commands to WP-CLI core, you’d already know how to make an extension.

In any case, the command we’re going to look at more closely and understand a little better is the database importer. As we mentioned in the article where we discussed WP-CLI, one of WP-CLI’s really cool powers is to import and export databases without needing to resort to phpMyAdmin or similar tools. This command is just the import side of that process. But first, a little more about the overall project in which it lives.

How Commands for WP-CLI Are Registered and Run

A quick note: a few different times in this article, I’m going to refer to line numbers in the db-command software package that makes the wp db functions available in WP-CLI. When you read this, it’s quite possible these line numbers will have shifted. But I’m going to link to each line number I refer to on Github, and those links are anchored to the commit that was current as I wrote it. So all references should be followable that way, though the code may change in the future.

That said, the way that a command (object) is registered with WP-CLI should be pretty comfortable for any long-time WordPress developer, because it has so much in common with the way that most other WordPress code is registered with the core application runtime.

After a small bit of boilerplate in db-command.php, the core line (12) that makes this set of commands, including our specific command wp db import, runnable in WP-CLI is:

WP_CLI::add_command( 'db', 'DB_Command' );

What does that do? It calls the static method add_command on the core WP_CLI class. The static method is a new wrinkle, but otherwise this pretty similar to the syntax of familiar functions like add_action() or register_widget(). The latter is a slightly better match as with both add_command and register_widget, you’re passing a class name rather than a function closure or function name.

However, for WP-CLI, you specify first the way your command (or set of commands) will be invoked from the command line. In our example here, that’s the db argument. As we mentioned, all the public methods of the DB_Command class will be accessible there. It is possible to register something like db coolmethod specifically, but in this case we’re seeing the full magic of how about a dozen commands are registered with WP-CLI in a single line.

How WP-CLI Commands Are Documented

One of the cool and kind-of-magic things about WP-CLI is that all the commands are documented on the command line. What’s more, this is actually a result of PHP comments that exist more-or-less inline with the command itself. This has lots of benefits.

So before we look at the code of the wp db import command, let’s look at its documentation block. The thing to realize is that what you’re looking (at lines 340-361) is what you get when you’re in the wp db import command’s help interface.

/**
 * Import a database from a file or from STDIN.
 *
 * Runs SQL queries using `DB_HOST`, `DB_NAME`, `DB_USER` and
 * `DB_PASSWORD` database credentials specified in wp-config.php. This
 * does not create database by itself and only performs whatever tasks are
 * defined in the SQL.
 *
 * ## OPTIONS
 *
 * [<file>]
 * : The name of the SQL file to import. If '-', then reads from STDIN. If omitted, it will look for '{dbname}.sql'.
 *
 * [--skip-optimization]
 * : When using an SQL file, do not include speed optimization such as disabling auto-commit and key checks.
 *
 * ## EXAMPLES
 *
 *     # Import MySQL from a file.
 *     $ wp db import wordpress_dbase.sql
 *     Success: Imported from 'wordpress_dbase.sql'.
 */

So the first paragraph, “Import a database…” is the short summary. It’s what you see when you hit wp db --help. It’s also the first line to wp db import --help. The rest of it is also visible when you do that latter command. The second paragraph is the more exhaustive description. The ## OPTIONS section tells you all the possible arguments, and the ## EXAMPLES section also just falls through to you viewing the command’s documentation on the command line.

This documentation is not completely required for a WP-CLI command to run properly, but it’s really a good habit to get in to. And if you write it first, it makes you think a little harder about how your user is going to make use of your work. That’s almost always a good thing.

How wp db import Works

The actual algorithm and set of functions that this specific command uses is beside the point if you’re reading this article to learn how WP-CLI commands are structured. But there are a few interesting things we can pick up from it.

Here it is, slightly abridged:

public function import( $args, $assoc_args ) {
    if ( ! empty( $args[0] ) ) {
        $result_file = $args[0];
    } else {
        $result_file = sprintf( '%s.sql', DB_NAME );
    }

    $mysql_args = array(
        'database' => DB_NAME,
    );

    if ( '-' !== $result_file ) {
        if ( ! is_readable( $result_file ) ) {
            WP_CLI::error( sprintf( 'Import file missing or not readable: %s', $result_file ) );
        }

        $query = 'SOURCE %s;';
        $mysql_args['execute'] = sprintf( $query, $result_file );
    }

    self::run( '/usr/bin/env mysql --no-defaults --no-auto-rehash', $mysql_args );

    WP_CLI::success( sprintf( "Imported from '%s'.", $result_file ) );
}

First, as I mentioned above, the fact that this method is declared as public function import(...) on 362 is crucial. That public is the interesting and important part. If you register a class of CLI commands, as this extension does, all public methods are treated as runnable CLI commands. So if you want to have other non-runnable methods for your class (for subroutines, etc) you need to make sure you make them private or protected.

After that, the command makes a guess about what file it should import from if that file’s not supplied (365). Here, the use of the first method argument, $args, is of some note. In that is the ordered list of passed options from the ## OPTIONS section of the documentation. The second method argument is the same, but with names put as keys rather than as mere positions.

Line 375 is notable for its use of the WP_CLI::error() method. This is a nice little wrapper (one of a few) to let you give a user notification that something happened. But in this case, it’s also an indication that something went wrong (or, for the success method, right) for the command they ran. What’s extra nice about these is that they also make the script emit the correct exit code for other CLI commands to understand. echo does work when writing WP-CLI commands, but these methods should be preferred.

Finally, line 385 merits some explanation:

self::run( '/usr/bin/env mysql --no-defaults --no-auto-rehash', $mysql_args );

self::run is a convenient way that this class has available to call out to the underlying shell and run a different CLI command from inside WP-CLI. Again, a PHP function like shell_exec can work, but there are advantages to the way it’s done here. Inside self::run you’ll see that it’s running a basic BASH-style command called mysql for actually making database requests. This is where you see the value in understanding the whole CLI environment, and not just the WordPress parts of WP-CLI. (Because I didn’t know: the use of the /urs/bin/env before the mysql of the command invocation is for standardizing behavior that might otherwise clash with people’s BASH aliases and the like. More explanation is here.)

What We’ve Learned about WP-CLI commands

I hope that you now have a sense of how a WP-CLI commands works internally, and that by understanding a little better how wp db import works—where its documentation lives, how WP-CLI knows about it, how the actual command runs—you’ve got more confidence that you can make your way through reading existing WP-CLI commands, and eventually even creating your own. Good luck!

Image credit: Vance AFB


2 Responses

Pingbacks

Add a comment

(required)

(required)

(optional)