Completed
Push — master ( f5d71d...bfd9cb )
by Sam
12:52
created

SS_Backtrace   B

Complexity

Total Complexity 41

Size/Duplication

Total Lines 194
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 194
rs 8.2769
wmc 41
lcom 1
cbo 2

5 Methods

Rating   Name   Duplication   Size   Complexity  
A Backtrace::filtered_backtrace() 0 3 1
C Backtrace::filter_backtrace() 0 50 14
A Backtrace::backtrace() 0 10 4
C Backtrace::full_func_name() 0 22 11
C Backtrace::get_rendered_backtrace() 0 26 11

How to fix   Complexity   

Complex Class

Complex classes like SS_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. 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 SS_Backtrace, and based on these observations, apply Extract Interface, too.

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