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}