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:
1 | <?php |
||
13 | class Ipinfo |
||
14 | { |
||
15 | /** |
||
16 | * The base url of the ipinfo service. |
||
17 | * |
||
18 | * @var string |
||
19 | */ |
||
20 | const BASE_URL = 'https://ipinfo.io/'; |
||
21 | |||
22 | /** |
||
23 | * The ip string. |
||
24 | * |
||
25 | * @var string |
||
26 | */ |
||
27 | const IP = 'ip'; |
||
28 | |||
29 | /** |
||
30 | * The hostname string. |
||
31 | * |
||
32 | * @var string |
||
33 | */ |
||
34 | const HOSTNAME = 'hostname'; |
||
35 | |||
36 | /** |
||
37 | * The loc string. |
||
38 | * |
||
39 | * @var string |
||
40 | */ |
||
41 | const LOC = 'loc'; |
||
42 | |||
43 | /** |
||
44 | * The org string. |
||
45 | * |
||
46 | * @var string |
||
47 | */ |
||
48 | const ORG = 'org'; |
||
49 | |||
50 | /** |
||
51 | * The city string. |
||
52 | * |
||
53 | * @var string |
||
54 | */ |
||
55 | const CITY = 'city'; |
||
56 | |||
57 | /** |
||
58 | * The region string. |
||
59 | * |
||
60 | * @var string |
||
61 | */ |
||
62 | const REGION = 'region'; |
||
63 | |||
64 | /** |
||
65 | * The country string. |
||
66 | * |
||
67 | * @var string |
||
68 | */ |
||
69 | const COUNTRY = 'country'; |
||
70 | |||
71 | /** |
||
72 | * The phone string. |
||
73 | * |
||
74 | * @var string |
||
75 | */ |
||
76 | const PHONE = 'phone'; |
||
77 | |||
78 | /** |
||
79 | * The geo string. |
||
80 | * |
||
81 | * @var string |
||
82 | */ |
||
83 | const GEO = 'geo'; |
||
84 | |||
85 | /** |
||
86 | * The postal string. |
||
87 | * |
||
88 | * @var string |
||
89 | */ |
||
90 | const POSTAL = 'postal'; |
||
91 | |||
92 | /** |
||
93 | * All the settings. |
||
94 | * |
||
95 | * @var array |
||
96 | */ |
||
97 | protected $settings; |
||
98 | |||
99 | /** |
||
100 | * Create an Ipinfo instance. |
||
101 | * |
||
102 | * @param array $settings An array with all the settings. |
||
103 | * Supported keys are: |
||
104 | * - token: string the developer token; |
||
105 | * - debug: boolean active or not the debug. |
||
106 | */ |
||
107 | 11 | public function __construct($settings = array()) |
|
116 | |||
117 | /** |
||
118 | * Get all the info about your own ip address. |
||
119 | * |
||
120 | * @return \DavidePastore\Ipinfo\Host The Host object with all the info. |
||
121 | * @throws InvalidTokenException |
||
122 | * @throws RateLimitExceedException |
||
123 | */ |
||
124 | 2 | View Code Duplication | public function getYourOwnIpDetails() |
|
|||
125 | { |
||
126 | 2 | $response = $this->makeCurlRequest($this::BASE_URL.'json'); |
|
127 | 2 | $response = $this->jsonDecodeResponse($response); |
|
128 | |||
129 | 1 | return new Host($response); |
|
130 | } |
||
131 | |||
132 | /** |
||
133 | * Get all the info about an ip address. |
||
134 | * |
||
135 | * @param string $ipAddress The ip address. |
||
136 | * |
||
137 | * @return \DavidePastore\Ipinfo\Host The Host object with all the info. |
||
138 | * @throws InvalidTokenException |
||
139 | * @throws RateLimitExceedException |
||
140 | */ |
||
141 | 2 | View Code Duplication | public function getFullIpDetails($ipAddress) |
142 | { |
||
143 | 2 | $response = $this->makeCurlRequest($this::BASE_URL.$ipAddress); |
|
144 | 2 | $response = $this->jsonDecodeResponse($response); |
|
145 | |||
146 | 2 | return new Host($response); |
|
147 | } |
||
148 | |||
149 | /** |
||
150 | * Get a specific field value. |
||
151 | * |
||
152 | * @param string $ipAddress The ip address. |
||
153 | * @param string $field The field. |
||
154 | * |
||
155 | * @return string|\DavidePastore\Ipinfo\Host The value of the given field for the given ip. |
||
156 | * This could returns an Host object if you call it with for the field |
||
157 | * \DavidePastore\Ipinfo\Ipinfo::GEO. |
||
158 | * @throws InvalidTokenException |
||
159 | * @throws RateLimitExceedException |
||
160 | */ |
||
161 | 6 | View Code Duplication | public function getSpecificField($ipAddress, $field) |
162 | { |
||
163 | 6 | $response = $this->makeCurlRequest($this::BASE_URL.$ipAddress.'/'.$field); |
|
164 | 6 | $response = $this->checkGeo($field, $response); |
|
165 | |||
166 | 5 | return $response; |
|
167 | } |
||
168 | |||
169 | /** |
||
170 | * Get a specific field value of your own ip address. |
||
171 | * |
||
172 | * @param string $field The field. |
||
173 | * |
||
174 | * @return string|\DavidePastore\Ipinfo\Host The value of the given field for your own ip. |
||
175 | * This could returns an Host object if you call it with for the field |
||
176 | * \DavidePastore\Ipinfo\Ipinfo::GEO. |
||
177 | * @throws InvalidTokenException |
||
178 | * @throws RateLimitExceedException |
||
179 | */ |
||
180 | 1 | View Code Duplication | public function getYourOwnIpSpecificField($field) |
181 | { |
||
182 | 1 | $response = $this->makeCurlRequest($this::BASE_URL.$field); |
|
183 | 1 | $response = $this->checkGeo($field, $response); |
|
184 | |||
185 | 1 | return $response; |
|
186 | } |
||
187 | |||
188 | /** |
||
189 | * Use the /geo call to get just the geolocation information, which will often be |
||
190 | * faster than getting the full response. |
||
191 | * |
||
192 | * @param string $ipAddress The ip address. |
||
193 | * |
||
194 | * @return \DavidePastore\Ipinfo\Host |
||
195 | * @throws InvalidTokenException |
||
196 | * @throws RateLimitExceedException |
||
197 | */ |
||
198 | 2 | public function getIpGeoDetails($ipAddress) |
|
199 | { |
||
200 | 2 | return $this->getSpecificField($ipAddress, $this::GEO); |
|
201 | } |
||
202 | |||
203 | /** |
||
204 | * Check if the response is GEO and set the parameters accordingly. |
||
205 | * |
||
206 | * @param string $field The field value. |
||
207 | * @param string $response The response from the server. |
||
208 | * |
||
209 | * @return \DavidePastore\Ipinfo\Host|string Returns an Host object if the request is |
||
210 | * of the GEO type, a string otherwise. If the field value is different from the GEO type, it will |
||
211 | * delete the last character ('\n'). |
||
212 | * @throws InvalidTokenException |
||
213 | * @throws RateLimitExceedException |
||
214 | */ |
||
215 | 7 | private function checkGeo($field, $response) |
|
216 | { |
||
217 | 7 | if ($field == $this::GEO) { |
|
218 | 2 | $response = $this->jsonDecodeResponse($response); |
|
219 | 2 | $response = new Host($response); |
|
220 | } else { |
||
221 | 5 | $this->checkErrors($response); |
|
222 | 4 | $response = substr($response, 0, -1); |
|
223 | } |
||
224 | |||
225 | 6 | return $response; |
|
226 | } |
||
227 | |||
228 | /** |
||
229 | * Make a curl request. |
||
230 | * |
||
231 | * @param string $address The address of the request. |
||
232 | * |
||
233 | * @return string Returns the response from the request. |
||
234 | */ |
||
235 | 11 | private function makeCurlRequest($address) |
|
236 | { |
||
237 | 11 | $curl = curl_init(); |
|
238 | |||
239 | 11 | if (!empty($this->settings['token'])) { |
|
240 | 2 | $address .= '?token='.$this->settings['token']; |
|
241 | } |
||
242 | |||
243 | 11 | if ($this->settings['debug']) { |
|
244 | 1 | echo 'Request address: '.$address."\n"; |
|
245 | } |
||
246 | |||
247 | 11 | curl_setopt_array($curl, |
|
248 | 11 | array_replace($this->settings['curlOptions'], |
|
249 | array( |
||
250 | 11 | CURLOPT_RETURNTRANSFER => 1, |
|
251 | 11 | CURLOPT_URL => $address |
|
252 | ) |
||
253 | ) |
||
254 | ); |
||
255 | |||
256 | 11 | $response = curl_exec($curl); |
|
257 | |||
258 | 11 | if ($response === false && $this->settings['debug']) { |
|
259 | $error = curl_error($curl); |
||
260 | echo "The error is".$error; |
||
261 | } |
||
262 | |||
263 | 11 | curl_close($curl); |
|
264 | |||
265 | 11 | return $response; |
|
266 | } |
||
267 | |||
268 | /** |
||
269 | * Returns the json decoded associative array. |
||
270 | * @param string $response Response from the http call. |
||
271 | * @return array Returns the associative array with the response. |
||
272 | * @throws RateLimitExceedException If you exceed the rate limit. |
||
273 | * @throws InvalidTokenException If the used token is invalid. |
||
274 | */ |
||
275 | 6 | private function jsonDecodeResponse($response) |
|
276 | { |
||
277 | 6 | if ($response) { |
|
278 | // Check if the response contains an error message |
||
279 | 5 | $this->checkErrors($response); |
|
280 | 4 | $response = json_decode($response, true); |
|
281 | } else { |
||
282 | 1 | $response = array(); |
|
283 | } |
||
284 | 5 | return $response; |
|
285 | } |
||
286 | |||
287 | /** |
||
288 | * Check if the given response has some kind of errors. |
||
289 | * @param string $response The response to check. |
||
290 | * @throws RateLimitExceedException If you exceed the rate limit. |
||
291 | * @throws InvalidTokenException If the used token is invalid. |
||
292 | */ |
||
293 | 10 | private function checkErrors($response) |
|
301 | } |
||
302 |
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.