Stop Writing Messy WP-CLI Commands: A Better Way

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.

author avatar
Ahmad Wael
I'm a WordPress and WooCommerce developer with 15+ years of experience building custom e-commerce solutions and plugins. I specialize in PHP development, following WordPress coding standards to deliver clean, maintainable code. Currently, I'm exploring AI and e-commerce by building multi-agent systems and SaaS products that integrate technologies like Google Gemini API with WordPress platforms, approaching every project with a commitment to performance, security, and exceptional user experience.

Leave a Reply

Your email address will not be published. Required fields are marked *