• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

nette / utils / 5940594296

22 Aug 2023 03:01PM UTC coverage: 92.065%. Remained the same
5940594296

push

github

dg
DateTime: modify() and modifyClone() throw exception on error (#293)

5 of 5 new or added lines in 1 file covered. (100.0%)

1868 of 2029 relevant lines covered (92.07%)

0.92 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

74.66
/src/Utils/FileSystem.php
1
<?php
2

3
/**
4
 * This file is part of the Nette Framework (https://nette.org)
5
 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6
 */
7

8
declare(strict_types=1);
9

10
namespace Nette\Utils;
11

12
use Nette;
13

14

15
/**
16
 * File system tool.
17
 */
18
final class FileSystem
19
{
20
        /**
21
         * Creates a directory if it does not exist, including parent directories.
22
         * @throws Nette\IOException  on error occurred
23
         */
24
        public static function createDir(string $dir, int $mode = 0777): void
1✔
25
        {
26
                if (!is_dir($dir) && !@mkdir($dir, $mode, true) && !is_dir($dir)) { // @ - dir may already exist
1✔
27
                        throw new Nette\IOException(sprintf(
1✔
28
                                "Unable to create directory '%s' with mode %s. %s",
1✔
29
                                self::normalizePath($dir),
1✔
30
                                decoct($mode),
1✔
31
                                Helpers::getLastError(),
1✔
32
                        ));
33
                }
34
        }
1✔
35

36

37
        /**
38
         * Copies a file or an entire directory. Overwrites existing files and directories by default.
39
         * @throws Nette\IOException  on error occurred
40
         * @throws Nette\InvalidStateException  if $overwrite is set to false and destination already exists
41
         */
42
        public static function copy(string $origin, string $target, bool $overwrite = true): void
1✔
43
        {
44
                if (stream_is_local($origin) && !file_exists($origin)) {
1✔
45
                        throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin)));
1✔
46

47
                } elseif (!$overwrite && file_exists($target)) {
1✔
48
                        throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target)));
1✔
49

50
                } elseif (is_dir($origin)) {
1✔
51
                        static::createDir($target);
1✔
52
                        foreach (new \FilesystemIterator($target) as $item) {
1✔
53
                                static::delete($item->getPathname());
1✔
54
                        }
55

56
                        foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
1✔
57
                                if ($item->isDir()) {
1✔
58
                                        static::createDir($target . '/' . $iterator->getSubPathName());
×
59
                                } else {
60
                                        static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName());
1✔
61
                                }
62
                        }
63
                } else {
64
                        static::createDir(dirname($target));
1✔
65
                        if (@stream_copy_to_stream(static::open($origin, 'rb'), static::open($target, 'wb')) === false) { // @ is escalated to exception
1✔
66
                                throw new Nette\IOException(sprintf(
×
67
                                        "Unable to copy file '%s' to '%s'. %s",
×
68
                                        self::normalizePath($origin),
×
69
                                        self::normalizePath($target),
×
70
                                        Helpers::getLastError(),
×
71
                                ));
72
                        }
73
                }
74
        }
1✔
75

76

77
        /**
78
         * Opens file and returns resource.
79
         * @return resource
80
         * @throws Nette\IOException  on error occurred
81
         */
82
        public static function open(string $path, string $mode)
1✔
83
        {
84
                $f = @fopen($path, $mode); // @ is escalated to exception
1✔
85
                if (!$f) {
1✔
86
                        throw new Nette\IOException(sprintf(
1✔
87
                                "Unable to open file '%s'. %s",
1✔
88
                                self::normalizePath($path),
1✔
89
                                Helpers::getLastError(),
1✔
90
                        ));
91
                }
92
                return $f;
1✔
93
        }
94

95

96
        /**
97
         * Deletes a file or an entire directory if exists. If the directory is not empty, it deletes its contents first.
98
         * @throws Nette\IOException  on error occurred
99
         */
100
        public static function delete(string $path): void
