From f8f04e401920b65946bc54790eb13e3f757f207e Mon Sep 17 00:00:00 2001 From: adrianmiu Date: Sun, 19 Nov 2023 19:05:45 +0200 Subject: [PATCH] Upgraded to PHP 8.1. Replaced PHPUnit with Pest. Introduced Rector --- .github/ISSUE_TEMPLATE/Bug.md | 24 + .github/ISSUE_TEMPLATE/Feature_Request.md | 21 + .github/ISSUE_TEMPLATE/Question.md | 8 + .github/workflows/ci.yml | 51 ++ .github/workflows/docs.yml | 28 + .php_cs.cache | 1 + composer.json | 36 +- phpstan.neon | 5 + phpunit.xml | 18 + src/Container/ContainerInterface.php | 17 +- src/Container/Local.php | 50 +- src/Handler.php | 142 ++--- src/HandlerAggregate.php | 34 +- src/Result/Collection.php | 18 +- src/Result/File.php | 34 +- src/Result/ResultInterface.php | 10 +- src/UploadHandlerInterface.php | 10 +- src/Util/Helper.php | 57 +- tests/.phpunit.result.cache | 1 + tests/Pest.php | 45 ++ tests/TestCase.php | 13 + tests/src/Container/LocalTest.php | 109 ++-- tests/src/HandlerAggregateTest.php | 152 +++-- tests/src/HandlerTest.php | 674 ++++++++++------------ 24 files changed, 830 insertions(+), 728 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/Bug.md create mode 100644 .github/ISSUE_TEMPLATE/Feature_Request.md create mode 100644 .github/ISSUE_TEMPLATE/Question.md create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/docs.yml create mode 100644 .php_cs.cache create mode 100644 phpstan.neon create mode 100644 phpunit.xml create mode 100644 tests/.phpunit.result.cache create mode 100644 tests/Pest.php create mode 100644 tests/TestCase.php diff --git a/.github/ISSUE_TEMPLATE/Bug.md b/.github/ISSUE_TEMPLATE/Bug.md new file mode 100644 index 0000000..f563f05 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Bug.md @@ -0,0 +1,24 @@ +--- +name: 🐛 Bug +about: Did you encounter a bug? +--- + +### Bug Report + + + +| Q | A +|------------ | ------ +| BC Break | yes/no +| Version | x.y.z + +#### Summary + + + +#### How to reproduce + + diff --git a/.github/ISSUE_TEMPLATE/Feature_Request.md b/.github/ISSUE_TEMPLATE/Feature_Request.md new file mode 100644 index 0000000..de0f669 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Feature_Request.md @@ -0,0 +1,21 @@ +--- +name: 🎉 Feature Request +about: Do you have a new feature in mind? +--- + +### Feature Request + + + +| Q | A +|------------ | ------ +| New Feature | yes/no +| BC Break | yes/no + +#### Scenario / Use-case + + + +#### Summary + + diff --git a/.github/ISSUE_TEMPLATE/Question.md b/.github/ISSUE_TEMPLATE/Question.md new file mode 100644 index 0000000..fa8e763 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/Question.md @@ -0,0 +1,8 @@ +--- +name: ❓ Question +about: Are you unsure about something? +--- + +### Question + + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..3fa51b5 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,51 @@ +name: CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + strategy: + matrix: + php: ['8.1', '8.2', '8.3'] + include: + - php: '8.1' + send-to-scrutinizer: 'yes' + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup PHP with fail-fast + uses: shivammathur/setup-php@v2 + with: + send-to-scrutinizer: 'no' + phpunit-flags: '--no-coverage' + php-version: ${{ matrix.php }} + coverage: xdebug + env: + fail-fast: true + - name: Validate composer.json and composer.lock + run: composer validate + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v2 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Install dependencies + if: steps.composer-cache.outputs.cache-hit != 'true' + run: composer install --prefer-dist --no-progress --no-suggest + + - name: Run test suite + run: | + mkdir -p build/logs + composer run stan + composer run test diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..8a8a771 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,28 @@ +name: Publish docs + +on: + push: + tags: + - '*.*.*' +jobs: + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + + - name: Build docs + run: | + curl -OS https://couscous.io/couscous.phar + php couscous.phar generate --target=build/docs/ ./docs + + - name: FTP Deployer + uses: sand4rt/ftp-deployer@v1.1 + with: + host: ${{ secrets.DOCS_FTP_HOST }} + username: ${{ secrets.DOCS_FTP_USER }} + password: ${{ secrets.DOCS_FTP_PASSWORD }} + remote_folder: upload + # The local folder location + local_folder: build/docs/ + # Remove existing files inside FTP remote folder + cleanup: false # optional diff --git a/.php_cs.cache b/.php_cs.cache new file mode 100644 index 0000000..e3481e0 --- /dev/null +++ b/.php_cs.cache @@ -0,0 +1 @@ +{"php":"7.2.4","version":"2.16.1","indent":" ","lineEnding":"\n","rules":{"blank_line_after_namespace":true,"braces":true,"class_definition":true,"constant_case":true,"elseif":true,"function_declaration":true,"indentation_type":true,"line_ending":true,"lowercase_keywords":true,"method_argument_space":{"on_multiline":"ensure_fully_multiline"},"no_break_comment":true,"no_closing_tag":true,"no_spaces_after_function_name":true,"no_spaces_inside_parenthesis":true,"no_trailing_whitespace":true,"no_trailing_whitespace_in_comment":true,"single_blank_line_at_eof":true,"single_class_element_per_statement":{"elements":["property"]},"single_import_per_statement":true,"single_line_after_imports":true,"switch_case_semicolon_to_colon":true,"switch_case_space":true,"visibility_required":true,"encoding":true,"full_opening_tag":true},"hashes":{"src\\Container\\ContainerInterface.php":1583588918,"src\\Container\\Local.php":71709479,"src\\Exception\\InvalidContainerException.php":1907413747,"src\\Exception\\InvalidResultException.php":466931389,"src\\Handler.php":534695839,"src\\HandlerAggregate.php":1499917949,"src\\Result\\Collection.php":2897468334,"src\\Result\\File.php":1434642790,"src\\UploadHandlerInterface.php":2299182802,"src\\Util\\Arr.php":1378696061,"src\\Result\\ResultInterface.php":844276353,"src\\Util\\Helper.php":3774982220}} \ No newline at end of file diff --git a/composer.json b/composer.json index 16db458..067920d 100755 --- a/composer.json +++ b/composer.json @@ -19,13 +19,16 @@ } ], "require": { - "php": ">=7.1", - "siriusphp/validation": "~3.0" + "php": ">=8.1", + "siriusphp/validation": "^4.0" }, "require-dev": { - "phpunit/phpunit": "~8.5", - "laminas/laminas-diactoros": "^2.2", - "symfony/http-foundation": "^4.4" + "laminas/laminas-diactoros": "^3.3", + "symfony/http-foundation": "^6.3", + "pestphp/pest": "^2.24", + "pestphp/pest-plugin-drift": "^2.5", + "symfony/mime": "^6.3", + "phpstan/phpstan": "^1.10" }, "suggest": { "league/flysystem": "To upload to different destinations, not just to the local file system", @@ -37,26 +40,25 @@ } }, "scripts": { - "cs": [ - "php phpcs.phar --standard=PSR2 ./src" - ], - "md": [ - "php phpmd.phar ./src xml phpmd.xml" - ], - "cbf": [ - "php phpcbf.phar ./src --standard=PSR2 -w" + "stan": [ + "php vendor/bin/phpstan analyse" ], "csfix": [ - "php php-cs-fixer.phar fix ./src --rules=@PSR2" + "tools/php-cs-fixer/vendor/bin/php-cs-fixer fix --standard=PSR-2 src" + ], + "test": [ + "php vendor/bin/pest" ], "build-docs": [ "php couscous.phar generate --target=build/docs/ ./docs" ], "docs": [ "cd docs && php ../couscous.phar preview" - ], - "test": [ - "vendor/bin/phpunit -c phpunit.xml" ] + }, + "config": { + "allow-plugins": { + "pestphp/pest-plugin": true + } } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..6615f46 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 8 + checkGenericClassInNonGenericObjectType: false + paths: + - src diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..7d0904f --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,18 @@ + + + + + ./tests + + + + + ./app + ./src + + + diff --git a/src/Container/ContainerInterface.php b/src/Container/ContainerInterface.php index ff689b7..3712aa7 100755 --- a/src/Container/ContainerInterface.php +++ b/src/Container/ContainerInterface.php @@ -9,14 +9,12 @@ interface ContainerInterface /** * Check if the container is writable */ - public function isWritable(); + public function isWritable(): bool; /** * This will check if a file is in the container - * - * @param string $file */ - public function has($file); + public function has(string $file): bool; /** * Saves the $content string as a file @@ -24,20 +22,15 @@ public function has($file); * @param string $file * @param string $content */ - public function save($file, $content); + public function save(string $file, string $content): bool; /** * Delete the file from the container - * - * @param string $file */ - public function delete($file); + public function delete(string $file): bool; /** * Moves a temporary uploaded file to a destination in the container - * - * @param string $localFile local path - * @param string $destination */ - public function moveUploadedFile($localFile, $destination); + public function moveUploadedFile(string $localFile, string $destination): bool; } diff --git a/src/Container/Local.php b/src/Container/Local.php index b282ce5..ab83b23 100755 --- a/src/Container/Local.php +++ b/src/Container/Local.php @@ -5,24 +5,25 @@ class Local implements ContainerInterface { - protected $baseDirectory; + protected string $baseDirectory; - public function __construct($baseDirectory) + public function __construct(string $baseDirectory) { $this->baseDirectory = $this->normalizePath($baseDirectory) . DIRECTORY_SEPARATOR; $this->ensureDirectory($this->baseDirectory); } - protected function normalizePath($path) + protected function normalizePath(string $path): string { $path = dirname(rtrim($path, '\\/') . DIRECTORY_SEPARATOR . 'xxx'); return rtrim($path, DIRECTORY_SEPARATOR); } - protected function ensureDirectory($directory):bool + protected function ensureDirectory(string $directory): bool { - if (!file_exists($directory)) { + if ( ! is_dir($directory)) { + echo $directory, PHP_EOL; mkdir($directory, 0755, true); } @@ -32,7 +33,7 @@ protected function ensureDirectory($directory):bool /** * Check if the container is writable */ - public function isWritable():bool + public function isWritable(): bool { return is_writable($this->baseDirectory); } @@ -40,39 +41,27 @@ public function isWritable():bool /** * This will check if a file is in the container * - * @param string $file + * @param string $file + * * @return bool */ - public function has($file):bool + public function has(string $file): bool { return $file && file_exists($this->baseDirectory . $file); } - /** - * Saves the $content string as a file - * - * @param string $file - * @param string $content - * @return bool - */ - public function save($file, $content):bool + public function save(string $file, string $content): bool { $file = $this->normalizePath($file); - $dir = dirname($this->baseDirectory . $file); + $dir = dirname($this->baseDirectory . $file); if ($this->ensureDirectory($dir)) { - return (bool) file_put_contents($this->baseDirectory . $file, $content); + return (bool)file_put_contents($this->baseDirectory . $file, $content); } return false; } - /** - * Delete the file from the container - * - * @param string $file - * @return bool - */ - public function delete($file):bool + public function delete(string $file): bool { $file = $this->normalizePath($file); if (file_exists($this->baseDirectory . $file)) { @@ -82,14 +71,7 @@ public function delete($file):bool return true; } - /** - * Moves a temporary uploaded file to a destination in the container - * - * @param string $localFile local path - * @param string $destination - * @return bool - */ - public function moveUploadedFile($localFile, $destination):bool + public function moveUploadedFile(string $localFile, string $destination): bool { $dir = dirname($this->baseDirectory . $destination); if (file_exists($localFile) && $this->ensureDirectory($dir)) { @@ -97,9 +79,11 @@ public function moveUploadedFile($localFile, $destination):bool // rename() would be good but this is better because $localFile may become 'unwritable' $result = copy($localFile, $this->baseDirectory . $destination); @unlink($localFile); + return $result; } } + return false; } } diff --git a/src/Handler.php b/src/Handler.php index dd5ef6d..a5fbc05 100755 --- a/src/Handler.php +++ b/src/Handler.php @@ -1,5 +1,6 @@ $options + * * @throws InvalidContainerException */ - public function __construct($directoryOrContainer, $options = [], ValueValidator $validator = null) + public function __construct(mixed $directoryOrContainer, array $options = [], ValueValidator $validator = null) { $container = $directoryOrContainer; if (is_string($directoryOrContainer)) { $container = new LocalContainer($directoryOrContainer); } - if (!$container instanceof ContainerInterface) { + if ( ! $container instanceof ContainerInterface) { throw new InvalidContainerException('Destination container for uploaded files is not valid'); } $this->container = $container; // create the validator - if (!$validator) { + if ( ! $validator) { $validator = new ValueValidator(); } $this->validator = $validator; // set options $availableOptions = [ - static::OPTION_PREFIX => 'setPrefix', - static::OPTION_OVERWRITE => 'setOverwrite', + static::OPTION_PREFIX => 'setPrefix', + static::OPTION_OVERWRITE => 'setOverwrite', static::OPTION_AUTOCONFIRM => 'setAutoconfirm' ]; foreach ($availableOptions as $key => $method) { @@ -100,16 +90,9 @@ public function __construct($directoryOrContainer, $options = [], ValueValidator } } - /** - * Enable/disable upload overwrite - * - * @param bool $overwrite - * - * @return Handler - */ - public function setOverwrite($overwrite) + public function setOverwrite(bool $overwrite): self { - $this->overwrite = (bool) $overwrite; + $this->overwrite = (bool)$overwrite; return $this; } @@ -120,11 +103,9 @@ public function setOverwrite($overwrite) * - a string to be used as prefix * - a function that returns a string * - * @param string|callable $prefix - * - * @return Handler + * @param string|callable $prefix */ - public function setPrefix($prefix) + public function setPrefix(mixed $prefix): self { $this->prefix = $prefix; @@ -134,46 +115,34 @@ public function setPrefix($prefix) /** * Enable/disable upload autoconfirmation * Autoconfirmation does not require calling `confirm()` - * - * @param boolean $autoconfirm - * - * @return Handler */ - public function setAutoconfirm($autoconfirm) + public function setAutoconfirm(bool $autoconfirm): self { - $this->autoconfirm = (bool) $autoconfirm; + $this->autoconfirm = (bool)$autoconfirm; return $this; } - + /** * Set the sanitizer function for cleaning up the file names - * - * @param callable $callback - * - * @return Handler * @throws \InvalidArgumentException */ - public function setSanitizerCallback($callback) + public function setSanitizerCallback(callable|\Closure $callback): self { - if (!is_callable($callback)) { + if ( ! is_callable($callback)) { throw new \InvalidArgumentException('The $callback parameter is not a valid callable entity'); } $this->sanitizerCallback = $callback; + return $this; } /** * Add validation rule (extension|size|width|height|ratio) * - * @param string $name - * @param mixed $options - * @param string $errorMessageTemplate - * @param string $label - * - * @return Handler + * @param array $options */ - public function addRule($name, $options = null, $errorMessageTemplate = null, $label = null):Handler + public function addRule(string $name, array $options = [], string $errorMessageTemplate = null, string $label = null): self { $predefinedRules = [ static::RULE_EXTENSION, @@ -187,18 +156,18 @@ public function addRule($name, $options = null, $errorMessageTemplate = null, $l if (in_array($name, $predefinedRules)) { $name = 'upload' . $name; } - $this->validator->add($name, $options, $errorMessageTemplate, $label); + if ($this->validator) { + $this->validator->add($name, $options, $errorMessageTemplate, $label); + } return $this; } /** * Processes a file upload and returns an upload result file/collection - * - * @param array $files * @return Result\Collection|Result\File|ResultInterface */ - public function process($files = []):ResultInterface + public function process(mixed $files): ResultInterface { $files = Helper::normalizeFiles($files); @@ -219,16 +188,17 @@ public function process($files = []):ResultInterface * - validates the file * - if valid, moves the file to the container * - * @param array $file - * @return array + * @param array $file + * + * @return array */ - protected function processSingleFile(array $file):array + protected function processSingleFile(array $file): array { // store it for future reference $file['original_name'] = $file['name']; // sanitize the file name - $file['name'] = $this->sanitizeFileName($file['name'], $file); + $file['name'] = $this->sanitizeFileName($file['name']); $file = $this->validateFile($file); // if there are messages the file is not valid @@ -239,13 +209,13 @@ protected function processSingleFile(array $file):array // add the prefix $prefix = ''; if (is_callable($this->prefix)) { - $prefix = (string) call_user_func($this->prefix, $file['name']); + $prefix = (string)call_user_func($this->prefix, $file['name']); } elseif (is_string($this->prefix)) { - $prefix = (string) $this->prefix; + $prefix = (string)$this->prefix; } // if overwrite is not allowed, check if the file is already in the container - if (!$this->overwrite) { + if ( ! $this->overwrite) { if ($this->container->has($prefix . $file['name'])) { // add the timestamp to ensure the file is unique // method is not bulletproof but it's pretty safe @@ -254,7 +224,7 @@ protected function processSingleFile(array $file):array } // attempt to move the uploaded file into the container - if (!$this->container->moveUploadedFile($file['tmp_name'], $prefix . $file['name'])) { + if ( ! $this->container->moveUploadedFile($file['tmp_name'], $prefix . $file['name'])) { $file['name'] = false; return $file; @@ -262,8 +232,8 @@ protected function processSingleFile(array $file):array $file['name'] = $prefix . $file['name']; // create the lock file if autoconfirm is disabled - if (!$this->autoconfirm) { - $this->container->save($file['name'] . '.lock', (string) time()); + if ( ! $this->autoconfirm) { + $this->container->save($file['name'] . '.lock', (string)time()); } return $file; @@ -272,12 +242,13 @@ protected function processSingleFile(array $file):array /** * Validates a file according to the rules configured on the handler * - * @param $file - * @return mixed + * @param array $file + * + * @return array */ - protected function validateFile($file) + protected function validateFile(array $file): array { - if (!$this->validator->validate($file)) { + if ($this->validator && ! $this->validator->validate($file)) { $file['messages'] = $this->validator->getMessages(); } @@ -287,16 +258,13 @@ protected function validateFile($file) /** * Sanitize the name of the uploaded file by stripping away bad characters * and replacing "invalid" characters with underscore _ - * - * @param string $name - * @param array $file - * @return string */ - protected function sanitizeFileName($name, $file) + protected function sanitizeFileName(string $name): string { - if ($this->sanitizerCallback) { - return call_user_func($this->sanitizerCallback, $name, $file); + if (is_callable($this->sanitizerCallback)) { + return call_user_func($this->sanitizerCallback, $name); } - return preg_replace('/[^A-Za-z0-9\.]+/', '_', $name); + + return preg_replace('/[^A-Za-z0-9\.]+/', '_', $name); // @phpstan-ignore-line } } diff --git a/src/HandlerAggregate.php b/src/HandlerAggregate.php index c2b99a9..edfbe7a 100755 --- a/src/HandlerAggregate.php +++ b/src/HandlerAggregate.php @@ -1,23 +1,22 @@ $handlers + */ + protected array $handlers = []; /** * Adds a handler on the aggregate - * - * @param string $selector - * @param Handler $handler - * @return $this */ - public function addHandler($selector, Handler $handler) + public function addHandler(string $selector, Handler $handler): self { $this->handlers[$selector] = $handler; @@ -25,18 +24,18 @@ public function addHandler($selector, Handler $handler) } /** - * Processes - * @param $files - * @return Collection + * @param array $files + * + * @return Result\Collection */ - public function process($files) + public function process(mixed $files): mixed { $result = new Collection(); foreach ($this->handlers as $selector => $handler) { /* @var $handler Handler */ $selectedFiles = Arr::getBySelector($files, $selector); - if (!$selectedFiles || !is_array($selectedFiles) || empty($selectedFiles)) { + if (empty($selectedFiles)) { continue; } @@ -50,15 +49,8 @@ public function process($files) return $result; } - /** - * Retrieve an external iterator - * - * @link http://php.net/manual/en/iteratoraggregate.getiterator.php - * @return \Traversable An instance of an object implementing Iterator or - * Traversable - */ - public function getIterator() + public function getIterator(): \Traversable { - return $this->handlers; + return new \ArrayIterator($this->handlers); } } diff --git a/src/Result/Collection.php b/src/Result/Collection.php index 28424d0..7c0c20d 100755 --- a/src/Result/Collection.php +++ b/src/Result/Collection.php @@ -7,10 +7,13 @@ class Collection extends \ArrayIterator implements ResultInterface { - public function __construct($files = [], ContainerInterface $container = null) + /** + * @param array $files + */ + public function __construct(array $files = [], ContainerInterface $container = null) { $filesArray = []; - if (is_array($files) && !empty($files)) { + if ($container && ! empty($files)) { foreach ($files as $key => $file) { $filesArray[$key] = new File($file, $container); } @@ -18,7 +21,7 @@ public function __construct($files = [], ContainerInterface $container = null) parent::__construct($filesArray); } - public function clear() + public function clear(): void { foreach ($this as $file) { /* @var $file \Sirius\Upload\Result\File */ @@ -26,7 +29,7 @@ public function clear() } } - public function confirm() + public function confirm(): void { foreach ($this as $file) { /* @var $file \Sirius\Upload\Result\File */ @@ -34,7 +37,7 @@ public function confirm() } } - public function isValid():bool + public function isValid(): bool { foreach ($this->getMessages() as $messages) { if ($messages) { @@ -45,7 +48,10 @@ public function isValid():bool return true; } - public function getMessages():array + /** + * @return array> + */ + public function getMessages(): array { $messages = []; foreach ($this as $key => $file) { diff --git a/src/Result/File.php b/src/Result/File.php index 9ac4c29..1c5bbc2 100755 --- a/src/Result/File.php +++ b/src/Result/File.php @@ -5,6 +5,9 @@ use Sirius\Upload\Container\ContainerInterface; +/** + * @property string $name + */ class File implements ResultInterface { @@ -15,23 +18,22 @@ class File implements ResultInterface * - tmp_name * etc * - * @var array + * @var array */ - protected $file; + protected array $file; /** * The container to which this file belongs to - * @var ContainerInterface */ - protected $container; + protected ContainerInterface $container; /** - * @param $file + * @param array $file * @param ContainerInterface $container */ - public function __construct($file, ContainerInterface $container) + public function __construct(array $file, ContainerInterface $container) { - $this->file = $file; + $this->file = $file; $this->container = $container; } @@ -40,7 +42,7 @@ public function __construct($file, ContainerInterface $container) * * @return bool */ - public function isValid():bool + public function isValid(): bool { return $this->file['name'] && count($this->getMessages()) === 0; } @@ -48,9 +50,9 @@ public function isValid():bool /** * Returns the validation error messages * - * @return array + * @return array */ - public function getMessages():array + public function getMessages(): array { if (isset($this->file['messages'])) { return $this->file['messages']; @@ -63,7 +65,7 @@ public function getMessages():array * The file that was saved during process() and has a .lock file attached * will be cleared, in case the form processing fails */ - public function clear() + public function clear(): void { $this->container->delete($this->name); $this->container->delete($this->name . '.lock'); @@ -74,18 +76,12 @@ public function clear() * Remove the .lock file attached to the file that was saved during process() * This should happen if the form fails validation/processing */ - public function confirm() + public function confirm(): void { $this->container->delete($this->name . '.lock'); } - /** - * File attribute getter - * - * @param $name - * @return mixed - */ - public function __get($name) + public function __get(string $name): mixed { if (isset($this->file[$name])) { return $this->file[$name]; diff --git a/src/Result/ResultInterface.php b/src/Result/ResultInterface.php index 97a6f05..a93fe9a 100644 --- a/src/Result/ResultInterface.php +++ b/src/Result/ResultInterface.php @@ -10,24 +10,24 @@ interface ResultInterface * * @return bool */ - public function isValid():bool; + public function isValid(): bool; /** * Returns the validation error messages * - * @return array + * @return array */ - public function getMessages():array; + public function getMessages(): array; /** * The file that was saved during process() and has a .lock file attached * will be cleared, in case the form processing fails */ - public function clear(); + public function clear(): void; /** * Remove the .lock file attached to the file that was saved during process() * This should happen if the form fails validation/processing */ - public function confirm(); + public function confirm(): void; } diff --git a/src/UploadHandlerInterface.php b/src/UploadHandlerInterface.php index e3cd62e..0aa813f 100755 --- a/src/UploadHandlerInterface.php +++ b/src/UploadHandlerInterface.php @@ -3,6 +3,10 @@ namespace Sirius\Upload; +use Psr\Http\Message\UploadedFileInterface; +use Sirius\Upload\Result\ResultInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; + interface UploadHandlerInterface { @@ -14,7 +18,9 @@ interface UploadHandlerInterface * be added by the container save() method so, in case the form is * not validated, the uploaded file will be removed. * - * @param array $files + * @param array|UploadedFileInterface|UploadedFile $files + * + * @return Result\Collection|Result\File|ResultInterface */ - public function process($files = []); + public function process(mixed $files): mixed; } diff --git a/src/Util/Helper.php b/src/Util/Helper.php index 5bf7543..def1dbb 100644 --- a/src/Util/Helper.php +++ b/src/Util/Helper.php @@ -3,6 +3,9 @@ namespace Sirius\Upload\Util; +use Psr\Http\Message\UploadedFileInterface; +use Symfony\Component\HttpFoundation\File\UploadedFile; + class Helper { const PSR7_UPLOADED_FILE_CLASS = '\Psr\Http\Message\UploadedFileInterface'; @@ -10,15 +13,14 @@ class Helper /** * We do not type-hint or import the class since it may not be used - * - * @param \Psr\Http\Message\UploadedFileInterface $file - * - * @return array + * @return array */ - public static function extractFromUploadedFileInterface($file) + public static function extractFromUploadedFileInterface(UploadedFileInterface $file): array { - /** @var \Psr\Http\Message\UploadedFileInterface $file */ $tempName = tempnam(sys_get_temp_dir(), 'srsupld_'); + if (!$tempName) { + throw new \RuntimeException('Could not create temporary directory'); + } $file->moveTo($tempName); $result = [ 'name' => $file->getClientFilename(), @@ -31,10 +33,11 @@ public static function extractFromUploadedFileInterface($file) return $result; } - public static function extractFromSymfonyFile($file) + /** + * @return array + */ + public static function extractFromSymfonyFile(UploadedFile $file): array { - /** @var \Symfony\Component\HttpFoundation\File\UploadedFile $file */ - $result = [ 'name' => $file->getClientOriginalName(), 'tmp_name' => $file->getPathname(), @@ -46,16 +49,21 @@ public static function extractFromSymfonyFile($file) return $result; } + /** + * @param array $files + * + * @return array + */ public static function remapFilesArray(array $files): array { $result = []; foreach (array_keys($files['name']) as $k) { $result[$k] = [ 'name' => $files['name'][$k], - 'type' => @$files['type'][$k], - 'size' => @$files['size'][$k], - 'error' => @$files['error'][$k], - 'tmp_name' => $files['tmp_name'][$k] + 'type' => $files['type'][$k] ?? null, + 'size' => $files['size'][$k] ?? null, + 'error' => $files['error'][$k] ?? null, + 'tmp_name' => $files['tmp_name'][$k] ?? null ]; } @@ -69,11 +77,11 @@ public static function remapFilesArray(array $files): array * multiple files are uploaded under the same name * @see http://www.php.net/manual/en/features.file-upload.php * - * @param array|Symfony\Component\HttpFoundation\File\UploadedFile|\Psr\Http\Message\UploadedFileInterface $files + * @param array|UploadedFile|UploadedFileInterface $files * - * @return array + * @return array */ - public static function normalizeFiles($files): array + public static function normalizeFiles(mixed $files): array { if (empty($files)) { return []; @@ -90,7 +98,8 @@ public static function normalizeFiles($files): array // If caller passed in an array of objects (Either PSR7 or Symfony) if (is_array($files) && is_object(reset($files))) { - if (is_subclass_of(reset($files), self::PSR7_UPLOADED_FILE_CLASS)) { + $firstFile = reset($files); + if ($firstFile instanceof UploadedFileInterface) { $result = []; foreach ($files as $file) { $result[] = self::extractFromUploadedFileInterface($file); @@ -99,7 +108,7 @@ public static function normalizeFiles($files): array return $result; } - if (get_class(reset($files)) == self::SYMFONY_UPLOADED_FILE_CLASS) { + if ($firstFile instanceof UploadedFile) { $result = []; foreach ($files as $file) { $result[] = self::extractFromSymfonyFile($file); @@ -112,23 +121,23 @@ public static function normalizeFiles($files): array // The caller passed $_FILES['some_field_name'] if (isset($files['name'])) { // we have a single file - if (! is_array($files['name'])) { + if ( ! is_array($files['name'])) { return [$files]; } else { // we have list of files, which PHP messes up - return Helper::remapFilesArray($files); + return Helper::remapFilesArray($files); // @phpstan-ignore-line } } else { // The caller passed $_FILES - $keys = array_keys($files); + $keys = array_keys($files); // @phpstan-ignore-line if (isset($keys[0]) && isset($files[$keys[0]]['name'])) { - if (! is_array($files[$keys[0]]['name'])) { + if ( ! is_array($files[$keys[0]]['name'])) { // $files is in the correct format already, even in the // case it contains a single element. - return $files; + return $files; //@phpstan-ignore-line } else { // we have list of files, which PHP messes up - return Helper::remapFilesArray($files[$keys[0]]); + return Helper::remapFilesArray($files[$keys[0]]); // @phpstan-ignore-line } } } diff --git a/tests/.phpunit.result.cache b/tests/.phpunit.result.cache new file mode 100644 index 0000000..2432fcc --- /dev/null +++ b/tests/.phpunit.result.cache @@ -0,0 +1 @@ +C:37:"PHPUnit\Runner\DefaultTestResultCache":2714:{a:2:{s:7:"defects";a:16:{s:64:"Sirius\Upload\HandlerTest::testExceptionTrwonForInvalidContainer";i:4;s:76:"Sirius\Upload\HandlerTest::testExceptionThrownForInvalidSanitizationCallback";i:4;s:47:"Sirius\Upload\HandlerAggregateTest::testProcess";i:4;s:48:"Sirius\Upload\HandlerTest::testPsr7UploadedFiles";i:3;s:51:"Sirius\Upload\HandlerTest::testSymfonyUploadedFiles";i:4;s:52:"Sirius\Upload\HandlerTest::testBasicUploadWithPrefix";i:4;s:46:"Sirius\Upload\HandlerTest::testUploadOverwrite";i:4;s:48:"Sirius\Upload\HandlerTest::testUploadAutoconfirm";i:4;s:55:"Sirius\Upload\HandlerTest::testSingleUploadConfirmation";i:4;s:51:"Sirius\Upload\HandlerTest::testSingleUploadClearing";i:4;s:42:"Sirius\Upload\HandlerTest::testMultiUpload";i:4;s:50:"Sirius\Upload\HandlerTest::testOriginalMultiUpload";i:4;s:46:"Sirius\Upload\HandlerTest::testWrongFilesArray";i:4;s:53:"Sirius\Upload\HandlerTest::testSingleUploadValidation";i:4;s:52:"Sirius\Upload\HandlerTest::testMultiUploadValidation";i:4;s:57:"Sirius\Upload\HandlerTest::testCustomSanitizationCallback";i:4;}s:5:"times";a:24:{s:43:"Sirius\Upload\Container\LocalTest::testSave";d:0.03;s:45:"Sirius\Upload\Container\LocalTest::testDelete";d:0.011;s:59:"Sirius\Upload\Container\LocalTest::testDeleteInexistingFile";d:0.007;s:55:"Sirius\Upload\Container\LocalTest::testMoveUploadedFile";d:0.014;s:62:"Sirius\Upload\Container\LocalTest::testMoveMissingUploadedFile";d:0.007;s:47:"Sirius\Upload\HandlerAggregateTest::testProcess";d:0.1;s:48:"Sirius\Upload\HandlerAggregateTest::testIterator";d:0.024;s:52:"Sirius\Upload\HandlerTest::testBasicUploadWithPrefix";d:0.019;s:46:"Sirius\Upload\HandlerTest::testUploadOverwrite";d:0.042;s:48:"Sirius\Upload\HandlerTest::testUploadAutoconfirm";d:0.018;s:55:"Sirius\Upload\HandlerTest::testSingleUploadConfirmation";d:0.024;s:51:"Sirius\Upload\HandlerTest::testSingleUploadClearing";d:0.024;s:42:"Sirius\Upload\HandlerTest::testMultiUpload";d:0.038;s:50:"Sirius\Upload\HandlerTest::testOriginalMultiUpload";d:0.032;s:46:"Sirius\Upload\HandlerTest::testWrongFilesArray";d:0.012;s:64:"Sirius\Upload\HandlerTest::testExceptionTrwonForInvalidContainer";d:0.013;s:53:"Sirius\Upload\HandlerTest::testSingleUploadValidation";d:0.017;s:52:"Sirius\Upload\HandlerTest::testMultiUploadValidation";d:0.018;s:57:"Sirius\Upload\HandlerTest::testCustomSanitizationCallback";d:0.022;s:76:"Sirius\Upload\HandlerTest::testExceptionThrownForInvalidSanitizationCallback";d:0.012;s:48:"Sirius\Upload\HandlerTest::testPsr7UploadedFiles";d:0.045;s:51:"Sirius\Upload\HandlerTest::testSymfonyUploadedFiles";d:0.07;s:56:"Sirius\Upload\HandlerTest::testSingleSymfonyUploadedFile";d:0.03;s:53:"Sirius\Upload\HandlerTest::testSinglePsr7UploadedFile";d:0.026;}}} \ No newline at end of file diff --git a/tests/Pest.php b/tests/Pest.php new file mode 100644 index 0000000..21e1252 --- /dev/null +++ b/tests/Pest.php @@ -0,0 +1,45 @@ +in('src'); + +/* +|-------------------------------------------------------------------------- +| Expectations +|-------------------------------------------------------------------------- +| +| When you're writing tests, you often need to check that values meet certain conditions. The +| "expect()" function gives you access to a set of "expectations" methods that you can use +| to assert different things. Of course, you may extend the Expectation API at any time. +| +*/ + +expect()->extend('toBeOne', function () { + return $this->toBe(1); +}); + +/* +|-------------------------------------------------------------------------- +| Functions +|-------------------------------------------------------------------------- +| +| While Pest is very powerful out-of-the-box, you may have some testing code specific to your +| project that you don't want to repeat in every file. Here you can also expose helpers as +| global functions to help you to reduce the number of lines of code in your test files. +| +*/ + +function something() +{ + // .. +} diff --git a/tests/TestCase.php b/tests/TestCase.php new file mode 100644 index 0000000..d9889fe --- /dev/null +++ b/tests/TestCase.php @@ -0,0 +1,13 @@ +tmpFolder . '/' . $name, $content); + } +} diff --git a/tests/src/Container/LocalTest.php b/tests/src/Container/LocalTest.php index 2d43dca..5ad6835 100755 --- a/tests/src/Container/LocalTest.php +++ b/tests/src/Container/LocalTest.php @@ -1,78 +1,65 @@ rrmdir($dir . "/" . $object); - } else { - unlink($dir . "/" . $object); - } + if (is_dir($dir)) { + $objects = scandir($dir); + foreach ($objects as $object) { + if ($object != "." && $object != "..") { + if (filetype($dir . "/" . $object) == "dir") { + rrmdir($dir . "/" . $object); + } else { + unlink($dir . "/" . $object); } } - reset($objects); - rmdir($dir); } + reset($objects); + rmdir($dir); } +} - protected function setUp(): void - { - $this->dir = realpath(__DIR__ . '/../../') . '/fixture/'; - $this->container = new LocalContainer($this->dir); - } +beforeEach(function () { + $this->dir = realpath(__DIR__ . '/../../') . '/fixture/'; + $this->container = new LocalContainer($this->dir); +}); - protected function tearDown(): void - { - $this->rrmdir($this->dir); - } +afterEach(function () { + rrmdir($this->dir); +}); - function testSave() - { - $file = 'subdir/test.txt'; - $this->assertTrue($this->container->save($file, 'cool')); - $this->assertTrue(file_exists($this->dir . $file)); - $this->assertTrue($this->container->has($file)); - $this->assertEquals('cool', file_get_contents($this->dir . $file)); - } +test('save', function () { + $file = 'subdir/test.txt'; + expect($this->container->save($file, 'cool'))->toBeTrue(); + expect(file_exists($this->dir . $file))->toBeTrue(); + expect($this->container->has($file))->toBeTrue(); + expect(file_get_contents($this->dir . $file))->toEqual('cool'); +}); - function testDelete() - { - $file = 'subdir/test.txt'; - $this->container->save($file, 'cool'); - $this->assertTrue(file_exists($this->dir . $file)); - $this->assertTrue($this->container->delete($file)); - $this->assertFalse(file_exists($this->dir . $file)); - } +test('delete', function () { + $file = 'subdir/test.txt'; + $this->container->save($file, 'cool'); + expect(file_exists($this->dir . $file))->toBeTrue(); + expect($this->container->delete($file))->toBeTrue(); + expect(file_exists($this->dir . $file))->toBeFalse(); +}); - function testDeleteInexistingFile() - { - $file = 'subdir/test.txt'; - $this->assertTrue($this->container->delete($file)); - } +test('delete inexisting file', function () { + $file = 'subdir/test.txt'; + expect($this->container->delete($file))->toBeTrue(); +}); - function testMoveUploadedFile() - { - $file = 'test.txt'; - $file2 = 'sub/test.txt'; - $this->container->save($file, 'cool'); - $this->assertTrue($this->container->moveUploadedFile($this->dir . $file, $file2)); - $this->assertEquals('cool', file_get_contents($this->dir . $file2)); - } +test('move uploaded file', function () { + $file = 'test.txt'; + $file2 = 'sub/test.txt'; + $this->container->save($file, 'cool'); + expect($this->container->moveUploadedFile($this->dir . $file, $file2))->toBeTrue(); + expect(file_get_contents($this->dir . $file2))->toEqual('cool'); +}); - function testMoveMissingUploadedFile() - { - $file = 'subdir/test.txt'; - $this->assertFalse($this->container->moveUploadedFile($this->dir . $file, $file)); - } - -} +test('move missing uploaded file', function () { + $file = 'subdir/test.txt'; + expect($this->container->moveUploadedFile($this->dir . $file, $file))->toBeFalse(); +}); diff --git a/tests/src/HandlerAggregateTest.php b/tests/src/HandlerAggregateTest.php index cc59b4a..da761b5 100755 --- a/tests/src/HandlerAggregateTest.php +++ b/tests/src/HandlerAggregateTest.php @@ -1,99 +1,81 @@ tmpFolder = realpath(__DIR__ . '/../fixitures/'); +beforeEach(function () { + $this->tmpFolder = realpath(__DIR__ . '/../fixitures/'); + if (!is_dir($this->tmpFolder)) { @mkdir($this->tmpFolder . '/container'); - $this->uploadFolder = realpath(__DIR__ . '/../fixitures/container/'); + } + $this->uploadFolder = realpath(__DIR__ . '/../fixitures/container/'); - $this->agg = new HandlerAggregate(); - $this->agg->addHandler( - 'user_picture', - new Handler( - $this->uploadFolder . '/user_picture', array( - Handler::OPTION_PREFIX => '', - Handler::OPTION_OVERWRITE => false, - Handler::OPTION_AUTOCONFIRM => false - ) + $this->agg = new HandlerAggregate(); + $this->agg->addHandler( + 'user_picture', + new Handler( + $this->uploadFolder . '/user_picture', array( + Handler::OPTION_PREFIX => '', + Handler::OPTION_OVERWRITE => false, + Handler::OPTION_AUTOCONFIRM => false ) - ); - $this->agg->addHandler( - 'resume', - new Handler( - $this->uploadFolder . '/resume', array( - Handler::OPTION_PREFIX => '', - Handler::OPTION_OVERWRITE => false, - Handler::OPTION_AUTOCONFIRM => false - ) + ) + ); + $this->agg->addHandler( + 'resume', + new Handler( + $this->uploadFolder . '/resume', array( + Handler::OPTION_PREFIX => '', + Handler::OPTION_OVERWRITE => false, + Handler::OPTION_AUTOCONFIRM => false ) - ); - $this->agg->addHandler( - 'portfolio[photos]', - new Handler( - $this->uploadFolder . '/photo', array( - Handler::OPTION_PREFIX => '', - Handler::OPTION_OVERWRITE => false, - Handler::OPTION_AUTOCONFIRM => false - ) + ) + ); + $this->agg->addHandler( + 'portfolio[photos]', + new Handler( + $this->uploadFolder . '/photo', array( + Handler::OPTION_PREFIX => '', + Handler::OPTION_OVERWRITE => false, + Handler::OPTION_AUTOCONFIRM => false ) - ); - } + ) + ); +}); - protected function tearDown(): void - { - $files = glob($this->uploadFolder . '/*'); // get all file names - foreach ($files as $file) { // iterate files - if (is_file($file)) { - unlink($file); - } // delete file - } +afterEach(function () { + $files = glob($this->uploadFolder . '/*'); + // get all file names + foreach ($files as $file) { // iterate files + if (is_file($file)) { + unlink($file); + } // delete file } +}); - function createTemporaryFile($name, $content = "") - { - file_put_contents($this->tmpFolder . '/' . $name, $content); - } +test('process', function () { + $this->createTemporaryFile('abc.tmp'); + $this->createTemporaryFile('def.tmp'); + $files = array( + 'user_picture' => array( + 'name' => 'pic.jpg', + 'tmp_name' => $this->tmpFolder . '/abc.tmp' + ), + 'resume' => array( + 'name' => 'resume.doc', + 'tmp_name' => $this->tmpFolder . '/def.tmp' + ) + ); + $result = $this->agg->process($files); - function testProcess() { - $this->createTemporaryFile('abc.tmp'); - $this->createTemporaryFile('def.tmp'); - $files = array( - 'user_picture' => array( - 'name' => 'pic.jpg', - 'tmp_name' => $this->tmpFolder . '/abc.tmp' - ), - 'resume' => array( - 'name' => 'resume.doc', - 'tmp_name' => $this->tmpFolder . '/def.tmp' - ) - ); - $result = $this->agg->process($files); - - $this->assertTrue(file_exists($this->uploadFolder . '/user_picture/' . $result['user_picture']->name)); - $this->assertTrue(file_exists($this->uploadFolder . '/user_picture/' . $result['user_picture']->name . '.lock')); + expect(file_exists($this->uploadFolder . '/user_picture/' . $result['user_picture']->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/user_picture/' . $result['user_picture']->name . '.lock'))->toBeTrue(); - $result->confirm(); - $this->assertFalse(file_exists($this->uploadFolder . '/user_picture/' . $result['user_picture']->name . '.lock')); - } + $result->confirm(); + expect(file_exists($this->uploadFolder . '/user_picture/' . $result['user_picture']->name . '.lock'))->toBeFalse(); +}); - function testIterator() { - $handlers = $this->agg->getIterator(); - $this->assertTrue($handlers['user_picture'] instanceof Handler); - } -} +test('iterator', function () { + $handlers = $this->agg->getIterator(); + expect($handlers['user_picture'] instanceof Handler)->toBeTrue(); +}); diff --git a/tests/src/HandlerTest.php b/tests/src/HandlerTest.php index 5be32bc..b88ba89 100755 --- a/tests/src/HandlerTest.php +++ b/tests/src/HandlerTest.php @@ -1,409 +1,371 @@ tmpFolder = realpath(__DIR__ . '/../fixitures/'); +beforeEach(function () { + $this->tmpFolder = realpath(__DIR__ . '/../fixitures/'); + if (!is_dir($this->tmpFolder)) { @mkdir($this->tmpFolder . '/container'); - $this->uploadFolder = realpath(__DIR__ . '/../fixitures/container/'); - $this->handler = new Handler( - $this->uploadFolder, array( - Handler::OPTION_PREFIX => '', - Handler::OPTION_OVERWRITE => false, - Handler::OPTION_AUTOCONFIRM => false - ) - ); - } - - protected function tearDown(): void - { - $files = glob($this->uploadFolder . '/*'); // get all file names - foreach ($files as $file) { // iterate files - if (is_file($file)) { - unlink($file); - } // delete file - } - } - - function createTemporaryFile($name, $content = "") - { - file_put_contents($this->tmpFolder . '/' . $name, $content); } - - function testBasicUploadWithPrefix() - { - $this->handler->setPrefix('subfolder/'); - $this->createTemporaryFile('abc.tmp'); - - $result = $this->handler->process( - array( - 'name' => 'abc.jpg', - 'tmp_name' => $this->tmpFolder . '/abc.tmp' - ) - ); - - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name)); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name . '.lock')); - // tearDown does not clean the subfolders - unlink($this->uploadFolder . '/' . $result->name); - unlink($this->uploadFolder . '/' . $result->name . '.lock'); + $this->uploadFolder = realpath(__DIR__ . '/../fixitures/container/'); + $this->handler = new Handler( + $this->uploadFolder, array( + Handler::OPTION_PREFIX => '', + Handler::OPTION_OVERWRITE => false, + Handler::OPTION_AUTOCONFIRM => false + ) + ); +}); + +afterEach(function () { + $files = glob($this->uploadFolder . '/*'); + // get all file names + foreach ($files as $file) { // iterate files + if (is_file($file)) { + unlink($file); + } // delete file } - - function testUploadOverwrite() - { - $this->createTemporaryFile('abc.tmp', 'first_file'); - - $result = $this->handler->process( +}); + +test('basic upload with prefix', function () { + $this->handler->setPrefix('subfolder/'); + $this->createTemporaryFile('abc.tmp'); + + $result = $this->handler->process( + array( + 'name' => 'abc.jpg', + 'tmp_name' => $this->tmpFolder . '/abc.tmp' + ) + ); + + expect(file_exists($this->uploadFolder . '/' . $result->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $result->name . '.lock'))->toBeTrue(); + + // tearDown does not clean the subfolders + unlink($this->uploadFolder . '/' . $result->name); + unlink($this->uploadFolder . '/' . $result->name . '.lock'); +}); + +test('upload overwrite', function () { + $this->createTemporaryFile('abc.tmp', 'first_file'); + + $result = $this->handler->process( + array( + 'name' => 'abc.jpg', + 'tmp_name' => $this->tmpFolder . '/abc.tmp' + ) + ); + + expect('first_file')->toEqual(file_get_contents($this->uploadFolder . '/abc.jpg')); + + // no overwrite, the first upload should be preserved + $this->handler->setOverwrite(false); + $this->createTemporaryFile('abc.tmp', 'second_file'); + + $result = $this->handler->process( + array( + 'name' => 'abc.jpg', + 'tmp_name' => $this->tmpFolder . '/abc.tmp' + ) + ); + + expect('first_file')->toEqual(file_get_contents($this->uploadFolder . '/abc.jpg')); + + // overwrite, the first uploaded file should be changed + $this->handler->setOverwrite(true); + $this->createTemporaryFile('abc.tmp', 'second_file'); + + $result = $this->handler->process( + array( + 'name' => 'abc.jpg', + 'tmp_name' => $this->tmpFolder . '/abc.tmp' + ) + ); + + expect('second_file')->toEqual(file_get_contents($this->uploadFolder . '/abc.jpg')); +}); + +test('upload autoconfirm', function () { + $this->handler->setAutoconfirm(true); + $this->createTemporaryFile('abc.tmp', 'first_file'); + + $result = $this->handler->process( + array( + 'name' => 'abc.jpg', + 'tmp_name' => $this->tmpFolder . '/abc.tmp' + ) + ); + + expect(file_exists($this->uploadFolder . '/' . $result->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $result->name . '.lock'))->toBeFalse(); +}); + +test('single upload confirmation', function () { + $this->createTemporaryFile('abc.tmp', 'first_file'); + + $result = $this->handler->process( + array( + 'name' => 'abc.jpg', + 'tmp_name' => $this->tmpFolder . '/abc.tmp' + ) + ); + + expect(file_exists($this->uploadFolder . '/' . $result->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $result->name . '.lock'))->toBeTrue(); + + $result->confirm(); + expect(file_exists($this->uploadFolder . '/' . $result->name . '.lock'))->toBeFalse(); +}); + +test('single upload clearing', function () { + $this->createTemporaryFile('abc.tmp', 'first_file'); + + $result = $this->handler->process( + array( + 'name' => 'abc.jpg', + 'tmp_name' => $this->tmpFolder . '/abc.tmp' + ) + ); + + expect(file_exists($this->uploadFolder . '/' . $result->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $result->name . '.lock'))->toBeTrue(); + + $fileName = $result->name; + $result->clear(); + + expect(file_exists($this->uploadFolder . '/' . $fileName))->toBeFalse(); + expect(file_exists($this->uploadFolder . '/' . $fileName . '.lock'))->toBeFalse(); +}); + +test('multi upload', function () { + $this->createTemporaryFile('abc.tmp', 'first_file'); + $this->createTemporaryFile('def.tmp', 'first_file'); + + // array is already properly formated + $result = $this->handler->process( + array( array( 'name' => 'abc.jpg', 'tmp_name' => $this->tmpFolder . '/abc.tmp' - ) - ); - - $this->assertEquals(file_get_contents($this->uploadFolder . '/abc.jpg'), 'first_file'); - - // no overwrite, the first upload should be preserved - $this->handler->setOverwrite(false); - $this->createTemporaryFile('abc.tmp', 'second_file'); - - $result = $this->handler->process( - array( - 'name' => 'abc.jpg', - 'tmp_name' => $this->tmpFolder . '/abc.tmp' - ) - ); - - $this->assertEquals(file_get_contents($this->uploadFolder . '/abc.jpg'), 'first_file'); - - // overwrite, the first uploaded file should be changed - $this->handler->setOverwrite(true); - $this->createTemporaryFile('abc.tmp', 'second_file'); - - $result = $this->handler->process( - array( - 'name' => 'abc.jpg', - 'tmp_name' => $this->tmpFolder . '/abc.tmp' - ) - ); - - $this->assertEquals(file_get_contents($this->uploadFolder . '/abc.jpg'), 'second_file'); - } - - function testUploadAutoconfirm() - { - $this->handler->setAutoconfirm(true); - $this->createTemporaryFile('abc.tmp', 'first_file'); - - $result = $this->handler->process( - array( - 'name' => 'abc.jpg', - 'tmp_name' => $this->tmpFolder . '/abc.tmp' - ) - ); - - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name)); - $this->assertFalse(file_exists($this->uploadFolder . '/' . $result->name . '.lock')); - } - - function testSingleUploadConfirmation() - { - $this->createTemporaryFile('abc.tmp', 'first_file'); - - $result = $this->handler->process( - array( - 'name' => 'abc.jpg', - 'tmp_name' => $this->tmpFolder . '/abc.tmp' - ) - ); - - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name)); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name . '.lock')); - - $result->confirm(); - $this->assertFalse(file_exists($this->uploadFolder . '/' . $result->name . '.lock')); - } - - - function testSingleUploadClearing() - { - $this->createTemporaryFile('abc.tmp', 'first_file'); - - $result = $this->handler->process( + ), array( - 'name' => 'abc.jpg', - 'tmp_name' => $this->tmpFolder . '/abc.tmp' + 'name' => 'def.jpg', + 'tmp_name' => $this->tmpFolder . '/def.tmp' ) - ); + ) + ); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name)); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name . '.lock')); + expect($result->isValid())->toBeTrue(); - $fileName = $result->name; - $result->clear(); - - $this->assertFalse(file_exists($this->uploadFolder . '/' . $fileName)); - $this->assertFalse(file_exists($this->uploadFolder . '/' . $fileName . '.lock')); + # var_dump(glob($this->uploadFolder . '/*')); + foreach ($result as $file) { + expect(file_exists($this->uploadFolder . '/' . $file->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $file->name . '.lock'))->toBeTrue(); } - function testMultiUpload() - { - $this->createTemporaryFile('abc.tmp', 'first_file'); - $this->createTemporaryFile('def.tmp', 'first_file'); - - // array is already properly formated - $result = $this->handler->process( - array( - array( - 'name' => 'abc.jpg', - 'tmp_name' => $this->tmpFolder . '/abc.tmp' - ), - array( - 'name' => 'def.jpg', - 'tmp_name' => $this->tmpFolder . '/def.tmp' - ) - ) - ); - - $this->assertTrue($result->isValid()); - -# var_dump(glob($this->uploadFolder . '/*')); - foreach ($result as $file) { - $this->assertTrue(file_exists($this->uploadFolder . '/' . $file->name)); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $file->name . '.lock')); - } - - // confirmation removes the .lock files - $result->confirm(); - foreach ($result as $file) { - $this->assertTrue(file_exists($this->uploadFolder . '/' . $file->name)); - $this->assertFalse(file_exists($this->uploadFolder . '/' . $file->name . '.lock')); - } - - // clearing removes the uploaded files and their locks (which are already removed) - $result->clear(); - foreach ($result as $file) { - $this->assertNull($file->name); - } - } - - function testOriginalMultiUpload() - { - $this->createTemporaryFile('abc.tmp', 'first_file'); - $this->createTemporaryFile('def.tmp', 'first_file'); - - // array is as provided by PHP - $result = $this->handler->process( - array( - 'name' => array( - 'abc.jpg', - 'def.jpg', - ), - 'tmp_name' => array( - $this->tmpFolder . '/abc.tmp', - $this->tmpFolder . '/def.tmp' - ), - ) - ); - - $this->assertEquals(count($result), 2); - foreach ($result as $file) { - $this->assertTrue(file_exists($this->uploadFolder . '/' . $file->name)); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $file->name . '.lock')); - } + // confirmation removes the .lock files + $result->confirm(); + foreach ($result as $file) { + expect(file_exists($this->uploadFolder . '/' . $file->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $file->name . '.lock'))->toBeFalse(); } - function testWrongFilesArray() - { - $result = $this->handler->process(array('names' => 'abc.jpg')); - $this->assertEquals(count($result), 0); + // clearing removes the uploaded files and their locks (which are already removed) + $result->clear(); + foreach ($result as $file) { + expect($file->name)->toBeNull(); } - - function testExceptionTrwonForInvalidContainer() - { - $this->expectException('Sirius\Upload\Exception\InvalidContainerException'); - - $handler = new Handler(new \stdClass()); +}); + +test('original multi upload', function () { + $this->createTemporaryFile('abc.tmp', 'first_file'); + $this->createTemporaryFile('def.tmp', 'first_file'); + + // array is as provided by PHP + $result = $this->handler->process( + array( + 'name' => array( + 'abc.jpg', + 'def.jpg', + ), + 'tmp_name' => array( + $this->tmpFolder . '/abc.tmp', + $this->tmpFolder . '/def.tmp' + ), + ) + ); + + expect(2)->toEqual(count($result)); + foreach ($result as $file) { + expect(file_exists($this->uploadFolder . '/' . $file->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $file->name . '.lock'))->toBeTrue(); } +}); + +test('wrong files array', function () { + $result = $this->handler->process(array('names' => 'abc.jpg')); + expect(0)->toEqual(count($result)); +}); + +test('exception trwon for invalid container', function () { + $this->expectException('Sirius\Upload\Exception\InvalidContainerException'); + + $handler = new Handler(new \stdClass()); +}); + +test('single upload validation', function () { + $this->createTemporaryFile('abc.tmp', 'non image file'); + + // uploaded files must be an image + $this->handler->addRule(Handler::RULE_IMAGE); + + $result = $this->handler->process( + array( + 'name' => 'abc.jpg', + 'tmp_name' => $this->tmpFolder . '/abc.tmp' + ) + ); + + expect($result->isValid())->toBeFalse(); + expect(1)->toEqual(count($result->getMessages())); + expect($result->nonAttribute)->toBeNull(); +}); + +test('multi upload validation', function () { + + $this->createTemporaryFile('abc.tmp', 'first_file'); + $this->createTemporaryFile('def.tmp', 'second_file'); + + // uploaded file must be an image + $this->handler->addRule(Handler::RULE_IMAGE); + + // array is as provided by PHP + $result = $this->handler->process( + array( + 'name' => array( + 'abc.jpg', + 'def.jpg', + ), + 'tmp_name' => array( + $this->tmpFolder . '/abc.tmp', + $this->tmpFolder . '/def.tmp' + ), + ) + ); + $messages = $result->getMessages(); + + expect($result->isValid())->toBeFalse(); + expect(2)->toEqual(count($messages)); + expect(1)->toEqual(count($messages[0])); +}); + +test('custom sanitization callback', function () { + $this->handler->setSanitizerCallback(function ($name) { + return preg_replace('/[^A-Za-z0-9\.]+/', '-', strtolower($name)); + }); + $this->createTemporaryFile('ABC 123.tmp', 'non image file'); + + $result = $this->handler->process( + array( + 'name' => 'ABC 123.tmp', + 'tmp_name' => $this->tmpFolder . '/ABC 123.tmp' + ) + ); + + expect(file_exists($this->uploadFolder . '/abc-123.tmp'))->toBeTrue(); +}); + +test('psr7 uploaded files', function () { + $files = ['abc.tmp', 'def.tmp']; + + $psr7Files = []; + + foreach ($files as $file) { + $this->createTemporaryFile($file, 'first_file'); - function testSingleUploadValidation() - { - $this->createTemporaryFile('abc.tmp', 'non image file'); - - // uploaded files must be an image - $this->handler->addRule(Handler::RULE_IMAGE); - - $result = $this->handler->process( - array( - 'name' => 'abc.jpg', - 'tmp_name' => $this->tmpFolder . '/abc.tmp' - ) + $factory = new StreamFactory(); + $stream = $factory->createStreamFromFile($this->tmpFolder . '/' . $file); + $psr7Files[] = new UploadedFile( + $stream, + $stream->getSize(), + UPLOAD_ERR_OK, + $file ); - - $this->assertFalse($result->isValid()); - $this->assertEquals(count($result->getMessages()), 1); - $this->assertNull($result->nonAttribute); } - function testMultiUploadValidation() - { - $this->createTemporaryFile('abc.tmp', 'first_file'); - $this->createTemporaryFile('def.tmp', 'second_file'); - - // uploaded file must be an image - $this->handler->addRule(Handler::RULE_IMAGE); + $result = $this->handler->process($psr7Files); - // array is as provided by PHP - $result = $this->handler->process( - array( - 'name' => array( - 'abc.jpg', - 'def.jpg', - ), - 'tmp_name' => array( - $this->tmpFolder . '/abc.tmp', - $this->tmpFolder . '/def.tmp' - ), - ) - ); - $messages = $result->getMessages(); + foreach ($result as $item) { + expect(file_exists($this->uploadFolder . '/' . $item->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $item->name . '.lock'))->toBeTrue(); - $this->assertFalse($result->isValid()); - $this->assertEquals(count($messages), 2); - $this->assertEquals(count($messages[0]), 1); + $item->confirm(); + expect(file_exists($this->uploadFolder . '/' . $item->name . '.lock'))->toBeFalse(); } +}); - function testCustomSanitizationCallback() - { - $this->handler->setSanitizerCallback(function ($name, $file) { - return preg_replace( - '/[^A-Za-z0-9\.]+/', '-', - substr(md5_file($file['tmp_name']), 0, 8) . '-' . strtolower($name) - ); - }); - $this->createTemporaryFile('ABC 123.tmp', 'non image file'); - - $result = $this->handler->process( - array( - 'name' => 'ABC 123.tmp', - 'tmp_name' => $this->tmpFolder . '/ABC 123.tmp' - ) - ); +test('single psr7 uploaded file', function () { + $file = 'abc.tmp'; - $this->assertTrue(file_exists($this->uploadFolder . '/35d41ded-abc-123.tmp')); - } - - function testExceptionThrownForInvalidSanitizationCallback() - { - $this->expectException('InvalidArgumentException'); - $this->handler->setSanitizerCallback('not a callable'); - } + $this->createTemporaryFile($file, 'first_file'); - function testPsr7UploadedFiles() - { - $files = ['abc.tmp', 'def.tmp']; + $factory = new StreamFactory(); + $stream = $factory->createStreamFromFile($this->tmpFolder . '/' . $file); + $psr7File = new UploadedFile( + $stream, + $stream->getSize(), + UPLOAD_ERR_OK, + $file + ); - $psr7Files = []; + $result = $this->handler->process($psr7File); - foreach ($files as $file) { - $this->createTemporaryFile($file, 'first_file'); + expect(file_exists($this->uploadFolder . '/' . $result->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $result->name . '.lock'))->toBeTrue(); - $factory = new StreamFactory(); - $stream = $factory->createStreamFromFile($this->tmpFolder . '/' . $file); - $psr7Files[] = new UploadedFile( - $stream, - $stream->getSize(), - UPLOAD_ERR_OK, - $file - ); - } + $result->confirm(); + expect(file_exists($this->uploadFolder . '/' . $result->name . '.lock'))->toBeFalse(); +}); - $result = $this->handler->process($psr7Files); +test('symfony uploaded files', function () { + $files = ['abc.tmp', 'def.tmp']; - foreach ($result as $item) { - $this->assertTrue(file_exists($this->uploadFolder . '/' . $item->name)); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $item->name . '.lock')); - - $item->confirm(); - $this->assertFalse(file_exists($this->uploadFolder . '/' . $item->name . '.lock')); - } - } - - function testSinglePsr7UploadedFile() - { - $file = 'abc.tmp'; + $symfonyFiles = []; + foreach ($files as $file) { $this->createTemporaryFile($file, 'first_file'); - $factory = new StreamFactory(); - $stream = $factory->createStreamFromFile($this->tmpFolder . '/' . $file); - $psr7File = new UploadedFile( - $stream, - $stream->getSize(), - UPLOAD_ERR_OK, - $file - ); - - $result = $this->handler->process($psr7File); - - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name)); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name . '.lock')); - - $result->confirm(); - $this->assertFalse(file_exists($this->uploadFolder . '/' . $result->name . '.lock')); + $symfonyFiles[] = new SymfonyUploadedFile($this->tmpFolder . '/' . $file, $file); } - function testSymfonyUploadedFiles() - { - $files = ['abc.tmp', 'def.tmp']; + $result = $this->handler->process($symfonyFiles); - $symfonyFiles = []; + foreach ($result as $item) { + expect(file_exists($this->uploadFolder . '/' . $item->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $item->name . '.lock'))->toBeTrue(); - foreach ($files as $file) { - $this->createTemporaryFile($file, 'first_file'); - - $symfonyFiles[] = new SymfonyUploadedFile($this->tmpFolder . '/' . $file, $file); - } - - $result = $this->handler->process($symfonyFiles); - - foreach ($result as $item) { - $this->assertTrue(file_exists($this->uploadFolder . '/' . $item->name)); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $item->name . '.lock')); - - $item->confirm(); - $this->assertFalse(file_exists($this->uploadFolder . '/' . $item->name . '.lock')); - } + $item->confirm(); + expect(file_exists($this->uploadFolder . '/' . $item->name . '.lock'))->toBeFalse(); } +}); - function testSingleSymfonyUploadedFile() - { - $file = 'abc.tmp'; +test('single symfony uploaded file', function () { + $file = 'abc.tmp'; - $this->createTemporaryFile($file, 'first_file'); - - $symfonyFile = new SymfonyUploadedFile($this->tmpFolder . '/' . $file, $file); + $this->createTemporaryFile($file, 'first_file'); - $result = $this->handler->process($symfonyFile); + $symfonyFile = new SymfonyUploadedFile($this->tmpFolder . '/' . $file, $file); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name)); - $this->assertTrue(file_exists($this->uploadFolder . '/' . $result->name . '.lock')); + $result = $this->handler->process($symfonyFile); - $result->confirm(); - $this->assertFalse(file_exists($this->uploadFolder . '/' . $result->name . '.lock')); - } + expect(file_exists($this->uploadFolder . '/' . $result->name))->toBeTrue(); + expect(file_exists($this->uploadFolder . '/' . $result->name . '.lock'))->toBeTrue(); -} + $result->confirm(); + expect(file_exists($this->uploadFolder . '/' . $result->name . '.lock'))->toBeFalse(); +});