• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tempestphp / tempest-framework / 14729611330

29 Apr 2025 11:00AM UTC coverage: 80.654%. First build
14729611330

Pull #1150

github

web-flow
Merge 7c1d15bb8 into 76d70c153
Pull Request #1150: refactor(filesystem): move from package to functions

155 of 202 new or added lines in 10 files covered. (76.73%)

12945 of 16050 relevant lines covered (80.65%)

101.87 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

73.68
/src/Tempest/Support/src/Filesystem/functions.php
1
<?php
2

3
declare(strict_types=1);
4

5
namespace Tempest\Support\Filesystem {
6
    use FilesystemIterator;
7

8
    use function copy as php_copy;
9
    use function dirname;
10
    use function file_exists;
11
    use function fileperms;
12
    use function is_dir;
13
    use function is_executable as php_is_executable;
14
    use function is_file as php_is_file;
15
    use function is_link as php_is_link;
16
    use function is_readable as php_is_readable;
17
    use function is_writable as php_is_writable;
18
    use function mkdir;
19
    use function readlink;
20
    use function Tempest\Support\Arr\partition;
21
    use function Tempest\Support\Arr\values;
22
    use function Tempest\Support\box;
23
    use function touch;
24

25
    /**
26
     * Gets a parent directory path.
27
     */
28
    function get_directory(string $node, int $levels = 1): string
29
    {
30
        return dirname($node, $levels);
26✔
31
    }
32

33
    /**
34
     * Copies a file from `$source` to `$destination`.
35
     */
36
    function copy_file(string $source, string $destination, bool $overwrite = false): void
37
    {
38
        $destination_exists = namespace\is_file($destination);
5✔
39

40
        if (! $overwrite && $destination_exists) {
5✔
NEW
41
            return;
×
42
        }
43

44
        if (namespace\is_directory($source)) {
5✔
45
            throw Exceptions\NotFileException::for($source);
1✔
46
        }
47

48
        if (! namespace\is_file($source)) {
4✔
49
            throw Exceptions\NotFoundException::forFile($source);
1✔
50
        }
51

52
        if (! namespace\is_readable($source)) {
3✔
53
            throw Exceptions\NotReadableException::forFile($source);
1✔
54
        }
55

56
        namespace\create_directory_for_file($destination);
2✔
57

58
        [$result, $errorMessage] = box(static fn (): bool => php_copy($source, $destination));
2✔
59

60
        if ($result === false) {
2✔
NEW
61
            throw new Exceptions\RuntimeException(
×
NEW
62
                sprintf('Failed to copy source file "%s" to destination "%s": %s', $source, $destination, $errorMessage),
×
NEW
63
            );
×
64
        }
65
    }
66

67
    /**
68
     * Writes the specified `$content` to the specified `$filename`.
69
     */
70
    function write_file(string $filename, mixed $content, int $flags = 0): void
71
    {
72
        namespace\create_directory_for_file($filename);
21✔
73

74
        [$result, $errorMessage] = box(static fn (): int|false => file_put_contents($filename, $content, $flags));
21✔
75

76
        if (false === $result) {
21✔
77
            throw new Exceptions\RuntimeException(sprintf(
1✔
78
                'Failed to write to file "%s": %s.',
1✔
79
                $filename,
1✔
80
                $errorMessage ?? 'internal error',
1✔
81
            ));
1✔
82
        }
83
    }
84

85
    /**
86
     * Reads the content of the specified `$filename`.
87
     */
88
    function read_file(string $filename): string
89
    {
90
        if (! namespace\exists($filename)) {
3✔
91
            throw Exceptions\NotFoundException::forFile($filename);
1✔
92
        }
93

94
        if (! namespace\is_readable($filename)) {
2✔
95
            throw Exceptions\NotReadableException::forFile($filename);
1✔
96
        }
97

98
        [$result, $message] = box(static fn (): false|string => file_get_contents($filename));
1✔
99

100
        if (false === $result) {
1✔
NEW
101
            throw new Exceptions\RuntimeException(sprintf(
×
NEW
102
                'Failed to read file "%s": %s',
×
NEW
103
                $filename,
×
NEW
104
                $message ?? 'internal error',
×
NEW
105
            ));
×
106
        }
107

108
        return $result;
1✔
109
    }
110

111
    /**
112
     * Ensures that the specified directory exists.
113
     */
114
    function ensure_directory_exists(string $directory): void
115
    {
116
        if (! namespace\exists($directory)) {
22✔
117
            namespace\create_directory($directory);
20✔
118
        }
119
    }
120

121
    /**
122
     * Creates the directory specified by $directory.
123
     *
124
     * @mago-expect best-practices/no-boolean-literal-comparison
125
     */
126
    function create_directory(string $directory, int $permissions = 0o777): void
127
    {
128
        if (namespace\is_directory($directory)) {
68✔
129
            return;
21✔
130
        }
131

132
        [$result, $errorMessage] = box(static fn (): bool => mkdir($directory, $permissions, recursive: true));
67✔
133

134
        if ($result === false && ! namespace\is_directory($directory)) { // @phpstan-ignore booleanNot.alwaysTrue
67✔
135
            throw new Exceptions\RuntimeException(sprintf(
1✔
136
                'Failed to create directory "%s": %s.',
1✔
137
                $directory,
1✔
138
                $errorMessage ?? 'internal error',
1✔
139
            ));
1✔
140
        }
141
    }
142

143
    /**
144
     * Creates the directory where the $filename is or will be stored.
145
     *
146
     * @return non-empty-string
147
     */
148
    function create_directory_for_file(string $filename, int $permissions = 0o777): string
149
    {
150
        $directory = namespace\get_directory($filename);
25✔
151
        namespace\create_directory($directory, $permissions);
25✔
152

153
        return $directory;
25✔
154
    }
155

156
    /**
157
     * Creates the file specified by $filename.
158
     *
159
     * @mago-expect best-practices/no-boolean-literal-comparison
160
     * @mago-expect best-practices/no-else-clause
161
     */
162
    function create_file(string $filename, ?int $time = null, ?int $accessTime = null): void
163
    {
164
        if (null === $accessTime && null === $time) {
1✔
165
            $fun = static fn (): bool => touch($filename);
1✔
NEW
166
        } elseif (null === $accessTime) {
×
NEW
167
            $fun = static fn (): bool => touch($filename, $time);
×
168
        } else {
NEW
169
            $time ??= $accessTime;
×
NEW
170
            $fun = static fn (): bool => touch($filename, $time, max($accessTime, $time));
×
171
        }
172

173
        namespace\create_directory_for_file($filename);
1✔
174

175
        [$result, $errorMessage] = box($fun);
1✔
176

177
        if (false === $result && ! namespace\is_file($filename)) {
1✔
NEW
178
            throw new Exceptions\RuntimeException(sprintf(
×
NEW
179
                'Failed to create file "%s": %s.',
×
NEW
180
                $filename,
×
NEW
181
                $errorMessage ?? 'internal error',
×
NEW
182
            ));
×
183
        }
184
    }
185

186
    /**
187
     * Checks whether `$path` exists.
188
     */
189
    function exists(string $path): bool
190
    {
191
        return file_exists($path);
68✔
192
    }
193

194
    /**
195
     * Deletes the file or directory at the specified `$path`.
196
     */
197
    function delete(string $path, bool $recursive = true): void
198
    {
199
        if (! namespace\exists($path)) {
1✔
200
            return;
1✔
201
        }
202

203
        if (namespace\is_file($path)) {
1✔
204
            namespace\delete_file($path);
1✔
205
        } elseif (namespace\is_directory($path)) {
1✔
206
            namespace\delete_directory($path, $recursive);
1✔
207
        }
208
    }
209

210
    /**
211
     * Deletes the specified `$file`.
212
     *
213
     * @mago-expect best-practices/no-boolean-literal-comparison
214
     */
215
    function delete_file(string $file): void
216
    {
217
        if (! namespace\exists($file)) {
54✔
218
            throw Exceptions\NotFoundException::forFile($file);
1✔
219
        }
220

221
        if (! namespace\is_file($file)) {
53✔
222
            throw Exceptions\NotFileException::for($file);
1✔
223
        }
224

225
        [$result, $errorMessage] = box(static fn (): bool => unlink($file));
52✔
226

227
        if ($result === false && namespace\is_file($file)) { // @phpstan-ignore booleanAnd.rightAlwaysTrue
52✔
NEW
228
            throw new Exceptions\RuntimeException(sprintf(
×
NEW
229
                'Failed to delete file "%s": %s.',
×
NEW
230
                $file,
×
NEW
231
                $errorMessage ?? 'internal error',
×
NEW
232
            ));
×
233
        }
234
    }
235

236
    /**
237
     * Gets the permissions of the file or directory at the specified `$path`.
238
     *
239
     * @mago-expect best-practices/no-boolean-literal-comparison
240
     */
241
    function get_permissions(string $path): int
242
    {
243
        if (! namespace\exists($path)) {
3✔
244
            throw Exceptions\NotFoundException::forPath($path);
1✔
245
        }
246

247
        [$result, $message] = box(static fn (): int|false => fileperms($path));
2✔
248

249
        if (false === $result) {
2✔
NEW
250
            throw new Exceptions\RuntimeException(sprintf(
×
NEW
251
                'Failed to retrieve permissions of file "%s": %s',
×
NEW
252
                $path,
×
NEW
253
                $message ?? 'internal error',
×
NEW
254
            ));
×
255
        }
256

257
        return $result;
2✔
258
    }
259

260
    /**
261
     * Cleans the specified `$directory` by deleting its contents, optionally creating it if it doesn't exist.
262
     */
263
    function ensure_directory_empty(string $directory): void
264
    {
265
        if (namespace\exists($directory) && ! namespace\is_directory($directory)) {
48✔
266
            throw Exceptions\NotDirectoryException::for($directory);
1✔
267
        }
268

269
        if (! namespace\is_directory($directory)) {
48✔
270
            namespace\create_directory($directory);
48✔
271
            return;
48✔
272
        }
273

274
        $permissions = PHP_OS_FAMILY === 'Windows'
2✔
NEW
275
            ? namespace\get_permissions($directory)
×
276
            : 0o777;
2✔
277

278
        namespace\delete_directory($directory, recursive: true);
2✔
279
        namespace\create_directory($directory, $permissions);
2✔
280
    }
281

282
    /**
283
     * Deletes the specified $directory.
284
     *
285
     * @mago-expect best-practices/no-boolean-literal-comparison
286
     * @mago-expect best-practices/no-else-clause
287
     */
288
    function delete_directory(string $directory, bool $recursive = true): void
289
    {
290
        if ($recursive && ! namespace\is_symbolic_link($directory)) {
66✔
291
            [$symbolicLinks, $files] = partition(
66✔
292
                iterable: list_directory($directory),
66✔
293
                predicate: static fn (string $node): bool => namespace\is_symbolic_link($node),
66✔
294
            );
66✔
295

296
            foreach ($symbolicLinks as $symbolicLink) {
66✔
297
                namespace\delete_file($symbolicLink);
2✔
298
            }
299

300
            foreach ($files as $node) {
66✔
301
                if (! namespace\is_directory($node)) {
58✔
302
                    namespace\delete_file($node);
50✔
303
                } else {
304
                    namespace\delete_directory($node, recursive: true);
41✔
305
                }
306
            }
307
        } else {
308
            if (! namespace\exists($directory)) {
1✔
NEW
309
                throw Exceptions\NotFoundException::forDirectory($directory);
×
310
            }
311

312
            if (! namespace\is_directory($directory)) {
1✔
NEW
313
                throw Exceptions\NotDirectoryException::for($directory);
×
314
            }
315
        }
316

317
        [$result, $errorMessage] = box(static fn (): bool => rmdir($directory));
66✔
318

319
        if (false === $result && namespace\is_directory($directory)) {
66✔
320
            throw new Exceptions\RuntimeException(sprintf(
1✔
321
                'Failed to delete directory "%s": %s.',
1✔
322
                $directory,
1✔
323
                $errorMessage ?? 'internal error',
1✔
324
            ));
1✔
325
        }
326
    }
327

328
    /**
329
     * Checks whether $path exists and is a regular file or a link to one.
330
     */
331
    function is_file(string $path): bool
332
    {
333
        return php_is_file($path);
54✔
334
    }
335

336
    /**
337
     * Checks whether $path exists and is readable.
338
     */
339
    function is_readable(string $path): bool
340
    {
341
        return php_is_readable($path);
66✔
342
    }
343

344
    /**
345
     * Checks whether $path exists and is a symbolic link.
346
     */
347
    function is_symbolic_link(string $path): bool
348
    {
349
        return php_is_link($path);
66✔
350
    }
351

352
    /**
353
     * Checks whether $path exists and is writable.
354
     */
355
    function is_writable(string $path): bool
356
    {
357
        return php_is_writable($path);
1✔
358
    }
359

360
    /**
361
     * Checks whether $path exists and is an executable file
362
     * or a directory with `execute` permission.
363
     */
364
    function is_executable(string $path): bool
365
    {
NEW
366
        return php_is_executable($path);
×
367
    }
368

369
    /**
370
     * Checks whether $path exists and is a directory.
371
     */
372
    function is_directory(string $path): bool
373
    {
374
        return is_dir($path);
68✔
375
    }
376

377
    /**
378
     * Returns an array of files and directories inside the specified directory.
379
     *
380
     * @return array<non-empty-string>
381
     */
382
    function list_directory(string $directory): array
383
    {
384
        if (! namespace\exists($directory)) {
66✔
NEW
385
            throw Exceptions\NotFoundException::forDirectory($directory);
×
386
        }
387

388
        if (! namespace\is_directory($directory)) {
66✔
389
            throw Exceptions\NotDirectoryException::for($directory);
2✔
390
        }
391

392
        if (! namespace\is_readable($directory)) {
66✔
NEW
393
            throw Exceptions\NotReadableException::forDirectory($directory);
×
394
        }
395

396
        /** @var array<non-empty-string> */
397
        return values(new FilesystemIterator(
66✔
398
            $directory,
66✔
399
            FilesystemIterator::CURRENT_AS_PATHNAME | FilesystemIterator::SKIP_DOTS,
66✔
400
        ));
66✔
401
    }
402

403
    /**
404
     * Returns the target of a symbolic link.
405
     *
406
     * @mago-expect best-practices/no-boolean-literal-comparison
407
     */
408
    function read_symbolic_link(string $path): string
409
    {
410
        if (! namespace\exists($path)) {
2✔
NEW
411
            throw Exceptions\NotFoundException::forSymbolicLink($path);
×
412
        }
413

414
        if (! namespace\is_symbolic_link($path)) {
2✔
415
            throw Exceptions\NotSymbolicLinkException::for($path);
1✔
416
        }
417

418
        [$result, $message] = box(static fn (): false|string => readlink($path));
1✔
419

420
        if (false === $result) {
1✔
NEW
421
            throw new Exceptions\RuntimeException(sprintf(
×
NEW
422
                'Failed to retrieve the target of symbolic link "%s": %s',
×
NEW
423
                $path,
×
NEW
424
                $message ?? 'internal error',
×
NEW
425
            ));
×
426
        }
427

428
        return $result;
1✔
429
    }
430
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc