diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..87ff7d7 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +tests +travis.yml +.php_cs +.phplint.yml +phpunit.xml +CODE_OF_CONDUCT.md \ No newline at end of file diff --git a/.gitignore b/.gitignore index 75c948e..8255be6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ composer.phar /vendor/ composer.lock +/build diff --git a/.php_cs b/.php_cs new file mode 100644 index 0000000..9c82555 --- /dev/null +++ b/.php_cs @@ -0,0 +1,18 @@ +in(__DIR__ . '/src') + ->in(__DIR__ . '/config') + ->in(__DIR__ . '/tests') + ->name('*.php') + ->ignoreDotFiles(true) + ->ignoreVCS(true); + +return PhpCsFixer\Config::create() + ->setRules(array( + '@PHP56Migration' => true, + '@Symfony' => true, + 'align_multiline_comment' => true, + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + )) + ->setFinder($finder); \ No newline at end of file diff --git a/.phplint.yml b/.phplint.yml new file mode 100644 index 0000000..cdcacbe --- /dev/null +++ b/.phplint.yml @@ -0,0 +1,7 @@ +path: ./ +jobs: 5 +cache: build/phplint.cache +extensions: + - php +exclude: + - vendor diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b7eebba..3b1d149 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ upload and progress. ## Pull Requests - **Use the [PSR-2 Coding Standard](https://github.com/php-fig/fig-standards/blob/master/accepted/PSR-2-coding-style-guide.md).** - The easiest way to apply the conventions is to install [PHP Code Sniffer](http://pear.php.net/package/PHP_CodeSniffer). + The easiest way to apply the conventions is to use `composer run lint:fix`. - **Consider our release cycle.** We try to follow [SemVer v2.0.0](http://semver.org/). @@ -20,6 +20,14 @@ upload and progress. - **Create feature branches.** Don't ask us to pull from your master branch. - **One pull request per feature.** If you want to do more than one thing, send multiple pull requests. + +### Before pull-request do: + +1. Rebase your changes on master branch +2. Lint project `composer run lint` +3. Run tests `composer run test` +4. (recommended) Write tests +5. (optinal) Rebase your commits to fewer commits **Thank you!** diff --git a/build/phplint.cache b/build/phplint.cache new file mode 100644 index 0000000..2c215ce --- /dev/null +++ b/build/phplint.cache @@ -0,0 +1 @@ +{"\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/config\/chunk-upload.php":"3bf9579f7cf6a1eb7d0fff65dffff244","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/tests\/Providers\/ChunkUploadServiceProviderMockTest.php":"2282c32e3a86aebd250a2742d8b72f7f","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/tests\/Handler\/DropZoneUploadHandlerTest.php":"040856dcb760f2d32aaaea9fe27f6e46","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/tests\/Handler\/ContentRangeUploadHandlerTest.php":"6bdfb25f81de89ab5090f0fa7fd9b047","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/tests\/Handler\/NgFileUploadHandlerTest.php":"ed9108dc2c65ad902e4c2fc45f39685b","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Handler\/Traits\/HandleParallelUploadTrait.php":"92f15e377c48a75bd107e386bb7b756d","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Handler\/SingleUploadHandler.php":"27b1ee9787fa8545613a34479e0709a8","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Handler\/ChunksInRequestSimpleUploadHandler.php":"68ecb40fdcb0c0b55ef526c4530ad734","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Handler\/ChunksInRequestUploadHandler.php":"f572bb9886f6a6ef228dba44ac259a5a","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/tests\/FileSystemDriverMock.php":"d9e586d7799383f802072b9512f7997f","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Handler\/ResumableJSUploadHandler.php":"1334bec4abeda2cf03471bfb3559c335","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Handler\/AbstractHandler.php":"26570a94d35a7823882c5d3e6fa1ba06","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Handler\/ContentRangeUploadHandler.php":"bb7e4d57073cc0e8d609dc45d36b2b04","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Handler\/NgFileUploadHandler.php":"dfffe2232b501e3d3d6bbd2e2b12a6cf","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Handler\/DropZoneUploadHandler.php":"6f8cd3e4e2f23a302112a47dcbc2f6d0","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Handler\/HandlerFactory.php":"83592250e6da64e58507be2a3e6ff121","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Config\/AbstractConfig.php":"039b10794f9ba0e736e40a2ddc516bb4","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Config\/FileConfig.php":"23f3ea30462fe38b36ff2c6ac0d50b95","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Providers\/ChunkUploadServiceProvider.php":"1d71ea1eb027b203a685cb7f26319187","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Exceptions\/MissingChunkFilesException.php":"ec3d99b7270a08cc83dde66a0437727c","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Exceptions\/ChunkInvalidValueException.php":"0b784da05a1331807e76090ed0cb86cf","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Exceptions\/ChunkSaveException.php":"28772177fbc184d45fe114d90b6f6178","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Exceptions\/ContentRangeValueToLargeException.php":"0aa46c6b0d5095a12c232cfcf0f04133","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Exceptions\/UploadMissingFileException.php":"089fc93c3d7c69d0e25fc97521e051f3","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Exceptions\/UploadFailedException.php":"57238f260bd21b453ab8b5ce19ad9b0b","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Receiver\/FileReceiver.php":"97ac4e59b58c5cd7ed75e6b150848e5e","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Storage\/ChunkStorage.php":"eea2d2545d641b2ddb0a1f23c624df76","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/ChunkFile.php":"f62fe0997220592566068840ca18c904","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Commands\/ClearChunksCommand.php":"2c2bf15e8b379fde6baf354c45703ce3","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Save\/AbstractSave.php":"edb3e978a639858378f3f0dea8e19f9c","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Save\/SingleSave.php":"3e5ddd1b6effe5ca24e21a0234e7096b","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Save\/ChunkSave.php":"b981c70ab1ff39a3c5ebe11ed10ec8f4","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/Save\/ParallelSave.php":"421451b03630fcaade56b3ced6a96c06","\/Users\/pion\/Work\/Projekty\/Github\/PHP\/laravel\/laravel-chunk-upload\/src\/FileMerger.php":"538a7559c05d5c83161f8c6640dc7f45"} \ No newline at end of file diff --git a/composer.json b/composer.json index b1cd203..16d1c44 100644 --- a/composer.json +++ b/composer.json @@ -11,7 +11,8 @@ "scripts": { "lint:fix": "./vendor/bin/php-cs-fixer fix --config=.php_cs --using-cache false", "lint:check": "./vendor/bin/phplint", - "lint": "composer run-script lint-fix && composer run-script lint-check" + "lint": "composer run-script lint:fix && composer run-script lint:check", + "test": "./vendor/bin/phpunit" }, "require": { "illuminate/http": "5.1.* || 5.2.* || 5.3.* || 5.4.* || 5.5.* || 5.6.* || 5.7.*", @@ -22,7 +23,7 @@ "require-dev": { "laravel/laravel": "5.1.* || 5.2.* || 5.3.* || 5.4.* || 5.5.* || 5.6.* || 5.7.*", "phpunit/phpunit": "5.7 || 6.0 || 7.0", - "mockery/mockery": "^0.9.9", + "mockery/mockery": "^1.1.0", "friendsofphp/php-cs-fixer": "^2.12", "overtrue/phplint": "^1.1" }, diff --git a/config/chunk-upload.php b/config/chunk-upload.php index c962eea..575f826 100644 --- a/config/chunk-upload.php +++ b/config/chunk-upload.php @@ -30,4 +30,12 @@ ], ], ], + 'handlers' => [ + // A list of handlers/providers that will be appended to existing list of handlers + 'custom' => [], + // Overrides the list of handlers - use only what you really want + 'override' => [ + // \Pion\Laravel\ChunkUpload\Handler\DropZoneUploadHandler::class + ], + ], ]; diff --git a/phpcs.xml b/phpcs.xml deleted file mode 100644 index 19f2aef..0000000 --- a/phpcs.xml +++ /dev/null @@ -1,63 +0,0 @@ - - - - - The Laravel Coding Standards - - - src - config - tests - - - */database/* - */cache/* - */*.js - */*.css - */*.xml - */*.blade.php - */autoload.php - */storage/* - */docs/* - */vendor/* - */migrations/* - - - - - - - - - - \ No newline at end of file diff --git a/src/Config/AbstractConfig.php b/src/Config/AbstractConfig.php index 5acffa9..bb89fd1 100644 --- a/src/Config/AbstractConfig.php +++ b/src/Config/AbstractConfig.php @@ -16,6 +16,13 @@ public static function config() return app(AbstractConfig::class); } + /** + * Returns a list custom handlers (custom, override). + * + * @return array + */ + abstract public function handlers(); + /** * Returns the disk name to use for the chunk storage. * diff --git a/src/Config/FileConfig.php b/src/Config/FileConfig.php index 9041c9f..71be4a6 100644 --- a/src/Config/FileConfig.php +++ b/src/Config/FileConfig.php @@ -14,6 +14,16 @@ class FileConfig extends AbstractConfig */ const FILE_NAME = 'chunk-upload'; + /** + * Returns a list custom handlers (custom, override). + * + * @return array + */ + public function handlers() + { + return $this->get('hanlders', []); + } + /** * Returns the disk name to use for the chunk storage. * diff --git a/src/Handler/HandlerFactory.php b/src/Handler/HandlerFactory.php index 26384ac..832cbd6 100644 --- a/src/Handler/HandlerFactory.php +++ b/src/Handler/HandlerFactory.php @@ -63,6 +63,26 @@ public static function register($handlerClass) static::$handlers[] = $handlerClass; } + /** + * Overrides the handler list. + * + * @param array $handlers + */ + public static function setHandlers($handlers) + { + static::$handlers = $handlers; + } + + /** + * Returns the handler list. + * + * @return array + */ + public static function getHandlers() + { + return static::$handlers; + } + /** * Sets the default fallback handler when the detection fails. * diff --git a/src/Providers/ChunkUploadServiceProvider.php b/src/Providers/ChunkUploadServiceProvider.php index 9d985fc..609692e 100644 --- a/src/Providers/ChunkUploadServiceProvider.php +++ b/src/Providers/ChunkUploadServiceProvider.php @@ -5,6 +5,7 @@ use Illuminate\Console\Scheduling\Schedule; use Illuminate\Http\Request; use Illuminate\Support\Arr; +use Illuminate\Support\Facades\Storage; use Illuminate\Support\ServiceProvider; use Pion\Laravel\ChunkUpload\Commands\ClearChunksCommand; use Pion\Laravel\ChunkUpload\Config\AbstractConfig; @@ -12,7 +13,6 @@ use Pion\Laravel\ChunkUpload\Handler\HandlerFactory; use Pion\Laravel\ChunkUpload\Receiver\FileReceiver; use Pion\Laravel\ChunkUpload\Storage\ChunkStorage; -use Storage; class ChunkUploadServiceProvider extends ServiceProvider { @@ -22,7 +22,8 @@ class ChunkUploadServiceProvider extends ServiceProvider public function boot() { // Get the schedule config - $scheduleConfig = AbstractConfig::config()->scheduleConfig(); + $config = $this->app->make(AbstractConfig::class); + $scheduleConfig = $config->scheduleConfig(); // Run only if schedule is enabled if (true === Arr::get($scheduleConfig, 'enabled', false)) { @@ -33,9 +34,12 @@ public function boot() $schedule = $this->app->make(Schedule::class); // Register the clear chunks with custom schedule - $schedule->command('uploads:clear')->cron(Arr::get($scheduleConfig, 'cron', '* * * * *')); + $schedule->command('uploads:clear') + ->cron(Arr::get($scheduleConfig, 'cron', '* * * * *')); }); } + + $this->registerHandlers($config->handlers()); } /** @@ -64,7 +68,7 @@ public function register() $config = $app->make(AbstractConfig::class); // Build the chunk storage - return new ChunkStorage(Storage::disk($config->chunksDiskName()), $config); + return new ChunkStorage($this->disk($config->chunksDiskName()), $config); }); /* @@ -83,11 +87,25 @@ public function register() } /** - * Publishes and mergers the config. Uses the FileConfig. + * Returns disk name. + * + * @param string $diskName + * + * @return \Illuminate\Contracts\Filesystem\Filesystem + */ + protected function disk($diskName) + { + return Storage::disk($diskName); + } + + /** + * Publishes and mergers the config. Uses the FileConfig. Registers custom handlers. * * @see FileConfig * @see ServiceProvider::publishes * @see ServiceProvider::mergeConfigFrom + * + * @return $this */ protected function registerConfig() { @@ -106,5 +124,30 @@ protected function registerConfig() $configPath, $configIndex ); + + return $this; + } + + /** + * Registers handlers from config. + * + * @param array $handlersConfig + * + * @return $this + */ + protected function registerHandlers(array $handlersConfig) + { + $overrideHandlers = Arr::get($handlersConfig, 'override', []); + if (count($overrideHandlers) > 0) { + HandlerFactory::setHandlers($overrideHandlers); + + return $this; + } + + foreach (Arr::get($handlersConfig, 'custom', []) as $handler) { + HandlerFactory::register($handler); + } + + return $this; } } diff --git a/src/Storage/ChunkStorage.php b/src/Storage/ChunkStorage.php index 0e9803e..aa9cc16 100644 --- a/src/Storage/ChunkStorage.php +++ b/src/Storage/ChunkStorage.php @@ -2,7 +2,7 @@ namespace Pion\Laravel\ChunkUpload\Storage; -use Illuminate\Filesystem\FilesystemAdapter; +use Illuminate\Contracts\Filesystem\Filesystem as FilesystemContract; use Illuminate\Support\Collection; use League\Flysystem\Adapter\Local; use League\Flysystem\FilesystemInterface; @@ -51,10 +51,10 @@ public static function storage() /** * ChunkStorage constructor. * - * @param FilesystemAdapter $disk the desired disk for chunk storage - * @param AbstractConfig $config + * @param FilesystemContract $disk the desired disk for chunk storage + * @param AbstractConfig $config */ - public function __construct(FilesystemAdapter $disk, $config) + public function __construct(FilesystemContract $disk, $config) { // save the config $this->config = $config; diff --git a/tests/FileSystemDriverMock.php b/tests/FileSystemDriverMock.php new file mode 100644 index 0000000..f26871d --- /dev/null +++ b/tests/FileSystemDriverMock.php @@ -0,0 +1,11 @@ +app = Mockery::mock(\Illuminate\Contracts\Container\Container::class); + $this->config = Mockery::mock(Repository::class); + + $this->app->shouldReceive('make') + ->with(AbstractConfig::class) + ->andReturn($this->config); + + $this->service = Mockery::mock(ChunkUploadServiceProvider::class, [$this->app])->makePartial(); + $this->service->shouldAllowMockingProtectedMethods(); + } + + public function testBootWithEmptyScheduleAndRegisterEmptyHandlers() + { + $this->app->shouldNotReceive('booted'); + $this->config->shouldReceive('scheduleConfig') + ->once() + ->andReturn([]); + + $this->config->shouldReceive('handlers') + ->once() + ->andReturn([]); + + $this->service->boot(); + + $this->assertEquals([ + ContentRangeUploadHandler::class, + ChunksInRequestUploadHandler::class, + ResumableJSUploadHandler::class, + DropZoneUploadHandler::class, + ChunksInRequestSimpleUploadHandler::class, + NgFileUploadHandler::class, + ], HandlerFactory::getHandlers()); + } + + public function testBootWithEmptyScheduleAndCustomHandler() + { + $this->app->shouldNotReceive('booted'); + $this->config->shouldReceive('scheduleConfig') + ->once() + ->andReturn([]); + + $this->config->shouldReceive('handlers') + ->once() + ->andReturn([ + 'custom' => [ + SingleUploadHandler::class, + SingleUploadHandler::class, + ], + ]); + + $this->service->boot(); + + $this->assertEquals([ + ContentRangeUploadHandler::class, + ChunksInRequestUploadHandler::class, + ResumableJSUploadHandler::class, + DropZoneUploadHandler::class, + ChunksInRequestSimpleUploadHandler::class, + NgFileUploadHandler::class, + SingleUploadHandler::class, + SingleUploadHandler::class, + ], HandlerFactory::getHandlers()); + } + + public function testBootWithEmptyScheduleAndOverrideHandler() + { + $this->app->shouldNotReceive('booted'); + $this->config->shouldReceive('scheduleConfig') + ->once() + ->andReturn([]); + + $this->config->shouldReceive('handlers') + ->once() + ->andReturn([ + 'override' => [ + ContentRangeUploadHandler::class, + ], + ]); + + $this->service->boot(); + + $this->assertEquals([ + ContentRangeUploadHandler::class, + ], HandlerFactory::getHandlers()); + } + + public function testBootScheduleDisabled() + { + $this->app->shouldNotReceive('booted'); + $this->config->shouldReceive('scheduleConfig') + ->once() + ->andReturn([ + 'enabled' => false, + ]); + + $this->config->shouldReceive('handlers') + ->once() + ->andReturn([]); + + $this->service->boot(); + } + + public function testBootScheduleEnabledAndBootWithoutCron() + { + $scheduleConfig = [ + 'enabled' => true, + ]; + $scheduleMock = Mockery::mock(); + $scheduleMock->shouldReceive('cron') + ->once() + ->with('* * * * *'); + + $scheduleMock->shouldReceive('command') + ->once() + ->with('uploads:clear') + ->andReturn($scheduleMock); + + $this->app->shouldReceive('make') + ->once() + ->with(Schedule::class) + ->andReturn($scheduleMock); + + $this->app->shouldReceive('booted') + ->once() + ->withArgs(function ($callback) { + $callback(); + + return true; + }); + $this->config->shouldReceive('scheduleConfig') + ->once() + ->andReturn($scheduleConfig); + $this->config->shouldReceive('handlers') + ->once() + ->andReturn([]); + + $this->service->boot(); + } + + public function testBootScheduleEnabledAndBootWithCronSettings() + { + $scheduleConfig = [ + 'enabled' => true, + 'cron' => '10 * * * *', + ]; + $scheduleMock = Mockery::mock(); + $scheduleMock->shouldReceive('cron') + ->once() + ->with('10 * * * *'); + + $scheduleMock->shouldReceive('command') + ->once() + ->with('uploads:clear') + ->andReturn($scheduleMock); + + $this->app->shouldReceive('make') + ->once() + ->with(Schedule::class) + ->andReturn($scheduleMock); + + $this->app->shouldReceive('booted') + ->once() + ->withArgs(function ($callback) { + $callback(); + + return true; + }); + $this->config->shouldReceive('scheduleConfig') + ->once() + ->andReturn($scheduleConfig); + $this->config->shouldReceive('handlers') + ->once() + ->andReturn([]); + + $this->service->boot(); + } + + public function testRegister() + { + $this->service->shouldReceive('commands') + ->once() + ->with([ClearChunksCommand::class]); + + $this->service->shouldReceive('registerConfig') + ->once() + ->andReturn($this->service); + + $this->app->shouldReceive('singleton') + ->withArgs(function ($class, $closure) { + if (AbstractConfig::class === $class) { + $chunkStorage = $closure($this->app); + $this->assertInstanceOf(FileConfig::class, $chunkStorage); + + return true; + } + if (ChunkStorage::class === $class) { + $this->config->shouldReceive('chunksDiskName') + ->once() + ->andReturn('local'); + + $fileSystemMock = Mockery::mock(FilesystemContract::class); + $fileSystemMock->shouldReceive('getDriver') + ->once() + ->andReturn(new FileSystemDriverMock()); + + // Force different file mock + $this->service->shouldReceive('disk') + ->with('local') + ->once() + ->andReturn($fileSystemMock); + + $chunkStorage = $closure($this->app); + $this->assertInstanceOf(ChunkStorage::class, $chunkStorage); + + return true; + } + + return false; + }) + ->twice(); + + $this->app->shouldReceive('bind') + ->withArgs(function ($class, $closure) { + if (FileReceiver::class === $class) { + $this->assertTrue(is_callable($closure)); + + return true; + } + + return false; + }) + ->once(); + + $this->service->register(); + } +}