ExceptionEncoder   C
last analyzed

Complexity

Total Complexity 56

Size/Duplication

Total Lines 335
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 0

Importance

Changes 0
Metric Value
wmc 56
lcom 1
cbo 0
dl 0
loc 335
rs 5.5199
c 0
b 0
f 0

12 Methods

Rating   Name   Duplication   Size   Complexity  
A setStackTraceLimit() 0 4 1
A setProjectRoot() 0 4 1
A exceptionToArray() 0 20 4
B setCurrentStackTrace() 0 24 6
B getCurrentStackTrace() 0 36 7
B stackTraceToArray() 0 40 8
B getCode() 0 38 7
A getStackTraceLine() 0 7 2
A getStackTraceFile() 0 7 2
A stackTraceCallToString() 0 17 4
A removeProjectRoot() 0 7 2
C stackTraceArgsToArray() 0 47 12

How to fix   Complexity   

Complex Class

Complex classes like ExceptionEncoder often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use ExceptionEncoder, and based on these observations, apply Extract Interface, too.

1
<?php namespace Understand\UnderstandLaravel5;
2
3
use Exception;
4
use Throwable;
5
use InvalidArgumentException;
6
use SplFileObject;
7
8
class ExceptionEncoder
9
{
10
11
    /**
12
     * @var string
13
     */
14
    protected $projectRoot;
15
16
    /**
17
     * @var int
18
     */
19
    protected $stackTraceLimit = 100;
20
21
    /**
22
     * @param integer $limit
23
     */
24
    public function setStackTraceLimit($limit)
25
    {
26
        $this->stackTraceLimit = $limit;
27
    }
28
29
    /**
30
     * @param string $projectRoot
31
     */
32
    public function setProjectRoot($projectRoot)
33
    {
34
        $this->projectRoot = $projectRoot;
35
    }
36
37
    /**
38
     * Serialize exception object
39
     *
40
     * @param mixed $exception
41
     * @return array
42
     */
43
    public function exceptionToArray($exception)
44
    {
45
        if ( ! ($exception instanceof Exception || $exception instanceof Throwable))
0 ignored issues
show
Bug introduced by
The class Throwable does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
46
        {
47
            throw new InvalidArgumentException('$exception must be instance of Exception or Throwable');
48
        }
49
50
        $trace = $exception->getTrace();
51
        $className = get_class($exception);
52
        $message = $exception->getMessage() ? $exception->getMessage() : $className;
53
54
        return [
55
            'message' => $message,
56
            'class' => $className,
57
            'code' => $exception->getCode(),
58
            'file' => $this->removeProjectRoot($exception->getFile()),
59
            'line' => $exception->getLine(),
60
            'stack' => $this->stackTraceToArray($trace, $exception->getFile(), $exception->getLine())
61
        ];
62
    }
63
64
    /**
65
     * @param array $errorLog
66
     * @return array
67
     */
68
    public function setCurrentStackTrace(array $errorLog)
69
    {
70
        $level = isset($errorLog['level']) ? $errorLog['level'] : null;
71
        $stackTrace = $this->getCurrentStackTrace($level);
0 ignored issues
show
Unused Code introduced by
The call to ExceptionEncoder::getCurrentStackTrace() has too many arguments starting with $level.

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress.

In this case you can add the @ignore PhpDoc annotation to the duplicate definition and it will be ignored.

Loading history...
72
        $firstLineSet = false;
73
74
        foreach($stackTrace as $trace)
75
        {
76
            if ($firstLineSet)
77
            {
78
                break;
79
            }
80
81
            $firstLineSet = true;
82
83
            $errorLog['class'] = null;
84
            $errorLog['file'] = isset($trace['file']) ? $trace['file'] : null;
85
            $errorLog['line'] = isset($trace['line']) ? $trace['line'] : null;
86
        }
87
88
        $errorLog['stack'] = $stackTrace;
89
90
        return $errorLog;
91
    }
92
93
    /**
94
     * @return array
95
     */
96
    protected function getCurrentStackTrace()
97
    {
98
        $stackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, $this->stackTraceLimit);
99
        $vendorExcluded = false;
100
101
        foreach($stackTrace as $index => $trace)
102
        {
103
            // exclude Understand service provider and helper classes
104
            if (isset($trace['class']) && strpos($trace['class'], 'Understand\UnderstandLaravel5\\') === 0)
105
            {
106
                unset($stackTrace[$index]);
107
            }
108
109
            if ( ! isset($trace['file']))
110
            {
111
                $vendorExcluded = true;
112
            }
113
114
            if ($vendorExcluded)
115
            {
116
                continue;
117
            }
118
119
            // exclude `vendor` folder until project path reached
120
            if (strpos($trace['file'], $this->projectRoot . 'vendor' . DIRECTORY_SEPARATOR) === 0)
121
            {
122
                unset($stackTrace[$index]);
123
            }
124
            else
125
            {
126
                $vendorExcluded = true;
127
            }
128
        }
129
130
        return $this->stackTraceToArray($stackTrace);
131
    }
132
133
    /**
134
     * Serialize stack trace to array
135
     *
136
     * @param array $stackTrace
137
     * @return array
138
     */
139
    public function stackTraceToArray(array $stackTrace, $topFile = null, $topLine = null)
