From 336771109f6b12cd962bf09cecb8d22d711806db Mon Sep 17 00:00:00 2001 From: Mufthi Ryanda <77824812+mufthiryanda@users.noreply.github.com> Date: Thu, 1 Oct 2026 14:01:54 +0700 Subject: [PATCH] feat : add new resolver --- src/Libraries/Storage/Storage.php | 90 +++++++++++++++++++++---- tests/Libraries/Storage/StorageTest.php | 55 +++++++++++++++ 2 files changed, 133 insertions(+), 12 deletions(-) diff --git a/src/Libraries/Storage/Storage.php b/src/Libraries/Storage/Storage.php index 420a5c5..5f9cfa7 100644 --- a/src/Libraries/Storage/Storage.php +++ b/src/Libraries/Storage/Storage.php @@ -111,7 +111,7 @@ public function readStream(string $filepath) $resolver = new DriverResolver(); if ($this->pendingAutoDetect) { - foreach ([self::MINIO, self::MINIO_BRIMEN, self::NFS] as $driverName) { + foreach ($this->getAutoDetectDriverNames($filepath) as $driverName) { $driver = $this->makeDriver($driverName); if ($driver->exists($filepath)) { return $driver->readStream($filepath); @@ -143,7 +143,7 @@ public function exists(string $filepath): bool { try { if ($this->pendingAutoDetect) { - foreach ([self::MINIO, self::MINIO_BRIMEN, self::NFS] as $driverName) { + foreach ($this->getAutoDetectDriverNames($filepath) as $driverName) { if ($this->makeDriver($driverName)->exists($filepath)) { return true; } @@ -249,7 +249,7 @@ public function info(string $filepath): StorageResult return $this->makeDriver($resolvedDriverName)->info($filepath); } - foreach ([self::MINIO, self::MINIO_BRIMEN, self::NFS] as $driverName) { + foreach ($this->getAutoDetectDriverNames($filepath) as $driverName) { $driver = $this->makeDriver($driverName); if ($driver->exists($filepath)) { return $driver->info($filepath); @@ -282,9 +282,10 @@ public function allFiles(string $dirpath): array } $resolver = new DriverResolver(); + $pathDriverName = $this->getPathDriverName($dirpath); $resolvedDriverName = $this->pendingDriver !== null ? $resolver->resolveExplicit($this->pendingDriver) - : $resolver->resolveDefault(); + : ($pathDriverName ?? $resolver->resolveDefault()); return $this->makeDriver($resolvedDriverName)->allFiles($dirpath); } finally { @@ -309,7 +310,7 @@ public function securelink(string $filepath, ?int $ttl = null): string $resolver = new DriverResolver(); if ($this->pendingAutoDetect) { - foreach ([self::MINIO, self::MINIO_BRIMEN, self::NFS] as $driverName) { + foreach ($this->getAutoDetectDriverNames($filepath) as $driverName) { $driver = $this->makeDriver($driverName); if ($driver->exists($filepath)) { return $driver->securelink($filepath, $ttl); @@ -485,9 +486,12 @@ public function buildZip(array $sourceFiles, string $destPath): StorageResult } $resolver = new DriverResolver(); - $resolvedDestinationName = $this->pendingToDriver !== null - ? $resolver->resolveExplicit($this->pendingToDriver) - : $resolver->resolveDefault(); + if ($this->pendingToDriver !== null) { + $resolvedDestinationName = $resolver->resolveExplicit($this->pendingToDriver); + } else { + $resolvedDestinationName = $this->getPathDriverName($destPath) + ?? $resolver->resolveDefault(); + } $destinationDriver = $this->makeDriver($resolvedDestinationName); if ($destinationDriver->exists($destPath)) { @@ -547,7 +551,7 @@ public function buildZip(array $sourceFiles, string $destPath): StorageResult ); } else { $sourceDriver = null; - foreach ([self::MINIO, self::MINIO_BRIMEN, self::NFS] as $driverName) { + foreach ($this->getAutoDetectDriverNames($pathFile) as $driverName) { $candidateDriver = $this->makeDriver($driverName); if ($candidateDriver->exists($pathFile)) { $sourceDriver = $candidateDriver; @@ -636,9 +640,12 @@ public function buildZipFolder(string $sourceFolder, string $destPath): StorageR { try { $resolver = new DriverResolver(); - $resolvedSourceDriverName = $this->pendingFromDriver !== null - ? $resolver->resolveExplicit($this->pendingFromDriver) - : $resolver->resolveDefault(); + if ($this->pendingFromDriver !== null) { + $resolvedSourceDriverName = $resolver->resolveExplicit($this->pendingFromDriver); + } else { + $resolvedSourceDriverName = $this->getPathDriverName($sourceFolder) + ?? $resolver->resolveDefault(); + } $sourceDriver = $this->makeDriver($resolvedSourceDriverName); $files = $sourceDriver->allFiles($sourceFolder); @@ -673,6 +680,65 @@ public function buildZipFolder(string $sourceFolder, string $destPath): StorageR } } + /** + * Detect a storage driver marker in a path. + * + * The earliest case-insensitive marker wins. If MINIO_BRIMEN and MINIO + * start at the same position, the more specific MINIO_BRIMEN marker wins. + * + * @param string $path full path, file path, or folder path + * + * @return string|null detected driver name, or null when no marker exists + */ + private function getPathDriverName(string $path): ?string + { + $driverPositions = [ + self::NFS => stripos($path, self::NFS), + self::MINIO_BRIMEN => stripos($path, self::MINIO_BRIMEN), + self::MINIO => stripos($path, self::MINIO), + ]; + $detectedDriver = null; + $detectedPosition = PHP_INT_MAX; + $detectedLength = 0; + + foreach ($driverPositions as $driverName => $position) { + if ($position === false) { + continue; + } + + $driverLength = strlen($driverName); + if ($position < $detectedPosition + || ($position === $detectedPosition && $driverLength > $detectedLength) + ) { + $detectedDriver = $driverName; + $detectedPosition = $position; + $detectedLength = $driverLength; + } + } + + return $detectedDriver; + } + + /** + * Resolve the autodetect query order from a storage path. + * + * A path marker is checked before querying storage so a known storage path + * only performs one remote or filesystem existence check. + * + * @param string $path full path, file path, or folder path + * + * @return array driver names ordered for autodetection + */ + private function getAutoDetectDriverNames(string $path): array + { + $detectedDriver = $this->getPathDriverName($path); + if ($detectedDriver !== null) { + return [$detectedDriver]; + } + + return [self::MINIO, self::NFS, self::MINIO_BRIMEN]; + } + /** * Instantiate the concrete driver for a given driver name. * diff --git a/tests/Libraries/Storage/StorageTest.php b/tests/Libraries/Storage/StorageTest.php index 2833c2a..3fe9ba4 100644 --- a/tests/Libraries/Storage/StorageTest.php +++ b/tests/Libraries/Storage/StorageTest.php @@ -81,6 +81,61 @@ public function testExistsAutoDetect(): void $this->assertTrue($storage->autoDetect()->exists('test.txt')); } + public function testExistsAutoDetectUsesNfsPathHint(): void + { + $path = sys_get_temp_dir() . '/nFs_' . uniqid('', true) . '/test.txt'; + mkdir(dirname($path), 0755, true); + file_put_contents($path, 'content'); + LaravelStorage::shouldReceive('disk')->never(); + + try { + $storage = new Storage(); + $this->assertTrue($storage->autoDetect()->exists($path)); + } finally { + unlink($path); + rmdir(dirname($path)); + } + } + + public function testAllFilesUsesNfsFolderPathHint(): void + { + $directory = sys_get_temp_dir() . '/nFs_' . uniqid('', true); + $path = $directory . '/test.txt'; + mkdir($directory, 0755, true); + file_put_contents($path, 'content'); + LaravelStorage::shouldReceive('disk')->never(); + + try { + $storage = new Storage(); + $this->assertCount(1, $storage->allFiles($directory)); + } finally { + unlink($path); + rmdir($directory); + } + } + + public function testExistsAutoDetectUsesMinioPathHintCaseInsensitively(): void + { + $diskMock = Mockery::mock(); + LaravelStorage::shouldReceive('disk')->with('minio')->once()->andReturn($diskMock); + LaravelStorage::shouldReceive('disk')->with('minio_brimen')->never(); + $diskMock->shouldReceive('exists')->with('folder/MiNiO/file.txt')->once()->andReturn(true); + + $storage = new Storage(); + $this->assertTrue($storage->autoDetect()->exists('folder/MiNiO/file.txt')); + } + + public function testExistsAutoDetectUsesMinioBrimenPathHint(): void + { + $diskMock = Mockery::mock(); + LaravelStorage::shouldReceive('disk')->with('minio')->never(); + LaravelStorage::shouldReceive('disk')->with('minio_brimen')->once()->andReturn($diskMock); + $diskMock->shouldReceive('exists')->with('folder/MINIO_BRIMEN/file.txt')->once()->andReturn(true); + + $storage = new Storage(); + $this->assertTrue($storage->autoDetect()->exists('folder/MINIO_BRIMEN/file.txt')); + } + public function testDeleteAllowed(): void { putenv('ALLOW_DELETE_STORAGE_SPOTLIB=true');