Passed
Pull Request — master (#6)
by Roman
01:59
created

FileHelper::normalizePath()   C

Complexity

Conditions 14
Paths 36

Size

Total Lines 26
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 16
CRAP Score 14.0398

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 14
eloc 16
c 2
b 0
f 0
nc 36
nop 1
dl 0
loc 26
ccs 16
cts 17
cp 0.9412
crap 14.0398
rs 6.2666

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
namespace Yiisoft\Files;
3
4
/**
5
 * FileHelper provides useful methods to manage files and directories
6
 */
7
class FileHelper
8
{
9
    /**
10
     * Normalizes a file/directory path.
11
     *
12
     * The normalization does the following work:
13
     *
14
     * - Convert all directory separators into `/` (e.g. "\a/b\c" becomes "/a/b/c")
15
     * - Remove trailing directory separators (e.g. "/a/b/c/" becomes "/a/b/c")
16
     * - Turn multiple consecutive slashes into a single one (e.g. "/a///b/c" becomes "/a/b/c")
17
     * - Remove ".." and "." based on their meanings (e.g. "/a/./b/../c" becomes "/a/c")
18
     *
19
     * @param string $path the file/directory path to be normalized
20
     * @return string the normalized file/directory path
21
     */
22 1
    public static function normalizePath(string $path): string
23
    {
24 1
        $isWindowsShare = strpos($path, '\\\\') === 0;
25 1
        if ($isWindowsShare) {
26 1
            $path = substr($path, 2);
27
        }
28
29 1
        $path = rtrim(strtr($path, '/\\', '//'), '/');
30 1
        if (strpos('/' . $path, '/.') === false && strpos($path, '//') === false) {
31 1
            return $isWindowsShare ? "\\\\$path" : $path;
32
        }
33
34 1
        $parts = [];
35
36 1
        foreach (explode('/', $path) as $part) {
37 1
            if ($part === '..' && !empty($parts) && end($parts) !== '..') {
38 1
                array_pop($parts);
39 1
            } elseif ($part !== '.' && ($part !== '' || empty($parts))) {
40 1
                $parts[] = $part;
41
            }
42
        }
43 1
        $path = implode('/', $parts);
44 1
        if ($isWindowsShare) {
45
            $path = '\\\\' . $path;
46
        }
47 1
        return $path === '' ? '.' : $path;
48
    }
49
50
    /**
51
     * Removes a directory (and all its content) recursively.
52
     *
53
     * @param string $directory the directory to be deleted recursively.
54
     * @param array $options options for directory remove. Valid options are:
55
     *
56
     * - traverseSymlinks: boolean, whether symlinks to the directories should be traversed too.
57
     *   Defaults to `false`, meaning the content of the symlinked directory would not be deleted.
58
     *   Only symlink would be removed in that default case.
59
     */
60 6
    public static function removeDirectory($directory, $options = []): void
61
    {
62 6
        if (!is_dir($directory)) {
63 1
            return;
64
        }
65 6
        if (!empty($options['traverseSymlinks']) || !is_link($directory)) {
66 6
            if (!($handle = opendir($directory))) {
67
                return;
68
            }
69 6
            while (($file = readdir($handle)) !== false) {
70 6
                if ($file === '.' || $file === '..') {
71 6
                    continue;
72
                }
73 5
                $path = $directory . '/' . $file;
74 5
                if (is_dir($path)) {
75 5
                    self::removeDirectory($path, $options);
76
                } else {
77 3
                    self::unlink($path);
78
                }
79
            }
80 6
            closedir($handle);
81
        }
82 6
        if (is_link($directory)) {
83 2
            self::unlink($directory);
84
        } else {
85 6
            rmdir($directory);
86
        }
87 6
    }
88
89
    /**
90
     * Removes a file or symlink in a cross-platform way
91
     *
92
     * @param string $path
93
     * @return bool
94
     */
95 3
    public static function unlink($path): bool
96
    {
97 3
        $isWindows = DIRECTORY_SEPARATOR === '\\';
98
99 3
        if (!$isWindows) {
100 3
            return unlink($path);
101
        }
102
103
        if (is_link($path) && is_dir($path)) {
104
            return rmdir($path);
105
        }
106
107
        return unlink($path);
108
    }
109
110
    /**
111
     * Creates a new directory
112
     *
113
     * This method is similar to the PHP `mkdir()` function except that
114
     * it uses `chmod()` to set the permission of the created directory
115
     * in order to avoid the impact of the `umask` setting.
116
     *
117
     * @param string $path path of the directory to be created.
118
     * @param int $mode the permission to be set for the created directory.
119
     * @return bool whether the directory is created successfully
120
     */
121 6
    public static function createDirectory($path, $mode = 0775): bool
122
    {
123 6
        if (is_dir($path)) {
124 1
            return true;
125
        }
126
        try {
127 6
            if (!mkdir($path, $mode, true) && !is_dir($path)) {
128 6
                return false;
129
            }
130
        } catch (\Exception $e) {
131
            if (!is_dir($path)) { // https://github.com/yiisoft/yii2/issues/9288
132
                throw new \RuntimeException("Failed to create directory \"$path\": " . $e->getMessage(), $e->getCode(), $e);
133
            }
134
        }
135
136
        try {
137 6
            return chmod($path, $mode);
138
        } catch (\Exception $e) {
139
            throw new \RuntimeException("Failed to change permissions for directory \"$path\": " . $e->getMessage(), $e->getCode(), $e);
140
        }
141
    }
142
}
143