diff --git a/.github/workflows/compare.yml b/.github/workflows/compare.yml deleted file mode 100644 index c6b5e9b..0000000 --- a/.github/workflows/compare.yml +++ /dev/null @@ -1,29 +0,0 @@ -# When a PR is opened or a push is made, compare -# code for backwards compatibility. -name: RoaveBC - -on: - pull_request: - branches: - - develop - paths: - - 'src/**' - -jobs: - compare: - name: Compare for Backwards Compatibility - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Run comparison (limited) - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} - uses: docker://nyholm/roave-bc-check-ga - - - name: Run comparison (authenticated) - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} - uses: docker://nyholm/roave-bc-check-ga - env: - COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} diff --git a/.github/workflows/deduplicate.yml b/.github/workflows/deduplicate.yml deleted file mode 100644 index 827bbbc..0000000 --- a/.github/workflows/deduplicate.yml +++ /dev/null @@ -1,39 +0,0 @@ -# When a PR is opened or a push is made, check code -# for duplication with PHP Copy/Paste Detector. -name: PHPCPD - -on: - pull_request: - branches: - - 'develop' - paths: - - 'app/**' - - 'src/**' - - 'tests/**' - - '.github/workflows/deduplicate.yml' - push: - branches: - - 'develop' - paths: - - 'app/**' - - 'src/**' - - 'tests/**' - - '.github/workflows/deduplicate.yml' - -jobs: - build: - name: Duplicate Code Detection - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: '8.0' - tools: phpcpd - extensions: dom, mbstring - - - name: Detect code duplication - run: phpcpd app/ src/ tests/ diff --git a/.github/workflows/inspect.yml b/.github/workflows/deptrac.yml similarity index 54% rename from .github/workflows/inspect.yml rename to .github/workflows/deptrac.yml index 23440fe..528cdc4 100644 --- a/.github/workflows/inspect.yml +++ b/.github/workflows/deptrac.yml @@ -1,41 +1,40 @@ -# When a PR is opened or a push is made, perform an -# architectural inspection on the code using Deptrac. name: Deptrac on: pull_request: branches: - - 'develop' + - develop paths: - - 'src/**' - - 'tests/**' + - '**.php' - 'composer.**' - 'depfile.yaml' - - '.github/workflows/inspect.yml' + - '.github/workflows/deptrac.yml' push: branches: - - 'develop' + - develop paths: - - 'src/**' - - 'tests/**' + - '**.php' - 'composer.**' - 'depfile.yaml' - - '.github/workflows/inspect.yml' + - '.github/workflows/deptrac.yml' jobs: build: - name: Architectural Inspection + name: Dependency Tracing runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + steps: - name: Checkout uses: actions/checkout@v2 - - name: Setup PHP + - name: Set up PHP uses: shivammathur/setup-php@v2 with: php-version: '8.0' - tools: composer, pecl, phive + tools: phive extensions: intl, json, mbstring, xml + coverage: none env: COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -43,14 +42,11 @@ jobs: id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Create composer cache directory - run: mkdir -p ${{ steps.composer-cache.outputs.dir }} - - name: Cache composer dependencies uses: actions/cache@v2 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Create Deptrac cache directory @@ -63,17 +59,16 @@ jobs: key: ${{ runner.os }}-deptrac-${{ github.sha }} restore-keys: ${{ runner.os }}-deptrac- - - name: Install dependencies (limited) - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} - run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - - - name: Install dependencies (authenticated) - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} - run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - env: - COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + - name: Install dependencies + run: | + composer -q config -g github-oauth.github.com "${{ secrets.GITHUB_TOKEN }}" + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi - - name: Run architectural inspection + - name: Trace dependencies run: | sudo phive --no-progress install --global --trust-gpg-keys B8F640134AB1782E,A98E898BB53EB748 qossmic/deptrac deptrac analyze --cache-file=build/deptrac.cache diff --git a/.github/workflows/infection.yml b/.github/workflows/infection.yml new file mode 100644 index 0000000..2e5d05d --- /dev/null +++ b/.github/workflows/infection.yml @@ -0,0 +1,73 @@ +name: Infection + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.**' + - 'phpunit*' + - '.github/workflows/infection.yml' + push: + branches: + - develop + paths: + - '**.php' + - 'composer.**' + - 'phpunit*' + - '.github/workflows/infection.yml' + +jobs: + main: + name: Mutation Testing + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + tools: infection, phpunit + extensions: intl, json, mbstring, gd, xml, sqlite3 + coverage: xdebug + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Set up problem matchers for PHPUnit + run: echo "::add-matcher::${{ runner.tool_cache }}/phpunit.json" + + - name: Configure matchers + uses: mheap/phpunit-matcher-action@v1 + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: | + composer -q config -g github-oauth.github.com "${{ secrets.GITHUB_TOKEN }}" + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi + + - name: Test with PHPUnit + run: vendor/bin/phpunit --teamcity + + - name: Mutate with Infection + run: | + git fetch --depth=1 origin $GITHUB_BASE_REF + infection --threads=2 --skip-initial-tests --coverage=build/phpunit --git-diff-base=origin/$GITHUB_BASE_REF --git-diff-filter=AM --logger-github --ignore-msi-with-no-mutations diff --git a/.github/workflows/phpcpd.yml b/.github/workflows/phpcpd.yml new file mode 100644 index 0000000..2c9d209 --- /dev/null +++ b/.github/workflows/phpcpd.yml @@ -0,0 +1,36 @@ +name: PHPCPD + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - '.github/workflows/phpcpd.yml' + push: + branches: + - develop + paths: + - '**.php' + - '.github/workflows/phpcpd.yml' + +jobs: + build: + name: Code Copy-Paste Detection + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.0' + tools: phpcpd + extensions: dom, mbstring + coverage: none + + - name: Detect duplicate code + run: phpcpd src/ tests/ diff --git a/.github/workflows/phpcsfixer.yml b/.github/workflows/phpcsfixer.yml new file mode 100644 index 0000000..6dd670f --- /dev/null +++ b/.github/workflows/phpcsfixer.yml @@ -0,0 +1,61 @@ +name: PHPCSFixer + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - '.github/workflows/phpcsfixer.yml' + push: + branches: + - develop + paths: + - '**.php' + - '.github/workflows/phpcsfixer.yml' + +jobs: + build: + name: PHP ${{ matrix.php-versions }} Coding Standards + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + strategy: + fail-fast: false + matrix: + php-versions: ['7.4', '8.0', '8.1'] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + extensions: json, tokenizer + coverage: none + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: | + composer -q config -g github-oauth.github.com "${{ secrets.GITHUB_TOKEN }}" + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi + + - name: Check code for standards compliance + run: vendor/bin/php-cs-fixer fix --verbose --ansi --dry-run --using-cache=no --diff diff --git a/.github/workflows/analyze.yml b/.github/workflows/phpstan.yml similarity index 53% rename from .github/workflows/analyze.yml rename to .github/workflows/phpstan.yml index 12c48c1..38300b9 100644 --- a/.github/workflows/analyze.yml +++ b/.github/workflows/phpstan.yml @@ -1,35 +1,33 @@ -# When a PR is opened or a push is made, perform -# a static analysis check on the code using PHPStan. name: PHPStan on: pull_request: branches: - - 'develop' + - develop paths: - - 'src/**' - - 'tests/**' + - '**.php' - 'composer.**' - 'phpstan*' - - '.github/workflows/analyze.yml' + - '.github/workflows/phpstan.yml' push: branches: - - 'develop' + - develop paths: - - 'src/**' - - 'tests/**' + - '**.php' - 'composer.**' - 'phpstan*' - - '.github/workflows/analyze.yml' + - '.github/workflows/phpstan.yml' jobs: build: name: PHP ${{ matrix.php-versions }} Static Analysis runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" strategy: fail-fast: false matrix: - php-versions: ['7.3', '7.4', '8.0'] + php-versions: ['7.4', '8.0', '8.1'] + steps: - name: Checkout uses: actions/checkout@v2 @@ -38,8 +36,9 @@ jobs: uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - tools: composer, pecl, phpunit - extensions: intl, json, mbstring, gd, mysqlnd, xdebug, xml, sqlite3 + tools: phpstan, phpunit + extensions: intl, json, mbstring, xml + coverage: none env: COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -47,14 +46,11 @@ jobs: id: composer-cache run: echo "::set-output name=dir::$(composer config cache-files-dir)" - - name: Create composer cache directory - run: mkdir -p ${{ steps.composer-cache.outputs.dir }} - - name: Cache composer dependencies uses: actions/cache@v2 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - name: Create PHPStan cache directory @@ -67,15 +63,14 @@ jobs: key: ${{ runner.os }}-phpstan-${{ github.sha }} restore-keys: ${{ runner.os }}-phpstan- - - name: Install dependencies (limited) - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} - run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - - - name: Install dependencies (authenticated) - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} - run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - env: - COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + - name: Install dependencies + run: | + composer -q config -g github-oauth.github.com "${{ secrets.GITHUB_TOKEN }}" + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi - name: Run static analysis run: vendor/bin/phpstan analyze diff --git a/.github/workflows/test.yml b/.github/workflows/phpunit.yml similarity index 62% rename from .github/workflows/test.yml rename to .github/workflows/phpunit.yml index ce38955..e1ba40e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/phpunit.yml @@ -4,32 +4,39 @@ on: pull_request: branches: - develop + paths: + - '**.php' + - 'composer.**' + - 'phpunit*' + - '.github/workflows/phpunit.yml' push: branches: - develop + paths: + - '**.php' + - 'composer.**' + - 'phpunit*' + - '.github/workflows/phpunit.yml' jobs: main: name: PHP ${{ matrix.php-versions }} Unit Tests - - strategy: - matrix: - php-versions: ['7.3', '7.4', '8.0'] - runs-on: ubuntu-latest - if: "!contains(github.event.head_commit.message, '[ci skip]')" + strategy: + matrix: + php-versions: ['7.4', '8.0', '8.1'] steps: - name: Checkout uses: actions/checkout@v2 - - name: Setup PHP, with composer and extensions + - name: Set up PHP uses: shivammathur/setup-php@v2 with: php-version: ${{ matrix.php-versions }} - tools: composer, infection, pecl, phive, phpunit - extensions: intl, json, mbstring, gd, mysqlnd, xdebug, xml, sqlite3 + tools: composer, phive, phpunit + extensions: intl, json, mbstring, gd, xdebug, xml, sqlite3 coverage: xdebug env: COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -42,18 +49,17 @@ jobs: uses: actions/cache@v2 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - - name: Install dependencies (limited) - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} - run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - - - name: Install dependencies (authenticated) - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} - run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - env: - COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + - name: Install dependencies + run: | + composer -q config -g github-oauth.github.com "${{ secrets.GITHUB_TOKEN }}" + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi - name: Test with PHPUnit run: vendor/bin/phpunit --verbose --coverage-text @@ -61,12 +67,6 @@ jobs: TERM: xterm-256color TACHYCARDIA_MONITOR_GA: enabled - - if: matrix.php-versions == '8.0' - name: Mutate with Infection - run: | - git fetch --depth=1 origin $GITHUB_BASE_REF - infection --threads=2 --skip-initial-tests --coverage=build/phpunit --git-diff-base=origin/$GITHUB_BASE_REF --git-diff-filter=AM --logger-github --ignore-msi-with-no-mutations - - if: matrix.php-versions == '8.0' name: Run Coveralls continue-on-error: true @@ -85,6 +85,7 @@ jobs: steps: - name: Upload Coveralls results uses: coverallsapp/github-action@master + continue-on-error: true with: github-token: ${{ secrets.GITHUB_TOKEN }} parallel-finished: true diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml new file mode 100644 index 0000000..fff0208 --- /dev/null +++ b/.github/workflows/rector.yml @@ -0,0 +1,68 @@ +name: Rector + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.**' + - 'rector.php' + - '.github/workflows/rector.yml' + push: + branches: + - develop + paths: + - '**.php' + - 'composer.**' + - 'rector.php' + - '.github/workflows/rector.yml' + +jobs: + build: + name: PHP ${{ matrix.php-versions }} Rector Analysis + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + strategy: + fail-fast: false + matrix: + php-versions: ['7.4', '8.0', '8.1'] + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: phpstan + extensions: intl, json, mbstring, xml + coverage: none + env: + COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Get composer cache directory + id: composer-cache + run: echo "::set-output name=dir::$(composer config cache-files-dir)" + + - name: Cache composer dependencies + uses: actions/cache@v2 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Install dependencies + run: | + composer -q config -g github-oauth.github.com "${{ secrets.GITHUB_TOKEN }}" + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi + + - name: Analyze for refactoring + run: | + composer global require --dev rector/rector:^0.12.10 + rector process --dry-run --no-progress-bar diff --git a/.github/workflows/unused.yml b/.github/workflows/unused.yml index 49e45bb..bf07cc7 100644 --- a/.github/workflows/unused.yml +++ b/.github/workflows/unused.yml @@ -1,27 +1,27 @@ -# When a PR is opened or a push is made, check code -# for unused packages with Composer Unused. name: Unused on: pull_request: branches: - - 'develop' + - develop paths: - - 'src/**' - - 'tests/**' + - '**.php' + - 'composer.**' - '.github/workflows/unused.yml' push: branches: - - 'develop' + - develop paths: - - 'src/**' - - 'tests/**' + - '**.php' + - 'composer.**' - '.github/workflows/unused.yml' jobs: build: name: Unused Package Detection runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + steps: - name: Checkout uses: actions/checkout@v2 @@ -32,6 +32,7 @@ jobs: php-version: '8.0' tools: composer, composer-unused extensions: intl, json, mbstring, xml + coverage: none env: COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -43,18 +44,27 @@ jobs: uses: actions/cache@v2 with: path: ${{ steps.composer-cache.outputs.dir }} - key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} restore-keys: ${{ runner.os }}-composer- - - name: Install dependencies (limited) - if: ${{ github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.event.pull_request.base.repo.full_name }} - run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + - name: Create PHPStan cache directory + run: mkdir -p build/phpstan - - name: Install dependencies (authenticated) - if: ${{ github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name == github.event.pull_request.base.repo.full_name }} - run: composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader - env: - COMPOSER_AUTH: ${{ secrets.COMPOSER_AUTH }} + - name: Cache PHPStan results + uses: actions/cache@v2 + with: + path: build/phpstan + key: ${{ runner.os }}-phpstan-${{ github.sha }} + restore-keys: ${{ runner.os }}-phpstan- + + - name: Install dependencies + run: | + composer -q config -g github-oauth.github.com "${{ secrets.GITHUB_TOKEN }}" + if [ -f composer.lock ]; then + composer install --no-progress --no-interaction --prefer-dist --optimize-autoloader + else + composer update --no-progress --no-interaction --prefer-dist --optimize-autoloader + fi - name: Detect unused packages - run: composer-unused -vvv --profile --ansi --no-interaction --no-progress --excludePackage=php --excludePackage=tatter/alerts --excludePackage=tatter/assets --excludePackage=tatter/audits --excludePackage=tatter/preferences --excludePackage=tatter/thumbnails + run: composer-unused -vvv --profile --ansi --no-interaction --no-progress --excludePackage=php --excludePackage=tatter/alerts --excludePackage=tatter/preferences --excludePackage=tatter/thumbnails diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index dfa45af..1185b9c 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -6,7 +6,10 @@ $finder = Finder::create() ->files() - ->in(__DIR__) + ->in([ + __DIR__ . '/src/', + __DIR__ . '/tests/', + ]) ->exclude('build') ->append([__FILE__]); diff --git a/README.md b/README.md index d2a6e0d..cf706ca 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ # Tatter\Files File uploads and management, for CodeIgniter 4 -[![](https://github.com/tattersoftware/codeigniter4-files/workflows/PHPUnit/badge.svg)](https://github.com/tattersoftware/codeigniter4-files/actions?query=workflow%3A%22PHPUnit%22) -[![](https://github.com/tattersoftware/codeigniter4-files/workflows/PHPStan/badge.svg)](https://github.com/tattersoftware/codeigniter4-files/actions?query=workflow%3A%PHPStan%22) +[![](https://github.com/tattersoftware/codeigniter4-files/workflows/PHPUnit/badge.svg)](https://github.com/tattersoftware/codeigniter4-files/actions/workflows/test.yml) +[![](https://github.com/tattersoftware/codeigniter4-files/workflows/PHPStan/badge.svg)](https://github.com/tattersoftware/codeigniter4-files/actions/workflows/analyze.yml) +[![](https://github.com/tattersoftware/codeigniter4-files/workflows/Deptrac/badge.svg)](https://github.com/tattersoftware/codeigniter4-files/actions/workflows/inspect.yml) [![Coverage Status](https://coveralls.io/repos/github/tattersoftware/codeigniter4-files/badge.svg?branch=develop)](https://coveralls.io/github/tattersoftware/codeigniter4-files?branch=develop) ## Quick Start diff --git a/composer.json b/composer.json index 87c8aae..b1af980 100644 --- a/composer.json +++ b/composer.json @@ -20,7 +20,7 @@ } ], "require": { - "php": "^7.3 || ^8.0", + "php": "^7.4 || ^8.0", "codeigniter4/authentication-implementation": "^1.0", "enyo/dropzone": "^6.0", "tatter/alerts": "^2.0", @@ -32,9 +32,17 @@ }, "require-dev": { "antecedent/patchwork": "^2.1", - "codeigniter4/codeigniter4": "dev-develop", - "tatter/imposter": "^1.0", - "tatter/tools": "^1.15" + "codeigniter4/devkit": "^1.0", + "codeigniter4/framework": "^4.1", + "tatter/imposter": "^1.0" + }, + "suggest": { + "tatter/audits": "Adds logging for user changes to Files database rows." + }, + "config": { + "allow-plugins": { + "phpstan/extension-installer": true + } }, "autoload": { "psr-4": { @@ -50,18 +58,11 @@ } }, "repositories": [ - { - "type": "vcs", - "url": "https://github.com/codeigniter4/CodeIgniter4.git" - }, { "type": "composer", "url": "https://asset-packagist.org" } ], - "suggest": { - "tatter/audits": "Adds logging for user changes to Files database rows." - }, "minimum-stability": "dev", "prefer-stable": true, "scripts": { @@ -72,11 +73,13 @@ "@analyze", "@test", "@inspect", + "rector process", "@style" ], "deduplicate": "phpcpd app/ src/", "inspect": "deptrac analyze --cache-file=build/deptrac.cache", "mutate": "infection --threads=2 --skip-initial-tests --coverage=build/phpunit", + "retool": "retool", "style": "php-cs-fixer fix --verbose --ansi --using-cache=no", "test": "phpunit" } diff --git a/depfile.yaml b/depfile.yaml index 4ca1cc7..2c4860d 100644 --- a/depfile.yaml +++ b/depfile.yaml @@ -1,7 +1,6 @@ paths: - - ./src - - ./vendor/codeigniter4/codeigniter4/system - - ./vendor/tatter + - ./src/ + - ./vendor/codeigniter4/framework/system exclude_files: - '#.*test.*#i' layers: diff --git a/infection.json.dist b/infection.json.dist index b175102..7badcc6 100644 --- a/infection.json.dist +++ b/infection.json.dist @@ -1,7 +1,7 @@ { "source": { "directories": [ - "src" + "src/" ], "excludes": [ "Config", @@ -15,5 +15,5 @@ "mutators": { "@default": true }, - "bootstrap": "vendor/codeigniter4/codeigniter4/system/Test/bootstrap.php" + "bootstrap": "vendor/codeigniter4/framework/system/Test/bootstrap.php" } diff --git a/phpstan.neon.dist b/phpstan.neon.dist index ee6f17b..e718267 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -2,10 +2,10 @@ parameters: tmpDir: build/phpstan level: 5 paths: - - src - - tests + - src/ + - tests/ bootstrapFiles: - - vendor/codeigniter4/codeigniter4/system/Test/bootstrap.php + - vendor/codeigniter4/framework/system/Test/bootstrap.php excludePaths: - src/Config/Routes.php - src/Views/* @@ -19,7 +19,8 @@ parameters: - CodeIgniter\Entity - Faker\Generator scanDirectories: - - vendor/codeigniter4/codeigniter4/system/Helpers + - src/Helpers + - vendor/codeigniter4/framework/system/Helpers - vendor/tatter/alerts/src/Helpers - vendor/tatter/handlers/src/Helpers - vendor/tatter/imposter/src/Helpers diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a90a1f2..ac88cfc 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ - ./src + ./src/ + ./src/Config ./src/Views - ./src/Config/Routes.php @@ -37,7 +37,7 @@ - + ./tests @@ -74,16 +74,16 @@ - + - + - + diff --git a/rector.php b/rector.php new file mode 100644 index 0000000..6d997d0 --- /dev/null +++ b/rector.php @@ -0,0 +1,111 @@ +import(SetList::DEAD_CODE); + $containerConfigurator->import(LevelSetList::UP_TO_PHP_74); + $containerConfigurator->import(PHPUnitSetList::PHPUNIT_SPECIFIC_METHOD); + $containerConfigurator->import(PHPUnitSetList::PHPUNIT_80); + + $parameters = $containerConfigurator->parameters(); + + $parameters->set(Option::PARALLEL, true); + // The paths to refactor (can also be supplied with CLI arguments) + $parameters->set(Option::PATHS, [ + __DIR__ . '/src/', + __DIR__ . '/tests/', + ]); + + // Do you need to include constants, class aliases, or a custom autoloader? + $parameters->set(Option::BOOTSTRAP_FILES, [ + realpath(getcwd()) . '/vendor/codeigniter4/framework/system/Test/bootstrap.php', + ]); + + // Set the target version for refactoring + $parameters->set(Option::PHP_VERSION_FEATURES, PhpVersion::PHP_74); + + // Auto-import fully qualified class names + $parameters->set(Option::AUTO_IMPORT_NAMES, true); + + // Are there files or rules you need to skip? + $parameters->set(Option::SKIP, [ + __DIR__ . '/src/Views', + + JsonThrowOnErrorRector::class, + StringifyStrNeedlesRector::class, + + // Note: requires php 8 + RemoveUnusedPromotedPropertyRector::class, + + // Ignore tests that might make calls without a result + RemoveEmptyMethodCallRector::class => [ + __DIR__ . '/tests', + ], + + // May load view files directly when detecting classes + StringClassNameToClassConstantRector::class, + + // May be uninitialized on purpose + AddDefaultValueForUndefinedVariableRector::class, + ]); + + // Additional rules to apply + $services = $containerConfigurator->services(); + $services->set(SimplifyUselessVariableRector::class); + $services->set(RemoveAlwaysElseRector::class); + $services->set(CountArrayToEmptyArrayComparisonRector::class); + $services->set(ForToForeachRector::class); + $services->set(ChangeNestedForeachIfsToEarlyContinueRector::class); + $services->set(ChangeIfElseValueAssignToEarlyReturnRector::class); + $services->set(SimplifyStrposLowerRector::class); + $services->set(CombineIfRector::class); + $services->set(SimplifyIfReturnBoolRector::class); + $services->set(InlineIfToExplicitIfRector::class); + $services->set(PreparedValueToEarlyReturnRector::class); + $services->set(ShortenElseIfRector::class); + $services->set(SimplifyIfElseToTernaryRector::class); + $services->set(UnusedForeachValueToArrayKeysRector::class); + $services->set(ChangeArrayPushToArrayAssignRector::class); + $services->set(UnnecessaryTernaryExpressionRector::class); + $services->set(AddPregQuoteDelimiterRector::class); + $services->set(SimplifyRegexPatternRector::class); + $services->set(FuncGetArgsToVariadicParamRector::class); + $services->set(MakeInheritedMethodVisibilitySameAsParentRector::class); + $services->set(SimplifyEmptyArrayCheckRector::class); + $services->set(NormalizeNamespaceByPSR4ComposerAutoloadRector::class); +}; diff --git a/src/Config/Files.php b/src/Config/Files.php index d7e418c..9955480 100644 --- a/src/Config/Files.php +++ b/src/Config/Files.php @@ -9,26 +9,22 @@ class Files extends BaseConfig { /** * Whether to include routes to the Files Controller. - * - * @var bool */ - public $routeFiles = true; + public bool $routeFiles = true; /** * View file aliases * * @var string[] */ - public $views = [ + public array $views = [ 'dropzone' => 'Tatter\Files\Views\Dropzone\config', ]; /** * Path to the default thumbnail file - * - * @var string */ - public $defaultThumbnail = 'Tatter\Files\Assets\Unavailable.jpg'; + public string $defaultThumbnail = 'Tatter\Files\Assets\Unavailable.jpg'; //-------------------------------------------------------------------- // Display Preferences @@ -37,25 +33,19 @@ class Files extends BaseConfig /** * Display format for listing files. * Included options are 'cards', 'list', 'select' - * - * @var string */ - public $format = 'cards'; + public string $format = 'cards'; /** * Default sort column. * See FileModel::$allowedFields for options. - * - * @var string */ - public $sort = 'filename'; + public string $sort = 'filename'; /** * Default sort ordering. "asc" or "desc" - * - * @var string */ - public $order = 'asc'; + public string $order = 'asc'; //-------------------------------------------------------------------- // Storage Options @@ -64,10 +54,8 @@ class Files extends BaseConfig /** * Directory to store files (with trailing slash). * Usually best to use getPath() - * - * @var string */ - protected $path = WRITEPATH . 'files' . DIRECTORY_SEPARATOR; + protected string $path = WRITEPATH . 'files' . DIRECTORY_SEPARATOR; /** * Normalizes and creates (if necessary) the storage and thumbnail paths, @@ -85,7 +73,7 @@ public function getPath(): string } // Normalize the storage path - $this->path = realpath($storage) ?: $storage; + $storage = realpath($storage) ?: $storage; $this->path = rtrim($storage, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR; // Check or create the thumbnails subdirectory diff --git a/src/Config/Routes.php b/src/Config/Routes.php index 6812506..f07c980 100644 --- a/src/Config/Routes.php +++ b/src/Config/Routes.php @@ -1,14 +1,14 @@ routeFiles)) { return; } - $options = [ 'filter' => 'assets:\Tatter\Files\Bundles\FilesBundle', 'namespace' => '\Tatter\Files\Controllers', ]; - // Routes to Files controller $routes->group('files', $options, static function ($routes) { $routes->get('/', 'Files::index'); diff --git a/src/Controllers/Files.php b/src/Controllers/Files.php index 888f73e..4b3f18e 100644 --- a/src/Controllers/Files.php +++ b/src/Controllers/Files.php @@ -19,17 +19,13 @@ class Files extends Controller { /** * Files config. - * - * @var FilesConfig */ - protected $config; + protected FilesConfig $config; /** * The model to use, may be a child of this library's. - * - * @var FileModel */ - protected $model; + protected FileModel $model; /** * Helpers to load. @@ -38,17 +34,15 @@ class Files extends Controller /** * Overriding data for views. - * - * @var array */ - protected $data = []; + protected array $data = []; /** * Validation for Preferences. * * @var array */ - protected $preferenceRules = [ + protected array $preferenceRules = [ 'sort' => 'in_list[filename,localname,clientname,type,size,created_at,updated_at,deleted_at]', 'order' => 'in_list[asc,desc]', 'format' => 'in_list[cards,list,select]', @@ -139,7 +133,7 @@ public function index() public function user($userId = null) { // Figure out user & access - $userId = $userId ?? user_id(); + $userId ??= user_id(); // Not logged in if ($userId === null) { @@ -379,7 +373,7 @@ public function upload() // Get chunks from target directory helper('filesystem'); $chunks = get_filenames($chunkDir, true); - if (empty($chunks)) { + if ($chunks === []) { throw FilesException::forNoChunks($chunkDir); } @@ -396,9 +390,9 @@ public function upload() } // Get additional post data to pass to model - $data = $this->request->getPost(); - $data['filename'] = $data['filename'] ?? $upload->getClientName(); - $data['clientname'] = $data['clientname'] ?? $upload->getClientName(); + $data = $this->request->getPost(); + $data['filename'] ??= $upload->getClientName(); + $data['clientname'] ??= $upload->getClientName(); // Accept the file $file = $this->model->createFromPath($path ?? $upload->getRealPath(), $data); @@ -473,19 +467,13 @@ public function export(string $slug, $fileId): ResponseInterface */ public function thumbnail($fileId): ResponseInterface { - if ($file = $this->model->find($fileId)) { - $path = $file->getThumbnail(); - } else { - $path = File::locateDefaultThumbnail(); - } + $path = ($file = $this->model->find($fileId)) ? $file->getThumbnail() : File::locateDefaultThumbnail(); return $this->response->setHeader('Content-type', 'image/jpeg')->setBody(file_get_contents($path)); } /** * Handles failures. - * - * @return RedirectResponse|ResponseInterface */ protected function failure(int $code, string $message, ?bool $isAjax = null): ResponseInterface { @@ -511,11 +499,7 @@ protected function failure(int $code, string $message, ?bool $isAjax = null): Re */ protected function setData(array $data, bool $overwrite = false): self { - if ($overwrite) { - $this->data = array_merge($this->data, $data); - } else { - $this->data = array_merge($data, $this->data); - } + $this->data = $overwrite ? array_merge($this->data, $data) : array_merge($data, $this->data); return $this; } @@ -565,16 +549,17 @@ protected function setPreferences(): self $validation = service('validation'); foreach ($this->preferenceRules as $field => $rule) { - if (null !== $value = $this->request->getVar($field)) { - if ($validation->check($value, $rule)) { - // Special case for perPage - $preference = $field === 'perPage' - ? 'Pager.' . $field - : 'Files.' . $field; - - preference($preference, $value); - } + if (null === ($value = $this->request->getVar($field))) { + continue; + } + if (! $validation->check($value, $rule)) { + continue; } + // Special case for perPage + $preference = $field === 'perPage' + ? 'Pager.' . $field + : 'Files.' . $field; + preference($preference, $value); } return $this; diff --git a/src/Entities/File.php b/src/Entities/File.php index 45e8b61..37bb218 100644 --- a/src/Entities/File.php +++ b/src/Entities/File.php @@ -17,10 +17,8 @@ class File extends Entity /** * Resolved path to the default thumbnail - * - * @var string|null */ - protected static $defaultThumbnail; + protected static ?string $defaultThumbnail; /** * Returns the absolute path to the configured default thumbnail @@ -62,26 +60,18 @@ public function getPath(): string public function getExtension($method = ''): string { if ($this->attributes['type'] !== 'application/octet-stream') { - if (! $method || $method === 'type') { - if ($extension = Mimes::guessExtensionFromType($this->attributes['type'])) { - return $extension; - } + if ((! $method || $method === 'type') && ($extension = Mimes::guessExtensionFromType($this->attributes['type']))) { + return $extension; } - if (! $method || $method === 'mime') { - if ($file = $this->getObject()) { - if ($extension = $file->guessExtension()) { - return $extension; - } - } + if ((! $method || $method === 'mime') && ($file = $this->getObject()) && ($extension = $file->guessExtension())) { + return $extension; } } foreach (['clientname', 'localname', 'filename'] as $attribute) { - if (! $method || $method === $attribute) { - if ($extension = pathinfo($this->attributes[$attribute], PATHINFO_EXTENSION)) { - return $extension; - } + if ((! $method || $method === $attribute) && ($extension = pathinfo($this->attributes[$attribute], PATHINFO_EXTENSION))) { + return $extension; } } diff --git a/src/Exceptions/FilesException.php b/src/Exceptions/FilesException.php index 3c00a05..1b88176 100644 --- a/src/Exceptions/FilesException.php +++ b/src/Exceptions/FilesException.php @@ -3,8 +3,9 @@ namespace Tatter\Files\Exceptions; use CodeIgniter\Exceptions\ExceptionInterface; +use RuntimeException; -class FilesException extends \RuntimeException implements ExceptionInterface +class FilesException extends RuntimeException implements ExceptionInterface { public static function forDirFail($dir) { diff --git a/src/Helpers/files_helper.php b/src/Helpers/files_helper.php index 86817ec..014bec6 100644 --- a/src/Helpers/files_helper.php +++ b/src/Helpers/files_helper.php @@ -118,7 +118,7 @@ function return_bytes(string $value): int // If it is not one of those modifiers then it was numerical bytes, add the final digit back // no break default: - $num = (int) ((string) $num . $unit); + $num = (int) ($num . $unit); } return $num; diff --git a/src/Language/en/Files.php b/src/Language/en/Files.php index a07424d..f9b8dea 100644 --- a/src/Language/en/Files.php +++ b/src/Language/en/Files.php @@ -1,5 +1,7 @@ 'Missing dependency: authentication function user_id()', diff --git a/src/Models/FileModel.php b/src/Models/FileModel.php index d85eeef..d4e08b1 100644 --- a/src/Models/FileModel.php +++ b/src/Models/FileModel.php @@ -8,6 +8,7 @@ use Faker\Generator; use Tatter\Files\Entities\File; use Tatter\Permits\Traits\PermitsTrait; +use Throwable; class FileModel extends Model { @@ -139,7 +140,7 @@ public function createFromFile(CIFile $file, array $data = []): File $this->update($fileId, [ 'thumbnail' => $thumbnail, ]); - } catch (\Throwable $e) { + } catch (Throwable $e) { log_message('error', $e->getMessage()); log_message('error', 'Unable to create thumbnail for ' . $row['filename']); @@ -165,7 +166,7 @@ public function fake(Generator &$faker): File 'localname' => $faker->md5, 'clientname' => $name, 'type' => $faker->mimeType, - 'size' => mt_rand(1000, 4000000), + 'size' => random_int(1000, 4_000_000), 'thumbnail' => $faker->text(5000), ]); } diff --git a/src/Publishers/DropzonePublisher.php b/src/Publishers/DropzonePublisher.php index fb99939..307d151 100644 --- a/src/Publishers/DropzonePublisher.php +++ b/src/Publishers/DropzonePublisher.php @@ -6,17 +6,12 @@ class DropzonePublisher extends FrontendPublisher { - /** - * @var string - */ protected $source = 'vendor/enyo/dropzone/dist'; /** * Destination path relative to AssetsConfig::directory. * * @see FrontendPublisher::__construct() - * - * @var string */ protected $path = 'dropzone'; diff --git a/src/Structures/FileObject.php b/src/Structures/FileObject.php index b07efee..0a620e2 100644 --- a/src/Structures/FileObject.php +++ b/src/Structures/FileObject.php @@ -15,10 +15,8 @@ class FileObject extends File { /** * Base file name to override disk version - * - * @var string|null */ - protected $basename; + protected ?string $basename; /** * Returns the full path to this file diff --git a/src/Views/Formats/select.php b/src/Views/Formats/select.php index fde58c6..7e537ff 100644 --- a/src/Views/Formats/select.php +++ b/src/Views/Formats/select.php @@ -2,7 +2,7 @@

