Completed
Push — master ( 925ecf...691399 )
by Aivis
08:17 queued 02:32
created

ExceptionEncoder::removeProjectRoot()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 3
nc 2
nop 1
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
     * @param string $projectRoot
18
     */
19
    public function setProjectRoot($projectRoot)
20
    {
21
        $this->projectRoot = $projectRoot;
22
    }
23
24
    /**
25
     * Serialize exception object
26
     *
27
     * @param mixed $exception
28
     * @return array
29
     */
30
    public function exceptionToArray($exception)
31
    {
32
        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...
33
        {
34
            throw new InvalidArgumentException('$exception must be instance of Exception or Throwable');
35
        }
36
37
        $trace = $exception->getTrace();
38
        $className = get_class($exception);
39
        $message = $exception->getMessage() ? $exception->getMessage() : $className;
40
41
        return [
42
            'message' => $message,
43
            'class' => $className,
44
            'code' => $exception->getCode(),
45
            'file' => $this->removeProjectRoot($exception->getFile()),
46
            'line' => $exception->getLine(),
47
            'stack' => $this->stackTraceToArray($trace, $exception->getFile(), $exception->getLine())
48
        ];
49
    }
50
51
    /**
52
     * @param array $errorLog
53
     * @return array
54
     */
55
    public function setCurrentStackTrace(array $errorLog)
56
    {
57
        $level = isset($errorLog['level']) ? $errorLog['level'] : null;
58
        $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...
59
        $firstLineSet = false;
60
61
        foreach($stackTrace as $trace)
62
        {
63
            if ($firstLineSet)
64
            {
65
                break;
66
            }
67
68
            $firstLineSet = true;
69
70
            $errorLog['class'] = null;
71
            $errorLog['file'] = isset($trace['file']) ? $trace['file'] : null;
72
            $errorLog['line'] = isset($trace['line']) ? $trace['line'] : null;
73
        }
74
75
        $errorLog['stack'] = $stackTrace;
76
77
        return $errorLog;
78
    }
79
80
    /**
81
     * @return array
82
     */
83
    protected function getCurrentStackTrace()
84
    {
85
        $stackTrace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT, 100);
86
        $vendorExcluded = false;
87
88
        foreach($stackTrace as $index => $trace)
89
        {
90
            // exclude Understand service provider and helper classes
91
            if (isset($trace['class']) && strpos($trace['class'], 'Understand\UnderstandLaravel5\\') === 0)
92
            {
93
                unset($stackTrace[$index]);
94
            }
95
96
            if ( ! isset($trace['file']))
97
            {
98
                $vendorExcluded = true;
99
            }
100
101
            if ($vendorExcluded)
102
            {
103
                continue;
104
            }
105
106
            // exclude `vendor` folder until project path reached
107
            if (strpos($trace['file'], $this->projectRoot . 'vendor' . DIRECTORY_SEPARATOR) === 0)
108
            {
109
                unset($stackTrace[$index]);
110
            }
111
            else
112
            {
113
                $vendorExcluded = true;
114
            }
115
        }
116
117
        return $this->stackTraceToArray($stackTrace);
118
    }
119
120
    /**
121
     * Serialize stack trace to array
122
     *
123
     * @param array $stackTrace
124
     * @return array
125
     */
126
    public function stackTraceToArray(array $stackTrace, $topFile = null, $topLine = null)
127
    {
128
        $stack = [];
129
130
        foreach ($stackTrace as $trace)
131
        {
132
            // Exception object `getTrace` does not return file and line number for the first line
133
            // http://php.net/manual/en/exception.gettrace.php#107563
134
            if (isset($topFile, $topLine) && $topFile && $topLine)
135
            {
136
                $trace['file'] = $topFile;
137
                $trace['line'] = $topLine;
138
139
                unset($topFile, $topLine);
140
            }
141
142
            $type = $this->stackTraceCallToString($trace);
143
            $args = $this->stackTraceArgsToArray($trace);
144
145
            $stack[] = [
146
                'class' => isset($trace['class']) ? $trace['class'] : null,
147
                'function' => isset($trace['function']) ? $trace['function'] : null,
148
                'args' => $args,
149
                'type' => $type,
150
                'file' => $this->getStackTraceFile($trace),
151
                'line' => $this->getStackTraceLine($trace),
152
                'code' => $this->getCode($this->getStackTraceFile($trace), $this->getStackTraceLine($trace)),
153
            ];
154
        }
155
156
        return $stack;
157
    }
