diff --git a/.github/workflows/static-analysis.yml b/.github/workflows/static-analysis.yml new file mode 100644 index 0000000..03d62cb --- /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.1" + - "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 4be1806..d34b488 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,15 @@ # dot-navigation +`dot-navigation` is Dotkernel's component that allows you to easily define and parse menus inside templates, using a configuration based approach. + +## Documentation + +Documentation is available at: https://docs.dotkernel.org/dot-navigation/. + +## Badges + ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-navigation) -![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-navigation/4.0.2) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-navigation/4.1.0) [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/issues) [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/network) @@ -10,8 +18,7 @@ [![Build Static](https://github.com/dotkernel/dot-navigation/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-navigation/actions/workflows/continuous-integration.yml) [![codecov](https://codecov.io/gh/dotkernel/dot-navigation/graph/badge.svg?token=AI9WFYDDX9)](https://codecov.io/gh/dotkernel/dot-navigation) - -Allows you to easily define and parse menus inside templates, configuration based approach. +[![PHPStan](https://github.com/dotkernel/dot-navigation/actions/workflows/static-analysis.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-navigation/actions/workflows/static-analysis.yml) ## Installation diff --git a/composer.json b/composer.json index 5460d86..4aa8925 100644 --- a/composer.json +++ b/composer.json @@ -35,8 +35,9 @@ }, "require-dev": { "laminas/laminas-coding-standard": "^3.0.0", - "phpunit/phpunit": "^10.4.2", - "vimeo/psalm": "^6.0" + "phpstan/phpstan": "^2.1", + "phpstan/phpstan-phpunit": "^2.0", + "phpunit/phpunit": "^10.4.2" }, "autoload": { "psr-4": { @@ -57,7 +58,6 @@ "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" } } diff --git a/docs/book/v4/overview.md b/docs/book/v4/overview.md index 159d976..646cf17 100644 --- a/docs/book/v4/overview.md +++ b/docs/book/v4/overview.md @@ -1,3 +1,17 @@ # Overview `dot-navigation` is Dotkernel's component that allows you to easily define and parse menus inside templates, using a configuration based approach. + +## Badges + +![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-navigation) +![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-navigation/4.1.0) + +[![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/issues) +[![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/network) +[![GitHub stars](https://img.shields.io/github/stars/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/stargazers) +[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-navigation)](https://github.com/dotkernel/dot-navigation/blob/4.0/LICENSE.md) + +[![Build Static](https://github.com/dotkernel/dot-navigation/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-navigation/actions/workflows/continuous-integration.yml) +[![codecov](https://codecov.io/gh/dotkernel/dot-navigation/graph/badge.svg?token=AI9WFYDDX9)](https://codecov.io/gh/dotkernel/dot-navigation) +[![PHPStan](https://github.com/dotkernel/dot-navigation/actions/workflows/static-analysis.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-navigation/actions/workflows/static-analysis.yml) 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 5b45731..0000000 --- a/psalm-baseline.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - IsAllowedFilter - - - - - RecursiveIterator - - - diff --git a/psalm.xml b/psalm.xml deleted file mode 100644 index 9dd8f07..0000000 --- a/psalm.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - diff --git a/src/View/AbstractNavigationRenderer.php b/src/View/AbstractNavigationRenderer.php index 8f14c4d..23822ac 100644 --- a/src/View/AbstractNavigationRenderer.php +++ b/src/View/AbstractNavigationRenderer.php @@ -98,11 +98,11 @@ protected function getContainer(string|NavigationContainer $container): Navigati { if (is_string($container)) { return $this->navigation->getContainer($container); - } elseif (! $container instanceof NavigationContainer) { + } elseif ($container instanceof NavigationContainer) { + return $container; + } else { throw new RuntimeException('Container must be a string or an instance of ' . NavigationContainer::class); } - - return $container; } protected function cleanAttributes(array $input, array $valid): array diff --git a/test/Exception/InvalidArgumentExceptionTest.php b/test/Exception/InvalidArgumentExceptionTest.php index d283fd9..0c37e59 100644 --- a/test/Exception/InvalidArgumentExceptionTest.php +++ b/test/Exception/InvalidArgumentExceptionTest.php @@ -13,7 +13,7 @@ class InvalidArgumentExceptionTest extends TestCase public function testWillReturnCorrectInstances(): void { $exception = new InvalidArgumentException('test'); - $this->assertInstanceOf(InvalidArgumentException::class, $exception); - $this->assertInstanceOf(ExceptionInterface::class, $exception); + $this->assertSame(InvalidArgumentException::class, $exception::class); + $this->assertContainsOnlyInstancesOf(ExceptionInterface::class, [$exception]); } } diff --git a/test/Exception/RuntimeExceptionTest.php b/test/Exception/RuntimeExceptionTest.php index 9d46320..e2428c9 100644 --- a/test/Exception/RuntimeExceptionTest.php +++ b/test/Exception/RuntimeExceptionTest.php @@ -18,7 +18,7 @@ public function setUp(): void public function testWillReturnCorrectInstances(): void { $exception = new RuntimeException('test'); - $this->assertInstanceOf(RuntimeException::class, $exception); - $this->assertInstanceOf(ExceptionInterface::class, $exception); + $this->assertSame(RuntimeException::class, $exception::class); + $this->assertContainsOnlyInstancesOf(ExceptionInterface::class, [$exception]); } } diff --git a/test/Factory/NavigationMiddlewareFactoryTest.php b/test/Factory/NavigationMiddlewareFactoryTest.php index d075b39..fed1db5 100644 --- a/test/Factory/NavigationMiddlewareFactoryTest.php +++ b/test/Factory/NavigationMiddlewareFactoryTest.php @@ -54,6 +54,6 @@ public function testWillCreateMiddleware(): void ->willReturn($navigation); $middleware = (new NavigationMiddlewareFactory())($container); - $this->assertInstanceOf(NavigationMiddleware::class, $middleware); + $this->assertSame(NavigationMiddleware::class, $middleware::class); } } diff --git a/test/Factory/NavigationOptionsFactoryTest.php b/test/Factory/NavigationOptionsFactoryTest.php index 6455d8d..1d1fa79 100644 --- a/test/Factory/NavigationOptionsFactoryTest.php +++ b/test/Factory/NavigationOptionsFactoryTest.php @@ -79,6 +79,6 @@ public function testWillCreateNavigationOptions(): void ]); $options = (new NavigationOptionsFactory())($container); - $this->assertInstanceOf(NavigationOptions::class, $options); + $this->assertSame(NavigationOptions::class, $options::class); } } diff --git a/test/Factory/NavigationRendererFactoryTest.php b/test/Factory/NavigationRendererFactoryTest.php index 38e91ff..b699ac0 100644 --- a/test/Factory/NavigationRendererFactoryTest.php +++ b/test/Factory/NavigationRendererFactoryTest.php @@ -97,6 +97,6 @@ public function testWillNotCreateNavigationRenderer(): void ]); $renderer = (new NavigationRendererFactory())($container); - $this->assertInstanceOf(NavigationRenderer::class, $renderer); + $this->assertSame(NavigationRenderer::class, $renderer::class); } } diff --git a/test/Factory/NavigationServiceFactoryTest.php b/test/Factory/NavigationServiceFactoryTest.php index fbe9326..1af2f12 100644 --- a/test/Factory/NavigationServiceFactoryTest.php +++ b/test/Factory/NavigationServiceFactoryTest.php @@ -101,7 +101,7 @@ public function testWillCreateNavigationServiceWithoutAuthorizationInterface(): ]); $service = (new NavigationServiceFactory())($container); - $this->assertInstanceOf(Navigation::class, $service); + $this->assertSame(Navigation::class, $service::class); } /** @@ -132,6 +132,6 @@ public function testWillCreateNavigationServiceWithAuthorizationInterface(): voi ]); $service = (new NavigationServiceFactory())($container); - $this->assertInstanceOf(Navigation::class, $service); + $this->assertSame(Navigation::class, $service::class); } } diff --git a/test/Factory/ProviderPluginManagerFactoryTest.php b/test/Factory/ProviderPluginManagerFactoryTest.php index e79cfa5..4f4511d 100644 --- a/test/Factory/ProviderPluginManagerFactoryTest.php +++ b/test/Factory/ProviderPluginManagerFactoryTest.php @@ -106,6 +106,6 @@ public function testWillCreateProviderPluginManager(): void ]); $manager = (new ProviderPluginManagerFactory())($container); - $this->assertInstanceOf(ProviderPluginManager::class, $manager); + $this->assertSame(ProviderPluginManager::class, $manager::class); } } diff --git a/test/Filter/IsAllowedFilterTest.php b/test/Filter/IsAllowedFilterTest.php index 6f9f3ed..0035bb6 100644 --- a/test/Filter/IsAllowedFilterTest.php +++ b/test/Filter/IsAllowedFilterTest.php @@ -74,7 +74,7 @@ public function testGetChildren(): void $navigation = $this->createMock(NavigationInterface::class); $filter = new IsAllowedFilter(new NavigationContainer([new Page()]), $navigation); - $this->assertInstanceOf(IsAllowedFilter::class, $filter->getChildren()); + $this->assertSame(IsAllowedFilter::class, $filter->getChildren()::class); } /** @@ -86,6 +86,6 @@ public function testWillCreateFilter(): void $navigation = $this->createMock(NavigationInterface::class); $filter = new IsAllowedFilter($iterator, $navigation); - $this->assertInstanceOf(IsAllowedFilter::class, $filter); + $this->assertSame(IsAllowedFilter::class, $filter::class); } } diff --git a/test/NavigationMiddlewareTest.php b/test/NavigationMiddlewareTest.php index 2063f7a..cb3a4e5 100644 --- a/test/NavigationMiddlewareTest.php +++ b/test/NavigationMiddlewareTest.php @@ -22,7 +22,7 @@ public function testWillCreateMiddleware(): void $navigation = $this->createMock(NavigationInterface::class); $middleware = new NavigationMiddleware($navigation); - $this->assertInstanceOf(NavigationMiddleware::class, $middleware); + $this->assertSame(NavigationMiddleware::class, $middleware::class); } /** @@ -36,6 +36,6 @@ public function testWillProcessRequest(): void $middleware = new NavigationMiddleware($navigation); $response = $middleware->process($request, $handler); - $this->assertInstanceOf(ResponseInterface::class, $response); + $this->assertContainsOnlyInstancesOf(ResponseInterface::class, [$response]); } } diff --git a/test/Options/NavigationOptionsTest.php b/test/Options/NavigationOptionsTest.php index dfe4f2d..b513994 100644 --- a/test/Options/NavigationOptionsTest.php +++ b/test/Options/NavigationOptionsTest.php @@ -12,7 +12,7 @@ class NavigationOptionsTest extends TestCase public function testWillCreateNavigationOptions(): void { $options = new NavigationOptions(); - $this->assertInstanceOf(NavigationOptions::class, $options); + $this->assertSame(NavigationOptions::class, $options::class); } public function testAccessors(): void diff --git a/test/PageTest.php b/test/PageTest.php index cc6601e..255fbf1 100644 --- a/test/PageTest.php +++ b/test/PageTest.php @@ -12,7 +12,7 @@ class PageTest extends TestCase public function testWillCreatePage(): void { $page = new Page(); - $this->assertInstanceOf(Page::class, $page); + $this->assertSame(Page::class, $page::class); } public function testParentAccessors(): void diff --git a/test/Provider/ArrayProviderTest.php b/test/Provider/ArrayProviderTest.php index 329194e..f006e07 100644 --- a/test/Provider/ArrayProviderTest.php +++ b/test/Provider/ArrayProviderTest.php @@ -13,7 +13,7 @@ class ArrayProviderTest extends TestCase public function testWillCreateArrayProvider(): void { $provider = new ArrayProvider(); - $this->assertInstanceOf(ArrayProvider::class, $provider); + $this->assertSame(ArrayProvider::class, $provider::class); } public function testAccessors(): void @@ -40,7 +40,7 @@ public function testGetContainer(): void 'items' => $pageSpecs, ]); $container = $provider->getContainer(); - $this->assertInstanceOf(NavigationContainer::class, $container); + $this->assertSame(NavigationContainer::class, $container::class); $this->assertCount(2, $container->getChildren()); } } diff --git a/test/Provider/FactoryTest.php b/test/Provider/FactoryTest.php index ea4bee7..7a40130 100644 --- a/test/Provider/FactoryTest.php +++ b/test/Provider/FactoryTest.php @@ -25,7 +25,7 @@ public function testWillCreateFactoryWithoutProviderPluginManager(): void $container = $this->createMock(ContainerInterface::class); $factory = new Factory($container); - $this->assertInstanceOf(Factory::class, $factory); + $this->assertSame(Factory::class, $factory::class); } /** @@ -37,7 +37,7 @@ public function testWillCreateFactoryWithProviderPluginManager(): void $manager = $this->createMock(ProviderPluginManager::class); $factory = new Factory($container, $manager); - $this->assertInstanceOf(Factory::class, $factory); + $this->assertSame(Factory::class, $factory::class); } /** @@ -84,7 +84,7 @@ public function testFactoryWillCreateProviderWithValidProviderTypeAndNoOptions() $provider = $factory->create([ 'type' => ArrayProvider::class, ]); - $this->assertInstanceOf(ProviderInterface::class, $provider); + $this->assertContainsOnlyInstancesOf(ProviderInterface::class, [$provider]); } /** @@ -100,7 +100,7 @@ public function testFactoryWillCreateProviderWithValidProviderTypeAndOptions(): 'type' => ArrayProvider::class, 'options' => [], ]); - $this->assertInstanceOf(ProviderInterface::class, $provider); + $this->assertContainsOnlyInstancesOf(ProviderInterface::class, [$provider]); } /** @@ -111,7 +111,7 @@ public function testFactoryWillGetProviderPluginManagerWithoutInitialProviderPlu $container = $this->createMock(ContainerInterface::class); $factory = new Factory($container); - $this->assertInstanceOf(ProviderPluginManager::class, $factory->getProviderPluginManager()); + $this->assertSame(ProviderPluginManager::class, $factory->getProviderPluginManager()::class); } /** @@ -123,6 +123,6 @@ public function testFactoryWillGetProviderPluginManagerWithInitialProviderPlugin $manager = $this->createMock(ProviderPluginManager::class); $factory = new Factory($container, $manager); - $this->assertInstanceOf(ProviderPluginManager::class, $factory->getProviderPluginManager()); + $this->assertContainsOnlyInstancesOf(ProviderPluginManager::class, [$factory->getProviderPluginManager()]); } } diff --git a/test/Service/NavigationTest.php b/test/Service/NavigationTest.php index 01c6574..5f1c2f9 100644 --- a/test/Service/NavigationTest.php +++ b/test/Service/NavigationTest.php @@ -30,7 +30,7 @@ public function testNavigationWillInitialize(): void $options = $this->createMock(NavigationOptions::class); $navigation = new Navigation($factory, $route, $options); - $this->assertInstanceOf(NavigationInterface::class, $navigation); + $this->assertContainsOnlyInstancesOf(NavigationInterface::class, [$navigation]); } /** @@ -84,7 +84,7 @@ public function testNavigationWillGetValidContainer(): void ]); $navigation = new Navigation($factory, $route, $options); - $this->assertInstanceOf(NavigationContainer::class, $navigation->getContainer('default')); + $this->assertContainsOnlyInstancesOf(NavigationContainer::class, [$navigation->getContainer('default')]); } /** diff --git a/test/View/NavigationRendererTest.php b/test/View/NavigationRendererTest.php index 12d25da..ab2c9cb 100644 --- a/test/View/NavigationRendererTest.php +++ b/test/View/NavigationRendererTest.php @@ -24,7 +24,7 @@ public function testWillCreateNavigationRenderer(): void $options = $this->createMock(NavigationOptions::class); $renderer = new NavigationRenderer($navigation, $template, $options); - $this->assertInstanceOf(NavigationRenderer::class, $renderer); + $this->assertSame(NavigationRenderer::class, $renderer::class); } /**