Passed
Push — 0.8.x ( b9fea5...670f54 )
by Alexander
14:25 queued 11:19
created

Filesystem::delete()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 11
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 4
eloc 5
c 2
b 0
f 0
nc 6
nop 1
dl 0
loc 11
rs 10
1
<?php 
2
3
/**
4
 * Lenevor Framework
5
 *
6
 * LICENSE
7
 *
8
 * This source file is subject to the new BSD license that is bundled
9
 * with this package in the file license.md.
10
 * It is also available through the world-wide-web at this URL:
11
 * https://lenevor.com/license
12
 * If you did not receive a copy of the license and are unable to
13
 * obtain it through the world-wide-web, please send an email
14
 * to [email protected] so we can send you a copy immediately.
15
 *
16
 * @package     Lenevor
17
 * @subpackage  Base
18
 * @link        https://lenevor.com
19
 * @copyright   Copyright (c) 2019 - 2023 Alexander Campo <[email protected]>
20
 * @license     https://opensource.org/licenses/BSD-3-Clause New BSD license or see https://lenevor.com/license or see /license.md
21
 */
22
23
namespace Syscodes\Components\Filesystem;
24
25
use FilesystemIterator;
26
use Syscodes\Components\Filesystem\Exceptions\FileException;
27
use Syscodes\Components\Filesystem\Exceptions\FileNotFoundException;
28
use Syscodes\Components\Filesystem\Exceptions\FileUnableToMoveException;
29
30
/**
31
 * Provides basic utility to manipulate the file system.
32
 */
33
class Filesystem 
34
{
35
	/**
36
	 * Enable locking for file reading and writing.
37
	 *
38
	 * @var null|bool $lock
39
	 */
40
	public $lock = null;
41
42
	/**
43
	 * Holds the file handler resource if the file is opened.
44
	 *
45
	 * @var resource $handler
46
	 */
47
	protected $handler;
48
49
	/**
50
	 * The files size in bytes.
51
	 *
52
	 * @var float $size
53
	 */
54
	protected $size;
55
56
	/**
57
	 * Append given data string to this file.
58
	 *
59
	 * @param  string  $path
60
	 * @param  string  $data
61
	 *
62
	 * @return bool
63
	 */
64
	public function append($path, $data)
65
	{
66
		return file_put_contents($path, $data, FILE_APPEND);
0 ignored issues
show
Bug Best Practice introduced by
The expression return file_put_contents...Filesystem\FILE_APPEND) returns the type integer which is incompatible with the documented return type boolean.
Loading history...
67
	}
68
69
	/**
70
	 * Copy a file to a new location.
71
	 *
72
	 * @param  string  $path
73
	 * @param  string  $target
74
	 * 
75
	 * @return bool
76
	 */
77
	public function copy($path, $target): bool
78
	{
79
		return copy($path, $target);
80
	}
81
	
82
	/**
83
	 * Get the contents of a file as decoded JSON.
84
	 * 
85
	 * @param  string  $path
86
	 * @param  int  $flags
87
	 * @param  bool  $lock
88
	 * 
89
	 * @return array
90
	 * 
91
	 * @throws \Syscodes\Components\Contracts\Filesystem\FileNotFoundException
92
	 */
93
	public function json($path, $flags = 0, $lock = false)
94
	{
95
		return json_decode($this->get($path, $lock), true, 512, $flags);
96
	}
97
98
	/**
99
	 * Get the contents of a file.
100
	 *
101
	 * @param  string  $path
102
	 * @param  bool  $lock  
103
	 * @param  bool  $force  
104
	 *
105
	 * @return string
106
	 *
107
	 * @throws FileNotFoundException
108
	 */
109
	public function get($path, $lock = false, $force = false): string
110
	{
111
		if ($this->isFile($path)) {
112
			return $lock ? $this->read($path, $force) : file_get_contents($path);
113
		}
114
115
		throw new FileNotFoundException($path);
116
	}
117
118
	/**
119
	 * Get contents of a file with shared access.
120
	 *
121
	 * @param  string  $path
122
	 * @param  bool  $force  
123
	 *
124
	 * @return string
125
	 */
126
	public function read($path, $force = false): string
127
	{
128
		$contents = '';
129
130
		$this->open($path, 'rb', $force);
131
		
132
		if ($this->handler) {
133
			try {
134
				if (flock($this->handler, LOCK_SH)) {
135
					$this->clearStatCache($path);
136
137
					$contents = fread($this->handler, $this->getSize($path) ?: 1);
0 ignored issues
show
Bug introduced by
Are you sure the usage of $this->getSize($path) targeting Syscodes\Components\File...m\Filesystem::getSize() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
138
					
139
					while ( ! feof($this->handler)) {
140
						$contents .= fgets($this->handler, 4096);
141
					}
142
143
					flock($this->handler, LOCK_UN);
144
				}
145
			} finally {
146
				$this->close();
147
			}
148
		}
149
150
		return trim($contents);
151
	}
152
153
	/**
154
	 * Opens the current file with a given $mode.
155
	 *
156
	 * @param  string  $path
157
	 * @param  string  $mode  A valid 'fopen' mode string (r|w|a ...)
158
	 * @param  bool  $force  
159
	 *
160
	 * @return bool
161
	 */
162
	public function open($path, $mode, $force = false): bool
163
	{
164
		if ( ! $force && is_resource($this->handler)) {
165
			return true;
166
		}
167
168
		if ($this->exists($path) === false) {
169
			if ($this->create($path) === false) {
170
				return false;
171
			}
172
		}
173
174
		$this->handler = fopen($path, $mode);
175
176
		return is_resource($this->handler);
177
	}
178
179
	/**
180
	 * Creates the file.
181
	 * 
182
	 * @param  string  $path
183
	 * 
184
	 * @return bool
185
	 */
186
	public function create($path): bool