158
159
    /**
160
     * @param $relativePath
161
     * @param $line
162
     * @param int $linesAround
163
     * @return array|void
164
     */
165
    public function getCode($relativePath, $line, $linesAround = 6)
166
    {
167
        if ( ! $relativePath || ! $line)
168
        {
169
            return;
170
        }
171
172
        $filePath = $this->projectRoot . $relativePath;
173
174
        try
175
        {
176
            $file = new SplFileObject($filePath);
177
            $file->setMaxLineLen(250);
178
            $file->seek(PHP_INT_MAX);
179
            $codeLines = [];
180
181
            $from = max(0, $line - $linesAround - 2);
182
            $to = min($line + $linesAround -1, $file->key() + 1);
183
184
            $file->seek($from);
185
186
            while ($file->key() < $to && ! $file->eof())
187
            {
188
                $file->next();
189
                // `key()` returns 0 as the first line
190
                $codeLines[] = [
191
                    'line' => $file->key() + 1,
192
                    'code' => rtrim($file->current())
193
                ];
194
            }
195
196
            return $codeLines;
197
        }
198
        catch (\Exception $e)
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
199
        {}
200
    }
201
202
    /**
203
     * Return stack trace line number
204
     *
205
     * @param array $trace
206
     * @return mixed
207
     */
208
    protected function getStackTraceLine(array $trace)
209
    {
210
        if (isset($trace['line']))
211
        {
212
            return $trace['line'];
213
        }
214
    }
215
216
    /**
217
     * Return stack trace file
218
     *
219
     * @param array $trace
220
     * @return mixed
221
     */
222
    protected function getStackTraceFile(array $trace)
223
    {
224
        if (isset($trace['file']))
225
        {
226
            return $this->removeProjectRoot($trace['file']);
227
        }
228
    }
229
230
    /**
231
     * Return call type
232
     *
233
     * @param array $trace
234
     * @return string
235
     */
236
    protected function stackTraceCallToString(array $trace)
237
    {
238
        if (! isset($trace['type']))
239
        {
240
            return 'function';
241
        }
242
243
        if ($trace['type'] == '::')
244
        {
245
            return 'static';
246
        }
247
248
        if ($trace['type'] == '->')
249
        {
250
            return 'method';
251
        }
252
    }
253
254
    /**
255
     * Serialize stack trace function arguments
256
     *
257
     * @param array $trace
258
     * @return array
259
     */
260
    protected function stackTraceArgsToArray(array $trace)
261
    {
262
        $params = [];
263
264
        if (! isset($trace['args']))
265
        {
266
            return $params;
267
        }
268
269
        foreach ($trace['args'] as $arg)
270
        {
271
            if (is_array($arg))
272
            {
273
                $params[] = 'array(' . count($arg) . ')';
274
            }
275
            else if (is_object($arg))
276
            {
277
                $params[] = get_class($arg);
278
            }
279
            else if (is_string($arg))
280
            {
281
                $params[] = 'string(' . $arg . ')';
282
            }
283
            else if (is_int($arg))
284
            {
285
                $params[] = 'int(' . $arg . ')';
286
            }
287
            else if (is_float($arg))
288
            {
289
                $params[] = 'float(' . $arg . ')';
290
            }
291
            else if (is_bool($arg))
292
            {
293
                $params[] = 'bool(' . ($arg ? 'true' : 'false') . ')';
294
            }
295
            else if ($arg instanceof \__PHP_Incomplete_Class)
296
            {
297
                $params[] = 'object(__PHP_Incomplete_Class)'; 
298
            }
299
            else
300
            {
301
                $params[] = gettype($arg);
302
            }
303
        }
304
305
        return $params;
306
    }
307
308
    /**
309
     * @param $path
310
     * @return string
311
     */
312
    protected function removeProjectRoot($path)
313
    {
314
        if (substr($path, 0, strlen($this->projectRoot)) == $this->projectRoot)
315
        {
316
            return substr($path, strlen($this->projectRoot));
317
        }
318
    }
319
}
320