Completed
Pull Request — v1 (#422)
by
unknown
03:17
created

PlainTextHandler::getFrameArgsOutput()   B

Complexity

Conditions 4
Paths 3

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 10
CRAP Score 4.8437

Importance

Changes 2
Bugs 1 Features 1
Metric Value
c 2
b 1
f 1
dl 0
loc 25
ccs 10
cts 16
cp 0.625
rs 8.5806
cc 4
eloc 14
nc 3
nop 2
crap 4.8437
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 bool
31
     */
32
    private $addTraceToOutput = true;
33
34
    /**
35
     * @var bool|integer
36
     */
37
    private $addTraceFunctionArgsToOutput = false;
38
39
    /**
40
     * @var integer
41
     */
42
    private $traceFunctionArgsOutputLimit = 1024;
43
44
    /**
45
     * @var bool
46
     */
47
    private $onlyForCommandLine = false;
48
49
    /**
50
     * @var bool
51
     */
52
    private $outputOnlyIfCommandLine = 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 1
    public function __construct($logger = null)
65
    {
66 1
        $this->setLogger($logger);
67
    }
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 2
    public function setLogger($logger = null)
75
    {
76 2
        if (! (is_null($logger)
77 2
            || $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 1
        $this->logger = $logger;
86 1
    }
87
88
    /**
89
     * @return \Psr\Log\LoggerInterface|null
90
     */
91
    public function getLogger()
92
    {
93
        return $this->logger;
94
    }
95
96
    /**
97
     * Add error trace to output.
98
     * @param  bool|null  $addTraceToOutput
99
     * @return bool|$this
100
     */
101 4
    public function addTraceToOutput($addTraceToOutput = null)
102
    {
103 4
        if (func_num_args() == 0) {
104 4
            return $this->addTraceToOutput;
105
        }
106
107 4
        $this->addTraceToOutput = (bool) $addTraceToOutput;
108 4
        return $this;
109
    }
110
111
    /**
112
     * Add error trace function arguments to output.
113
     * Set to True for all frame args, or integer for the n first frame args.
114
     * @param  bool|integer|null $addTraceFunctionArgsToOutput
115
     * @return null|bool|integer
116
     */
117 2
    public function addTraceFunctionArgsToOutput($addTraceFunctionArgsToOutput = null)
118
    {
119 2
        if (func_num_args() == 0) {
120 2
            return $this->addTraceFunctionArgsToOutput;
121
        }
122
123 2
        if (! is_integer($addTraceFunctionArgsToOutput)) {
124 1
            $this->addTraceFunctionArgsToOutput = (bool) $addTraceFunctionArgsToOutput;
125 1
        } else {
126 2
            $this->addTraceFunctionArgsToOutput = $addTraceFunctionArgsToOutput;
127
        }
128 2
    }
129
130
    /**
131
     * Set the size limit in bytes of frame arguments var_dump output.
132
     * If the limit is reached, the var_dump output is discarded.
133
     * Prevent memory limit errors.
134
     * @var integer
135
     */
136 1
    public function setTraceFunctionArgsOutputLimit($traceFunctionArgsOutputLimit)
137
    {
138 1
        $this->traceFunctionArgsOutputLimit = (integer) $traceFunctionArgsOutputLimit;
139 1
    }
140
141
    /**
142
     * Get the size limit in bytes of frame arguments var_dump output.
143
     * If the limit is reached, the var_dump output is discarded.
144
     * Prevent memory limit errors.
145
     * @return integer
146
     */
147 1
    public function getTraceFunctionArgsOutputLimit()
148
    {
149 1
        return $this->traceFunctionArgsOutputLimit;
150
    }
151
152
    /**
153
     * Restrict error handling to command line calls.
154
     * @param  bool|null $onlyForCommandLine
155
     * @return null|bool
156
     */
157 1
    public function onlyForCommandLine($onlyForCommandLine = null)
158
    {
159 1
        if (func_num_args() == 0) {
160 1
            return $this->onlyForCommandLine;
161
        }
162 1
        $this->onlyForCommandLine = (bool) $onlyForCommandLine;
163 1
    }
164
165
    /**
166
     * Output the error message only if using command line.
167
     * else, output to logger if available.
168
     * Allow to safely add this handler to web pages.
169
     * @param  bool|null $outputOnlyIfCommandLine
170
     * @return null|bool
171
     */
172 1
    public function outputOnlyIfCommandLine($outputOnlyIfCommandLine = null)
173
    {
174 1
        if (func_num_args() == 0) {
175 1
            return $this->outputOnlyIfCommandLine;
176
        }
177 1
        $this->outputOnlyIfCommandLine = (bool) $outputOnlyIfCommandLine;
178 1
    }
179
180
    /**
181
     * Only output to logger.
182
     * @param  bool|null $loggerOnly
183
     * @return null|bool
184
     */
185 1
    public function loggerOnly($loggerOnly = null)
186
    {
187 1
        if (func_num_args() == 0) {
188 1
            return $this->loggerOnly;
189
        }
190
191 1
        $this->loggerOnly = (bool) $loggerOnly;
192 1
    }
193
194
    /**
195
     * Check, if possible, that this execution was triggered by a command line.
196
     * @return bool
197
     */
198
    private function isCommandLine()
199
    {
200
        return PHP_SAPI == 'cli';
201
    }
202
203
    /**
204
     * Test if handler can process the exception..
205
     * @return bool
206
     */
207 2
    private function canProcess()
208
    {
209 2
        return $this->isCommandLine() || !$this->onlyForCommandLine();
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->onlyForCommandLine() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
210
    }
211
212
    /**
213
     * Test if handler can output to stdout.
214
     * @return bool
215
     */
216 2
    private function canOutput()
217
    {
218 2
        return ($this->isCommandLine() || ! $this->outputOnlyIfCommandLine())
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->outputOnlyIfCommandLine() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
219 2
            && ! $this->loggerOnly();
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->loggerOnly() of type boolean|null is loosely compared to false; this is ambiguous if the boolean can be false. You might want to explicitly use !== null instead.

If an expression can have both false, and null as possible values. It is generally a good practice to always use strict comparison to clearly distinguish between those two values.

$a = canBeFalseAndNull();

// Instead of
if ( ! $a) { }

// Better use one of the explicit versions:
if ($a !== null) { }
if ($a !== false) { }
if ($a !== null && $a !== false) { }
Loading history...
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
        var_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("\n%s",
250 1
            preg_replace('/^/m', self::VAR_DUMP_PREFIX, ob_get_clean())
251 1
        );
252
    }
253
254
    /**
255
     * Get the exception trace as plain text.
256
     * @return string
257
     */
258 2
    private function getTraceOutput()
259
    {
260 2
        if (! $this->addTraceToOutput()) {
261
            return '';
262
        }
263 2
        $inspector = $this->getInspector();
264 2
        $frames = $inspector->getFrames();
265
266 2
        $response = "\nStack trace:";
267
268 2
        $line = 1;
269 2
        foreach ($frames as $frame) {
270
            /** @var Frame $frame */
271 2
            $class = $frame->getClass();
272
273 2
            $template = "\n%3d. %s->%s() %s:%d%s";
274 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...
275
                // Remove method arrow (->) from output.
276
                $template = "\n%3d. %s%s() %s:%d%s";
277
            }
278
279 2
            $response .= sprintf(
280 2
                $template,
281 2
                $line,
282 2
                $class,
283 2
                $frame->getFunction(),
284 2
                $frame->getFile(),
285 2
                $frame->getLine(),
286 2
                $this->getFrameArgsOutput($frame, $line)
287 2
            );
288
289 2
            $line++;
290 2
        }
291
292 2
        return $response;
293
    }
294
295
    /**
296
     * @return int
297
     */
298 3
    public function handle()
299
    {
300 3
        if (! $this->canProcess()) {
301
            return Handler::DONE;
302
        }
303
304 3
        $exception = $this->getException();
305
306 3
        $response = sprintf("%s: %s in file %s on line %d%s\n",
307 3
                get_class($exception),
308 3
                $exception->getMessage(),
309 3
                $exception->getFile(),
310 3
                $exception->getLine(),
311 3
                $this->getTraceOutput()
312 3
            );
313
314 3
        if ($this->getLogger()) {
315
            $this->getLogger()->error($response);
316
        }
317
318 3
        if (! $this->canOutput()) {
319
            return Handler::DONE;
320
        }
321
322 3
        if (class_exists('\Whoops\Util\Misc')
323 3
            && \Whoops\Util\Misc::canSendHeaders()) {
324
            header('Content-Type: text/plain');
325
        }
326
327 3
        echo $response;
328
329 3
        return Handler::QUIT;
330
    }
331
}
332