I took over a project once where the deployment checklist was a mile long. Clear this cache, sync that data, run this report. All manual. The obvious first step was to automate it with a bunch of custom WP-CLI commands. Fast forward a few months, and we had over 20 commands. The file we kept them in was a total mess—just a long, ugly list of disconnected functions that nobody wanted to touch.
My first move to clean it up was to bundle all of them into a single, massive class. You know, `class MyProject_CLI_Commands`, where each public method was a command. It felt like a win. For about a day. Then I realized command `A` needed a connection to a Salesforce API, command `B` needed a Redis client, and command `C` needed a special data logger. The class constructor became a dumping ground for every dependency for every command. Total nightmare. It wasn’t clean; it was just a different kind of mess.
The Right Way to Build Custom WP-CLI Commands
You have to treat every command as its own self-contained object. The best way to do this is with an invokable class. This approach, which builds on a great concept I saw over at carlalexander.ca, keeps everything clean and separate. Each command class defines and loads only the dependencies it actually needs. No more bloated, monstrous constructors.
The trick is to start with a simple interface. This is the contract that all your command classes will follow. It forces you to be consistent and makes the entire system predictable. Trust me on this, when you’re 30 commands deep, you’ll be glad you did this.
<?php
interface CommandInterface {
public function get_name();
public function get_description();
public function get_synopsis();
public function __invoke($args, $assoc_args);
}
// An example command that follows the contract.
class Sync_Product_Data_Command implements CommandInterface {
private $api_client;
// Only inject what THIS command needs.
public function __construct( ProductAPIClient $api_client ) {
$this->api_client = $api_client;
}
public function get_name() {
return 'product sync-data';
}
// ... other required interface methods ...
public function __invoke($args, $assoc_args) {
// Now you do the real work.
WP_CLI::success( 'Product data sync complete!' );
}
}So, What’s the Point?
This isn’t just about writing code that looks good. It’s about building something that won’t make you want to pull your hair out six months from now. When your CLI tools are built this way, they’re predictable. You can add a new command, fix a bug in an old one, or delete one entirely without worrying about causing a cascade of failures. It comes down to a few key ideas:
- One command, one class. Period.
- An interface ensures every command behaves predictably.
- Only inject the dependencies a command actually uses.
Look, this stuff gets complicated fast. If you’re tired of debugging someone else’s mess and just want your site to work, drop my team a line. We’ve probably seen it before.
Leave a Reply