Completed
Pull Request — master (#537)
by Luis Henrique
01:30
created

PlainTextHandler::generateResponse()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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