diff --git a/CHANGELOG.md b/CHANGELOG.md index 6ab20029a..4a4d10a74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,3 +15,4 @@ * introduced a new `Psl\IO\Stream` component. * refactored `Psl\IO` handles API. * introduced a new `Psl\File` component. +* refactor `Psl\Filesystem\write_file`, `Psl\Filesystem\append_file`, and `Psl\Filesystem\read_file` to use `Psl\File` component. diff --git a/docs/component/filesystem.md b/docs/component/filesystem.md index 71356fbaf..289ec1d3c 100644 --- a/docs/component/filesystem.md +++ b/docs/component/filesystem.md @@ -16,7 +16,7 @@ #### `Functions` -- [append_file](./../../src/Psl/Filesystem/append_file.php#L18) +- [append_file](./../../src/Psl/Filesystem/append_file.php#L21) - [canonicalize](./../../src/Psl/Filesystem/canonicalize.php#L15) - [change_group](./../../src/Psl/Filesystem/change_group.php#L20) - [change_owner](./../../src/Psl/Filesystem/change_owner.php#L20) @@ -49,8 +49,8 @@ - [is_symbolic_link](./../../src/Psl/Filesystem/is_symbolic_link.php#L19) - [is_writable](./../../src/Psl/Filesystem/is_writable.php#L20) - [read_directory](./../../src/Psl/Filesystem/read_directory.php#L19) -- [read_file](./../../src/Psl/Filesystem/read_file.php#L24) +- [read_file](./../../src/Psl/Filesystem/read_file.php#L23) - [read_symbolic_link](./../../src/Psl/Filesystem/read_symbolic_link.php#L21) -- [write_file](./../../src/Psl/Filesystem/write_file.php#L18) +- [write_file](./../../src/Psl/Filesystem/write_file.php#L21) diff --git a/docs/documenter.php b/docs/documenter.php index 5abfdd48e..cb72b14c1 100644 --- a/docs/documenter.php +++ b/docs/documenter.php @@ -12,7 +12,7 @@ use Psl\Type; use Psl\Vec; -require_once __DIR__ . "/../src/bootstrap.php"; +require_once __DIR__ . "/../vendor/autoload.php"; (static function (array $args) { $command = Str\lowercase($args[1] ?? 'regenerate'); diff --git a/src/Psl/File/ReadHandle.php b/src/Psl/File/ReadHandle.php index 2438d8474..d30406e00 100644 --- a/src/Psl/File/ReadHandle.php +++ b/src/Psl/File/ReadHandle.php @@ -22,8 +22,8 @@ final class ReadHandle extends Internal\AbstractHandleWrapper implements ReadHan */ public function __construct(string $path) { - Psl\invariant(Filesystem\is_file($path), '$filename is not a file.'); - Psl\invariant(Filesystem\is_readable($path), '$filename is not readable.'); + Psl\invariant(Filesystem\is_file($path), 'File "%s" is not a file.', $path); + Psl\invariant(Filesystem\is_readable($path), 'File "%s" is not readable.', $path); $this->readHandle = Internal\open($path, 'r', read: true, write: false); diff --git a/src/Psl/File/ReadWriteHandle.php b/src/Psl/File/ReadWriteHandle.php index 387b070c4..598fd795a 100644 --- a/src/Psl/File/ReadWriteHandle.php +++ b/src/Psl/File/ReadWriteHandle.php @@ -25,21 +25,21 @@ final class ReadWriteHandle extends Internal\AbstractHandleWrapper implements Re public function __construct(string $path, WriteMode $write_mode = WriteMode::OPEN_OR_CREATE) { $is_file = Filesystem\is_file($path); - Psl\invariant(!Filesystem\exists($path) || $is_file, '$path points to a non-file node.'); + Psl\invariant(!Filesystem\exists($path) || $is_file, 'File "%s" is not a file.', $path); $open_or_create = $write_mode === WriteMode::OPEN_OR_CREATE; $must_create = $write_mode === WriteMode::MUST_CREATE; if ($must_create && $is_file) { - Psl\invariant_violation('$path already exists.'); + Psl\invariant_violation('File "%s" already exists.', $path); } $creating = $open_or_create || $must_create; if (!$creating && !$is_file) { - Psl\invariant_violation('$path does not exist.'); + Psl\invariant_violation('File "%s" does not exist.', $path); } if ((!$creating || ($open_or_create && $is_file)) && !Filesystem\is_writable($path)) { - Psl\invariant_violation('$path is not writable.'); + Psl\invariant_violation('File "%s" is not writable.', $path); } if ($creating && !$is_file) { diff --git a/src/Psl/File/WriteHandle.php b/src/Psl/File/WriteHandle.php index d78e5151b..37ea510ce 100644 --- a/src/Psl/File/WriteHandle.php +++ b/src/Psl/File/WriteHandle.php @@ -24,21 +24,21 @@ final class WriteHandle extends Internal\AbstractHandleWrapper implements WriteH public function __construct(string $path, WriteMode $write_mode = WriteMode::OPEN_OR_CREATE) { $is_file = Filesystem\is_file($path); - Psl\invariant(!Filesystem\exists($path) || $is_file, '$path points to a non-file node.'); + Psl\invariant(!Filesystem\exists($path) || $is_file, 'File "%s" is not a file.', $path); $open_or_create = $write_mode === WriteMode::OPEN_OR_CREATE; $must_create = $write_mode === WriteMode::MUST_CREATE; if ($must_create && $is_file) { - Psl\invariant_violation('$path already exists.'); + Psl\invariant_violation('File "%s" already exists.', $path); } $creating = $open_or_create || $must_create; if (!$creating && !$is_file) { - Psl\invariant_violation('$path does not exist.'); + Psl\invariant_violation('File "%s" does not exist.', $path); } if ((!$creating || ($open_or_create && $is_file)) && !Filesystem\is_writable($path)) { - Psl\invariant_violation('$path is not writable.'); + Psl\invariant_violation('File "%s" is not writable.', $path); } /** diff --git a/src/Psl/Filesystem/Internal/write_file.php b/src/Psl/Filesystem/Internal/write_file.php deleted file mode 100644 index 3ca71b2fd..000000000 --- a/src/Psl/Filesystem/Internal/write_file.php +++ /dev/null @@ -1,67 +0,0 @@ - file_put_contents($file, $content, $flags) - ); - - // @codeCoverageIgnoreStart - if (false === $written || null !== $error) { - throw new Exception\RuntimeException(Str\format( - 'Failed to write to file "%s": %s.', - $file, - $error ?? 'internal error', - )); - } - - $length = Str\Byte\length($content); - if ($written !== $length) { - throw new Exception\RuntimeException(Str\format( - 'Failed to write the whole content to "%s" ( %g of %g bytes written ).', - $file, - $written, - $length, - )); - } - // @codeCoverageIgnoreEnd -} diff --git a/src/Psl/Filesystem/append_file.php b/src/Psl/Filesystem/append_file.php index 1ca11f193..87f9937ad 100644 --- a/src/Psl/Filesystem/append_file.php +++ b/src/Psl/Filesystem/append_file.php @@ -5,6 +5,9 @@ namespace Psl\Filesystem; use Psl; +use Psl\File; +use Psl\IO; +use Psl\Str; /** * Append $content to $file. @@ -17,5 +20,18 @@ */ function append_file(string $file, string $content): void { - Internal\write_file($file, $content, true); + try { + $handle = File\open_write_only($file, File\WriteMode::APPEND); + $lock = $handle->lock(File\LockType::EXCLUSIVE); + + $handle->writeAll($content); + + $lock->release(); + $handle->close(); + } catch (File\Exception\ExceptionInterface | IO\Exception\ExceptionInterface $previous) { + throw new Exception\RuntimeException(Str\format( + 'Failed to write to file "%s".', + $file, + ), 0, $previous); + } } diff --git a/src/Psl/Filesystem/read_file.php b/src/Psl/Filesystem/read_file.php index ce36e8152..e5a75a3ea 100644 --- a/src/Psl/Filesystem/read_file.php +++ b/src/Psl/Filesystem/read_file.php @@ -5,11 +5,10 @@ namespace Psl\Filesystem; use Psl; -use Psl\Internal; +use Psl\File; +use Psl\IO; use Psl\Str; -use function file_get_contents; - /** * Reads entire file into a string. * @@ -19,33 +18,25 @@ * * @throws Psl\Exception\InvariantViolationException If the file specified by * $file does not exist, or is not readable. - * @throws Exception\RuntimeException If an error + * @throws Exception\RuntimeException In case of an error. */ function read_file(string $file, int $offset = 0, ?int $length = null): string { - Psl\invariant(exists($file), 'File "%s" does not exist.', $file); - Psl\invariant(is_file($file), 'File "%s" is not a file.', $file); - Psl\invariant(is_readable($file), 'File "%s" is not readable.', $file); + try { + $handle = File\open_read_only($file); + $lock = $handle->lock(File\LockType::SHARED); - if (null === $length) { - [$content, $error] = Internal\box( - static fn() => file_get_contents($file, false, null, $offset) - ); - } else { - [$content, $error] = Internal\box( - static fn() => file_get_contents($file, false, null, $offset, $length) - ); - } + $handle->seek($offset); + $content = $handle->readAll($length); - // @codeCoverageIgnoreStart - if (false === $content || null !== $error) { + $lock->release(); + $handle->close(); + + return $content; + } catch (File\Exception\ExceptionInterface | IO\Exception\ExceptionInterface $previous) { throw new Exception\RuntimeException(Str\format( - 'Failed to read file "%s": %s.', + 'Failed to read file "%s".', $file, - $error ?? 'internal error', - )); + ), 0, $previous); } - // @codeCoverageIgnoreEnd - - return $content; } diff --git a/src/Psl/Filesystem/write_file.php b/src/Psl/Filesystem/write_file.php index 18c1a70fa..9dee92dbf 100644 --- a/src/Psl/Filesystem/write_file.php +++ b/src/Psl/Filesystem/write_file.php @@ -5,6 +5,9 @@ namespace Psl\Filesystem; use Psl; +use Psl\File; +use Psl\IO; +use Psl\Str; /** * Write $content to $file. @@ -17,5 +20,24 @@ */ function write_file(string $file, string $content): void { - Internal\write_file($file, $content, false); + try { + if (namespace\is_file($file)) { + $mode = File\WriteMode::TRUNCATE; + } else { + $mode = File\WriteMode::OPEN_OR_CREATE; + } + + $handle = File\open_write_only($file, $mode); + $lock = $handle->lock(File\LockType::EXCLUSIVE); + + $handle->writeAll($content); + + $lock->release(); + $handle->close(); + } catch (File\Exception\ExceptionInterface | IO\Exception\ExceptionInterface $previous) { + throw new Exception\RuntimeException(Str\format( + 'Failed to write to file "%s".', + $file, + ), 0, $previous); + } } diff --git a/src/Psl/IO/Internal/ResourceHandle.php b/src/Psl/IO/Internal/ResourceHandle.php index bf0d7c7b5..dbd73bbe9 100644 --- a/src/Psl/IO/Internal/ResourceHandle.php +++ b/src/Psl/IO/Internal/ResourceHandle.php @@ -160,6 +160,8 @@ public function seek(int $offset): void throw new Exception\AlreadyClosedException('Handle has already been closed.'); } + Psl\invariant($offset >= 0, '$offset must be a positive-int.'); + /** @psalm-suppress PossiblyInvalidArgument */ $result = @fseek($this->resource, $offset); if (0 !== $result) { diff --git a/src/Psl/IO/SeekHandleInterface.php b/src/Psl/IO/SeekHandleInterface.php index d314d1512..9f3f9b501 100644 --- a/src/Psl/IO/SeekHandleInterface.php +++ b/src/Psl/IO/SeekHandleInterface.php @@ -17,7 +17,7 @@ interface SeekHandleInterface extends HandleInterface * Offset is relative to the start of the handle - so, the beginning of the * handle is always offset 0. * - * @throws Psl\Exception\InvalidArgumentException If $offset is negative. + * @throws Psl\Exception\InvariantViolationException If $offset is negative. * @throws Exception\AlreadyClosedException If the handle has been already closed. * @throws Exception\RuntimeException If an error occurred during the operation. */ diff --git a/src/Psl/Internal/Loader.php b/src/Psl/Internal/Loader.php index a159f6fa4..92950744b 100644 --- a/src/Psl/Internal/Loader.php +++ b/src/Psl/Internal/Loader.php @@ -391,7 +391,6 @@ final class Loader 'Psl\Html\decode', 'Psl\Html\decode_special_characters', 'Psl\Html\strip_tags', - 'Psl\Filesystem\Internal\write_file', 'Psl\Filesystem\change_group', 'Psl\Filesystem\change_owner', 'Psl\Filesystem\change_permissions', diff --git a/tests/unit/File/ReadWriteHandleTest.php b/tests/unit/File/ReadWriteHandleTest.php index 0f95e20e7..9677551c9 100644 --- a/tests/unit/File/ReadWriteHandleTest.php +++ b/tests/unit/File/ReadWriteHandleTest.php @@ -62,7 +62,7 @@ public function testReading(): void public function testMustCreateExistingFile(): void { $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('$path already exists.'); + $this->expectExceptionMessage('already exists.'); new File\ReadWriteHandle(__FILE__, File\WriteMode::MUST_CREATE); } @@ -70,7 +70,7 @@ public function testMustCreateExistingFile(): void public function testAppendToNonExistingFile(): void { $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('$path does not exist.'); + $this->expectExceptionMessage('does not exist.'); new File\ReadWriteHandle(__FILE__ . '.fake', File\WriteMode::APPEND); } @@ -81,7 +81,7 @@ public function testAppendToANonWritableFile(): void Filesystem\change_permissions($temporary_file, 0555); $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('$path is not writable.'); + $this->expectExceptionMessage('is not writable.'); new File\ReadWriteHandle($temporary_file, File\WriteMode::APPEND); } diff --git a/tests/unit/File/WriteHandleTest.php b/tests/unit/File/WriteHandleTest.php index c694b196d..8bbd50f20 100644 --- a/tests/unit/File/WriteHandleTest.php +++ b/tests/unit/File/WriteHandleTest.php @@ -14,7 +14,7 @@ final class WriteHandleTest extends TestCase public function testMustCreateExistingFile(): void { $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('$path already exists.'); + $this->expectExceptionMessage('already exists.'); new File\WriteHandle(__FILE__, File\WriteMode::MUST_CREATE); } @@ -22,7 +22,7 @@ public function testMustCreateExistingFile(): void public function testAppendToNonExistingFile(): void { $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('$path does not exist.'); + $this->expectExceptionMessage('does not exist.'); $f = new File\WriteHandle(__FILE__ . '.fake', File\WriteMode::APPEND); $f->write('g'); @@ -34,7 +34,7 @@ public function testAppendToANonWritableFile(): void Filesystem\change_permissions($temporary_file, 0555); $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('$path is not writable.'); + $this->expectExceptionMessage('is not writable.'); new File\WriteHandle($temporary_file, File\WriteMode::APPEND); } diff --git a/tests/unit/Filesystem/FileTest.php b/tests/unit/Filesystem/FileTest.php index 209107638..38e9956cc 100644 --- a/tests/unit/Filesystem/FileTest.php +++ b/tests/unit/Filesystem/FileTest.php @@ -110,7 +110,7 @@ public function testWriteFileThrowsForNonWritableFiles(): void Filesystem\change_permissions($file, 0111); $this->expectException(InvariantViolationException::class); - $this->expectExceptionMessage('File "' . $file . '" is not writeable.'); + $this->expectExceptionMessage('File "' . $file . '" is not writable.'); Filesystem\write_file($file, 'hello'); }