[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/vendor/splitbrain/php-cli/src/ -> CLI.php (source)

   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  }