From 0dffb8de3bf336b31848b45be9d58ed3e797354a Mon Sep 17 00:00:00 2001 From: Toon Verwerft Date: Mon, 29 Jan 2024 19:09:18 +0100 Subject: [PATCH] Attempt to create file and directory when starting a writer for a file --- docs/writer.md | 2 ++ src/Xml/Writer/Opener/xml_file_opener.php | 10 ++++-- src/Xml/Writer/Writer.php | 1 + tests/Xml/Writer/Opener/XmlFileOpenerTest.php | 34 ++++++++++++++++--- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/docs/writer.md b/docs/writer.md index 93bb851..6713beb 100644 --- a/docs/writer.md +++ b/docs/writer.md @@ -534,6 +534,8 @@ $doc = Writer::inMemory(...$configurators) #### xml_file_opener Loads an XML document from a file. +When the file or folder does not exists, the code will attempt to create it. +If it is not possible to create a target to write to, a `RuntimException` will be thrown. ```php use VeeWee\Xml\Writer\Writer; diff --git a/src/Xml/Writer/Opener/xml_file_opener.php b/src/Xml/Writer/Opener/xml_file_opener.php index 275a189..7c34167 100644 --- a/src/Xml/Writer/Opener/xml_file_opener.php +++ b/src/Xml/Writer/Opener/xml_file_opener.php @@ -5,16 +5,22 @@ namespace VeeWee\Xml\Writer\Opener; use Closure; -use Webmozart\Assert\Assert; +use Psl\File\WriteMode; use XMLWriter; +use function Psl\File\write; /** + * @param non-empty-string $file + * * @return Closure(XMLWriter): bool XMLWriter */ function xml_file_opener(string $file): Closure { return static function (XMLWriter $writer) use ($file) : bool { - Assert::writable($file); + // Try to create the file first. + // If the file exists, it will truncated. (Default behaviour of XMLWriter as well) + // If it cannot be created, it will throw exceptions. + write($file, '', WriteMode::TRUNCATE); return $writer->openUri($file); }; diff --git a/src/Xml/Writer/Writer.php b/src/Xml/Writer/Writer.php index 5c34319..edd2e9d 100644 --- a/src/Xml/Writer/Writer.php +++ b/src/Xml/Writer/Writer.php @@ -40,6 +40,7 @@ public static function fromUnsafeWriter(XMLWriter $writer, callable ... $configu } /** + * @param non-empty-string $file * @param list<(callable(XMLWriter): XMLWriter)> $configurators */ public static function forFile(string $file, callable ... $configurators): self diff --git a/tests/Xml/Writer/Opener/XmlFileOpenerTest.php b/tests/Xml/Writer/Opener/XmlFileOpenerTest.php index 6d3c97e..eadb4c5 100644 --- a/tests/Xml/Writer/Opener/XmlFileOpenerTest.php +++ b/tests/Xml/Writer/Opener/XmlFileOpenerTest.php @@ -5,6 +5,9 @@ namespace VeeWee\Tests\Xml\Writer\Opener; use PHPUnit\Framework\TestCase; +use Psl\File; +use Psl\Filesystem; +use Psl\OS; use VeeWee\Tests\Xml\Helper\TmpFileTrait; use VeeWee\Xml\Exception\RuntimeException; use VeeWee\Xml\Writer\Writer; @@ -14,10 +17,11 @@ final class XmlFileOpenerTest extends TestCase { use TmpFileTrait; - public function test_it_can_open_a_file(): void { $this->createTmpFile(static function (string $path): void { + File\write($path, 'will be truncated', File\WriteMode::TRUNCATE); + $writer = Writer::forFile($path); $writer->write(element('root')); @@ -25,12 +29,34 @@ public function test_it_can_open_a_file(): void }); } - + public function test_it_can_write_to_a_new_file(): void + { + $temporaryFile = Filesystem\create_temporary_file(); + Filesystem\delete_file($temporaryFile); + + $writer = Writer::forFile($temporaryFile); + $writer->write(element('root')); + static::assertXmlStringEqualsXmlFile($temporaryFile, ''); + + unlink($temporaryFile); + } + public function test_it_errors_if_file_is_not_writable(): void { + if (OS\is_windows()) { + static::markTestSkipped('Permissions are not reliable on windows.'); + } + + $temporary_file = Filesystem\create_temporary_file(); + Filesystem\delete_file($temporary_file); + Filesystem\create_directory($temporary_file); + Filesystem\change_permissions($temporary_file, 0555); + + $file = $temporary_file . Filesystem\SEPARATOR . 'foo'; + $this->expectException(RuntimeException::class); - $this->expectExceptionMessage('The path "doesnotexist" is not writable.'); + $this->expectExceptionMessage('File "' . $file . '" is not writable.'); - Writer::forFile('doesnotexist'); + Writer::forFile($file); } }