Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 4747e73

Browse files
committed
Support old and new PSR-3 versions
New versions of PSR-3 have adjusted method signatures, making it incompatible with the old implementation. By splitting out the core functionality and implement different classes for logging with different method signatures, the library is now compatible with both versions. Alternative fix for #28
1 parent 8c2c001 commit 4747e73

File tree

5 files changed

+310
-247
lines changed

5 files changed

+310
-247
lines changed

README.md

+15-7
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,12 @@ for further info.
7575

7676
## Exceptions
7777

78-
By default the CLI class registers an exception handler and will print the exception's message to the end user and
78+
By default, the CLI class registers an exception handler and will print the exception's message to the end user and
7979
exit the programm with a non-zero exit code. You can disable this behaviour and catch all exceptions yourself by
8080
passing false to the constructor.
8181

8282
You can use the provided ``splitbrain\phpcli\Exception`` to signal any problems within your main code yourself. The
83-
exceptions's code will be used as the exit code then.
83+
exception's code will be used as the exit code then.
8484

8585
Stacktraces will be printed on log level `debug`.
8686

@@ -129,25 +129,33 @@ The table formatter is used for the automatic help screen accessible when callin
129129

130130
The CLI class is a fully PSR-3 compatible logger (printing colored log data to STDOUT and STDERR). This is useful when
131131
you call backend code from your CLI that expects a Logger instance to produce any sensible status output while running.
132-
133-
To use this ability simply inherit from `splitbrain\phpcli\PSR3CLI` instead of `splitbrain\phpcli\CLI`, then pass `$this`
134-
as the logger instance. Be sure you have the suggested `psr/log` composer package installed.
135132

136-
![Screenshot](screenshot2.png)
133+
If you need to pass a class implementing the `Psr\Log\LoggerInterface` you can do so by inheriting from one of the two provided classes implementing this interface instead of `splitbrain\phpcli\CLI`.
134+
135+
* Use `splitbrain\phpcli\PSR3CLI` if you're using version 2 of PSR3 (PHP < 8.0)
136+
* Use `splitbrain\phpcli\PSR3CLIv3` if you're using version 3 of PSR3 (PHP >= 8.0)
137+
138+
The resulting object then can be passed as the logger instance. The difference between the two is in adjusted method signatures (with appropriate type hinting) only. Be sure you have the suggested `psr/log` composer package installed when using these classes.
139+
140+
Note: if your backend code calls for a PSR-3 logger but does not actually type check for the interface (AKA being LoggerAware only) you can also just pass an instance of `splitbrain\phpcli\CLI`.
141+
142+
## Log Levels
137143

138144
You can adjust the verbosity of your CLI tool using the `--loglevel` parameter. Supported loglevels are the PSR-3
139145
loglevels and our own `success` level:
140146

141147
* debug
142148
* info
143149
* notice
144-
* success
150+
* success (this is not defined in PSR-3)
145151
* warning
146152
* error
147153
* critical
148154
* alert
149155
* emergency
150156

157+
![Screenshot](screenshot2.png)
158+
151159
Convenience methods for all log levels are available. Placeholder interpolation as described in PSR-3 is available, too.
152160
Messages from `warning` level onwards are printed to `STDERR` all below are printed to `STDOUT`.
153161

src/Base.php

+263
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,263 @@
1+
<?php
2+
3+
namespace splitbrain\phpcli;
4+
5+
/**
6+
* Class CLIBase
7+
*
8+
* All base functionality is implemented here.
9+
*
10+
* Your commandline should not inherit from this class, but from one of the *CLI* classes
11+
*
12+
* @author Andreas Gohr <[email protected]>
13+
* @license MIT
14+
*/
15+
abstract class Base
16+
{
17+
/** @var string the executed script itself */
18+
protected $bin;
19+
/** @var Options the option parser */
20+
protected $options;
21+
/** @var Colors */
22+
public $colors;
23+
24+
/** @var array PSR-3 compatible loglevels and their prefix, color, output channel */
25+
protected $loglevel = array(
26+
'debug' => array('', Colors::C_RESET, STDOUT),
27+
'info' => array('', Colors::C_CYAN, STDOUT),
28+
'notice' => array('', Colors::C_CYAN, STDOUT),
29+
'success' => array('', Colors::C_GREEN, STDOUT),
30+
'warning' => array('', Colors::C_BROWN, STDERR),
31+
'error' => array('', Colors::C_RED, STDERR),
32+
'critical' => array('', Colors::C_LIGHTRED, STDERR),
33+
'alert' => array('', Colors::C_LIGHTRED, STDERR),
34+
'emergency' => array('', Colors::C_LIGHTRED, STDERR),
35+
);
36+
37+
protected $logdefault = 'info';
38+
39+
/**
40+
* constructor
41+
*
42+
* Initialize the arguments, set up helper classes and set up the CLI environment
43+
*
44+
* @param bool $autocatch should exceptions be catched and handled automatically?
45+
*/
46+
public function __construct($autocatch = true)
47+
{
48+
if ($autocatch) {
49+
set_exception_handler(array($this, 'fatal'));
50+
}
51+
52+
$this->colors = new Colors();
53+
$this->options = new Options($this->colors);
54+
}
55+
56+
/**
57+
* Register options and arguments on the given $options object
58+
*
59+
* @param Options $options
60+
* @return void
61+
*
62+
* @throws Exception
63+
*/
64+
abstract protected function setup(Options $options);
65+
66+
/**
67+
* Your main program
68+
*
69+
* Arguments and options have been parsed when this is run
70+
*
71+
* @param Options $options
72+
* @return void
73+
*
74+
* @throws Exception
75+
*/
76+
abstract protected function main(Options $options);
77+
78+
/**
79+
* Execute the CLI program
80+
*
81+
* Executes the setup() routine, adds default options, initiate the options parsing and argument checking
82+
* and finally executes main() - Each part is split into their own protected function below, so behaviour
83+
* can easily be overwritten
84+
*
85+
* @throws Exception
86+
*/
87+
public function run()
88+
{
89+
if ('cli' != php_sapi_name()) {
90+
throw new Exception('This has to be run from the command line');
91+
}
92+
93+
$this->setup($this->options);
94+
$this->registerDefaultOptions();
95+
$this->parseOptions();
96+
$this->handleDefaultOptions();
97+
$this->setupLogging();
98+
$this->checkArgments();
99+
$this->execute();
100+
101+
exit(0);
102+
}
103+
104+
// region run handlers - for easier overriding
105+
106+
/**
107+
* Add the default help, color and log options
108+
*/
109+
protected function registerDefaultOptions()
110+
{
111+
$this->options->registerOption(
112+
'help',
113+
'Display this help screen and exit immediately.',
114+
'h'
115+
);
116+
$this->options->registerOption(
117+
'no-colors',
118+
'Do not use any colors in output. Useful when piping output to other tools or files.'
119+
);
120+
$this->options->registerOption(
121+
'loglevel',
122+
'Minimum level of messages to display. Default is ' . $this->colors->wrap($this->logdefault, Colors::C_CYAN) . '. ' .
123+
'Valid levels are: debug, info, notice, success, warning, error, critical, alert, emergency.',
124+
null,
125+
'level'
126+
);
127+
}
128+
129+
/**
130+
* Handle the default options
131+
*/
132+
protected function handleDefaultOptions()
133+
{
134+
if ($this->options->getOpt('no-colors')) {
135+
$this->colors->disable();
136+
}
137+
if ($this->options->getOpt('help')) {
138+
echo $this->options->help();
139+
exit(0);
140+
}
141+
}
142+
143+
/**
144+
* Handle the logging options
145+
*/
146+
protected function setupLogging()
147+
{
148+
$level = $this->options->getOpt('loglevel', $this->logdefault);
149+
if (!isset($this->loglevel[$level])) $this->fatal('Unknown log level');
150+
foreach (array_keys($this->loglevel) as $l) {
151+
if ($l == $level) break;
152+
unset($this->loglevel[$l]);
153+
}
154+
}
155+
156+
/**
157+
* Wrapper around the option parsing
158+
*/
159+
protected function parseOptions()
160+
{
161+
$this->options->parseOptions();
162+
}
163+
164+
/**
165+
* Wrapper around the argument checking
166+
*/
167+
protected function checkArgments()
168+
{
169+
$this->options->checkArguments();
170+
}
171+
172+
/**
173+
* Wrapper around main
174+
*/
175+
protected function execute()
176+
{
177+
$this->main($this->options);
178+
}
179+
180+
// endregion
181+
182+
// region logging
183+
184+
/**
185+
* Exits the program on a fatal error
186+
*
187+
* @param \Exception|string $error either an exception or an error message
188+
* @param array $context
189+
*/
190+
public function fatal($error, array $context = array())
191+
{
192+
$code = 0;
193+
if (is_object($error) && is_a($error, 'Exception')) {
194+
/** @var Exception $error */
195+
$this->logMessage('debug', get_class($error) . ' caught in ' . $error->getFile() . ':' . $error->getLine());
196+
$this->logMessage('debug', $error->getTraceAsString());
197+
$code = $error->getCode();
198+
$error = $error->getMessage();
199+
200+
}
201+
if (!$code) {
202+
$code = Exception::E_ANY;
203+
}
204+
205+
$this->logMessage('critical', $error, $context);
206+
exit($code);
207+
}
208+
209+
/**
210+
* Normal, positive outcome (This is not a PSR-3 level)
211+
*
212+
* @param string $string
213+
* @param array $context
214+
*/
215+
public function success($string, array $context = array())
216+
{
217+
$this->logMessage('success', $string, $context);
218+
}
219+
220+
/**
221+
* @param string $level
222+
* @param string $message
223+
* @param array $context
224+
*/
225+
protected function logMessage($level, $message, array $context = array())
226+
{
227+
// is this log level wanted?
228+
if (!isset($this->loglevel[$level])) return;
229+
230+
/** @var string $prefix */
231+
/** @var string $color */
232+
/** @var resource $channel */
233+
list($prefix, $color, $channel) = $this->loglevel[$level];
234+
if (!$this->colors->isEnabled()) $prefix = '';
235+
236+
$message = $this->interpolate($message, $context);
237+
$this->colors->ptln($prefix . $message, $color, $channel);
238+
}
239+
240+
/**
241+
* Interpolates context values into the message placeholders.
242+
*
243+
* @param $message
244+
* @param array $context
245+
* @return string
246+
*/
247+
protected function interpolate($message, array $context = array())
248+
{
249+
// build a replacement array with braces around the context keys
250+
$replace = array();
251+
foreach ($context as $key => $val) {
252+
// check that the value can be casted to string
253+
if (!is_array($val) && (!is_object($val) || method_exists($val, '__toString'))) {
254+
$replace['{' . $key . '}'] = $val;
255+
}
256+
}
257+
258+
// interpolate replacement values into the message and return
259+
return strtr((string)$message, $replace);
260+
}
261+
262+
// endregion
263+
}

0 commit comments

Comments
 (0)