Subversion-Projekte lars-tiefland.laravel_shop

Revision

Revision 148 | Details | Vergleich mit vorheriger | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
148 lars 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
	use Nette\StaticClass;
21
 
22
	/**
399 lars 23
	 * Creates a directory if it does not exist, including parent directories.
148 lars 24
	 * @throws Nette\IOException  on error occurred
25
	 */
26
	public static function createDir(string $dir, int $mode = 0777): void
27
	{
28
		if (!is_dir($dir) && !@mkdir($dir, $mode, true) && !is_dir($dir)) { // @ - dir may already exist
29
			throw new Nette\IOException(sprintf(
30
				"Unable to create directory '%s' with mode %s. %s",
31
				self::normalizePath($dir),
32
				decoct($mode),
399 lars 33
				Helpers::getLastError(),
148 lars 34
			));
35
		}
36
	}
37
 
38
 
39
	/**
399 lars 40
	 * Copies a file or an entire directory. Overwrites existing files and directories by default.
148 lars 41
	 * @throws Nette\IOException  on error occurred
42
	 * @throws Nette\InvalidStateException  if $overwrite is set to false and destination already exists
43
	 */
44
	public static function copy(string $origin, string $target, bool $overwrite = true): void
45
	{
46
		if (stream_is_local($origin) && !file_exists($origin)) {
47
			throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin)));
48
 
49
		} elseif (!$overwrite && file_exists($target)) {
50
			throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target)));
51
 
52
		} elseif (is_dir($origin)) {
53
			static::createDir($target);
54
			foreach (new \FilesystemIterator($target) as $item) {
55
				static::delete($item->getPathname());
56
			}
57
 
58
			foreach ($iterator = new \RecursiveIteratorIterator(new \RecursiveDirectoryIterator($origin, \RecursiveDirectoryIterator::SKIP_DOTS), \RecursiveIteratorIterator::SELF_FIRST) as $item) {
59
				if ($item->isDir()) {
60
					static::createDir($target . '/' . $iterator->getSubPathName());
61
				} else {
62
					static::copy($item->getPathname(), $target . '/' . $iterator->getSubPathName());
63
				}
64
			}
65
		} else {
66
			static::createDir(dirname($target));
399 lars 67
			if (@stream_copy_to_stream(static::open($origin, 'rb'), static::open($target, 'wb')) === false) { // @ is escalated to exception
148 lars 68
				throw new Nette\IOException(sprintf(
69
					"Unable to copy file '%s' to '%s'. %s",
70
					self::normalizePath($origin),
71
					self::normalizePath($target),
399 lars 72
					Helpers::getLastError(),
148 lars 73
				));
74
			}
75
		}
76
	}
77
 
78
 
79
	/**
399 lars 80
	 * Opens file and returns resource.
81
	 * @return resource
148 lars 82
	 * @throws Nette\IOException  on error occurred
83
	 */
399 lars 84
	public static function open(string $path, string $mode)
85
	{
86
		$f = @fopen($path, $mode); // @ is escalated to exception
87
		if (!$f) {
88
			throw new Nette\IOException(sprintf(
89
				"Unable to open file '%s'. %s",
90
				self::normalizePath($path),
91
				Helpers::getLastError(),
92
			));
93
		}
94
		return $f;
95
	}
96
 
97
 
98
	/**
99
	 * Deletes a file or an entire directory if exists. If the directory is not empty, it deletes its contents first.
100
	 * @throws Nette\IOException  on error occurred
101
	 */
148 lars 102
	public static function delete(string $path): void
