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 Elk_Autoloader 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 Elk_Autoloader, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
21 | class Elk_Autoloader |
||
22 | { |
||
23 | /** |
||
24 | * Instance manager |
||
25 | * @var Elk_Autoloader |
||
26 | */ |
||
27 | private static $_instance; |
||
28 | |||
29 | /** |
||
30 | * Stores whether the autoloader has been initialized |
||
31 | * @var boolean |
||
32 | */ |
||
33 | protected $_setup = false; |
||
34 | |||
35 | /** |
||
36 | * Stores whether the autoloader verifies file existence or not |
||
37 | * @var boolean |
||
38 | */ |
||
39 | protected $_strict = false; |
||
40 | |||
41 | /** |
||
42 | * Stores whether the autoloader verifies file existence or not for each |
||
43 | * namespace separately |
||
44 | * @var boolean |
||
45 | */ |
||
46 | protected $_strict_namespace = array(); |
||
47 | |||
48 | /** |
||
49 | * Path to directory containing ElkArte |
||
50 | * @var string |
||
51 | */ |
||
52 | protected $_dir = '.'; |
||
53 | |||
54 | /** |
||
55 | * Holds the name of the exploded class name |
||
56 | * @var array |
||
57 | */ |
||
58 | protected $_name; |
||
59 | |||
60 | /** |
||
61 | * Holds the class name ahead of any _ if any |
||
62 | * @var string |
||
63 | */ |
||
64 | protected $_surname; |
||
65 | |||
66 | /** |
||
67 | * Holds the class name after the first _ if any |
||
68 | * @var string |
||
69 | */ |
||
70 | protected $_givenname; |
||
71 | |||
72 | /** |
||
73 | * The current namespace |
||
74 | * @var string|false |
||
75 | */ |
||
76 | protected $_current_namespace; |
||
77 | |||
78 | /** |
||
79 | * Holds the name full file name of the file to load (require) |
||
80 | * @var string|boolean |
||
81 | */ |
||
82 | protected $_file_name = false; |
||
83 | |||
84 | /** |
||
85 | * Holds the pairs namespace => paths |
||
86 | * @var array |
||
87 | */ |
||
88 | protected $_paths; |
||
89 | |||
90 | /** |
||
91 | * Constructor, not used, instead use getInstance() |
||
92 | */ |
||
93 | protected function __construct() |
||
94 | { |
||
95 | } |
||
96 | |||
97 | /** |
||
98 | * Setup the autoloader environment |
||
99 | * |
||
100 | * @param string|string[] $dir |
||
101 | */ |
||
102 | public function setupAutoloader($dir) |
||
103 | { |
||
104 | if (!is_array($dir)) |
||
105 | { |
||
106 | $dir = array($dir); |
||
107 | } |
||
108 | |||
109 | foreach ($dir as $path) |
||
110 | { |
||
111 | $this->register($path, '\\' . strtr($path, array(BOARDDIR => 'ElkArte', '/' => '\\'))); |
||
112 | } |
||
113 | } |
||
114 | |||
115 | /** |
||
116 | * Registers new paths for the autoloader |
||
117 | * |
||
118 | * @param string $dir |
||
119 | * @param string|null $namespace |
||
120 | * @param bool $strict |
||
121 | */ |
||
122 | 22 | public function register($dir, $namespace = null, $strict = false) |
|
123 | { |
||
124 | 22 | if ($namespace === null) |
|
125 | 22 | { |
|
126 | $namespace = 0; |
||
127 | } |
||
128 | |||
129 | 22 | if (!isset($this->_paths[$namespace])) |
|
130 | 22 | { |
|
131 | 1 | $this->_paths[$namespace] = array(); |
|
132 | 1 | } |
|
133 | |||
134 | 22 | $this->_paths[$namespace][] = $dir; |
|
135 | 22 | $this->_paths[$namespace] = array_unique($this->_paths[$namespace]); |
|
136 | 22 | $this->_strict_namespace[$namespace] = $strict; |
|
137 | |||
138 | 22 | $this->_buildPaths((array) $dir); |
|
139 | 22 | } |
|
140 | |||
141 | /** |
||
142 | * Build the directory path names to search for files to autoload |
||
143 | * |
||
144 | * @param array $dir |
||
145 | */ |
||
146 | 22 | protected function _buildPaths($dir) |
|
147 | { |
||
148 | // Build the paths where we are going to look for files |
||
149 | 22 | foreach ($dir as $include) |
|
150 | { |
||
151 | 22 | $this->_dir .= $include . PATH_SEPARATOR; |
|
152 | 22 | } |
|
153 | |||
154 | // Initialize |
||
155 | 22 | $this->_setupAutoloader(); |
|
156 | 22 | $this->_setup = true; |
|
157 | 22 | } |
|
158 | |||
159 | /** |
||
160 | * Method that actually registers the autoloader. |
||
161 | */ |
||
162 | 22 | protected function _setupAutoloader() |
|
163 | { |
||
164 | // Make sure our paths are in the include path |
||
165 | 22 | set_include_path($this->_dir . '.' . (!@ini_get('open_basedir') ? PATH_SEPARATOR . get_include_path() : '')); |
|
166 | |||
167 | // The autoload "magic" |
||
168 | 22 | if (!$this->_setup) |
|
169 | 22 | { |
|
170 | spl_autoload_register(array($this, 'elk_autoloader')); |
||
171 | } |
||
172 | 22 | } |
|
173 | |||
174 | /** |
||
175 | * Callback for the spl_autoload_register, loads the requested class |
||
176 | * |
||
177 | * @param string $class |
||
178 | */ |
||
179 | 19 | public function elk_autoloader($class) |
|
180 | { |
||
181 | // Break the class name in to its parts |
||
182 | 19 | if (!$this->_string_to_class($class)) |
|
183 | 19 | { |
|
184 | return false; |
||
185 | } |
||
186 | |||
187 | // If passed a namespace, /name/space/class |
||
188 | 19 | if ($this->_current_namespace !== false) |
|
189 | 19 | { |
|
190 | 10 | $this->_handle_namespaces(); |
|
191 | 10 | } |
|
192 | |||
193 | // Basic cases like Util.class, Action.class, Request.class |
||
194 | 19 | if ($this->_file_name === false) |
|
195 | 19 | { |
|
196 | 13 | $this->_handle_basic_cases(); |
|
197 | 13 | } |
|
198 | |||
199 | // All the rest |
||
200 | 19 | if ($this->_file_name === false) |
|
201 | 19 | { |
|
202 | 11 | $this->_handle_other_cases(); |
|
203 | 11 | } |
|
204 | |||
205 | 19 | $file = $this->_file_name; |
|
206 | |||
207 | // Start fresh for the next one |
||
208 | 19 | $this->_file_name = false; |
|
209 | |||
210 | // Well do we have something to do? |
||
211 | 19 | if (!empty($file)) |
|
212 | 19 | { |
|
213 | // Are we going to validate the file exists? |
||
214 | 18 | if ($this->_strict) |
|
215 | 18 | { |
|
216 | if (stream_resolve_include_path($file)) |
||
217 | { |
||
218 | require_once($file); |
||
219 | } |
||
220 | } |
||
221 | else |
||
222 | { |
||
223 | 18 | require_once($file); |
|
224 | } |
||
225 | |||
226 | 18 | $this->_strict = false; |
|
227 | 18 | } |
|
228 | |||
229 | 19 | return true; |
|
230 | } |
||
231 | |||
232 | /** |
||
233 | * Resolves a class name to an autoload name |
||
234 | * |
||
235 | * @param string $class - Name of class to autoload |
||
236 | */ |
||
237 | 19 | private function _string_to_class($class) |
|
286 | |||
287 | /** |
||
288 | * This handles any case where a namespace is present. |
||
289 | * |
||
290 | * @return boolean|null false if the namespace was found, but the file not, true otherwise |
||
291 | */ |
||
292 | 10 | protected function _handle_namespaces() |
|
314 | |||
315 | /** |
||
316 | * This handles the simple cases, mostly single word class names. |
||
317 | * |
||
318 | * - Bypasses db classes as those are done elsewhere |
||
319 | */ |
||
320 | 13 | private function _handle_basic_cases() |
|
361 | |||
362 | /** |
||
363 | * This handles Some_Controller style classes |
||
364 | */ |
||
365 | 11 | private function _handle_other_cases() |
|
461 | |||
462 | /** |
||
463 | * Returns the instance of the autoloader |
||
464 | * |
||
465 | * - Uses final definition to prevent child classes from overriding this method |
||
466 | */ |
||
467 | 20 | final public static function instance() |
|
468 | { |
||
469 | 20 | if (!self::$_instance) |
|
470 | 20 | { |
|
471 | self::$_instance = new self(); |
||
472 | } |
||
473 | |||
474 | 20 | return self::$_instance; |
|
475 | } |
||
476 | |||
477 | /** |
||
478 | * Manually sets the autoloader instance. |
||
479 | * |
||
480 | * - Use this to inject a modified version. |
||
481 | * |
||
482 | * @param Elk_Autoloader|null $loader |
||
483 | */ |
||
484 | public static function setInstance(Elk_Autoloader $loader = null) |
||
488 | } |
||
489 |