From a25fea06dab57a7821245d0a42fb52f96fa00967 Mon Sep 17 00:00:00 2001
From: horea <horea@rospace.com>
Date: Mon, 24 Feb 2025 17:45:08 +0200
Subject: [PATCH 1/6] Issue#45: Replace Psalm with PHPStan

Signed-off-by: horea <horea@rospace.com>
---
 .github/workflows/static-analysis.yml         | 50 +++++++++++++++++++
 README.md                                     |  2 +-
 composer.json                                 |  8 +--
 phpstan.neon                                  |  8 +++
 psalm.xml                                     | 17 -------
 test/AbstractActionControllerTest.php         |  4 +-
 ...ontrollerEventListenersInitializerTest.php |  2 -
 test/Factory/PluginManagerFactoryTest.php     |  4 +-
 8 files changed, 67 insertions(+), 28 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..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 f35dbc4..4107946 100644
--- a/README.md
+++ b/README.md
@@ -14,7 +14,7 @@ It is more lightweight though, but supports controller plugins and event listene
 
 [![Build Static](https://github.com/dotkernel/dot-controller/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-controller/actions/workflows/continuous-integration.yml)
 [![codecov](https://codecov.io/gh/dotkernel/dot-controller/graph/badge.svg?token=VUBG5LM4CK)](https://codecov.io/gh/dotkernel/dot-controller)
-
+[![PHPStan](https://github.com/dotkernel/dot-controller/actions/workflows/static-analysis.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-controller/actions/workflows/static-analysis.yml)
 ## Installation
 
 Install `dot-controller` by executing the following Composer command:
diff --git a/composer.json b/composer.json
index 7182060..0f3796c 100644
--- a/composer.json
+++ b/composer.json
@@ -28,8 +28,9 @@
     "require-dev": {
         "laminas/laminas-coding-standard": "^3.0",
         "laminas/laminas-diactoros": "^3.0",
-        "phpunit/phpunit": "^10.2",
-        "vimeo/psalm": "^6.0"
+        "phpstan/phpstan": "^2.1",
+        "phpstan/phpstan-phpunit": "^2.0",
+        "phpunit/phpunit": "^10.2"
     },
     "autoload": {
         "psr-4": {
@@ -50,8 +51,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/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 @@
-<?xml version="1.0"?>
-<psalm
-        errorLevel="4"
-        resolveFromConfigFile="true"
-        findUnusedCode="false"
-        findUnusedBaselineEntry="true"
-        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
-        xmlns="https://getpsalm.org/schema/config"
-        xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
->
-    <projectFiles>
-        <directory name="src" />
-        <ignoreFiles>
-            <directory name="vendor" />
-        </ignoreFiles>
-    </projectFiles>
-</psalm>
diff --git a/test/AbstractActionControllerTest.php b/test/AbstractActionControllerTest.php
index 4f54ac7..831e888 100644
--- a/test/AbstractActionControllerTest.php
+++ b/test/AbstractActionControllerTest.php
@@ -28,7 +28,7 @@ public function testNonExistingActionWillReturnResponse(): void
         };
 
         $response = $subject->process($request, $handler);
-        $this->assertInstanceOf(ResponseInterface::class, $response);
+        $this->assertContainsOnlyInstancesOf(ResponseInterface::class, [$response]);
     }
 
     /**
@@ -49,6 +49,6 @@ public function indexAction(): ResponseInterface
         };
 
         $response = $subject->process($request, $handler);
-        $this->assertInstanceOf(ResponseInterface::class, $response);
+        $this->assertContainsOnlyInstancesOf(ResponseInterface::class, [$response]);
     }
 }
diff --git a/test/Factory/ControllerEventListenersInitializerTest.php b/test/Factory/ControllerEventListenersInitializerTest.php
index 26ed7e5..62c5302 100644
--- a/test/Factory/ControllerEventListenersInitializerTest.php
+++ b/test/Factory/ControllerEventListenersInitializerTest.php
@@ -73,7 +73,5 @@ public function testAttachControllerListeners(): void
             ->with($this->isInstanceOf(ControllerEventListenerInterface::class), 1);
 
         $this->subject->attachControllerListeners($this->container, $this->controller);
-
-        $this->assertTrue(true);
     }
 }
diff --git a/test/Factory/PluginManagerFactoryTest.php b/test/Factory/PluginManagerFactoryTest.php
index ef4f3b1..1d3c2b1 100644
--- a/test/Factory/PluginManagerFactoryTest.php
+++ b/test/Factory/PluginManagerFactoryTest.php
@@ -60,7 +60,7 @@ public function testInvokeWithUrlHelper(): void
 
         $pluginManager = $this->subject->__invoke($this->container);
 
-        $this->assertInstanceOf(PluginManager::class, $pluginManager);
+        $this->assertContainsOnlyInstancesOf(PluginManager::class, [$pluginManager]);
         $this->assertTrue($pluginManager->has('url'));
         $this->assertFalse($pluginManager->has('template'));
         $this->assertInstanceOf(UrlHelperPlugin::class, $pluginManager->get('url'));
@@ -91,7 +91,7 @@ public function testInvokeWithTemplateRenderer(): void
 
         $pluginManager = $this->subject->__invoke($this->container);
 
-        $this->assertInstanceOf(PluginManager::class, $pluginManager);
+        $this->assertContainsOnlyInstancesOf(PluginManager::class, [$pluginManager]);
         $this->assertTrue($pluginManager->has('template'));
         $this->assertFalse($pluginManager->has('url'));
         $this->assertInstanceOf(TemplatePlugin::class, $pluginManager->get('template'));

From cdbefd2a054e2cc4ee5517d1347b504a7699764b Mon Sep 17 00:00:00 2001
From: horea <horea@rospace.com>
Date: Mon, 24 Feb 2025 17:49:10 +0200
Subject: [PATCH 2/6] Issue#45: Replace Psalm with PHPStan

Signed-off-by: horea <horea@rospace.com>
---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 4107946..858d229 100644
--- a/README.md
+++ b/README.md
@@ -15,6 +15,7 @@ It is more lightweight though, but supports controller plugins and event listene
 [![Build Static](https://github.com/dotkernel/dot-controller/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-controller/actions/workflows/continuous-integration.yml)
 [![codecov](https://codecov.io/gh/dotkernel/dot-controller/graph/badge.svg?token=VUBG5LM4CK)](https://codecov.io/gh/dotkernel/dot-controller)
 [![PHPStan](https://github.com/dotkernel/dot-controller/actions/workflows/static-analysis.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-controller/actions/workflows/static-analysis.yml)
+
 ## Installation
 
 Install `dot-controller` by executing the following Composer command:

From a6796c87606d17eb23f2a942243a75ecb87f4ec9 Mon Sep 17 00:00:00 2001
From: horea <horea@rospace.com>
Date: Mon, 24 Feb 2025 18:04:49 +0200
Subject: [PATCH 3/6] Issue#45: Replace Psalm with PHPStan

Signed-off-by: horea <horea@rospace.com>
---
 phpstan.neon | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/phpstan.neon b/phpstan.neon
index 349be25..8a8b704 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -6,3 +6,8 @@ parameters:
         - src
         - test
     treatPhpDocTypesAsCertain: false
+    ignoreErrors:
+            - message: '#Call to an undefined method .*::somePlugin\(\)\.#'
+              path: test/AbstractControllerTest.php
+            - message: '#Call to an undefined method .*::callablePlugin\(\)\.#'
+              path: test/AbstractControllerTest.php

From 8da48a6e3c19539d7e48dd701b858eb20c50a2de Mon Sep 17 00:00:00 2001
From: horea <horea@rospace.com>
Date: Tue, 25 Feb 2025 15:58:33 +0200
Subject: [PATCH 4/6] Issue#45: Replace Psalm with PHPStan

Signed-off-by: horea <horea@rospace.com>
---
 README.md                |  8 +++++++-
 docs/book/v4/overview.md | 14 ++++++++++++++
 2 files changed, 21 insertions(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 858d229..e708948 100644
--- a/README.md
+++ b/README.md
@@ -4,8 +4,14 @@ This is Dotkernel's controller package that can be use like middleware inside Do
 It provides base classes for action based controllers similar to Laminas controller component.
 It is more lightweight though, but supports controller plugins and event listeners.
 
+## Documentation
+
+Documentation is available at: https://docs.dotkernel.org/dot-controller/.
+
+## Badges
+
 ![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-controller)
-![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-controller/4.0.1)
+![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-controller/4.1.0)
 
 [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/issues)
 [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/network)
diff --git a/docs/book/v4/overview.md b/docs/book/v4/overview.md
index be4e4b6..1d4a822 100644
--- a/docs/book/v4/overview.md
+++ b/docs/book/v4/overview.md
@@ -3,3 +3,17 @@
 `dot-controller` is Dotkernel's controller package that can be use like middleware inside Dotkernel or Mezzio application.
 It provides base classes for action based controllers similar to Laminas controller component.
 It is more lightweight though, but supports controller plugins and event listeners.
+
+## Badges
+
+![OSS Lifecycle](https://img.shields.io/osslifecycle/dotkernel/dot-controller)
+![PHP from Packagist (specify version)](https://img.shields.io/packagist/php-v/dotkernel/dot-controller/4.1.0)
+
+[![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/issues)
+[![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/network)
+[![GitHub stars](https://img.shields.io/github/stars/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/stargazers)
+[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/blob/3.0/LICENSE)
+
+[![Build Static](https://github.com/dotkernel/dot-controller/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-controller/actions/workflows/continuous-integration.yml)
+[![codecov](https://codecov.io/gh/dotkernel/dot-controller/graph/badge.svg?token=HZKFRQWDSV)](https://codecov.io/gh/dotkernel/dot-controller)
+[![PHPStan](https://github.com/dotkernel/dot-controller/actions/workflows/static-analysis.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-controller/actions/workflows/static-analysis.yml)

From 244a3da61021cef2be11073b6d36a2350f0b77c2 Mon Sep 17 00:00:00 2001
From: horea <horea@rospace.com>
Date: Wed, 26 Feb 2025 17:55:38 +0200
Subject: [PATCH 5/6] Issue#45: Replace Psalm with PHPStan

Signed-off-by: horea <horea@rospace.com>
---
 docs/book/v4/overview.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/docs/book/v4/overview.md b/docs/book/v4/overview.md
index 1d4a822..d9875cc 100644
--- a/docs/book/v4/overview.md
+++ b/docs/book/v4/overview.md
@@ -12,8 +12,8 @@ It is more lightweight though, but supports controller plugins and event listene
 [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/issues)
 [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/network)
 [![GitHub stars](https://img.shields.io/github/stars/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/stargazers)
-[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/blob/3.0/LICENSE)
+[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/blob/4.0/LICENSE)
 
 [![Build Static](https://github.com/dotkernel/dot-controller/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-controller/actions/workflows/continuous-integration.yml)
-[![codecov](https://codecov.io/gh/dotkernel/dot-controller/graph/badge.svg?token=HZKFRQWDSV)](https://codecov.io/gh/dotkernel/dot-controller)
+[![codecov](https://codecov.io/gh/dotkernel/dot-controller/graph/badge.svg?token=VUBG5LM4CK)](https://codecov.io/gh/dotkernel/dot-controller)
 [![PHPStan](https://github.com/dotkernel/dot-controller/actions/workflows/static-analysis.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-controller/actions/workflows/static-analysis.yml)

From b7d8cdf4e2b1ce765a1672e56201ccabf7bbf165 Mon Sep 17 00:00:00 2001
From: Alex Karajos <alex.merlin.1985@gmail.com>
Date: Thu, 27 Feb 2025 07:16:20 +0200
Subject: [PATCH 6/6] Update docs/book/v4/overview.md

---
 docs/book/v4/overview.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/docs/book/v4/overview.md b/docs/book/v4/overview.md
index d9875cc..f44077b 100644
--- a/docs/book/v4/overview.md
+++ b/docs/book/v4/overview.md
@@ -12,7 +12,7 @@ It is more lightweight though, but supports controller plugins and event listene
 [![GitHub issues](https://img.shields.io/github/issues/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/issues)
 [![GitHub forks](https://img.shields.io/github/forks/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/network)
 [![GitHub stars](https://img.shields.io/github/stars/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/stargazers)
-[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/blob/4.0/LICENSE)
+[![GitHub license](https://img.shields.io/github/license/dotkernel/dot-controller)](https://github.com/dotkernel/dot-controller/blob/4.0/LICENSE.md)
 
 [![Build Static](https://github.com/dotkernel/dot-controller/actions/workflows/continuous-integration.yml/badge.svg?branch=4.0)](https://github.com/dotkernel/dot-controller/actions/workflows/continuous-integration.yml)
 [![codecov](https://codecov.io/gh/dotkernel/dot-controller/graph/badge.svg?token=VUBG5LM4CK)](https://codecov.io/gh/dotkernel/dot-controller)
