Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.
Common duplication problems, and corresponding solutions are:
Complex classes like Debug 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 Debug, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
25 | class Debug { |
||
26 | |||
27 | /** |
||
28 | * @config |
||
29 | * @var String indicating the file where errors are logged. |
||
30 | * Filename is relative to the site root. |
||
31 | * The named file will have a terse log sent to it, and the full log (an |
||
32 | * encoded file containing backtraces and things) will go to a file of a similar |
||
33 | * name, but with the suffix ".full" added. |
||
34 | */ |
||
35 | private static $log_errors_to = null; |
||
36 | |||
37 | /** |
||
38 | * @config |
||
39 | * @var string The header of the message shown to users on the live site when a fatal error occurs. |
||
40 | */ |
||
41 | private static $friendly_error_header = 'There has been an error'; |
||
42 | |||
43 | /** |
||
44 | * @config |
||
45 | * @var string The body of the message shown to users on the live site when a fatal error occurs. |
||
46 | */ |
||
47 | private static $friendly_error_detail = 'The website server has not been able to respond to your request.'; |
||
48 | |||
49 | /** |
||
50 | * Show the contents of val in a debug-friendly way. |
||
51 | * Debug::show() is intended to be equivalent to dprintr() |
||
52 | */ |
||
53 | public static function show($val, $showHeader = true) { |
||
54 | if(!Director::isLive()) { |
||
55 | if($showHeader) { |
||
56 | $caller = Debug::caller(); |
||
57 | if(Director::is_ajax() || Director::is_cli()) |
||
58 | echo "Debug ($caller[class]$caller[type]$caller[function]() in " . basename($caller['file']) |
||
59 | . ":$caller[line])\n"; |
||
60 | else |
||
61 | echo "<div style=\"background-color: white; text-align: left;\">\n<hr>\n" |
||
62 | . "<h3>Debug <span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]()" |
||
63 | . " \nin " . basename($caller['file']) . ":$caller[line])</span>\n</h3>\n"; |
||
64 | } |
||
65 | |||
66 | echo Debug::text($val); |
||
67 | |||
68 | if(!Director::is_ajax() && !Director::is_cli()) echo "</div>"; |
||
69 | else echo "\n\n"; |
||
70 | } |
||
71 | |||
72 | } |
||
73 | |||
74 | /** |
||
75 | * Returns the caller for a specific method |
||
76 | * |
||
77 | * @return array |
||
78 | */ |
||
79 | public static function caller() { |
||
80 | $bt = debug_backtrace(); |
||
81 | $caller = $bt[2]; |
||
82 | $caller['line'] = $bt[1]['line']; |
||
83 | $caller['file'] = $bt[1]['file']; |
||
84 | if(!isset($caller['class'])) $caller['class'] = ''; |
||
85 | if(!isset($caller['type'])) $caller['type'] = ''; |
||
86 | return $caller; |
||
87 | } |
||
88 | |||
89 | /** |
||
90 | * Close out the show dumper |
||
91 | * |
||
92 | * @param mixed $val |
||
93 | */ |
||
94 | public static function endshow($val) { |
||
95 | if(!Director::isLive()) { |
||
96 | $caller = Debug::caller(); |
||
97 | echo "<hr>\n<h3>Debug \n<span style=\"font-size: 65%\">($caller[class]$caller[type]$caller[function]()" |
||
98 | . " \nin " . basename($caller['file']) . ":$caller[line])</span>\n</h3>\n"; |
||
99 | echo Debug::text($val); |
||
100 | die(); |
||
101 | } |
||
102 | } |
||
103 | |||
104 | /** |
||
105 | * Quick dump of a variable. |
||
106 | * |
||
107 | * @param mixed $val |
||
108 | */ |
||
109 | public static function dump($val) { |
||
112 | |||
113 | /** |
||
114 | * ?? |
||
115 | * |
||
116 | * @param unknown_type $val |
||
117 | * @return unknown |
||
118 | */ |
||
119 | public static function text($val) { |
||
120 | if(is_object($val)) { |
||
121 | if(method_exists($val, 'hasMethod')) { |
||
122 | $hasDebugMethod = $val->hasMethod('debug'); |
||
123 | } else { |
||
124 | $hasDebugMethod = method_exists($val, 'debug'); |
||
125 | } |
||
126 | |||
127 | if($hasDebugMethod) { |
||
128 | return $val->debug(); |
||
129 | } |
||
130 | } |
||
131 | |||
132 | if(is_array($val)) { |
||
133 | $result = "<ul>\n"; |
||
134 | foreach($val as $k => $v) { |
||
135 | $result .= "<li>$k = " . Debug::text($v) . "</li>\n"; |
||
136 | } |
||
137 | $val = $result . "</ul>\n"; |
||
138 | |||
139 | } else if (is_object($val)) { |
||
140 | $val = var_export($val, true); |
||
141 | } else if (is_bool($val)) { |
||
142 | $val = $val ? 'true' : 'false'; |
||
143 | $val = '(bool) ' . $val; |
||
144 | } else { |
||
145 | if(!Director::is_cli() && !Director::is_ajax()) { |
||
146 | $val = "<pre style=\"font-family: Courier new\">" . htmlentities($val, ENT_COMPAT, 'UTF-8') |
||
147 | . "</pre>\n"; |
||
148 | } |
||
149 | } |
||
150 | |||
151 | return $val; |
||
|
|||
152 | } |
||
153 | |||
154 | /** |
||
155 | * Show a debugging message |
||
156 | */ |
||
157 | public static function message($message, $showHeader = true) { |
||
158 | if(!Director::isLive()) { |
||
159 | $caller = Debug::caller(); |
||
160 | $file = basename($caller['file']); |
||
161 | if(Director::is_cli()) { |
||
162 | if($showHeader) echo "Debug (line $caller[line] of $file):\n "; |
||
163 | echo $message . "\n"; |
||
164 | } else { |
||
165 | echo "<p class=\"message warning\">\n"; |
||
166 | if($showHeader) echo "<b>Debug (line $caller[line] of $file):</b>\n "; |
||
167 | echo Convert::raw2xml($message) . "</p>\n"; |
||
168 | } |
||
169 | } |
||
170 | } |
||
171 | |||
172 | // Keep track of how many headers have been sent |
||
173 | private static $headerCount = 0; |
||
174 | |||
175 | /** |
||
176 | * Send a debug message in an HTTP header. Only works if you are |
||
177 | * on Dev, and headers have not yet been sent. |
||
178 | * |
||
179 | * @param string $msg |
||
180 | * @param string $prefix (optional) |
||
181 | * @return void |
||
182 | */ |
||
183 | public static function header($msg, $prefix = null) { |
||
189 | |||
190 | /** |
||
191 | * Log to a standard text file output. |
||
192 | * |
||
193 | * @param $message string to output |
||
194 | */ |
||
195 | public static function log($message) { |
||
196 | if (defined('BASE_PATH')) { |
||
197 | $path = BASE_PATH; |
||
198 | } |
||
199 | else { |
||
200 | $path = dirname(__FILE__) . '/../..'; |
||
201 | } |
||
202 | $file = $path . '/debug.log'; |
||
203 | $now = date('r'); |
||
204 | $content = "\n\n== $now ==\n$message\n"; |
||
205 | file_put_contents($file, $content, FILE_APPEND); |
||
206 | } |
||
207 | |||
208 | /** |
||
209 | * Load error handlers into environment. |
||
210 | * Caution: The error levels default to E_ALL is the site is in dev-mode (set in main.php). |
||
211 | */ |
||
212 | public static function loadErrorHandlers() { |
||
216 | |||
217 | View Code Duplication | public static function noticeHandler($errno, $errstr, $errfile, $errline, $errcontext) { |
|
239 | |||
240 | /** |
||
241 | * Handle a non-fatal warning error thrown by PHP interpreter. |
||
242 | * |
||
243 | * @param unknown_type $errno |
||
244 | * @param unknown_type $errstr |
||
245 | * @param unknown_type $errfile |
||
246 | * @param unknown_type $errline |
||
247 | * @param unknown_type $errcontext |
||
248 | */ |
||
249 | View Code Duplication | public static function warningHandler($errno, $errstr, $errfile, $errline, $errcontext) { |
|
271 | |||
272 | /** |
||
273 | * Handle a fatal error, depending on the mode of the site (ie: Dev, Test, or Live). |
||
274 | * |
||
275 | * Runtime execution dies immediately once the error is generated. |
||
276 | * |
||
277 | * @param unknown_type $errno |
||
278 | * @param unknown_type $errstr |
||
279 | * @param unknown_type $errfile |
||
280 | * @param unknown_type $errline |
||
281 | * @param unknown_type $errcontext |
||
282 | */ |
||
283 | public static function fatalHandler($errno, $errstr, $errfile, $errline, $errcontext) { |
||
305 | |||
306 | /** |
||
307 | * Render a user-facing error page, using the default HTML error template |
||
308 | * rendered by {@link ErrorPage} if it exists. Doesn't use the standard {@link SS_HTTPResponse} class |
||
309 | * the keep dependencies minimal. |
||
310 | * |
||
311 | * @uses ErrorPage |
||
312 | * |
||
313 | * @param int $statusCode HTTP Status Code (Default: 500) |
||
314 | * @param string $friendlyErrorMessage User-focused error message. Should not contain code pointers |
||
315 | * or "tech-speak". Used in the HTTP Header and ajax responses. |
||
316 | * @param string $friendlyErrorDetail Detailed user-focused message. Is just used if no {@link ErrorPage} is found |
||
317 | * for this specific status code. |
||
318 | * @return string HTML error message for non-ajax requests, plaintext for ajax-request. |
||
319 | */ |
||
320 | public static function friendlyError($statusCode=500, $friendlyErrorMessage=null, $friendlyErrorDetail=null) { |
||
369 | |||
370 | /** |
||
371 | * Create an instance of an appropriate DebugView object. |
||
372 | * |
||
373 | * @return DebugView |
||
374 | */ |
||
375 | public static function create_debug_view() { |
||
381 | |||
382 | /** |
||
383 | * Render a developer facing error page, showing the stack trace and details |
||
384 | * of the code where the error occured. |
||
385 | * |
||
386 | * @param unknown_type $errno |
||
387 | * @param unknown_type $errstr |
||
388 | * @param unknown_type $errfile |
||
389 | * @param unknown_type $errline |
||
390 | * @param unknown_type $errcontext |
||
391 | */ |
||
392 | public static function showError($errno, $errstr, $errfile, $errline, $errcontext, $errtype) { |
||
434 | |||
435 | /** |
||
436 | * Utility method to render a snippet of PHP source code, from selected file |
||
437 | * and highlighting the given line number. |
||
438 | * |
||
439 | * @param string $errfile |
||
440 | * @param int $errline |
||
441 | */ |
||
442 | public static function showLines($errfile, $errline) { |
||
459 | |||
460 | /** |
||
461 | * Check if the user has permissions to run URL debug tools, |
||
462 | * else redirect them to log in. |
||
463 | */ |
||
464 | public static function require_developer_login() { |
||
503 | } |
||
504 | |||
566 |
If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.
Let’s take a look at an example:
Our function
my_function
expects aPost
object, and outputs the author of the post. The base classPost
returns a simple string and outputting a simple string will work just fine. However, the child classBlogPost
which is a sub-type ofPost
instead decided to return anobject
, and is therefore violating the SOLID principles. If aBlogPost
were passed tomy_function
, PHP would not complain, but ultimately fail when executing thestrtoupper
call in its body.