From 147c462259be0c5c8711ec5c3167412da7d82533 Mon Sep 17 00:00:00 2001 From: blaipr Date: Mon, 7 Sep 2026 23:17:40 +0200 Subject: [PATCH] fix: archive a whole directory without a pattern MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ArchiveHandler::compressDirectory(string $directory, ?string $regex = null) offers 'archive the whole directory' as an option, and taking it handed the null straight to PharData::buildFromDirectory(), whose second parameter is declared string. That is a deprecation on PHP 8.5, a fatal on PHP 9, and already a hard error under the test suite's error handler — which is how it surfaced. The one production caller always passes BACKUP_INCLUDE_REGEX, so nothing had reached it. The one-argument form is what 'no pattern' means to Phar, so the argument is passed only when there is one. Corrects #903's description, which called this a TypeError on the strength of the test-suite failure: in ordinary use it is a deprecation and the archive is still built. --- src/Infrastructure/File/ArchiveHandler.php | 11 ++++++- .../File/ArchiveHandlerTest.php | 29 ++++++++++++++++--- 2 files changed, 35 insertions(+), 5 deletions(-) diff --git a/src/Infrastructure/File/ArchiveHandler.php b/src/Infrastructure/File/ArchiveHandler.php index b7388c0a9..85b1fe671 100644 --- a/src/Infrastructure/File/ArchiveHandler.php +++ b/src/Infrastructure/File/ArchiveHandler.php @@ -73,7 +73,16 @@ public function compressDirectory(string $directory, ?string $regex = null): str $umask = umask(self::OWNER_ONLY); try { - $this->archive->buildFromDirectory($directory, $regex); + // Only pass the pattern when there is one. `?string $regex = null` offers "archive + // the whole directory" as an option, and `PharData::buildFromDirectory()` declares its + // second parameter `string` — so taking that option emits a deprecation on PHP 8.5, + // becomes a fatal on PHP 9, and is already a hard error under the test suite's error + // handler. The one-argument form is what "no pattern" means to Phar. + if ($regex === null) { + $this->archive->buildFromDirectory($directory); + } else { + $this->archive->buildFromDirectory($directory, $regex); + } // Before compressing, not only after: the uncompressed archive holds the same thing // the compressed one does and exists for as long as compressing takes, which on a diff --git a/tests/Unit/Infrastructure/File/ArchiveHandlerTest.php b/tests/Unit/Infrastructure/File/ArchiveHandlerTest.php index 6b161749f..9e294e51e 100644 --- a/tests/Unit/Infrastructure/File/ArchiveHandlerTest.php +++ b/tests/Unit/Infrastructure/File/ArchiveHandlerTest.php @@ -50,10 +50,8 @@ class ArchiveHandlerTest extends TestCase { /** - * The one production caller always passes a regex (`BackupFile::BACKUP_INCLUDE_REGEX`), and so - * does this — `compressDirectory()`'s `?string $regex = null` default reaches - * `PharData::buildFromDirectory()`, which requires a string, so the null is a TypeError. It is - * unreachable today and is not this change's to fix. + * The one production caller passes a regex (`BackupFile::BACKUP_INCLUDE_REGEX`); most of these + * do too, and one below takes the other option deliberately. */ private const EVERYTHING = '/.*/'; @@ -132,6 +130,29 @@ public function theCompressedArchiveIsOwnerOnly(): void self::assertSame(0600, fileperms($this->archivePath()) & 0777); } + /** + * Archiving a whole directory, which is what `?string $regex = null` offers. + * + * Taking that option handed the null straight to `PharData::buildFromDirectory()`, whose second + * parameter is declared `string` — a deprecation on PHP 8.5, a fatal on PHP 9, and already a + * hard error under this suite's error handler, which is how it was found. The one production + * caller always passes a regex, so nothing had reached it. + * + * Asserted on the contents rather than on the absence of an error: the fix is the one-argument + * form, and the thing worth pinning is that it still archives everything. + */ + #[Test] + public function aDirectoryCanBeArchivedWithNoPatternAtAll(): void + { + file_put_contents($this->source . DIRECTORY_SEPARATOR . 'second.txt', 'more'); + + $this->handler()->compressDirectory($this->source); + + $entries = iterator_to_array(new PharData($this->archivePath())); + + self::assertCount(2, $entries); + } + /** * And nothing is left beside it — the uncompressed tar, which holds exactly the same thing, * is removed once the gz exists.