From dd2974951fdd60f6a43c63ff0312be200f006ce8 Mon Sep 17 00:00:00 2001 From: horea Date: Mon, 3 Mar 2025 17:42:23 +0200 Subject: [PATCH 1/4] Issue #58: Replace Psalm with PHPStan Signed-off-by: horea --- .github/workflows/static-analysis.yml | 49 +++++++++++++++++++++++++ README.md | 9 ++++- composer.json | 12 +++--- docs/book/v4/overview.md | 14 +++++++ phpstan.neon | 17 +++++++++ psalm.xml | 17 --------- test/AnnotatedRepositoryFactoryTest.php | 2 +- test/AnnotatedServiceFactoryTest.php | 4 +- 8 files changed, 97 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/static-analysis.yml create mode 100644 phpstan.neon 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..dc4b6fa --- /dev/null +++ b/.github/workflows/static-analysis.yml @@ -0,0 +1,49 @@ +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" + + 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/README.md b/README.md index 92bbeb9..0b22926 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,14 @@ Dotkernel component used to create services through [Laminas Service Manager](ht This package can clean up your code, by getting rid of all the factories you write, sometimes just to inject a dependency or two. +## Documentation + +Documentation is available at: https://docs.dotkernel.org/dot-controller/. + +## Badges + ![OSS Lifecycle](https://img.shields.io/osslifecycle?file_url=https%3A%2F%2Fgithub.com%2Fdotkernel%2Fdot-annotated-services%2Fblob%2F4.0%2FOSSMETADATA) -![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-annotated-services/4.2.1) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-annotated-services/4.3.0) [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-annotated-services)](https://github.com/dotkernel/dot-annotated-services/issues) [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-annotated-services)](https://github.com/dotkernel/dot-annotated-services/network) @@ -14,6 +20,7 @@ This package can clean up your code, by getting rid of all the factories you wri [![Build Static](https://github.com/dotkernel/dot-annotated-services/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-annotated-services/actions/workflows/continuous-integration.yml) [![codecov](https://codecov.io/gh/dotkernel/dot-annotated-services/graph/badge.svg?token=ZBZDEA3LY8)](https://codecov.io/gh/dotkernel/dot-annotated-services) +[![PHPStan](https://github.com/dotkernel/dot-annotated-services/actions/workflows/static-analysis.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-annotated-services/actions/workflows/static-analysis.yml) ## Installation diff --git a/composer.json b/composer.json index 01a86ab..f9df673 100644 --- a/composer.json +++ b/composer.json @@ -1,12 +1,12 @@ { "name": "dotkernel/dot-annotated-services", "type": "library", - "description": "DotKernel service creation component through laminas-servicemanager and annotations", + "description": "Dotkernel service creation component through laminas-servicemanager and annotations", "license": "MIT", "homepage": "https://github.com/dotkernel/dot-annotated-services", "authors": [ { - "name": "DotKernel Team", + "name": "Dotkernel Team", "email": "team@dotkernel.com" } ], @@ -28,8 +28,9 @@ }, "require-dev": { "laminas/laminas-coding-standard": "^3.0", - "phpunit/phpunit": "^10.5.9", - "vimeo/psalm": "^6.0" + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.5.9" }, "autoload": { "psr-4": { @@ -49,8 +50,7 @@ "cs-check": "phpcs", "cs-fix": "phpcbf", "test": "phpunit --colors=always", - "test-coverage": "phpunit --colors=always --coverage-clover clover.xml", - "static-analysis": "psalm --shepherd --stats" + "static-analysis": "phpstan analyse --memory-limit 1G" }, "config": { "sort-packages": true, diff --git a/docs/book/v4/overview.md b/docs/book/v4/overview.md index 6b39e6a..dc75eae 100644 --- a/docs/book/v4/overview.md +++ b/docs/book/v4/overview.md @@ -3,3 +3,17 @@ `dot-annotated-services` is Dotkernel's dependency injection service. By providing reusable factories for service and repository injection, it reduces code complexity in projects. + +## Badges + +![OSS Lifecycle](https://img.shields.io/osslifecycle?file_url=https%3A%2F%2Fgithub.com%2Fdotkernel%2Fdot-annotated-services%2Fblob%2F4.0%2FOSSMETADATA) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-annotated-services/4.3.0) + +[![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-annotated-services)](https://github.com/dotkernel/dot-annotated-services/issues) +[![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-annotated-services)](https://github.com/dotkernel/dot-annotated-services/network) +[![GitHub stars](https://img.shields.io/github/stars/dotkernel/dot-annotated-services)](https://github.com/dotkernel/dot-annotated-services/stargazers) +[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-annotated-services)](https://github.com/dotkernel/dot-annotated-services/blob/4.0/LICENSE.md) + +[![Build Static](https://github.com/dotkernel/dot-annotated-services/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-annotated-services/actions/workflows/continuous-integration.yml) +[![codecov](https://codecov.io/gh/dotkernel/dot-annotated-services/graph/badge.svg?token=ZBZDEA3LY8)](https://codecov.io/gh/dotkernel/dot-annotated-services) +[![PHPStan](https://github.com/dotkernel/dot-annotated-services/actions/workflows/static-analysis.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-annotated-services/actions/workflows/static-analysis.yml) diff --git a/phpstan.neon b/phpstan.neon new file mode 100644 index 0000000..7946cbc --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,17 @@ +includes: + - vendor/phpstan/phpstan-phpunit/extension.neon +parameters: + level: 5 + paths: + - src + - test + treatPhpDocTypesAsCertain: false + ignoreErrors: + - message: '#Call to an undefined method .*::method\(\)\.#' + path: test/AnnotatedRepositoryFactoryTest.php + - message: '~Parameter #1 \$className of method PHPUnit\\Framework\\TestCase::getMockBuilder\(\) expects class-string, string given.~' + path: test/AnnotatedRepositoryFactoryTest.php + - message: '#Call to an undefined method .*::method\(\)\.#' + path: test/AnnotatedServiceFactoryTest.php + - message: '~Parameter #1 \$className of method PHPUnit\\Framework\\TestCase::getMockBuilder\(\) expects class-string, string given.~' + path: test/AnnotatedServiceFactoryTest.php \ No newline at end of file diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 7272b57..0000000 --- a/psalm.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - diff --git a/test/AnnotatedRepositoryFactoryTest.php b/test/AnnotatedRepositoryFactoryTest.php index 3220350..eabce4e 100644 --- a/test/AnnotatedRepositoryFactoryTest.php +++ b/test/AnnotatedRepositoryFactoryTest.php @@ -87,6 +87,6 @@ public function testCreateObjectReturnsEntityRepository() $object = $this->subject->__invoke($this->container, $repository::class); - $this->assertInstanceOf(EntityRepository::class, $object); + $this->assertContainsOnlyInstancesOf(EntityRepository::class, [$object]); } } diff --git a/test/AnnotatedServiceFactoryTest.php b/test/AnnotatedServiceFactoryTest.php index 9578f8a..c51607c 100644 --- a/test/AnnotatedServiceFactoryTest.php +++ b/test/AnnotatedServiceFactoryTest.php @@ -60,7 +60,7 @@ public function testReturnServiceWithNoDependencies() $object = $this->subject->__invoke($this->container, $requestedName); - $this->assertInstanceOf($requestedName, $object); + $this->assertSame($requestedName, $object::class); } public function testThrowsExceptionAnnotationNotFound() @@ -108,6 +108,6 @@ public function testReturnService() $service = $this->subject->__invoke($this->container, $requestedName); - $this->assertInstanceOf($requestedName, $service); + $this->assertSame($requestedName, $service::class); } } From 6bdf1c1e3dca144a073de59e8ce2b082528931fa Mon Sep 17 00:00:00 2001 From: horea Date: Tue, 4 Mar 2025 16:28:25 +0200 Subject: [PATCH 2/4] Issue #58: Replace Psalm with PHPStan Signed-off-by: horea --- README.md | 5 +++-- composer.json | 3 ++- docs/book/v4/overview.md | 2 +- docs/book/v5/overview.md | 2 +- phpstan.neon | 9 --------- 5 files changed, 7 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 0b22926..069d311 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,13 @@ # dot-annotated-services -Dotkernel component used to create services through [Laminas Service Manager](https://github.com/laminas/laminas-servicemanager) and inject them with dependencies just using method annotations. It can also create services without the need to write factories. Annotation parsing can be cached, to improve performance. +Dotkernel component used to create services through [Laminas Service Manager](https://github.com/laminas/laminas-servicemanager) and inject them with dependencies just using method annotations. +It can also create services without the need to write factories. Annotation parsing can be cached, to improve performance. This package can clean up your code, by getting rid of all the factories you write, sometimes just to inject a dependency or two. ## Documentation -Documentation is available at: https://docs.dotkernel.org/dot-controller/. +Documentation is available at: https://docs.dotkernel.org/dot-annotated-services/. ## Badges diff --git a/composer.json b/composer.json index f9df673..1be1d4b 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,8 @@ "scripts": { "check": [ "@cs-check", - "@test" + "@test", + "@static-analysis" ], "cs-check": "phpcs", "cs-fix": "phpcbf", diff --git a/docs/book/v4/overview.md b/docs/book/v4/overview.md index dc75eae..d82bfdc 100644 --- a/docs/book/v4/overview.md +++ b/docs/book/v4/overview.md @@ -1,6 +1,6 @@ # Overview -`dot-annotated-services` is Dotkernel's dependency injection service. +`dot-annotated-services` is Dotkernel's dependency injection service using annotations. By providing reusable factories for service and repository injection, it reduces code complexity in projects. diff --git a/docs/book/v5/overview.md b/docs/book/v5/overview.md index 06a23f1..e75266d 100644 --- a/docs/book/v5/overview.md +++ b/docs/book/v5/overview.md @@ -1,6 +1,6 @@ # Overview -`dot-annotated-services` is DotKernel's dependency injection service. +`dot-annotated-services` is Dotkernel's dependency injection service. By providing reusable factories for service and repository injection, it reduces code complexity in projects. diff --git a/phpstan.neon b/phpstan.neon index 7946cbc..349be25 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,12 +6,3 @@ parameters: - src - test treatPhpDocTypesAsCertain: false - ignoreErrors: - - message: '#Call to an undefined method .*::method\(\)\.#' - path: test/AnnotatedRepositoryFactoryTest.php - - message: '~Parameter #1 \$className of method PHPUnit\\Framework\\TestCase::getMockBuilder\(\) expects class-string, string given.~' - path: test/AnnotatedRepositoryFactoryTest.php - - message: '#Call to an undefined method .*::method\(\)\.#' - path: test/AnnotatedServiceFactoryTest.php - - message: '~Parameter #1 \$className of method PHPUnit\\Framework\\TestCase::getMockBuilder\(\) expects class-string, string given.~' - path: test/AnnotatedServiceFactoryTest.php \ No newline at end of file From 9037b34d817ec896e5fbfbcc38ddea6e7656512f Mon Sep 17 00:00:00 2001 From: horea Date: Tue, 4 Mar 2025 16:58:38 +0200 Subject: [PATCH 3/4] Issue #58: Replace Psalm with PHPStan Signed-off-by: horea --- test/AnnotatedRepositoryFactoryTest.php | 41 ++++++++++++++++++---- test/AnnotatedServiceFactoryTest.php | 45 ++++++++++++++++++++----- test/TestClass.php | 9 +++++ 3 files changed, 81 insertions(+), 14 deletions(-) create mode 100644 test/TestClass.php diff --git a/test/AnnotatedRepositoryFactoryTest.php b/test/AnnotatedRepositoryFactoryTest.php index eabce4e..0b498c1 100644 --- a/test/AnnotatedRepositoryFactoryTest.php +++ b/test/AnnotatedRepositoryFactoryTest.php @@ -10,19 +10,26 @@ use Dot\AnnotatedServices\Annotation\Entity; use Dot\AnnotatedServices\Exception\RuntimeException; use Dot\AnnotatedServices\Factory\AnnotatedRepositoryFactory as Subject; +//use DotTest\AnnotatedServices\TestClass; +use PHPUnit\Framework\MockObject\Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; +use ReflectionException; use function get_class; class AnnotatedRepositoryFactoryTest extends TestCase { - private ContainerInterface $container; - - private Subject $subject; - - private Reader $annotationReader; + private MockObject&ContainerInterface $container; + private MockObject&Subject $subject; + private MockObject&Reader $annotationReader; + /** + * @throws Exception + */ public function setUp(): void { $this->container = $this->createMock(ContainerInterface::class); @@ -30,6 +37,11 @@ public function setUp(): void $this->subject = $this->createPartialMock(Subject::class, ['createAnnotationReader']); } + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws ReflectionException + */ public function testThrowsExceptionClassNotFound() { $requestedName = 'TestRepository'; @@ -39,9 +51,14 @@ public function testThrowsExceptionClassNotFound() $this->subject->__invoke($this->container, $requestedName); } + /** + * @throws ContainerExceptionInterface + * @throws ReflectionException + * @throws NotFoundExceptionInterface + */ public function testThrowsExceptionClassNotExtendsEntityRepository() { - $requestedName = 'TestRepository'; + $requestedName = TestClass::class; $this->getMockBuilder($requestedName)->getMock(); @@ -50,6 +67,12 @@ public function testThrowsExceptionClassNotExtendsEntityRepository() $this->subject->__invoke($this->container, $requestedName); } + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws Exception + * @throws ReflectionException + */ public function testCreateObjectThrowsExceptionAnnotationNotFound() { $repository = $this->createMock(EntityRepository::class); @@ -67,6 +90,12 @@ public function testCreateObjectThrowsExceptionAnnotationNotFound() $this->subject->__invoke($this->container, $repository::class); } + /** + * @throws ContainerExceptionInterface + * @throws Exception + * @throws NotFoundExceptionInterface + * @throws ReflectionException + */ public function testCreateObjectReturnsEntityRepository() { $repository = $this->createMock(EntityRepository::class); diff --git a/test/AnnotatedServiceFactoryTest.php b/test/AnnotatedServiceFactoryTest.php index c51607c..8f56ec4 100644 --- a/test/AnnotatedServiceFactoryTest.php +++ b/test/AnnotatedServiceFactoryTest.php @@ -8,21 +8,27 @@ use Dot\AnnotatedServices\Annotation\Inject; use Dot\AnnotatedServices\Exception\RuntimeException; use Dot\AnnotatedServices\Factory\AnnotatedServiceFactory as Subject; +use PHPUnit\Framework\MockObject\Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Psr\Container\ContainerExceptionInterface; use Psr\Container\ContainerInterface; +use Psr\Container\NotFoundExceptionInterface; use ReflectionClass; +use ReflectionException; use ReflectionMethod; use function get_class; class AnnotatedServiceFactoryTest extends TestCase { - private ContainerInterface $container; - - private Subject $subject; - - private Reader $annotationReader; + private MockObject&ContainerInterface $container; + private MockObject&Subject $subject; + private MockObject&Reader $annotationReader; + /** + * @throws Exception + */ public function setUp(): void { $this->container = $this->createMock(ContainerInterface::class); @@ -33,6 +39,11 @@ public function setUp(): void ]); } + /** + * @throws ContainerExceptionInterface + * @throws NotFoundExceptionInterface + * @throws ReflectionException + */ public function testThrowsExceptionClassNotFound() { $requestedName = 'TestService'; @@ -43,9 +54,15 @@ public function testThrowsExceptionClassNotFound() $this->subject->__invoke($this->container, $requestedName); } + /** + * @throws ContainerExceptionInterface + * @throws Exception + * @throws NotFoundExceptionInterface + * @throws ReflectionException + */ public function testReturnServiceWithNoDependencies() { - $requestedName = 'TestService'; + $requestedName = TestClass::class; $this->getMockBuilder($requestedName)->allowMockingUnknownTypes()->getMock(); $refClass = $this->createMock(ReflectionClass::class); @@ -63,9 +80,15 @@ public function testReturnServiceWithNoDependencies() $this->assertSame($requestedName, $object::class); } + /** + * @throws ContainerExceptionInterface + * @throws Exception + * @throws NotFoundExceptionInterface + * @throws ReflectionException + */ public function testThrowsExceptionAnnotationNotFound() { - $requestedName = 'TestService'; + $requestedName = TestClass::class; $this->getMockBuilder($requestedName)->allowMockingUnknownTypes()->getMock(); $refClass = $this->createMock(ReflectionClass::class); $refConstructor = $this->createMock(ReflectionMethod::class); @@ -89,9 +112,15 @@ public function testThrowsExceptionAnnotationNotFound() $this->subject->__invoke($this->container, $requestedName); } + /** + * @throws ContainerExceptionInterface + * @throws Exception + * @throws NotFoundExceptionInterface + * @throws ReflectionException + */ public function testReturnService() { - $requestedName = 'TestService'; + $requestedName = TestClass::class; $this->getMockBuilder($requestedName)->allowMockingUnknownTypes()->getMock(); $refClass = $this->createMock(ReflectionClass::class); $refConstructor = $this->createMock(ReflectionMethod::class); diff --git a/test/TestClass.php b/test/TestClass.php new file mode 100644 index 0000000..b8b3d80 --- /dev/null +++ b/test/TestClass.php @@ -0,0 +1,9 @@ + Date: Tue, 4 Mar 2025 17:27:43 +0200 Subject: [PATCH 4/4] Issue #58: Replace Psalm with PHPStan Signed-off-by: horea --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 069d311..ee8e997 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,8 @@ # dot-annotated-services Dotkernel component used to create services through [Laminas Service Manager](https://github.com/laminas/laminas-servicemanager) and inject them with dependencies just using method annotations. -It can also create services without the need to write factories. Annotation parsing can be cached, to improve performance. +It can also create services without the need to write factories. +Annotation parsing can be cached, to improve performance. This package can clean up your code, by getting rid of all the factories you write, sometimes just to inject a dependency or two.