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 WebApiContext 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 WebApiContext, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
27 | class WebApiContext implements ApiClientAwareContext |
||
28 | { |
||
29 | /** |
||
30 | * @var string |
||
31 | */ |
||
32 | private $authorization; |
||
33 | |||
34 | /** |
||
35 | * @var ClientInterface |
||
36 | */ |
||
37 | private $client; |
||
38 | |||
39 | /** |
||
40 | * @var array |
||
41 | */ |
||
42 | private $headers = array(); |
||
43 | |||
44 | /** |
||
45 | * @var \GuzzleHttp\Message\RequestInterface|RequestInterface |
||
46 | */ |
||
47 | private $request; |
||
48 | |||
49 | /** |
||
50 | * @var \GuzzleHttp\Message\ResponseInterface|ResponseInterface |
||
51 | */ |
||
52 | private $response; |
||
53 | |||
54 | private $placeHolders = array(); |
||
55 | |||
56 | /** |
||
57 | * {@inheritdoc} |
||
58 | */ |
||
59 | public function setClient(ClientInterface $client) |
||
63 | |||
64 | /** |
||
65 | * Adds Basic Authentication header to next request. |
||
66 | * |
||
67 | * @param string $username |
||
68 | * @param string $password |
||
69 | * |
||
70 | * @Given /^I am authenticating as "([^"]*)" with "([^"]*)" password$/ |
||
71 | */ |
||
72 | public function iAmAuthenticatingAs($username, $password) |
||
78 | |||
79 | /** |
||
80 | * Sets a HTTP Header. |
||
81 | * |
||
82 | * @param string $name header name |
||
83 | * @param string $value header value |
||
84 | * |
||
85 | * @Given /^I set header "([^"]*)" with value "([^"]*)"$/ |
||
86 | */ |
||
87 | public function iSetHeaderWithValue($name, $value) |
||
91 | |||
92 | /** |
||
93 | * Sends HTTP request to specific relative URL. |
||
94 | * |
||
95 | * @param string $method request method |
||
96 | * @param string $url relative url |
||
97 | * |
||
98 | * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)"$/ |
||
99 | */ |
||
100 | public function iSendARequest($method, $url) |
||
115 | |||
116 | /** |
||
117 | * Sends HTTP request to specific URL with field values from Table. |
||
118 | * |
||
119 | * @param string $method request method |
||
120 | * @param string $url relative url |
||
121 | * @param TableNode $post table of post values |
||
122 | * |
||
123 | * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with values:$/ |
||
124 | */ |
||
125 | public function iSendARequestWithValues($method, $url, TableNode $post) |
||
149 | |||
150 | /** |
||
151 | * Sends HTTP request to specific URL with raw body from PyString. |
||
152 | * |
||
153 | * @param string $method request method |
||
154 | * @param string $url relative url |
||
155 | * @param PyStringNode $string request body |
||
156 | * |
||
157 | * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with body:$/ |
||
158 | */ |
||
159 | public function iSendARequestWithBody($method, $url, PyStringNode $string) |
||
179 | |||
180 | /** |
||
181 | * Sends HTTP request to specific URL with form data from PyString. |
||
182 | * |
||
183 | * @param string $method request method |
||
184 | * @param string $url relative url |
||
185 | * @param PyStringNode $body request body |
||
186 | * |
||
187 | * @When /^(?:I )?send a ([A-Z]+) request to "([^"]+)" with form data:$/ |
||
188 | */ |
||
189 | public function iSendARequestWithFormData($method, $url, PyStringNode $body) |
||
210 | |||
211 | /** |
||
212 | * Checks that response has specific status code. |
||
213 | * |
||
214 | * @param string $code status code |
||
215 | * |
||
216 | * @Then /^(?:the )?response code should be (\d+)$/ |
||
217 | */ |
||
218 | public function theResponseCodeShouldBe($code) |
||
224 | |||
225 | /** |
||
226 | * Checks that the response has specific header key/value |
||
227 | * |
||
228 | * @param string $key header key |
||
229 | * @param string $value header value |
||
230 | * |
||
231 | * @Then the response header :key should be :value |
||
232 | */ |
||
233 | public function theResponseHeaderShouldBe($key, $value) |
||
237 | |||
238 | /** |
||
239 | * Checks that response body contains specific text. |
||
240 | * |
||
241 | * @param string $text |
||
242 | * |
||
243 | * @Then /^(?:the )?response should contain "([^"]*)"$/ |
||
244 | */ |
||
245 | public function theResponseShouldContain($text) |
||
251 | |||
252 | /** |
||
253 | * Checks that response body doesn't contains specific text. |
||
254 | * |
||
255 | * @param string $text |
||
256 | * |
||
257 | * @Then /^(?:the )?response should not contain "([^"]*)"$/ |
||
258 | */ |
||
259 | public function theResponseShouldNotContain($text) |
||
265 | |||
266 | /** |
||
267 | * Checks that response body contains JSON from PyString. |
||
268 | * |
||
269 | * Do not check that the response body /only/ contains the JSON from PyString, |
||
270 | * |
||
271 | * @param PyStringNode $jsonString |
||
272 | * |
||
273 | * @throws \RuntimeException |
||
274 | * |
||
275 | * @Then /^(?:the )?response should contain json:$/ |
||
276 | */ |
||
277 | public function theResponseShouldContainJson(PyStringNode $jsonString) |
||
300 | |||
301 | /** |
||
302 | * Prints last response body. |
||
303 | * |
||
304 | * @Then print response |
||
305 | */ |
||
306 | public function printResponse() |
||
319 | |||
320 | /** |
||
321 | * Prepare URL by replacing placeholders and trimming slashes. |
||
322 | * |
||
323 | * @param string $url |
||
324 | * |
||
325 | * @return string |
||
326 | */ |
||
327 | private function prepareUrl($url) |
||
331 | |||
332 | /** |
||
333 | * Sets place holder for replacement. |
||
334 | * |
||
335 | * you can specify placeholders, which will |
||
336 | * be replaced in URL, request or response body. |
||
337 | * |
||
338 | * @param string $key token name |
||
339 | * @param string $value replace value |
||
340 | */ |
||
341 | public function setPlaceHolder($key, $value) |
||
345 | |||
346 | /** |
||
347 | * Replaces placeholders in provided text. |
||
348 | * |
||
349 | * @param string $string |
||
350 | * |
||
351 | * @return string |
||
352 | */ |
||
353 | protected function replacePlaceHolder($string) |
||
361 | |||
362 | /** |
||
363 | * Returns headers, that will be used to send requests. |
||
364 | * |
||
365 | * @return array |
||
366 | */ |
||
367 | protected function getHeaders() |
||
371 | |||
372 | /** |
||
373 | * Adds header |
||
374 | * |
||
375 | * @param string $name |
||
376 | * @param string $value |
||
377 | */ |
||
378 | protected function addHeader($name, $value) |
||
390 | |||
391 | /** |
||
392 | * Removes a header identified by $headerName |
||
393 | * |
||
394 | * @param string $headerName |
||
395 | */ |
||
396 | protected function removeHeader($headerName) |
||
402 | |||
403 | private function sendRequest() |
||
415 | |||
416 | private function getClient() |
||
424 | } |
||
425 |
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.