Completed
Push — feature/logger ( 1c2c4d )
by Todd
02:23
created

LogFormatter::fullMessageStr()   B

Complexity

Conditions 6
Paths 24

Size

Total Lines 24
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 0
loc 24
ccs 18
cts 18
cp 1
rs 8.5126
cc 6
eloc 15
nc 24
nop 4
crap 6
1
<?php
2
/**
3
 * @author Todd Burry <[email protected]>
4
 * @copyright 2009-2015 Vanilla Forums Inc.
5
 * @license MIT
6
 */
7
8
namespace Garden\Cli;
9
10
/**
11
 * A helper class to format CLI output in a log-style format.
12
 */
13
class LogFormatter {
14
    /**
15
     * @var string The date format as passed to {@link strftime()}.
16
     */
17
    protected $dateFormat = '[%F %R]';
18
19
    /**
20
     * @var string The end of line string to use.
21
     */
22
    protected $eol = PHP_EOL;
23
24
    /**
25
     * @var bool Whether or not to format output.
26
     */
27
    protected $formatOutput;
28
29
    /**
30
     * @var bool Whether or not the console is on a new line.
31
     */
32
    protected $isNewline = true;
33
34
    /**
35
     * @var int The maximum level deep to output.
36
     */
37
    protected $maxLevel = 2;
38
39
    /**
40
     * @var array An array of currently running tasks.
41
     */
42
    protected $taskStack = [];
43
44
    /**
45
     * LogFormatter constructor.
46
     */
47 9
    public function __construct() {
48 9
        $this->formatOutput = $this->guessFormatOutput();
49 9
    }
50
51
    /**
52
     * Output an error message.
53
     *
54
     * When formatting is turned on, error messages are displayed in red. Error messages are always output, even if they
55
     * are past the maximum display level.
56
     *
57
     * @param string $str The message to output.
58
     * @return LogFormatter Returns `$this` for fluent calls.
59
     */
60 2
    public function error($str) {
61 2
        return $this->message($this->formatString($str, ["\033[1;31m", "\033[0m"]), true);
62
    }
63
64
    /**
65
     * Output a success message.
66
     *
67
     * When formatting is turned on, success messages are displayed in green.
68
     *
69
     * @param string $str The message to output.
70
     * @return LogFormatter Returns `$this` for fluent calls.
71
     */
72
    public function success($str) {
73
        return $this->message($this->formatString($str, ["\033[1;32m", "\033[0m"]));
74
    }
75
76
    /**
77
     * Get the current depth of tasks.
78
     *
79
     * @return int Returns the current level.
80
     */
81 9
    protected function currentLevel() {
82 9
        return count($this->taskStack) + 1;
83
    }
84
85
    /**
86
     * Output a message that designates the beginning of a task.
87
     *
88
     * @param string $str The message to output.
89
     * @return $this Returns `$this` for fluent calls.
90
     */
91 7
    public function begin($str) {
92 7
        $output = $this->currentLevel() <= $this->getMaxLevel();
93 7
        $task = [$str, microtime(true), $output];
94
95 7
        if ($output) {
96 7
            if (!$this->isNewline) {
97 2
                echo $this->getEol();
98 2
                $this->isNewline = true;
99 2
            }
100
101 7
            echo $this->messageStr($str, false);
102 7
            $this->isNewline = false;
103 7
        }
104
105 7
        array_push($this->taskStack, $task);
106
107 7
        return $this;
108
    }
109
110
    /**
111
     * Output a message that designates a task being completed.
112
     *
113
     * @param string $str The message to output.
114
     * @param bool $force Whether or not to always output the message even if the task is past the max depth.
115
     * @return $this Returns `$this` for fluent calls.
116
     */
117 7
    public function end($str, $force = false) {
118
        // Find the task we are finishing.
119 7
        $task = array_pop($this->taskStack);
120 7
        if ($task !== null) {
121 7
            list($taskStr, $taskTimestamp, $taskOutput) = $task;
122 7
            $timespan = microtime(true) - $taskTimestamp;
0 ignored issues
show
Unused Code introduced by
$timespan is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
123 7
        } else {
124
            trigger_error('Called LogFormatter::end() without calling LogFormatter::begin()', E_USER_NOTICE);
125
        }
126
127 7
        $pastMaxLevel = $this->currentLevel() > $this->getMaxLevel();
128 7
        if ($pastMaxLevel) {
129 3
            if ($force && isset($taskStr)) {
130 1
                if (!$taskOutput) {
0 ignored issues
show
Bug introduced by
The variable $taskOutput does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
131
                    // Output the full task string if it hasn't already been output.
132 1
                    $str = $taskStr.' '.$str;
133 1
                }
134 1
                if (!$this->isNewline) {
135 1
                    echo $this->getEol();
136 1
                    $this->isNewline = true;
137 1
                }
138 1
            } else {
139 2
                return $this;
140
            }
141 1
        }
142
143 7
        if ($this->isNewline) {
144
            // Output the end message as a normal message.
145 5
            $this->message($str, $force);
146 5
        } else {
147
            // Output the end message on the same line.
148 3
            echo ' '.$str.$this->getEol();
149 3
            $this->isNewline = true;
150
        }
151
152 7
        return $this;
153
    }
154
155
    /**
156
     * Output a message that represents a task being completed in success.
157
     *
158
     * When formatting is turned on, success messages are output in green.
159
     *
160
     * @param string $str The message to output.
161
     * @return LogFormatter Returns `$this` for fluent calls.
162
     */
163
    public function endSuccess($str) {
164
        return $this->end($this->formatString($str, ["\033[1;32m", "\033[0m"]));
165
    }
166
167
    /**
168
     * Output a message that represents a task being completed in an error.
169
     *
170
     * When formatting is turned on, error messages are output in red. Error messages are always output even if they are
171
     * past the maximum depth.
172
     *
173
     * @param string $str The message to output.
174
     * @return LogFormatter Returns `$this` for fluent calls.
175
     */
176 1
    public function endError($str) {
177 1
        return $this->end($this->formatString($str, ["\033[1;31m", "\033[0m"]), true);
178
    }
179
180
    /**
181
     * Output a message that ends a task with an HTTP status code.
182
     *
183
     * 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
184
     * response code to this message.
185
     *
186
     * @param int $httpStatus The HTTP status code that represents the completion of a task.
187
     * @return $this Returns `$this` for fluent calls.
188
     * @see LogFormatter::endSuccess(), LogFormatter::endError().
189
     */
190
    public function endHttpStatus($httpStatus) {
191
        $statusStr = sprintf('%03d', $httpStatus);
192
193
        if ($httpStatus == 0 || $httpStatus >= 400) {
194
            $this->endError($statusStr);
195
        } elseif ($httpStatus <= 200) {
196
            $this->endSuccess($statusStr);
197
        } else {
198
            $this->message($statusStr);
199
        }
200
201
        return $this;
202
    }
203
204
    /**
205
     * Output a message.
206
     *
207
     * @param string $str The message to output.
208
     * @param bool $force Whether or not to force output of the message even if it's past the max depth.
209
     * @return $this Returns `$this` for fluent calls.
210
     */
211 8
    public function message($str, $force = false) {
212 8
        $pastMaxLevel = $this->currentLevel() > $this->getMaxLevel();
213
214 8
        if ($pastMaxLevel) {
215 5
            if ($force) {
216
                // Trace down the task list and output everything that hasn't already been output.
217 3
                foreach ($this->taskStack as $indent => $task) {
218 3
                    list($taskStr, $taskTimestamp, $taskOutput) = $this->taskStack[$indent];
219 3
                    if (!$taskOutput) {
220 1
                        if (!$this->isNewline) {
221 1
                            echo $this->eol;
222 1
                            $this->isNewline = true;
223 1
                        }
224 1
                        echo $this->fullMessageStr($taskTimestamp, $taskStr, $indent, true);
225 1
                        $this->taskStack[$indent][2] = true;
226 1
                    } else {
227 3
                        continue;
228
                    }
229 3
                }
230 3
            } else {
231 5
                return $this;
232
            }
233 3
        }
234
235 7
        if (!$this->isNewline) {
236 2
            echo $this->eol;
237 2
            $this->isNewline = true;
238 2
        }
239 7
        echo $this->messageStr($str, true);
240 7
        return $this;
241
    }
242
243
    /**
244
     * Get the format.
245
     *
246
     * @return boolean Returns the format.
247
     */
248
    public function getFormatOutput() {
249
        return $this->formatOutput;
250
    }
251
252
    /**
253
     * Set the format.
254
     *
255
     * @param boolean $formatOutput
256
     * @return LogFormatter Returns `$this` for fluent calls.
257
     */
258 9
    public function setFormatOutput($formatOutput) {
259 9
        $this->formatOutput = $formatOutput;
260 9
        return $this;
261
    }
262
263 9
    protected function fullMessageStr($timestamp, $str, $indent = null, $eol = true) {
264 9
        if ($indent === null) {
265 9
            $indent = $this->currentLevel() - 1;
266 9
        }
267
268 9
        if ($indent <= 0) {
269 9
            $indentStr = '';
270 9
        } elseif ($indent === 1) {
271 5
            $indentStr = '- ';
272 5
        } else {
273 2
            $indentStr = str_repeat('  ', $indent - 1).'- ';
274
        }
275
276 9
        $result = $indentStr.$str;
277
278 9
        if ($this->getDateFormat()) {
279 8
            $result = strftime($this->getDateFormat(), $timestamp).' '.$result;
280 8
        }
281
282 9
        if ($eol) {
283 7
            $result .= $this->eol;
284 7
        }
285 9
        return $result;
286
    }
287
288 9
    protected function messageStr($str, $eol = true) {
289 9
        return $this->fullMessageStr(time(), $str, null, $eol);
290
    }
291
292
    /**
293
     * Format some text for the console.
294
     *
295
     * @param string $text The text to format.
296
     * @param array $wrap The format to wrap in the form ['before', 'after'].
297
     * @return string Returns the string formatted according to {@link Cli::$format}.
298
     */
299 3
    protected function formatString($text, array $wrap) {
300 3
        if ($this->formatOutput) {
301
            return "{$wrap[0]}$text{$wrap[1]}";
302
        } else {
303 3
            return $text;
304
        }
305
    }
306
307 9
    public function guessFormatOutput() {
308 9
        if (defined('PHP_WINDOWS_VERSION_MAJOR')) {
309
            return false;
310 9
        } elseif (function_exists('posix_isatty')) {
311 9
            return posix_isatty(STDOUT);
312
        } else {
313
            return true;
314
        }
315
    }
316
317
    /**
318
     * Get the maxLevel.
319
     *
320
     * @return int Returns the maxLevel.
321
     */
322 9
    public function getMaxLevel() {
323 9
        return $this->maxLevel;
324
    }
325
326
    /**
327
     * @param int $maxLevel
328
     * @return LogFormatter
329
     */
330 9
    public function setMaxLevel($maxLevel) {
331 9
        if ($maxLevel < 0) {
332
            throw new \InvalidArgumentException("The max level must be greater than zero.", 416);
333
        }
334
335 9
        $this->maxLevel = $maxLevel;
336 9
        return $this;
337
    }
338
339
    /**
340
     * Get the date format as passed to {@link strftime()}.
341
     *
342
     * @return string Returns the date format.
343
     * @see strftime()
344
     */
345 9
    public function getDateFormat() {
346 9
        return $this->dateFormat;
347
    }
348
349
    /**
350
     * Set the date format as passed to {@link strftime()}.
351
     *
352
     * @param string $dateFormat
353
     * @return LogFormatter Returns `$this` for fluent calls.
354
     * @see strftime()
355
     */
356 9
    public function setDateFormat($dateFormat) {
357 9
        $this->dateFormat = $dateFormat;
358 9
        return $this;
359
    }
360
361
    /**
362
     * Get the end of line string to use.
363
     *
364
     * @return string Returns the eol string.
365
     */
366 5
    public function getEol() {
367 5
        return $this->eol;
368
    }
369
370
    /**
371
     * Set the end of line string.
372
     *
373
     * @param string $eol The end of line string to use.
374
     * @return LogFormatter Returns `$this` for fluent calls.
375
     */
376 9
    public function setEol($eol) {
377 9
        $this->eol = $eol;
378 9
        return $this;
379
    }
380
}
381