Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
LocalFilesystem
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
6 / 6
18
100.00% covered (success)
100.00%
1 / 1
 readFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 readPrefix
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 openReadable
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 writeFile
100.00% covered (success)
100.00%
8 / 8
100.00% covered (success)
100.00%
1 / 1
7
 assertReadableFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 assertLocalPath
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
1<?php
2
3declare(strict_types=1);
4
5namespace Phpdftk\Filesystem;
6
7final class LocalFilesystem
8{
9    public static function readFile(string $path, string $label = 'file'): string
10    {
11        self::assertReadableFile($path, $label);
12
13        $bytes = file_get_contents($path);
14        // @codeCoverageIgnoreStart
15        // Defensive: assertReadableFile validated is_file + is_readable; this
16        // branch fires only on a race (file deleted/permissions revoked between
17        // the check and the read) or a transient I/O error.
18        if ($bytes === false) {
19            throw new \RuntimeException("Cannot read $label$path");
20        }
21        // @codeCoverageIgnoreEnd
22
23        return $bytes;
24    }
25
26    public static function readPrefix(string $path, int $length, string $label = 'file'): string
27    {
28        self::assertReadableFile($path, $label);
29
30        $bytes = file_get_contents($path, false, null, 0, $length);
31        // @codeCoverageIgnoreStart
32        // Defensive: see readFile().
33        if ($bytes === false) {
34            throw new \RuntimeException("Cannot read $label$path");
35        }
36        // @codeCoverageIgnoreEnd
37
38        return $bytes;
39    }
40
41    /** @return resource */
42    public static function openReadable(string $path, string $label = 'file')
43    {
44        self::assertReadableFile($path, $label);
45
46        $handle = fopen($path, 'rb');
47        // @codeCoverageIgnoreStart
48        // Defensive: assertReadableFile validated is_file + is_readable.
49        if ($handle === false) {
50            throw new \RuntimeException("Cannot open $label$path");
51        }
52        // @codeCoverageIgnoreEnd
53
54        return $handle;
55    }
56
57    public static function writeFile(string $path, string $bytes, bool $createDirectories = false): void
58    {
59        self::assertLocalPath($path);
60
61        $dir = dirname($path);
62        if (!is_dir($dir) && $createDirectories && !mkdir($dir, 0755, true) && !is_dir($dir)) {
63            throw new \RuntimeException("Directory does not exist: $dir");
64        }
65
66        if (!is_dir($dir)) {
67            throw new \RuntimeException("Directory does not exist: $dir");
68        }
69
70        if (file_put_contents($path, $bytes) === false) {
71            throw new \RuntimeException("Cannot write file: $path");
72        }
73    }
74
75    public static function assertReadableFile(string $path, string $label = 'file'): void
76    {
77        self::assertLocalPath($path);
78
79        if (!is_file($path) || !is_readable($path)) {
80            throw new \RuntimeException("Cannot read $label$path");
81        }
82    }
83
84    public static function assertLocalPath(string $path): void
85    {
86        if (preg_match('/^[A-Za-z][A-Za-z0-9+.-]*:\\/\\//', $path) === 1) {
87            throw new \InvalidArgumentException("Stream wrappers are not allowed for file paths: $path");
88        }
89    }
90}