Passed
Pull Request — 4 (#10222)
by Steve
07:01
created

Backtrace   B

Complexity

Total Complexity 44

Size/Duplication

Total Lines 220
Duplicated Lines 0 %

Importance

Changes 1
Bugs 0 Features 1
Metric Value
eloc 117
dl 0
loc 220
rs 8.8798
c 1
b 0
f 1
wmc 44

5 Methods

Rating   Name   Duplication   Size   Complexity  
B get_rendered_backtrace() 0 30 11
A backtrace() 0 9 4
A filtered_backtrace() 0 3 1
B full_func_name() 0 28 11
C filter_backtrace() 0 59 17

How to fix   Complexity   

Complex Class

Complex classes like Backtrace 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.

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 Backtrace, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace SilverStripe\Dev;
4
5
use SilverStripe\Control\Director;
6
use SilverStripe\Core\Config\Configurable;
7
8
/**
9
 * Backtrace helper
10
 */
11
class Backtrace
12
{
13
    use Configurable;
14
15
    /**
16
     * @var array Replaces all arguments with a '<filtered>' string,
17
     * mostly for security reasons. Use string values for global functions,
18
     * and array notation for class methods.
19
     * PHP's debug_backtrace() doesn't allow to inspect the argument names,
20
     * so all arguments of the provided functions will be filtered out.
21
     */
22
    private static $ignore_function_args = [
0 ignored issues
show
introduced by
The private property $ignore_function_args is not used, and could be removed.
Loading history...
23
        'mysql_connect',
24
        'mssql_connect',
25
        'pg_connect',
26
        ['PDO', '__construct'],
27
        ['mysqli', 'mysqli'],
28
        ['mysqli', 'select_db'],
29
        ['mysqli', 'real_connect'],
30
        ['SilverStripe\\ORM\\DB', 'connect'],
31
        ['SilverStripe\\Security\\Security', 'check_default_admin'],
32
        ['SilverStripe\\Security\\Security', 'encrypt_password'],
33
        ['SilverStripe\\Security\\Security', 'setDefaultAdmin'],
34
        ['SilverStripe\\ORM\\DB', 'createDatabase'],
35
        ['SilverStripe\\Security\\Member', 'checkPassword'],
36
        ['SilverStripe\\Security\\Member', 'changePassword'],
37
        ['SilverStripe\\Security\\MemberAuthenticator\\MemberAuthenticator', 'checkPassword'],
38
        ['SilverStripe\\Security\\MemberPassword', 'checkPassword'],
39
        ['SilverStripe\\Security\\PasswordValidator', 'validate'],
40
        ['SilverStripe\\Security\\PasswordEncryptor_PHPHash', 'encrypt'],
41
        ['SilverStripe\\Security\\PasswordEncryptor_PHPHash', 'salt'],
42
        ['SilverStripe\\Security\\PasswordEncryptor_LegacyPHPHash', 'encrypt'],
43
        ['SilverStripe\\Security\\PasswordEncryptor_LegacyPHPHash', 'salt'],
44
        ['SilverStripe\\Security\\PasswordEncryptor_MySQLPassword', 'encrypt'],
45
        ['SilverStripe\\Security\\PasswordEncryptor_MySQLPassword', 'salt'],
46
        ['SilverStripe\\Security\\PasswordEncryptor_MySQLOldPassword', 'encrypt'],
47
        ['SilverStripe\\Security\\PasswordEncryptor_MySQLOldPassword', 'salt'],
48
        ['SilverStripe\\Security\\PasswordEncryptor_Blowfish', 'encrypt'],
49
        ['SilverStripe\\Security\\PasswordEncryptor_Blowfish', 'salt'],
50
        ['*', 'updateValidatePassword'],
51
    ];
52
53
    /**
54
     * Return debug_backtrace() results with functions filtered
55
     * specific to the debugging system, and not the trace.
56
     *
57
     * @param null|array $ignoredFunctions If an array, filter these functions out of the trace
58
     * @return array
59
     */
60
    public static function filtered_backtrace($ignoredFunctions = null)
61
    {
62
        return self::filter_backtrace(debug_backtrace(), $ignoredFunctions);
63
    }
64
65
    /**
66
     * Filter a backtrace so that it doesn't show the calls to the
67
     * debugging system, which is useless information.
68
     *
69
     * @param array $bt Backtrace to filter
70
     * @param null|array $ignoredFunctions List of extra functions to filter out
71
     * @return array
72
     */
73
    public static function filter_backtrace($bt, $ignoredFunctions = null)
74
    {
75
        $defaultIgnoredFunctions = [
76
            'SilverStripe\\Logging\\Log::log',
77
            'SilverStripe\\Dev\\Backtrace::backtrace',
78
            'SilverStripe\\Dev\\Backtrace::filtered_backtrace',
79
            'Zend_Log_Writer_Abstract->write',
80
            'Zend_Log->log',
81
            'Zend_Log->__call',
82
            'Zend_Log->err',
83
            'SilverStripe\\Dev\\DebugView->writeTrace',
84
            'SilverStripe\\Dev\\CliDebugView->writeTrace',
85
            'SilverStripe\\Dev\\Debug::emailError',
86
            'SilverStripe\\Dev\\Debug::warningHandler',
87
            'SilverStripe\\Dev\\Debug::noticeHandler',
88
            'SilverStripe\\Dev\\Debug::fatalHandler',
89
            'errorHandler',
90
            'SilverStripe\\Dev\\Debug::showError',
91
            'SilverStripe\\Dev\\Debug::backtrace',
92
            'exceptionHandler'
93
        ];
94
95
        if ($ignoredFunctions) {
96
            foreach ($ignoredFunctions as $ignoredFunction) {
97
                $defaultIgnoredFunctions[] = $ignoredFunction;
98
            }
99
        }
100
101
        while ($bt && in_array(self::full_func_name($bt[0]), $defaultIgnoredFunctions ?: [])) {
102
            array_shift($bt);
103
        }
104
105
        $ignoredArgs = static::config()->get('ignore_function_args');
106
107
        // Filter out arguments
108
        foreach ($bt as $i => $frame) {
109
            $match = false;
110
            if (!empty($bt[$i]['class'])) {
111
                foreach ($ignoredArgs as $fnSpec) {
112
                    if (is_array($fnSpec) &&
113
                        ('*' == $fnSpec[0] || $bt[$i]['class'] == $fnSpec[0]) &&
114
                        $bt[$i]['function'] == $fnSpec[1]
115
                    ) {
116
                        $match = true;
117
                    }
118
                }
119
            } else {
120
                if (in_array($bt[$i]['function'], $ignoredArgs ?: [])) {
121
                    $match = true;
122
                }
123
            }
124
            if ($match) {
125
                foreach ($bt[$i]['args'] as $j => $arg) {
126
                    $bt[$i]['args'][$j] = '<filtered>';
127
                }
128
            }
129
        }
130
131
        return $bt;
132
    }
133
134
    /**
135
     * Render or return a backtrace from the given scope.
136
     *
137
     * @param mixed $returnVal
138
     * @param bool $ignoreAjax
139
     * @param array $ignoredFunctions
140
     * @return mixed
141
     */
142
    public static function backtrace($returnVal = false, $ignoreAjax = false, $ignoredFunctions = null)
143
    {
144
        $plainText = Director::is_cli() || (Director::is_ajax() && !$ignoreAjax);
145
        $result = self::get_rendered_backtrace(debug_backtrace(), $plainText, $ignoredFunctions);
146
        if ($returnVal) {
147
            return $result;
148
        } else {
149
            echo $result;
150
            return null;
151
        }
152
    }
153
154
    /**
155
     * Return the full function name.  If showArgs is set to true, a string representation of the arguments will be
156
     * shown
157
     *
158
     * @param Object $item
159
     * @param bool $showArgs
160
     * @param int $argCharLimit
161
     * @return string
162
     */
163
    public static function full_func_name($item, $showArgs = false, $argCharLimit = 10000)
164
    {
165
        $funcName = '';
166
        if (isset($item['class'])) {
167
            $funcName .= $item['class'];
168
        }
169
        if (isset($item['type'])) {
170
            $funcName .= $item['type'];
171
        }
172
        if (isset($item['function'])) {
173
            $funcName .= $item['function'];
174
        }
175
176
        if ($showArgs && isset($item['args'])) {
177
            $args = [];
178
            foreach ($item['args'] as $arg) {
179
                if (!is_object($arg) || method_exists($arg, '__toString')) {
180
                    $sarg = is_array($arg) ? 'Array' : strval($arg);
181
                    $args[] = (strlen((string) $sarg) > $argCharLimit) ? substr($sarg, 0, $argCharLimit) . '...' : $sarg;
182
                } else {
183
                    $args[] = get_class($arg);
184
                }
185
            }
186
187
            $funcName .= "(" . implode(", ", $args) . ")";
188
        }
189
190
        return $funcName;
191
    }
192
193
    /**
194
     * Render a backtrace array into an appropriate plain-text or HTML string.
195
     *
196
     * @param array $bt The trace array, as returned by debug_backtrace() or Exception::getTrace()
197
     * @param boolean $plainText Set to false for HTML output, or true for plain-text output
198
     * @param array $ignoredFunctions List of functions that should be ignored. If not set, a default is provided
199
     * @return string The rendered backtrace
200
     */
201
    public static function get_rendered_backtrace($bt, $plainText = false, $ignoredFunctions = null)
202
    {
203
        if (empty($bt)) {
204
            return '';
205
        }
206
        $bt = self::filter_backtrace($bt, $ignoredFunctions);
207
        $result = ($plainText) ? '' : '<ul>';
208
        foreach ($bt as $item) {
209
            if ($plainText) {
210
                $result .= self::full_func_name($item, true) . "\n";
211
                if (isset($item['line']) && isset($item['file'])) {
212
                    $result .= basename($item['file']) . ":$item[line]\n";
213
                }
214
                $result .= "\n";
215
            } else {
216
                if ($item['function'] == 'user_error') {
217
                    $name = $item['args'][0];
218
                } else {
219
                    $name = self::full_func_name($item, true);
220
                }
221
                $result .= "<li><b>" . htmlentities((string) $name, ENT_COMPAT, 'UTF-8') . "</b>\n<br />\n";
222
                $result .=  isset($item['file']) ? htmlentities(basename($item['file']), ENT_COMPAT, 'UTF-8') : '';
223
                $result .= isset($item['line']) ? ":$item[line]" : '';
224
                $result .= "</li>\n";
225
            }
226
        }
227
        if (!$plainText) {
228
            $result .= '</ul>';
229
        }
230
        return $result;
231
    }
232
}
233