LogFormatter::getFormatOutput()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
nc 1
nop 0
dl 0
loc 2
ccs 0
cts 2
cp 0
crap 2
rs 10
c 0
b 0
f 0
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
 * @deprecated
14
 */
15
class LogFormatter {
16
    /**
17
     * @var string The date format as passed to {@link strftime()}.
18
     */
19
    protected $dateFormat = '[%F %T]';
20
21
    /**
22
     * @var string The end of line string to use.
23
     */
24
    protected $eol = PHP_EOL;
25
26
    /**
27
     * @var bool Whether or not to format output.
28
     */
29
    protected $formatOutput;
30
31
    /**
32
     * @var resource The output file handle.
33
     */
34
    protected $outputHandle;
35
36
    /**
37
     * @var bool Whether or not the console is on a new line.
38
     */
39
    protected $isNewline = true;
40
41
    /**
42
     * @var int The maximum level deep to output.
43
     */
44
    protected $maxLevel = 2;
45
46
    /**
47
     * @var bool Whether or not to show durations for tasks.
48
     */
49
    protected $showDurations = true;
50
51
    /**
52
     * @var array An array of currently running tasks.
53
     */
54
    protected $taskStack = [];
55
56
    /**
57
     * LogFormatter constructor.
58
     */
59 13
    public function __construct() {
60 13
        $this->formatOutput = Cli::guessFormatOutput();
61 13
        $this->outputHandle = fopen('php://output', 'w');
0 ignored issues
show
Documentation Bug introduced by
It seems like fopen('php://output', 'w') can also be of type false. However, the property $outputHandle is declared as type resource. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

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