Steve Grunwell@stevegrunwell
We're glad you're here!
If a script is only meant to be run once, making it available via wp-admin would be insane.
# Install Son of Clippy $ wp plugin install son-of-clippy --activate # Upgrade Yoast SEO $ wp plugin update wordpress-seo # Remove TwentyThirteen $ wp theme delete twentythirteen
# Add a new editor named Hypnotoad. $ wp user create hypnotoad hypnotoad@example.com --role=editor # ALL GLORY TO THE HYPNOTOAD! $ wp user set-role hypnotoad admin
# Export WXR files for all post types in 5MB chunks $ wp export --max_file_size=5 # Export only posts from 2016 $ wp export --start_date=2016-01-01 --post_type=post # Import WXR files $ wp import my-wxr-file.xml
# Dump the database $ wp db export my-backup.sql # Import that database file $ wp db import my-backup.sql # Optimize the database $ wp db optimize # Repair the database $ wp db repair
# Update production URLs for staging $ wp search-replace example.dev example.com # Replace all instances of "foo" with "bar" *only* in wp_options $ wp search-replace foo bar wp_options
Intelligently handles PHP serialized data!
WP-CLI will inherently be used by more technical users, but that doesn't mean we shouldn't make them friendly
The output of one command should be "pipe-able"
$ wp post list | grep "hello" > 1 Hello world! hello-world 2014-11-11 20:46:47 publish
Make it obvious what the user is doing!
# ¯\_(ツ)_/¯ $ wp my-command --launch # Fffffuuuuuuuuuuuuu...... $ wp my-command --launch-nukes
class My_Awesome_Command extends WP_CLI_Command { // Magic will happen in here! } // Tell WP-CLI that My_Awesome_Command is 'awesome'. WP_CLI::add_command( 'awesome', 'My_Awesome_Command' );
$ wp awesome
class My_Awesome_Command extends WP_CLI_Command { /** * Convert terms from taxonomy A to taxonomy B. * * ... */ public function convert_terms( $args, $assoc_args ) { // All sorts of fancy logic. } }
$ wp awesome convert_terms
A one-line description of what the command does.
/** * Convert terms from taxonomy A to taxonomy B. *
Arguments accepted by the command.
/** * ... * * ## OPTIONS * * <origin> * : The original taxonomy. * * <destination> * : The destination taxonomy. *
How people might use your command.
/** * ... * * ## EXAMPLES * * wp awesome convert-terms category post_tag *
/** * ... * * @synopsis <origin> <destination> * @subcommand convert-terms * @alias do-conversion */
/** * Demonstrate how arguments work. * * ## OPTIONS * * <required> * : This is a required, positional argument. * * [<optional>] * : This positional argument is optional. * * ...
/** * ... * * --option=<required> * : This is a required, associative argument. * * [--option2=<optional>] * : This associative argument is optional. * * [--flag] * : An optional flag. */ public function my_command( $args, $assoc_args ) { // ...
public function my_command( $args, $assoc_args ) { if ( isset( $assoc_args['option2'] ) ) { // The user passed some value to --option2. } }
# Confirm an action. WP_CLI::confirm( 'Are you sure you want to do this?' );
> Are you sure you want to do this? [y/n]
Request additional information from the user.
$email = cli\prompt( 'Email address', 'user@example.com' );
Email address [user@example.com]:
Present the user with a list of options.
$options = array( 'foo' => 'Foo', 'bar' => 'Bar', 'baz' => 'Baz', ); $selected = cli\menu( $options, 'foo', 'What to do?' );
1. Foo 2. Bar 3. Baz What to do? [Foo]:
# Simply output text. WP_CLI::log( 'A normal message' ); # Prepend the text with a colored "Warning:". WP_CLI::warn( '¯\_(ツ)_/¯' ); # Prepend the message with "Error:" and return. WP_CLI::error( '(╯°□°)╯︵ ┻━┻' ); # Prepend the text with a colored "Success:"! WP_CLI::success( 'GREAT SUCCESS!' );
$table = new cli\Table; $table->setHeaders( array( 'id' => 'ID', 'title' => 'Title' ) ); foreach ( $posts as $post ) { $table->addRow( array( 'id' => $post->ID, 'title' => $post->post_title, ) ); } $table->display();
# $ wp my-plugin get-posts +----+----------------------+ | ID | Title | +----+----------------------+ | 1 | Hello World! | +----+----------------------+ | 2 | Goodbye Cruel World! | +----+----------------------+
# $ wp my-plugin get-posts > posts.csv ID Title 1 Hello World! 2 Goodbye Cruel World!
Shortcut for table construction, also works for YAML, JSON, and more:
$headers = array( 'id' => 'ID', 'title' => 'Title' ); $rows = array(); foreach ( $posts as $post ) { $rows[] = array( 'id' => $post->ID, 'title' => $post->post_title, ); } WP_CLI\Utils\format_items( 'table', $rows, $headers );
Show the user that progress is being made.
$progress = WP_CLI\Utils\make_progress_bar( 'Making progress', 10 ); for ( $i = 0; $i < 10; $i++ ) { sleep( 1 ); // Do something real here, please. $progress->tick(); }
Making progress 100%[=================================] 0:10 / 0:10
Don't forget the PHP error log!
error_log( 'Something went wrong!' );
When performing destructive options, consider a --dry-run flag
$dry_run = isset( $assoc_args['dry-run'] ); if ( ! $dry_run ) { do_something_destructive(); } WP_CLI::log( 'Something destructive happened' );
Great scripts will include options for --verbose, --quiet, or both.
// Define the value once. $verbose = isset( $assoc_args['verbose'] ); // Simple conditional around output. if ( $verbose ) { WP_CLI::log( 'Thank you for reading me!' ); }
Always remember your audience and build your command around their needs.
WP-CLI commands make a great addition to most plugins!
// Only load our CLI command when loaded via WP_CLI. if ( defined( 'WP_CLI' ) && WP_CLI ) { require_once dirname( __FILE__ ) . '/my-cli-class.php'; }
The actions and filters you use on your site are still active, so use them!
// Trigger some action in my theme do_action( 'my_theme_some_action' ); // Remove filters you won't be needing. remove_filter( 'the_content', 'wpautop' );
Don't forget useful WordPress functions like wp_parse_args() for setting defaults!
$assoc_args = wp_parse_args( $assoc_args, array( 'foo' => true, 'bar' => 'baz', ) );
if ( ! defined( 'WP_IMPORTING' ) ) { define( 'WP_IMPORTING', true ); }
Useful constant for debugging, major bottleneck when running large scripts!
Activated by default in VIP Quickstart!
When a variable, instance, etc. is no longer being used, PHP will try to clean up the memory in a process known as garbage collection
unset( $instance_var ); $my_global_var = null;
Normally handled at end of request, but CLI commands can run far longer!
protected function stop_the_insanity() { global $wpdb, $wp_object_cache; $wpdb->queries = array(); if ( is_object( $wp_object_cache ) ) { $wp_object_cache->group_ops = array(); $wp_object_cache->stats = array(); $wp_object_cache->memcache_debug = array(); $wp_object_cache->cache = array(); if ( method_exists( $wp_object_cache, '__remoteset' ) ) { $wp_object_cache->__remoteset(); // important } } }
When working with WordPress Multisite, know what site you're working on!
# Runs on the default site. $ wp cache empty # Runs on a specific site. $ wp cache empty --url=http://subsite.example.com
Remember: calling die() or exit will kill the entire script, so use with care!
If in doubt, return early!
# Launch an external process. WP_CLI::launch( WP_CLI\Utils\esc_cmd( $command ) ); # Call another WP-CLI command. WP_CLI::launch_self( $command, $args, $assoc_args );
Steve Grunwellstevegrunwell.comgrowella.com