Completed
Push — master ( bb6f11...96b540 )
by Denis
48:14 queued 46:35
created

PlainTextHandler::getExceptionOutput()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 10
ccs 0
cts 0
cp 0
rs 9.9332
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
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 1
    /**
60
     * Constructor.
61 1
     * @throws InvalidArgumentException     If argument is not null or a LoggerInterface
62
     * @param  \Psr\Log\LoggerInterface|null $logger
63
     */
64
    public function __construct($logger = null)
65
    {
66
        $this->setLogger($logger);
67
    }
68
69 2
    /**
70
     * Set the output logger interface.
71 2
     * @throws InvalidArgumentException     If argument is not null or a LoggerInterface
72 2
     * @param  \Psr\Log\LoggerInterface|null $logger
73 2
     */
74 2
    public function setLogger($logger = null)
75 2
    {
76 2
        if (! (is_null($logger)
77 2
            || $logger instanceof LoggerInterface)) {
78
            throw new InvalidArgumentException(
79
                'Argument to ' . __METHOD__ .
80 1
                " must be a valid Logger Interface (aka. Monolog), " .
81 1
                get_class($logger) . ' given.'
82
            );
83
        }
84
85
        $this->logger = $logger;
86
    }
87
88
    /**
89
     * @return \Psr\Log\LoggerInterface|null
90
     */
91
    public function getLogger()
92
    {
93
        return $this->logger;
94
    }
95
96
    /**
97
     * Set var dumper callback function.
98
     *
99
     * @param  callable $dumper
100
     * @return void
101
     */
102
    public function setDumper(callable $dumper)
103
    {
104
        $this->dumper = $dumper;
105
    }
106
107 4
    /**
108
     * Add error trace to output.
109 4
     * @param  bool|null  $addTraceToOutput
110 4
     * @return bool|$this
111
     */
112
    public function addTraceToOutput($addTraceToOutput = null)
113 4
    {
114 4
        if (func_num_args() == 0) {
115
            return $this->addTraceToOutput;
116
        }
117
118
        $this->addTraceToOutput = (bool) $addTraceToOutput;
119
        return $this;
120
    }
121
122
    /**
123 2
     * Add previous exceptions to output.
124
     * @param  bool|null $addPreviousToOutput
125 2
     * @return bool|$this
126 2
     */
127
    public function addPreviousToOutput($addPreviousToOutput = null)
128
    {
129 2
        if (func_num_args() == 0) {
130 1
            return $this->addPreviousToOutput;
131 1
        }
132 2
133
        $this->addPreviousToOutput = (bool) $addPreviousToOutput;
134 2
        return $this;
135
    }
136
137
    /**
138
     * Add error trace function arguments to output.
139
     * Set to True for all frame args, or integer for the n first frame args.
140
     * @param  bool|integer|null $addTraceFunctionArgsToOutput
141
     * @return null|bool|integer
142 1
     */
143
    public function addTraceFunctionArgsToOutput($addTraceFunctionArgsToOutput = null)
144 1
    {
145 1
        if (func_num_args() == 0) {
146
            return $this->addTraceFunctionArgsToOutput;
147
        }
148
149
        if (! is_integer($addTraceFunctionArgsToOutput)) {
150
            $this->addTraceFunctionArgsToOutput = (bool) $addTraceFunctionArgsToOutput;
151
        } else {
152
            $this->addTraceFunctionArgsToOutput = $addTraceFunctionArgsToOutput;
153
        }
154
    }
155
156
    /**
157
     * Set the size limit in bytes of frame arguments var_dump output.
158
     * If the limit is reached, the var_dump output is discarded.
159
     * Prevent memory limit errors.
160
     * @var integer
161
     */
162
    public function setTraceFunctionArgsOutputLimit($traceFunctionArgsOutputLimit)
163
    {
164
        $this->traceFunctionArgsOutputLimit = (integer) $traceFunctionArgsOutputLimit;
165
    }
166
167
    /**
168
     * Create plain text response and return it as a string
169
     * @return string
170 1
     */
171
    public function generateResponse()
172 1
    {
173
        $exception = $this->getException();
174
        $message = $this->getExceptionOutput($exception);
175
176
        if ($this->addPreviousToOutput) {
177
            $previous = $exception->getPrevious();
178
            while ($previous) {
179
                $message .= "\n\nCaused by\n" . $this->getExceptionOutput($previous);
180 1
                $previous = $previous->getPrevious();
181
            }
182 1
        }
183 1
184
185
        return $message . $this->getTraceOutput() . "\n";
186 1
    }
