GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.

Filesystem   C
last analyzed

Complexity

Total Complexity 75

Size/Duplication

Total Lines 416
Duplicated Lines 2.88 %

Coupling/Cohesion

Components 2
Dependencies 2

Importance

Changes 0
Metric Value
dl 12
loc 416
rs 5.5056
c 0
b 0
f 0
wmc 75
lcom 2
cbo 2

15 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 4 1
A remove() 0 12 3
A isDirEmpty() 0 6 4
B removeDirectory() 0 24 5
A removeDirectoryPhp() 0 15 3
A ensureDirectoryExists() 0 15 4
A copyThenRemove() 0 5 1
B copy() 0 22 4
B rename() 0 39 6
C findShortestPath() 6 32 11
C findShortestPathCode() 6 32 14
A isAbsolutePath() 0 4 2
A size() 0 11 3
C normalizePath() 0 30 11
A directorySize() 0 14 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

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 Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like Filesystem 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 Filesystem, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * This file is part of Composer.
4
 *
5
 * (c) Nils Adermann <[email protected]>
6
 *     Jordi Boggiano <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * PHP Version 5
12
 *
13
 * @category   Netresearch
14
 * @package    Netresearch\Kite
15
 * @subpackage Service
16
 * @author     Christian Opitz <[email protected]>
17
 * @license    https://github.com/composer/composer/blob/master/LICENSE Composer license
18
 * @link       http://www.netresearch.de
19
 */
20
21
namespace Netresearch\Kite\Service;
22
23
use RecursiveDirectoryIterator;
24
use RecursiveIteratorIterator;
25
26
/**
27
 * A shell command service
28
 *
29
 * @category   Netresearch
30
 * @package    Netresearch\Kite
31
 * @subpackage Service
32
 * @author     Jordi Boggiano <[email protected]>
33
 * @author     Johannes M. Schmitt <[email protected]>
34
 * @author     Christian Opitz <[email protected]>
35
 * @license    https://github.com/composer/composer/blob/master/LICENSE Composer license
36
 * @link       http://www.netresearch.de
37
 */
38
class Filesystem
39
{
40
    /**
41
     * @var Console
42
     */
43
    protected $console;
44
45
    /**
46
     * Construct Filesystem
47
     *
48
     * @param Console $console The console
49
     */
50
    public function __construct(Console $console)
51
    {
52
        $this->console = $console;
53
    }
54
55
56
    /**
57
     * Remove a file
58
     *
59
     * @param string $file The file or dir
60
     *
61
     * @return bool
62
     */
63
    public function remove($file)
64
    {
65
        if (is_dir($file)) {
66
            return $this->removeDirectory($file);
67
        }
68
69
        if (file_exists($file)) {
70
            return unlink($file);
71
        }
72
73
        return false;
74
    }
75
76
    /**
77
     * Checks if a directory is empty
78
     *
79
     * @param string $dir The dir
80
     *
81
     * @return bool
82
     */
83
    public function isDirEmpty($dir)
84
    {
85
        $dir = rtrim($dir, '/\\');
86
87
        return count(glob($dir . '/*') ?: array()) === 0 && count(glob($dir . '/.*') ?: array()) === 2;
88
    }
89
90
    /**
91
     * Recursively remove a directory
92
     *
93
     * Uses the process component if proc_open is enabled on the PHP
94
     * installation.
95
     *
96
     * @param string $directory The dir
97
     *
98
     * @return bool
99
     */
100
    public function removeDirectory($directory)
101
    {
102
        if (!is_dir($directory)) {
103
            return true;
104
        }
105
106
        if (preg_match('{^(?:[a-z]:)?[/\\\\]+$}i', $directory)) {
107
            throw new \RuntimeException('Aborting an attempted deletion of ' . $directory . ', this was probably not intended, if it is a real use case please report it.');
108
        }
109
110
        if (!function_exists('proc_open')) {
111
            return $this->removeDirectoryPhp($directory);
112
        }
113
114
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
115
            $cmd = sprintf('rmdir /S /Q %s', escapeshellarg(realpath($directory)));
116
        } else {
117
            $cmd = sprintf('rm -rf %s', escapeshellarg($directory));
118
        }
119
120
        $this->console->createProcess($cmd)->run();
121
122
        return !is_dir($directory);
123
    }
124
125
    /**
126
     * Recursively delete directory using PHP iterators.
127
     *
128
     * Uses a CHILD_FIRST RecursiveIteratorIterator to sort files
129
     * before directories, creating a single non-recursive loop
130
     * to delete files/directories in the correct order.
131
     *
132
     * @param string $directory The dir
133
     *
134
     * @return bool
135
     */
136
    public function removeDirectoryPhp($directory)
137
    {
138
        $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
139
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
140
141
        foreach ($ri as $file) {
142
            if ($file->isDir()) {
143
                rmdir($file->getPathname());
144
            } else {
145
                unlink($file->getPathname());
146
            }
147
        }
148
149
        return rmdir($directory);
150
    }
