diff --git a/.editorconfig b/.editorconfig index 30202d3f..26c64fbc 100644 --- a/.editorconfig +++ b/.editorconfig @@ -6,13 +6,12 @@ root = true [*] indent_style = space indent_size = 4 -charset = "utf-8" +charset = utf-8 end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true [*.yml] -indent_style = space indent_size = 2 [*.neon] diff --git a/.gitattributes b/.gitattributes index 797f418b..a399e5d7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -4,8 +4,10 @@ CONTRIBUTING.md export-ignore .gitattributes export-ignore .gitignore export-ignore phpunit.xml.dist export-ignore -.scrutinizer.yml export-ignore -.stickler.yml export-ignore tests export-ignore docs export-ignore .github export-ignore +.phive export-ignore +phpcs.xml export-ignore +psalm.xml export-ignore +phpstan.neon export-ignore diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2c0fe41e..214ac91f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,90 +1,22 @@ name: CI -on: [push, pull_request] +on: + push: + branches: + - master + - cake-5 + pull_request: + branches: + - '*' + +permissions: + contents: read jobs: testsuite: - runs-on: ubuntu-18.04 - strategy: - fail-fast: false - matrix: - php-version: ['7.4', '8.0', '8.1'] - db-type: [mysql, pgsql] - prefer-lowest: [''] - include: - - php-version: '7.2' - db-type: 'sqlite' - prefer-lowest: 'prefer-lowest' - - services: - postgres: - image: postgres - ports: - - 5432:5432 - env: - POSTGRES_PASSWORD: postgres - - steps: - - uses: actions/checkout@v2 - - - name: Setup Service - if: matrix.db-type == 'mysql' - run: | - sudo service mysql start - mysql -h 127.0.0.1 -u root -proot -e 'CREATE DATABASE cakephp;' - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-version }} - extensions: mbstring, intl, pdo_${{ matrix.db-type }} - coverage: pcov - - - name: Composer install - run: | - if ${{ matrix.prefer-lowest == 'prefer-lowest' }}; then - composer update --prefer-lowest --prefer-stable - else - composer install - fi - - - name: Run PHPUnit - run: | - if [[ ${{ matrix.db-type }} == 'sqlite' ]]; then export DB_URL='sqlite:///:memory:'; fi - if [[ ${{ matrix.db-type }} == 'mysql' ]]; then export DB_URL='mysql://root:root@127.0.0.1/cakephp'; fi - if [[ ${{ matrix.db-type }} == 'pgsql' ]]; then export DB_URL='postgres://postgres:postgres@127.0.0.1/postgres'; fi - - if [[ ${{ matrix.php-version }} == '7.4' && ${{ matrix.db-type }} == 'mysql' ]]; then - vendor/bin/phpunit --coverage-clover=coverage.xml - else - vendor/bin/phpunit - fi - - - name: Code Coverage Report - if: success() && matrix.php-version == '7.4' && matrix.db-type == 'mysql' - uses: codecov/codecov-action@v2 + uses: cakephp/.github/.github/workflows/testsuite-with-db.yml@5.x + secrets: inherit cs-stan: - name: Coding Standard & Static Analysis - runs-on: ubuntu-18.04 - - steps: - - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '7.4' - extensions: mbstring, intl - coverage: none - tools: cs2pr, phpstan:1.2 - - - name: Composer Install - run: composer install - - - name: Run phpcs - run: vendor/bin/phpcs --report=checkstyle --standard=vendor/cakephp/cakephp-codesniffer/CakePHP src/ tests/ | cs2pr - - - name: Run phpstan - if: success() || failure() - run: phpstan analyse + uses: cakephp/.github/.github/workflows/cs-stan.yml@5.x + secrets: inherit diff --git a/.phive/phars.xml b/.phive/phars.xml new file mode 100644 index 00000000..17093fd6 --- /dev/null +++ b/.phive/phars.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/.scrutinizer.yml b/.scrutinizer.yml deleted file mode 100644 index 27146f70..00000000 --- a/.scrutinizer.yml +++ /dev/null @@ -1,21 +0,0 @@ -imports: - - php - -filter: - excluded_paths: - - docs/ - - tests/ -tools: - php_mess_detector: true - php_cpd: - excluded_dirs: - - docs/ - - tests/ - php_loc: - excluded_dirs: - - docs/ - - tests/ - php_pdepend: - excluded_dirs: - 1: docs/ - 2: tests/ diff --git a/README.md b/README.md index 49e612e4..6522153a 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://img.shields.io/github/workflow/status/FriendsOfCake/cakephp-upload/CI/master?style=flat-square)](https://github.com/FriendsOfCake/cakephp-upload/actions?query=workflow%3ACI+branch%3Amaster) +[![Build Status](https://img.shields.io/github/actions/workflow/status/FriendsOfCake/cakephp-upload/ci.yml?style=flat-square)](https://github.com/FriendsOfCake/cakephp-upload/actions?query=workflow%3ACI+branch%3Amaster) [![Coverage Status](https://img.shields.io/codecov/c/github/FriendsOfCake/cakephp-upload/master?style=flat-square)](https://codecov.io/gh/FriendsOfCake/cakephp-upload) [![Total Downloads](https://img.shields.io/packagist/dt/josegonzalez/cakephp-upload.svg?style=flat-square)](https://packagist.org/packages/josegonzalez/cakephp-upload) [![Latest Stable Version](https://img.shields.io/packagist/v/josegonzalez/cakephp-upload.svg?style=flat-square)](https://packagist.org/packages/josegonzalez/cakephp-upload) @@ -8,6 +8,8 @@ The Upload Plugin is an attempt to easily handle file uploads with CakePHP. +See [7.x branch](https://github.com/FriendsOfCake/cakephp-upload/tree/7.x) for CakePHP 4.x documentation. + See [4.x branch](https://github.com/FriendsOfCake/cakephp-upload/tree/4.x) for CakePHP 3.x documentation. See [2.x branch](https://github.com/FriendsOfCake/cakephp-upload/tree/2.x) for CakePHP 2.x documentation. diff --git a/composer.json b/composer.json index 926dcab8..4912c0f2 100644 --- a/composer.json +++ b/composer.json @@ -12,15 +12,30 @@ } ], "require": { - "cakephp/orm": "^4.0.2", - "league/flysystem": "^2.2|^3.0" + "cakephp/orm": "^5.0", + "league/flysystem": "^3.15.1.0" }, "require-dev": { - "cakephp/cakephp": "^4.0.2", - "phpunit/phpunit": "^8.5 || ^9.3", - "cakephp/cakephp-codesniffer": "^4.0", - "league/flysystem-memory": "^2.0|^3.0", - "mikey179/vfsstream": "^1.6.10" + "cakephp/cakephp": "^5.0", + "phpunit/phpunit": "^10.1.0", + "cakephp/cakephp-codesniffer": "^5.0", + "league/flysystem-memory": "^3.15", + "mikey179/vfsstream": "^1.6.10", + "cakephp/migrations": "^4.1" + }, + "scripts": { + "cs-check": "phpcs --colors --parallel=16 -p src/ tests/", + "cs-fix": "phpcbf --colors --parallel=16 -p src/ tests/", + "phpstan": "tools/phpstan analyse", + "psalm": "tools/psalm --show-info=false", + "stan": [ + "@phpstan", + "@psalm" + ], + "stan-baseline": "tools/phpstan --generate-baseline", + "psalm-baseline": "tools/psalm --set-baseline=psalm-baseline.xml", + "stan-setup": "phive install", + "test": "phpunit" }, "autoload": { "psr-4": { diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 00000000..584f45c3 --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/phpstan.neon b/phpstan.neon index 63128791..5ef12379 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 7 + level: 8 checkMissingIterableValueType: false checkGenericClassInNonGenericObjectType: false paths: @@ -21,11 +21,11 @@ parameters: path: src/Model/Behavior/UploadBehavior.php - - message: "#^Cannot use array destructuring on array\\|false\\.$#" + message: "#^Cannot use array destructuring on array\\\\|false\\.$#" count: 4 path: src/Validation/DefaultValidation.php - - message: "#^Cannot use array destructuring on array\\|false\\.$#" + message: "#^Cannot use array destructuring on array\\\\|false\\.$#" count: 4 path: src/Validation/ImageValidation.php diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 024a3378..5afdae57 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,40 +1,23 @@ - + + + - - - + - - - ./tests/TestCase + + tests/TestCase/ - - - - ./src - - - - - - - - - - - - + + + + + + src/ + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 00000000..0eeb98b1 --- /dev/null +++ b/psalm.xml @@ -0,0 +1,19 @@ + + + + + + + + + diff --git a/src/Database/Type/FileType.php b/src/Database/Type/FileType.php index f59faa75..684e5287 100644 --- a/src/Database/Type/FileType.php +++ b/src/Database/Type/FileType.php @@ -3,7 +3,7 @@ namespace Josegonzalez\Upload\Database\Type; -use Cake\Database\DriverInterface; +use Cake\Database\Driver; use Cake\Database\Type\BaseType; class FileType extends BaseType @@ -17,7 +17,7 @@ class FileType extends BaseType * @param mixed $value The value to convert. * @return mixed Converted value. */ - public function marshal($value) + public function marshal(mixed $value): mixed { return $value; } @@ -25,7 +25,7 @@ public function marshal($value) /** * @inheritDoc */ - public function toDatabase($value, DriverInterface $driver) + public function toDatabase(mixed $value, Driver $driver): mixed { return $value; } @@ -33,7 +33,7 @@ public function toDatabase($value, DriverInterface $driver) /** * @inheritDoc */ - public function toPHP($value, DriverInterface $driver) + public function toPHP(mixed $value, Driver $driver): mixed { return $value; } diff --git a/src/File/Path/Basepath/DefaultTrait.php b/src/File/Path/Basepath/DefaultTrait.php index e6925278..99e82ddc 100644 --- a/src/File/Path/Basepath/DefaultTrait.php +++ b/src/File/Path/Basepath/DefaultTrait.php @@ -23,7 +23,7 @@ public function basepath(): string { $defaultPath = 'webroot{DS}files{DS}{model}{DS}{field}{DS}'; $path = Hash::get($this->settings, 'path', $defaultPath); - if (strpos($path, '{primaryKey}') !== false) { + if (str_contains($path, '{primaryKey}')) { if ($this->entity->isNew()) { throw new LogicException('{primaryKey} substitution not allowed for new entities'); } @@ -43,7 +43,7 @@ public function basepath(): string '{microtime}' => microtime(true), '{DS}' => DIRECTORY_SEPARATOR, ]; - if (strpos($path, '{primaryKey}') !== false) { + if (str_contains($path, '{primaryKey}')) { $replacements['{primaryKey}'] = $this->entity->get($this->table->getPrimaryKey()); } diff --git a/src/File/Path/DefaultProcessor.php b/src/File/Path/DefaultProcessor.php index d3b1df92..76944382 100644 --- a/src/File/Path/DefaultProcessor.php +++ b/src/File/Path/DefaultProcessor.php @@ -7,6 +7,7 @@ use Cake\ORM\Table; use Josegonzalez\Upload\File\Path\Basepath\DefaultTrait as BasepathTrait; use Josegonzalez\Upload\File\Path\Filename\DefaultTrait as FilenameTrait; +use Psr\Http\Message\UploadedFileInterface; class DefaultProcessor implements ProcessorInterface { @@ -18,47 +19,52 @@ class DefaultProcessor implements ProcessorInterface * * @var \Cake\ORM\Table */ - protected $table; + protected Table $table; /** * Entity instance. * * @var \Cake\Datasource\EntityInterface */ - protected $entity; + protected EntityInterface $entity; /** * Instance of \Psr\Http\Message\UploadedFileInterface conaining the meta info from the file. * * @var \Psr\Http\Message\UploadedFileInterface|string */ - protected $data; + protected UploadedFileInterface|string $data; /** * Name of field * * @var string */ - protected $field; + protected string $field; /** * Settings for processing a path * * @var array */ - protected $settings; + protected array $settings; /** * Constructor * - * @param \Cake\ORM\Table $table the instance managing the entity + * @param \Cake\ORM\Table $table the instance managing the entity * @param \Cake\Datasource\EntityInterface $entity the entity to construct a path for. - * @param \Psr\Http\Message\UploadedFileInterface|string $data the data being submitted for a save or filename stored in db - * @param string $field the field for which data will be saved - * @param array $settings the settings for the current field + * @param \Psr\Http\Message\UploadedFileInterface|string $data the data being submitted for a save or filename stored in db + * @param string $field the field for which data will be saved + * @param array $settings the settings for the current field */ - public function __construct(Table $table, EntityInterface $entity, $data, string $field, array $settings) - { + public function __construct( + Table $table, + EntityInterface $entity, + UploadedFileInterface|string $data, + string $field, + array $settings + ) { $this->table = $table; $this->entity = $entity; $this->data = $data; diff --git a/src/File/Path/Filename/DefaultTrait.php b/src/File/Path/Filename/DefaultTrait.php index 379212ba..e7dd9c41 100644 --- a/src/File/Path/Filename/DefaultTrait.php +++ b/src/File/Path/Filename/DefaultTrait.php @@ -16,15 +16,21 @@ trait DefaultTrait */ public function filename(): string { - $processor = Hash::get($this->settings, 'nameCallback', null); + $processor = Hash::get($this->settings, 'nameCallback'); if (is_callable($processor)) { - return $processor($this->table, $this->entity, $this->data, $this->field, $this->settings); + return $processor( + $this->table, + $this->entity, + $this->data, + $this->field, + $this->settings + ); } if (is_string($this->data)) { return $this->data; } - return $this->data->getClientFilename(); + return (string)$this->data->getClientFilename(); } } diff --git a/src/File/Path/ProcessorInterface.php b/src/File/Path/ProcessorInterface.php index 96735eef..4eb3e70f 100644 --- a/src/File/Path/ProcessorInterface.php +++ b/src/File/Path/ProcessorInterface.php @@ -5,6 +5,7 @@ use Cake\Datasource\EntityInterface; use Cake\ORM\Table; +use Psr\Http\Message\UploadedFileInterface; interface ProcessorInterface { @@ -13,11 +14,17 @@ interface ProcessorInterface * * @param \Cake\ORM\Table $table the instance managing the entity * @param \Cake\Datasource\EntityInterface $entity the entity to construct a path for. - * @param \Psr\Http\Message\UploadedFileInterface|string $data the data being submitted for a save or filename stored in db + * @param \Psr\Http\Message\UploadedFileInterface|string $data the data being submitted for a save or filename stored in db * @param string $field the field for which data will be saved * @param array $settings the settings for the current field */ - public function __construct(Table $table, EntityInterface $entity, $data, string $field, array $settings); + public function __construct( + Table $table, + EntityInterface $entity, + string|UploadedFileInterface $data, + string $field, + array $settings + ); /** * Returns the basepath for the current field/data combination diff --git a/src/File/Transformer/DefaultTransformer.php b/src/File/Transformer/DefaultTransformer.php index 7d7c32f2..09c48fd1 100644 --- a/src/File/Transformer/DefaultTransformer.php +++ b/src/File/Transformer/DefaultTransformer.php @@ -9,62 +9,22 @@ class DefaultTransformer implements TransformerInterface { - /** - * Table instance. - * - * @var \Cake\ORM\Table - */ - protected $table; - - /** - * Entity instance. - * - * @var \Cake\Datasource\EntityInterface - */ - protected $entity; - - /** - * Array of uploaded data for this field - * - * @var \Psr\Http\Message\UploadedFileInterface - */ - protected $data; - - /** - * Name of field - * - * @var string - */ - protected $field; - - /** - * Settings for processing a path - * - * @var array - */ - protected $settings; - /** * Constructor * - * @param \Cake\ORM\Table $table the instance managing the entity + * @param \Cake\ORM\Table $table the instance managing the entity * @param \Cake\Datasource\EntityInterface $entity the entity to construct a path for. * @param \Psr\Http\Message\UploadedFileInterface $data the data being submitted for a save - * @param string $field the field for which data will be saved - * @param array $settings the settings for the current field + * @param string $field the field for which data will be saved + * @param array $settings the settings for the current field */ public function __construct( - Table $table, - EntityInterface $entity, - UploadedFileInterface $data, - string $field, - array $settings + protected Table $table, + protected EntityInterface $entity, + protected UploadedFileInterface $data, + protected string $field, + protected array $settings ) { - $this->table = $table; - $this->entity = $entity; - $this->data = $data; - $this->field = $field; - $this->settings = $settings; } /** diff --git a/src/File/Writer/DefaultWriter.php b/src/File/Writer/DefaultWriter.php index 548ee354..06218f13 100644 --- a/src/File/Writer/DefaultWriter.php +++ b/src/File/Writer/DefaultWriter.php @@ -17,41 +17,6 @@ class DefaultWriter implements WriterInterface { - /** - * Table instance. - * - * @var \Cake\ORM\Table - */ - protected $table; - - /** - * Entity instance. - * - * @var \Cake\Datasource\EntityInterface - */ - protected $entity; - - /** - * Array of uploaded data for this field - * - * @var \Psr\Http\Message\UploadedFileInterface|null - */ - protected $data; - - /** - * Name of field - * - * @var string - */ - protected $field; - - /** - * Settings for processing a path - * - * @var array - */ - protected $settings; - /** * Constructs a writer * @@ -62,17 +27,12 @@ class DefaultWriter implements WriterInterface * @param array $settings the settings for the current field */ public function __construct( - Table $table, - EntityInterface $entity, - ?UploadedFileInterface $data, - string $field, - array $settings + protected Table $table, + protected EntityInterface $entity, + protected ?UploadedFileInterface $data, + protected string $field, + protected array $settings ) { - $this->table = $table; - $this->entity = $entity; - $this->data = $data; - $this->field = $field; - $this->settings = $settings; } /** @@ -117,7 +77,7 @@ public function delete(array $files): array * @param string $path that path to which the file should be written * @return bool */ - public function writeFile(FilesystemOperator $filesystem, $file, $path): bool + public function writeFile(FilesystemOperator $filesystem, string $file, string $path): bool { // phpcs:ignore $stream = @fopen($file, 'r'); @@ -134,10 +94,10 @@ public function writeFile(FilesystemOperator $filesystem, $file, $path): bool try { $filesystem->move($tempPath, $path); $success = true; - } catch (FilesystemException $e) { + } catch (FilesystemException) { // noop } - } catch (FilesystemException $e) { + } catch (FilesystemException) { // noop } @@ -159,7 +119,7 @@ public function deletePath(FilesystemOperator $filesystem, string $path): bool $success = true; try { $filesystem->delete($path); - } catch (FilesystemException $e) { + } catch (FilesystemException) { $success = false; // TODO: log this? } diff --git a/src/Model/Behavior/UploadBehavior.php b/src/Model/Behavior/UploadBehavior.php index 702c23bf..abde3949 100644 --- a/src/Model/Behavior/UploadBehavior.php +++ b/src/Model/Behavior/UploadBehavior.php @@ -18,6 +18,9 @@ use Psr\Http\Message\UploadedFileInterface; use UnexpectedValueException; +/** + * UploadBehavior + */ class UploadBehavior extends Behavior { /** @@ -25,7 +28,7 @@ class UploadBehavior extends Behavior * * @var array */ - private $protectedFieldNames = [ + private array $protectedFieldNames = [ 'priority', ]; @@ -47,7 +50,7 @@ public function initialize(array $config): void } $this->setConfig($configs); - $this->setConfig('className', null); + $this->setConfig('className'); $schema = $this->_table->getSchema(); /** @var string $field */ @@ -65,7 +68,7 @@ public function initialize(array $config): void * @param \ArrayObject $options options for the current event * @return void */ - public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options) + public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObject $options): void { $validator = $this->_table->getValidator(); $dataArray = $data->getArrayCopy(); @@ -96,9 +99,9 @@ public function beforeMarshal(EventInterface $event, ArrayObject $data, ArrayObj * @param \Cake\Event\EventInterface $event The beforeSave event that was fired * @param \Cake\Datasource\EntityInterface $entity The entity that is going to be saved * @param \ArrayObject $options the options passed to the save method - * @return void|false + * @return void */ - public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function beforeSave(EventInterface $event, EntityInterface $entity, ArrayObject $options): void { foreach ($this->getConfig(null, []) as $field => $settings) { if ( @@ -134,7 +137,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array $writer = $this->getWriter($entity, $data, $field, $settings); $success = $writer->write($files); if ((new Collection($success))->contains(false)) { - return false; + return; } $entity->set($field, $filename); @@ -153,7 +156,7 @@ public function beforeSave(EventInterface $event, EntityInterface $entity, Array * @param \ArrayObject $options the options passed to the delete method * @return bool */ - public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options) + public function afterDelete(EventInterface $event, EntityInterface $entity, ArrayObject $options): bool { $result = true; @@ -169,11 +172,14 @@ public function afterDelete(EventInterface $event, EntityInterface $entity, Arra $path = $this->getPathProcessor($entity, $entity->get($field), $field, $settings)->basepath(); } - $callback = Hash::get($settings, 'deleteCallback', null); + $callback = Hash::get($settings, 'deleteCallback'); if ($callback && is_callable($callback)) { $files = $callback($path, $entity, $field, $settings); } else { - $files = [$path . $entity->get($field)]; + /** @var \Psr\Http\Message\UploadedFileInterface $uploaded */ + $uploaded = $entity->get($field); + + $files = [$path . $uploaded->getClientFilename()]; } $writer = $this->getWriter($entity, null, $field, $settings); @@ -197,8 +203,12 @@ public function afterDelete(EventInterface $event, EntityInterface $entity, Arra * @param array $settings the settings for the current field * @return \Josegonzalez\Upload\File\Path\ProcessorInterface */ - public function getPathProcessor(EntityInterface $entity, $data, string $field, array $settings): ProcessorInterface - { + public function getPathProcessor( + EntityInterface $entity, + string|UploadedFileInterface $data, + string $field, + array $settings + ): ProcessorInterface { /** @var class-string<\Josegonzalez\Upload\File\Path\ProcessorInterface> $processorClass */ $processorClass = Hash::get($settings, 'pathProcessor', DefaultProcessor::class); diff --git a/src/Plugin.php b/src/UploadPlugin.php similarity index 58% rename from src/Plugin.php rename to src/UploadPlugin.php index 141eef7d..f420e81f 100644 --- a/src/Plugin.php +++ b/src/UploadPlugin.php @@ -11,8 +11,29 @@ /** * Plugin class. */ -class Plugin extends BasePlugin +class UploadPlugin extends BasePlugin { + /** + * The name of this plugin + * + * @var string|null + */ + protected ?string $name = 'Upload'; + + /** + * Console middleware + * + * @var bool + */ + protected bool $consoleEnabled = false; + + /** + * Load routes or not + * + * @var bool + */ + protected bool $routesEnabled = false; + /** * Plugin bootstrap. * diff --git a/src/Validation/Traits/ImageValidationTrait.php b/src/Validation/Traits/ImageValidationTrait.php index b69d0bb4..7ef8a747 100644 --- a/src/Validation/Traits/ImageValidationTrait.php +++ b/src/Validation/Traits/ImageValidationTrait.php @@ -14,7 +14,7 @@ trait ImageValidationTrait * @param int $width Width of Image * @return bool Success */ - public static function isAboveMinWidth($check, int $width): bool + public static function isAboveMinWidth(mixed $check, int $width): bool { if ($check instanceof UploadedFileInterface) { $file = $check->getStream()->getMetadata('uri'); @@ -37,7 +37,7 @@ public static function isAboveMinWidth($check, int $width): bool * @param int $width Width of Image * @return bool Success */ - public static function isBelowMaxWidth($check, int $width): bool + public static function isBelowMaxWidth(mixed $check, int $width): bool { if ($check instanceof UploadedFileInterface) { $file = $check->getStream()->getMetadata('uri'); @@ -61,7 +61,7 @@ public static function isBelowMaxWidth($check, int $width): bool * @param int $height Height of Image * @return bool Success */ - public static function isAboveMinHeight($check, int $height): bool + public static function isAboveMinHeight(mixed $check, int $height): bool { if ($check instanceof UploadedFileInterface) { $file = $check->getStream()->getMetadata('uri'); @@ -84,7 +84,7 @@ public static function isAboveMinHeight($check, int $height): bool * @param int $height Height of Image * @return bool Success */ - public static function isBelowMaxHeight($check, int $height): bool + public static function isBelowMaxHeight(mixed $check, int $height): bool { if ($check instanceof UploadedFileInterface) { $file = $check->getStream()->getMetadata('uri'); diff --git a/src/Validation/Traits/UploadValidationTrait.php b/src/Validation/Traits/UploadValidationTrait.php index 21ab3338..cd4ace87 100644 --- a/src/Validation/Traits/UploadValidationTrait.php +++ b/src/Validation/Traits/UploadValidationTrait.php @@ -15,7 +15,7 @@ trait UploadValidationTrait * @param mixed $check Value to check * @return bool Success */ - public static function isUnderPhpSizeLimit($check): bool + public static function isUnderPhpSizeLimit(mixed $check): bool { if ($check instanceof UploadedFileInterface) { return $check->getError() !== UPLOAD_ERR_INI_SIZE; @@ -31,7 +31,7 @@ public static function isUnderPhpSizeLimit($check): bool * @param mixed $check Value to check * @return bool Success */ - public static function isUnderFormSizeLimit($check): bool + public static function isUnderFormSizeLimit(mixed $check): bool { if ($check instanceof UploadedFileInterface) { return $check->getError() !== UPLOAD_ERR_FORM_SIZE; @@ -46,7 +46,7 @@ public static function isUnderFormSizeLimit($check): bool * @param mixed $check Value to check * @return bool Success */ - public static function isCompletedUpload($check): bool + public static function isCompletedUpload(mixed $check): bool { if ($check instanceof UploadedFileInterface) { return $check->getError() !== UPLOAD_ERR_PARTIAL; @@ -61,7 +61,7 @@ public static function isCompletedUpload($check): bool * @param mixed $check Value to check * @return bool Success */ - public static function isFileUpload($check): bool + public static function isFileUpload(mixed $check): bool { if ($check instanceof UploadedFileInterface) { return $check->getError() !== UPLOAD_ERR_NO_FILE; @@ -76,7 +76,7 @@ public static function isFileUpload($check): bool * @param mixed $check Value to check * @return bool Success */ - public static function isSuccessfulWrite($check): bool + public static function isSuccessfulWrite(mixed $check): bool { if ($check instanceof UploadedFileInterface) { return $check->getError() !== UPLOAD_ERR_CANT_WRITE; @@ -92,7 +92,7 @@ public static function isSuccessfulWrite($check): bool * @param int $size Minimum file size * @return bool Success */ - public static function isAboveMinSize($check, $size): bool + public static function isAboveMinSize(mixed $check, int $size): bool { if ($check instanceof UploadedFileInterface) { return $check->getSize() >= $size; @@ -108,7 +108,7 @@ public static function isAboveMinSize($check, $size): bool * @param int $size Maximum file size * @return bool Success */ - public static function isBelowMaxSize($check, $size): bool + public static function isBelowMaxSize(mixed $check, int $size): bool { if ($check instanceof UploadedFileInterface) { return $check->getSize() <= $size; diff --git a/tests/Fixture/FilesFixture.php b/tests/Fixture/FilesFixture.php index f890514f..57a5ad89 100644 --- a/tests/Fixture/FilesFixture.php +++ b/tests/Fixture/FilesFixture.php @@ -5,26 +5,12 @@ class FilesFixture extends TestFixture { - public $table = 'files'; - - /** - * fields property - * - * @var array - */ - public $fields = [ - 'id' => ['type' => 'integer'], - 'filename' => ['type' => 'string'], - 'created' => ['type' => 'datetime', 'null' => true], - '_constraints' => ['primary' => ['type' => 'primary', 'columns' => ['id']]], - ]; - /** * records property * * @var array */ - public $records = [ + public array $records = [ ['filename' => 'FileOne'], ['filename' => 'FileTwo'], ['filename' => 'FileThree'], diff --git a/tests/Stub/ChildBehavior.php b/tests/Stub/ChildBehavior.php index b8afddb5..03ba4b3e 100644 --- a/tests/Stub/ChildBehavior.php +++ b/tests/Stub/ChildBehavior.php @@ -9,7 +9,7 @@ class ChildBehavior extends UploadBehavior { - protected $_defaultConfig = ['key' => 'value']; + protected array $_defaultConfig = ['key' => 'value']; public function constructFiles( EntityInterface $entity, diff --git a/tests/TestCase/File/Writer/DefaultWriterTest.php b/tests/TestCase/File/Writer/DefaultWriterTest.php index 602d481d..8ce52fb4 100644 --- a/tests/TestCase/File/Writer/DefaultWriterTest.php +++ b/tests/TestCase/File/Writer/DefaultWriterTest.php @@ -64,11 +64,10 @@ public function testInvoke() ], 'field', [])); } - public function testDelete() + public function testDeleteSucess() { $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemOperator')->getMock(); - $filesystem->expects($this->at(0))->method('delete'); - $filesystem->expects($this->at(1))->method('delete')->will($this->throwException(new UnableToDeleteFile())); + $filesystem->expects($this->once())->method('delete'); $writer = $this->getMockBuilder('Josegonzalez\Upload\File\Writer\DefaultWriter') ->onlyMethods(['getFilesystem']) ->setConstructorArgs([$this->table, $this->entity, $this->data, $this->field, $this->settings]) @@ -79,6 +78,19 @@ public function testDelete() $this->assertEquals([true], $writer->delete([ $this->vfs->url() . '/tempfile', ])); + } + + public function testDeleteFailure() + { + $filesystem = $this->getMockBuilder('League\Flysystem\FilesystemOperator')->getMock(); + $filesystem->expects($this->once())->method('delete')->will($this->throwException(new UnableToDeleteFile())); + $writer = $this->getMockBuilder('Josegonzalez\Upload\File\Writer\DefaultWriter') + ->onlyMethods(['getFilesystem']) + ->setConstructorArgs([$this->table, $this->entity, $this->data, $this->field, $this->settings]) + ->getMock(); + $writer->expects($this->any())->method('getFilesystem')->will($this->returnValue($filesystem)); + + $this->assertEquals([], $writer->delete([])); $this->assertEquals([false], $writer->delete([ $this->vfs->url() . '/invalid.txt', diff --git a/tests/TestCase/Model/Behavior/UploadBehaviorTest.php b/tests/TestCase/Model/Behavior/UploadBehaviorTest.php index bda3545f..45e7b9b3 100644 --- a/tests/TestCase/Model/Behavior/UploadBehaviorTest.php +++ b/tests/TestCase/Model/Behavior/UploadBehaviorTest.php @@ -16,7 +16,7 @@ class UploadBehaviorTest extends TestCase { - protected $fixtures = [ + protected array $fixtures = [ 'plugin.Josegonzalez/Upload.Files', ]; @@ -74,12 +74,9 @@ public function testInitialize() $schema->expects($this->once()) ->method('setColumnType') ->with('field', 'upload.file'); - $table->expects($this->at(0)) + $table->expects($this->any()) ->method('getSchema') ->will($this->returnValue($schema)); - $table->expects($this->at(1)) - ->method('setSchema') - ->will($this->returnValue($schema)); $methods = array_diff($this->behaviorMethods, ['initialize']); $behavior = $this->getMockBuilder('Josegonzalez\Upload\Model\Behavior\UploadBehavior') @@ -119,12 +116,9 @@ public function testInitializeIndexedConfig() $schema->expects($this->once()) ->method('setColumnType') ->with('field', 'upload.file'); - $table->expects($this->at(0)) + $table->expects($this->any()) ->method('getSchema') ->will($this->returnValue($schema)); - $table->expects($this->at(1)) - ->method('setSchema') - ->will($this->returnValue($schema)); $methods = array_diff($this->behaviorMethods, ['initialize', 'setConfig', 'getConfig']); $behavior = $this->getMockBuilder('Josegonzalez\Upload\Model\Behavior\UploadBehavior') @@ -154,12 +148,9 @@ public function testInitializeAddBehaviorOptionsInterfaceConfig() $schema->expects($this->once()) ->method('setColumnType') ->with('field', 'upload.file'); - $table->expects($this->at(0)) + $table->expects($this->any()) ->method('getSchema') ->will($this->returnValue($schema)); - $table->expects($this->at(1)) - ->method('setSchema') - ->will($this->returnValue($schema)); $methods = array_diff($this->behaviorMethods, ['initialize', 'setConfig', 'getConfig']); //$behavior = $this->getMock('Josegonzalez\Upload\Model\Behavior\UploadBehavior', $methods, [$table, $settings], '', false); @@ -444,7 +435,10 @@ public function testAfterDeleteOk() ->setConstructorArgs([$this->table, $this->dataOk]) ->getMock(); $behavior->setConfig($this->configOk); - + $this->entity->expects($this->any()) + ->method('get') + ->with('field') + ->will($this->returnValue($this->dataOk['field'])); $behavior->expects($this->any()) ->method('getPathProcessor') ->willReturn($this->processor); @@ -466,7 +460,10 @@ public function testAfterDeleteFail() ->setConstructorArgs([$this->table, $this->dataOk]) ->getMock(); $behavior->setConfig($this->configOk); - + $this->entity->expects($this->any()) + ->method('get') + ->with('field') + ->will($this->returnValue($this->dataOk['field'])); $behavior->expects($this->any()) ->method('getPathProcessor') ->willReturn($this->processor); @@ -476,7 +473,6 @@ public function testAfterDeleteFail() $this->writer->expects($this->any()) ->method('delete') ->will($this->returnValue([false])); - $this->assertFalse($behavior->afterDelete(new Event('fake.event'), $this->entity, new ArrayObject())); } @@ -502,7 +498,13 @@ public function testAfterDeleteSkip() public function testAfterDeleteUsesPathProcessorToDetectPathToTheFile() { $dir = '/some/path/'; - $field = 'file.txt'; + $field = new UploadedFile( + fopen('php://temp', 'wb+'), + 1, + UPLOAD_ERR_OK, + 'file.txt', + 'text/plain' + ); $methods = array_diff($this->behaviorMethods, ['afterDelete', 'config', 'setConfig', 'getConfig']); $behavior = $this->getMockBuilder('Josegonzalez\Upload\Model\Behavior\UploadBehavior') @@ -511,15 +513,12 @@ public function testAfterDeleteUsesPathProcessorToDetectPathToTheFile() ->getMock(); $behavior->setConfig($this->configOk); - $this->entity->expects($this->at(0)) + $this->entity->expects($this->once()) ->method('has') ->with('dir') ->will($this->returnValue(false)); - $this->entity->expects($this->at(1)) - ->method('get') - ->with('field') - ->will($this->returnValue($field)); - $this->entity->expects($this->at(2)) + + $this->entity->expects($this->exactly(2)) ->method('get') ->with('field') ->will($this->returnValue($field)); @@ -541,7 +540,7 @@ public function testAfterDeleteUsesPathProcessorToDetectPathToTheFile() // and here we check that file with right path will be deleted $this->writer->expects($this->once()) ->method('delete') - ->with([$dir . $field]) + ->with([$dir . $field->getClientFilename()]) ->willReturn([true]); $behavior->afterDelete(new Event('fake.event'), $this->entity, new ArrayObject()); @@ -550,7 +549,13 @@ public function testAfterDeleteUsesPathProcessorToDetectPathToTheFile() public function testAfterDeletePrefersStoredPathOverPathProcessor() { $dir = '/some/path/'; - $field = 'file.txt'; + $field = new UploadedFile( + fopen('php://temp', 'wb+'), + 1, + UPLOAD_ERR_OK, + 'file.txt', + 'text/plain' + ); $methods = array_diff($this->behaviorMethods, ['afterDelete', 'config', 'setConfig', 'getConfig']); $behavior = $this->getMockBuilder('Josegonzalez\Upload\Model\Behavior\UploadBehavior') @@ -559,19 +564,16 @@ public function testAfterDeletePrefersStoredPathOverPathProcessor() ->getMock(); $behavior->setConfig($this->configOk); - $this->entity->expects($this->at(0)) + $this->entity->expects($this->once()) ->method('has') ->with('dir') ->will($this->returnValue(true)); - $this->entity->expects($this->at(1)) - ->method('get') - ->with('dir') - ->will($this->returnValue($dir)); - $this->entity->expects($this->at(2)) - ->method('get') - ->with('field') - ->will($this->returnValue($field)); + $this->entity->method('get') + ->will($this->returnValueMap([ + ['dir', $dir], + ['field', $field], + ])); $behavior->expects($this->never()) ->method('getPathProcessor'); $behavior->expects($this->once()) @@ -580,7 +582,7 @@ public function testAfterDeletePrefersStoredPathOverPathProcessor() $this->writer->expects($this->once()) ->method('delete') - ->with([$dir . $field]) + ->with([$dir . $field->getClientFilename()]) ->will($this->returnValue([true])); $this->assertTrue($behavior->afterDelete(new Event('fake.event'), $this->entity, new ArrayObject())); @@ -599,8 +601,12 @@ public function testAfterDeleteNoDeleteCallback() $this->configOk['field']['deleteCallback'] = null; $behavior->setConfig($this->configOk); + $this->entity->expects($this->any()) + ->method('get') + ->with('field') + ->will($this->returnValue($this->dataOk['field'])); $behavior->expects($this->once())->method('getPathProcessor') - ->with($this->entity, $this->entity->field, 'field', $this->configOk['field']) + ->with($this->entity, $this->dataOk['field'], 'field', $this->configOk['field']) ->willReturn($this->processor); $this->processor->expects($this->once())->method('basepath') ->willReturn($path); @@ -610,7 +616,7 @@ public function testAfterDeleteNoDeleteCallback() $this->writer->expects($this->once()) ->method('delete') ->with([ - $path . $this->entity->field, + $path . $this->entity->field . $this->dataOk['field']->getClientFilename(), ]) ->willReturn([true, true, true]); @@ -636,8 +642,12 @@ public function testAfterDeleteUsesDeleteCallback() }; $behavior->setConfig($this->configOk); + $this->entity->expects($this->any()) + ->method('get') + ->with('field') + ->will($this->returnValue($this->dataOk['field'])); $behavior->expects($this->once())->method('getPathProcessor') - ->with($this->entity, $this->entity->field, 'field', $this->configOk['field']) + ->with($this->entity, $this->dataOk['field'], 'field', $this->configOk['field']) ->willReturn($this->processor); $this->processor->expects($this->once())->method('basepath') ->willReturn($path); diff --git a/tests/bootstrap.php b/tests/bootstrap.php index 5ba11a54..3e295d12 100644 --- a/tests/bootstrap.php +++ b/tests/bootstrap.php @@ -2,9 +2,12 @@ declare(strict_types=1); use Cake\Core\Configure; +use Cake\Datasource\ConnectionManager; +use Cake\TestSuite\Fixture\SchemaLoader; +use function Cake\Core\env; -/* - * Test suite bootstrap +/** + * Test suite bootstrap. * * This function is used to find the location of CakePHP whether CakePHP * has been installed as a dependency of the plugin, or the plugin is itself @@ -20,11 +23,26 @@ } while ($root !== $lastRoot); throw new Exception('Cannot find the root of the application, unable to run tests'); }; - $root = $findRoot(__FILE__); unset($findRoot); chdir($root); -require $root . '/vendor/cakephp/cakephp/tests/bootstrap.php'; +require_once 'vendor/autoload.php'; + +define('ROOT', $root . DS . 'tests' . DS . 'test_app' . DS); +define('APP', ROOT . 'App' . DS); +define('TMP', sys_get_temp_dir() . DS); +define('CONFIG', ROOT . DS . 'config' . DS); + +if (!getenv('DB_URL')) { + putenv('DB_URL=sqlite:///:memory:'); +} +ConnectionManager::setConfig('test', ['url' => getenv('DB_URL')]); + +Configure::write('App.namespace', 'Josegonzalez\Upload\Test\TestApp'); -Configure::write('Error.ignoredDeprecationPaths', ['src/TestSuite/Fixture/FixtureInjector.php']); +// Create test database schema +if (env('FIXTURE_SCHEMA_METADATA')) { + $loader = new SchemaLoader(); + $loader->loadInternalFile(env('FIXTURE_SCHEMA_METADATA')); +} diff --git a/tests/schema.php b/tests/schema.php new file mode 100644 index 00000000..37da7c83 --- /dev/null +++ b/tests/schema.php @@ -0,0 +1,19 @@ + 'files', + 'columns' => [ + 'id' => ['type' => 'integer'], + 'filename' => ['type' => 'string'], + 'created' => ['type' => 'datetime', 'null' => true], + ], + 'constraints' => [ + 'primary' => [ + 'type' => 'primary', + 'columns' => ['id'], + ], + ], + ], +];