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 Ftp 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 Ftp, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
14 | class Ftp implements Adapter, |
||
|
|||
15 | FileFactory, |
||
16 | ListKeysAware |
||
17 | { |
||
18 | protected $connection = null; |
||
19 | protected $directory; |
||
20 | protected $host; |
||
21 | protected $port; |
||
22 | protected $username; |
||
23 | protected $password; |
||
24 | protected $passive; |
||
25 | protected $create; |
||
26 | protected $mode; |
||
27 | protected $ssl; |
||
28 | protected $fileData = array(); |
||
29 | protected $utf8; |
||
30 | |||
31 | /** |
||
32 | * @param string $directory The directory to use in the ftp server |
||
33 | * @param string $host The host of the ftp server |
||
34 | * @param array $options The options like port, username, password, passive, create, mode |
||
35 | */ |
||
36 | public function __construct($directory, $host, $options = array()) |
||
53 | |||
54 | /** |
||
55 | * {@inheritdoc} |
||
56 | */ |
||
57 | public function read($key) |
||
73 | |||
74 | /** |
||
75 | * {@inheritdoc} |
||
76 | */ |
||
77 | public function write($key, $content) |
||
100 | |||
101 | /** |
||
102 | * {@inheritdoc} |
||
103 | */ |
||
104 | public function rename($sourceKey, $targetKey) |
||
115 | |||
116 | /** |
||
117 | * {@inheritdoc} |
||
118 | */ |
||
119 | public function exists($key) |
||
139 | |||
140 | /** |
||
141 | * {@inheritdoc} |
||
142 | */ |
||
143 | public function keys() |
||
151 | |||
152 | /** |
||
153 | * {@inheritdoc} |
||
154 | */ |
||
155 | public function listKeys($prefix = '') |
||
180 | |||
181 | /** |
||
182 | * {@inheritdoc} |
||
183 | */ |
||
184 | public function mtime($key) |
||
197 | |||
198 | /** |
||
199 | * {@inheritdoc} |
||
200 | */ |
||
201 | public function delete($key) |
||
211 | |||
212 | /** |
||
213 | * {@inheritdoc} |
||
214 | */ |
||
215 | public function isDirectory($key) |
||
221 | |||
222 | /** |
||
223 | * Lists files from the specified directory. If a pattern is |
||
224 | * specified, it only returns files matching it. |
||
225 | * |
||
226 | * @param string $directory The path of the directory to list from |
||
227 | * |
||
228 | * @return array An array of keys and dirs |
||
229 | */ |
||
230 | public function listDirectory($directory = '') |
||
267 | |||
268 | /** |
||
269 | * {@inheritdoc} |
||
270 | */ |
||
271 | public function createFile($key, Filesystem $filesystem) |
||
291 | |||
292 | /** |
||
293 | * Ensures the specified directory exists. If it does not, and the create |
||
294 | * parameter is set to TRUE, it tries to create it. |
||
295 | * |
||
296 | * @param string $directory |
||
297 | * @param bool $create Whether to create the directory if it does not |
||
298 | * exist |
||
299 | * |
||
300 | * @throws RuntimeException if the directory does not exist and could not |
||
301 | * be created |
||
302 | */ |
||
303 | protected function ensureDirectoryExists($directory, $create = false) |
||
304 | { |
||
305 | if (!$this->isDir($directory)) { |
||
306 | if (!$create) { |
||
307 | throw new \RuntimeException(sprintf('The directory \'%s\' does not exist.', $directory)); |
||
308 | } |
||
309 | |||
310 | $this->createDirectory($directory); |
||
311 | } |
||
312 | } |
||
313 | |||
314 | /** |
||
315 | * Creates the specified directory and its parent directories. |
||
316 | * |
||
317 | * @param string $directory Directory to create |
||
318 | * |
||
319 | * @throws RuntimeException if the directory could not be created |
||
320 | */ |
||
321 | protected function createDirectory($directory) |
||
322 | { |
||
323 | // create parent directory if needed |
||
324 | $parent = str_replace('\\', '/', \Gaufrette\Util\Path::dirname($directory)); |
||
325 | if (!$this->isDir($parent)) { |
||
326 | $this->createDirectory($parent); |
||
327 | } |
||
328 | |||
329 | // create the specified directory |
||
330 | $created = ftp_mkdir($this->getConnection(), $directory); |
||
331 | if (false === $created) { |
||
332 | throw new \RuntimeException(sprintf('Could not create the \'%s\' directory.', $directory)); |
||
333 | } |
||
334 | } |
||
335 | |||
336 | /** |
||
337 | * @param string $directory - full directory path |
||
338 | * |
||
339 | * @return bool |
||
340 | */ |
||
341 | private function isDir($directory) |
||
356 | |||
357 | private function fetchKeys($directory = '', $onlyKeys = true) |
||
416 | |||
417 | /** |
||
418 | * Parses the given raw list. |
||
419 | * |
||
420 | * @param array $rawlist |
||
421 | * |
||
422 | * @return array |
||
423 | */ |
||
424 | private function parseRawlist(array $rawlist) |
||
425 | { |
||
426 | $parsed = array(); |
||
427 | foreach ($rawlist as $line) { |
||
428 | $infos = preg_split("/[\s]+/", $line, 9); |
||
429 | |||
430 | if ($this->isLinuxListing($infos)) { |
||
431 | $infos[7] = (strrpos($infos[7], ':') != 2) ? ($infos[7].' 00:00') : (date('Y').' '.$infos[7]); |
||
432 | if ('total' !== $infos[0]) { |
||
433 | $parsed[] = array( |
||
434 | 'perms' => $infos[0], |
||
435 | 'num' => $infos[1], |
||
436 | 'size' => $infos[4], |
||
437 | 'time' => strtotime($infos[5].' '.$infos[6].'. '.$infos[7]), |
||
438 | 'name' => $infos[8], |
||
439 | ); |
||
440 | } |
||
441 | } elseif (count($infos) >= 4) { |
||
442 | $isDir = (boolean) ('<dir>' === $infos[2]); |
||
443 | $parsed[] = array( |
||
444 | 'perms' => $isDir ? 'd' : '-', |
||
445 | 'num' => '', |
||
446 | 'size' => $isDir ? '' : $infos[2], |
||
447 | 'time' => strtotime($infos[0].' '.$infos[1]), |
||
448 | 'name' => $infos[3], |
||
449 | ); |
||
450 | } |
||
451 | } |
||
452 | |||
453 | return $parsed; |
||
454 | } |
||
455 | |||
456 | /** |
||
457 | * Computes the path for the given key. |
||
458 | * |
||
459 | * @param string $key |
||
460 | */ |
||
461 | private function computePath($key) |
||
465 | |||
466 | /** |
||
467 | * Indicates whether the adapter has an open ftp connection. |
||
468 | * |
||
469 | * @return bool |
||
470 | */ |
||
471 | private function isConnected() |
||
475 | |||
476 | /** |
||
477 | * Returns an opened ftp connection resource. If the connection is not |
||
478 | * already opened, it open it before. |
||
479 | * |
||
480 | * @return resource The ftp connection |
||
481 | */ |
||
482 | private function getConnection() |
||
483 | { |
||
484 | if (!$this->isConnected()) { |
||
485 | $this->connect(); |
||
486 | } |
||
487 | |||
488 | return $this->connection; |
||
489 | } |
||
490 | |||
491 | /** |
||
492 | * Opens the adapter's ftp connection. |
||
493 | * |
||
494 | * @throws RuntimeException if could not connect |
||
495 | */ |
||
496 | private function connect() |
||
548 | |||
549 | /** |
||
550 | * Closes the adapter's ftp connection. |
||
551 | */ |
||
552 | private function close() |
||
558 | |||
559 | private function isLinuxListing($info) |
||
560 | { |
||
561 | return count($info) >= 9; |
||
563 | } |
||
564 |