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'); |
|
|
|
|
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
|
|
|
|
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 theid
property of an instance of theAccount
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.