Skip to content
On this page

Custom Commands

Sometimes you need to write custom terminal commands to be used within a project, either in production or locally. Laravel has a tool called Artisan, Symfony has Console, WordPress has WP-Cli, Rails has Rake, and so on. These can all usually be extended with custom commands.

Forme has Wrangle.

Writing Commands

Like other similar php framework tools, Wrangle is based on Symfony Console.

To create a custom command, you simply add a command class to the app/Commands/Wrangle directory in the corresponding namespace.

php
<?php
declare(strict_types=1);

namespace Foobar\MyAwesomePlugin\Commands\Wrangle;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;

use function Laravel\Prompts\info;

class MyAwesomeCommand extends Command
{
   protected $defaultName = 'foobar';

  public function configure(): void
  {
    // see https://symfony.com/doc/current/console.html
    $this->addOption(name: 'foo',shortcut: 'f', mode: InputOption::VALUE_OPTIONAL, default: 'bar', description: 'Foo description');
    $this->setDescription('My Awesome Command');
    $this->setHelp('Help text for my awesome command');
  }

  protected function execute(): int
  {
    // grab user options/arguments (don't forget to validate!)
    $foo = $this->input->getOption('foo');
    // do/output stuff
    info($foo);

    // return exit code
    return Command::SUCCESS;
  }
}

You should then be able to go to the project root and run ./tools/wrangle foo to run your command and see the output.

Symfony Console

Since this is plain old Symfony Console app, you have access to all the usual Symfony Console functionality and helpers.

See here for a list of available helpers.

Laravel Prompts

Laravel prompts is also available and can be used alongside or instead of the Console helpers. It's a great library with some fantastic looking TUI components and interactions.

Symfony Process

Sometimes you need to run external commands from within your command, otherwise known as "shelling out". Maybe you need to call the globally installed WP Cli for example. You can do that with Symfony Process - see here for more information.

php
use Symfony\Component\Process\Process;

$pluginList = Process::fromShellCommandline('wp plugin list --format=json')->mustRun()->getOutput();

Symlinking in the project root

If you care about command line aesthetics, you can symlink ./tools/wrangle into your project root.

bash
ln -s ./tools/wrangle wrangle

This means you can now run

bash
./wrangle foo
#or
php wrangle foo

Code generation

You can use forme codegen to generate a new custom command.

bash
forme make command MyAwesomeCommand

As a library

Sometimes you want to be able to share a command across different projects and websites. You can do that by creating a composer/packagist library and putting your wrangle commands in there.

Just add your commands to the Commands\Wrangle namespace as you would for a plugin or theme, and then make sure you explicitly require each command file in your composer.json.

json
"autoload": {
  "files": [
    "src/Commands/Wrangle/MyAwesomeCommand.php",
    "src/Commands/Wrangle/MyOtherCommand.php"
  ]
}

After publishing it to a packagist repository, you can now require it like any other library.

Wrangle vs WP CLI vs Forme Codegen

The cli tool ecosystem within Forme is a little bit fragmented at the time of writing (2024-09-04).

The venerable WP Cli is the official WordPress tool and covers most of the website management functionality from the WordPress admin. Like its counterparts in more modern PHP frameworks, it can be extended with custom commands. While this works very well for a lot of general use cases it does have its limitations - we've run into library version conflicts in certain situations due to the way it's architected, especially since WordPress doesn't really do dependency management in any meaningful sense.

Forme codegen is our own globally installed development command line tool. It's aimed at local development environments, and is not designed for use in production nor for being extended.

We originally built Wrangle to fulfill the use case of needing project-level custom commands that for whatever reason would not work within WP Cli. Eventually we will be looking to merge Forme codegen's local development features into Wrangle, and subsequently deprecate Forme codegen. We are also considering whether we might eventually alias WP Cli (or at least a subset of it) within Wrangle. This should provide a more unified DX.

How does Wrangle work under the hood?

Like WP Cli, Artisan, Forme codegen and the rest, Wrangle is based on Symfony Console.

Our latest theme and plugin starter boilerplate has a CommandRegistry class which naively globs the files in the theme or plugin's app/Commands/Wrangle directory and requires them as long as we're in a cli context.

Wrangle command libraries need to add each command class file to the autoload files array in their composer.json file so that they're automatically included.

When the wrangle bin script is run from project root, it simply checks - again, naively - for any loaded classes whose namespace contains Commands\Wrangle and adds them to a Symfony console application.

Made by Moussa Clarke @ Sanders Web Works with ❤️