151
152
    /**
153
     * Ensures a dir exists
154
     *
155
     * @param string $directory The dir
156
     *
157
     * @return void
158
     */
159
    public function ensureDirectoryExists($directory)
160
    {
161
        if (!is_dir($directory)) {
162
            if (file_exists($directory)) {
163
                throw new \RuntimeException(
164
                    $directory . ' exists and is not a directory.'
165
                );
166
            }
167
            if (!@mkdir($directory, 0777, true)) {
168
                throw new \RuntimeException(
169
                    $directory . ' does not exist and could not be created.'
170
                );
171
            }
172
        }
173
    }
174
175
    /**
176
     * Copy then delete is a non-atomic version of {@link rename}.
177
     *
178
     * Some systems can't rename and also don't have proc_open,
179
     * which requires this solution.
180
     *
181
     * @param string $source The source
182
     * @param string $target The target
183
     *
184
     * @return void
185
     */
186
    public function copyThenRemove($source, $target)
187
    {
188
        $this->copy($source, $target);
189
        $this->removeDirectoryPhp($source);
190
    }
191
192
    /**
193
     * Copy a file or folder to a target destination
194
     *
195
     * @param string $source The source
196
     * @param string $target The target
197
     *
198
     * @return void
199
     */
200
    public function copy($source, $target)
201
    {
202
        $path = realpath($source);
203
        if (is_dir($path)) {
204
            $it = new RecursiveDirectoryIterator($source, RecursiveDirectoryIterator::SKIP_DOTS);
205
            $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::SELF_FIRST);
206
207
            $this->ensureDirectoryExists($target);
208
209
            foreach ($ri as $file) {
210
                $targetPath = $target . DIRECTORY_SEPARATOR . $ri->getSubPathName();
211
                if ($file->isDir()) {
212
                    $this->ensureDirectoryExists($targetPath);
213
                } else {
214
                    copy($file->getPathname(), $targetPath);
215
                }
216
            }
217
        } else {
218
            $this->ensureDirectoryExists(dirname($target));
219
            copy($source, $target);
220
        }
221
    }
222
223
    /**
224
     * Rename a file or folder to a target destination
225
     *
226
     * @param string $source The source
227
     * @param string $target The target
228
     *
229
     * @return void
230
     */
231
    public function rename($source, $target)
232
    {
233
        if (true === @rename($source, $target)) {
234
            return;
235
        }
236
237
        if (!function_exists('proc_open')) {
238
            return $this->copyThenRemove($source, $target);
239
        }
240
241
        if (defined('PHP_WINDOWS_VERSION_BUILD')) {
242
            // Try to copy & delete - this is a workaround for random "Access denied" errors.
243
            $command = sprintf('xcopy %s %s /E /I /Q', escapeshellarg($source), escapeshellarg($target));
244
            $result = $this->console->createProcess($command)->run();
245
246
            // clear stat cache because external processes aren't tracked by the php stat cache
247
            clearstatcache();
248
249
            if (0 === $result) {
250
                $this->remove($source);
251
252
                return;
253
            }
254
        } else {
255
            // We do not use PHP's "rename" function here since it does not support
256
            // the case where $source, and $target are located on different partitions.
257
            $command = sprintf('mv %s %s', escapeshellarg($source), escapeshellarg($target));
258
            try {
259
                $this->console->createProcess($command)->run();
260
                // clear stat cache because external processes aren't tracked by the php stat cache
261
                clearstatcache();
262
                return;
263
            } catch (\Exception $e) {
264
                // do copyThenRemove
265
            }
266
        }
267
268
        return $this->copyThenRemove($source, $target);
269
    }
270
271
    /**
272
     * Returns the shortest path from $from to $to
273
     *
274
     * @param string $from        From
275
     * @param string $to          To
276
     * @param bool   $directories if true, the source/target are considered to be directories
277
     *
278
     * @throws \InvalidArgumentException
279
     *
280
     * @return string
281
     */
282
    public function findShortestPath($from, $to, $directories = false)
283
    {
284 View Code Duplication
        if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
285
            throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to));
286
        }
287
288
        $from = lcfirst($this->normalizePath($from));
289
        $to = lcfirst($this->normalizePath($to));
290
291
        if ($directories) {
292
            $from .= '/dummy_file';
293
        }
294
295
        if (dirname($from) === dirname($to)) {
296
            return './' . basename($to);
297
        }
298
299
        $commonPath = $to;
300 View Code Duplication
        while (strpos($from . '/', $commonPath . '/') !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
301
            $commonPath = strtr(dirname($commonPath), '\\', '/');
302
        }
303
304
        if (0 !== strpos($from, $commonPath) || '/' === $commonPath) {
305
            return $to;
306
        }
307
308
        $commonPath = rtrim($commonPath, '/') . '/';
309
        $sourcePathDepth = substr_count(substr($from, strlen($commonPath)), '/');
310
        $commonPathCode = str_repeat('../', $sourcePathDepth);
311
312
        return ($commonPathCode . substr($to, strlen($commonPath))) ?: './';
