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
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.
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.
ln -s ./tools/wrangle wrangle
This means you can now run
./wrangle foo
#or
php wrangle foo
Code generation
You can use forme codegen to generate a new custom command.
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.
"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.