187
	{
188
		if (($this->isDirectory($path)) && ($this->isWritable($path)) || ( ! $this->exists($path))) {
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($this->isDirectory($pat... ! $this->exists($path), Probably Intended Meaning: $this->isDirectory($path...! $this->exists($path))
Loading history...
189
			if (touch($path)) {
190
				return true;
191
			}
192
		}
193
194
		return false;
195
	}
196
197
	/**
198
	 * Determine if a file exists.
199
	 *
200
	 * @param  string  $path
201
	 *
202
	 * @return bool
203
	 */
204
	public function exists($path): bool
205
	{
206
		$this->clearStatCache($path);
207
208
		return file_exists($path);
209
	}
210
211
	/**
212
	 * Clear PHP's internal stat cache.
213
	 *
214
	 * @param  string  $path
215
	 * @param  bool  $all  Clear all cache or not
216
	 *
217
	 * @return void
218
	 */
219
	public function clearStatCache($path, $all = false): void
220
	{
221
		if ($all === false) {
222
			clearstatcache(false, $path);
223
		}
224
225
		clearstatcache();
226
	}
227
228
	/**
229
	 * Get the returned value of a file.
230
	 * 
231
	 * @param  string  $path
232
	 * @param  array  $data
233
	 * 
234
	 * @return mixed
235
	 * 
236
	 * @throws \Syscodes\Filesystem\Exceptions\FileNotFoundException
237
	 */
238
	public function getRequire($path, array $data = [])
239
	{
240
		if ($this->isFile($path)) {
241
			$__path = $path;
242
			$__data = $data;
243
244
			return (static function () use ($__path, $__data) {
245
				extract($__data, EXTR_SKIP);
246
247
				return require $__path;
248
			})();
249
		}
250
251
		throw new FileNotFoundException($path);
252
	}
253
254
	/**
255
	 * Require the given file once.
256
	 * 
257
	 * @param  string  $path
258
	 * @param  array  $data
259
	 * 
260
	 * @return mixed
261
	 * 
262
	 * @throws \Syscodes\Filesystem\Exceptions\FileNotFoundException
263
	 */
264
	public function getRequireOnce($path, array $data = [])
265
	{
266
		if ($this->isFile($path)) {
267
			$__path = $path;
268
			$__data = $data;
269
270
			return (static function () use ($__path, $__data) {
271
				extract($__data, EXTR_SKIP);
272
273
				return require_once $__path;
274
			})();
275
		}
276
277
		throw new FileNotFoundException($path);
278
	}
279
280
	/**
281
	 * Retrieve the file size.
282
	 *
283
	 * Implementations SHOULD return the value stored in the "size" key of
284
	 * the file in the $_FILES array if available, as PHP calculates this
285
	 * based on the actual size transmitted.
286
	 *
287
	 * @param  string  $path
288
	 * @param  string  $unit
289
	 * 
290
	 * @return int|null  The file size in bytes or null if unknown
291
	 */
292
	public function getSize($path, $unit = 'b')
293
	{
294
		if ( ! $this->exists($path)) {
295
			if (is_null($this->size)) {
0 ignored issues
show
introduced by
The condition is_null($this->size) is always false.
Loading history...
296
				$this->size = filesize($path);
297
			}
298
299
			return match (strtolower($unit)) {
300
				'kb' => number_format($this->size / 1024, 3),
301
				'mb' => number_format(($this->size / 1024) / 1024, 3),
302
				default => $this->size,
303
			};
304
		}
305
306
		return null;
307
	}
308
	
309
	/**
310
	 * Returns the file's group.
311
	 *
312
	 * @param  string  $path
313
	 * 
314
	 * @return int|bool  The file group, or false in case of an error
315
	 */
316
	public function group($path)
317
	{
318
		if ( ! $this->exists($path)) {
319
			return filegroup($path);
320
		}
321
322
		return false;
323
	}
324
	
325
	/**
326
	 * Returns true if the file is executable.
327
	 *
328
	 * @param  string  $path
329
	 * 
330
	 * @return bool  True if file is executable, false otherwise
331
	 */
332
	public function exec($path): bool
333
	{
334
		return is_executable($path);
335
	}
336
337
	/**
338
	 * Determine if the given path is a directory.
339
	 *
340
	 * @param  string  $directory
341
	 *
342
	 * @return bool
343
	 */
344
	public function isDirectory($directory): bool
345
	{
346
		return is_dir($directory);
347
	}
348
349
	/**
350
	 * Determine if the given path is a file.
351
	 *
352
	 * @param  string  $file
353
	 *
354
	 * @return bool
355
	 */
356
	public function isFile($file): bool
357
	{
358
		return is_file($file);
359
	}
360
361
	/**
362
	 * Determine if the given path is writable.
363
	 * 
364
	 * @param  string  $path
365
	 * 
366
	 * @return bool
367
	 */
368
	public function isWritable($path): bool
369
	{
370
		return is_writable($path);
371
	}
372
373
	/**
374
	 * Returns if true the file is readable.
375
	 *
376
	 * @param  string  $path
377
	 * 
378
	 * @return bool  True if file is readable, false otherwise
379
	 */
380
	public function isReadable($path): bool
381
	{
382
		return is_readable($path);
383
	}
384
385
	/**
386
	 * Returns last access time.
387
	 *
388
	 * @param  string  $path
389
	 * 
390
	 * @return int|bool  Timestamp of last access time, or false in case of an error
391
	 */
392
	public function lastAccess($path)
393
	{
394
		if ( ! $this->exists($path)) {
395
			return fileatime($path);
396
		}
397
398
		return false;
399
	}
400
401
	/**
402
	 * Returns last modified time.
403
	 *
404
	 * @param  string  $path
405
	 * 
406
	 * @return int|bool  Timestamp of last modified time, or false in case of an error
407
	 */
408
	public function lastModified($path)
409
	{
410
		if ( ! $this->exists($path)) {
411
			return filemtime($path);
412
		}
413
414
		return false;		
415
	}
416
417
	/**
418
	 * Get all of the directories within a given directory.
419
	 * 
420
	 * @param  string  $directory
421
	 * 
422
	 * @return array
423
	 */
424
	public function directories($directory): array
425
	{
426
		$directories = [];
427
428
		$iterators = new FilesystemIterator($directory);
429
430
		foreach ($iterators as $iterator) {
431
			$directories[] = trim($iterator->getPathname(), '/').'/';
432
		}
433
434
		return $directories;
435
	}
436
437
	/**
438
	 * Delete the file at a given path.
439
	 * 
440
	 * @param  string  $paths
441
	 * 
442
	 * @return bool
443
	 */
444
	public function delete($paths): bool
445
	{
446
		$paths = is_array($paths) ? $paths : func_get_args();
447
448
		$success = true;
449
450
		foreach ($paths as $path) {
451
			if ( ! @unlink($path)) $success = false;
452
		}
453
454
		return $success;
455
	}
456
	
457
	/**
458
	 * Get the hash of the file at the given path.
459
	 * 
460
	 * @param  string  $path
461
	 * @param  string  $algorithm
462
	 * 
463
	 * @return string
464
	 */
465
	public function hash($path, $algorithm = 'md5'): string
466
	{
467
		return hash_file($algorithm, $path);
468
	}
469
470
	/**
471
	 * Create a directory.
472
	 *
473
	 * @param  string  $path
474
	 * @param  int  $mode
475
	 * @param  bool  $recursive
476
	 * @param  bool  $force
477
	 *
478
	 * @return bool
479
	 * 
480
	 * @throws FileException
481
	 */
482
	public function makeDirectory($path, $mode = 0755, $recursive = false, $force = false): bool
483
	{
484
		if ($force) {
485
			return @mkdir($path, $mode, $recursive);
486
		}
487
488
		mkdir($path, $mode, $recursive);
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
489
	}
490
491
	/**
492
	 * Copy a directory from one location to another.
493
	 * 
494
	 * @param  string  $directory
495
	 * @param  string  $destination
496
	 * @param  int  $options  
497
	 * 
498
	 * @return bool
499
	 */
500
	public function copyDirectory($directory, $destination, $options = null): bool
501
	{
502
		if ( ! $this->isDirectory($directory)) return false;
503
504
		$options = $options ?: FilesystemIterator::SKIP_DOTS;
505
		
506
		// If the destination directory does not actually exist, we will go ahead and
507
		// create it recursively, which just gets the destination prepared to copy
508
		// the files over. Once we make the directory we'll proceed the copying.
509
		if ( ! $this->isdirectory($destination)) {
510
			$this->makeDirectory($destination, 0777, true);
511
		}
512
513
		$iterators = new FilesystemIterator($directory, $options);
514
515
		foreach ($iterators as $iterator) {
516
			$target = $destination.DIRECTORY_SEPARATOR.$iterator->getBasename();
517
			
518
			// As we spin through items, we will check to see if the current file is actually
519
			// a directory or a file. When it is actually a directory we will need to call
520
			// back into this function recursively to keep copying these nested folders.
521
			if ($iterator->isDir()) {
522
				if ( ! $this->copyDirectory($iterator->getPathname(), $target, $options)) return false;
523
			}
524
			// If the current items is just a regular file, we will just copy this to the new
525
			// location and keep looping. If for some reason the copy fails we'll bail out
526
			// and return false, so the developer is aware that the copy process failed.
527
			else {
528
				if ( ! $this->copy($iterator->getPathname(), $target)) return false;
529
			}
530
		}
531
532
		return true;
533
	}
534
535
	/**
536
	 * Recursively delete a directory and optionally you can keep 
537
	 * the directory if you wish.
538
	 * 
539
	 * @param  string  $directory
540
	 * @param  bool  $keep
541
	 * 
542
	 * @return bool
543
	 */
544
	public function deleteDirectory($directory, $keep = false): bool
545
	{
546
		if ( ! $this->isDirectory($directory)) return false;
547
548
		$iterators = new filesystemIterator($directory);
549
550
		foreach ($iterators as $iterator) {
551
			// If the item is a directory, we can just recurse into the function and delete 
552
			// that sub-directory otherwise we'll just delete the file and keep iterating 
553
			// through each file until the directory is cleaned.
554
			if ($iterator->isDir() && ! $iterator->isLink()) {
555
				$this->deleteDirectory($iterator->getPathname());
556
			}
557
			// If the item is just a file, we can go ahead and delete it since we're
558
			// just looping through and waxing all of the files in this directory
559
			// and calling directories recursively, so we delete the real path.
560
			else {
561
				$this->delete($iterator->getPathname());
562
			}
563
		}
564
565
		if ( ! $keep) @rmdir($directory);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for rmdir(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

565
		if ( ! $keep) /** @scrutinizer ignore-unhandled */ @rmdir($directory);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
566
567
		return true;
568
	}
569
570
	/**
571
	 * Empty the specified directory of all files and folders.
572
	 * 
573
	 * 
574
	 * @param  string  $directory
575
	 * 
576
	 * @return bool
577
	 */
578
	public function cleanDirectory($directory): bool
579
	{
580
		return $this->deleteDirectory($directory, true);
581
	}
582
583
	/**
584
	 * Moves a file to a new location.
585
	 * 
586
	 * @param  string  $from
587
	 * @param  string  $to
588
	 * @param  bool  $overwrite  
589
	 * 
590
	 * @return bool
591
	 */
592
	public function moveDirectory($from, $to, $overwrite = false): bool
593
	{
594
		if ($overwrite && $this->isDirectory($to) && ! $this->deleteDirectory($to)) return false;
595
596
		if (false === @rename($from, $to)) {
597
			$error = error_get_last();
598
599
			throw new FileUnableToMoveException($from, $to, strip_tags($error['message']));
600
		}
601
602
		$this->perms($to, 0777 & ~umask());
0 ignored issues
show
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
603
	}
604
605
	/**
606
	 * Attempts to determine the file extension based on the trusted
607
	 * getType() method. If the mime type is unknown, will return null.
608
	 * 
609
	 * @param  string  $path
610
	 * 
611
	 * @return string|null
612
	 */
613
	public function guessExtension($path)
614
	{
615
		return FileMimeType::guessExtensionFromType($this->getMimeType($path));
616
	}
617
618
	/**
619
	 * Retrieve the media type of the file. 
620
	 * 
621
	 * @param  string  $path
622
	 * 
623
	 * @return string|null
624
	 */
625
	public function getMimeType($path)
626
	{
627
		$finfo    = finfo_open(FILEINFO_MIME_TYPE);
628
		$mimeType = finfo_file($finfo, $path);
629
630
		finfo_close($finfo);
631
632
		return $mimeType;
633
	}
634
635
	/**
636
	 * Move a file to a new location.
637
	 *
638
	 * @param  string  $path
639
	 * @param  string  $target
640
	 *
641
	 * @return bool
642
	 */
643
	public function move($path, $target): bool
644
	{
645
		if ($this->exists($path)) {
646
			return rename($path, $target);
647
		}
0 ignored issues
show
Bug Best Practice introduced by
The function implicitly returns null when the if condition on line 645 is false. This is incompatible with the type-hinted return boolean. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

class MyClass implements ReturnsInt {
    public function returnsIntHinted(): int
    {
        if (foo()) {
            return 123;
        }
        // here: null is implicitly returned
    }
}
Loading history...
648
	}
649
650
	/**
651
	 * Extract the file name from a file path.
652
	 * 
653
	 * @param  string  $path
654
	 * 
655
	 * @return string
656
	 */
657
	public function name($path)
658
	{
659
		return pathinfo($path, PATHINFO_FILENAME);
0 ignored issues
show
Bug Best Practice introduced by
The expression return pathinfo($path, S...stem\PATHINFO_FILENAME) also could return the type array which is incompatible with the documented return type string.
Loading history...
660
	}
661
662
	/**
663
	 * Extract the trailing name component from a file path.
664
	 * 
665
	 * @param  string  $path
666
	 * 
667
	 * @return string
668
	 */
669
	public function basename($path)
670
	{
671
		return pathinfo($path, PATHINFO_BASENAME);
0 ignored issues
show
Bug Best Practice introduced by
The expression return pathinfo($path, S...stem\PATHINFO_BASENAME) also could return the type array which is incompatible with the documented return type string.
Loading history...
672
	}
673
674
	/**
675
	 * Extract the parent directory from a file path.
676
	 * 
677
	 * @param  string  $path
678
	 * 
679
	 * @return string
680
	 */
681
	public function dirname($path)
682
	{
683
		return pathinfo($path, PATHINFO_DIRNAME);
0 ignored issues
show
Bug Best Practice introduced by
The expression return pathinfo($path, S...ystem\PATHINFO_DIRNAME) also could return the type array which is incompatible with the documented return type string.
Loading history...
684
	}
685
686
	/**
687
	 * Extract the file extension from a file path.
688
	 * 
689
	 * @param  string  $path
690
	 * 
691
	 * @return string
692
	 */
693
	public function extension($path)
694
	{
695
		return pathinfo($path, PATHINFO_EXTENSION);
0 ignored issues
show
Bug Best Practice introduced by
The expression return pathinfo($path, S...tem\PATHINFO_EXTENSION) also could return the type array which is incompatible with the documented return type string.
Loading history...
696
	}
697
698
	/**
699
	 *  Find path names matching a given pattern.
700
	 * 
701
	 * @param  string  $pattern
702
	 * @param  int  $flags  (0 by default)
703
	 * 
704
	 * @return array
705
	 */
706
	public function glob($pattern, $flags = 0): bool
707
	{
708
		return glob($pattern, $flags);
0 ignored issues
show
Bug Best Practice introduced by
The expression return glob($pattern, $flags) returns the type array which is incompatible with the type-hinted return boolean.
Loading history...
709
	}
710
711
	/**
712
	 * Returns the file's owner.
713
	 *
714
	 * @param  string  $path
715
	 * 
716
	 * @return int|bool  The file owner, or false in case of an error
717
	 */
718
	public function owner($path)
719
	{
720
		if ($this->exists($path)) {
721
			return fileowner($path);
722
		}
723
724
		return false;
725
	}
726
727
	/**
728
	 * Returns the "chmod" (permissions) of the file.
729
	 *
730
	 * @param  string  $path
731
	 * @param  int|null  $mode  
732
	 * 
733
	 * @return mixed  Permissions for the file, or false in case of an error
734
	 */
735
	public function perms($path, $mode = null)
736
	{
737
		if ($mode) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $mode of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
738
			chmod($path, $mode);
739
		}
740
741
		return substr(sprintf('%o', fileperms($path)), -4);
742
	}
743
744
	/**
745
	 * Prepend to a file.
746
	 * 
747
	 * @param  string  $path
748
	 * @param  string  $data
749
	 * 
750
	 * @return int
751
	 */
752
	public function prepend($path, $data): int
753
	{
754
		if ($this->exists($path)) {
755
			$this->put($path, $data.$this->get($path));
756
		}
757
758
		return $this->put($path, $data);
759
	}
760
761
	/**
762
	 * Write the content of a file.
763
	 *
764
	 * @param  string  $path
765
	 * @param  string  $contents
766
	 * @param  bool  $lock  
767
	 *
768
	 * @return int|bool
769
	 */
770
	public function put($path, $contents, $lock = false): int|bool
771
	{
772
		return file_put_contents($path, $contents, $lock ? LOCK_EX : 0);
773
	}
774
775
	/**
776
	 * Get the file type of a given file.
777
	 * 
778
	 * @param  string  $path
779
	 * 
780
	 * @return string
781
	 */
782
	public function type($path): string
783
	{
784
		return filetype($path);
785
	}
786
	
787
	/**
788
	 * Write the contents of a file, replacing it atomically if it already exists.
789
	 * 
790
	 * @param  string  $path
791
	 * @param  string  $content
792
	 * 
793
	 * @return void
794
	 */
795
	public function replace($path, $content): void
796
	{
797
		$this->clearstatcache($path);
798
		
799
		$path = realpath($path) ?: $path;
800
		
801
		$tempPath = tempnam(dirname($path), basename($path));
802
		
803
		$this->perms($tempPath, 0777 - umask());
804
		
805
		$this->put($tempPath, $content);
806
		
807
		$this->move($tempPath, $path);
808
    }
809
810
	/**
811
	 * Searches for a given text and replaces the text if found.
812
	 *
813
	 * @param  string  $path
814
	 * @param  string  $search
815
	 * @param  string  $replace
816
	 *
817
	 * @return bool
818
	 */
819
	public function replaceText($path, $search, $replace): bool
820
	{
821
		if ( ! $this->open($path, 'r+')) {
822
			return false;
823
		}
824
825
		if ($this->lock !== null) {
826
			if (flock($this->handler, LOCK_EX) === false)
827
			{
828
				return false;
829
			}
830
		}
831
832
		$replaced = $this->write($path, str_replace($search, $replace, $this->get($path)), true);
833
834
		if ($this->lock !== null) {
835
			flock($this->handler, LOCK_UN);
836
		}
837
838
		$this->close();
839
840
		return $replaced;
841
	}	
842
843
	/**
844
	 * Closes the current file if it is opened.
845
	 *
846
	 * @return bool
847
	 */
848
	public function close(): bool
849
	{
850
		if ( ! is_resource($this->handler)) {
851
			return true;
852
		}
853
854
		return fclose($this->handler);
855
	}
856
857
	/**
858
	 * Write given data to this file.
859
	 *
860
	 * @param  string  $path
861
	 * @param  string  $data  Data to write to this File
862
	 * @param  bool  $force  The file to open
863
	 *
864
	 * @return bool
865
	 */
866
	public function write($path, $data, $force = false): bool
867
	{
868
		$success = false;
869
870
		if ($this->open($path, 'w', $force) === true) {
871
			if ($this->lock !== null) {
872
				if (flock($this->handler, LOCK_EX) === false) {
873
					return false;
874
				}
875
			}
876
877
			if (fwrite($this->handler, $data) !== false) {
878
				$success = true;
879
			}
880
881
			if ($this->lock !== null) {
882
				flock($this->handler, LOCK_UN);
883
			}
884
		}
885
886
		return $success;
887
	}
888
}