Completed
Push — master ( 583d88...813225 )
by Basil
02:08
created

ErrorHandlerTrait::buildTraceItem()   F

Complexity

Conditions 13
Paths 448

Size

Total Lines 56

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 56
rs 3.2166
c 0
b 0
f 0
cc 13
nc 448
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace luya\traits;
4
5
use Yii;
6
use yii\helpers\Json;
7
use Curl\Curl;
8
use luya\helpers\Url;
9
use luya\helpers\ObjectHelper;
10
use luya\helpers\ArrayHelper;
11
use yii\web\Application;
12
use yii\web\HttpException;
13
use yii\base\Exception;
14
use yii\base\ErrorException;
15
use luya\Boot;
16
17
/**
18
 * ErrorHandler trait to extend the renderException method with an api call if enabled.
19
 *
20
 * @author Basil Suter <[email protected]>
21
 * @since 1.0.0
22
 */
23
trait ErrorHandlerTrait
24
{
25
    /**
26
     * @var string The url of the error api without trailing slash. Make sure you have installed the error api
27
     * module on the requested api url (https://luya.io/guide/module/luyadev---luya-module-errorapi).
28
     *
29
     * An example when using the erroapi module, the url could look like this `https://luya.io/errorapi`.
30
     */
31
    public $api;
32
33
    /**
34
     * @var boolean Enable the transfer of exceptions to the defined `$api` server.
35
     */
36
    public $transferException = false;
37
38
    /**
39
     * @var \Curl\Curl|null The curl object from the last error api call.
40
     * @since 1.0.5
41
     */
42
    public $lastTransferCall;
43
    
44
    /**
45
     * @var array An array of exceptions which are whitelisted and therefore NOT TRANSFERED.
46
     * @since 1.0.5
47
     */
48
    public $whitelist = ['yii\web\NotFoundHttpException'];
49
    
50
    /**
51
     * @var array
52
     * @since 1.0.6
53
     */
54
    public $sensitiveKeys = ['password', 'pwd', 'pass', 'passwort', 'pw', 'token', 'hash', 'authorization'];
55
    
56
    /**
57
     * Send a custom message to the api server event its not related to an exception.
58
     *
59
     * Sometimes you just want to pass informations from your application, this method allows you to transfer
60
     * a message to the error api server.
61
     *
62
     * Example of sending a message
63
     *
64
     * ```php
65
     * Yii::$app->errorHandler->transferMessage('Something went wrong here!', __FILE__, __LINE__);
66
     * ```
67
     *
68
     * @param string $message The message you want to send to the error api server.
69
     * @param string $file The you are currently send the message (use __FILE__)
70
     * @param string $line The line you want to submit (use __LINE__)
71
     * @return bool|null
72
     */
73
    public function transferMessage($message, $file = __FILE__, $line = __LINE__)
74
    {
75
        return $this->apiServerSendData($this->getExceptionArray([
76
            'message' => $message,
77
            'file' => $file,
78
            'line' => $line,
79
        ]));
80
    }
81
    
82
    /**
83
     * Send the array data to the api server.
84
     *
85
     * @param array $data The array to be sent to the server.
86
     * @return boolean|null true/false if data has been sent to the api successfull or not, null if the transfer is disabled.
87
     */
88
    private function apiServerSendData(array $data)
89
    {
90
        if ($this->transferException) {
91
            $curl = new Curl();
92
            $curl->setOpt(CURLOPT_CONNECTTIMEOUT, 2);
93
            $curl->setOpt(CURLOPT_TIMEOUT, 2);
94
            $curl->post(Url::ensureHttp(rtrim($this->api, '/')).'/create', [
95
                'error_json' => Json::encode($data),
96
            ]);
97
            
98
            $this->lastTransferCall = $curl;
99
            
100
            return $curl->isSuccess();
101
        }
102
        
103
        return null;
104
    }
105
    
106
    /**
107
     * @inheritdoc
108
     */
109
    public function renderException($exception)
110
    {
111
        if (!ObjectHelper::isInstanceOf($exception, $this->whitelist, false) && $this->transferException) {
112
            $this->apiServerSendData($this->getExceptionArray($exception));
113
        }
114
        
115
        return parent::renderException($exception);
116
    }
117
118
    /**
119
     * Get an readable array to transfer from an exception
120
     *
121
     * @param mixed $exception Exception object
122
     * @return array An array with transformed exception data
123
     */
124
    public function getExceptionArray($exception)
125
    {
126
        $_message = 'Uknonwn exception object, not instance of \Exception.';
127
        $_file = 'unknown';
128
        $_line = 0;
129
        $_trace = [];
130
        $_previousException = [];
131
        $_exceptionClassName = 'Unknown';
132
        
133
        if (is_object($exception)) {
134
            $_exceptionClassName = get_class($exception);
135
            $prev = $exception->getPrevious();
136
            
137
            if (!empty($prev)) {
138
                $_previousException = [
139
                    'message' => $prev->getMessage(),
140
                    'file' => $prev->getFile(),
141
                    'line' => $prev->getLine(),
142
                    'trace' => $this->buildTrace($prev),
143
                ];
144
            }
145
            
146
            $_trace = $this->buildTrace($exception);
147
            $_message = $exception->getMessage();
148
            $_file = $exception->getFile();
149
            $_line = $exception->getLine();
150
        } elseif (is_string($exception)) {
151
            $_message = 'exception string: ' . $exception;
152
        } elseif (is_array($exception)) {
153
            $_message = isset($exception['message']) ? $exception['message'] : 'exception array dump: ' . print_r($exception, true);
154
            $_file = isset($exception['file']) ? $exception['file'] : __FILE__;
155
            $_line = isset($exception['line']) ? $exception['line'] : __LINE__;
156
        }
157
158
        $exceptionName = 'Exception';
159
160
        if ($exception instanceof Exception) {
161
            $exceptionName = $exception->getName();
162
        } elseif ($exception instanceof ErrorException) {
163
            $exceptionName = $exception->getName();
164
        }
165
166
        return [
167
            'message' => $_message,
168
            'file' => $_file,
169
            'line' => $_line,
170
            'requestUri' => isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : null,
171
            'serverName' => isset($_SERVER['SERVER_NAME']) ? $_SERVER['SERVER_NAME'] : null,
172
            'date' => date('d.m.Y H:i'),
173
            'trace' => $_trace,
174
            'previousException' => $_previousException,
175
            'ip' => isset($_SERVER['REMOTE_ADDR']) ? $_SERVER['REMOTE_ADDR'] : null,
176
            'get' => isset($_GET) ? ArrayHelper::coverSensitiveValues($_GET, $this->sensitiveKeys) : [],
177
            'post' => isset($_POST) ? ArrayHelper::coverSensitiveValues($_POST, $this->sensitiveKeys) : [],
178
            'bodyParams' => Yii::$app instanceof Application ? ArrayHelper::coverSensitiveValues(Yii::$app->request->bodyParams) : [],
179
            'session' => isset($_SESSION) ? ArrayHelper::coverSensitiveValues($_SESSION, $this->sensitiveKeys) : [],
180
            'server' => isset($_SERVER) ? ArrayHelper::coverSensitiveValues($_SERVER, $this->sensitiveKeys) : [],
181
            // since 1.0.20
182
            'yii_debug' => YII_DEBUG,
183
            'yii_env' => YII_ENV,
184
            'status_code' => $exception instanceof HttpException ? $exception->statusCode : 500,
185
            'exception_name' => $exceptionName,
186
            'exception_class_name' => $_exceptionClassName,
187
            'php_version' => phpversion(),
188
            'luya_version' => Boot::VERSION,
189
        ];
190
    }
191
    
192
    /**
193
     * Build trace array from exception.
194
     *
195
     * @param object $exception
196
     * @return array
197
     */
198
    private function buildTrace($exception)
199
    {
200
        $_trace = [];
201
        foreach ($exception->getTrace() as $key => $item) {
202
            $_trace[$key] = $this->buildTraceItem($item);
203
        }
204
        
205
        return $_trace;
206
    }
207
208
    private function buildTraceItem(array $item)
209
    {
210
        $file = isset($item['file']) ? $item['file'] : null;
211
        $line = isset($item['line']) ? $item['line'] : null;
212
        $contextLine = null;
213
        $preContext = [];
214
        $postContext = [];
215
216
        try {
217
            $lineInArray = $line -1;
218
            // load file from path
219
            $fileInfo = file($file, FILE_IGNORE_NEW_LINES);
220
            // load file if false from real path
221
            if (!$fileInfo) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fileInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
222
                $fileInfo = file(realpath($file), FILE_IGNORE_NEW_LINES);
223
            }
224
225
            if ($fileInfo && isset($fileInfo[$lineInArray])) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $fileInfo of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
226
                $contextLine = $fileInfo[$lineInArray];
227
            }
228
229
            if ($contextLine) {
230
                for ($i = 1; $i <= 6; $i++) {
231
232
                    // pre context
233
                    if (isset($fileInfo[$lineInArray - $i])) {
234
                        $preContext[] = $fileInfo[$lineInArray - $i];
235
                    }
236
237
                    // post context
238
                    if (isset($fileInfo[$i + $lineInArray])) {
239
                        $postContext[] = $fileInfo[$i + $lineInArray];
240
                    }
241
                }
242
243
                $preContext = array_reverse($preContext);
244
            }
245
        } catch (\Exception $e) {
246
247
        }
248
249
        unset($fileInfo);
250
251
        return [
252
            'file' => $file,
253
            'abs_path' => realpath($file),
254
            'line' => $line,
255
            'context_line' => $contextLine,
256
            'pre_context' => $preContext,
257
            'post_context' => $postContext,
258
            'function' => isset($item['function']) ? $item['function'] : null,
259
            'class' => isset($item['class']) ? $item['class'] : null,
260
            // currently arguments wont be transmited due to large amount of informations based on base object
261
            //'args' => isset($item['args']) ? ArrayHelper::coverSensitiveValues($item['args'], $this->sensitiveKeys) : [],
262
        ];
263
    }
264
}
265