Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
| Total | |
100.00% |
22 / 22 |
|
100.00% |
6 / 6 |
CRAP | |
100.00% |
1 / 1 |
| LocalFilesystem | |
100.00% |
22 / 22 |
|
100.00% |
6 / 6 |
18 | |
100.00% |
1 / 1 |
| readFile | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| readPrefix | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| openReadable | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
2 | |||
| writeFile | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
7 | |||
| assertReadableFile | |
100.00% |
3 / 3 |
|
100.00% |
1 / 1 |
3 | |||
| assertLocalPath | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
| 1 | <?php |
| 2 | |
| 3 | declare(strict_types=1); |
| 4 | |
| 5 | namespace Phpdftk\Filesystem; |
| 6 | |
| 7 | final 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 | } |