Skip to content

Instantly share code, notes, and snippets.

@emsifa
Last active February 8, 2017 04:19
Show Gist options
  • Save emsifa/6372a35b519a1d544c9e4bfca906de88 to your computer and use it in GitHub Desktop.
Save emsifa/6372a35b519a1d544c9e4bfca906de88 to your computer and use it in GitHub Desktop.
PHP Simple Console Command

Quick Start

Initialize and Register Command(s)

Create file called commando without .php. Write code below:

<?php

require 'Commando.php';

$app = new Commando($argv);

$app->command('hello', "Show hello world message", function() {
    $this->writeln("Hello World!");
});

$app->run();

Usage

php commando hello

Show Available Commands

php commando list

Command Argument

Required Argument

$app->command('hello {name}', "Say hello to someone", function($name) {
    $this->writeln("Hello " . $name);
});

Usage

php commando hello "John Doe"

Optional Argument

$app->command('db:migrate {filename?}', "Migrate database", function($filename) {
    // $filename will null if not given
});

Optional Argument With Default Value

$app->command('db:migrate {filename=all}', "Migrate database", function($filename) {
    // $filename will be string 'all' if not given
});

Argument Array

$app->command('install {packages*}', "Install some packages", function($packages) {
    // $packages here is array
});

Usage Example

php commando install foo bar baz

Then $packages will be ['foo', 'bar', 'baz'].

Command Option

Switch Option

Switch option is option only will be true or false. If you put that option or give any value to that option, the value will be true.

$app->command('render {filename} {--minify}', "Render something", function($filename) {
    $minify = $this->option('minify'); // true or false
});

Usage Example:

php commando render something.ext --minify
php commando render something.ext --minify=1
php commando render something.ext --minify=0
php commando render something.ext --minify=false

Option minify will be true. If you didn't put that option like this

php commando render something.ext

Option minify will be false.

Option With Value

Just add = in your option.

$app->command('render {filename} {--output=}', "Render something", function($filename) {
    $output = $this->option('output');
});

Usage Example

php commando render something.ext --output result.ext

Or

php commando render something.ext --output=result.ext

Option Alias

$app->command('render {filename} {--o|output=}', "Render something", function($filename) {
    $option = $this->option('output'); // same
});

Usage Example:

php commando render something.ext -o result.ext

Or

php commando render something.ext -o=result.ext

Option And Argument Description

Add ::<description> in your option or argument.

$app->command('
    render 
    {filename::Input filename} 
    {--o|output=::Output filename}',
    "Render something", 
    function($filename) {
        // do something
    }
);

Now you can display it like this:

php commando render --help

The result will be like this:

 Render something

 Usage:

  render <filename> [options]

 Arguments: 

  filename       Input filename

 Options: 

  -o, --output   Output filename

