Extracting file move operations to a FileMover class.

This improves maintainability as well as doesn't update the db row until the file was actually successfully moved on disk. This keeps db / filesystem in sync.
parent ebac68ee
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
namespace Modules\Media\Events\Handlers; namespace Modules\Media\Events\Handlers;
use Illuminate\Contracts\Filesystem\Factory; use Illuminate\Contracts\Filesystem\Factory;
use League\Flysystem\FileExistsException;
use Modules\Media\Events\FileStartedMoving; use Modules\Media\Events\FileStartedMoving;
use Modules\Media\Image\Thumbnail; use Modules\Media\Image\Thumbnail;
use Modules\Media\Image\ThumbnailManager; use Modules\Media\Image\ThumbnailManager;
...@@ -56,11 +57,15 @@ class MoveFileOnDisk ...@@ -56,11 +57,15 @@ class MoveFileOnDisk
private function move($fromPath, $toPath) private function move($fromPath, $toPath)
{ {
$this->filesystem->disk($this->getConfiguredFilesystem()) try {
->move( $this->filesystem->disk($this->getConfiguredFilesystem())
$this->getDestinationPath($fromPath), ->move(
$this->getDestinationPath($toPath) $this->getDestinationPath($fromPath),
); $this->getDestinationPath($toPath)
);
} catch (FileExistsException $e) {
}
} }
/** /**
......
...@@ -84,7 +84,6 @@ class MediaServiceProvider extends ServiceProvider ...@@ -84,7 +84,6 @@ class MediaServiceProvider extends ServiceProvider
$events->listen(FolderWasUpdated::class, RenameFolderOnDisk::class); $events->listen(FolderWasUpdated::class, RenameFolderOnDisk::class);
$events->listen(FolderIsDeleting::class, DeleteFolderOnDisk::class); $events->listen(FolderIsDeleting::class, DeleteFolderOnDisk::class);
$events->listen(FolderIsDeleting::class, DeleteAllChildrenOfFolder::class); $events->listen(FolderIsDeleting::class, DeleteAllChildrenOfFolder::class);
$events->listen(FileStartedMoving::class, MoveFileOnDisk::class);
$this->app[TagManager::class]->registerNamespace(new File()); $this->app[TagManager::class]->registerNamespace(new File());
$this->registerThumbnails(); $this->registerThumbnails();
......
<?php
namespace Modules\Media\Services;
use Illuminate\Contracts\Filesystem\Factory;
use League\Flysystem\FileExistsException;
use Modules\Media\Entities\File;
use Modules\Media\Image\Thumbnail;
use Modules\Media\Image\ThumbnailManager;
use Modules\Media\Repositories\FileRepository;
use Modules\Media\Repositories\FolderRepository;
use Modules\Media\ValueObjects\MediaPath;
class FileMover implements Mover
{
/**
* All the different images types where thumbnails should be created
* @var array
*/
private $imageExtensions = ['jpg', 'png', 'jpeg', 'gif'];
/**
* @var Factory
*/
private $filesystem;
private $fromPath;
private $toPath;
/**
* @var FileRepository
*/
private $file;
/**
* @var ThumbnailManager
*/
private $manager;
public function __construct(Factory $filesystem, FileRepository $file, ThumbnailManager $manager)
{
$this->filesystem = $filesystem;
$this->file = $file;
$this->manager = $manager;
}
public function move(File $file, File $destination): bool
{
$movedOnDisk = $this->moveOriginalOnDisk($file, $destination);
if ($movedOnDisk === false) {
return false;
}
$file = $this->moveDatabase($file, $destination);
if ($this->isImage($this->fromPath)) {
$this->moveThumbnails();
}
return true;
}
private function moveThumbnails()
{
foreach ($this->manager->all() as $thumbnail) {
$fromPath = $this->getFilenameFor($this->fromPath, $thumbnail);
$toPath = $this->getFilenameFor($this->toPath, $thumbnail);
$this->moveFile($fromPath, $toPath);
}
}
private function moveOriginalOnDisk(File $folder, File $destination) : bool
{
$this->fromPath = $folder->path->getRelativeUrl();
$this->toPath = $this->getNewPathFor($folder->filename, $destination->id);
return $this->moveFile($this->fromPath, $this->toPath);
}
private function moveDatabase(File $file, File $destination) : File
{
return $this->file->move($file, $destination);
}
private function moveFile($fromPath, $toPath) : bool
{
try {
$this->filesystem->disk($this->getConfiguredFilesystem())
->move(
$this->getDestinationPath($fromPath),
$this->getDestinationPath($toPath)
);
} catch (FileExistsException $e) {
return false;
}
return true;
}
private function getDestinationPath($path) : string
{
if ($this->getConfiguredFilesystem() === 'local') {
return basename(public_path()) . $path;
}
return $path;
}
private function getConfiguredFilesystem() : string
{
return config('asgard.media.config.filesystem');
}
private function getNewPathFor(string $filename, int $folderId)
{
if ($folderId !== 0) {
$parent = app(FolderRepository::class)->findFolder($folderId);
if ($parent !== null) {
return $parent->path->getRelativeUrl() . '/' . $filename;
}
}
return config('asgard.media.config.files-path') . $filename;
}
/**
* Check if the given path is en image
* @param string $path
* @return bool
*/
private function isImage($path)
{
return in_array(pathinfo($path, PATHINFO_EXTENSION), $this->imageExtensions);
}
/**
* @param string $path
* @param Thumbnail|string $thumbnail
* @return string
*/
private function getFilenameFor(string $path, $thumbnail)
{
if ($thumbnail instanceof Thumbnail) {
$thumbnail = $thumbnail->name();
}
$filenameWithoutPrefix = $this->removeConfigPrefix($path);
$filename = substr(strrchr($filenameWithoutPrefix, '/'), 1);
$folders = str_replace($filename, '', $filenameWithoutPrefix);
if ($filename === false) {
return config('asgard.media.config.files-path') . $this->newFilename($path, $thumbnail);
}
return config('asgard.media.config.files-path') . $folders . $this->newFilename($path, $thumbnail);
}
/**
* @param string $path
* @return string
*/
private function removeConfigPrefix(string $path) : string
{
$configAssetPath = config('asgard.media.config.files-path');
return str_replace([
$configAssetPath,
ltrim($configAssetPath, '/'),
], '', $path);
}
/**
* Prepend the thumbnail name to filename
* @param $path
* @param $thumbnail
* @return mixed|string
*/
private function newFilename($path, $thumbnail)
{
$filename = pathinfo($path, PATHINFO_FILENAME);
return $filename . '_' . $thumbnail . '.' . pathinfo($path, PATHINFO_EXTENSION);
}
}
...@@ -257,112 +257,6 @@ class EloquentFileRepositoryTest extends MediaTestCase ...@@ -257,112 +257,6 @@ class EloquentFileRepositoryTest extends MediaTestCase
$this->assertCount(2, $this->file->allForGrid()); $this->assertCount(2, $this->file->allForGrid());
} }
/** @test */
public function it_can_move_a_file_database()
{
$folderRepository = app(FolderRepository::class);
$parentFolder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$folder = $folderRepository->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]);
$file = \Illuminate\Http\UploadedFile::fake()->create('my-file.pdf');
$file = app(FileService::class)->store($file);
$file = $this->file->move($file, $folder);
$this->assertEquals('my-file.pdf', $file->filename);
$this->assertEquals($file->folder_id, $folder->id);
$this->assertEquals('/assets/media/my-folder/child-folder/my-file.pdf', $file->path->getRelativeUrl());
}
/** @test */
public function it_can_move_file_on_disk()
{
$folderRepository = app(FolderRepository::class);
$parentFolder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$folder = $folderRepository->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]);
$file = \Illuminate\Http\UploadedFile::fake()->create('my-file.pdf');
$file = app(FileService::class)->store($file);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.pdf')));
$this->file->move($file, $folder);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file.pdf')));
}
/** @test */
public function it_can_move_file_with_thumbnails_on_disk()
{
$folderRepository = app(FolderRepository::class);
$parentFolder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$folder = $folderRepository->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]);
$file = \Illuminate\Http\UploadedFile::fake()->image('my-file.jpg');
$file = app(FileService::class)->store($file);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.jpg')));
$this->file->move($file, $folder);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file_smallThumb.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file_mediumThumb.jpg')));
}
/** @test */
public function it_can_move_file_back_to_root_folder()
{
$folderRepository = app(FolderRepository::class);
$parentFolder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$folder = $folderRepository->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]);
$file = \Illuminate\Http\UploadedFile::fake()->create('my-file.pdf');
$file = app(FileService::class)->store($file, $folder->id);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file.pdf')));
$this->file->move($file, $this->makeRootFolder());
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.pdf')));
}
/** @test */
public function it_can_move_file_with_thumbnails_back_to_root_folder()
{
$folderRepository = app(FolderRepository::class);
$parentFolder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$folder = $folderRepository->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]);
$file = \Illuminate\Http\UploadedFile::fake()->image('my-file.jpg');
$file = app(FileService::class)->store($file, $folder->id);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file.jpg')));
$this->file->move($file, $this->makeRootFolder());
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file_smallThumb.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file_mediumThumb.jpg')));
}
/** @test */
public function it_can_store_same_filename_in_other_folder_with_no_suffix()
{
$folderRepository = app(FolderRepository::class);
$folder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$file = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $folder->id);
$fileTwo = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'));
$subFolder = $folderRepository->create(['name' => 'My Sub Folder', 'parent_id' => $folder->id]);
$fileThree = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $subFolder->id);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/my-file.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/my-sub-folder/my-file.jpg')));
$this->assertEquals('/assets/media/my-folder/my-file.jpg', $file->path->getRelativeUrl());
$this->assertEquals('/assets/media/my-file.jpg', $fileTwo->path->getRelativeUrl());
$this->assertEquals('/assets/media/my-folder/my-sub-folder/my-file.jpg', $fileThree->path->getRelativeUrl());
}
private function createFile($fileName = 'random/name.jpg') private function createFile($fileName = 'random/name.jpg')
{ {
return File::create([ return File::create([
...@@ -374,13 +268,4 @@ class EloquentFileRepositoryTest extends MediaTestCase ...@@ -374,13 +268,4 @@ class EloquentFileRepositoryTest extends MediaTestCase
'folder_id' => 0, 'folder_id' => 0,
]); ]);
} }
private function makeRootFolder() : File
{
return new File([
'id' => 0,
'folder_id' => 0,
'path' => config('asgard.media.config.files-path'),
]);
}
} }
<?php
namespace Modules\Media\Tests;
use Modules\Media\Entities\File;
use Modules\Media\Repositories\FileRepository;
use Modules\Media\Repositories\FolderRepository;
use Modules\Media\Services\FileMover;
use Modules\Media\Services\FileService;
final class FileMoverTest extends MediaTestCase
{
/**
* @var FileMover
*/
private $mover;
/**
* @var FileRepository
*/
private $file;
protected function setUp()
{
parent::setUp();
$this->resetDatabase();
$this->mover = app(FileMover::class);
$this->file = app(FileRepository::class);
$this->app['config']->set('asgard.media.config.files-path', '/assets/media/');
}
public function tearDown()
{
if ($this->app['files']->isDirectory(public_path('assets')) === true) {
$this->app['files']->deleteDirectory(public_path('assets'));
}
}
/** @test */
public function it_can_move_file_on_disk()
{
$folderRepository = app(FolderRepository::class);
$parentFolder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$folder = $folderRepository->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]);
$file = \Illuminate\Http\UploadedFile::fake()->create('my-file.pdf');
$file = app(FileService::class)->store($file);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.pdf')));
$this->mover->move($file, $folder);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file.pdf')));
}
/** @test */
public function it_can_move_a_file_database()
{
$folderRepository = app(FolderRepository::class);
$parentFolder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$folder = $folderRepository->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]);
$file = \Illuminate\Http\UploadedFile::fake()->create('my-file.pdf');
$file = app(FileService::class)->store($file);
$this->mover->move($file, $folder);
$this->assertEquals('my-file.pdf', $file->filename);
$this->assertEquals($file->folder_id, $folder->id);
$this->assertEquals('/assets/media/my-folder/child-folder/my-file.pdf', $file->path->getRelativeUrl());
}
/** @test */
public function it_can_move_file_with_thumbnails_on_disk()
{
$folderRepository = app(FolderRepository::class);
$parentFolder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$folder = $folderRepository->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]);
$file = \Illuminate\Http\UploadedFile::fake()->image('my-file.jpg');
$file = app(FileService::class)->store($file);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.jpg')));
$this->mover->move($file, $folder);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file_smallThumb.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file_mediumThumb.jpg')));
}
/** @test */
public function it_can_move_file_back_to_root_folder()
{
$folderRepository = app(FolderRepository::class);
$parentFolder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$folder = $folderRepository->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]);
$file = \Illuminate\Http\UploadedFile::fake()->create('my-file.pdf');
$file = app(FileService::class)->store($file, $folder->id);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file.pdf')));
$this->mover->move($file, $this->makeRootFolder());
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.pdf')));
}
/** @test */
public function it_can_move_file_with_thumbnails_back_to_root_folder()
{
$folderRepository = app(FolderRepository::class);
$parentFolder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$folder = $folderRepository->create(['name' => 'Child Folder', 'parent_id' => $parentFolder->id]);
$file = \Illuminate\Http\UploadedFile::fake()->image('my-file.jpg');
$file = app(FileService::class)->store($file, $folder->id);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/child-folder/my-file.jpg')));
$this->mover->move($file, $this->makeRootFolder());
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file_smallThumb.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file_mediumThumb.jpg')));
}
/** @test */
public function it_can_store_same_filename_in_other_folder_with_no_suffix()
{
$folderRepository = app(FolderRepository::class);
$folder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$file = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $folder->id);
$fileTwo = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'));
$subFolder = $folderRepository->create(['name' => 'My Sub Folder', 'parent_id' => $folder->id]);
$fileThree = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $subFolder->id);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/my-file.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/my-sub-folder/my-file.jpg')));
$this->assertEquals('/assets/media/my-folder/my-file.jpg', $file->path->getRelativeUrl());
$this->assertEquals('/assets/media/my-file.jpg', $fileTwo->path->getRelativeUrl());
$this->assertEquals('/assets/media/my-folder/my-sub-folder/my-file.jpg', $fileThree->path->getRelativeUrl());
}
/** @test */
public function it_does_not_move_file_if_file_name_exists_at_location()
{
$folderRepository = app(FolderRepository::class);
$folder = $folderRepository->create(['name' => 'My Folder', 'parent_id' => 0]);
$file = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'), $folder->id);
$fileTwo = app(FileService::class)->store(\Illuminate\Http\UploadedFile::fake()->image('my-file.jpg'));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/my-file.jpg')));
$this->assertEquals('/assets/media/my-folder/my-file.jpg', $file->path->getRelativeUrl());
$this->assertEquals('/assets/media/my-file.jpg', $fileTwo->path->getRelativeUrl());
$this->mover->move($fileTwo, $folder);
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-file.jpg')));
$this->assertTrue($this->app['files']->exists(public_path('/assets/media/my-folder/my-file.jpg')));
$this->assertEquals('/assets/media/my-folder/my-file.jpg', $file->path->getRelativeUrl());
$this->assertEquals('/assets/media/my-file.jpg', $fileTwo->path->getRelativeUrl());
}
private function makeRootFolder() : File
{
return new File([
'id' => 0,
'folder_id' => 0,
'path' => config('asgard.media.config.files-path'),
]);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment