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 Postcode 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 Postcode, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
9 | class Postcode |
||
10 | { |
||
11 | |||
12 | /** |
||
13 | * Postcode To Constituency |
||
14 | */ |
||
15 | |||
16 | 2 | public static function postcodeToConstituency($postcode) { |
|
19 | |||
20 | /** |
||
21 | * Postcode To Constituencies |
||
22 | */ |
||
23 | |||
24 | 1 | public static function postcodeToConstituencies($postcode) { |
|
27 | |||
28 | /** |
||
29 | * Postcode To Constituencies (Internal) |
||
30 | * |
||
31 | * @param boolean $mp_only |
||
32 | */ |
||
33 | |||
34 | 3 | private static function postcodeToConstituenciesInternal($postcode, $mp_only) { |
|
35 | 3 | global $last_postcode, $last_postcode_value; |
|
36 | |||
37 | 3 | $postcode = preg_replace('#[^a-z0-9]#i', '', $postcode); |
|
38 | 3 | $postcode = self::canonicalisePostcode($postcode); |
|
39 | |||
40 | 3 | if ($last_postcode == $postcode) { |
|
41 | 1 | $return_value = $mp_only ? $last_postcode_value['WMC'] : $last_postcode_value; |
|
42 | 1 | twfy_debug ("TIME", "Postcode $postcode looked up last time, is " . ( is_array($return_value) ? implode(', ', $return_value) : $return_value )); |
|
43 | 1 | return $return_value; |
|
44 | } |
||
45 | |||
46 | 3 | if (!validate_postcode($postcode)) { |
|
47 | 1 | return ''; |
|
48 | } |
||
49 | |||
50 | 2 | $ret = self::postcodeFetchFromDb($postcode); |
|
51 | 2 | if (!$ret) { |
|
52 | $ret = self::postcodeFetchFromMapit($postcode); |
||
53 | } |
||
54 | |||
55 | 2 | if (is_string($ret)) return $ret; |
|
56 | |||
57 | 2 | $last_postcode = $postcode; |
|
58 | 2 | $last_postcode_value = $ret; |
|
59 | 2 | return $mp_only ? $ret['WMC'] : $ret; |
|
60 | } |
||
61 | |||
62 | /** |
||
63 | * Fetch Postcode Information from DB |
||
64 | * |
||
65 | * @param string $postcode |
||
66 | */ |
||
67 | |||
68 | 2 | private static function postcodeFetchFromDb($postcode) { |
|
89 | |||
90 | /** |
||
91 | * Fetch Postcode Information from MapIt |
||
92 | * |
||
93 | * @param string $postcode |
||
94 | */ |
||
95 | |||
96 | private static function postcodeFetchFromMapit($postcode) { |
||
97 | if (!defined('OPTION_MAPIT_URL') || !OPTION_MAPIT_URL) { |
||
98 | return ''; |
||
99 | } |
||
100 | $filename = 'postcode/' . rawurlencode($postcode); |
||
101 | $ch = curl_init(OPTION_MAPIT_URL . $filename); |
||
102 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); |
||
103 | curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20); |
||
104 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); |
||
105 | curl_setopt($ch, CURLOPT_TIMEOUT, 10); |
||
106 | $file = curl_exec($ch); |
||
107 | if (curl_errno($ch)) { |
||
108 | $errno = curl_errno($ch); |
||
109 | trigger_error("Postcode database: " . $errno . ' ' . curl_error($ch), E_USER_WARNING); |
||
110 | return 'CONNECTION_TIMED_OUT'; |
||
111 | } |
||
112 | curl_close($ch); |
||
113 | |||
114 | $r = json_decode($file, true); |
||
115 | if (!$r) { |
||
116 | trigger_error("Postcode database is not working. Content:\n".$file.", request: ". $filename, E_USER_WARNING); |
||
117 | return ''; |
||
118 | } |
||
119 | if (isset($r['error']) || !isset($r['areas'])) { |
||
120 | return ''; |
||
121 | } |
||
122 | $areas = array(); |
||
123 | foreach ($r['areas'] as $row) { |
||
124 | if (in_array($row['type'], array('WMC', 'SPC', 'SPE', 'NIE'))) |
||
125 | $areas[$row['type']] = utf8_decode($row['name']); |
||
126 | } |
||
127 | |||
128 | if (!isset($areas['WMC'])) { |
||
129 | return ''; |
||
130 | } |
||
131 | |||
132 | # Normalise name - assume SP and NI are already so... |
||
133 | $normalised = Constituencies::normaliseConstituencyName(strtolower($areas['WMC'])); |
||
134 | if ($normalised) { |
||
135 | $areas['WMC'] = $normalised; |
||
136 | if (self::postcodeIsScottish($postcode)) { |
||
137 | $serialized = "$areas[WMC]|$areas[SPC]|$areas[SPE]"; |
||
138 | } elseif (self::postcodeIsNi($postcode)) { |
||
139 | $serialized = "$areas[WMC]|$areas[NIE]"; |
||
140 | } else { |
||
141 | $serialized = $normalised; |
||
142 | } |
||
143 | $db = new \ParlDB; |
||
144 | $db->query('replace into postcode_lookup values(:postcode, :serialized)', |
||
145 | array( |
||
146 | ':postcode' => $postcode, |
||
147 | ':serialized' => $serialized |
||
148 | )); |
||
149 | } else { |
||
150 | return ''; |
||
151 | } |
||
152 | |||
153 | return $areas; |
||
154 | } |
||
155 | |||
156 | /** |
||
157 | * Canonicalise Postcode |
||
158 | * |
||
159 | * Take a postcode and turn it into a tidied, uppercased canonical version. |
||
160 | */ |
||
161 | |||
162 | 6 | public static function canonicalisePostcode($pc) { |
|
169 | |||
170 | /** |
||
171 | * Is Postcode Scottish? |
||
172 | */ |
||
173 | |||
174 | 3 | public static function postcodeIsScottish($pc) { |
|
211 | |||
212 | /** |
||
213 | * Is Postcode Northern Irish? |
||
214 | */ |
||
215 | |||
216 | 3 | public static function postcodeIsNi($pc) { |
|
222 | |||
223 | } |
||
224 | |||
225 |