Complex classes like Target 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 Target, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
20 | class Target |
||
21 | { |
||
22 | /** |
||
23 | * Absolute path to the directory where to store the backup. |
||
24 | * |
||
25 | * @var string |
||
26 | */ |
||
27 | private $path; |
||
28 | |||
29 | /** |
||
30 | * Path to the backup with potential date placeholders like %d. |
||
31 | * |
||
32 | * @var string |
||
33 | */ |
||
34 | private $pathRaw; |
||
35 | |||
36 | /** |
||
37 | * Indicates if the path changes over time. |
||
38 | * |
||
39 | * @var bool |
||
40 | */ |
||
41 | private $pathIsChanging = false; |
||
42 | |||
43 | /** |
||
44 | * Part of the path without placeholders |
||
45 | * |
||
46 | * @var string |
||
47 | */ |
||
48 | private $pathNotChanging; |
||
49 | |||
50 | /** |
||
51 | * List of all path elements. |
||
52 | * |
||
53 | * @var string[] |
||
54 | */ |
||
55 | private $pathElements = []; |
||
56 | |||
57 | /** |
||
58 | * Backup filename. |
||
59 | * |
||
60 | * @var string |
||
61 | */ |
||
62 | private $filename; |
||
63 | |||
64 | /** |
||
65 | * Filename with potential date placeholders like %d. |
||
66 | * |
||
67 | * @var string |
||
68 | */ |
||
69 | private $filenameRaw; |
||
70 | |||
71 | /** |
||
72 | * List of custom file suffixes f.e. 'tar' |
||
73 | * |
||
74 | * @var string[] |
||
75 | */ |
||
76 | private $fileSuffixes = []; |
||
77 | |||
78 | /** |
||
79 | * Indicates if the filename changes over time. |
||
80 | * |
||
81 | * @var bool |
||
82 | */ |
||
83 | private $filenameIsChanging = false; |
||
84 | |||
85 | /** |
||
86 | * Target MIME type |
||
87 | * |
||
88 | * @var string |
||
89 | */ |
||
90 | private $mimeType = 'text/plain'; |
||
91 | |||
92 | /** |
||
93 | * Size in bytes |
||
94 | * |
||
95 | * @var int |
||
96 | */ |
||
97 | private $size; |
||
98 | |||
99 | /** |
||
100 | * Should the file be compressed. |
||
101 | * |
||
102 | * @var bool |
||
103 | */ |
||
104 | private $compress = false; |
||
105 | |||
106 | /** |
||
107 | * File compression. |
||
108 | * |
||
109 | * @var \phpbu\App\Backup\Target\Compression |
||
110 | */ |
||
111 | private $compression; |
||
112 | |||
113 | /** |
||
114 | * Should the file be encrypted. |
||
115 | * |
||
116 | * @var bool |
||
117 | */ |
||
118 | private $crypt = false; |
||
119 | |||
120 | /** |
||
121 | * File crypter. |
||
122 | * |
||
123 | * @var \phpbu\App\Backup\Crypter |
||
124 | */ |
||
125 | private $crypter; |
||
126 | |||
127 | /** |
||
128 | * Constructor. |
||
129 | * |
||
130 | * @param string $path |
||
131 | * @param string $filename |
||
132 | * @param integer $time |
||
133 | * @throws \phpbu\App\Exception |
||
134 | */ |
||
135 | 46 | public function __construct($path, $filename, $time = null) |
|
136 | { |
||
137 | 46 | $this->setPath($path, $time); |
|
138 | 46 | $this->setFile($filename, $time); |
|
139 | 46 | } |
|
140 | |||
141 | /** |
||
142 | * Directory setter. |
||
143 | * |
||
144 | * @param string $path |
||
145 | * @param int $time |
||
146 | */ |
||
147 | 46 | public function setPath($path, $time = null) |
|
148 | { |
||
149 | // remove trailing slashes |
||
150 | 46 | $path = rtrim($path, DIRECTORY_SEPARATOR); |
|
151 | 46 | $this->pathRaw = $path; |
|
152 | 46 | $this->pathNotChanging = $path; |
|
153 | |||
154 | 46 | if (Str::isContainingPlaceholder($path)) { |
|
155 | 11 | $this->pathIsChanging = true; |
|
156 | 11 | $this->detectPathNotChanging($path); |
|
157 | // replace potential date placeholder |
||
158 | 11 | $path = Str::replaceDatePlaceholders($path, $time); |
|
159 | } |
||
160 | |||
161 | 46 | $this->path = $path; |
|
162 | 46 | } |
|
163 | |||
164 | /** |
||
165 | * Return path element at given index. |
||
166 | * |
||
167 | * @param int $index |
||
168 | * @return string |
||
169 | */ |
||
170 | 6 | public function getPathElementAtIndex(int $index) : string |
|
171 | { |
||
172 | 6 | return $this->pathElements[$index]; |
|
173 | } |
||
174 | |||
175 | /** |
||
176 | * Return the full target path depth. |
||
177 | * |
||
178 | * @return int |
||
179 | */ |
||
180 | 10 | public function getPathDepth() : int |
|
181 | { |
||
182 | 10 | return count($this->pathElements); |
|
183 | } |
||
184 | |||
185 | /** |
||
186 | * Find path elements that can't change because of placeholder usage. |
||
187 | * |
||
188 | * @param string $path |
||
189 | */ |
||
190 | 11 | private function detectPathNotChanging(string $path) |
|
191 | { |
||
192 | 11 | $partsNotChanging = []; |
|
193 | 11 | $foundChangingElement = false; |
|
194 | |||
195 | 11 | foreach (Cli::getDirectoryList($path) as $depth => $dir) { |
|
196 | 11 | $this->pathElements[] = $dir; |
|
197 | |||
198 | // already found placeholder or found one right now |
||
199 | // path isn't static anymore so don't add directory to path not changing |
||
200 | 11 | if ($foundChangingElement || Str::isContainingPlaceholder($dir)) { |
|
201 | 11 | $foundChangingElement = true; |
|
202 | 11 | continue; |
|
203 | } |
||
204 | // do not add the / element leading slash will be re-added later |
||
205 | 11 | if ($dir !== '/') { |
|
206 | 11 | $partsNotChanging[] = $dir; |
|
207 | } |
||
208 | } |
||
209 | 11 | $this->pathNotChanging = DIRECTORY_SEPARATOR . implode(DIRECTORY_SEPARATOR, $partsNotChanging); |
|
210 | 11 | } |
|
211 | |||
212 | /** |
||
213 | * Filename setter. |
||
214 | * |
||
215 | * @param string $file |
||
216 | * @param int $time |
||
217 | */ |
||
218 | 46 | public function setFile($file, $time = null) |
|
219 | { |
||
220 | 46 | $this->filenameRaw = $file; |
|
221 | 46 | if (Str::isContainingPlaceholder($file)) { |
|
222 | 32 | $this->filenameIsChanging = true; |
|
223 | 32 | $file = Str::replaceDatePlaceholders($file, $time); |
|
224 | } |
||
225 | 46 | $this->filename = $file; |
|
226 | 46 | } |
|
227 | |||
228 | /** |
||
229 | * Append another suffix to the filename. |
||
230 | * |
||
231 | * @param string $suffix |
||
232 | */ |
||
233 | 3 | public function appendFileSuffix(string $suffix) |
|
234 | { |
||
235 | 3 | $this->fileSuffixes[] = $suffix; |
|
236 | 3 | } |
|
237 | |||
238 | /** |
||
239 | * Checks if the backup target directory is writable. |
||
240 | * Creates the Directory if it doesn't exist. |
||
241 | * |
||
242 | * @throws \phpbu\App\Exception |
||
243 | */ |
||
244 | 4 | public function setupPath() |
|
245 | { |
||
246 | // if directory doesn't exist, create it |
||
247 | 4 | if (!is_dir($this->path)) { |
|
248 | 3 | $reporting = error_reporting(); |
|
249 | 3 | error_reporting(0); |
|
250 | 3 | $created = mkdir($this->path, 0755, true); |
|
251 | 3 | error_reporting($reporting); |
|
252 | 3 | if (!$created) { |
|
253 | 1 | throw new Exception(sprintf('cant\'t create directory: %s', $this->path)); |
|
254 | } |
||
255 | } |
||
256 | 3 | if (!is_writable($this->path)) { |
|
257 | 1 | throw new Exception(sprintf('no write permission for directory: %s', $this->path)); |
|
258 | } |
||
259 | 2 | } |
|
260 | |||
261 | /** |
||
262 | * Target file MIME type setter. |
||
263 | * |
||
264 | * @param string $mime |
||
265 | */ |
||
266 | 1 | public function setMimeType(string $mime) |
|
267 | { |
||
268 | 1 | $this->mimeType = $mime; |
|
269 | 1 | } |
|
270 | |||
271 | /** |
||
272 | * Return the path to the backup file. |
||
273 | * |
||
274 | * @return string |
||
275 | */ |
||
276 | 3 | public function getPath() : string |
|
277 | { |
||
278 | 3 | return $this->path; |
|
279 | } |
||
280 | |||
281 | /** |
||
282 | * Return the path to the backup file. |
||
283 | * |
||
284 | * @return string |
||
285 | */ |
||
286 | 1 | public function getPathRaw() : string |
|
287 | { |
||
288 | 1 | return $this->pathRaw; |
|
289 | } |
||
290 | |||
291 | /** |
||
292 | * Return the name to the backup file. |
||
293 | * |
||
294 | * @param bool $plain |
||
295 | * @return string |
||
296 | */ |
||
297 | 26 | public function getFilename(bool $plain = false) : string |
|
298 | { |
||
299 | 26 | return $this->filename . $this->getFilenameSuffix($plain); |
|
300 | } |
||
301 | |||
302 | /** |
||
303 | * Return the name of the backup file without compressor or encryption suffix. |
||
304 | * |
||
305 | * @return string |
||
306 | */ |
||
307 | 1 | public function getFilenamePlain() : string |
|
308 | { |
||
309 | 1 | return $this->getFilename(true); |
|
310 | } |
||
311 | |||
312 | /** |
||
313 | * Return the raw name of the backup file incl. date placeholder. |
||
314 | * |
||
315 | * @param bool $plain |
||
316 | * @return string |
||
317 | */ |
||
318 | 14 | public function getFilenameRaw($plain = false) : string |
|
319 | { |
||
320 | 14 | return $this->filenameRaw . $this->getFilenameSuffix($plain); |
|
321 | } |
||
322 | |||
323 | /** |
||
324 | * Return custom file suffix like '.tar'. |
||
325 | * |
||
326 | * @param bool $plain |
||
327 | * @return string |
||
328 | */ |
||
329 | 26 | public function getFilenameSuffix($plain = false) : string |
|
330 | { |
||
331 | 26 | return $this->getSuffixToAppend() . ($plain ? '' : $this->getCompressionSuffix() . $this->getCrypterSuffix()); |
|
332 | } |
||
333 | |||
334 | /** |
||
335 | * Return added suffixes. |
||
336 | * |
||
337 | * @return string |
||
338 | */ |
||
339 | 26 | public function getSuffixToAppend() : string |
|
340 | { |
||
341 | 26 | return count($this->fileSuffixes) ? '.' . implode('.', $this->fileSuffixes) : ''; |
|
342 | } |
||
343 | |||
344 | /** |
||
345 | * Return the compressor suffix. |
||
346 | * |
||
347 | * @return string |
||
348 | */ |
||
349 | 24 | public function getCompressionSuffix() : string |
|
350 | { |
||
351 | 24 | return $this->shouldBeCompressed() ? '.' . $this->compression->getSuffix() : ''; |
|
352 | } |
||
353 | |||
354 | /** |
||
355 | * Return the crypter suffix. |
||
356 | * |
||
357 | * @return string |
||
358 | */ |
||
359 | 24 | public function getCrypterSuffix() : string |
|
360 | { |
||
361 | 24 | return $this->shouldBeEncrypted() ? '.' . $this->crypter->getSuffix() : ''; |
|
362 | } |
||
363 | |||
364 | /** |
||
365 | * Return file MIME type. |
||
366 | * |
||
367 | * @return string |
||
368 | */ |
||
369 | 3 | public function getMimeType() : string |
|
370 | { |
||
371 | 3 | $mimeType = $this->mimeType; |
|
372 | 3 | if ($this->shouldBeCompressed()) { |
|
373 | 1 | $mimeType = $this->compression->getMimeType(); |
|
374 | } |
||
375 | 3 | return $mimeType; |
|
376 | } |
||
377 | |||
378 | /** |
||
379 | * Size setter. |
||
380 | * |
||
381 | * @param int $size |
||
382 | */ |
||
383 | 1 | public function setSize(int $size) |
|
384 | { |
||
385 | 1 | $this->size = $size; |
|
386 | 1 | } |
|
387 | |||
388 | /** |
||
389 | * Return the actual file size in bytes. |
||
390 | * |
||
391 | * @return int |
||
392 | * @throws \phpbu\App\Exception |
||
393 | */ |
||
394 | 3 | public function getSize() : int |
|
395 | { |
||
396 | 3 | if (null === $this->size) { |
|
397 | 2 | if (!file_exists($this)) { |
|
398 | 1 | throw new Exception(sprintf('target file \'%s\' doesn\'t exist', $this->getFilename())); |
|
399 | } |
||
400 | 1 | $this->size = filesize($this); |
|
401 | } |
||
402 | 2 | return $this->size; |
|
403 | } |
||
404 | |||
405 | /** |
||
406 | * Target file exists already. |
||
407 | * |
||
408 | * @param bool $plain |
||
409 | * @return bool |
||
410 | */ |
||
411 | 1 | public function fileExists(bool $plain = false) : bool |
|
412 | { |
||
413 | 1 | return file_exists($this->getPathname($plain)); |
|
414 | } |
||
415 | |||
416 | /** |
||
417 | * Return as backup file object. |
||
418 | * |
||
419 | * @return \phpbu\App\Backup\File\Local |
||
420 | */ |
||
421 | 1 | public function toFile() : Local |
|
422 | { |
||
423 | 1 | return new Local(new \SplFileInfo($this->getPathname())); |
|
424 | } |
||
425 | |||
426 | /** |
||
427 | * Return path and filename of the backup file. |
||
428 | * |
||
429 | * @param bool $plain |
||
430 | * @return string |
||
431 | */ |
||
432 | 16 | public function getPathname(bool $plain = false) : string |
|
433 | { |
||
434 | 16 | return $this->path . DIRECTORY_SEPARATOR . $this->getFilename($plain); |
|
435 | } |
||
436 | |||
437 | /** |
||
438 | * Return path and plain filename of the backup file. |
||
439 | * |
||
440 | * @return string |
||
441 | */ |
||
442 | 1 | public function getPathnamePlain() : string |
|
443 | { |
||
444 | 1 | return $this->getPathname(true); |
|
445 | } |
||
446 | |||
447 | /** |
||
448 | * Is dirname configured with any date placeholders. |
||
449 | * |
||
450 | * @return bool |
||
451 | */ |
||
452 | 3 | public function hasChangingPath() : bool |
|
453 | { |
||
454 | 3 | return $this->pathIsChanging; |
|
455 | } |
||
456 | |||
457 | /** |
||
458 | * Return the part of the path that is not changing. |
||
459 | * |
||
460 | * @return string |
||
461 | */ |
||
462 | 10 | public function getPathThatIsNotChanging() : string |
|
463 | { |
||
464 | 10 | return $this->pathNotChanging; |
|
465 | } |
||
466 | |||
467 | /** |
||
468 | * Filename configured with any date placeholders. |
||
469 | * |
||
470 | * @return bool |
||
471 | */ |
||
472 | 2 | public function hasChangingFilename() : bool |
|
476 | |||
477 | /** |
||
478 | * Disable file compression. |
||
479 | */ |
||
480 | 2 | public function disableCompression() |
|
481 | { |
||
482 | 2 | $this->compress = false; |
|
483 | 2 | } |
|
484 | |||
485 | /** |
||
486 | * Enable file compression. |
||
487 | * |
||
488 | * @throws \phpbu\App\Exception |
||
489 | */ |
||
490 | 2 | public function enableCompression() |
|
491 | { |
||
492 | 2 | if (null == $this->compression) { |
|
493 | 1 | throw new Exception('can\'t enable compression without a compressor'); |
|
494 | } |
||
495 | 1 | $this->compress = true; |
|
496 | 1 | } |
|
497 | |||
498 | /** |
||
499 | * Compression setter. |
||
500 | * |
||
501 | * @param \phpbu\App\Backup\Target\Compression $compression |
||
502 | */ |
||
503 | 12 | public function setCompression(Target\Compression $compression) |
|
504 | { |
||
505 | 12 | $this->compression = $compression; |
|
508 | |||
509 | /** |
||
510 | * Compressor getter. |
||
511 | * |
||
512 | * @return \phpbu\App\Backup\Target\Compression |
||
513 | */ |
||
514 | 1 | public function getCompression() : Target\Compression |
|
518 | |||
519 | /** |
||
520 | * Is a compressor set? |
||
521 | * |
||
522 | * @return bool |
||
523 | */ |
||
524 | 29 | public function shouldBeCompressed() : bool |
|
528 | |||
529 | /** |
||
530 | * Crypter setter. |
||
531 | * |
||
532 | * @param \phpbu\App\Backup\Crypter $crypter |
||
533 | */ |
||
534 | 3 | public function setCrypter(Crypter $crypter) |
|
539 | |||
540 | /** |
||
541 | * Crypter getter. |
||
542 | * |
||
543 | * @return \phpbu\App\Backup\Crypter |
||
544 | */ |
||
545 | 1 | public function getCrypter() : Crypter |
|
549 | |||
550 | /** |
||
551 | * Disable file encryption. |
||
552 | */ |
||
553 | 1 | public function disableEncryption() |
|
557 | |||
558 | /** |
||
559 | * Is a crypter set? |
||
560 | * |
||
561 | * @return bool |
||
562 | */ |
||
563 | 25 | public function shouldBeEncrypted() : bool |
|
567 | |||
568 | /** |
||
569 | * Magic to string method. |
||
570 | * |
||
571 | * @return string |
||
572 | */ |
||
573 | 2 | public function __toString() : string |
|
577 | } |
||
578 |
Let’s assume that you have a directory layout like this:
and let’s assume the following content of
Bar.php
:If both files
OtherDir/Foo.php
andSomeDir/Foo.php
are loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php
However, as
OtherDir/Foo.php
does not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php
, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: