1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112: 113: 114: 115: 116: 117: 118: 119: 120: 121: 122: 123: 124: 125: 126: 127: 128: 129: 130: 131: 132: 133: 134: 135: 136: 137: 138: 139: 140: 141: 142: 143: 144: 145: 146: 147: 148: 149: 150: 151: 152: 153: 154: 155: 156: 157: 158: 159: 160: 161: 162: 163: 164: 165: 166: 167: 168: 169: 170: 171: 172: 173: 174: 175: 176: 177: 178: 179: 180: 181: 182: 183: 184: 185: 186: 187: 188: 189: 190: 191: 192: 193: 194: 195: 196: 197: 198: 199: 200: 201: 202: 203: 204: 205: 206: 207: 208: 209: 210: 211: 212: 213: 214: 215: 216: 217: 218: 219: 220: 221: 222: 223: 224: 225: 226: 227: 228: 229: 230: 231: 232: 233: 234: 235: 236: 237: 238: 239: 240: 241: 242: 243: 244: 245: 246: 247: 248: 249: 250: 251: 252: 253: 254: 255: 256: 257: 258: 259: 260: 261: 262: 263: 264: 265: 266: 267: 268: 269: 270: 271: 272: 273: 274: 275: 276: 277: 278: 279: 280: 281: 282: 283: 284: 285: 286: 287: 288: 289: 290: 291: 292: 293: 294: 295: 296: 297: 298: 299: 300: 301: 302: 303: 304: 305: 306: 307: 308: 309: 310: 311: 312: 313: 314: 315: 316: 317: 318: 319: 320: 321: 322: 323: 324: 325: 326: 327: 328: 329: 330: 331: 332: 333: 334: 335: 336: 337: 338: 339: 340: 341: 342: 343: 344: 345: 346: 347: 348: 349: 350: 351: 352: 353: 354: 355: 356: 357: 358: 359: 360: 361:
<?php
namespace CzProject\PhpCli\Application;
use CzProject\PhpCli\ApplicationException;
use CzProject\PhpCli\Console;
use CzProject\PhpCli\ConsoleFactory;
class Application
{
protected $console;
protected $commands;
protected $applicationName;
protected $defaultCommand;
public function __construct(Console $console = NULL)
{
$this->console = $console ? $console : ConsoleFactory::createConsole();
}
public function setDefaultCommand($defaultCommand)
{
$this->defaultCommand = $defaultCommand;
return $this;
}
public function addCommand(ICommand $command)
{
$this->setCommand($command->getName(), $command);
return $this;
}
public function setCommand($name, ICommand $command)
{
$this->commands[$name] = $command;
return $this;
}
public function setApplicationName($applicationName)
{
$this->applicationName = $applicationName;
return $this;
}
public function run(array $rawParameters = NULL)
{
$parameters = $this->console->getParameters();
if ($parameters === NULL && $rawParameters === NULL) {
if (isset($_SERVER['argv']) && is_array($_SERVER['argv'])) {
$rawParameters = $_SERVER['argv'];
}
}
if ($rawParameters !== NULL) {
$parameters = $this->console->processRawParameters($rawParameters);
}
if (empty($parameters)) {
$this->printHelp();
} else {
$request = $this->createRequest($parameters);
if (!$request) {
throw new ApplicationException('Missing command name.');
}
$commandName = $request->getCommandName();
if (!isset($this->commands[$commandName])) {
throw new ApplicationException("Unknow command '$commandName'.");
}
$command = $this->commands[$commandName];
$commandOptions = $command->getOptions();
$options = $this->processOptions($request->getOptions(), $commandOptions !== NULL ? $commandOptions : array());
$command->run($this->console, $options, $request->getArguments());
}
}
public function printException(\Exception $e)
{
$console = $this->console;
$console->nl();
$console->error('ERROR: ' . $e->getMessage());
$console->nl();
$console->nl();
$console->error('Details:');
$console->nl();
$console->error(' - type: ' . get_class($e));
$console->nl();
$console->error(' - code: ' . $e->getCode());
$console->nl();
$console->error(' - file: ' . $e->getFile());
$console->nl();
$console->error(' - line: ' . $e->getLine());
$console->nl();
}
protected function createRequest(array $parameters)
{
$command = NULL;
$options = array();
$arguments = array();
foreach ($parameters as $parameterName => $parameterValue) {
if (is_string($parameterName)) {
$options[$parameterName] = $parameterValue;
} else {
if ($command === NULL) {
$command = $parameterValue;
} else {
$arguments[] = $parameterValue;
}
}
}
if ($command === NULL) {
$command = $this->defaultCommand;
if ($command === NULL) {
return NULL;
}
}
return new ApplicationRequest($command, $options, $arguments);
}
protected function processOptions(array $options, array $definitions)
{
$result = array();
foreach ($definitions as $name => $definition) {
$originalName = $name;
$originalValue = NULL;
$isAlias = FALSE;
if (isset($options[$name])) {
$originalValue = $options[$name];
unset($options[$name]);
}
if (is_string($definition) || isset($definition['alias'])) {
$aliasName = is_string($definition) ? $definition : $definition['alias'];
if (!is_string($aliasName)) {
throw new ApplicationException("Alias in option '$optionName' must be string, " . gettype($aliasName) . ' given.');
}
if (!isset($definitions[$aliasName])) {
throw new ApplicationException("Unknow alias '$aliasName' in option '$name'.");
}
$definition = $definitions[$aliasName];
$name = $aliasName;
$isAlias = TRUE;
}
if ($isAlias && $originalValue === NULL) {
continue;
}
if (!is_array($definition)) {
throw new ApplicationException("Definition of option '$name' must be array, " . gettype($definition) . ' given.');
}
if (array_key_exists($name, $result)) {
throw new ApplicationException("Value for option '$name' already exists. Remove option '$originalName' from parameters.");
}
$result[$name] = $this->processOption($name, $originalValue, $definition);
}
if (!empty($options)) {
$labels = array_map(function ($label) {
return '\'' . $label . '\'';
}, array_keys($options));
throw new ApplicationException("Unknow options " . implode(', ', $labels) . '.');
}
return $result;
}
protected function processOption($name, $value, array $definition)
{
static $types = array(
'boolean',
'bool',
'integer',
'int',
'float',
'string',
);
if (!isset($definition['type'])) {
throw new ApplicationException("Missing 'type' definition for option '$name'.");
}
if (!is_string($definition['type'])) {
throw new ApplicationException("Invalid 'type' definition for option '$name'. Type must be string, " . gettype($definition['type']) . ' given.');
}
$type = strtolower($definition['type']);
$required = isset($definition['required']) && $definition['required'];
$nullable = isset($definition['nullable']) && $definition['nullable'];
$repeatable = isset($definition['repeatable']) && $definition['repeatable'];
$defaultValue = isset($definition['defaultValue']) ? $definition['defaultValue'] : NULL;
$value = isset($value) ? $value : $defaultValue;
if (!in_array($type, $types, TRUE)) {
throw new ApplicationException("Unknow type '$type' in definition for option '$name'.");
}
if ($value === NULL && $required) {
throw new ApplicationException("Missing value for required option '$name'.");
}
if ($repeatable && !is_array($value)) {
$value = array($value);
}
if (!$repeatable && is_array($value)) {
throw new ApplicationException("Multiple values for option '$name'.");
}
if ($nullable && ((is_array($value) && empty($value)) || $value === '')) {
$value = NULL;
}
if ($value !== NULL) {
if (is_array($value)) {
$values = array();
foreach ($value as $val) {
$values[] = $this->convertType($name, $val, $type);
}
$value = $values;
} else {
$value = $this->convertType($name, $value, $type);
}
}
return $value;
}
protected function convertType($name, $value, $type)
{
if ($type === 'bool' || $type === 'boolean') {
if (is_string($value)) {
$lValue = strtolower($value);
if ($lValue === 'yes' || $lValue === 'y' || $lValue === 'on' || $lValue === 'true' || $lValue === '1') {
return TRUE;
} elseif ($lValue === 'no' || $lValue === 'n' || $lValue === 'off' || $lValue === 'false' || $lValue === '0') {
return FALSE;
}
} elseif (is_bool($value) || is_int($value) || is_float($value)) {
return (bool) $value;
}
throw new \CzProject\PhpCli\InvalidValueException("Invalid boolean value for option '$name'.");
} elseif ($type === 'string' || $type === 'int' || $type === 'integer' || $type === 'float') {
settype($value, $type);
return $value;
}
throw new \CzProject\PhpCli\InvalidArgumentException("Unknow type '$type'.");
}
protected function printHelp()
{
if (isset($this->applicationName)) {
$this->console->nl()
->output($this->applicationName)
->nl();
}
$this->console->nl()
->warning('Usage:')
->nl()
->output(' command [options] -- [arguments]')
->nl();
$this->console->nl()
->warning('Available commands:')
->nl();
$len = 0;
foreach ($this->commands as $commandName => $command) {
$len = max($len, strlen($commandName));
}
foreach ($this->commands as $commandName => $command) {
$commandDescription = $command->getDescription();
$this->console->output(' ')
->success($commandName);
if (isset($commandDescription)) {
$this->console->output(str_repeat(' ', $len - strlen($commandName)))
->output(' ')
->output($commandDescription);
}
$this->console->nl();
}
$this->console->nl();
}
}