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(InputInterface $input, OutputInterface $output): int
{
// grab user options/arguments (don't forget to validate!)
$foo = $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 wrangleThis means you can now run
./wrangle foo
#or
php wrangle fooCode generation
You can use forme codegen to generate a new custom command.
forme make command MyAwesomeCommandAs 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.
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.