Automating Local WordPress Site Setup with Scripts Part 2: Creating a WP-CLI Package

db-automatelocalwp-part2

In my previous article in this series, I discussed some simple ways to make working with local WordPress development environments a bit easier by using shell scripts to automate the more repetitive tasks, such as installing and deleting WordPress sites.

Soon after publishing that post, Daniel Bachuber mentioned that the scripts could have been written up as a WP-CLI command, which led me to realize that there are quite a few good reasons to do so –

  • I’m more comfortable in PHP than I am in Shell
  • The Shell scripts were mostly firing off WP-CLI commands anyway
  • WP-CLI can handle a lot of the leg work regarding parameter validation and documentation
  • WP-CLI commands can be easily shared via the Package Index

In this article, we’ll look at automating things a bit further and converting the scripts to WP-CLI commands. We’ll also look at how we can use the WP-CLI Package Index to make things easier and share our workflows with other developers.

Creating a WP-CLI Command

Let’s get started by creating our very own WP-CLI command. We’ll be able to use this command to quickly install or delete WordPress sites instead of using the old Shell scripts.

To do this, we just need to create a new PHP file with the following contents:

<?php

if ( defined( 'WP_CLI' ) && WP_CLI ) {
    WP_CLI::add_command( 'installer', ‘WP_CLI_Installer’, array( ‘when’ => ‘before_wp_load’ );
}

/**
 * Quickly manage WordPress installations.
 *
 * Usages:
 *
 * wp installer install
 * wp installer uninstall
 *
 */
class WP_CLI_Installer {

    /**
     * Install WordPress Core
     *
     * ## OPTIONS
     *
     * <dest>
     * : The destination for the new WordPress install.
     *
     * [--base_path=<path>]
     * : Base path to install all sites in
     *
     * [--base_url=<url>]
     * : Base URL that sites will be subdirectories of
     *
     * [--multisite]
     * : Convert the install to a Multisite installation
     *
     * [--dbuser=<user>]
     * : Database username
     *
     * [--dbpass=<pass>]
     * : Database password
     *
     * [--dbhost=<host>]
     * : Database host
     *
     * [--admin_user]
     * : Admin username
     *
     * [--admin_password]
     * : Admin password
     *
     * [--admin_email]
     * : Admin email
     */
    public function install( $args, $assoc_args ) {

    }

    /**
     * Uninstall the given WordPress install.
     *
     * ## OPTIONS
     *
     * <dest>
     * : The site that should be uninstalled.
     *
     * [--base_path=<path>]
     * : Base path that all sites are installed in
     */
    public function uninstall( $args, $assoc_args ) {

    }

}

The code above does a few things. First, it checks if WP-CLI is available and adds the installer CLI command if it is. The installer command has two subcommands for now, install and uninstall.

The docblock for each function serves a dual purpose – not only does it tell us at a glance what each function does, but it also exposes information to WP-CLI. If you were to load the file as a plugin and run wp installer at this point, you would see usage information about the wp installer command:

wp-installer

Now let’s start adding some functionality to the subcommands. First we’ll implement the wp installer install subcommand, mainly recreating the functionality of the old install-wp.sh script:

public function install( $args, $assoc_args ) {
    $base_path = isset( $assoc_args['base_path'] ) ? $assoc_args['base_path'] : getcwd();
    $site_path = $base_path . '/' . $args[0];
    $dbuser    = $assoc_args['dbuser'];
    $dbpass    = $assoc_args['dbpass'];
    $dbhost    = $assoc_args['dbhost'];

    // Download WordPress
    $download = "wp core download --path=%s";
    WP_CLI::log( 'Downloading WordPress...' );
    WP_CLI::launch( \WP_CLI\Utils\esc_cmd( $download, $site_path ) );

    // Create the wp-config file
    $config = "wp --path=%s core config --dbname=%s --dbuser=%s --dbpass=%s --dbhost=%s";
    WP_CLI::log( 'Creating wp-config.php...' );
    WP_CLI::launch( \WP_CLI\Utils\esc_cmd( $config, $site_path, $args[0], $dbuser, $dbpass, $dbhost ) );

    // Create the database
    $db_create = "wp --path=%s db create";
    WP_CLI::log( 'Creating the database...' );
    WP_CLI::launch( \WP_CLI\Utils\esc_cmd( $db_create, $site_path ) );

    // Install WordPress core.
    $admin_user  = $assoc_args['admin_user'];
    $admin_pass  = $assoc_args['admin_password'];
    $admin_email = $assoc_args['admin_email'];
    $subcommand  = 'install';
    $base_url    = $assoc_args['base_url'];

    if ( isset( $assoc_args['multisite'] ) ) {
        $subcommand = 'multisite-install';
    }

    $core_install = "wp --path=%s core %s --url=%s --title=%s --admin_user=%s --admin_password=%s --admin_email=%s";
    WP_CLI::log( 'Installing WordPress...' );
    WP_CLI::launch( \WP_CLI\Utils\esc_cmd( $core_install, $site_path, $subcommand, $base_url . $args[0], $args[0], $admin_user, $admin_pass, $admin_email ) );

    WP_CLI::success( "WordPress installed at $site_path" );
}

The WP_CLI::launch() command is used here to launch several existing WP-CLI commands that are responsible for downloading WordPress, creating the wp-config.php file, and creating/installing the database.

We use WP_CLI::launch() here instead of WP_CLI::run_command() because launching the commands as new processes allows us to specify an external path to run the commands. In this case, that path will be the base path that all future sites will be installed under. One popular option is to have a ~/Sites folder that will contain all of the sites that you’ll be working on.

By default, the base path will be set to the current working directory in the terminal, but this can be easily overridden by providing the --base_path argument to the command.

Creating the uninstall subcommand is even easier:

public function uninstall( $args, $assoc_args ) {
    $base_path = isset( $assoc_args['base_path'] ) ? $assoc_args['base_path'] : getcwd();
    $site_path = $base_path . '/' . $args[0];

    // Let's make sure we really want to do this
    if ( ! isset( $assoc_args['yes'] ) ) {
        WP_CLI::confirm( 'Are you sure you want to proceed? Data WILL be lost!', $assoc_args );
    }

    // Drop the database
    $db_drop = "wp --path=%s db drop --yes";
    WP_CLI::log( 'Dropping database...' );
    WP_CLI::launch( \WP_CLI\Utils\esc_cmd( $db_drop, $site_path ) );

    // Remove the files
    $remove_files = "rm -rf %s";
    WP_CLI::log( 'Removing files...' );
    WP_CLI::launch( \WP_CLI\Utils\esc_cmd( $remove_files, $site_path ) );

    WP_CLI::success( "Uninstalled WordPress from $site_path" );
}

To prevent accidental data loss, the WP_CLI::confirm() function is used here to prompt the user to make sure they want to proceed. To support integration with other scripts, this confirmation can be skipped by passing --yes as an arguement to the subcommand. After that, the database is dropped using the wp db drop command, and the files are deleted.

Utilizing the WP-CLI Config File

You may have noticed that while running the above subcommands, I didn’t pass in any of the arguments such as the base path or database connection information. While it’s definitely possible to pass these arguments to the command manually every time, another possibility is to store commonly used information in the WP-CLI config file.

The WP-CLI config file is usually located at ~/.wp-cli/config.yml, but this can vary from system to system. To be sure, you can run wp cli info and double check the location of the WP-CLI global config.

Once you’ve found that (or created a new file if necessary), you can edit the file to pass default arguments to each subcommand like so:

# WP Installer default args
installer install:
  base_path: ‘/Users/Example/Sites‘
  base_url: http://localhost/
  dbuser: root
  dbpass: root
  dbhost: 127.0.0.1
  admin_user: test
  admin_password: test
  admin_email: test@example.com
installer uninstall:
  base_path: '/Users/Example/Sites'

Now when you run wp installer install <site name>, any of the default values stored in the WP-CLI config file will be automatically applied as associative arguments, and WordPress should be installed in the desired location.

The WP-CLI Package Index

If you haven’t already, you should definitely take a few minutes to check out the WP-CLI Package Index, a growing collection of 3rd party WP-CLI commands that can be installed in seconds. Any package can be installed by simply running wp package install from your terminal.

Luckily, it’s just as easy to submit our own commands to the index, so anyone else can download and use the wp installer command without installing it as a plugin and running the command from an active WordPress installation.

To submit a package to the index, you should create a repository for your command and push the code up to GitHub. Once you’ve done that, you can fork the WP-CLI Package Index GitHub repository and add your repository to the repositories.txt file. Finally, submit a pull request back to the main repo with your changes:

pr-open

Once that gets merged, you can now run wp package install followed by the name of your package to install it. In this case, running wp package install matt/wp-installer will install the wp installer command:

wp-package-install

Now that it has been installed as a package, the wp installer command can be run from any location in the terminal to quickly spin up or delete a site.

What’s Next

While the WP installer command is still somewhat limited in functionality, it’s incredibly easy to extend it further by adding more WP-CLI subcommands. Another logical step would be to add functional tests and some better error handling to make the code more future proof, and I’m sure I’ll be refactoring as I use the commands more.

Have you started converting your old shell scripts to WP-CLI commands? Do you have any suggestions to improve these commands? Let me know in the comments.

About the Author

Matt Shaw

Matt is a WordPress plugin developer located near Philadelphia, PA. He loves to create awesome new tools with PHP, Javascript, and whatever else he happens to get his hands on.

  • Using the wp-cli config file for the default arguments is a great idea; I didn’t know you could do that!

    Using a command like this is a huge time-saver. Laravel Valet users might be interested in my wp-cli ‘valet’ command package which is similar to this installer command but uses Valet to create a site that’s completely ready to use in the browser, including https! It also supports optionally using sqlite instead of mysql, as well as creating installs for composer-based WordPress projects supported by Valet.

  • Nick Breen

    I wrote an extension that lets you install themes and plugins directly from GitHub and bit bucket. https://github.com/nickbreen/docker-wp-setup.

    Scripting like this also makes it very easy to setup docker containers or cloud-config for deployments.

  • apmeyer

    The initial command to install WordPress at the globally defined path works for me, but then anything after that fails. Well, anything after the logged message “Creating wp-config.php…”. I’ve logged the variables to make sure they are pulling through, and the values are being read properly from the global file.

    There are no error messages, so it’s hard to know why it’s failing. It just stops. I’ve gone through and run each of the commands manually after the successful download and they all work that way (creating the config, database and installing).

    I’m sure it’s something on my end, but I’m not sure what. I’ve been banging my head against it for a while. Any tips?

    Thanks!

    • apmeyer

      Note: Your previous article on using shell scripts works flawlessly. I didn’t run into any issues with that approach. But it would be awesome to get it working purely in wp cli too. Thanks for sharing your knowledge!

      • Matt

        It’s hard to know without seeing the setup, but If the commands are working when you run them manually but not working when ran via the script, it sounds like there could be two different PATHs being loaded.

        This can happen when you’re using MAMP or something similar and WP-CLI is loading the system default version of PHP instead of the version that comes with MAMP. The system version might not have MySQL in the path which can cause issues connecting to the database.

        WP-CLI has more info on that in the docs – http://wp-cli.org/docs/installing/#using-a-custom-php-binary

        • apmeyer

          That is, in fact, what I’m running – MAMP. I’ve got WP CLI setup to recognize MAMP, but there may be more I need to do for the system. I’ll do some digging. Thanks Matt.

  • Anoop D

    I installed your package and created the yml config file , and used the command , but when i accessed the site what i got was the ‘select language’ page aka setup-config.php . It is quite natural as we are not giving the database name that we want to use with WP and we have to reenter the username password as well as email …… Just wanted to let you know . It was a really great tutorial , could have been more helpful if you could be specific on where to create the .php file ( where to save that for creating WP-CLI command ) . Thanks a lot .

  • Frederic

    Hi,

    When I run wp installer.php I get the following error message:

    Error: ‘installer.php’ is not a registered wp command. See ‘wp help’.

    Could anyone please tell me where I go wrong?
    Do I need to put the file in the wp-plugin directory?