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

FileHelper::createDirectory()   B

Complexity

Conditions 7
Paths 7

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 11.6004

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 12
c 1
b 0
f 0
nc 7
nop 2
dl 0
loc 19
ccs 6
cts 11
cp 0.5455
crap 11.6004
rs 8.8333
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