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 CurlFtpAdapter 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 CurlFtpAdapter, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
14 | class CurlFtpAdapter extends AbstractFtpAdapter |
||
15 | { |
||
16 | protected $configurable = [ |
||
17 | 'host', |
||
18 | 'port', |
||
19 | 'username', |
||
20 | 'password', |
||
21 | 'root', |
||
22 | 'ssl', |
||
23 | ]; |
||
24 | |||
25 | /** @var Curl */ |
||
26 | protected $connection; |
||
27 | |||
28 | /** @var bool */ |
||
29 | protected $isPureFtpd; |
||
30 | |||
31 | /** |
||
32 | * Establish a connection. |
||
33 | */ |
||
34 | public function connect() |
||
35 | { |
||
36 | $this->connection = new Curl(); |
||
37 | $this->connection->setOptions([ |
||
38 | CURLOPT_URL => $this->getBaseUri(), |
||
39 | CURLOPT_USERPWD => $this->getUsername() . ':' . $this->getPassword(), |
||
40 | CURLOPT_SSL_VERIFYPEER => false, |
||
41 | CURLOPT_SSL_VERIFYHOST => false, |
||
42 | CURLOPT_FTPSSLAUTH => CURLFTPAUTH_TLS, |
||
43 | CURLOPT_RETURNTRANSFER => true, |
||
44 | ]); |
||
45 | |||
46 | if ($this->ssl) { |
||
47 | $this->connection->setOption(CURLOPT_FTP_SSL, CURLFTPSSL_ALL); |
||
48 | } |
||
49 | |||
50 | $this->pingConnection(); |
||
51 | $this->setConnectionRoot(); |
||
52 | } |
||
53 | |||
54 | /** |
||
55 | * Close the connection. |
||
56 | */ |
||
57 | public function disconnect() |
||
58 | { |
||
59 | if ($this->connection !== null) { |
||
60 | $this->connection = null; |
||
61 | } |
||
62 | $this->isPureFtpd = null; |
||
63 | } |
||
64 | |||
65 | /** |
||
66 | * Check if a connection is active. |
||
67 | * |
||
68 | * @return bool |
||
69 | */ |
||
70 | public function isConnected() |
||
71 | { |
||
72 | return $this->connection !== null; |
||
73 | } |
||
74 | |||
75 | /** |
||
76 | * Write a new file. |
||
77 | * |
||
78 | * @param string $path |
||
79 | * @param string $contents |
||
80 | * @param Config $config Config object |
||
81 | * |
||
82 | * @return array|false false on failure file meta data on success |
||
83 | */ |
||
84 | public function write($path, $contents, Config $config) |
||
85 | { |
||
86 | $stream = fopen('php://temp', 'w+b'); |
||
87 | fwrite($stream, $contents); |
||
88 | rewind($stream); |
||
89 | |||
90 | $result = $this->writeStream($path, $stream, $config); |
||
91 | |||
92 | if ($result === false) { |
||
93 | return false; |
||
94 | } |
||
95 | |||
96 | $result['contents'] = $contents; |
||
97 | $result['mimetype'] = Util::guessMimeType($path, $contents); |
||
98 | |||
99 | return $result; |
||
100 | } |
||
101 | |||
102 | /** |
||
103 | * Write a new file using a stream. |
||
104 | * |
||
105 | * @param string $path |
||
106 | * @param resource $resource |
||
107 | * @param Config $config Config object |
||
108 | * |
||
109 | * @return array|false false on failure file meta data on success |
||
110 | */ |
||
111 | public function writeStream($path, $resource, Config $config) |
||
112 | { |
||
113 | $connection = $this->getConnection(); |
||
114 | |||
115 | $result = $connection->exec([ |
||
116 | CURLOPT_URL => $this->getBaseUri() . '/' . $path, |
||
117 | CURLOPT_UPLOAD => 1, |
||
118 | CURLOPT_INFILE => $resource, |
||
119 | ]); |
||
120 | |||
121 | if ($result === false) { |
||
122 | return false; |
||
123 | } |
||
124 | |||
125 | $type = 'file'; |
||
126 | |||
127 | return compact('type', 'path'); |
||
128 | } |
||
129 | |||
130 | /** |
||
131 | * Update a file. |
||
132 | * |
||
133 | * @param string $path |
||
134 | * @param string $contents |
||
135 | * @param Config $config Config object |
||
136 | * |
||
137 | * @return array|false false on failure file meta data on success |
||
138 | */ |
||
139 | public function update($path, $contents, Config $config) |
||
140 | { |
||
141 | return $this->write($path, $contents, $config); |
||
142 | } |
||
143 | |||
144 | /** |
||
145 | * Update a file using a stream. |
||
146 | * |
||
147 | * @param string $path |
||
148 | * @param resource $resource |
||
149 | * @param Config $config Config object |
||
150 | * |
||
151 | * @return array|false false on failure file meta data on success |
||
152 | */ |
||
153 | public function updateStream($path, $resource, Config $config) |
||
154 | { |
||
155 | return $this->writeStream($path, $resource, $config); |
||
156 | } |
||
157 | |||
158 | /** |
||
159 | * Rename a file. |
||
160 | * |
||
161 | * @param string $path |
||
162 | * @param string $newpath |
||
163 | * |
||
164 | * @return bool |
||
165 | */ |
||
166 | public function rename($path, $newpath) |
||
167 | { |
||
168 | $connection = $this->getConnection(); |
||
169 | |||
170 | $response = $this->rawCommand($connection, 'RNFR ' . $path); |
||
171 | list($code) = explode(' ', end($response), 2); |
||
172 | if ((int) $code !== 350) { |
||
173 | return false; |
||
174 | } |
||
175 | |||
176 | $response = $this->rawCommand($connection, 'RNTO ' . $newpath); |
||
177 | list($code) = explode(' ', end($response), 2); |
||
178 | |||
179 | return (int) $code === 250; |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Copy a file. |
||
184 | * |
||
185 | * @param string $path |
||
186 | * @param string $newpath |
||
187 | * |
||
188 | * @return bool |
||
189 | */ |
||
190 | public function copy($path, $newpath) |
||
200 | |||
201 | /** |
||
202 | * Delete a file. |
||
203 | * |
||
204 | * @param string $path |
||
205 | * |
||
206 | * @return bool |
||
207 | */ |
||
208 | View Code Duplication | public function delete($path) |
|
217 | |||
218 | /** |
||
219 | * Delete a directory. |
||
220 | * |
||
221 | * @param string $dirname |
||
222 | * |
||
223 | * @return bool |
||
224 | */ |
||
225 | View Code Duplication | public function deleteDir($dirname) |
|
234 | |||
235 | /** |
||
236 | * Create a directory. |
||
237 | * |
||
238 | * @param string $dirname directory name |
||
239 | * @param Config $config |
||
240 | * |
||
241 | * @return array|false |
||
242 | */ |
||
243 | public function createDir($dirname, Config $config) |
||
255 | |||
256 | /** |
||
257 | * Set the visibility for a file. |
||
258 | * |
||
259 | * @param string $path |
||
260 | * @param string $visibility |
||
261 | * |
||
262 | * @return array|false file meta data |
||
263 | */ |
||
264 | public function setVisibility($path, $visibility) |
||
283 | |||
284 | /** |
||
285 | * Read a file. |
||
286 | * |
||
287 | * @param string $path |
||
288 | * |
||
289 | * @return array|false |
||
290 | */ |
||
291 | public function read($path) |
||
303 | |||
304 | /** |
||
305 | * Read a file as a stream. |
||
306 | * |
||
307 | * @param string $path |
||
308 | * |
||
309 | * @return array|false |
||
310 | */ |
||
311 | public function readStream($path) |
||
332 | |||
333 | /** |
||
334 | * Get all the meta data of a file or directory. |
||
335 | * |
||
336 | * @param string $path |
||
337 | * |
||
338 | * @return array|false |
||
339 | */ |
||
340 | public function getMetadata($path) |
||
357 | |||
358 | /** |
||
359 | * Get the mimetype of a file. |
||
360 | * |
||
361 | * @param string $path |
||
362 | * |
||
363 | * @return array|false |
||
364 | */ |
||
365 | public function getMimetype($path) |
||
375 | |||
376 | /** |
||
377 | * Get the timestamp of a file. |
||
378 | * |
||
379 | * @param string $path |
||
380 | * |
||
381 | * @return array|false |
||
382 | */ |
||
383 | public function getTimestamp($path) |
||
395 | |||
396 | /** |
||
397 | * {@inheritdoc} |
||
398 | * |
||
399 | * @param string $directory |
||
400 | */ |
||
401 | protected function listDirectoryContents($directory, $recursive = false) |
||
421 | |||
422 | /** |
||
423 | * {@inheritdoc} |
||
424 | * |
||
425 | * @param string $directory |
||
426 | */ |
||
427 | protected function listDirectoryContentsRecursive($directory) |
||
447 | |||
448 | /** |
||
449 | * Normalize a permissions string. |
||
450 | * |
||
451 | * @param string $permissions |
||
452 | * |
||
453 | * @return int |
||
454 | */ |
||
455 | protected function normalizePermissions($permissions) |
||
472 | |||
473 | /** |
||
474 | * Normalize path depending on server. |
||
475 | * |
||
476 | * @param string $path |
||
477 | * |
||
478 | * @return string |
||
479 | */ |
||
480 | protected function normalizePath($path) |
||
495 | |||
496 | /** |
||
497 | * @return bool |
||
498 | */ |
||
499 | protected function isPureFtpdServer() |
||
509 | |||
510 | /** |
||
511 | * Sends an arbitrary command to an FTP server. |
||
512 | * |
||
513 | * @param Curl $connection The CURL instance |
||
514 | * @param string $command The command to execute |
||
515 | * |
||
516 | * @return array Returns the server's response as an array of strings |
||
517 | */ |
||
518 | protected function rawCommand($connection, $command) |
||
533 | |||
534 | /** |
||
535 | * Returns the base url of the connection. |
||
536 | * |
||
537 | * @return string |
||
538 | */ |
||
539 | protected function getBaseUri() |
||
545 | |||
546 | /** |
||
547 | * Check the connection is established. |
||
548 | */ |
||
549 | protected function pingConnection() |
||
556 | |||
557 | /** |
||
558 | * Set the connection root. |
||
559 | */ |
||
560 | protected function setConnectionRoot() |
||
574 | } |
||
575 |
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.