No files to display.

- + diff --git a/tests/_support/FeatureTestCase.php b/tests/_support/FeatureTestCase.php index b64227e..70684bc 100644 --- a/tests/_support/FeatureTestCase.php +++ b/tests/_support/FeatureTestCase.php @@ -13,26 +13,8 @@ abstract class FeatureTestCase extends TestCase { use FeatureTestTrait; - /** - * If present, will override application - * routes when using call(). - * - * @var \CodeIgniter\Router\RouteCollection - */ - protected $routes; - - /** - * Values to be set in the SESSION global - * before running the test. - * - * @var array - */ - protected $session = []; - /** * Enabled auto clean op buffer after request call - * - * @var bool */ protected $clean = true; diff --git a/tests/_support/TestCase.php b/tests/_support/TestCase.php index de49c7f..2ba3229 100644 --- a/tests/_support/TestCase.php +++ b/tests/_support/TestCase.php @@ -22,10 +22,8 @@ abstract class TestCase extends CIUnitTestCase /** * Path to a test file to work with - * - * @var string */ - protected $testPath; + protected string $testPath; public static function setUpBeforeClass(): void { diff --git a/tests/feature/DisplayTest.php b/tests/feature/DisplayTest.php index 52a3ecb..4b2155b 100644 --- a/tests/feature/DisplayTest.php +++ b/tests/feature/DisplayTest.php @@ -57,7 +57,7 @@ public function testFormat(string $format, string $configFormat) { $_REQUEST['format'] = $format; - $file = fake(FileModel::class); + fake(FileModel::class); $result = $this->get('files'); $result->assertStatus(200); @@ -84,7 +84,7 @@ public function testSorts(string $sort, string $configSort) { $_REQUEST['sort'] = $sort; - $file = fake(FileModel::class); + fake(FileModel::class); $result = $this->get('files'); $result->assertStatus(200); @@ -107,7 +107,7 @@ public function testOrders(string $order, string $configOrder) { $_REQUEST['order'] = $order; - $file = fake(FileModel::class); + fake(FileModel::class); $result = $this->get('files'); $result->assertStatus(200); @@ -129,7 +129,7 @@ public function testSearches(string $keyword) { $_REQUEST['search'] = $keyword; - $file = fake(FileModel::class); + fake(FileModel::class); $result = $this->get('files'); $result->assertStatus(200); diff --git a/tests/feature/PermissionsTest.php b/tests/feature/PermissionsTest.php index 7a9411e..70c0760 100644 --- a/tests/feature/PermissionsTest.php +++ b/tests/feature/PermissionsTest.php @@ -32,11 +32,7 @@ final class PermissionsTest extends FeatureTestCase */ private const PROCTOR_ID = 104; - /** - * @var FileModel - */ - protected $model; - + protected FileModel $model; protected $seeded = false; /** diff --git a/tests/misc/ControllerTest.php b/tests/misc/ControllerTest.php index f17bb46..174ee57 100644 --- a/tests/misc/ControllerTest.php +++ b/tests/misc/ControllerTest.php @@ -46,7 +46,7 @@ public function testSetPreferencesUsesValidInput() ]; $method = $this->getPrivateMethodInvoker($this->controller, 'setPreferences'); - $result = $method(); + $method(); $this->assertSame('size', preference('Files.sort')); $this->assertSame('desc', preference('Files.order')); @@ -65,7 +65,7 @@ public function testSetPreferencesIgnoreInvalidInput() ]; $method = $this->getPrivateMethodInvoker($this->controller, 'setPreferences'); - $result = $method(); + $method(); $this->assertSame($config->sort, preference('Files.sort')); $this->assertSame($config->order, preference('Files.order')); diff --git a/tests/misc/ModelTest.php b/tests/misc/ModelTest.php index 2744b65..2688817 100644 --- a/tests/misc/ModelTest.php +++ b/tests/misc/ModelTest.php @@ -9,10 +9,7 @@ */ final class ModelTest extends TestCase { - /** - * @var FileModel - */ - protected $model; + protected FileModel $model; protected function setUp(): void {