diff --git a/Readme.md b/Readme.md index ffe4368..66faded 100644 --- a/Readme.md +++ b/Readme.md @@ -10,19 +10,34 @@ Storage is a filesystem abstraction which allows you to easily swap out a local ## TLDR; ```php -//faster way -(new StorageBuilder())->build()->put('/tmp/test.txt',"this is a test"); - -//more customized -$sb = new StorageBuilder(); -$s = $sb->addAdapter('S3AWS') - ->addAdapter(new DropBox()) - ->addAdapter('FileSystem') +//one adapter (save data to S3) +$s3Adapter = new \Cmp\Storage\Adapter\S3AWSAdapter(); +$s->put('/tmp/test.txt',"this is a test"); + + +//two adapters with a fallback strategy and decorated with a logger +$s3Adapter = new \Cmp\Storage\Adapter\S3AWSAdapter(); +$fallBackAdapter = (new StorageBuilder())->addAdapter($s3Adapter) + ->addAdapter($s3Adapter) //the order matters with FallBackChainStrategy + ->addAdapter($fileSystemAdapter) ->setLogger(new Logger()) ->build(new \Cmp\Storage\Strategy\FallBackChainStrategy()); -$s->put('/tmp/test.txt',"this is a test"); + +//it saves data to S3 and if fails save the data to FS +$fallBackAdapter->put('/tmp/test.txt',"this is a test"); +//one step more fs adapter bind to one folder and strategy to another folder +$vfs = new \Cmp\Storage\MountableVirtualStorage($fileSystemStorage); //bind to any path that non match with mountpoint folders +$localMountPoint = new \Cmp\Storage\MountPoint('/tmp', $fileSystemAdapter); +$publicMountPoint = new \Cmp\Storage\MountPoint('/var/www/app/public', $s3Adapter); +$vfs->registerMountPoint($localMountPoint); +$vfs->registerMountPoint($publicMountPoint); + +/* +//move file from /tmp (FS) to /var/www/app/public (S3) and if fails try to move from /tmp (FS) to /var/www/app/public (FS) +*/ +$vfs->move('/tmp/testfile.jpg','/var/www/app/public/avatar.jpg' ); ``` ## Installation @@ -55,6 +70,7 @@ The adapter interface contains these methods: * `get` * `getStream` * `rename` +* `copy` * `delete` * `put` * `putStream` @@ -84,8 +100,12 @@ After that you can register new mount points. Example: ```php - $localMountPoint = new \Cmp\Storage\MountPoint('/tmp', $this->fileSystemStorage); - $publicMountPoint = new \Cmp\Storage\MountPoint('/var/www/app/public', $this->s3Adapter); + $s3Adapter = new \Cmp\Storage\Adapter\S3AWSAdapter(); + $fileSystemAdapter = new \Cmp\Storage\Adapter\FileSystemAdapter(); + + $localMountPoint = new \Cmp\Storage\MountPoint('/tmp', $fileSystemAdapter); + $publicMountPoint = new \Cmp\Storage\MountPoint('/var/www/app/public', $s3Adapter); + $vfs = new \Cmp\Storage\MountableVirtualStorage($this->fileSystemStorage); //bind to / $vfs->registerMountPoint($localMountPoint); $vfs->registerMountPoint($publicMountPoint); @@ -145,6 +165,9 @@ Retrieves a read-stream for a file. ### Rename Rename a file. +### Copy +Copy a file. + ### Delete Delete a file or directory (even if is not empty). diff --git a/src/Cmp/Storage/Adapter/FileSystemAdapter.php b/src/Cmp/Storage/Adapter/FileSystemAdapter.php index 8a31a63..62fbfd4 100644 --- a/src/Cmp/Storage/Adapter/FileSystemAdapter.php +++ b/src/Cmp/Storage/Adapter/FileSystemAdapter.php @@ -97,6 +97,22 @@ public function rename($path, $newpath, $overwrite = false) return rename($path, $newpath); } + /** + * Copy a file. + * + * @param string $path Path to the existing file + * @param string $newpath The destination path of the copy + * + * @return bool + */ + public function copy($path, $newpath) + { + $path = $this->normalizePath($path); + $this->assertNotFileExists($path); + + return copy($path, $newpath); + } + /** * Delete a file or directory. * diff --git a/src/Cmp/Storage/Adapter/S3AWSAdapter.php b/src/Cmp/Storage/Adapter/S3AWSAdapter.php index 3d8a032..c959103 100644 --- a/src/Cmp/Storage/Adapter/S3AWSAdapter.php +++ b/src/Cmp/Storage/Adapter/S3AWSAdapter.php @@ -243,12 +243,14 @@ public function putStream($path, $resource) } /** - * @param string $path - * @param string $newpath + * Copy a file. + * + * @param string $path Path to the existing file + * @param string $newpath The destination path of the copy * * @return bool */ - private function copy($path, $newpath) + public function copy($path, $newpath) { $path = $this->trimPrefix($path); $newpath = $this->trimPrefix($newpath); diff --git a/src/Cmp/Storage/MountableVirtualStorage.php b/src/Cmp/Storage/MountableVirtualStorage.php index 7d146d3..b827e78 100644 --- a/src/Cmp/Storage/MountableVirtualStorage.php +++ b/src/Cmp/Storage/MountableVirtualStorage.php @@ -42,16 +42,17 @@ public function registerMountPoint(MountPoint $mountPoint) } /** - * @param $path + * @param VirtualPath $vp * * @return MountPoint + * */ - public function getMountPointForPath($path) + public function getMountPointForPath(VirtualPath $vp) { $it = $this->mountPoints->getIterator(); - $virtualPath = new VirtualPath($path); + foreach ($it as $mountPoint) { - if ($mountPoint->getVirtualPath()->isChild($virtualPath)) { + if ($mountPoint->getVirtualPath()->isChild($vp)) { return $mountPoint; } } @@ -61,54 +62,75 @@ public function getMountPointForPath($path) public function exists($path) { - return $this->getStorageForPath($path)->exists($path); + $vp = new VirtualPath($path); + return $this->getStorageForPath($vp)->exists($vp->getPath()); } public function get($path) { - return $this->getStorageForPath($path)->get($path); + $vp = new VirtualPath($path); + return $this->getStorageForPath($vp)->get($vp->getPath()); } public function getStream($path) { - return $this->getStorageForPath($path)->getStream($path); + $vp = new VirtualPath($path); + return $this->getStorageForPath($vp)->getStream($vp->getPath()); } public function rename($path, $newpath, $overwrite = false) { - $storageSrc = $this->getStorageForPath($path); - $storageDst = $this->getStorageForPath($newpath); + $svp = new VirtualPath($path); + $dvp = new VirtualPath($newpath); + $storageSrc = $this->getStorageForPath($svp); + $storageDst = $this->getStorageForPath($dvp); + + if (!$overwrite && $storageDst->exists($dvp->getPath())) { + throw new FileExistsException($dvp->getPath()); - if (!$storageSrc->exists($path)) { - return false; } - if (!$overwrite && $storageDst->exists($newpath)) { - throw new FileExistsException($newpath); + return $this->copy($svp->getPath(), $dvp->getPath()) && $storageSrc->delete($svp->getPath()); + } + + + public function copy($path, $newpath) + { + $svp = new VirtualPath($path); + $dvp = new VirtualPath($newpath); + $storageSrc = $this->getStorageForPath($svp); + $storageDst = $this->getStorageForPath($dvp); + + if (!$storageSrc->exists($svp->getPath())) { + return false; } - $stream = $storageSrc->getStream($path); + $stream = $storageSrc->getStream($svp->getPath()); if (!$stream) { return false; } - $storageDst->putStream($newpath, $stream); + $storageDst->putStream($dvp->getPath(), $stream); - return $storageDst->exists($newpath) && $storageSrc->delete($path); + return $storageDst->exists($dvp->getPath()); } + public function delete($path) { - return $this->getStorageForPath($path)->delete($path); + $vp = new VirtualPath($path); + return $this->getStorageForPath($vp)->delete($vp->getPath()); } public function put($path, $contents) { - return $this->getStorageForPath($path)->put($path, $contents); + $vp = new VirtualPath($path); + return $this->getStorageForPath($vp)->put($vp->getPath(), $contents); } public function putStream($path, $resource) { - return $this->getStorageForPath($path)->putStream($path, $resource); + $vp = new VirtualPath($path); + return $this->getStorageForPath($vp)->putStream($vp->getPath(), $resource); } /** @@ -123,14 +145,15 @@ private function getDefaultMountPoint(VirtualStorageInterface $defaultVirtualSto return $defaultMountPoint; } + /** - * @param $path + * @param VirtualPath $vp * * @return VirtualStorageInterface */ - private function getStorageForPath($path) + private function getStorageForPath(VirtualPath $vp) { - $mountPoint = $this->getMountPointForPath($path); + $mountPoint = $this->getMountPointForPath($vp); return $mountPoint->getStorage(); } diff --git a/src/Cmp/Storage/Strategy/CallAllStrategy.php b/src/Cmp/Storage/Strategy/CallAllStrategy.php index 43d138c..e6673db 100644 --- a/src/Cmp/Storage/Strategy/CallAllStrategy.php +++ b/src/Cmp/Storage/Strategy/CallAllStrategy.php @@ -87,6 +87,23 @@ public function rename($path, $newpath, $overwrite = false) return $this->runAllAndLog($fn); } + /** + * Copy a file. + * + * @param string $path Path to the existing file + * @param string $newpath The new path of the file + * + * @return bool + */ + public function copy($path, $newpath) + { + $fn = function ($adapter) use ($path, $newpath) { + return $adapter->copy($path, $newpath); + }; + + return $this->runAllAndLog($fn); + } + /** * Delete a file. * diff --git a/src/Cmp/Storage/Strategy/FallBackChainStrategy.php b/src/Cmp/Storage/Strategy/FallBackChainStrategy.php index c26fae9..02a7bca 100644 --- a/src/Cmp/Storage/Strategy/FallBackChainStrategy.php +++ b/src/Cmp/Storage/Strategy/FallBackChainStrategy.php @@ -87,6 +87,25 @@ public function rename($path, $newpath, $overwrite = false) return $this->runChainAndLog($fn); } + /** + * Copy a file. + * + * @param string $path Path to the existing file + * @param string $newpath The new path of the file + * + * @return bool + * + * @throws FileExistsException Thrown if $newpath exists + */ + public function copy($path, $newpath) + { + $fn = function (AdapterInterface $adapter) use ($path, $newpath) { + return $adapter->copy($path, $newpath); + }; + + return $this->runChainAndLog($fn); + } + /** * Delete a file. * diff --git a/src/Cmp/Storage/VirtualStorageInterface.php b/src/Cmp/Storage/VirtualStorageInterface.php index 83f5936..292c5f4 100644 --- a/src/Cmp/Storage/VirtualStorageInterface.php +++ b/src/Cmp/Storage/VirtualStorageInterface.php @@ -49,6 +49,17 @@ public function getStream($path); */ public function rename($path, $newpath, $overwrite = false); + + /** + * Rename a file. + * + * @param string $path Path to the existing file + * @param string $newpath The destination path of the copy + * + * @return bool + */ + public function copy($path, $newpath); + /** * Delete a file or directory. * diff --git a/test/integration/Cmp/Storage/Adapter/FileSystemAdapterTest.php b/test/integration/Cmp/Storage/Adapter/FileSystemAdapterTest.php index 7360ffb..e06c153 100644 --- a/test/integration/Cmp/Storage/Adapter/FileSystemAdapterTest.php +++ b/test/integration/Cmp/Storage/Adapter/FileSystemAdapterTest.php @@ -59,6 +59,20 @@ public function testFileRename() $this->assertFileExists($filenameNew); } + public function testFileCopy() + { + $filenameOld = $this->getTempFileName(); + $filenameNew = $this->getTempFileName(); + $this->assertFileNotExists($filenameOld); + $this->assertFileNotExists($filenameNew); + $this->assertTrue($this->fileSystemStorage->put($filenameOld, 'testFileRename')); + $this->assertFileExists($filenameOld); + $this->assertTrue($this->fileSystemStorage->copy($filenameOld, $filenameNew)); + $this->assertFileExists($filenameOld); + $this->assertFileExists($filenameNew); + } + + public function testFileRenameWithOverWrite() { $filenameOld = $this->getTempFileName(); diff --git a/test/integration/Cmp/Storage/Adapter/S3AwsAdapterTest.php b/test/integration/Cmp/Storage/Adapter/S3AwsAdapterTest.php index 49d36f7..dbf2afe 100644 --- a/test/integration/Cmp/Storage/Adapter/S3AwsAdapterTest.php +++ b/test/integration/Cmp/Storage/Adapter/S3AwsAdapterTest.php @@ -54,6 +54,19 @@ public function testFileRename() $this->assertTrue($this->s3Adapter->exists($filenameNew)); } + public function testFileCopy() + { + $filenameOld = $this->getTempFileName(); + $filenameNew = $this->getTempFileName(); + $this->assertFalse($this->s3Adapter->exists($filenameOld)); + $this->assertFalse($this->s3Adapter->exists($filenameNew)); + $this->assertTrue($this->s3Adapter->put($filenameOld, 'testFileRename')); + $this->assertTrue($this->s3Adapter->exists($filenameOld)); + $this->assertTrue($this->s3Adapter->copy($filenameOld, $filenameNew)); + $this->assertTrue($this->s3Adapter->exists($filenameOld)); + $this->assertTrue($this->s3Adapter->exists($filenameNew)); + } + public function testFileRenameWithOverWrite() { $filenameOld = $this->getTempFileName(); diff --git a/test/integration/Cmp/Storage/MountPointsTest.php b/test/integration/Cmp/Storage/MountPointsTest.php index 2d74214..1299e52 100644 --- a/test/integration/Cmp/Storage/MountPointsTest.php +++ b/test/integration/Cmp/Storage/MountPointsTest.php @@ -140,6 +140,23 @@ public function moveFilesBetweenEndpoints() $this->assertTrue($this->vfs->exists($filenameNew)); } + public function copyFilesBetweenEndpoints() + { + $path = $this->getAvailablePath(); + $filenameOld = $this->getTempFileNameInPath($path['tmp']); + $filenameNew = $this->getTempFileNameInPath($path['public']); + + $this->assertFalse($this->vfs->exists($filenameOld)); + $this->assertFalse($this->vfs->exists($filenameNew)); + + $this->assertTrue($this->vfs->put($filenameOld, 'testFileRename')); + $this->assertFalse($this->vfs->exists($filenameNew)); + + $this->assertTrue($this->vfs->copy($filenameOld, $filenameNew)); + $this->assertTrue($this->vfs->exists($filenameOld)); + $this->assertTrue($this->vfs->exists($filenameNew)); + } + public function testFilePutWithStrategies() { diff --git a/test/spec/Cmp/Storage/MountableVirtualStorageSpec.php b/test/spec/Cmp/Storage/MountableVirtualStorageSpec.php index 8be247c..de8faa6 100644 --- a/test/spec/Cmp/Storage/MountableVirtualStorageSpec.php +++ b/test/spec/Cmp/Storage/MountableVirtualStorageSpec.php @@ -66,7 +66,7 @@ public function it_returns_the_mountpoint_assigned_to_one_path( $mountPoint->getVirtualPath()->willReturn($v1); $this->registerMountPoint($mountPoint, $virtualStorage); - $this->getMountPointForPath('/tmp/a/b/c/d/f')->shouldBe($mountPoint); + $this->getMountPointForPath(new VirtualPath('/tmp/a/b/c/d/f'))->shouldBe($mountPoint); } public function it_always_returns_the_nearest_mount_point( @@ -99,15 +99,15 @@ public function it_always_returns_the_nearest_mount_point( $test1 = '/tmp/a'; $test2 = '/tmp/a/b/c'; $test3 = '/tmp/a/b/c/d/e/f/n'; - $this->getMountPointForPath($test1)->shouldBe($mountPoint1); - $this->getMountPointForPath($test2)->shouldBe($mountPoint1); - $this->getMountPointForPath($test3)->shouldBe($mountPoint3); + $this->getMountPointForPath(new VirtualPath($test1))->shouldBe($mountPoint1); + $this->getMountPointForPath(new VirtualPath($test2))->shouldBe($mountPoint1); + $this->getMountPointForPath(new VirtualPath($test3))->shouldBe($mountPoint3); } public function it_has_a_default_virtual_storage(VirtualStorageInterface $defaultVirtualStorage) { $path = '/tmp/a/b/d/e/f/g'; - $this->getMountPointForPath($path)->getStorage()->shouldBe($defaultVirtualStorage); + $this->getMountPointForPath(new VirtualPath($path))->getStorage()->shouldBe($defaultVirtualStorage); } public function it_can_move_files_between_mount_points( @@ -148,6 +148,43 @@ public function it_can_move_files_between_mount_points( $this->rename($fileSrc, $fileDst); } + public function it_can_copy_files_between_mount_points( + VirtualStorageInterface $fsStorage, + VirtualStorageInterface $awsStorage, + MountPoint $mountPoint1, + MountPoint $mountPoint2 + ) { + $pathTemp = '/tmp/'; + $pathPublic = '/var/www/public'; + + $fileSrc = '/tmp/upload.txt'; + $fileDst = '/var/www/public/assets/upload.txt'; + + //stream creation + $string = "Hi I'm a stream."; + $stream = fopen('php://memory', 'r+'); + fwrite($stream, $string); + rewind($stream); + + $fsStorage->exists($fileSrc)->shouldBeCalled()->willReturn(true); + $awsStorage->exists($fileDst)->shouldBeCalled()->willReturn(true); + $fsStorage->getStream($fileSrc)->shouldBeCalled()->willReturn($stream); + $awsStorage->putStream($fileDst, $stream)->shouldBeCalled()->willReturn(true); + + $mountPoint1->getStorage()->willReturn($fsStorage); + $v1 = new VirtualPath($pathTemp); + $mountPoint1->getVirtualPath()->willReturn($v1); + + $mountPoint2->getStorage()->willReturn($awsStorage); + $v2 = new VirtualPath($pathPublic); + $mountPoint2->getVirtualPath()->willReturn($v2); + + $this->registerMountPoint($mountPoint1); + $this->registerMountPoint($mountPoint2); + + $this->copy($fileSrc, $fileDst); + } + public function it_checks_if_some_file_exists_in_the_mount_point( VirtualStorageInterface $virtualStorage, MountPoint $mountPoint diff --git a/test/spec/Cmp/Storage/Strategy/CallAllStrategySpec.php b/test/spec/Cmp/Storage/Strategy/CallAllStrategySpec.php index 71df9a3..59f9206 100644 --- a/test/spec/Cmp/Storage/Strategy/CallAllStrategySpec.php +++ b/test/spec/Cmp/Storage/Strategy/CallAllStrategySpec.php @@ -94,6 +94,21 @@ public function it_wraps_the_rename_call( $this->rename($path, $newpath, true)->shouldBe(true); } + + public function it_wraps_the_copy_call( + AdapterInterface $adapter1, + AdapterInterface $adapter2, + AdapterInterface $adapter3 + ) { + $path = '/b/c'; + $newpath = '/b/d'; + $adapter1->copy($path, $newpath)->willReturn(true); + $adapter2->copy($path, $newpath)->willReturn(true); + $adapter3->copy($path, $newpath)->willReturn(true); + $this->copy($path, $newpath)->shouldBe(true); + } + + public function it_wraps_the_delete_call( AdapterInterface $adapter1, AdapterInterface $adapter2, diff --git a/test/spec/Cmp/Storage/Strategy/FallBackChainStrategySpec.php b/test/spec/Cmp/Storage/Strategy/FallBackChainStrategySpec.php index 207de80..1df67f5 100644 --- a/test/spec/Cmp/Storage/Strategy/FallBackChainStrategySpec.php +++ b/test/spec/Cmp/Storage/Strategy/FallBackChainStrategySpec.php @@ -144,6 +144,21 @@ public function it_wraps_the_rename_call( $adapter3->rename($path)->shouldNotHaveBeenCalled(); } + + public function it_wraps_the_copy_call( + AdapterInterface $adapter1, + AdapterInterface $adapter2, + AdapterInterface $adapter3 + ) { + $path = '/b/c'; + $newpath = '/b/d'; + $adapter1->copy($path, $newpath)->willReturn(true); + $this->copy($path, $newpath)->shouldBe(true); + + $adapter2->copy($path)->shouldNotHaveBeenCalled(); + $adapter3->copy($path)->shouldNotHaveBeenCalled(); + } + public function it_wraps_the_delete_call( AdapterInterface $adapter1, AdapterInterface $adapter2,