140
    {
141
        $stack = [];
142
        $counter = 0;
143
144
        foreach ($stackTrace as $trace)
145
        {
146
            // Exception object `getTrace` does not return file and line number for the first line
147
            // http://php.net/manual/en/exception.gettrace.php#107563
148
            if (isset($topFile, $topLine) && $topFile && $topLine)
149
            {
150
                $trace['file'] = $topFile;
151
                $trace['line'] = $topLine;
152
153
                unset($topFile, $topLine);
154
            }
155
156
            $type = $this->stackTraceCallToString($trace);
157
            $args = $this->stackTraceArgsToArray($trace);
158
159
            $stack[] = [
160
                'class' => isset($trace['class']) ? $trace['class'] : null,
161
                'function' => isset($trace['function']) ? $trace['function'] : null,
162
                'args' => $args,
163
                'type' => $type,
164
                'file' => $this->getStackTraceFile($trace),
165
                'line' => $this->getStackTraceLine($trace),
166
                'code' => $this->getCode($this->getStackTraceFile($trace), $this->getStackTraceLine($trace)),
167
            ];
168
169
            $counter++;
170
171
            if ($counter >= $this->stackTraceLimit)
172
            {
173
                break;
174
            }
175
        }
176
177
        return $stack;
178
    }
179
180
    /**
181
     * @param $relativePath
182
     * @param $line
183
     * @param int $linesAround
184
     * @return array|void
185
     */
186
    public function getCode($relativePath, $line, $linesAround = 6)
187
    {
188
        if ( ! $relativePath || ! $line)
189
        {
190
            return;
191
        }
192
193
        $filePath = $this->projectRoot . $relativePath;
194
195
        try
196
        {
197
            $file = new SplFileObject($filePath);
198
            $file->setMaxLineLen(250);
199
            $file->seek(PHP_INT_MAX);
200
            $codeLines = [];
201
202
            $from = max(0, $line - $linesAround - 2);
203
            $to = min($line + $linesAround -1, $file->key() + 1);
204
205
            $file->seek($from);
206
207
            while ($file->key() < $to && ! $file->eof())
208
            {
209
                $file->next();
210
                // `key()` returns 0 as the first line
211
                $codeLines[] = [
212
                    'line' => $file->key() + 1,
213
                    'code' => rtrim($file->current())
214
                ];
215
            }
216
217
            return $codeLines;
218
        }
219
        catch (\Throwable $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
Bug introduced by
The class Throwable does not exist. Is this class maybe located in a folder that is not analyzed, or in a newer version of your dependencies than listed in your composer.lock/composer.json?
Loading history...
220
        {}
221
        catch (\Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
222
        {}
223
    }
224
225
    /**
226
     * Return stack trace line number
227
     *
228
     * @param array $trace
229
     * @return mixed
230
     */
231
    protected function getStackTraceLine(array $trace)
232
    {
233
        if (isset($trace['line']))
234
        {
235
            return $trace['line'];
236
        }
237
    }
238
239
    /**
240
     * Return stack trace file
241
     *
242
     * @param array $trace
243
     * @return mixed
244
     */
245
    protected function getStackTraceFile(array $trace)
246
    {
247
        if (isset($trace['file']))
248
        {
249
            return $this->removeProjectRoot($trace['file']);
250
        }
251
    }
252
253
    /**
254
     * Return call type
255
     *
256
     * @param array $trace
257
     * @return string
258
     */
259
    protected function stackTraceCallToString(array $trace)
260
    {
261
        if (! isset($trace['type']))
262
        {
263
            return 'function';
264
        }
265
266
        if ($trace['type'] == '::')
267
        {
268
            return 'static';
269
        }
270
271
        if ($trace['type'] == '->')
272
        {
273
            return 'method';
274
        }
275
    }
276
277
    /**
278
     * Serialize stack trace function arguments
279
     *
280
     * @param array $trace
281
     * @return array
282
     */
283
    protected function stackTraceArgsToArray(array $trace)
284
    {
285
        $params = [];
286
287
        if (! isset($trace['args']))
288
        {
289
            return $params;
290
        }
291
292
        foreach ($trace['args'] as $arg)
293
        {
294
            if (is_array($arg))
295
            {
296
                $params[] = 'array(' . count($arg) . ')';
297
            }
298
            else if (is_object($arg))
299
            {
300
                $params[] = get_class($arg);
301
            }
302
            else if (is_string($arg))
303
            {
304
                $params[] = 'string(' . (strlen($arg) > 70 ? substr($arg, 0, 70) . '...' : $arg) . ')';
305
            }
306
            else if (is_int($arg))
307
            {
308
                $params[] = 'int(' . $arg . ')';
309
            }
310
            else if (is_float($arg))
311
            {
312
                $params[] = 'float(' . $arg . ')';
313
            }
314
            else if (is_bool($arg))
315
            {
316
                $params[] = 'bool(' . ($arg ? 'true' : 'false') . ')';
317
            }
318
            else if ($arg instanceof \__PHP_Incomplete_Class)
319
            {
320
                $params[] = 'object(__PHP_Incomplete_Class)'; 
321
            }
322
            else
323
            {
324
                $params[] = gettype($arg);
325
            }
326
        }
327
328
        return $params;
329
    }
330
331
    /**
332
     * @param $path
333
     * @return string
334
     */
335
    protected function removeProjectRoot($path)
336
    {
337
        if (substr($path, 0, strlen($this->projectRoot)) == $this->projectRoot)
338
        {
339
            return substr($path, strlen($this->projectRoot));
340
        }
341
    }
342
}
343