PlainTextHandler   A
last analyzed

Complexity

Total Complexity 36

Size/Duplication

Total Lines 340
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 4

Test Coverage

Coverage 83.33%

Importance

Changes 0
Metric Value
wmc 36
lcom 1
cbo 4
dl 0
loc 340
ccs 105
cts 126
cp 0.8333
rs 9.52
c 0
b 0
f 0

18 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A setLogger() 0 13 3
A getLogger() 0 4 1
A setDumper() 0 5 1
A addTraceToOutput() 0 9 2
A addPreviousToOutput() 0 9 2
A addTraceFunctionArgsToOutput() 0 13 3
A setTraceFunctionArgsOutputLimit() 0 5 1
A generateResponse() 0 16 3
A getTraceFunctionArgsOutputLimit() 0 4 1
A loggerOnly() 0 9 2
A canOutput() 0 4 1
A getFrameArgsOutput() 0 26 4
A dump() 0 8 2
A getTraceOutput() 0 36 4
A getExceptionOutput() 0 10 1
A handle() 0 16 3
A contentType() 0 4 1
1
<?php
2
/**
3
* Whoops - php errors for cool kids
4
* @author Filipe Dobreira <http://github.com/filp>
5
* Plaintext handler for command line and logs.
6
* @author Pierre-Yves Landuré <https://howto.biapy.com/>
7
*/
8
9
namespace Whoops\Handler;
10
11
use InvalidArgumentException;
12
use Psr\Log\LoggerInterface;
13
use Whoops\Exception\Frame;
14
15
/**
16
* Handler outputing plaintext error messages. Can be used
17
* directly, or will be instantiated automagically by Whoops\Run
18
* if passed to Run::pushHandler
19
*/
20
class PlainTextHandler extends Handler
21
{
22
    const VAR_DUMP_PREFIX = '   | ';
23
24
    /**
25
     * @var \Psr\Log\LoggerInterface
26
     */
27
    protected $logger;
28
29
    /**
30
     * @var callable
31
     */
32
    protected $dumper;
33
34
    /**
35
     * @var bool
36
     */
37
    private $addTraceToOutput = true;
38
39
    /**
40
     * @var bool|integer
41
     */
42
    private $addTraceFunctionArgsToOutput = false;
43
44
    /**
45
     * @var integer
46
     */
47
    private $traceFunctionArgsOutputLimit = 1024;
48
49
    /**
50
     * @var bool
51
     */
52
    private $addPreviousToOutput = true;
53
54
    /**
55
     * @var bool
56
     */
57
    private $loggerOnly = false;
58
59
    /**
60
     * Constructor.
61
     * @throws InvalidArgumentException     If argument is not null or a LoggerInterface
62
     * @param  \Psr\Log\LoggerInterface|null $logger
63
     */
64 3
    public function __construct($logger = null)
65
    {
66 3
        $this->setLogger($logger);
67 2
    }
68
69
    /**
70
     * Set the output logger interface.
71
     * @throws InvalidArgumentException     If argument is not null or a LoggerInterface
72
     * @param  \Psr\Log\LoggerInterface|null $logger
73
     */
74 4
    public function setLogger($logger = null)
75
    {
76 4
        if (! (is_null($logger)
77 4
            || $logger instanceof LoggerInterface)) {
78 2
            throw new InvalidArgumentException(
79 2
                'Argument to ' . __METHOD__ .
80 2
                " must be a valid Logger Interface (aka. Monolog), " .
81 2
                get_class($logger) . ' given.'
82 2
            );
83
        }
84
85 3
        $this->logger = $logger;
86 3
    }
87
88
    /**
89
     * @return \Psr\Log\LoggerInterface|null
90
     */
91 2
    public function getLogger()
92
    {
93 2
        return $this->logger;
94
    }
95
96
    /**
97
     * Set var dumper callback function.
98
     *
99
     * @param  callable $dumper
100
     * @return static
101
     */
102
    public function setDumper(callable $dumper)
103
    {
104
        $this->dumper = $dumper;
105
        return $this;
106
    }
107
108
    /**
109
     * Add error trace to output.
110
     * @param  bool|null  $addTraceToOutput
111
     * @return bool|static
112
     */
113 6
    public function addTraceToOutput($addTraceToOutput = null)
114
    {
115 6
        if (func_num_args() == 0) {
116 6
            return $this->addTraceToOutput;
117
        }
118
119 6
        $this->addTraceToOutput = (bool) $addTraceToOutput;
120 6
        return $this;
121
    }
122
123
    /**
124
     * Add previous exceptions to output.
125
     * @param  bool|null $addPreviousToOutput
126
     * @return bool|static
127
     */
128 2
    public function addPreviousToOutput($addPreviousToOutput = null)
129
    {
130 2
        if (func_num_args() == 0) {
131
            return $this->addPreviousToOutput;
132
        }
133
134 2
        $this->addPreviousToOutput = (bool) $addPreviousToOutput;
135 2
        return $this;
136
    }
137
138
    /**
139
     * Add error trace function arguments to output.
140
     * Set to True for all frame args, or integer for the n first frame args.
141
     * @param  bool|integer|null $addTraceFunctionArgsToOutput
142
     * @return static|bool|integer
143
     */
144 4
    public function addTraceFunctionArgsToOutput($addTraceFunctionArgsToOutput = null)
145
    {
146 4
        if (func_num_args() == 0) {
147 2
            return $this->addTraceFunctionArgsToOutput;
148
        }
149
150 4
        if (! is_integer($addTraceFunctionArgsToOutput)) {
151 3
            $this->addTraceFunctionArgsToOutput = (bool) $addTraceFunctionArgsToOutput;
152 3
        } else {
153 2
            $this->addTraceFunctionArgsToOutput = $addTraceFunctionArgsToOutput;
154
        }
155 4
        return $this;
156
    }
157
158
    /**
159
     * Set the size limit in bytes of frame arguments var_dump output.
160
     * If the limit is reached, the var_dump output is discarded.
161
     * Prevent memory limit errors.
162
     * @var integer
163
     * @return static
164
     */
165 3
    public function setTraceFunctionArgsOutputLimit($traceFunctionArgsOutputLimit)
166
    {
167 3
        $this->traceFunctionArgsOutputLimit = (integer) $traceFunctionArgsOutputLimit;
168 3
        return $this;
169
    }
170
171
    /**
172
     * Create plain text response and return it as a string
173
     * @return string
174
     */
175 2
    public function generateResponse()
176
    {
177 2
        $exception = $this->getException();
178 2
        $message = $this->getExceptionOutput($exception);
179
180 2
        if ($this->addPreviousToOutput) {
181 1
            $previous = $exception->getPrevious();
182 1
            while ($previous) {
183 1
                $message .= "\n\nCaused by\n" . $this->getExceptionOutput($previous);
184 1
                $previous = $previous->getPrevious();
185 1
            }
186 1
        }
187
188
189 2
        return $message . $this->getTraceOutput() . "\n";
190
    }
191
192
    /**
193
     * Get the size limit in bytes of frame arguments var_dump output.
194
     * If the limit is reached, the var_dump output is discarded.
195
     * Prevent memory limit errors.
196
     * @return integer
197
     */
198 1
    public function getTraceFunctionArgsOutputLimit()
199
    {
200 1
        return $this->traceFunctionArgsOutputLimit;
201
    }
202
203
    /**
204
     * Only output to logger.
205
     * @param  bool|null $loggerOnly
206
     * @return static|bool
207
     */
208 3
    public function loggerOnly($loggerOnly = null)
209
    {
210 3
        if (func_num_args() == 0) {
211 3
            return $this->loggerOnly;
212
        }
213
214 3
        $this->loggerOnly = (bool) $loggerOnly;
215 3
        return $this;
216
    }
217
218
    /**
219
     * Test if handler can output to stdout.
220
     * @return bool
221
     */
222 4
    private function canOutput()
223
    {
224 4
        return !$this->loggerOnly();
225
    }
226
227
    /**
228
     * Get the frame args var_dump.
229
     * @param  \Whoops\Exception\Frame $frame [description]
230
     * @param  integer                 $line  [description]
231
     * @return string
232
     */
233 1
    private function getFrameArgsOutput(Frame $frame, $line)
234
    {
235 1
        if ($this->addTraceFunctionArgsToOutput() === false
236 1
            || $this->addTraceFunctionArgsToOutput() < $line) {
237 1
            return '';
238
        }
239
240
        // Dump the arguments:
241 1
        ob_start();
242 1
        $this->dump($frame->getArgs());
243 1
        if (ob_get_length() > $this->getTraceFunctionArgsOutputLimit()) {
244
            // The argument var_dump is to big.
245
            // Discarded to limit memory usage.
246
            ob_clean();
247
            return sprintf(
248
                "\n%sArguments dump length greater than %d Bytes. Discarded.",
249
                self::VAR_DUMP_PREFIX,
250
                $this->getTraceFunctionArgsOutputLimit()
251
            );
252
        }
253
254 1
        return sprintf(
255 1
            "\n%s",
256 1
            preg_replace('/^/m', self::VAR_DUMP_PREFIX, ob_get_clean())
257 1
        );
258
    }
259
260
    /**
261
     * Dump variable.
262
     *
263
     * @param mixed $var
264
     * @return void
265
     */
266
    protected function dump($var)
267
    {
268
        if ($this->dumper) {
269
            call_user_func($this->dumper, $var);
270
        } else {
271
            var_dump($var);
0 ignored issues
show
Security Debugging Code introduced by
var_dump($var); looks like debug code. Are you sure you do not want to remove it? This might expose sensitive data.
Loading history...
272
        }
273
    }
274
275
    /**
276
     * Get the exception trace as plain text.
277
     * @return string
278
     */
279 4
    private function getTraceOutput()
280
    {
281 4
        if (! $this->addTraceToOutput()) {
282 2
            return '';
283
        }
284 2
        $inspector = $this->getInspector();
285 2
        $frames = $inspector->getFrames();
286
287 2
        $response = "\nStack trace:";
288
289 2
        $line = 1;
290 2
        foreach ($frames as $frame) {
291
            /** @var Frame $frame */
292 2
            $class = $frame->getClass();
293
294 2
            $template = "\n%3d. %s->%s() %s:%d%s";
295 2
            if (! $class) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $class of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
296
                // Remove method arrow (->) from output.
297
                $template = "\n%3d. %s%s() %s:%d%s";
298
            }
299
300 2
            $response .= sprintf(
301 2
                $template,
302 2
                $line,
303 2
                $class,
304 2
                $frame->getFunction(),
305 2
                $frame->getFile(),
306 2
                $frame->getLine(),
307 2
                $this->getFrameArgsOutput($frame, $line)
308 2
            );
309
310 2
            $line++;
311 2
        }
312
313 2
        return $response;
314
    }
315
316
    /**
317
     * Get the exception as plain text.
318
     * @param \Throwable $exception
319
     * @return string
320
     */
321 2
    private function getExceptionOutput($exception)
322
    {
323 2
        return sprintf(
324 2
            "%s: %s in file %s on line %d",
325 2
            get_class($exception),
326 2
            $exception->getMessage(),
327 2
            $exception->getFile(),
328 2
            $exception->getLine()
329 2
        );
330
    }
331
332
    /**
333
     * @return int
334
     */
335 5
    public function handle()
336
    {
337 5
        $response = $this->generateResponse();
338
339 5
        if ($this->getLogger()) {
340
            $this->getLogger()->error($response);
341
        }
342
343 5
        if (! $this->canOutput()) {
344
            return Handler::DONE;
345
        }
346
347 5
        echo $response;
348
349 5
        return Handler::QUIT;
350
    }
351
352
    /**
353
     * @return string
354
     */
355 2
    public function contentType()
356
    {
357 2
        return 'text/plain';
358
    }
359
}
360