Completed
Push — master ( 114a8f...51c2de )
by Denis
01:38
created

PlainTextHandler::setLogger()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 3

Importance

Changes 0
Metric Value
dl 0
loc 13
ccs 10
cts 10
cp 1
rs 9.8333
c 0
b 0
f 0
cc 3
nc 2
nop 1
crap 3
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 void
101
     */
102
    public function setDumper(callable $dumper)
103
    {
104
        $this->dumper = $dumper;
105
    }
106
107
    /**
108
     * Add error trace to output.
109
     * @param  bool|null  $addTraceToOutput
110
     * @return bool|$this
111
     */
112 6
    public function addTraceToOutput($addTraceToOutput = null)
113
    {
114 6
        if (func_num_args() == 0) {
115 6
            return $this->addTraceToOutput;
116
        }
117
118 6
        $this->addTraceToOutput = (bool) $addTraceToOutput;
119 6
        return $this;
120
    }
121
122
    /**
123
     * Add previous exceptions to output.
124
     * @param  bool|null $addPreviousToOutput
125
     * @return bool|$this
126
     */
127 2
    public function addPreviousToOutput($addPreviousToOutput = null)
128
    {
129 2
        if (func_num_args() == 0) {
130
            return $this->addPreviousToOutput;
131
        }
132
133 2
        $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
     */
143 4
    public function addTraceFunctionArgsToOutput($addTraceFunctionArgsToOutput = null)
144
    {
145 4
        if (func_num_args() == 0) {
146 2
            return $this->addTraceFunctionArgsToOutput;
147
        }
148
149 4
        if (! is_integer($addTraceFunctionArgsToOutput)) {
150 3
            $this->addTraceFunctionArgsToOutput = (bool) $addTraceFunctionArgsToOutput;
151 3
        } else {
152 2
            $this->addTraceFunctionArgsToOutput = $addTraceFunctionArgsToOutput;
153
        }
154 4
    }
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 3
    public function setTraceFunctionArgsOutputLimit($traceFunctionArgsOutputLimit)
163
    {
164 3
        $this->traceFunctionArgsOutputLimit = (integer) $traceFunctionArgsOutputLimit;
165 3
    }
166
167
    /**
168
     * Create plain text response and return it as a string
169
     * @return string
170
     */
171 2
    public function generateResponse()
172
    {
173 2
        $exception = $this->getException();
174 2
        $message = $this->getExceptionOutput($exception);
175
176 2
        if ($this->addPreviousToOutput) {
177 1
            $previous = $exception->getPrevious();
178 1
            while ($previous) {
179 1
                $message .= "\n\nCaused by\n" . $this->getExceptionOutput($previous);
180 1
                $previous = $previous->getPrevious();
181 1
            }
182 1
        }
183
184
185 2
        return $message . $this->getTraceOutput() . "\n";
186
    }
187
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
     */
194 1
    public function getTraceFunctionArgsOutputLimit()
195
    {
196 1
        return $this->traceFunctionArgsOutputLimit;
197
    }
198
199
    /**
200
     * Only output to logger.
201
     * @param  bool|null $loggerOnly
202
     * @return null|bool
203
     */
204 3
    public function loggerOnly($loggerOnly = null)
205
    {
206 3
        if (func_num_args() == 0) {
207 3
            return $this->loggerOnly;
208
        }
209
210 3
        $this->loggerOnly = (bool) $loggerOnly;
211 3
    }
212
213
    /**
214
     * Test if handler can output to stdout.
215
     * @return bool
216
     */
217 4
    private function canOutput()
218
    {
219 4
        return !$this->loggerOnly();
220
    }
221
222
    /**
223
     * Get the frame args var_dump.
224
     * @param  \Whoops\Exception\Frame $frame [description]
225
     * @param  integer                 $line  [description]
226
     * @return string
227
     */
228 1
    private function getFrameArgsOutput(Frame $frame, $line)
229
    {
230 1
        if ($this->addTraceFunctionArgsToOutput() === false
231 1
            || $this->addTraceFunctionArgsToOutput() < $line) {
232 1
            return '';
233
        }
234
235
        // Dump the arguments:
236 1
        ob_start();
237 1
        $this->dump($frame->getArgs());
238 1
        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 1
        return sprintf(
250 1
            "\n%s",
251 1
            preg_replace('/^/m', self::VAR_DUMP_PREFIX, ob_get_clean())
252 1
        );
253
    }
254
255
    /**
256
     * Dump variable.
257
     *
258
     * @param mixed $var
259
     * @return void
260
     */
261
    protected function dump($var)
262
    {
263
        if ($this->dumper) {
264
            call_user_func($this->dumper, $var);
265
        } else {
266
            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
     * Get the exception trace as plain text.
272
     * @return string
273
     */
274 4
    private function getTraceOutput()
275
    {
276 4
        if (! $this->addTraceToOutput()) {
277 2
            return '';
278
        }
279 2
        $inspector = $this->getInspector();
280 2
        $frames = $inspector->getFrames();
281
282 2
        $response = "\nStack trace:";
283
284 2
        $line = 1;
285 2
        foreach ($frames as $frame) {
286
            /** @var Frame $frame */
287 2
            $class = $frame->getClass();
288
289 2
            $template = "\n%3d. %s->%s() %s:%d%s";
290 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...
291
                // Remove method arrow (->) from output.
292
                $template = "\n%3d. %s%s() %s:%d%s";
293
            }
294
295 2
            $response .= sprintf(
296 2
                $template,
297 2
                $line,
298 2
                $class,
299 2
                $frame->getFunction(),
300 2
                $frame->getFile(),
301 2
                $frame->getLine(),
302 2
                $this->getFrameArgsOutput($frame, $line)
303 2
            );
304
305 2
            $line++;
306 2
        }
307
308 2
        return $response;
309
    }
310
311
    /**
312
     * Get the exception as plain text.
313
     * @param \Throwable $exception
314
     * @return string
315
     */
316 2
    private function getExceptionOutput($exception)
317
    {
318 2
        return sprintf(
319 2
            "%s: %s in file %s on line %d",
320 2
            get_class($exception),
321 2
            $exception->getMessage(),
322 2
            $exception->getFile(),
323 2
            $exception->getLine()
324 2
        );
325
    }
326
327
    /**
328
     * @return int
329
     */
330 5
    public function handle()
331
    {
332 5
        $response = $this->generateResponse();
333
334 5
        if ($this->getLogger()) {
335
            $this->getLogger()->error($response);
336
        }
337
338 5
        if (! $this->canOutput()) {
339
            return Handler::DONE;
340
        }
341
342 5
        echo $response;
343
344 5
        return Handler::QUIT;
345
    }
346
347
    /**
348
     * @return string
349
     */
350 2
    public function contentType()
351
    {
352 2
        return 'text/plain';
353
    }
354
}
355