Skip to content

Commit

Permalink
Extracting file move operations to a FileMover class.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nWidart committed Oct 10, 2017
1 parent ebac68e commit 09055ee
Show file tree
Hide file tree
Showing 5 changed files with 365 additions and 121 deletions.
15 changes: 10 additions & 5 deletions Modules/Media/Events/Handlers/MoveFileOnDisk.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace Modules\Media\Events\Handlers;

use Illuminate\Contracts\Filesystem\Factory;
use League\Flysystem\FileExistsException;
use Modules\Media\Events\FileStartedMoving;
use Modules\Media\Image\Thumbnail;
use Modules\Media\Image\ThumbnailManager;
Expand Down Expand Up @@ -56,11 +57,15 @@ private function moveThumbnails($event)

private function move($fromPath, $toPath)
{
$this->filesystem->disk($this->getConfiguredFilesystem())
->move(
$this->getDestinationPath($fromPath),
$this->getDestinationPath($toPath)
);
try {
$this->filesystem->disk($this->getConfiguredFilesystem())
->move(
$this->getDestinationPath($fromPath),
$this->getDestinationPath($toPath)
);
} catch (FileExistsException $e) {

}
}

/**
Expand Down
1 change: 0 additions & 1 deletion Modules/Media/Providers/MediaServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ public function boot(DispatcherContract $events)
$events->listen(FolderWasUpdated::class, RenameFolderOnDisk::class);
$events->listen(FolderIsDeleting::class, DeleteFolderOnDisk::class);
$events->listen(FolderIsDeleting::class, DeleteAllChildrenOfFolder::class);
$events->listen(FileStartedMoving::class, MoveFileOnDisk::class);

$this->app[TagManager::class]->registerNamespace(new File());
$this->registerThumbnails();
Expand Down
180 changes: 180 additions & 0 deletions Modules/Media/Services/FileMover.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
<?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);
}
}
115 changes: 0 additions & 115 deletions Modules/Media/Tests/EloquentFileRepositoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -257,112 +257,6 @@ public function it_can_fetch_all_files_only()
$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')
{
return File::create([
Expand All @@ -374,13 +268,4 @@ private function createFile($fileName = 'random/name.jpg')
'folder_id' => 0,
]);
}

private function makeRootFolder() : File
{
return new File([
'id' => 0,
'folder_id' => 0,
'path' => config('asgard.media.config.files-path'),
]);
}
}
Loading

0 comments on commit 09055ee

Please sign in to comment.