From fa9efe55d653835e2e18f3c5c02956ff0e348a5c Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Wed, 29 Oct 2025 08:02:55 +0200 Subject: [PATCH 1/3] Issue #47: Remove PHP 8.1 and add PHP 8.4 & 8.5 support Signed-off-by: alexmerlin --- .laminas-ci.json | 2 +- README.md | 6 +++--- SECURITY.md | 8 ++++---- composer.json | 2 +- docs/book/v3/overview.md | 12 ++++++++++++ docs/book/v3/setting-up-as-cronjob.md | 2 +- docs/book/v3/usage.md | 2 +- psalm.xml | 3 +++ 8 files changed, 26 insertions(+), 11 deletions(-) diff --git a/.laminas-ci.json b/.laminas-ci.json index 82cd446..d7d1ae0 100644 --- a/.laminas-ci.json +++ b/.laminas-ci.json @@ -1,6 +1,6 @@ { "ignore_php_platform_requirements": { - "8.4": true + "8.5": true }, "backwardCompatibilityCheck": true } diff --git a/README.md b/README.md index 455e171..cf395bd 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ > dot-cli is a wrapper on top of [laminas-cli](https://github.com/laminas/laminas-cli) ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-cli) -![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-cli/3.8.1) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-cli/3.10.0) [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-cli)](https://github.com/dotkernel/dot-cli/issues) [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-cli)](https://github.com/dotkernel/dot-cli/network) @@ -18,7 +18,7 @@ Dotkernel component to build console applications based on [laminas-cli](https:/ ## Requirements -- **PHP**: 8.2, 8.3 or 8.4 +- **PHP**: 8.2, 8.3, 8.4 or 8.5 - **laminas/laminas-servicemanager**: >= 3.11 || >= 4.0, - **laminas/laminas-cli**: >= 1.4 @@ -85,7 +85,7 @@ Available commands: As shown in `config/autoload/cli.global.php`, dot-cli includes a demo command `demo:command` that will help you understand the basics of creating a new command. For more information, see [laminas-cli documentation](https://docs.laminas.dev/laminas-cli/). -## Setting up as cronjob +## Setting up as a cronjob ```text * * * * * /opt/plesk/php/7.4/bin/php /var/www/vhosts/example.com/httpdocs/bin/cli.php demo:command -q diff --git a/SECURITY.md b/SECURITY.md index 4c5da79..4497fe9 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -3,10 +3,10 @@ ## Supported Versions -| Version | Supported | PHP Version | -|---------|--------------------|---------------------------------------------------------------------------------------------------------| -| 3.x | :white_check_mark: | ![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-cli/3.4.2) | -| <= 2.x | :x: | | +| Version | Supported | PHP Version | +|---------|--------------------|----------------------------------------------------------------------------------------------------------| +| 3.x | :white_check_mark: | ![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-cli/3.10.0) | +| <= 2.x | :x: | | ## Reporting Potential Security Issues diff --git a/composer.json b/composer.json index d0d6af9..d3eec6b 100644 --- a/composer.json +++ b/composer.json @@ -23,7 +23,7 @@ } }, "require": { - "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~8.2.0 || ~8.3.0 || ~8.4.0 || ~8.5.0", "laminas/laminas-cli": "^1.4.0", "laminas/laminas-servicemanager": "^3.11.1 || ^4.0" }, diff --git a/docs/book/v3/overview.md b/docs/book/v3/overview.md index e8f6a6b..77b193f 100644 --- a/docs/book/v3/overview.md +++ b/docs/book/v3/overview.md @@ -1,3 +1,15 @@ # Overview +> [!IMPORTANT] > dot-cli is a wrapper on top of [laminas-cli](https://github.com/laminas/laminas-cli) + +![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-cli) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-cli/3.10.0) + +[![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-cli)](https://github.com/dotkernel/dot-cli/issues) +[![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-cli)](https://github.com/dotkernel/dot-cli/network) +[![GitHub stars](https://img.shields.io/github/stars/dotkernel/dot-cli)](https://github.com/dotkernel/dot-cli/stargazers) +[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-cli)](https://github.com/dotkernel/dot-cli/blob/3.0/LICENSE) + +[![Build Static](https://github.com/dotkernel/dot-cli/actions/workflows/continuous-integration.yml/badge.svg?branch=3.0)](https://github.com/dotkernel/dot-cli/actions/workflows/continuous-integration.yml) +[![codecov](https://codecov.io/gh/dotkernel/dot-cli/graph/badge.svg?token=0DFCK2GUBT)](https://codecov.io/gh/dotkernel/dot-cli) diff --git a/docs/book/v3/setting-up-as-cronjob.md b/docs/book/v3/setting-up-as-cronjob.md index 5312129..7c8861d 100644 --- a/docs/book/v3/setting-up-as-cronjob.md +++ b/docs/book/v3/setting-up-as-cronjob.md @@ -1,4 +1,4 @@ -# Setting up as cronjob +# Setting up as a cronjob ```text * * * * * php /var/www/vhosts/example.com/httpdocs/bin/cli.php demo:command -q diff --git a/docs/book/v3/usage.md b/docs/book/v3/usage.md index 08e5915..8e93b4a 100644 --- a/docs/book/v3/usage.md +++ b/docs/book/v3/usage.md @@ -1,7 +1,7 @@ # Usage Use `src/Command/DemoCommand.php` as an example when creating a new command. -Update the name & description in the `AsCommand` attribute as needed. +Update the name and description in the `AsCommand` attribute as needed. Also update the `$defaultName` property and the description set inside the `configure` method to match the `AsCommand` attribute. ## Running diff --git a/psalm.xml b/psalm.xml index 9dd8f07..cb3cd0c 100644 --- a/psalm.xml +++ b/psalm.xml @@ -15,4 +15,7 @@ + + + From 234e3eb5c885464f2d387ffe01afc8196ebccdc7 Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Wed, 29 Oct 2025 08:05:29 +0200 Subject: [PATCH 2/3] Issue #47: Remove PHP 8.1 and add PHP 8.4 & 8.5 support Signed-off-by: alexmerlin --- .github/workflows/codecov.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml index 32a1a00..c0fbc53 100644 --- a/.github/workflows/codecov.yml +++ b/.github/workflows/codecov.yml @@ -18,6 +18,7 @@ jobs: - "8.2" - "8.3" - "8.4" + - "8.5" steps: - name: Checkout From bd970310d3372121d4512acdfc4a6c47107183cc Mon Sep 17 00:00:00 2001 From: alexmerlin Date: Wed, 29 Oct 2025 09:22:02 +0200 Subject: [PATCH 3/3] Issue #47: Remove PHP 8.1 and add PHP 8.4 & 8.5 support Signed-off-by: alexmerlin --- .github/workflows/static-analysis.yml | 50 +++++++++++++++++++++++++++ composer.json | 9 ++--- phpstan.neon | 8 +++++ psalm-baseline.xml | 13 ------- psalm.xml | 21 ----------- test/Command/DemoCommandTest.php | 2 +- test/FileLockerTest.php | 32 ++++++++--------- 7 files changed, 80 insertions(+), 55 deletions(-) create mode 100644 .github/workflows/static-analysis.yml create mode 100644 phpstan.neon delete mode 100644 psalm-baseline.xml delete mode 100644 psalm.xml diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..9976515 --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,50 @@ +on: + - push + +name: Run PHPStan checks + +jobs: + mutation: + name: PHPStan ${{ matrix.php }}-${{ matrix.os }} + + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: + - ubuntu-latest + + php: + - "8.2" + - "8.3" + - "8.4" + - "8.5" + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install PHP + uses: shivammathur/setup-php@v2 + with: + php-version: "${{ matrix.php }}" + coverage: pcov + ini-values: assert.exception=1, zend.assertions=1, error_reporting=-1, log_errors_max_len=0, display_errors=On + tools: composer:v2, cs2pr + + - name: Determine composer cache directory + run: echo "COMPOSER_CACHE_DIR=$(composer config cache-dir)" >> $GITHUB_ENV + + - name: Cache dependencies installed with composer + uses: actions/cache@v4 + with: + path: ${{ env.COMPOSER_CACHE_DIR }} + key: php${{ matrix.php }}-composer-${{ hashFiles('**/composer.json') }} + restore-keys: | + php${{ matrix.php }}-composer- + + - name: Install dependencies with composer + run: composer install --prefer-dist --no-interaction --no-progress --optimize-autoloader --ansi + + - name: Run static analysis with PHPStan + run: vendor/bin/phpstan analyse diff --git a/composer.json b/composer.json index d3eec6b..69fdefc 100644 --- a/composer.json +++ b/composer.json @@ -35,8 +35,9 @@ "require-dev": { "laminas/laminas-coding-standard": "^3.0", "mikey179/vfsstream": "^1.6.7", - "phpunit/phpunit": "^10.2", - "vimeo/psalm": "^6.0" + "phpstan/phpstan": "^2.1.17", + "phpstan/phpstan-phpunit": "^2.0.6", + "phpunit/phpunit": "^10.2" }, "autoload-dev": { "psr-4": { @@ -51,8 +52,8 @@ ], "cs-check": "phpcs", "cs-fix": "phpcbf", + "static-analysis": "phpstan analyse --memory-limit 1G", "test": "phpunit --colors=always", - "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", - "static-analysis": "psalm --shepherd --stats" + "test-coverage": "phpunit --colors=always --coverage-clover clover.xml" } } diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..349be25 --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,8 @@ +includes: + - vendor/phpstan/phpstan-phpunit/extension.neon +parameters: + level: 5 + paths: + - src + - test + treatPhpDocTypesAsCertain: false diff --git a/psalm-baseline.xml b/psalm-baseline.xml deleted file mode 100644 index 9352203..0000000 --- a/psalm-baseline.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - new TerminateListener($config) - new ContainerCommandLoader($container, $config['commands']) - - - new TerminateListener($config) - new ContainerCommandLoader($container, $config['commands']) - - - diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index cb3cd0c..0000000 --- a/psalm.xml +++ /dev/null @@ -1,21 +0,0 @@ - - - - - - - - - - - - diff --git a/test/Command/DemoCommandTest.php b/test/Command/DemoCommandTest.php index 1987287..2ff0c22 100644 --- a/test/Command/DemoCommandTest.php +++ b/test/Command/DemoCommandTest.php @@ -20,7 +20,7 @@ class DemoCommandTest extends TestCase public function testWillCreateCommand(): void { $command = new DemoCommand(); - $this->assertInstanceOf(DemoCommand::class, $command); + $this->assertContainsOnlyInstancesOf(DemoCommand::class, [$command]); } /** diff --git a/test/FileLockerTest.php b/test/FileLockerTest.php index 996d2e5..a9fa075 100644 --- a/test/FileLockerTest.php +++ b/test/FileLockerTest.php @@ -32,28 +32,28 @@ public function testAccessors(): void $fileLocker = new FileLocker(); $this->assertFalse($fileLocker->isEnabled()); $fileLocker->enable(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertTrue($fileLocker->isEnabled()); $fileLocker->disable(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertFalse($fileLocker->isEnabled()); $fileLocker->setEnabled(true); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertTrue($fileLocker->isEnabled()); $fileLocker->setEnabled(false); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertFalse($fileLocker->isEnabled()); $this->assertNull($fileLocker->getDirPath()); $fileLocker->setDirPath('test'); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertSame('test', $fileLocker->getDirPath()); $this->assertNull($fileLocker->getCommandName()); $fileLocker->setCommandName('test'); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertSame('test', $fileLocker->getCommandName()); $this->assertNull($fileLocker->getLockFile()); $fileLocker->setLockFile('test'); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertSame('test', $fileLocker->getLockFile()); $this->assertSame('test/command-test.lock', $fileLocker->getLockFilePath()); } @@ -65,7 +65,7 @@ public function testWillInitLockFile(): void $fileLocker = new FileLocker(true, $config['dirPath'], 'test'); $this->assertNull($fileLocker->getLockFile()); $fileLocker->initLockFile(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertIsResource($fileLocker->getLockFile()); } @@ -79,7 +79,7 @@ public function testWillNotLockWhenDisabled(): void $fileLocker = new FileLocker(false, $config['dirPath'], 'test'); $this->assertNull($fileLocker->getLockFile()); $fileLocker->lock(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertNull($fileLocker->getLockFile()); } @@ -93,7 +93,7 @@ public function testWillNotLockWithoutValidCommandName(): void $fileLocker = new FileLocker(true, $config['dirPath']); $this->assertNull($fileLocker->getLockFile()); $fileLocker->lock(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertNull($fileLocker->getLockFile()); } @@ -107,7 +107,7 @@ public function testWillLockWhenLockedAndEnabledAndHasValidCommandName(): void $fileLocker = new FileLocker(true, $config['dirPath'], 'test'); $this->assertNull($fileLocker->getLockFile()); $fileLocker->lock(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertIsResource($fileLocker->getLockFile()); $this->assertFileExists($fileLocker->getLockFilePath()); } @@ -119,7 +119,7 @@ public function testWillNotUnlockWhenDisabled(): void $fileLocker = new FileLocker(false, $config['dirPath'], 'test'); $this->assertNull($fileLocker->getLockFile()); $fileLocker->unlock(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertNull($fileLocker->getLockFile()); } @@ -130,7 +130,7 @@ public function testWillNotUnlockWithoutValidCommandName(): void $fileLocker = new FileLocker(false, $config['dirPath']); $this->assertNull($fileLocker->getLockFile()); $fileLocker->unlock(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertNull($fileLocker->getLockFile()); } @@ -141,7 +141,7 @@ public function testWillNotUnlockWhenEnabledAndWithoutValidCommandName(): void $fileLocker = new FileLocker(true, $config['dirPath']); $this->assertNull($fileLocker->getLockFile()); $fileLocker->unlock(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertNull($fileLocker->getLockFile()); } @@ -154,11 +154,11 @@ public function testWillUnlockWhenLockedAndEnabledAndHasValidCommandName(): void $fileLocker = new FileLocker(true, $config['dirPath'], 'test'); $fileLocker->lock(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertIsResource($fileLocker->getLockFile()); $this->assertFileExists($fileLocker->getLockFilePath()); $fileLocker->unlock(); - $this->assertInstanceOf(FileLocker::class, $fileLocker); + $this->assertContainsOnlyInstancesOf(FileLocker::class, [$fileLocker]); $this->assertFileIsReadable($fileLocker->getLockFilePath()); $this->assertFileIsWritable($fileLocker->getLockFilePath()); }