Failed Conditions
Branch master (215e8c)
by Johannes
04:26
created

Postcode   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 215
Duplicated Lines 1.86 %

Coupling/Cohesion

Components 1
Dependencies 3

Test Coverage

Coverage 44.26%

Importance

Changes 0
Metric Value
dl 4
loc 215
ccs 54
cts 122
cp 0.4426
rs 6.433
c 0
b 0
f 0
wmc 57
lcom 1
cbo 3

8 Methods

Rating   Name   Duplication   Size   Complexity  
A postcodeToConstituency() 0 3 1
A postcodeToConstituencies() 0 3 1
B postcodeFetchFromDb() 4 21 6
A canonicalisePostcode() 0 7 1
D postcodeIsScottish() 0 37 25
A postcodeIsNi() 0 6 2
C postcodeToConstituenciesInternal() 0 27 8
C postcodeFetchFromMapit() 0 59 13

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

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
2
namespace MySociety\TheyWorkForYou\Utility;
3
4
/**
5
 * Postcode Utilities
6
 *
7
 * Utility functions related to postcodes
8
 */
9
class Postcode
10
{
11
12
    /**
13
     * Postcode To Constituency
14
     */
15
16 2
    public static function postcodeToConstituency($postcode) {
17 2
        return self::postcodeToConstituenciesInternal($postcode, true);
18
    }
19
20
    /**
21
     * Postcode To Constituencies
22
     */
23
24 1
    public static function postcodeToConstituencies($postcode) {
25 1
        return self::postcodeToConstituenciesInternal($postcode, false);
26
    }
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) {
69 2
        $db = new \ParlDB;
70 2
        $q = $db->query('select name from postcode_lookup where postcode = :postcode', array(
71
            ':postcode' => $postcode
72 2
            ));
73
74 2
        if ($q->rows > 0) {
75 2
            $name = $q->field(0, 'name');
76 2
            if (self::postcodeIsScottish($postcode)) {
77 1
                $name = explode('|', $name);
78 1 View Code Duplication
                if (count($name)==3)
79 1
                    return array('WMC' => $name[0], 'SPC' => $name[1], 'SPE' => $name[2]);
80 2
            } elseif (self::postcodeIsNi($postcode)) {
81 1
                $name = explode('|', $name);
82 1 View Code Duplication
                if (count($name)==2)
83 1
                    return array('WMC' => $name[0], 'NIE' => $name[1]);
84
            } else {
85 1
                return array('WMC' => $name);
86
            }
87
        }
88
    }
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) {
163 6
        $pc = str_replace(' ', '', $pc);
164 6
        $pc = trim($pc);
165 6
        $pc = strtoupper($pc);
166 6
        $pc = preg_replace('#(\d[A-Z]{2})#', ' $1', $pc);
167 6
        return $pc;
168
    }
169
170
    /**
171
     * Is Postcode Scottish?
172
     */
173
174 3
    public static function postcodeIsScottish($pc) {
175 3
        if (!preg_match('#^([A-Z]{1,2})(\d+) (\d)([A-Z]{2})#', self::canonicalisePostcode($pc), $m))
176 3
            return false;
177
178
        # Check for Scottish postal areas
179 2
        if (in_array($m[1], array('AB', 'DD', 'EH', 'FK', 'G', 'HS', 'IV', 'KA', 'KW', 'KY', 'ML', 'PA', 'PH', 'ZE')))
180 2
            return true;
181
182 1
        if ($m[1]=='DG') {
183
            if ($m[2]==16 && $m[3]==5 && in_array($m[4], array('HT','HU','HZ','JA','JB'))) return false; # A few postcodes in England
184
            return true;
185
        }
186
187
        # Damn postcodes crossing country boundaries
188 1
        if ($m[1]=='TD') {
189
            if ($m[2]!=15 && $m[2]!=12 && $m[2]!=9) return true; # TD1-8, 10-11, 13-14 all in Scotland
190
            if ($m[2]==9) {
191
                if ($m[3]!=0) return true; # TD9 1-9 all in Scotland
192
                if (!in_array($m[4], array('TJ','TP','TR','TS','TT','TU','TW'))) return true; # Nearly all of TD9 0 in Scotland
193
            }
194
            $m[5] = substr($m[4], 0, 1);
195
            if ($m[2]==12) { # $m[3] will be 4 currently.
196
                if ($m[4]=='XE') return true;
197
                if (in_array($m[5], array('A','B','D','E','H','J','L','N','W','Y'))) return true; # These bits of TD12 4 are in Scotland, others (Q, R, S, T, U, X) in England
198
            }
199
            # TD15 is mostly England
200
            if ($m[2]==15) {
201
                if ($m[3]!=1) return false; # TD15 2 and 9 are in England
202
                if (in_array($m[4], array('BT','SU','SZ','UF','UG','UH','UJ','UL','US','UZ','WY','WZ'))) return true;
203
                if ($m[5]=='T' && $m[4]!='TA' && $m[4]!='TB') return true; # Most of TD15 1T* in Scotland
204
                if ($m[5]=='X' && $m[4]!='XX') return true; # TD15 1XX in England, rest of TD15 1X* in Scotland
205
            }
206
        }
207
208
        # Not in Scotland
209 1
        return false;
210
    }
211
212
    /**
213
     * Is Postcode Northern Irish?
214
     */
215
216 3
    public static function postcodeIsNi($pc) {
217 3
        $prefix = substr(self::canonicalisePostcode($pc), 0, 2);
218 3
        if ($prefix == 'BT')
219 3
            return true;
220 2
        return false;
221
    }
222
223
}
224
225