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..ee8e997 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,19 @@ # 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-annotated-services/. + +## 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 +22,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..1be1d4b 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": { @@ -44,13 +45,13 @@ "scripts": { "check": [ "@cs-check", - "@test" + "@test", + "@static-analysis" ], "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..d82bfdc 100644 --- a/docs/book/v4/overview.md +++ b/docs/book/v4/overview.md @@ -1,5 +1,19 @@ # 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. + +## 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/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 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.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..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); @@ -87,6 +116,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..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); @@ -60,12 +77,18 @@ public function testReturnServiceWithNoDependencies() $object = $this->subject->__invoke($this->container, $requestedName); - $this->assertInstanceOf($requestedName, $object); + $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); @@ -108,6 +137,6 @@ public function testReturnService() $service = $this->subject->__invoke($this->container, $requestedName); - $this->assertInstanceOf($requestedName, $service); + $this->assertSame($requestedName, $service::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 @@ +