103
	{
104
		if (is_file($path) || is_link($path)) {
105
			$func = DIRECTORY_SEPARATOR === '\\' && is_dir($path) ? 'rmdir' : 'unlink';
106
			if (!@$func($path)) { // @ is escalated to exception
107
				throw new Nette\IOException(sprintf(
108
					"Unable to delete '%s'. %s",
109
					self::normalizePath($path),
399 lars 110
					Helpers::getLastError(),
148 lars 111
				));
112
			}
113
		} elseif (is_dir($path)) {
114
			foreach (new \FilesystemIterator($path) as $item) {
115
				static::delete($item->getPathname());
116
			}
117
 
118
			if (!@rmdir($path)) { // @ is escalated to exception
119
				throw new Nette\IOException(sprintf(
120
					"Unable to delete directory '%s'. %s",
121
					self::normalizePath($path),
399 lars 122
					Helpers::getLastError(),
148 lars 123
				));
124
			}
125
		}
126
	}
127
 
128
 
129
	/**
130
	 * Renames or moves a file or a directory. Overwrites existing files and directories by default.
131
	 * @throws Nette\IOException  on error occurred
132
	 * @throws Nette\InvalidStateException  if $overwrite is set to false and destination already exists
133
	 */
134
	public static function rename(string $origin, string $target, bool $overwrite = true): void
135
	{
136
		if (!$overwrite && file_exists($target)) {
137
			throw new Nette\InvalidStateException(sprintf("File or directory '%s' already exists.", self::normalizePath($target)));
138
 
139
		} elseif (!file_exists($origin)) {
140
			throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($origin)));
141
 
142
		} else {
143
			static::createDir(dirname($target));
144
			if (realpath($origin) !== realpath($target)) {
145
				static::delete($target);
146
			}
147
 
148
			if (!@rename($origin, $target)) { // @ is escalated to exception
149
				throw new Nette\IOException(sprintf(
150
					"Unable to rename file or directory '%s' to '%s'. %s",
151
					self::normalizePath($origin),
152
					self::normalizePath($target),
399 lars 153
					Helpers::getLastError(),
148 lars 154
				));
155
			}
156
		}
157
	}
158
 
159
 
160
	/**
161
	 * Reads the content of a file.
162
	 * @throws Nette\IOException  on error occurred
163
	 */
164
	public static function read(string $file): string
165
	{
166
		$content = @file_get_contents($file); // @ is escalated to exception
167
		if ($content === false) {
168
			throw new Nette\IOException(sprintf(
169
				"Unable to read file '%s'. %s",
170
				self::normalizePath($file),
399 lars 171
				Helpers::getLastError(),
148 lars 172
			));
173
		}
174
 
175
		return $content;
176
	}
177
 
178
 
179
	/**
399 lars 180
	 * Reads the file content line by line. Because it reads continuously as we iterate over the lines,
181
	 * it is possible to read files larger than the available memory.
182
	 * @return \Generator<int, string>
183
	 * @throws Nette\IOException  on error occurred
184
	 */
185
	public static function readLines(string $file, bool $stripNewLines = true): \Generator
186
	{
187
		return (function ($f) use ($file, $stripNewLines) {
188
			$counter = 0;
189
			do {
190
				$line = Callback::invokeSafe('fgets', [$f], fn($error) => throw new Nette\IOException(sprintf(
191
					"Unable to read file '%s'. %s",
192
					self::normalizePath($file),
193
					$error,
194
				)));
195
				if ($line === false) {
196
					fclose($f);
197
					break;
198
				}
199
				if ($stripNewLines) {
200
					$line = rtrim($line, "\r\n");
201
				}
202
 
203
				yield $counter++ => $line;
204
 
205
			} while (true);
206
		})(static::open($file, 'r'));
207
	}
208
 
209
 
210
	/**
148 lars 211
	 * Writes the string to a file.
212
	 * @throws Nette\IOException  on error occurred
213
	 */
214
	public static function write(string $file, string $content, ?int $mode = 0666): void