<?php
class Commando
{
protected $filename;
protected $command;
protected $arguments = [];
protected $options = [];
protected $optionsAlias = [];
protected $commands = [];
protected $resolvedOptions = [];
protected $foregroundColors = [
'black' => '0;30',
'dark_gray' => '1;30',
'blue' => '0;34',
'light_blue' => '1;34',
'green' => '0;32',
'light_green' => '1;32',
'cyan' => '0;36',
'light_cyan' => '1;36',
'red' => '0;31',
'light_red' => '1;31',
'purple' => '0;35',
'light_purple' => '1;35',
'brown' => '0;33',
'yellow' => '1;33',
'light_gray' => '0;37',
'white' => '1;37',
];
protected $backgroundColors = [
'black' => '40',
'red' => '41',
'green' => '42',
'yellow' => '43',
'blue' => '44',
'magenta' => '45',
'cyan' => '46',
'light_gray' => '47',
];
public function __construct(array $argv = null)
{
if (is_null($argv)) {
$argv = $GLOBALS['argv'];
}
list(
$this->filename,
$this->command,
$this->arguments,
$this->options,
$this->optionsAlias
) = $this->parseArgv($argv);
$this->command('list', "Show available commands", [$this, 'showAvailableCommands']);
}
public function command($command, $description, callable $handler)
{
list($command, $args, $options) = $this->parseCommand($command);
$this->commands[$command] = [
'handler' => $handler,
'description' => $description,
'args' => $args,
'options' => $options
];
}
public function run()
{
return $this->execute($this->command);
}
public function execute($command)
{
if (!isset($this->commands[$command])) {
return $this->showAvailableCommands();
}
if (array_key_exists('help', $this->options) OR array_key_exists('h', $this->optionsAlias)) {
return $this->showHelp($command);
}
$handler = $this->commands[$command]['handler'];
$arguments = $this->validateAndResolveArguments($command);
$this->validateAndResolveOptions($command);
if ($handler instanceof \Closure) {
$handler = $handler->bindTo($this);
}
call_user_func_array($handler, $arguments);
}
public function option($key)
{
return isset($this->resolvedOptions[$key])? $this->resolvedOptions[$key] : null;
}
protected function showAvailableCommands()
{
$count = 0;
$maxLen = 0;
$this->writeln(PHP_EOL.$this->color(" Available Commands: ", 'blue').PHP_EOL);
foreach(array_keys($this->commands) as $name) {
if (strlen($name ) > $maxLen) $maxLen = strlen($name);
}
$pad = $maxLen + 3;
foreach ($this->commands as $name => $command) {
$no = ++$count.') ';
$this->write(str_repeat(' ', 4 - strlen($no)).$this->color($no, 'dark_gray'));
$this->write($this->color($name, 'green').str_repeat(' ', $pad - strlen($name)));
$this->writeln($command['description']);
$this->writeln('');
}
$this->writeln(" Type '".$this->color("php ".$this->filename." <command> --help", 'blue')."' for usage information".PHP_EOL);
}
protected function showHelp($commandName)
{
$command = $this->commands[$commandName];
$maxLen = 0;
$args = $command['args'];
$opts = $command['options'];
$usageArgs = [$commandName];
$displayArgs = [];
$displayOpts = [];
foreach($args as $argName => $argSetting) {
$usageArgs[] = "<".$argName.">";
$displayArg = $argName;
if ($argSetting['is_optional']) {
$displayArg .= " (optional)";
}
if (strlen($displayArg) > $maxLen) {
$maxLen = strlen($displayArg);
}
$displayArgs[$displayArg] = $argSetting['description'];
}
$usageArgs[] = "[options]";
foreach($opts as $optName => $optSetting) {
$displayOpt = $optSetting['alias']? str_pad('-'.$optSetting['alias'].',', 4) : str_repeat(' ', 4);
$displayOpt .= "--".$optName;
if (strlen($displayOpt) > $maxLen) {
$maxLen = strlen($displayOpt);
}
$displayOpts[$displayOpt] = $optSetting['description'];
}
$pad = $maxLen + 3;
$this->writeln(PHP_EOL." ".$command['description'].PHP_EOL);
$this->writeln($this->color(" Usage:", 'blue'));
$this->writeln('');
$this->writeln(" ".implode(" ", $usageArgs));
$this->writeln("");
$this->writeln($this->color(" Arguments: ", 'blue').PHP_EOL);
foreach($displayArgs as $argName => $argDesc) {
$this->writeln(" ".$this->color($argName, 'green').str_repeat(' ', $pad - strlen($argName)).$argDesc);
}
$this->writeln('');
$this->writeln($this->color(" Options: ", 'blue').PHP_EOL);
foreach($displayOpts as $optName => $optDesc) {
$this->writeln(" ".$this->color($optName, 'green').str_repeat(' ', $pad - strlen($optName)).$optDesc);
}
$this->writeln('');
}
public function write($message)
{
print($message);
}
public function writeln($message)
{
return $this->write($message.PHP_EOL);
}
public function error($message, $exit = true)
{
$this->writeln(PHP_EOL." WHOOPS! ".$message.PHP_EOL);
if ($exit) exit();
}
public function color($text, $fg, $bg = null)
{
$coloredString = "";
$colored = false;
// Check if given foreground color found
if (isset($this->foregroundColors[$fg])) {
$colored = true;
$coloredString .= "\033[" . $this->foregroundColors[$fg] . "m";
}
// Check if given background color found
if (isset($this->backgroundColors[$bg])) {
$colored = true;
$coloredString .= "\033[" . $this->backgroundColors[$bg] . "m";
}
// Add string and end coloring
$coloredString .= $text . ($colored? "\033[0m" : "");
return $coloredString;
}
protected function validateAndResolveArguments($command)
{
$args = $this->arguments;
$commandArgs = $this->commands[$command]['args'];
$resolvedArgs = [];
foreach($commandArgs as $argName => $argOption) {
if (!$argOption['is_optional'] AND empty($args)) {
return $this->error("Argument {$argName} is required");
}
if ($argOption['is_array']) {
$value = $args;
} else {
$value = array_shift($args) ?: $argOption['default'];
}
$resolvedArgs[$argName] = $value;
}
return $resolvedArgs;
}
protected function validateAndResolveOptions($command)
{
$options = $this->options;
$optionsAlias = $this->optionsAlias;
$commandOptions = $this->commands[$command]['options'];
$resolvedOptions = [];
foreach ($commandOptions as $optName => $optionSetting) {
$alias = $optionSetting['alias'];
if ($alias AND isset($optionsAlias[$alias])) {
$value = isset($optionsAlias[$alias])? $optionsAlias[$alias] : $optionSetting['default'];
} else {
$value = isset($options[$optName])? $options[$optName] : $optionSetting['default'];
}
if (!$optionSetting['is_valuable']) {
$resolvedOptions[$optName] = !empty($value);
} else {
$resolvedOptions[$optName] = $value;
}
}
$this->resolvedOptions = $resolvedOptions;
}
protected function getHandlerParams($handler)
{
if ($handler instanceof \Closure) {
$reflection = new ReflectionFunction($handler);
} elseif(is_array($handler)) {
$reflection = new ReflectionMethod($handler[0], $handler[1]);
} elseif(is_string($handler)) {
$reflection = new ReflectionFunction($handler);
}
return $reflection->getParameters();
}
protected function parseCommand($command)
{
$exp = explode(" ", trim($command), 2);
$command = trim($exp[0]);
$args = [];
$options = [];
if (isset($exp[1])) {
preg_match_all("/\{(?<name>\w+)(?<arr>\*)?((=(?<default>[^\}]+))|(?<optional>\?))?(::(?<desc>[^}]+))?\}/i", $exp[1], $matchArgs);
preg_match_all("/\{--((?<alias>[a-zA-Z])\|)?(?<name>\w+)((?<valuable>=)(?<default>[^\}]+)?)?(::(?<desc>[^}]+))?\}/i", $exp[1], $matchOptions);
foreach($matchArgs['name'] as $i => $argName) {
$default = $matchArgs['default'][$i];
$expDefault = explode('::', $default, 2);
if (count($expDefault) > 1) {
$default = $expDefault[0];
$description = $expDefault[1];
} else {
$default = $expDefault[0];
$description = $matchArgs['desc'][$i];
}
$args[$argName] = [
'is_array' => !empty($matchArgs['arr'][$i]),
'is_optional' => !empty($matchArgs['optional'][$i]) || !empty($default),
'default' => $default ?: null,
'description' => $description,
];
}
foreach($matchOptions['name'] as $i => $optName) {
$default = $matchOptions['default'][$i];
$expDefault = explode('::', $default, 2);
if (count($expDefault) > 1) {
$default = $expDefault[0];
$description = $expDefault[1];
} else {
$default = $expDefault[0];
$description = $matchOptions['desc'][$i];
}
$options[$optName] = [
'is_valuable' => !empty($matchOptions['valuable'][$i]),
'default' => $default ?: null,
'description' => $description,
'alias' => $matchOptions['alias'][$i] ?: null,
];
}
}
return [$command, $args, $options];
}
protected function parseArgv(array $argv)
{
$filename = array_shift($argv);
$command = array_shift($argv);
$arguments = [];
$options = [];
$optionsAlias = [];
while (count($argv)) {
$arg = array_shift($argv);
if ($this->isOption($arg)) {
$optName = ltrim($arg, "-");
if ($this->isOptionWithValue($arg)) {
list($optName, $optvalue) = explode("=", $arg);
} else {
$optvalue = array_shift($argv);
}
$options[$optName] = $optvalue;
} elseif ($this->isOptionAlias($arg)) {
$alias = ltrim($arg, "-");
$exp = explode("=", $alias);
$aliases = str_split($exp[0]);
if (count($aliases) > 1) {
foreach($aliases as $aliasName) {
$optionsAlias[$aliasName] = null;
}
} else {
$aliasName = $aliases[0];
if (count($exp) > 1) {
list($aliasName, $aliasValue) = $exp;
} else {
$aliasValue = array_shift($argv);
}
$optionsAlias[$aliasName] = $aliasValue;
}
} else {
$arguments[] = $arg;
}
}
return [$filename, $command, $arguments, $options, $optionsAlias];
}
protected function isOption($arg)
{
return (bool) preg_match("/^--\w+/", $arg);
}
protected function isOptionAlias($arg)
{
return (bool) preg_match("/^-[a-z]+/i", $arg);
}
protected function isOptionWithValue($arg)
{
return strpos($arg, "=") !== false;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment