Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
95.83% covered (success)
95.83%
46 / 48
86.67% covered (warning)
86.67%
13 / 15
CRAP
0.00% covered (danger)
0.00%
0 / 1
File
95.83% covered (success)
95.83%
46 / 48
86.67% covered (warning)
86.67%
13 / 15
27
0.00% covered (danger)
0.00%
0 / 1
 createEmptyFileIfNotExists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getFileSize
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 readContent
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 saveContent
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 writeContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 appendContent
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 readJsonFile
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 writeJsonFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 exists
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 copy
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 removeFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 removeFileIfExists
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 createUniqueFilePath
83.33% covered (warning)
83.33%
5 / 6
0.00% covered (danger)
0.00%
0 / 1
3.04
 createTemporaryFilePath
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 createTemporaryFileStream
75.00% covered (warning)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
1<?php
2
3declare(strict_types=1);
4
5namespace PeServer\Core\IO;
6
7use stdClass;
8use PeServer\Core\Binary;
9use PeServer\Core\Cryptography;
10use PeServer\Core\Encoding;
11use PeServer\Core\Environment;
12use PeServer\Core\Errors\ErrorHandler;
13use PeServer\Core\IO\Directory;
14use PeServer\Core\IO\IOState;
15use PeServer\Core\IO\IOUtility;
16use PeServer\Core\IO\Stream;
17use PeServer\Core\Serialization\Json;
18use PeServer\Core\ResultData;
19use PeServer\Core\Serialization\JsonSerializer;
20use PeServer\Core\Text;
21use PeServer\Core\Throws\ArgumentException;
22use PeServer\Core\Throws\FileNotFoundException;
23use PeServer\Core\Throws\IOException;
24use PeServer\Core\Throws\ParseException;
25
26/**
27 * ファイル処理系。
28 */
29abstract class File
30{
31    #region function
32
33    /**
34     * ファイルが存在しない場合に空ファイルの作成。
35     *
36     * `touch` ラッパー。
37     *
38     * @param string $path
39     * @see https://www.php.net/manual/function.touch.php
40     */
41    public static function createEmptyFileIfNotExists(string $path): void
42    {
43        touch($path);
44    }
45
46    /**
47     * ファイルサイズを取得。
48     *
49     * @param string $path
50     * @return int
51     * @phpstan-return non-negative-int
52     * @see https://www.php.net/manual/function.filesize.php
53     * @throws IOException
54     */
55    public static function getFileSize(string $path): int
56    {
57        $result = ErrorHandler::trap(fn () => filesize($path));
58        if ($result->isFailureOrFalse()) {
59            throw new IOException();
60        }
61
62        return $result->value;
63    }
64
65    /**
66     * ファイルの内容を取得。
67     *
68     * @param string $path
69     * @return Binary
70     * @see https://www.php.net/manual/function.file-get-contents.php
71     * @throws IOException
72     */
73    public static function readContent(string $path): Binary
74    {
75        $result = ErrorHandler::trap(fn () => file_get_contents($path));
76        if ($result->isFailureOrFalse()) {
77            throw new IOException($path);
78        }
79
80        return new Binary($result->value);
81    }
82
83    /**
84     * 対象ファイルに指定データを書き込み。
85     *
86     * @param string $path
87     * @param Binary $data
88     * @param boolean $append
89     * @return int 書き込みサイズ。
90     * @throws IOException
91     */
92    private static function saveContent(string $path, Binary $data, bool $append): int
93    {
94        $flag = $append ? FILE_APPEND : 0;
95
96        $result = ErrorHandler::trap(fn () => file_put_contents($path, $data->raw, LOCK_EX | $flag));
97        if ($result->isFailureOrFalse()) {
98            throw new IOException($path);
99        }
100
101        return $result->value;
102    }
103
104    /**
105     * 書き込み。
106     *
107     * @param string $path
108     * @param Binary $data
109     * @return int 書き込みサイズ。
110     * @throws IOException
111     */
112    public static function writeContent(string $path, Binary $data): int
113    {
114        return self::saveContent($path, $data, false);
115    }
116
117    /**
118     * 追記。
119     *
120     * @param string $path
121     * @param Binary $data
122     * @return int 書き込みサイズ。
123     * @throws IOException
124     */
125    public static function appendContent(string $path, Binary $data): int
126    {
127        return self::saveContent($path, $data, true);
128    }
129
130    /**
131     * JSONとしてファイル読み込み。
132     *
133     * @param string $path パス。
134     * @return array<mixed> 応答JSON。
135     * @param JsonSerializer|null $jsonSerializer JSON処理
136     * @throws IOException
137     * @throws ParseException パース失敗。
138     */
139    public static function readJsonFile(string $path, JsonSerializer $jsonSerializer = null): array
140    {
141        $content = self::readContent($path);
142
143        $jsonSerializer ??= new JsonSerializer();
144        /** @var array<mixed> */
145        $value = $jsonSerializer->load($content);
146
147        return $value;
148    }
149
150    /**
151     * JSONファイルとして出力。
152     *
153     * @param string $path
154     * @param array<mixed>|object $data
155     * @param JsonSerializer|null $jsonSerializer JSON処理
156     * @return int 書き込みサイズ。
157     * @throws IOException
158     * @throws ParseException
159     */
160    public static function writeJsonFile(string $path, array|object $data, ?JsonSerializer $jsonSerializer = null): int
161    {
162        $jsonSerializer ??= new JsonSerializer();
163        $value = $jsonSerializer->save($data);
164
165        return self::saveContent($path, $value, false);
166    }
167
168    /**
169     * ファイルが存在するか。
170     *
171     * `IOUtility::exists` より速い。
172     * `file_exists`より`is_file`の方が速いらすぃ
173     *
174     * `is_file` ラッパー。
175     *
176     * @param string $path
177     * @return boolean 存在するか。
178     * @see https://www.php.net/manual/function.is-file.php
179     */
180    public static function exists(string $path): bool
181    {
182        return is_file($path);
183    }
184
185    /**
186     * ファイルコピー。
187     *
188     * @param string $fromPath
189     * @param string $toPath
190     * @return bool
191     * @see https://www.php.net/manual/function.copy.php
192     */
193    public static function copy(string $fromPath, string $toPath): bool
194    {
195        return \copy($fromPath, $toPath);
196    }
197
198    /**
199     * ファイル削除。
200     *
201     * @param string $filePath ファイルパス。
202     * @throws IOException
203     */
204    public static function removeFile(string $filePath): void
205    {
206        $result = ErrorHandler::trap(fn () => unlink($filePath));
207        if ($result->isFailureOrFalse()) {
208            throw new IOException();
209        }
210    }
211
212    /**
213     * ファイルが存在する場合に削除する。
214     *
215     * @param string $filePath
216     * @return bool
217     */
218    public static function removeFileIfExists(string $filePath): bool
219    {
220        if (!IOUtility::exists($filePath)) {
221            return false;
222        }
223
224        $result = ErrorHandler::trap(fn () => unlink($filePath));
225        if (!$result->success) {
226            return false;
227        }
228
229        return $result->value;
230    }
231
232    /**
233     * 一意なファイルパスを取得。
234     *
235     * `tempnam` ラッパー。
236     *
237     * @param string $directoryPath ファイル名の親ディレクトリ。
238     * @param string $prefix プレフィックス。
239     * @return string
240     * @see https://www.php.net/manual/function.tempnam.php
241     */
242    public static function createUniqueFilePath(string $directoryPath, string $prefix): string
243    {
244        if (Text::isNullOrWhiteSpace($directoryPath)) {
245            throw new ArgumentException('$directoryPath');
246        }
247
248        $result = tempnam($directoryPath, $prefix);
249        if ($result === false) {
250            throw new IOException();
251        }
252
253        return $result;
254    }
255
256    /**
257     * 一時ファイルの取得。
258     *
259     * @param string $prefix
260     * @return string
261     */
262    public static function createTemporaryFilePath(string $prefix = ''): string
263    {
264        if (Text::isNullOrWhiteSpace($prefix)) {
265            $prefixLength = PHP_OS === 'Windows' ? 3 : 64;
266            $prefix = Cryptography::generateRandomString($prefixLength, Cryptography::FILE_RANDOM_STRING);
267        }
268
269        return self::createUniqueFilePath(Directory::getTemporaryDirectory(), $prefix);
270    }
271
272    /**
273     * 一時ファイルのストリーム作成。
274     *
275     * メモリ・一時ファイル兼メモリのストリームを使用する場合は、
276     * `Stream::openMemory`, `Stream::openTemporary` を参照のこと。
277     *
278     * `tmpfile` ラッパー。
279     *
280     * @param Encoding|null $encoding
281     * @return Stream
282     * @throws IOException
283     * @see https://www.php.net/manual/function.tmpfile.php
284     */
285    public static function createTemporaryFileStream(?Encoding $encoding = null): Stream
286    {
287        $resource = tmpfile();
288        if ($resource === false) {
289            throw new IOException();
290        }
291
292        return new Stream($resource, $encoding);
293    }
294
295    #endregion
296}