diff --git a/.editorconfig b/.editorconfig index cd8eb86..7382bb8 100644 --- a/.editorconfig +++ b/.editorconfig @@ -11,5 +11,5 @@ end_of_line = lf insert_final_newline = true trim_trailing_whitespace = true -[*.md] -trim_trailing_whitespace = false +[*.{yml,yaml}] +indent_size = 2 diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml deleted file mode 100644 index 12c48c1..0000000 --- a/.github/workflows/analyze.yml +++ /dev/null @@ -1,81 +0,0 @@ -# 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' - paths: - - 'src/**' - - 'tests/**' - - 'composer.**' - - 'phpstan*' - - '.github/workflows/analyze.yml' - push: - branches: - - 'develop' - paths: - - 'src/**' - - 'tests/**' - - 'composer.**' - - 'phpstan*' - - '.github/workflows/analyze.yml' - -jobs: - build: - name: PHP ${{ matrix.php-versions }} Static Analysis - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - php-versions: ['7.3', '7.4', '8.0'] - steps: - - name: Checkout - uses: actions/checkout@v2 - - - name: Setup PHP - uses: shivammathur/setup-php@v2 - with: - php-version: ${{ matrix.php-versions }} - tools: composer, pecl, phpunit - extensions: intl, json, mbstring, gd, mysqlnd, xdebug, xml, sqlite3 - 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: 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') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Create PHPStan cache directory - run: mkdir -p build/phpstan - - - 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 (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: Run static analysis - run: vendor/bin/phpstan analyze 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/deptrac.yml b/.github/workflows/deptrac.yml new file mode 100644 index 0000000..9042772 --- /dev/null +++ b/.github/workflows/deptrac.yml @@ -0,0 +1,73 @@ +name: Deptrac + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'depfile.yaml' + - '.github/workflows/deptrac.yml' + push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'depfile.yaml' + - '.github/workflows/deptrac.yml' + +jobs: + build: + name: Dependency Tracing + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: phive + extensions: intl, json, mbstring, xml + coverage: none + env: + GITHUB_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@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Create Deptrac cache directory + run: mkdir -p build/ + + - name: Cache Deptrac results + uses: actions/cache@v3 + with: + path: build + key: ${{ runner.os }}-deptrac-${{ github.sha }} + restore-keys: ${{ runner.os }}-deptrac- + + - name: Install dependencies + run: | + 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: 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/inspect.yml b/.github/workflows/inspect.yml deleted file mode 100644 index 23440fe..0000000 --- a/.github/workflows/inspect.yml +++ /dev/null @@ -1,79 +0,0 @@ -# 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' - paths: - - 'src/**' - - 'tests/**' - - 'composer.**' - - 'depfile.yaml' - - '.github/workflows/inspect.yml' - push: - branches: - - 'develop' - paths: - - 'src/**' - - 'tests/**' - - 'composer.**' - - 'depfile.yaml' - - '.github/workflows/inspect.yml' - -jobs: - build: - name: Architectural Inspection - 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: composer, pecl, phive - extensions: intl, json, mbstring, xml - 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: 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') }} - restore-keys: ${{ runner.os }}-composer- - - - name: Create Deptrac cache directory - run: mkdir -p build/ - - - name: Cache Deptrac results - uses: actions/cache@v2 - with: - path: build - 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: Run architectural inspection - 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/phpcpd.yml b/.github/workflows/phpcpd.yml new file mode 100644 index 0000000..41abe9a --- /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@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + 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..302e3ae --- /dev/null +++ b/.github/workflows/phpcsfixer.yml @@ -0,0 +1,56 @@ +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: Coding Standards + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + extensions: json, tokenizer + coverage: none + env: + GITHUB_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@v3 + 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: | + 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/phpstan.yml b/.github/workflows/phpstan.yml new file mode 100644 index 0000000..55c2bd9 --- /dev/null +++ b/.github/workflows/phpstan.yml @@ -0,0 +1,75 @@ +name: PHPStan + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpstan*' + - '.github/workflows/phpstan.yml' + push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'phpstan*' + - '.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.4', '8.0', '8.1'] + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php-versions }} + tools: phpstan, phpunit + extensions: intl, json, mbstring, xml + coverage: none + env: + GITHUB_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@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Create PHPStan cache directory + run: mkdir -p build/phpstan + + - name: Cache PHPStan results + uses: actions/cache@v3 + with: + path: build/phpstan + key: ${{ runner.os }}-phpstan-${{ github.sha }} + restore-keys: ${{ runner.os }}-phpstan- + + - name: Install dependencies + run: | + 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 58% rename from .github/workflows/test.yml rename to .github/workflows/phpunit.yml index ce38955..cd165a5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/phpunit.yml @@ -4,56 +4,61 @@ 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 + uses: actions/checkout@v3 - - 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 }} + GITHUB_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 + uses: actions/cache@v3 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: | + 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 +66,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 +84,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/psalm.yml b/.github/workflows/psalm.yml new file mode 100644 index 0000000..aabe1f2 --- /dev/null +++ b/.github/workflows/psalm.yml @@ -0,0 +1,71 @@ +name: Psalm + +on: + pull_request: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'psalm*' + - '.github/workflows/psalm.yml' + push: + branches: + - develop + paths: + - '**.php' + - 'composer.*' + - 'psalm*' + - '.github/workflows/psalm.yml' + +jobs: + build: + name: Psalm Analysis + runs-on: ubuntu-latest + if: "!contains(github.event.head_commit.message, '[ci skip]')" + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.1' + tools: phpstan, phpunit + extensions: intl, json, mbstring, xml + coverage: none + env: + GITHUB_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@v3 + with: + path: ${{ steps.composer-cache.outputs.dir }} + key: ${{ runner.os }}-composer-${{ hashFiles('**/composer.json') }}-${{ hashFiles('**/composer.lock') }} + restore-keys: ${{ runner.os }}-composer- + + - name: Create Psalm cache directory + run: mkdir -p build/psalm + + - name: Cache Psalm results + uses: actions/cache@v3 + with: + path: build/psalm + key: ${{ runner.os }}-psalm-${{ github.sha }} + restore-keys: ${{ runner.os }}-psalm- + + - name: Install dependencies + run: | + 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 Psalm analysis + run: vendor/bin/psalm diff --git a/.github/workflows/rector.yml b/.github/workflows/rector.yml new file mode 100644 index 0000000..d8bd7b0 --- /dev/null +++ b/.github/workflows/rector.yml @@ -0,0 +1,67 @@ +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@v3 + + - 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: + GITHUB_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@v3 + 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: | + 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.15 + rector process --dry-run --no-progress-bar diff --git a/.github/workflows/unused.yml b/.github/workflows/unused.yml index 4d2ee36..0506531 100644 --- a/.github/workflows/unused.yml +++ b/.github/workflows/unused.yml @@ -1,60 +1,59 @@ -# 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 + uses: actions/checkout@v3 - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.0' + php-version: '8.1' tools: composer, composer-unused extensions: intl, json, mbstring, xml + coverage: none env: - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_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 + uses: actions/cache@v3 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: | + 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 + run: composer-unused -vvv --output-format=github --ansi --no-interaction --no-progress 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/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..b712dd6 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,10 @@ +# Contributing to CodeIgniter4 + +CodeIgniter is a community driven project and accepts contributions of +code and documentation from the community. + +If you'd like to contribute, please read [Contributing to CodeIgniter](https://github.com/codeigniter4/CodeIgniter4/blob/develop/contributing/README.md) +in the [main repository](https://github.com/codeigniter4/CodeIgniter4). + +If you are going to contribute to this repository, please report bugs or send PRs +to this repository instead of the main repository. diff --git a/README.md b/README.md index 97fef1e..7d3ebae 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,12 @@ config classes. ## Quick Start -1. Install with Composer: `> composer require codeigniter4/settings` +1. Install with Composer: + + ```console + composer require codeigniter4/settings + ``` + 2. Create a new migration and copy the provided class from below into it. `Settings` provides a simple interface that you can use in place of calling `config()` to allow you to read and store @@ -24,7 +29,10 @@ and still allows your users to override those settings once the site is live. Install easily via Composer to take advantage of CodeIgniter 4's autoloading capabilities and always be up-to-date: -* `> composer require codeigniter4/settings` + +```console +composer require codeigniter4/settings +``` Or, install manually by downloading the source files and adding the directory to `app/Config/Autoload.php`. @@ -33,13 +41,21 @@ Or, install manually by downloading the source files and adding the directory to In order to store the settings in the database, you can run the provided migration: +```console +php spark migrate --all ``` -> php spark migrate --all -``` -This will also migrate all other packages. If you don't want to do that you can copy the file -from `vendor/codeigniter4/settings/src/Database/Migrations/2021-07-04-041948_CreateSettingsTable.php` -into `app/Database/Migrations`, and migrate without the `--all` flag. +This will also migrate all other packages. If you don't want to do that you can run migrate with the `-n` flag: + +1. **For Windows:** + ```console + php spark migrate -n CodeIgniter\Settings + ``` + +2. **For Unix:** + ```console + php spark migrate -n CodeIgniter\\Settings + ``` ## dot Notation @@ -70,25 +86,25 @@ will be converted back into a boolean when retrieved. Arrays and objects are ser when retrieved. ```php -service('setting')->set('App.siteName', 'My Great Site'); +service('settings')->set('App.siteName', 'My Great Site'); ``` You can delete a value from the persistent storage with the `forget()` method. Since it is removed from the storage, it effectively resets itself back to the default value in config file, if any. ```php -service('setting')->forget('App.siteName') +service('settings')->forget('App.siteName'); ``` ### Contextual Settings -In addition to the default behavior describe above, `Settings` can can be used to define "contextual settings". +In addition to the default behavior describe above, `Settings` can be used to define "contextual settings". A context may be anything you want, but common examples are a runtime environment or an authenticated user. In order to use a context you pass it as an additional parameter to the `get()`/`set()`/`forget()` methods; if a context setting is requested and does not exist then the general value will be used. Contexts may be any unique string you choose, but a recommended format for supplying some consistency is to -give them a category and identifier, like `environment:production` or `group:42`. +give them a category and identifier, like `environment:production`, `group:superadmin` or `lang:en`. An example... Say your App config includes the name of a theme to use to enhance your display. By default your config file specifies `App.theme = 'default'`. When a user changes their theme, you do not want this to @@ -96,14 +112,14 @@ change the theme for all visitors to the site, so you need to provide the user a ```php $context = 'user:' . user_id(); -service('setting')->set('App.theme', 'dark', $context); +service('settings')->set('App.theme', 'dark', $context); ``` Now when your filter is determining which theme to apply it can check for the current user as the context: ```php $context = 'user:' . user_id(); -$theme = service('setting')->get('App.theme', $context); +$theme = service('settings')->get('App.theme', $context); // or using the helper setting()->get('App.theme', $context); @@ -133,7 +149,7 @@ setting()->set('App.siteName', 'My Great Site'); setting()->forget('App.siteName'); ``` -> Note: Due to the shorthand nature of the helper function it cannot access contextual settings. +> **Note** Due to the shorthand nature of the helper function it cannot access contextual settings. ## Known Limitations diff --git a/SECURITY.md b/SECURITY.md index 81800d9..7879188 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -6,7 +6,7 @@ The development team and community take all security issues seriously. **Please Thank you for improving the security of our code! Any assistance in removing security flaws will be acknowledged. -**Please report security flaws by emailing the development team directly: **security@codeigniter.com**. +**Please report security flaws by emailing the development team directly: security@codeigniter.com**. The lead maintainer will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating the next steps in handling your report. After the initial reply to your report, the security team will endeavor to keep you informed of the diff --git a/composer-unused.php b/composer-unused.php new file mode 100644 index 0000000..6ba235a --- /dev/null +++ b/composer-unused.php @@ -0,0 +1,15 @@ + $config + // ->addNamedFilter(NamedFilter::fromString('symfony/config')) + // ->addPatternFilter(PatternFilter::fromString('/symfony-.*/')) + ->setAdditionalFilesFor('codeigniter4/framework', [ + ...Glob::glob(__DIR__ . '/vendor/codeigniter4/framework/system/Helpers/*.php'), + ]); diff --git a/composer.json b/composer.json index b79fa47..1ac2c78 100644 --- a/composer.json +++ b/composer.json @@ -1,14 +1,13 @@ { "name": "codeigniter4/settings", - "type": "library", "description": "Settings library for CodeIgniter 4", + "license": "MIT", + "type": "library", "keywords": [ "codeigniter", "codeigniter4", "settings" ], - "homepage": "https://github.com/codeigniter4/settings", - "license": "MIT", "authors": [ { "name": "Lonnie Ezell", @@ -16,21 +15,17 @@ "role": "Developer" } ], + "homepage": "https://github.com/codeigniter4/settings", "require": { - "php": "^7.3 || ^8.0" + "php": "^7.4 || ^8.0" }, "require-dev": { - "codeigniter/coding-standard": "^1.1", - "codeigniter4/codeigniter4": "dev-develop", - "fakerphp/faker": "^1.9", - "mockery/mockery": "^1.0", - "nexusphp/cs-config": "^3.1", - "nexusphp/tachycardia": "^1.0", - "php-coveralls/php-coveralls": "^2.4", - "phpstan/phpstan": "^1.0", - "phpunit/phpunit": "^9.0", - "squizlabs/php_codesniffer": "^3.3" + "codeigniter4/devkit": "^1.0", + "codeigniter4/framework": "^4.2.3", + "rector/rector": "0.16.0" }, + "minimum-stability": "dev", + "prefer-stable": true, "autoload": { "psr-4": { "CodeIgniter\\Settings\\": "src" @@ -45,31 +40,35 @@ "Tests\\Support\\": "tests/_support" } }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/codeigniter4/CodeIgniter4" + "config": { + "allow-plugins": { + "phpstan/extension-installer": true } - ], - "minimum-stability": "dev", - "prefer-stable": true, + }, "scripts": { "post-update-cmd": [ "bash -c \"if [ -f admin/setup.sh ]; then bash admin/setup.sh; fi\"" ], - "analyze": "phpstan analyze", + "analyze": [ + "phpstan analyze", + "psalm", + "rector process --dry-run" + ], + "sa": "@analyze", "ci": [ "Composer\\Config::disableProcessTimeout", + "@cs", "@deduplicate", "@analyze", - "@test", "@inspect", - "@style" + "@test" ], + "cs": "php-cs-fixer fix --ansi --verbose --dry-run --diff", + "cs-fix": "php-cs-fixer fix --ansi --verbose --diff --using-cache=yes", + "style": "@cs-fix", "deduplicate": "phpcpd app/ src/", "inspect": "deptrac analyze --cache-file=build/deptrac.cache", "mutate": "infection --threads=2 --skip-initial-tests --coverage=build/phpunit", - "style": "php-cs-fixer fix --verbose --ansi --using-cache=no", "test": "phpunit" } } diff --git a/depfile.yaml b/depfile.yaml deleted file mode 100644 index 23e144a..0000000 --- a/depfile.yaml +++ /dev/null @@ -1,155 +0,0 @@ -paths: - - ./src - - ./vendor/codeigniter4/codeigniter4/system -exclude_files: - - '#.*test.*#i' -layers: - - name: Model - collectors: - - type: bool - must: - - type: className - regex: .*[A-Za-z]+Model$ - must_not: - - type: directory - regex: vendor/.* - - name: Vendor Model - collectors: - - type: bool - must: - - type: className - regex: .*[A-Za-z]+Model$ - - type: directory - regex: vendor/.* - - name: Controller - collectors: - - type: bool - must: - - type: className - regex: .*\/Controllers\/.* - must_not: - - type: directory - regex: vendor/.* - - name: Vendor Controller - collectors: - - type: bool - must: - - type: className - regex: .*\/Controllers\/.* - - type: directory - regex: vendor/.* - - name: Config - collectors: - - type: bool - must: - - type: directory - regex: src/Config/.* - must_not: - - type: className - regex: .*Services - - type: directory - regex: vendor/.* - - name: Vendor Config - collectors: - - type: bool - must: - - type: directory - regex: vendor/.*/Config/.* - must_not: - - type: className - regex: .*Services - - name: Entity - collectors: - - type: bool - must: - - type: directory - regex: src/Entities/.* - must_not: - - type: directory - regex: vendor/.* - - name: Vendor Entity - collectors: - - type: bool - must: - - type: directory - regex: vendor/.*/Entities/.* - - name: View - collectors: - - type: bool - must: - - type: directory - regex: src/Views/.* - must_not: - - type: directory - regex: vendor/.* - - name: Vendor View - collectors: - - type: bool - must: - - type: directory - regex: vendor/.*/Views/.* - - name: Service - collectors: - - type: className - regex: .*Services.* -ruleset: - Entity: - - Config - - Model - - Service - - Vendor Config - - Vendor Entity - - Vendor Model - Config: - - Service - - Vendor Config - Model: - - Config - - Entity - - Service - - Vendor Config - - Vendor Entity - - Vendor Model - Service: - - Config - - Vendor Config - - # Ignore anything in the Vendor layers - Vendor Model: - - Config - - Service - - Vendor Config - - Vendor Controller - - Vendor Entity - - Vendor Model - - Vendor View - Vendor Controller: - - Service - - Vendor Config - - Vendor Controller - - Vendor Entity - - Vendor Model - - Vendor View - Vendor Config: - - Config - - Service - - Vendor Config - - Vendor Controller - - Vendor Entity - - Vendor Model - - Vendor View - Vendor Entity: - - Service - - Vendor Config - - Vendor Controller - - Vendor Entity - - Vendor Model - - Vendor View - Vendor View: - - Service - - Vendor Config - - Vendor Controller - - Vendor Entity - - Vendor Model - - Vendor View -skip_violations: diff --git a/deptrac.yaml b/deptrac.yaml new file mode 100644 index 0000000..ea116b1 --- /dev/null +++ b/deptrac.yaml @@ -0,0 +1,156 @@ +parameters: + paths: + - ./src/ + - ./vendor/codeigniter4/framework/system/ + exclude_files: + - '#.*test.*#i' + layers: + - name: Model + collectors: + - type: bool + must: + - type: className + regex: .*[A-Za-z]+Model$ + must_not: + - type: directory + regex: vendor/.* + - name: Vendor Model + collectors: + - type: bool + must: + - type: className + regex: .*[A-Za-z]+Model$ + - type: directory + regex: vendor/.* + - name: Controller + collectors: + - type: bool + must: + - type: className + regex: .*\/Controllers\/.* + must_not: + - type: directory + regex: vendor/.* + - name: Vendor Controller + collectors: + - type: bool + must: + - type: className + regex: .*\/Controllers\/.* + - type: directory + regex: vendor/.* + - name: Config + collectors: + - type: bool + must: + - type: directory + regex: src/Config/.* + must_not: + - type: className + regex: .*Services + - type: directory + regex: vendor/.* + - name: Vendor Config + collectors: + - type: bool + must: + - type: directory + regex: vendor/.*/Config/.* + must_not: + - type: className + regex: .*Services + - name: Entity + collectors: + - type: bool + must: + - type: directory + regex: src/Entities/.* + must_not: + - type: directory + regex: vendor/.* + - name: Vendor Entity + collectors: + - type: bool + must: + - type: directory + regex: vendor/.*/Entities/.* + - name: View + collectors: + - type: bool + must: + - type: directory + regex: src/Views/.* + must_not: + - type: directory + regex: vendor/.* + - name: Vendor View + collectors: + - type: bool + must: + - type: directory + regex: vendor/.*/Views/.* + - name: Service + collectors: + - type: className + regex: .*Services.* + ruleset: + Entity: + - Config + - Model + - Service + - Vendor Config + - Vendor Entity + - Vendor Model + Config: + - Service + - Vendor Config + Model: + - Config + - Entity + - Service + - Vendor Config + - Vendor Entity + - Vendor Model + Service: + - Config + - Vendor Config + + # Ignore anything in the Vendor layers + Vendor Model: + - Config + - Service + - Vendor Config + - Vendor Controller + - Vendor Entity + - Vendor Model + - Vendor View + Vendor Controller: + - Service + - Vendor Config + - Vendor Controller + - Vendor Entity + - Vendor Model + - Vendor View + Vendor Config: + - Config + - Service + - Vendor Config + - Vendor Controller + - Vendor Entity + - Vendor Model + - Vendor View + Vendor Entity: + - Service + - Vendor Config + - Vendor Controller + - Vendor Entity + - Vendor Model + - Vendor View + Vendor View: + - Service + - Vendor Config + - Vendor Controller + - Vendor Entity + - Vendor Model + - Vendor View + skip_violations: 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 4d2089e..f27f414 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/* @@ -15,7 +15,7 @@ parameters: - CodeIgniter\Entity\Entity - Faker\Generator scanDirectories: - - vendor/codeigniter4/codeigniter4/system/Helpers + - vendor/codeigniter4/framework/system/Helpers dynamicConstantNames: - APP_NAMESPACE - CI_DEBUG 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/psalm-baseline.xml b/psalm-baseline.xml new file mode 100644 index 0000000..d5494fe --- /dev/null +++ b/psalm-baseline.xml @@ -0,0 +1,8 @@ + + + + + $query->get() + + + diff --git a/psalm.xml b/psalm.xml new file mode 100644 index 0000000..34d358b --- /dev/null +++ b/psalm.xml @@ -0,0 +1,19 @@ + + + + + + + + + + diff --git a/psalm_autoload.php b/psalm_autoload.php new file mode 100644 index 0000000..046a273 --- /dev/null +++ b/psalm_autoload.php @@ -0,0 +1,27 @@ +sets([SetList::DEAD_CODE, LevelSetList::UP_TO_PHP_74, PHPUnitSetList::PHPUNIT_SPECIFIC_METHOD, PHPUnitSetList::PHPUNIT_80]); + $rectorConfig->parallel(); + // The paths to refactor (can also be supplied with CLI arguments) + $rectorConfig->paths([ + __DIR__ . '/src/', + __DIR__ . '/tests/', + ]); + + // Include Composer's autoload - required for global execution, remove if running locally + $rectorConfig->autoloadPaths([ + __DIR__ . '/vendor/autoload.php', + ]); + + // Do you need to include constants, class aliases, or a custom autoloader? + $rectorConfig->bootstrapFiles([ + realpath(getcwd()) . '/vendor/codeigniter4/framework/system/Test/bootstrap.php', + ]); + + if (is_file(__DIR__ . '/phpstan.neon.dist')) { + $rectorConfig->phpstanConfig(__DIR__ . '/phpstan.neon.dist'); + } + + // Set the target version for refactoring + $rectorConfig->phpVersion(PhpVersion::PHP_74); + + // Auto-import fully qualified class names + $rectorConfig->importNames(); + + // Are there files or rules you need to skip? + $rectorConfig->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', + ], + + // Ignore files that should not be namespaced + NormalizeNamespaceByPSR4ComposerAutoloadRector::class => [ + __DIR__ . '/src/Helpers', + __DIR__ . '/tests/_support', + ], + + // May load view files directly when detecting classes + StringClassNameToClassConstantRector::class, + + // May be uninitialized on purpose + AddDefaultValueForUndefinedVariableRector::class, + ]); + $rectorConfig->rule(SimplifyUselessVariableRector::class); + $rectorConfig->rule(RemoveAlwaysElseRector::class); + $rectorConfig->rule(CountArrayToEmptyArrayComparisonRector::class); + $rectorConfig->rule(ForToForeachRector::class); + $rectorConfig->rule(ChangeNestedForeachIfsToEarlyContinueRector::class); + $rectorConfig->rule(ChangeIfElseValueAssignToEarlyReturnRector::class); + $rectorConfig->rule(SimplifyStrposLowerRector::class); + $rectorConfig->rule(CombineIfRector::class); + $rectorConfig->rule(SimplifyIfReturnBoolRector::class); + $rectorConfig->rule(InlineIfToExplicitIfRector::class); + $rectorConfig->rule(PreparedValueToEarlyReturnRector::class); + $rectorConfig->rule(ShortenElseIfRector::class); + $rectorConfig->rule(SimplifyIfElseToTernaryRector::class); + $rectorConfig->rule(UnusedForeachValueToArrayKeysRector::class); + $rectorConfig->rule(ChangeArrayPushToArrayAssignRector::class); + $rectorConfig->rule(UnnecessaryTernaryExpressionRector::class); + $rectorConfig->rule(AddPregQuoteDelimiterRector::class); + $rectorConfig->rule(SimplifyRegexPatternRector::class); + $rectorConfig->rule(FuncGetArgsToVariadicParamRector::class); + $rectorConfig->rule(MakeInheritedMethodVisibilitySameAsParentRector::class); + $rectorConfig->rule(SimplifyEmptyArrayCheckRector::class); + $rectorConfig->rule(NormalizeNamespaceByPSR4ComposerAutoloadRector::class); + $rectorConfig + ->ruleWithConfiguration(TypedPropertyFromAssignsRector::class, [ + // Set to false if you use in libraries, or it does create breaking changes. + TypedPropertyFromAssignsRector::INLINE_PUBLIC => false, + ]); +}; diff --git a/src/Config/Services.php b/src/Config/Services.php index d7862f5..f4fca95 100644 --- a/src/Config/Services.php +++ b/src/Config/Services.php @@ -30,9 +30,6 @@ public static function settings(?SettingsConfig $config = null, bool $getShared return static::getSharedInstance('settings', $config); } - /** @var SettingsConfig $config */ - $config = $config ?? config('Settings'); - - return new Settings($config); + return new Settings($config ?? config('Settings')); } } diff --git a/src/Config/Settings.php b/src/Config/Settings.php index 7ac4fdc..a4695e3 100644 --- a/src/Config/Settings.php +++ b/src/Config/Settings.php @@ -2,10 +2,11 @@ namespace CodeIgniter\Settings\Config; +use CodeIgniter\Config\BaseConfig; use CodeIgniter\Settings\Handlers\ArrayHandler; use CodeIgniter\Settings\Handlers\DatabaseHandler; -class Settings +class Settings extends BaseConfig { /** * The available handlers. The alias must @@ -30,6 +31,7 @@ class Settings public $database = [ 'class' => DatabaseHandler::class, 'table' => 'settings', + 'group' => null, 'writeable' => true, ]; } diff --git a/src/Database/Migrations/2021-07-04-041948_CreateSettingsTable.php b/src/Database/Migrations/2021-07-04-041948_CreateSettingsTable.php index c7b0053..eb45235 100644 --- a/src/Database/Migrations/2021-07-04-041948_CreateSettingsTable.php +++ b/src/Database/Migrations/2021-07-04-041948_CreateSettingsTable.php @@ -2,10 +2,22 @@ namespace CodeIgniter\Settings\Database\Migrations; +use CodeIgniter\Database\Forge; use CodeIgniter\Database\Migration; +use CodeIgniter\Settings\Config\Settings; class CreateSettingsTable extends Migration { + private Settings $config; + + public function __construct(?Forge $forge = null) + { + $this->config = config('Settings'); + $this->DBGroup = $this->config->database['group'] ?? null; + + parent::__construct($forge); + } + public function up() { $this->forge->addField('id'); @@ -36,11 +48,11 @@ public function up() 'null' => false, ], ]); - $this->forge->createTable(config('Settings')->database['table'], true); + $this->forge->createTable($this->config->database['table'], true); } public function down() { - $this->forge->dropTable(config('Settings')->database['table']); + $this->forge->dropTable($this->config->database['table']); } } diff --git a/src/Database/Migrations/2021-11-14-143905_AddContextColumn.php b/src/Database/Migrations/2021-11-14-143905_AddContextColumn.php index 5898512..9bf7fe2 100644 --- a/src/Database/Migrations/2021-11-14-143905_AddContextColumn.php +++ b/src/Database/Migrations/2021-11-14-143905_AddContextColumn.php @@ -2,13 +2,25 @@ namespace CodeIgniter\Settings\Database\Migrations; +use CodeIgniter\Database\Forge; use CodeIgniter\Database\Migration; +use CodeIgniter\Settings\Config\Settings; class AddContextColumn extends Migration { + private Settings $config; + + public function __construct(?Forge $forge = null) + { + $this->config = config('Settings'); + $this->DBGroup = $this->config->database['group'] ?? null; + + parent::__construct($forge); + } + public function up() { - $this->forge->addColumn(config('Settings')->database['table'], [ + $this->forge->addColumn($this->config->database['table'], [ 'context' => [ 'type' => 'varchar', 'constraint' => 255, @@ -20,6 +32,6 @@ public function up() public function down() { - $this->forge->dropColumn(config('Settings')->database['table'], 'context'); + $this->forge->dropColumn($this->config->database['table'], 'context'); } } diff --git a/src/Handlers/ArrayHandler.php b/src/Handlers/ArrayHandler.php index 60b1cce..b78bfdb 100644 --- a/src/Handlers/ArrayHandler.php +++ b/src/Handlers/ArrayHandler.php @@ -17,7 +17,7 @@ class ArrayHandler extends BaseHandler * * @var array> */ - private $general = []; + private array $general = []; /** * Storage for context settings. @@ -25,7 +25,7 @@ class ArrayHandler extends BaseHandler * * @var array */ - private $contexts = []; + private array $contexts = []; public function has(string $class, string $property, ?string $context = null): bool { @@ -53,14 +53,10 @@ public function forget(string $class, string $property, ?string $context = null) protected function hasStored(string $class, string $property, ?string $context): bool { if ($context === null) { - return isset($this->general[$class]) - ? array_key_exists($property, $this->general[$class]) - : false; + return isset($this->general[$class]) && array_key_exists($property, $this->general[$class]); } - return isset($this->contexts[$context][$class]) - ? array_key_exists($property, $this->contexts[$context][$class]) - : false; + return isset($this->contexts[$context][$class]) && array_key_exists($property, $this->contexts[$context][$class]); } /** diff --git a/src/Handlers/BaseHandler.php b/src/Handlers/BaseHandler.php index cf2a22c..6ec86a7 100644 --- a/src/Handlers/BaseHandler.php +++ b/src/Handlers/BaseHandler.php @@ -26,9 +26,9 @@ abstract public function get(string $class, string $property, ?string $context = * * @param mixed $value * - * @throws RuntimeException - * * @return void + * + * @throws RuntimeException */ public function set(string $class, string $property, $value = null, ?string $context = null) { @@ -41,9 +41,9 @@ public function set(string $class, string $property, $value = null, ?string $con * Not all Handlers will support writing values. * Must throw RuntimeException for any failures. * - * @throws RuntimeException - * * @return void + * + * @throws RuntimeException */ public function forget(string $class, string $property, ?string $context = null) { diff --git a/src/Handlers/DatabaseHandler.php b/src/Handlers/DatabaseHandler.php index ef136d3..96806cb 100644 --- a/src/Handlers/DatabaseHandler.php +++ b/src/Handlers/DatabaseHandler.php @@ -2,7 +2,10 @@ namespace CodeIgniter\Settings\Handlers; +use CodeIgniter\Database\BaseBuilder; +use CodeIgniter\Database\BaseConnection; use CodeIgniter\I18n\Time; +use CodeIgniter\Settings\Config\Settings; use RuntimeException; /** @@ -12,25 +15,32 @@ class DatabaseHandler extends ArrayHandler { /** - * The database table to use. - * - * @var string + * The DB connection for the Settings. */ - private $table; + private BaseConnection $db; + + /** + * The Query Builder for the Settings table. + */ + private BaseBuilder $builder; /** * Array of contexts that have been stored. * - * @var ?string[] + * @var null[]|string[] */ private $hydrated = []; + private Settings $config; + /** * Stores the configured database table. */ public function __construct() { - $this->table = config('Settings')->database['table'] ?? 'settings'; + $this->config = config('Settings'); + $this->db = db_connect($this->config->database['group']); + $this->builder = $this->db->table($this->config->database['table']); } /** @@ -61,9 +71,9 @@ public function get(string $class, string $property, ?string $context = null) * * @param mixed $value * - * @throws RuntimeException For database failures - * * @return void + * + * @throws RuntimeException For database failures */ public function set(string $class, string $property, $value = null, ?string $context = null) { @@ -73,7 +83,7 @@ public function set(string $class, string $property, $value = null, ?string $con // If it was stored then we need to update if ($this->has($class, $property, $context)) { - $result = db_connect()->table($this->table) + $result = $this->builder ->where('class', $class) ->where('key', $property) ->where('context', $context) @@ -85,7 +95,7 @@ public function set(string $class, string $property, $value = null, ?string $con ]); // ...otherwise insert it } else { - $result = db_connect()->table($this->table) + $result = $this->builder ->insert([ 'class' => $class, 'key' => $property, @@ -98,7 +108,7 @@ public function set(string $class, string $property, $value = null, ?string $con } if ($result !== true) { - throw new RuntimeException(db_connect()->error()['message'] ?? 'Error writing to the database.'); + throw new RuntimeException($this->db->error()['message'] ?? 'Error writing to the database.'); } // Update storage @@ -116,14 +126,14 @@ public function forget(string $class, string $property, ?string $context = null) $this->hydrate($context); // Delete from the database - $result = db_connect()->table($this->table) + $result = $this->builder ->where('class', $class) ->where('key', $property) ->where('context', $context) ->delete(); if (! $result) { - throw new RuntimeException(db_connect()->error()['message'] ?? 'Error writing to the database.'); + throw new RuntimeException($this->db->error()['message'] ?? 'Error writing to the database.'); } // Delete from local storage @@ -147,9 +157,9 @@ private function hydrate(?string $context): void if ($context === null) { $this->hydrated[] = null; - $query = db_connect()->table($this->table)->where('context', null); + $query = $this->builder->where('context', null); } else { - $query = db_connect()->table($this->table)->where('context', $context); + $query = $this->builder->where('context', $context); // If general has not been hydrated we will do that at the same time if (! in_array(null, $this->hydrated, true)) { @@ -161,7 +171,7 @@ private function hydrate(?string $context): void } if (is_bool($result = $query->get())) { - throw new RuntimeException(db_connect()->error()['message'] ?? 'Error reading from database.'); + throw new RuntimeException($this->db->error()['message'] ?? 'Error reading from database.'); } foreach ($result->getResultObject() as $row) { diff --git a/src/Helpers/setting_helper.php b/src/Helpers/setting_helper.php index fa91380..c2457f8 100644 --- a/src/Helpers/setting_helper.php +++ b/src/Helpers/setting_helper.php @@ -1,15 +1,18 @@ set($key, $value); + $setting->set($key, $value); } } diff --git a/src/Settings.php b/src/Settings.php index 1fe1237..244ea9f 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -19,14 +19,14 @@ class Settings * * @var BaseHandler[] */ - private $handlers = []; + private array $handlers = []; /** * An array of the config options for each handler. * * @var array> */ - private $options; + private ?array $options = null; /** * Grabs instances of our handlers. @@ -55,7 +55,7 @@ public function get(string $key, ?string $context = null) [$class, $property, $config] = $this->prepareClassAndProperty($key); // Check each of our handlers - foreach ($this->handlers as $name => $handler) { + foreach ($this->handlers as $handler) { if ($handler->has($class, $property, $context)) { return $handler->get($class, $property, $context); } @@ -104,9 +104,9 @@ public function forget(string $key, ?string $context = null) /** * Returns the handler that is set to store values. * - * @throws RuntimeException - * * @return BaseHandler[] + * + * @throws RuntimeException */ private function getWriteHandlers() { @@ -128,9 +128,9 @@ private function getWriteHandlers() /** * Analyzes the given key and breaks it into the class.field parts. * - * @throws InvalidArgumentException - * * @return string[] + * + * @throws InvalidArgumentException */ private function parseDotSyntax(string $key): array { diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index fb603eb..2244c2c 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -5,6 +5,7 @@ use CodeIgniter\I18n\Time; use CodeIgniter\Settings\Settings; use CodeIgniter\Test\DatabaseTestTrait; +use InvalidArgumentException; use Tests\Support\TestCase; /** @@ -22,6 +23,11 @@ final class DatabaseHandlerTest extends TestCase */ protected $table; + /** + * @var string + */ + protected $group; + /** * Ensures we are using the database handler. */ @@ -29,11 +35,13 @@ protected function setUp(): void { parent::setUp(); + /** @var \CodeIgniter\Settings\Config\Settings $config */ $config = config('Settings'); $config->handlers = ['database']; $this->settings = new Settings($config); $this->table = $config->database['table']; + $this->group = $config->database['group']; } public function testSetInsertsNewRows() @@ -48,6 +56,39 @@ public function testSetInsertsNewRows() ]); } + public function testInvalidGroup() + { + $this->expectException(InvalidArgumentException::class); + + /** @var \CodeIgniter\Settings\Config\Settings $config */ + $config = config('Settings'); + $config->handlers = ['database']; + $config->database['group'] = 'another'; + + $this->settings = new Settings($config); + + $this->settings->set('Test.siteName', true); + } + + public function testSetDefaultGroup() + { + /** @var \CodeIgniter\Settings\Config\Settings $config */ + $config = config('Settings'); + $config->handlers = ['database']; + $config->database['group'] = 'default'; + + $this->settings->set('Test.siteName', true); + + $this->seeInDatabase($this->table, [ + 'class' => 'Tests\Support\Config\Test', + 'key' => 'siteName', + 'value' => '1', + 'type' => 'boolean', + ]); + + $this->assertTrue($this->settings->get('Test.siteName')); + } + public function testSetInsertsBoolTrue() { $this->settings->set('Test.siteName', true); diff --git a/tests/HelperTest.php b/tests/HelperTest.php index a058c00..9ac17ea 100644 --- a/tests/HelperTest.php +++ b/tests/HelperTest.php @@ -1,6 +1,6 @@