diff --git a/src/InMemory/InMemoryFilesystemAdapter.php b/src/InMemory/InMemoryFilesystemAdapter.php index b744c0f48..0d05474a3 100644 --- a/src/InMemory/InMemoryFilesystemAdapter.php +++ b/src/InMemory/InMemoryFilesystemAdapter.php @@ -19,6 +19,8 @@ use function array_keys; use function rtrim; +use function str_starts_with; +use function strlen; use function strpos; class InMemoryFilesystemAdapter implements FilesystemAdapter @@ -232,12 +234,27 @@ public function move(string $source, string $destination, Config $config): void $source = $this->preparePath($source); $destination = $this->preparePath($destination); - if ( ! $this->fileExists($source) || $this->fileExists($destination)) { - throw UnableToMoveFile::fromLocationTo($source, $destination); + if ($this->fileExists($destination) || $this->directoryExists($destination)) { + throw UnableToMoveFile::fromLocationTo($source, $destination, new \RuntimeException("Destination already exist")); } - $this->files[$destination] = $this->files[$source]; - unset($this->files[$source]); + try { + $this->copy($source, $destination, $config); + } catch (UnableToCopyFile $e) { + throw UnableToMoveFile::fromLocationTo($source, $destination, $e); + } + + if ($this->fileExists($source)) { + $this->delete($source); + + return; + } elseif ($this->directoryExists($source)) { + $this->deleteDirectory($source); + + return; + } + + throw UnableToMoveFile::fromLocationTo($source, $destination); } public function copy(string $source, string $destination, Config $config): void @@ -245,13 +262,29 @@ public function copy(string $source, string $destination, Config $config): void $source = $this->preparePath($source); $destination = $this->preparePath($destination); - if ( ! $this->fileExists($source)) { - throw UnableToCopyFile::fromLocationTo($source, $destination); - } - $lastModified = $config->get('timestamp', time()); + if ($this->fileExists($source)) { + $this->files[$destination] = $this->files[$source]->withLastModified($lastModified); + + return; + } elseif ($this->directoryExists($source)) { + $sourcePrefix = rtrim($source, '/') . '/'; + $destinationPrefix = rtrim($destination, '/') . '/'; + + $sourcePrefixLength = strlen($source) + 1; + + foreach (array_keys($this->files) as $path) { + if (str_starts_with($path, $sourcePrefix)) { + $newPath = $destinationPrefix . substr($path, $sourcePrefixLength); + + $this->files[$newPath] = $this->files[$path]->withLastModified($lastModified); + } + } + + return; + } - $this->files[$destination] = $this->files[$source]->withLastModified($lastModified); + throw UnableToCopyFile::fromLocationTo($source, $destination, new \RuntimeException("Source does not exist")); } private function preparePath(string $path): string diff --git a/src/InMemory/InMemoryFilesystemAdapterTest.php b/src/InMemory/InMemoryFilesystemAdapterTest.php index 618979921..594c8b847 100644 --- a/src/InMemory/InMemoryFilesystemAdapterTest.php +++ b/src/InMemory/InMemoryFilesystemAdapterTest.php @@ -212,6 +212,52 @@ public function trying_to_move_a_non_existing_file(): void $this->adapter()->move('path.txt', 'new-path.txt', new Config()); } + /** + * @test + */ + public function moving_a_directory_successfully(): void + { + $adapter = $this->adapter(); + $adapter->write('a/first_file.txt', 'contents1', new Config()); + $adapter->write('a/second_file.txt', 'contents2', new Config()); + $adapter->move('a', 'b', new Config()); + + $this->assertTrue($adapter->fileExists('b/first_file.txt')); + $this->assertTrue($adapter->fileExists('b/second_file.txt')); + $this->assertFalse($adapter->fileExists('a/first_file.txt')); + $this->assertFalse($adapter->fileExists('a/second_file.txt')); + } + + /** + * @test + */ + public function trying_to_move_a_directory_with_file_collision(): void + { + $this->expectException(UnableToMoveFile::class); + $adapter = $this->adapter(); + $adapter->write('a/path.txt', 'contents1', new Config()); + $adapter->write('b', 'contents2', new Config()); + $adapter->move('a', 'b', new Config()); + + $this->assertEquals('contents2', $adapter->read('b')); + $this->assertTrue($adapter->fileExists('a/path.txt')); + } + + /** + * @test + */ + public function trying_to_move_a_directory_with_directory_collision(): void + { + $this->expectException(UnableToMoveFile::class); + $adapter = $this->adapter(); + $adapter->write('a/path.txt', 'contents1', new Config()); + $adapter->write('b/path-new.txt', 'contents2', new Config()); + $adapter->move('a', 'b', new Config()); + + $this->assertEquals('contents2', $adapter->read('b/path-new.txt')); + $this->assertTrue($adapter->fileExists('a/path.txt')); + } + /** * @test */