Total Complexity | 47 |
Total Lines | 540 |
Duplicated Lines | 0 % |
Changes | 0 |
Complex classes like SVNHelper 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.
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 SVNHelper, and based on these observations, apply Extract Interface, too.
1 | <?php |
||
25 | class SVNHelper |
||
26 | { |
||
27 | /** |
||
28 | * @var integer |
||
29 | */ |
||
30 | public const ERROR_LOCAL_PATH_DOES_NOT_EXIST = 22401; |
||
31 | |||
32 | /** |
||
33 | * @var integer |
||
34 | */ |
||
35 | public const ERROR_INVALID_REP_URL = 22402; |
||
36 | |||
37 | /** |
||
38 | * @var integer |
||
39 | */ |
||
40 | public const ERROR_PATH_IS_OUTSIDE_REPOSITORY = 22403; |
||
41 | |||
42 | /** |
||
43 | * @var integer |
||
44 | */ |
||
45 | public const ERROR_TARGET_FOLDER_IS_A_FILE = 22404; |
||
46 | |||
47 | /** |
||
48 | * @var integer |
||
49 | */ |
||
50 | public const ERROR_CANNOT_ADD_INEXISTENT_FILE = 22405; |
||
51 | |||
52 | /** |
||
53 | * @var integer |
||
54 | */ |
||
55 | public const ERROR_TARGET_PATH_NOT_FOUND = 22406; |
||
56 | |||
57 | /** |
||
58 | * @var integer |
||
59 | */ |
||
60 | public const ERROR_INVALID_TARGET_TYPE = 22407; |
||
61 | |||
62 | /** |
||
63 | * @var integer |
||
64 | */ |
||
65 | public const ERROR_INVALID_LOG_CALLBACK = 22408; |
||
66 | |||
67 | /** |
||
68 | * @var SVNHelper_Target_Folder |
||
69 | */ |
||
70 | protected $target; |
||
71 | |||
72 | /** |
||
73 | * @var string |
||
74 | */ |
||
75 | protected $path; |
||
76 | |||
77 | /** |
||
78 | * @var string |
||
79 | */ |
||
80 | protected $url; |
||
81 | |||
82 | /** |
||
83 | * @var string |
||
84 | */ |
||
85 | protected $user; |
||
86 | |||
87 | /** |
||
88 | * @var string |
||
89 | */ |
||
90 | protected $pass; |
||
91 | |||
92 | /** |
||
93 | * @var array |
||
94 | */ |
||
95 | protected $options = array( |
||
96 | 'binaries-path' => '' |
||
97 | ); |
||
98 | |||
99 | /** |
||
100 | * @var boolean |
||
101 | */ |
||
102 | protected $isWindows = false; |
||
103 | |||
104 | /** |
||
105 | * @var array |
||
106 | */ |
||
107 | protected $normalize = array( |
||
108 | 'from' => '\\', |
||
109 | 'to' => '/' |
||
110 | ); |
||
111 | |||
112 | /** |
||
113 | * @var string |
||
114 | */ |
||
115 | protected $sourcePath; |
||
116 | |||
117 | /** |
||
118 | * @param string $repPath The path to the repository |
||
119 | * @param string $repURL The SVN URL to the repository |
||
120 | * @throws SVNHelper_Exception |
||
121 | */ |
||
122 | public function __construct(string $repPath, string $repURL) |
||
123 | { |
||
124 | $this->isWindows = substr(PHP_OS, 0, 3) == 'WIN'; |
||
125 | |||
126 | if($this->isWindows) { |
||
127 | $this->normalize['from'] = '/'; |
||
128 | $this->normalize['to'] = '\\'; |
||
129 | } |
||
130 | |||
131 | // in case of symlinks, we need to store the original |
||
132 | // path so we can correctly adjust paths later on. |
||
133 | $this->sourcePath = $this->normalizePath($repPath); |
||
134 | |||
135 | // ensure that the path exists in the filesystem, thanks to |
||
136 | // realpath with the actual filesystem case even if the source |
||
137 | // path case does not entirely match. |
||
138 | // |
||
139 | // NOTE: In case of symlinks, this resolves the symlink to its source (WIN/NIX) |
||
140 | $realPath = realpath($this->sourcePath); |
||
141 | if(!is_dir($realPath)) { |
||
142 | throw new SVNHelper_Exception( |
||
143 | 'Local repository path does not exist', |
||
144 | sprintf( |
||
145 | 'Could not find the path [%s] on disk.', |
||
146 | $repPath |
||
147 | ), |
||
148 | self::ERROR_LOCAL_PATH_DOES_NOT_EXIST |
||
149 | ); |
||
150 | } |
||
151 | |||
152 | $this->path = $this->normalizePath($realPath); |
||
153 | $this->target = $this->getFolder(''); |
||
154 | $this->url = $repURL; |
||
155 | |||
156 | $result = array(); |
||
157 | preg_match_all('%([^:]+):(.+)@(https|http|svn)://(.+)%sm', $repURL, $result, PREG_PATTERN_ORDER); |
||
158 | |||
159 | if(!isset($result[1]) || !isset($result[1][0])) { |
||
160 | throw new SVNHelper_Exception( |
||
161 | 'Invalid SVN repository URL', |
||
162 | 'The SVN URL must have the following format: [username:password@http://domain.com/path/to/rep].', |
||
163 | self::ERROR_INVALID_REP_URL |
||
164 | ); |
||
165 | } |
||
166 | |||
167 | $this->pass = $result[2][0]; |
||
168 | $this->user = $result[1][0]; |
||
169 | $this->url = $result[3][0].'://'.$result[4][0]; |
||
170 | } |
||
171 | |||
172 | public function getAuthUser() |
||
173 | { |
||
174 | return $this->user; |
||
175 | } |
||
176 | |||
177 | public function getAuthPassword() |
||
178 | { |
||
179 | return $this->pass; |
||
180 | } |
||
181 | |||
182 | /** |
||
183 | * Normalizes slashes in the path according to the |
||
184 | * operating system, i.e. forward slashes for NIX-systems |
||
185 | * and backward slashes for Windows. |
||
186 | * |
||
187 | * @param string $path An absolute path to normalize |
||
188 | * @param bool $relativize Whether to return a path relative to the repository |
||
189 | * @throws SVNHelper_Exception |
||
190 | * @return string |
||
191 | */ |
||
192 | public function normalizePath($path, $relativize=false) |
||
193 | { |
||
194 | if(empty($path)) { |
||
195 | return ''; |
||
196 | } |
||
197 | |||
198 | if($relativize) |
||
199 | { |
||
200 | $path = $this->normalizePath($path); |
||
201 | |||
202 | // path is absolute, and does not match the realpath or the source path? |
||
203 | if(strstr($path, ':'.$this->getSlash()) && (!stristr($path, $this->path) && !stristr($path, $this->sourcePath))) { |
||
204 | throw new SVNHelper_Exception( |
||
205 | 'Cannot relativize path outside of repository', |
||
206 | sprintf( |
||
207 | 'The path [%s] is outside of the repository [%s].', |
||
208 | $path, |
||
209 | $this->path |
||
210 | ), |
||
211 | self::ERROR_PATH_IS_OUTSIDE_REPOSITORY |
||
212 | ); |
||
213 | } |
||
214 | |||
215 | $path = str_replace(array($this->path, $this->sourcePath), '', $path); |
||
216 | return ltrim($path, $this->normalize['to']); |
||
217 | } |
||
218 | |||
219 | return str_replace( |
||
220 | $this->normalize['from'], |
||
221 | $this->normalize['to'], |
||
222 | $path |
||
223 | ); |
||
224 | } |
||
225 | |||
226 | /** |
||
227 | * Retrieves the path slash style according to the |
||
228 | * current operating system. |
||
229 | * |
||
230 | * @return string |
||
231 | */ |
||
232 | public function getSlash() |
||
233 | { |
||
234 | return $this->normalize['to']; |
||
235 | } |
||
236 | |||
237 | /** |
||
238 | * Keeps instances of files. |
||
239 | * @var SVNHelper_Target[] |
||
240 | */ |
||
241 | protected $targets = array(); |
||
242 | |||
243 | /** |
||
244 | * Retrieves a file instance from the SVN repository: |
||
245 | * this allows all possible operations on the file as |
||
246 | * well as accessing more information on it. |
||
247 | * |
||
248 | * @param string $path A path to the file, relative to the repository path or absolute. |
||
249 | * @return SVNHelper_Target_File |
||
250 | * @throws SVNHelper_Exception |
||
251 | */ |
||
252 | public function getFile(string $path) : SVNHelper_Target_File |
||
253 | { |
||
254 | $path = $this->filterPath($path); |
||
255 | |||
256 | return $this->getTarget('File', $this->relativizePath($path)) |
||
257 | ->requireIsFile(); |
||
258 | } |
||
259 | |||
260 | /** |
||
261 | * Retrieves a folder instance from the SVN repository: |
||
262 | * This allows all possible operations on the folder as |
||
263 | * well as accessing more information on it. |
||
264 | * |
||
265 | * @param string $path |
||
266 | * @return SVNHelper_Target_Folder |
||
267 | * @throws SVNHelper_Exception |
||
268 | */ |
||
269 | public function getFolder(string $path) : SVNHelper_Target_Folder |
||
275 | } |
||
276 | |||
277 | /** |
||
278 | * Passes the path through realpath and ensures it exists. |
||
279 | * |
||
280 | * @param string $path |
||
281 | * @throws SVNHelper_Exception |
||
282 | * @return string |
||
283 | */ |
||
284 | protected function filterPath($path) |
||
285 | { |
||
286 | if(empty($path)) { |
||
287 | return ''; |
||
288 | } |
||
289 | |||
290 | $path = $this->getPath().'/'.$this->relativizePath($path); |
||
291 | |||
292 | $real = realpath($path); |
||
293 | if($real !== false) { |
||
294 | return $real; |
||
295 | } |
||
296 | |||
297 | throw new SVNHelper_Exception( |
||
298 | 'Target file does not exist', |
||
299 | sprintf( |
||
300 | 'Could not find file [%s] on disk in SVN repository [%s].', |
||
301 | $path, |
||
302 | $this->getPath() |
||
303 | ), |
||
304 | self::ERROR_TARGET_PATH_NOT_FOUND |
||
305 | ); |
||
306 | } |
||
307 | |||
308 | /** |
||
309 | * Retrieves a target file or folder within the repository. |
||
310 | * |
||
311 | * @param string $type The target type, "File" or "Folder". |
||
312 | * @param string $relativePath A path relative to the root folder. |
||
313 | * @return SVNHelper_Target |
||
314 | */ |
||
315 | protected function getTarget(string $type, string $relativePath) : SVNHelper_Target |
||
316 | { |
||
317 | $key = $type.':'.$relativePath; |
||
318 | |||
319 | $relativePath = $this->normalizePath($relativePath, true); |
||
320 | if(isset($this->targets[$key])) { |
||
321 | return $this->targets[$key]; |
||
322 | } |
||
323 | |||
324 | $target = null; |
||
325 | |||
326 | switch($type) |
||
327 | { |
||
328 | case 'File': |
||
329 | $target = new SVNHelper_Target_File($this, $relativePath); |
||
330 | break; |
||
331 | |||
332 | case 'Folder': |
||
333 | $target = new SVNHelper_Target_Folder($this, $relativePath); |
||
334 | break; |
||
335 | |||
336 | default: |
||
337 | throw new SVNHelper_Exception( |
||
338 | 'Unknown target type', |
||
339 | sprintf( |
||
340 | 'The target type [%s] is not a valid type.', |
||
341 | $type |
||
342 | ), |
||
343 | self::ERROR_INVALID_TARGET_TYPE |
||
344 | ); |
||
345 | } |
||
346 | |||
347 | $this->targets[$key] = $target; |
||
348 | |||
349 | return $target; |
||
350 | } |
||
351 | |||
352 | public function getPath() |
||
353 | { |
||
354 | return $this->path; |
||
355 | } |
||
356 | |||
357 | public function getURL() |
||
358 | { |
||
359 | return $this->url; |
||
360 | } |
||
361 | |||
362 | /** |
||
363 | * Updates the whole SVN repository from the root folder. |
||
364 | * @return SVNHelper_CommandResult |
||
365 | */ |
||
366 | public function runUpdate() |
||
367 | { |
||
368 | return $this->createUpdate($this->target)->execute(); |
||
369 | } |
||
370 | |||
371 | /** |
||
372 | * Creates an update command for the target file or folder. |
||
373 | * This can be configured further before it is executed. |
||
374 | * |
||
375 | * @param SVNHelper_Target $target |
||
376 | * @return SVNHelper_Command_Update |
||
377 | */ |
||
378 | public function createUpdate(SVNHelper_Target $target) |
||
379 | { |
||
380 | return $this->createCommand('Update', $target); |
||
381 | } |
||
382 | |||
383 | /** |
||
384 | * Creates an add command for the targt file or folder. |
||
385 | * |
||
386 | * @param SVNHelper_Target $target |
||
387 | * @return SVNHelper_Command_Add |
||
388 | */ |
||
389 | public function createAdd(SVNHelper_Target $target) |
||
390 | { |
||
391 | return $this->createCommand('Add', $target); |
||
392 | } |
||
393 | |||
394 | /** |
||
395 | * Creates an info command for the target file or folder. |
||
396 | * |
||
397 | * @param SVNHelper_Target $target |
||
398 | * @return SVNHelper_Command_Info |
||
399 | */ |
||
400 | public function createInfo(SVNHelper_Target $target) |
||
403 | } |
||
404 | |||
405 | /** |
||
406 | * Creates a status command for the target file or folder. |
||
407 | * |
||
408 | * @param SVNHelper_Target $target |
||
409 | * @return SVNHelper_Command_Status |
||
410 | */ |
||
411 | public function createStatus(SVNHelper_Target $target) |
||
414 | } |
||
415 | |||
416 | /** |
||
417 | * Creates a commit command for the target file or folder. |
||
418 | * |
||
419 | * @param SVNHelper_Target $target |
||
420 | * @return SVNHelper_Command_Commit |
||
421 | */ |
||
422 | public function createCommit(SVNHelper_Target $target, $message) |
||
425 | } |
||
426 | |||
427 | protected function createCommand($type, SVNHelper_Target $target) |
||
428 | { |
||
429 | $class = 'AppUtils\SVNHelper_Command_'.$type; |
||
430 | |||
431 | $cmd = new $class($this, $target); |
||
432 | return $cmd; |
||
433 | } |
||
434 | |||
435 | /** |
||
436 | * Creates a path relative to the repository for the target |
||
437 | * file or folder, from an absolute path. |
||
438 | * |
||
439 | * @param string $path An absolute path. |
||
440 | * @return string |
||
441 | */ |
||
442 | public function relativizePath($path) |
||
443 | { |
||
444 | return $this->normalizePath($path, true); |
||
445 | } |
||
446 | |||
447 | /** |
||
448 | * Adds a folder: creates it as necessary (recursive), |
||
449 | * and adds it to be committed if it is not versioned yet. |
||
450 | * Use this instead of {@link getFolder()} when you are |
||
451 | * not sure that it exists yet, and will need it. |
||
452 | * |
||
453 | * @param string $path Absolute or relative path to the folder |
||
454 | * @throws SVNHelper_Exception |
||
455 | * @return SVNHelper_Target_Folder |
||
456 | */ |
||
457 | public function addFolder($path) |
||
458 | { |
||
459 | if(is_dir($path)) { |
||
460 | return $this->getFolder($path); |
||
461 | } |
||
462 | |||
463 | $path = $this->relativizePath($path); |
||
464 | $tokens = explode($this->getSlash(), $path); |
||
465 | |||
466 | $target = $this->path; |
||
467 | foreach($tokens as $folder) |
||
468 | { |
||
469 | $target .= $this->getSlash().$folder; |
||
470 | if(file_exists($target)) |
||
471 | { |
||
472 | if(!is_dir($target)) { |
||
473 | throw new SVNHelper_Exception( |
||
474 | 'Target folder is a file', |
||
475 | sprintf( |
||
476 | 'The folder [%s] is actually a file.', |
||
477 | $folder |
||
478 | ), |
||
479 | self::ERROR_TARGET_FOLDER_IS_A_FILE |
||
480 | ); |
||
481 | } |
||
482 | |||
483 | continue; |
||
484 | } |
||
485 | |||
486 | if(!mkdir($target, 0777)) { |
||
487 | throw new SVNHelper_Exception( |
||
488 | 'Cannot create folder', |
||
489 | sprintf( |
||
490 | 'Could not create the folder [%s] on disk.', |
||
491 | $target |
||
492 | ) |
||
493 | ); |
||
494 | } |
||
495 | |||
496 | // we commit the new folder directly, since we |
||
497 | // will need it later. |
||
498 | $this->addFolder($target)->runCommit('Added for nested files.'); |
||
499 | } |
||
500 | |||
501 | return $this->getFolder($path)->runAdd(); |
||
502 | } |
||
503 | |||
504 | protected static $logCallback; |
||
505 | |||
506 | /** |
||
507 | * Sets the callback function/method to use for |
||
508 | * SVH helper log messages. This gets the message |
||
509 | * and the SVNHelper instance as parameters. |
||
510 | * |
||
511 | * @param callable $callback |
||
512 | * @throws SVNHelper_Exception |
||
513 | */ |
||
514 | public static function setLogCallback($callback) |
||
515 | { |
||
516 | if(!is_callable($callback)) { |
||
517 | throw new SVNHelper_Exception( |
||
518 | 'Not a valid logging callback', |
||
519 | 'The specified argument is not callable.', |
||
520 | self::ERROR_INVALID_LOG_CALLBACK |
||
521 | ); |
||
522 | } |
||
523 | |||
524 | self::$logCallback = $callback; |
||
525 | } |
||
526 | |||
527 | public static function log($message) |
||
531 | } |
||
532 | } |
||
533 | |||
534 | /** |
||
535 | * Retrieves information about the file, and adds it |
||
536 | * to be committed later if it not versioned yet. |
||
537 | * |
||
538 | * @param string $path |
||
539 | * @return SVNHelper_Target_File |
||
540 | */ |
||
541 | public function addFile($path) |
||
542 | { |
||
543 | return $this->getFile($path)->runAdd(); |
||
544 | } |
||
545 | |||
546 | /** |
||
547 | * Commits all changes in the repository. |
||
548 | * @param string $message The commit message to log. |
||
549 | */ |
||
550 | public function runCommit($message) |
||
551 | { |
||
552 | $this->createCommit($this->getFolder($this->path), $message)->execute(); |
||
553 | } |
||
554 | |||
555 | protected static $loggers = array(); |
||
556 | |||
557 | public static function registerExceptionLogger($callback) |
||
558 | { |
||
559 | self::$loggers[] = $callback; |
||
560 | } |
||
561 | |||
562 | public static function getExceptionLoggers() |
||
565 | } |
||
566 | } |
||
567 |