215
	{
216
		static::createDir(dirname($file));
217
		if (@file_put_contents($file, $content) === false) { // @ is escalated to exception
218
			throw new Nette\IOException(sprintf(
219
				"Unable to write file '%s'. %s",
220
				self::normalizePath($file),
399 lars 221
				Helpers::getLastError(),
148 lars 222
			));
223
		}
224
 
225
		if ($mode !== null && !@chmod($file, $mode)) { // @ is escalated to exception
226
			throw new Nette\IOException(sprintf(
227
				"Unable to chmod file '%s' to mode %s. %s",
228
				self::normalizePath($file),
229
				decoct($mode),
399 lars 230
				Helpers::getLastError(),
148 lars 231
			));
232
		}
233
	}
234
 
235
 
236
	/**
399 lars 237
	 * Sets file permissions to `$fileMode` or directory permissions to `$dirMode`.
238
	 * Recursively traverses and sets permissions on the entire contents of the directory as well.
148 lars 239
	 * @throws Nette\IOException  on error occurred
240
	 */
241
	public static function makeWritable(string $path, int $dirMode = 0777, int $fileMode = 0666): void
242
	{
243
		if (is_file($path)) {
244
			if (!@chmod($path, $fileMode)) { // @ is escalated to exception
245
				throw new Nette\IOException(sprintf(
246
					"Unable to chmod file '%s' to mode %s. %s",
247
					self::normalizePath($path),
248
					decoct($fileMode),
399 lars 249
					Helpers::getLastError(),
148 lars 250
				));
251
			}
252
		} elseif (is_dir($path)) {
253
			foreach (new \FilesystemIterator($path) as $item) {
254
				static::makeWritable($item->getPathname(), $dirMode, $fileMode);
255
			}
256
 
257
			if (!@chmod($path, $dirMode)) { // @ is escalated to exception
258
				throw new Nette\IOException(sprintf(
259
					"Unable to chmod directory '%s' to mode %s. %s",
260
					self::normalizePath($path),
261
					decoct($dirMode),
399 lars 262
					Helpers::getLastError(),
148 lars 263
				));
264
			}
265
		} else {
266
			throw new Nette\IOException(sprintf("File or directory '%s' not found.", self::normalizePath($path)));
267
		}
268
	}
269
 
270
 
271
	/**
272
	 * Determines if the path is absolute.
273
	 */
274
	public static function isAbsolute(string $path): bool
275
	{
276
		return (bool) preg_match('#([a-z]:)?[/\\\\]|[a-z][a-z0-9+.-]*://#Ai', $path);
277
	}
278
 
279
 
280
	/**
281
	 * Normalizes `..` and `.` and directory separators in path.
282
	 */
283
	public static function normalizePath(string $path): string
284
	{
285
		$parts = $path === '' ? [] : preg_split('~[/\\\\]+~', $path);
286
		$res = [];
287
		foreach ($parts as $part) {
288
			if ($part === '..' && $res && end($res) !== '..' && end($res) !== '') {
289
				array_pop($res);
290
			} elseif ($part !== '.') {
291
				$res[] = $part;
292
			}
293
		}
294
 
295
		return $res === ['']
296
			? DIRECTORY_SEPARATOR
297
			: implode(DIRECTORY_SEPARATOR, $res);
298
	}
299
 
300
 
301
	/**
302
	 * Joins all segments of the path and normalizes the result.
303
	 */
304
	public static function joinPaths(string ...$paths): string
305
	{
306
		return self::normalizePath(implode('/', $paths));
307
	}
399 lars 308
 
309
 
310
	/**
311
	 * Converts backslashes to slashes.
312
	 */
313
	public static function unixSlashes(string $path): string
314
	{
315
		return strtr($path, '\\', '/');
316
	}
317
 
318
 
319
	/**
320
	 * Converts slashes to platform-specific directory separators.
321
	 */
322
	public static function platformSlashes(string $path): string
323
	{
324
		return DIRECTORY_SEPARATOR === '/'
325
			? strtr($path, '\\', '/')
326
			: str_replace(':\\\\', '://', strtr($path, '/', '\\')); // protocol://
327
	}
148 lars 328
}