Completed
Push — feature/logger ( 7bcbf9...bd5b6f )
by
unknown
01:54
created

LogFormatter::end()   C

Complexity

Conditions 11
Paths 42

Size

Total Lines 41
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 11.0113

Importance

Changes 0
Metric Value
dl 0
loc 41
ccs 21
cts 22
cp 0.9545
rs 5.2653
c 0
b 0
f 0
cc 11
eloc 25
nc 42
nop 3
crap 11.0113

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2018 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Cli;
9
10
use Garden\Cli\Logger\LoggerInterface;
11
use Garden\Cli\Logger\LogLevels;
12
13
/**
14
 * A helper class to format CLI output in a log-style format.
15
 */
16
class LogFormatter implements LoggerInterface {
17
    /**
18
     * @var string The date format as passed to {@link strftime()}.
19
     */
20
    protected $dateFormat = '[%F %T]';
21
22
    /**
23
     * @var string The end of line string to use.
24
     */
25
    protected $eol = PHP_EOL;
26
27
    /**
28
     * @var bool Whether or not to format output.
29
     */
30
    protected $formatOutput;
31
32
    /**
33
     * @var resource The output file handle.
34
     */
35
    protected $outputHandle;
36
37
    /**
38
     * @var bool Whether or not the console is on a new line.
39
     */
40
    protected $isNewline = true;
41
42
    /**
43
     * @var int The maximum level deep to output.
44
     */
45
    protected $maxLevel = 2;
46
47
    /**
48
     * @var bool Whether or not to show durations for tasks.
49
     */
50
    protected $showDurations = true;
51
52
    /**
53
     * @var array An array of currently running tasks.
54
     */
55
    protected $taskStack = [];
56
57
    /**
58
     * LogFormatter constructor.
59
     */
60 13
    public function __construct() {
61 13
        $this->formatOutput = Cli::guessFormatOutput();
62 13
        $this->outputHandle = fopen('php://output', 'w');
63 13
    }
64
65
    /**
66
     * Output an error message.
67
     *
68
     * When formatting is turned on, error messages are displayed in red. Error messages are always output, even if they
69
     * are past the maximum display level.
70
     *
71
     * @param string $str The message to output.
72
     * @return $this
73
     */
74 3
    public function error(string $str) {
75 3
        return $this->message($this->formatString($str, ["\033[1;31m", "\033[0m"]), true);
76
    }
77
78
    /**
79
     * Output a success message.
80
     *
81
     * When formatting is turned on, success messages are displayed in green.
82
     *
83
     * @param string $str The message to output.
84
     * @return $this
85
     */
86 1
    public function success(string $str) {
87 1
        return $this->message($this->formatString($str, ["\033[1;32m", "\033[0m"]));
88
    }
89
90
    /**
91
     * Output a warning message.
92
     *
93
     * When formatting is turned on, warning messages are displayed in yellow.
94
     *
95
     * @param string $str The message to output.
96
     * @return LogFormatter Returns `$this` for fluent calls.
97
     */
98
    public function warn(string $str) {
99
        return $this->message($this->formatString($str, ["\033[1;33m", "\033[0m"]));
100
    }
101
102
    /**
103
     * Get the current depth of tasks.
104
     *
105
     * @return int Returns the current level.
106
     */
107 11
    protected function currentLevel() {
108 11
        return count($this->taskStack) + 1;
109
    }
110
111
    /**
112
     * Output a message that designates the beginning of a task.
113
     *
114
     * @param string $str The message to output.
115
     * @return $this Returns `$this` for fluent calls.
116
     */
117 8
    public function begin(string $str) {
118 8
        $output = $this->currentLevel() <= $this->getMaxLevel();
119 8
        $task = [$str, microtime(true), $output];
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 3 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
120
121 8
        if ($output) {
122 8
            if (!$this->isNewline) {
123 2
                $this->write($this->getEol());
124 2
                $this->isNewline = true;
125
            }
126
127 8
            $this->write($this->messageStr($str, false));
128 8
            $this->isNewline = false;
129
        }
130
131 8
        array_push($this->taskStack, $task);
132
133 8
        return $this;
134
    }
135
136
    /**
137
     * Output a message that designates a task being completed.
138
     *
139
     * @param string $str The message to output.
140
     * @param bool $force Whether or not to always output the message even if the task is past the max depth.
141
     * @param string $logLevel An unused parameter to keep the interface happy.
142
     *
143
     * @return $this Returns `$this` for fluent calls.
144
     */
145 8
    public function end(string $str = '', bool $force = false, $logLevel = LogLevels::INFO) {
146
        // Find the task we are finishing.
147 8
        $task = array_pop($this->taskStack);
148 8
        if ($task !== null) {
149 8
            list($taskStr, $taskTimestamp, $taskOutput) = $task;
150 8
            $timespan = microtime(true) - $taskTimestamp;
0 ignored issues
show
Coding Style introduced by
Equals sign not aligned with surrounding assignments; expected 35 spaces but found 1 space

This check looks for multiple assignments in successive lines of code. It will report an issue if the operators are not in a straight line.

To visualize

$a = "a";
$ab = "ab";
$abc = "abc";

will produce issues in the first and second line, while this second example

$a   = "a";
$ab  = "ab";
$abc = "abc";

will produce no issues.

Loading history...
151
        } else {
152 1
            trigger_error('Called LogFormatter::end() without calling LogFormatter::begin()', E_USER_NOTICE);
153
        }
154
155 8
        $pastMaxLevel = $this->currentLevel() > $this->getMaxLevel();
156 8
        if ($pastMaxLevel) {
157 3
            if ($force && isset($taskStr) && isset($taskOutput)) {
158 1
                if (!$taskOutput) {
159
                    // Output the full task string if it hasn't already been output.
160 1
                    $str = trim($taskStr.' '.$str);
161
                }
162 1
                if (!$this->isNewline) {
163 1
                    $this->write($this->getEol());
164 1
                    $this->isNewline = true;
165
                }
166
            } else {
167 2
                return $this;
168
            }
169
        }
170
171 8
        if (!empty($timespan) && $this->getShowDurations()) {
172
            $str = trim($str.' '.$this->formatString($this->formatDuration($timespan), ["\033[1;34m", "\033[0m"]));
173
        }
174
175 8
        if ($this->isNewline) {
176
            // Output the end message as a normal message.
177 6
            $this->message($str, $force);
178
        } else {
179
            // Output the end message on the same line.
180 4
            $this->write(' '.$str.$this->getEol());
181 4
            $this->isNewline = true;
182
        }
183
184 8
        return $this;
185
    }
186
187
    /**
188
     * Output a message that represents a task being completed in success.
189
     *
190
     * When formatting is turned on, success messages are output in green.
191
     *
192
     * @param string $str The message to output.
193
     * @param bool $force Whether or not to force a message past the max level to be output.
194
     * @return $this
195
     */
196
    public function endSuccess(string $str, bool $force = false) {
197
        return $this->end($this->formatString($str, ["\033[1;32m", "\033[0m"]), $force);
198
    }
199
200
    /**
201
     * Output a message that represents a task being completed in an error.
202
     *
203
     * When formatting is turned on, error messages are output in red. Error messages are always output even if they are
204
     * past the maximum depth.
205
     *
206
     * @param string $str The message to output.
207
     * @return $this
208
     */
209 1
    public function endError(string $str) {
210 1
        return $this->end($this->formatString($str, ["\033[1;31m", "\033[0m"]), true);
211
    }
212
213
    /**
214
     * Output a message that ends a task with an HTTP status code.
215
     *
216
     * This method is useful if you are making a call to an external API as a task. You can end the task by passing the
217
     * response code to this message.
218
     *
219
     * @param int $httpStatus The HTTP status code that represents the completion of a task.
220
     * @param bool $force Whether or not to force message output.
221
     * @return $this Returns `$this` for fluent calls.
222
     * @see LogFormatter::endSuccess(), LogFormatter::endError().
223
     */
224
    public function endHttpStatus(int $httpStatus, bool $force = false) {
225
        $statusStr = sprintf('%03d', $httpStatus);
226
227
        if ($httpStatus == 0 || $httpStatus >= 400) {
228
            $this->endError($statusStr);
229
        } elseif ($httpStatus >= 200 && $httpStatus < 300) {
230
            $this->endSuccess($statusStr, $force);
231
        } else {
232
            $this->end($statusStr, $force);
233
        }
234
235
        return $this;
236
    }
237
238
    /**
239
     * Format a time duration.
240
     *
241
     * @param float $duration The duration in seconds and fractions of a second.
242
     * @return string Returns the duration formatted for humans.
243
     * @see microtime()
244
     */
245 1
    public function formatDuration(float $duration) {
246 1
        if ($duration < 1.0e-3) {
247 1
            $n = number_format($duration * 1.0e6, 0);
248 1
            $sx = 'μs';
249 1
        } elseif ($duration < 1) {
250 1
            $n = number_format($duration * 1000, 0);
251 1
            $sx = 'ms';
252 1
        } elseif ($duration < 60) {
253 1
            $n = number_format($duration, 1);
254 1
            $sx = 's';
255 1
        } elseif ($duration < 3600) {
256 1
            $n = number_format($duration / 60, 1);
257 1
            $sx = 'm';
258 1
        } elseif ($duration < 86400) {
259 1
            $n = number_format($duration / 3600, 1);
260 1
            $sx = 'h';
261
        } else {
262 1
            $n = number_format($duration / 86400, 1);
263 1
            $sx = 'd';
264
        }
265
266 1
        $result = rtrim($n, '0.').$sx;
267 1
        return $result;
268
    }
269
270
    /**
271
     * Output a message.
272
     *
273
     * @param string $str The message to output.
274
     * @param bool $force Whether or not to force output of the message even if it's past the max depth.
275
     * @return $this Returns `$this` for fluent calls.
276
     */
277 10
    public function message(string $str, bool $force = false) {
278 10
        $pastMaxLevel = $this->currentLevel() > $this->getMaxLevel();
279
280 10
        if ($pastMaxLevel) {
281 5
            if ($force) {
282
                // Trace down the task list and output everything that hasn't already been output.
283 3
                foreach ($this->taskStack as $indent => $task) {
284 3
                    list($taskStr, $taskTimestamp, $taskOutput) = $this->taskStack[$indent];
285 3
                    if (!$taskOutput) {
286 1
                        if (!$this->isNewline) {
287 1
                            $this->write($this->eol);
288 1
                            $this->isNewline = true;
289
                        }
290 1
                        $this->write($this->fullMessageStr($taskTimestamp, $taskStr, $indent, true));
291 1
                        $this->taskStack[$indent][2] = true;
292
                    } else {
293 3
                        continue;
294
                    }
295
                }
296
            } else {
297 5
                return $this;
298
            }
299
        }
300
301 9
        if (!$this->isNewline) {
302 2
            $this->write($this->eol);
303 2
            $this->isNewline = true;
304
        }
305 9
        $this->write($this->messageStr($str, true));
306 9
        return $this;
307
    }
308
309
    /**
310
     * Get whether or not output should be formatted.
311
     *
312
     * @return boolean Returns **true** if output should be formatted or **false** otherwise.
313
     */
314
    public function getFormatOutput() {
315
        return $this->formatOutput;
316
    }
317
318
    /**
319
     * Set whether or not output should be formatted.
320
     *
321
     * @param boolean $formatOutput Whether or not to format output.
322
     * @return $this
323
     */
324 11
    public function setFormatOutput(bool $formatOutput) {
325 11
        $this->formatOutput = $formatOutput;
326 11
        return $this;
327
    }
328
329 11
    protected function fullMessageStr($timestamp, $str, $indent = null, $eol = true) {
330 11
        if ($indent === null) {
331 11
            $indent = $this->currentLevel() - 1;
332
        }
333
334 11
        if ($indent <= 0) {
335 11
            $indentStr = '';
336 5
        } elseif ($indent === 1) {
337 5
            $indentStr = '- ';
338
        } else {
339 2
            $indentStr = str_repeat('  ', $indent - 1).'- ';
340
        }
341
342 11
        $result = $indentStr.$str;
343
344 11
        if ($this->getDateFormat()) {
345 9
            $result = strftime($this->getDateFormat(), $timestamp).' '.$result;
346
        }
347
348 11
        if ($eol) {
349 9
            $result .= $this->eol;
350
        }
351 11
        return $result;
352
    }
353
354
    /**
355
     * Create a message string.
356
     *
357
     * @param string $str The message to output.
358
     * @param bool $eol Whether or not to add an EOL.
359
     * @return string Returns the message.
360
     */
361 11
    protected function messageStr($str, $eol = true) {
362 11
        return $this->fullMessageStr(time(), $str, null, $eol);
363
    }
364
365
    /**
366
     * Format some text for the console.
367
     *
368
     * @param string $text The text to format.
369
     * @param string[] $wrap The format to wrap in the form ['before', 'after'].
370
     * @return string Returns the string formatted according to {@link Cli::$format}.
371
     */
372 4
    protected function formatString($text, array $wrap) {
373 4
        if ($this->formatOutput) {
374 1
            return "{$wrap[0]}$text{$wrap[1]}";
375
        } else {
376 3
            return $text;
377
        }
378
    }
379
380
    /**
381
     * Get the maxLevel.
382
     *
383
     * @return int Returns the maxLevel.
384
     */
385 11
    public function getMaxLevel() {
386 11
        return $this->maxLevel;
387
    }
388
389
    /**
390
     * @param int $maxLevel
391
     * @return LogFormatter
392
     */
393 12
    public function setMaxLevel(int $maxLevel) {
394 12
        if ($maxLevel < 0) {
395 1
            throw new \InvalidArgumentException("The max level must be greater than zero.", 416);
396
        }
397
398 11
        $this->maxLevel = $maxLevel;
399 11
        return $this;
400
    }
401
402
    /**
403
     * Get the date format as passed to {@link strftime()}.
404
     *
405
     * @return string Returns the date format.
406
     * @see strftime()
407
     */
408 11
    public function getDateFormat() {
409 11
        return $this->dateFormat;
410
    }
411
412
    /**
413
     * Set the date format as passed to {@link strftime()}.
414
     *
415
     * @param string $dateFormat
416
     * @return $this
417
     * @see strftime()
418
     */
419 11
    public function setDateFormat(string $dateFormat) {
420 11
        $this->dateFormat = $dateFormat;
421 11
        return $this;
422
    }
423
424
    /**
425
     * Get the end of line string to use.
426
     *
427
     * @return string Returns the eol string.
428
     */
429 6
    public function getEol() {
430 6
        return $this->eol;
431
    }
432
433
    /**
434
     * Set the end of line string.
435
     *
436
     * @param string $eol The end of line string to use.
437
     * @return $this
438
     */
439 11
    public function setEol($eol) {
440 11
        $this->eol = $eol;
441 11
        return $this;
442
    }
443
444
    /**
445
     * Get the showDurations.
446
     *
447
     * @return boolean Returns the showDurations.
448
     */
449 8
    public function getShowDurations() {
450 8
        return $this->showDurations;
451
    }
452
453
    /**
454
     * Set the showDurations.
455
     *
456
     * @param boolean $showDurations
457
     * @return $this
458
     */
459 11
    public function setShowDurations(bool $showDurations) {
460 11
        $this->showDurations = $showDurations;
461 11
        return $this;
462
    }
463
464
    /**
465
     * Set the output file handle.
466
     *
467
     * @param resource $handle
468
     * @return $this
469
     */
470
    public function setOutputHandle($handle) {
471
        if (feof($handle)) {
472
            throw new \InvalidArgumentException("The provided file handle must be open.", 416);
473
        }
474
        $this->outputHandle = $handle;
475
        return $this;
476
    }
477
478
    /**
479
     * Write a string to the CLI.
480
     *
481
     * This method is intended to centralize the echoing of output in case the class is subclassed and the behaviour
482
     * needs to change.
483
     *
484
     * @param string $str The string to write.
485
     */
486 11
    public function write($str) {
487 11
        if (feof($this->outputHandle)) {
488
            trigger_error('Called LogFormatter::write() but file handle was closed.', E_USER_WARNING);
489
            return;
490
        }
491 11
        fwrite($this->outputHandle, $str);
492 11
    }
493
}
494