187 1
188
    /**
189
     * Get the size limit in bytes of frame arguments var_dump output.
190
     * If the limit is reached, the var_dump output is discarded.
191
     * Prevent memory limit errors.
192
     * @return integer
193 2
     */
194
    public function getTraceFunctionArgsOutputLimit()
195 2
    {
196
        return $this->traceFunctionArgsOutputLimit;
197
    }
198
199
    /**
200
     * Only output to logger.
201
     * @param  bool|null $loggerOnly
202
     * @return null|bool
203
     */
204 1
    public function loggerOnly($loggerOnly = null)
205
    {
206 1
        if (func_num_args() == 0) {
207 1
            return $this->loggerOnly;
208 1
        }
209
210
        $this->loggerOnly = (bool) $loggerOnly;
211
    }
212 1
213 1
    /**
214 1
     * Test if handler can output to stdout.
215
     * @return bool
216
     */
217
    private function canOutput()
218
    {
219
        return !$this->loggerOnly();
220
    }
221
222
    /**
223
     * Get the frame args var_dump.
224
     * @param  \Whoops\Exception\Frame $frame [description]
225 1
     * @param  integer                 $line  [description]
226 1
     * @return string
227 1
     */
228 1
    private function getFrameArgsOutput(Frame $frame, $line)
229
    {
230
        if ($this->addTraceFunctionArgsToOutput() === false
231
            || $this->addTraceFunctionArgsToOutput() < $line) {
232
            return '';
233
        }
234
235
        // Dump the arguments:
236
        ob_start();
237
        $this->dump($frame->getArgs());
238
        if (ob_get_length() > $this->getTraceFunctionArgsOutputLimit()) {
239
            // The argument var_dump is to big.
240
            // Discarded to limit memory usage.
241
            ob_clean();
242
            return sprintf(
243
                "\n%sArguments dump length greater than %d Bytes. Discarded.",
244
                self::VAR_DUMP_PREFIX,
245
                $this->getTraceFunctionArgsOutputLimit()
246
            );
247
        }
248
249
        return sprintf(
250 2
            "\n%s",
251
            preg_replace('/^/m', self::VAR_DUMP_PREFIX, ob_get_clean())
252 2
        );
253
    }
254
255 2
    /**
256 2
     * Dump variable.
257
     *
258 2
     * @param mixed $var
259
     * @return void
260 2
     */
261 2
    protected function dump($var)
262
    {
263 2
        if ($this->dumper) {
264
            call_user_func($this->dumper, $var);
265 2
        } else {
266 2
            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...
267
        }
268
    }
269
270
    /**
271 2
     * Get the exception trace as plain text.
272 2
     * @return string
273 2
     */
274 2
    private function getTraceOutput()
275 2
    {
276 2
        if (! $this->addTraceToOutput()) {
277 2
            return '';
278 2
        }
279 2
        $inspector = $this->getInspector();
280
        $frames = $inspector->getFrames();
281 2
282 2
        $response = "\nStack trace:";
283
284 2
        $line = 1;
285
        foreach ($frames as $frame) {
286
            /** @var Frame $frame */
287
            $class = $frame->getClass();
288
289
            $template = "\n%3d. %s->%s() %s:%d%s";
290 3
            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...
291
                // Remove method arrow (->) from output.
292 3
                $template = "\n%3d. %s%s() %s:%d%s";
293
            }
294 3
295
            $response .= sprintf(
296
                $template,
297
                $line,
298 3
                $class,
299
                $frame->getFunction(),
300
                $frame->getFile(),
301
                $frame->getLine(),
302 3
                $this->getFrameArgsOutput($frame, $line)
303
            );
304 3
305
            $line++;
306
        }
307
308
        return $response;
309
    }
310
311
    /**
312
     * Get the exception as plain text.
313
     * @param \Throwable $exception
314
     * @return string
315
     */
316
    private function getExceptionOutput($exception)
317
    {
318
        return sprintf(
319
            "%s: %s in file %s on line %d",
320
            get_class($exception),
321
            $exception->getMessage(),
322
            $exception->getFile(),
323
            $exception->getLine()
324
        );
325
    }
326
327
    /**
328
     * @return int
329
     */
330
    public function handle()
331
    {
332
        $response = $this->generateResponse();
333
334
        if ($this->getLogger()) {
335
            $this->getLogger()->error($response);
336
        }
337
338
        if (! $this->canOutput()) {
339
            return Handler::DONE;
340
        }
341
342
        echo $response;
343
344
        return Handler::QUIT;
345
    }
346
347
    /**
348
     * @return string
349
     */
350
    public function contentType()
351
    {
352
        return 'text/plain';
353
    }
354
}
355