313
    }
314
315
    /**
316
     * Returns PHP code that, when executed in $from, will return the path to $to
317
     *
318
     * @param string $from        From
319
     * @param string $to          To
320
     * @param bool   $directories if true, the source/target are considered to be directories
321
     *
322
     * @throws \InvalidArgumentException
323
     *
324
     * @return string
325
     */
326
    public function findShortestPathCode($from, $to, $directories = false)
327
    {
328 View Code Duplication
        if (!$this->isAbsolutePath($from) || !$this->isAbsolutePath($to)) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
329
            throw new \InvalidArgumentException(sprintf('$from (%s) and $to (%s) must be absolute paths.', $from, $to));
330
        }
331
332
        $from = lcfirst($this->normalizePath($from));
333
        $to = lcfirst($this->normalizePath($to));
334
335
        if ($from === $to) {
336
            return $directories ? '__DIR__' : '__FILE__';
337
        }
338
339
        $commonPath = $to;
340 View Code Duplication
        while (strpos($from . '/', $commonPath . '/') !== 0 && '/' !== $commonPath && !preg_match('{^[a-z]:/?$}i', $commonPath) && '.' !== $commonPath) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

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.

Loading history...
341
            $commonPath = strtr(dirname($commonPath), '\\', '/');
342
        }
343
344
        if (0 !== strpos($from, $commonPath) || '/' === $commonPath || '.' === $commonPath) {
345
            return var_export($to, true);
346
        }
347
348
        $commonPath = rtrim($commonPath, '/') . '/';
349
        if (strpos($to, $from . '/') === 0) {
350
            return '__DIR__ . ' . var_export(substr($to, strlen($from)), true);
351
        }
352
        $sourcePathDepth = substr_count(substr($from, strlen($commonPath)), '/') + $directories;
353
        $commonPathCode = str_repeat('dirname(', $sourcePathDepth) . '__DIR__' . str_repeat(')', $sourcePathDepth);
354
        $relTarget = substr($to, strlen($commonPath));
355
356
        return $commonPathCode . (strlen($relTarget) ? '.' . var_export('/' . $relTarget, true) : '');
357
    }
358
359
    /**
360
     * Checks if the given path is absolute
361
     *
362
     * @param string $path Path
363
     *
364
     * @return bool
365
     */
366
    public function isAbsolutePath($path)
367
    {
368
        return substr($path, 0, 1) === '/' || substr($path, 1, 1) === ':';
369
    }
370
371
    /**
372
     * Returns size of a file or directory specified by path. If a directory is
373
     * given, it's size will be computed recursively.
374
     *
375
     * @param string $path Path to the file or directory
376
     *
377
     * @throws \RuntimeException
378
     *
379
     * @return int
380
     */
381
    public function size($path)
382
    {
383
        if (!file_exists($path)) {
384
            throw new \RuntimeException("$path does not exist.");
385
        }
386
        if (is_dir($path)) {
387
            return $this->directorySize($path);
388
        }
389
390
        return filesize($path);
391
    }
392
393
    /**
394
     * Normalize a path. This replaces backslashes with slashes, removes ending
395
     * slash and collapses redundant separators and up-level references.
396
     *
397
     * @param string $path Path to the file or directory
398
     *
399
     * @return string
400
     */
401
    public function normalizePath($path)
402
    {
403
        $parts = array();
404
        $path = strtr($path, '\\', '/');
405
        $prefix = '';
406
        $absolute = false;
407
408
        if (preg_match('{^([0-9a-z]+:(?://(?:[a-z]:)?)?)}i', $path, $match)) {
409
            $prefix = $match[1];
410
            $path = substr($path, strlen($prefix));
411
        }
412
413
        if (substr($path, 0, 1) === '/') {
414
            $absolute = true;
415
            $path = substr($path, 1);
416
        }
417
418
        $up = false;
419
        foreach (explode('/', $path) as $chunk) {
420
            if ('..' === $chunk && ($absolute || $up)) {
421
                array_pop($parts);
422
                $up = !(empty($parts) || '..' === end($parts));
423
            } elseif ('.' !== $chunk && '' !== $chunk) {
424
                $parts[] = $chunk;
425
                $up = '..' !== $chunk;
426
            }
427
        }
428
429
        return $prefix . ($absolute ? '/' : '') . implode('/', $parts);
430
    }
431
432
    /**
433
     * Get directory size
434
     *
435
     * @param string $directory The dir
436
     *
437
     * @return int
438
     */
439
    protected function directorySize($directory)
440
    {
441
        $it = new RecursiveDirectoryIterator($directory, RecursiveDirectoryIterator::SKIP_DOTS);
442
        $ri = new RecursiveIteratorIterator($it, RecursiveIteratorIterator::CHILD_FIRST);
443
444
        $size = 0;
445
        foreach ($ri as $file) {
446
            if ($file->isFile()) {
447
                $size += $file->getSize();
448
            }
449
        }
450
451
        return $size;
452
    }
453
}
454
?>
455