1✔
101
        {
102
                if (is_file($path) || is_link($path)) {
1✔
103
                        $func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink';
1✔
104
                        if (!@$func($path)) { // @ is escalated to exception
1✔
105
                                throw new Nette\IOException(sprintf(
×
106
                                        "Unable to delete '%s'. %s",
×
107
                                        self::normalizePath($path),
×
108
                                        Helpers::getLastError(),
1✔
109
                                ));
110
                        }
111
                } elseif (is_dir($path)) {
1✔
112
                        foreach (new \FilesystemIterator($path) as $item) {
1✔
113
                                static::delete($item->getPathname());
1✔
114
                        }
115

116
                        if (!@rmdir($path)) { // @ is escalated to exception
1✔
117
                                throw new Nette\IOException(sprintf(
×
118
                                        "Unable to delete directory '%s'. %s",
×
119
                                        self::normalizePath($path),
×
120
                                        Helpers::getLastError(),
×
121
                                ));
122
                        }
123
                }
124
        }
1✔
125

126

127
        /**
128
         * Renames or moves a file or a directory. Overwrites existing files and directories by default.
129
         * @throws Nette\IOException  on error occurred
130
         * @throws Nette\InvalidStateException  if $overwrite is set to false and destination already exists
131
         */
132
        public static function rename(string $origin, string $target, bool $overwrite = true): void
1✔
133
        {
134
                if (!$overwrite && file_exists($target)) {
1✔
135
                        throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target)));
1✔
136

137
                } elseif (!file_exists($origin)) {
1✔
138
                        throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin)));
1✔
139

140
                } else {
141
                        static::createDir(dirname($target));
1✔
142
                        if (realpath($origin) !== realpath($target)) {
1✔
143
                                static::delete($target);
1✔
144
                        }
145

146
                        if (!@rename($origin, $target)) { // @ is escalated to exception
1✔
147
                                throw new Nette\IOException(sprintf(
×
148
                                        "Unable to rename file or directory '%s' to '%s'. %s",
×
149
                                        self::normalizePath($origin),
×
150
                                        self::normalizePath($target),
×
151
                                        Helpers::getLastError(),
×
152
                                ));
153
                        }
154
                }
155
        }
1✔
156

157

158
        /**
159
         * Reads the content of a file.
160
         * @throws Nette\IOException  on error occurred
161
         */
162
        public static function read(string $file): string
1✔
163
        {
164
                $content = @file_get_contents($file); // @ is escalated to exception
1✔
165
                if ($content === false) {
1✔
166
                        throw new Nette\IOException(sprintf(
1✔
167
                                "Unable to read file '%s'. %s",
1✔
168
                                self::normalizePath($file),
1✔
169
                                Helpers::getLastError(),
1✔
170
                        ));
171
                }
172

173
                return $content;
1✔
174
        }
175

176

177
        /**
178
         * Reads the file content line by line. Because it reads continuously as we iterate over the lines,
179
         * it is possible to read files larger than the available memory.
180
         * @return \Generator<int, string>
181
         * @throws Nette\IOException  on error occurred
182
         */
183
        public static function readLines(string $file, bool $stripNewLines = true): \Generator
1✔
184
        {
185
                return (function ($f) use ($file, $stripNewLines) {
1✔
186
                        $counter = 0;
1✔
187
                        do {
188
                                $line = Callback::invokeSafe('fgets', [$f], fn($error) => throw new Nette\IOException(sprintf(
1✔
189
                                        "Unable to read file '%s'. %s",
×
190
                                        self::normalizePath($file),
×
191
                                        $error,
192
                                )));
1✔
193
                                if ($line === false) {
1✔
194
                                        fclose($f);
1✔
195
                                        break;
1✔
196
                                }
197
                                if ($stripNewLines) {
1✔
198
                                        $line = rtrim($line, "\r\n");
1✔
199
                                }
200

201
                                yield $counter++ => $line;
1✔
202

203
                        } while (true);
1✔
204
                })(static::open($file, 'r'));
1✔
205
        }
206

207

208
        /**
209
         * Writes the string to a file.
210
         * @throws Nette\IOException  on error occurred
211
         */
212
        public static function write(string $file, string $content, ?int $mode = 0666): void
1✔
213
        {
214
                static::createDir(dirname($file));
1✔
215
                if (@file_put_contents($file, $content) === false) { // @ is escalated to exception
1✔
216
                        throw new Nette\IOException(sprintf(
×
217
                                "Unable to write file '%s'. %s",
×
218
                                self::normalizePath($file),
×
219
                                Helpers::getLastError(),
×
220
                        ));
221
                }
222

223
                if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception
1✔
224
                        throw new Nette\IOException(sprintf(
×
225
                                "Unable to chmod file '%s' to mode %s. %s",
×
226
                                self::normalizePath($file),
×
227
                                decoct($mode),
×
228
                                Helpers::getLastError(),
×
229
                        ));
230
                }
231
        }
1✔
232

233

234
        /**
235
         * Sets file permissions to `$fileMode` or directory permissions to `$dirMode`.
236
         * Recursively traverses and sets permissions on the entire contents of the directory as well.
237
         * @throws Nette\IOException  on error occurred
238
         */
239
        public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void
1✔
240
        {
241
                if (is_file($path)) {
1✔
242
                        if (!@chmod($path, $fileMode)) { // @ is escalated to exception
1✔
243
                                throw new Nette\IOException(sprintf(
×
244
                                        "Unable to chmod file '%s' to mode %s. %s",
×
245
                                        self::normalizePath($path),
×
246
                                        decoct($fileMode),
×
247
                                        Helpers::getLastError(),
1✔
248
                                ));
249
                        }
250
                } elseif (is_dir($path)) {
1✔
251
                        foreach (new \FilesystemIterator($path) as $item) {
1✔
252
                                static::makeWritable($item->getPathname(), $dirMode, $fileMode);
1✔
253
                        }
254

255
                        if (!@chmod($path, $dirMode)) { // @ is escalated to exception
1✔
256
                                throw new Nette\IOException(sprintf(
×
257
                                        "Unable to chmod directory '%s' to mode %s. %s",
×
258
                                        self::normalizePath($path),
×
259
                                        decoct($dirMode),
×
260
                                        Helpers::getLastError(),
1✔
261
                                ));
262
                        }
263
                } else {
264
                        throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($path)));
1✔
265
                }
266
        }
1✔
267

268

269
        /**
270
         * Determines if the path is absolute.
271
         */
272
        public static function isAbsolute(string $path): bool
1✔
273
        {
274
                return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path);
1✔
275
        }
276

277

278
        /**
279
         * Normalizes `..` and `.` and directory separators in path.
280
         */
281
        public static function normalizePath(string $path): string
1✔
282
        {
283
                $parts = $path === '' ? [] : preg_split('~[/\\\\]+~', $path);
1✔
284
                $res = [];
1✔
285
                foreach ($parts as $part) {
1✔
286
                        if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') {
1✔
287
                                array_pop($res);
1✔
288
                        } elseif ($part !== '.') {
1✔
289
                                $res[] = $part;
1✔
290
                        }
291
                }
292

293
                return $res === ['']
1✔
294
                        ? DIRECTORY_SEPARATOR
1✔
295
                        : implode(DIRECTORY_SEPARATOR, $res);
1✔
296
        }
297

298

299
        /**
300
         * Joins all segments of the path and normalizes the result.
301
         */
302
        public static function joinPaths(string ...$paths): string
1✔
303
        {
304
                return self::normalizePath(implode('/', $paths));
1✔
305
        }
306

307

308
        /**
309
         * Converts backslashes to slashes.
310
         */
311
        public static function unixSlashes(string $path): string
1✔
312
        {
313
                return strtr($path, '\\', '/');
1✔
314
        }
315

316

317
        /**
318
         * Converts slashes to platform-specific directory separators.
319
         */
320
        public static function platformSlashes(string $path): string
1✔
321
        {
322
                return DIRECTORY_SEPARATOR === '/'
1✔
323
                        ? strtr($path, '\\', '/')
1✔
324
                        : str_replace(':\\\\', '://', strtr($path, '/', '\\')); // protocol://
1✔
325
        }
326
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc