diff --git a/composer.json b/composer.json index 14a3e7bf..6bda4158 100644 --- a/composer.json +++ b/composer.json @@ -13,13 +13,14 @@ ], "require": { "cakephp/orm": "^4.0.2", - "league/flysystem": "^1.0" + "league/flysystem": "^2.2" }, "require-dev": { "cakephp/cakephp": "^4.0.2", "phpunit/phpunit": "~8.5.0", - "league/flysystem-vfs": "*", - "cakephp/cakephp-codesniffer": "^4.0" + "cakephp/cakephp-codesniffer": "^4.0", + "league/flysystem-memory": "^2.0", + "php-vfs/php-vfs": "^1.4.2" }, "autoload": { "psr-4": { diff --git a/src/File/Writer/DefaultWriter.php b/src/File/Writer/DefaultWriter.php index d7087225..c21be10a 100644 --- a/src/File/Writer/DefaultWriter.php +++ b/src/File/Writer/DefaultWriter.php @@ -6,11 +6,12 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Table; use Cake\Utility\Hash; -use League\Flysystem\Adapter\Local; -use League\Flysystem\AdapterInterface; -use League\Flysystem\FileNotFoundException; use League\Flysystem\Filesystem; -use League\Flysystem\FilesystemInterface; +use League\Flysystem\FilesystemAdapter; +use League\Flysystem\FilesystemException; +use League\Flysystem\FilesystemOperator; +use League\Flysystem\Local\LocalFilesystemAdapter; +use League\Flysystem\Visibility; use Psr\Http\Message\UploadedFileInterface; use UnexpectedValueException; @@ -111,12 +112,12 @@ public function delete(array $files): array /** * Writes a set of files to an output * - * @param \League\Flysystem\FilesystemInterface $filesystem a filesystem wrapper + * @param \League\Flysystem\FilesystemOperator $filesystem a filesystem wrapper * @param string $file a full path to a temp file * @param string $path that path to which the file should be written * @return bool */ - public function writeFile(FilesystemInterface $filesystem, $file, $path): bool + public function writeFile(FilesystemOperator $filesystem, $file, $path): bool { // phpcs:ignore $stream = @fopen($file, 'r'); @@ -127,10 +128,19 @@ public function writeFile(FilesystemInterface $filesystem, $file, $path): bool $success = false; $tempPath = $path . '.temp'; $this->deletePath($filesystem, $tempPath); - if ($filesystem->writeStream($tempPath, $stream)) { + try { + $filesystem->writeStream($tempPath, $stream); $this->deletePath($filesystem, $path); - $success = $filesystem->rename($tempPath, $path); + try { + $filesystem->move($tempPath, $path); + $success = true; + } catch (FilesystemException $e) { + // noop + } + } catch (FilesystemException $e) { + // noop } + $this->deletePath($filesystem, $tempPath); is_resource($stream) && fclose($stream); @@ -140,16 +150,17 @@ public function writeFile(FilesystemInterface $filesystem, $file, $path): bool /** * Deletes a path from a filesystem * - * @param \League\Flysystem\FilesystemInterface $filesystem a filesystem writer + * @param \League\Flysystem\FilesystemOperator $filesystem a filesystem writer * @param string $path the path that should be deleted * @return bool */ - public function deletePath(FilesystemInterface $filesystem, string $path): bool + public function deletePath(FilesystemOperator $filesystem, string $path): bool { - $success = false; + $success = true; try { - $success = $filesystem->delete($path); - } catch (FileNotFoundException $e) { + $filesystem->delete($path); + } catch (FilesystemException $e) { + $success = false; // TODO: log this? } @@ -161,19 +172,19 @@ public function deletePath(FilesystemInterface $filesystem, string $path): bool * * @param string $field the field for which data will be saved * @param array $settings the settings for the current field - * @return \League\Flysystem\FilesystemInterface + * @return \League\Flysystem\FilesystemOperator */ - public function getFilesystem(string $field, array $settings = []): FilesystemInterface + public function getFilesystem(string $field, array $settings = []): FilesystemOperator { - $adapter = new Local(Hash::get($settings, 'filesystem.root', ROOT . DS)); + $adapter = new LocalFilesystemAdapter(Hash::get($settings, 'filesystem.root', ROOT . DS)); $adapter = Hash::get($settings, 'filesystem.adapter', $adapter); if (is_callable($adapter)) { $adapter = $adapter(); } - if ($adapter instanceof AdapterInterface) { + if ($adapter instanceof FilesystemAdapter) { return new Filesystem($adapter, Hash::get($settings, 'filesystem.options', [ - 'visibility' => AdapterInterface::VISIBILITY_PUBLIC, + 'visibility' => Visibility::PUBLIC, ])); } diff --git a/tests/TestCase/File/Writer/DefaultWriterTest.php b/tests/TestCase/File/Writer/DefaultWriterTest.php index 6ce625db..00b80f38 100644 --- a/tests/TestCase/File/Writer/DefaultWriterTest.php +++ b/tests/TestCase/File/Writer/DefaultWriterTest.php @@ -6,8 +6,10 @@ use Cake\TestSuite\TestCase; use Josegonzalez\Upload\File\Writer\DefaultWriter; use Laminas\Diactoros\UploadedFile; -use League\Flysystem\Adapter\NullAdapter; -use League\Flysystem\Vfs\VfsAdapter; +use League\Flysystem\InMemory\InMemoryFilesystemAdapter; +use League\Flysystem\UnableToDeleteFile; +use League\Flysystem\UnableToMoveFile; +use League\Flysystem\UnableToWriteFile; use VirtualFileSystem\FileSystem as Vfs; class DefaultWriterTest extends TestCase @@ -29,7 +31,7 @@ public function setUp(): void $this->settings = [ 'filesystem' => [ 'adapter' => function () { - return new VfsAdapter(new Vfs()); + return new InMemoryFilesystemAdapter(); }, ], ]; @@ -65,9 +67,9 @@ public function testInvoke() public function testDelete() { - $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemInterface')->getMock(); - $filesystem->expects($this->at(0))->method('delete')->will($this->returnValue(true)); - $filesystem->expects($this->at(1))->method('delete')->will($this->returnValue(false)); + $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemOperator')->getMock(); + $filesystem->expects($this->at(0))->method('delete'); + $filesystem->expects($this->at(1))->method('delete')->will($this->throwException(new UnableToDeleteFile())); $writer = $this->getMockBuilder('Josegonzalez\Upload\File\Writer\DefaultWriter') ->setMethods(['getFilesystem']) ->setConstructorArgs([$this->table, $this->entity, $this->data, $this->field, $this->settings]) @@ -86,51 +88,51 @@ public function testDelete() public function testWriteFile() { - $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemInterface')->getMock(); - $filesystem->expects($this->once())->method('writeStream')->will($this->returnValue(true)); - $filesystem->expects($this->exactly(3))->method('delete')->will($this->returnValue(true)); - $filesystem->expects($this->once())->method('rename')->will($this->returnValue(true)); + $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemOperator')->getMock(); + $filesystem->expects($this->once())->method('writeStream'); + $filesystem->expects($this->exactly(3))->method('delete'); + $filesystem->expects($this->once())->method('move'); $this->assertTrue($this->writer->writeFile($filesystem, $this->vfs->path('/tmp/tempfile'), 'path')); - $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemInterface')->getMock(); - $filesystem->expects($this->once())->method('writeStream')->will($this->returnValue(false)); - $filesystem->expects($this->exactly(2))->method('delete')->will($this->returnValue(true)); - $filesystem->expects($this->never())->method('rename'); + $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemOperator')->getMock(); + $filesystem->expects($this->once())->method('writeStream')->will($this->throwException(new UnableToWriteFile())); + $filesystem->expects($this->exactly(2))->method('delete'); + $filesystem->expects($this->never())->method('move'); $this->assertFalse($this->writer->writeFile($filesystem, $this->vfs->path('/tmp/tempfile'), 'path')); - $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemInterface')->getMock(); - $filesystem->expects($this->once())->method('writeStream')->will($this->returnValue(true)); - $filesystem->expects($this->exactly(3))->method('delete')->will($this->returnValue(true)); - $filesystem->expects($this->once())->method('rename')->will($this->returnValue(false)); + $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemOperator')->getMock(); + $filesystem->expects($this->once())->method('writeStream'); + $filesystem->expects($this->exactly(3))->method('delete'); + $filesystem->expects($this->once())->method('move')->will($this->throwException(new UnableToMoveFile())); $this->assertFalse($this->writer->writeFile($filesystem, $this->vfs->path('/tmp/tempfile'), 'path')); } public function testDeletePath() { - $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemInterface')->getMock(); - $filesystem->expects($this->any())->method('delete')->will($this->returnValue(true)); + $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemOperator')->getMock(); + $filesystem->expects($this->any())->method('delete'); $this->assertTrue($this->writer->deletePath($filesystem, 'path')); - $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemInterface')->getMock(); - $filesystem->expects($this->any())->method('delete')->will($this->returnValue(false)); + $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemOperator')->getMock(); + $filesystem->expects($this->any())->method('delete')->will($this->throwException(new UnableToDeleteFile())); $this->assertFalse($this->writer->deletePath($filesystem, 'path')); } public function testGetFilesystem() { - $this->assertInstanceOf('League\Flysystem\FilesystemInterface', $this->writer->getFilesystem('field', [])); - $this->assertInstanceOf('League\Flysystem\FilesystemInterface', $this->writer->getFilesystem('field', [ + $this->assertInstanceOf('League\Flysystem\FilesystemOperator', $this->writer->getFilesystem('field', [])); + $this->assertInstanceOf('League\Flysystem\FilesystemOperator', $this->writer->getFilesystem('field', [ 'key' => 'value', ])); - $this->assertInstanceOf('League\Flysystem\FilesystemInterface', $this->writer->getFilesystem('field', [ + $this->assertInstanceOf('League\Flysystem\FilesystemOperator', $this->writer->getFilesystem('field', [ 'filesystem' => [ - 'adapter' => new NullAdapter(), + 'adapter' => new InMemoryFilesystemAdapter(), ], ])); - $this->assertInstanceOf('League\Flysystem\FilesystemInterface', $this->writer->getFilesystem('field', [ + $this->assertInstanceOf('League\Flysystem\FilesystemOperator', $this->writer->getFilesystem('field', [ 'filesystem' => [ 'adapter' => function () { - return new NullAdapter(); + return new InMemoryFilesystemAdapter(); }, ], ]));