[ Index ] |
PHP Cross Reference of DokuWiki |
[Summary view] [Print] [Text view]
1 <?php 2 3 namespace splitbrain\phpcli; 4 5 /** 6 * Class CLI 7 * 8 * Your commandline script should inherit from this class and implement the abstract methods. 9 * 10 * @author Andreas Gohr <andi@splitbrain.org> 11 * @license MIT 12 */ 13 abstract class CLI 14 { 15 /** @var string the executed script itself */ 16 protected $bin; 17 /** @var Options the option parser */ 18 protected $options; 19 /** @var Colors */ 20 public $colors; 21 22 /** @var array PSR-3 compatible loglevels and their prefix, color, output channel */ 23 protected $loglevel = array( 24 'debug' => array('', Colors::C_LIGHTGRAY, STDOUT), 25 'info' => array('ℹ ', Colors::C_CYAN, STDOUT), 26 'notice' => array('☛ ', Colors::C_CYAN, STDOUT), 27 'success' => array('✓ ', Colors::C_GREEN, STDOUT), 28 'warning' => array('⚠ ', Colors::C_BROWN, STDERR), 29 'error' => array('✗ ', Colors::C_RED, STDERR), 30 'critical' => array('☠ ', Colors::C_LIGHTRED, STDERR), 31 'alert' => array('✖ ', Colors::C_LIGHTRED, STDERR), 32 'emergency' => array('✘ ', Colors::C_LIGHTRED, STDERR), 33 ); 34 35 protected $logdefault = 'info'; 36 37 /** 38 * constructor 39 * 40 * Initialize the arguments, set up helper classes and set up the CLI environment 41 * 42 * @param bool $autocatch should exceptions be catched and handled automatically? 43 */ 44 public function __construct($autocatch = true) 45 { 46 if ($autocatch) { 47 set_exception_handler(array($this, 'fatal')); 48 } 49 50 $this->colors = new Colors(); 51 $this->options = new Options($this->colors); 52 } 53 54 /** 55 * Register options and arguments on the given $options object 56 * 57 * @param Options $options 58 * @return void 59 * 60 * @throws Exception 61 */ 62 abstract protected function setup(Options $options); 63 64 /** 65 * Your main program 66 * 67 * Arguments and options have been parsed when this is run 68 * 69 * @param Options $options 70 * @return void 71 * 72 * @throws Exception 73 */ 74 abstract protected function main(Options $options); 75 76 /** 77 * Execute the CLI program 78 * 79 * Executes the setup() routine, adds default options, initiate the options parsing and argument checking 80 * and finally executes main() - Each part is split into their own protected function below, so behaviour 81 * can easily be overwritten 82 * 83 * @throws Exception 84 */ 85 public function run() 86 { 87 if ('cli' != php_sapi_name()) { 88 throw new Exception('This has to be run from the command line'); 89 } 90 91 $this->setup($this->options); 92 $this->registerDefaultOptions(); 93 $this->parseOptions(); 94 $this->handleDefaultOptions(); 95 $this->setupLogging(); 96 $this->checkArgments(); 97 $this->execute(); 98 99 exit(0); 100 } 101 102 // region run handlers - for easier overriding 103 104 /** 105 * Add the default help, color and log options 106 */ 107 protected function registerDefaultOptions() 108 { 109 $this->options->registerOption( 110 'help', 111 'Display this help screen and exit immeadiately.', 112 'h' 113 ); 114 $this->options->registerOption( 115 'no-colors', 116 'Do not use any colors in output. Useful when piping output to other tools or files.' 117 ); 118 $this->options->registerOption( 119 'loglevel', 120 'Minimum level of messages to display. Default is ' . $this->colors->wrap($this->logdefault, Colors::C_CYAN) . '. ' . 121 'Valid levels are: debug, info, notice, success, warning, error, critical, alert, emergency.', 122 null, 123 'level' 124 ); 125 } 126 127 /** 128 * Handle the default options 129 */ 130 protected function handleDefaultOptions() 131 { 132 if ($this->options->getOpt('no-colors')) { 133 $this->colors->disable(); 134 } 135 if ($this->options->getOpt('help')) { 136 echo $this->options->help(); 137 exit(0); 138 } 139 } 140 141 /** 142 * Handle the logging options 143 */ 144 protected function setupLogging() 145 { 146 $level = $this->options->getOpt('loglevel', $this->logdefault); 147 if (!isset($this->loglevel[$level])) $this->fatal('Unknown log level'); 148 foreach (array_keys($this->loglevel) as $l) { 149 if ($l == $level) break; 150 unset($this->loglevel[$l]); 151 } 152 } 153 154 /** 155 * Wrapper around the option parsing 156 */ 157 protected function parseOptions() 158 { 159 $this->options->parseOptions(); 160 } 161 162 /** 163 * Wrapper around the argument checking 164 */ 165 protected function checkArgments() 166 { 167 $this->options->checkArguments(); 168 } 169 170 /** 171 * Wrapper around main 172 */ 173 protected function execute() 174 { 175 $this->main($this->options); 176 } 177 178 // endregion 179 180 // region logging 181 182 /** 183 * Exits the program on a fatal error 184 * 185 * @param \Exception|string $error either an exception or an error message 186 * @param array $context 187 */ 188 public function fatal($error, array $context = array()) 189 { 190 $code = 0; 191 if (is_object($error) && is_a($error, 'Exception')) { 192 /** @var Exception $error */ 193 $this->debug(get_class($error) . ' caught in ' . $error->getFile() . ':' . $error->getLine()); 194 $this->debug($error->getTraceAsString()); 195 $code = $error->getCode(); 196 $error = $error->getMessage(); 197 198 } 199 if (!$code) { 200 $code = Exception::E_ANY; 201 } 202 203 $this->critical($error, $context); 204 exit($code); 205 } 206 207 /** 208 * System is unusable. 209 * 210 * @param string $message 211 * @param array $context 212 * 213 * @return void 214 */ 215 public function emergency($message, array $context = array()) 216 { 217 $this->log('emergency', $message, $context); 218 } 219 220 /** 221 * Action must be taken immediately. 222 * 223 * Example: Entire website down, database unavailable, etc. This should 224 * trigger the SMS alerts and wake you up. 225 * 226 * @param string $message 227 * @param array $context 228 */ 229 public function alert($message, array $context = array()) 230 { 231 $this->log('alert', $message, $context); 232 } 233 234 /** 235 * Critical conditions. 236 * 237 * Example: Application component unavailable, unexpected exception. 238 * 239 * @param string $message 240 * @param array $context 241 */ 242 public function critical($message, array $context = array()) 243 { 244 $this->log('critical', $message, $context); 245 } 246 247 /** 248 * Runtime errors that do not require immediate action but should typically 249 * be logged and monitored. 250 * 251 * @param string $message 252 * @param array $context 253 */ 254 public function error($message, array $context = array()) 255 { 256 $this->log('error', $message, $context); 257 } 258 259 /** 260 * Exceptional occurrences that are not errors. 261 * 262 * Example: Use of deprecated APIs, poor use of an API, undesirable things 263 * that are not necessarily wrong. 264 * 265 * @param string $message 266 * @param array $context 267 */ 268 public function warning($message, array $context = array()) 269 { 270 $this->log('warning', $message, $context); 271 } 272 273 /** 274 * Normal, positive outcome 275 * 276 * @param string $string 277 * @param array $context 278 */ 279 public function success($string, array $context = array()) 280 { 281 $this->log('success', $string, $context); 282 } 283 284 /** 285 * Normal but significant events. 286 * 287 * @param string $message 288 * @param array $context 289 */ 290 public function notice($message, array $context = array()) 291 { 292 $this->log('notice', $message, $context); 293 } 294 295 /** 296 * Interesting events. 297 * 298 * Example: User logs in, SQL logs. 299 * 300 * @param string $message 301 * @param array $context 302 */ 303 public function info($message, array $context = array()) 304 { 305 $this->log('info', $message, $context); 306 } 307 308 /** 309 * Detailed debug information. 310 * 311 * @param string $message 312 * @param array $context 313 */ 314 public function debug($message, array $context = array()) 315 { 316 $this->log('debug', $message, $context); 317 } 318 319 /** 320 * @param string $level 321 * @param string $message 322 * @param array $context 323 */ 324 public function log($level, $message, array $context = array()) 325 { 326 // is this log level wanted? 327 if (!isset($this->loglevel[$level])) return; 328 329 /** @var string $prefix */ 330 /** @var string $color */ 331 /** @var resource $channel */ 332 list($prefix, $color, $channel) = $this->loglevel[$level]; 333 if (!$this->colors->isEnabled()) $prefix = ''; 334 335 $message = $this->interpolate($message, $context); 336 $this->colors->ptln($prefix . $message, $color, $channel); 337 } 338 339 /** 340 * Interpolates context values into the message placeholders. 341 * 342 * @param $message 343 * @param array $context 344 * @return string 345 */ 346 function interpolate($message, array $context = array()) 347 { 348 // build a replacement array with braces around the context keys 349 $replace = array(); 350 foreach ($context as $key => $val) { 351 // check that the value can be casted to string 352 if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) { 353 $replace['{' . $key . '}'] = $val; 354 } 355 } 356 357 // interpolate replacement values into the message and return 358 return strtr($message, $replace); 359 } 360 361 // endregion 362 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body