friendship ended with social-app. php is my new best friend
1<?php 2/** 3 * Class File 4 * 5 * @created 29.10.2024 6 * @author smiley <smiley@chillerlan.net> 7 * @copyright 2024 smiley 8 * @license MIT 9 */ 10declare(strict_types=1); 11 12namespace chillerlan\Utilities; 13 14use InvalidArgumentException; 15use RuntimeException; 16use function clearstatcache; 17use function dirname; 18use function file_exists; 19use function file_get_contents; 20use function file_put_contents; 21use function is_file; 22use function is_link; 23use function is_readable; 24use function is_writable; 25use function readlink; 26use function realpath; 27use function unlink; 28 29/** 30 * Basic file operations 31 */ 32final class File{ 33 34 /** 35 * Checks whether a file exists 36 * 37 * @codeCoverageIgnore 38 */ 39 public static function exists(string $file):bool{ 40 return file_exists($file) && is_file($file); 41 } 42 43 /** 44 * Checks whether the given file is readable 45 * 46 * @codeCoverageIgnore 47 */ 48 public static function isReadable(string $file):bool{ 49 return self::exists($file) && is_readable($file); 50 } 51 52 /** 53 * Checks whether the given file is writable 54 * 55 * @codeCoverageIgnore 56 */ 57 public static function isWritable(string $file):bool{ 58 return self::exists($file) && is_writable($file); 59 } 60 61 /** 62 * Returns the absolute real path to the given file or directory 63 * 64 * @codeCoverageIgnore 65 */ 66 public static function realpath(string $path):string{ 67 68 if(is_link($path)){ 69 $path = readlink($path); 70 71 if($path === false){ 72 throw new InvalidArgumentException('invalid link'); 73 } 74 } 75 76 $realpath = realpath($path); 77 78 if($realpath === false){ 79 throw new InvalidArgumentException('invalid path'); 80 } 81 82 return $realpath; 83 } 84 85 /** 86 * Deletes a file 87 */ 88 public static function delete(string $file):bool{ 89 $file = self::realpath($file); 90 91 if(!self::isWritable($file)){ 92 throw new RuntimeException('cannot read the given file'); // @codeCoverageIgnore 93 } 94 95 if(!unlink($file)){ 96 throw new RuntimeException('unlink error'); // @codeCoverageIgnore 97 } 98 99 clearstatcache(); 100 101 return true; 102 } 103 104 /** 105 * reads the given file into a string 106 * 107 * @see \file_get_contents() 108 * 109 * @throws \RuntimeException 110 */ 111 public static function load(string $file, int $offset = 0, int|null $length = null):string{ 112 $file = self::realpath($file); 113 114 if(!self::isReadable($file)){ 115 throw new RuntimeException('cannot read the given file'); // @codeCoverageIgnore 116 } 117 118 $content = file_get_contents(filename: $file, offset: $offset, length: $length); 119 120 if($content === false){ 121 throw new RuntimeException('could not load file contents'); // @codeCoverageIgnore 122 } 123 124 return $content; 125 } 126 127 /** 128 * saves the given data string to the given file path 129 */ 130 public static function save(string $file, string $data):int{ 131 132 if(!Directory::isWritable(dirname($file))){ 133 throw new RuntimeException('target directory is not writable or does not extist'); 134 } 135 136 $result = file_put_contents($file, $data); 137 138 if($result === false){ 139 throw new RuntimeException('could not save file contents'); // @codeCoverageIgnore 140 } 141 142 return $result; 143 } 144 145 /** 146 * load a JSON string from file into an array or object (convenience) 147 * 148 * @codeCoverageIgnore 149 */ 150 public static function loadJSON(string $file, bool $associative = false, int $flags = 0):mixed{ 151 return Str::jsonDecode(self::load($file), $associative, $flags); 152 } 153 154 /** 155 * save to a JSON file (convenience) 156 * 157 * @codeCoverageIgnore 158 */ 159 public static function saveJSON(string $file, mixed $data, int $flags = Str::JSON_ENCODE_FLAGS_DEFAULT):int{ 160 return self::save($file, Str::jsonEncode($data, $flags)); 161 } 162 163}