From 6050536ab6b44bcae0cab521605fdfdefab0db87 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Fri, 7 Apr 2023 09:12:09 +0200
Subject: [PATCH 01/40] Add Magento 2.4.6 to CI pipeline

---
 .github/workflows/ci.yml | 7 +++++++
 composer.json            | 2 +-
 2 files changed, 8 insertions(+), 1 deletion(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index e75020f..7a73f92 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -21,6 +21,9 @@ jobs:
           - php-versions: '8.1'
             magento: '2.4.5'
             operating-system: 'ubuntu-latest'
+          - php-versions: '8.1'
+            magento: '2.4.6'
+            operating-system: 'ubuntu-latest'
             coveralls: true
     steps:
       - name: Checkout repo
@@ -77,6 +80,10 @@ jobs:
         if: matrix.magento == '2.4.5'
         run: composer update --with-dependencies magento/framework:103.0.5 laminas/laminas-code:4.5.2 roave/security-advisories
 
+      - name: Install Magento 2.4.6
+        if: matrix.magento == '2.4.6'
+        run: composer update --with-dependencies magento/framework:103.0.6 laminas/laminas-code:4.10.0 roave/security-advisories
+
       - name: Composer license check
         run: composer check-license
 
diff --git a/composer.json b/composer.json
index f3b336e..67a7eac 100644
--- a/composer.json
+++ b/composer.json
@@ -22,7 +22,7 @@
   "require": {
     "php": "^7.2.0 || ^8.1.0",
     "ext-dom": "*",
-    "laminas/laminas-code": "~3.3.0 || ~3.4.1 || ~3.5.1 || ^4.5",
+    "laminas/laminas-code": "~3.3.0 || ~3.4.1 || ~3.5.1 || ^4.5 || ^4.10",
     "phpstan/phpstan": "~1.10.3",
     "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0"
   },

From b431120a67f65070d3f22f97d3feec1c67ace059 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Fri, 7 Apr 2023 09:29:50 +0200
Subject: [PATCH 02/40] Fix Composer dependencies in CI pipeline

---
 .github/workflows/ci.yml | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7a73f92..07d5709 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -74,15 +74,15 @@ jobs:
 
       - name: Install Magento 2.4.4
         if: matrix.magento == '2.4.4'
-        run: composer update --with-dependencies magento/framework:103.0.4 laminas/laminas-code:4.5.1
+        run: composer update --with-dependencies magento/framework:103.0.4 laminas/laminas-code:4.5.1 symfony/yaml symfony/console
 
       - name: Install Magento 2.4.5
         if: matrix.magento == '2.4.5'
-        run: composer update --with-dependencies magento/framework:103.0.5 laminas/laminas-code:4.5.2 roave/security-advisories
+        run: composer update --with-dependencies magento/framework:103.0.5 laminas/laminas-code:4.5.2 roave/security-advisories symfony/yaml symfony/console
 
       - name: Install Magento 2.4.6
         if: matrix.magento == '2.4.6'
-        run: composer update --with-dependencies magento/framework:103.0.6 laminas/laminas-code:4.10.0 roave/security-advisories
+        run: composer update --with-dependencies magento/framework:103.0.6 laminas/laminas-code:4.10.0 roave/security-advisories symfony/yaml symfony/console
 
       - name: Composer license check
         run: composer check-license

From a6a1d5feacc8b859d15fc3296790b06d9ef25a0d Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Fri, 7 Apr 2023 11:11:18 +0200
Subject: [PATCH 03/40] Add rule "resource models should be used directly"

---
 docs/features.md                              | 11 +++
 extension.neon                                |  6 +-
 ...ResourceModelsShouldBeUsedDirectlyRule.php | 73 ++++++++++++++
 .../Magento/Rules/Helper/resource_model.php   |  4 +
 ...ModelsShouldBeUsedDirectlyRuleUnitTest.php | 97 +++++++++++++++++++
 5 files changed, 190 insertions(+), 1 deletion(-)
 create mode 100644 src/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRule.php
 create mode 100644 tests/bitExpert/PHPStan/Magento/Rules/Helper/resource_model.php
 create mode 100644 tests/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRuleUnitTest.php

diff --git a/docs/features.md b/docs/features.md
index 2da9045..ed736e1 100644
--- a/docs/features.md
+++ b/docs/features.md
@@ -61,6 +61,17 @@ parameters:
         checkServiceContracts: false
 ```
 
+### Resource Models should be used directly
+
+Since Magento framework version 100.1.0 it is no longer recommended to use `\Magento\Framework\Model\AbtractModel::getResource()` for retrieving the model resource. Use [service contracts](https://devdocs.magento.com/guides/v2.4/extension-dev-guide/service-contracts/service-contracts.html) instead.
+
+To disable this rule add the following code to your `phpstan.neon` configuration file:
+```neon
+parameters:
+    magento:
+        checkResourceModelsUsedDirectly: false
+```
+
 ### Collections should be used directly via factory
 
 Since Magento framework version 101.0.0 Collections should be used directly via factory instead of calling
diff --git a/extension.neon b/extension.neon
index ed89b52..bbd27a3 100644
--- a/extension.neon
+++ b/extension.neon
@@ -2,6 +2,7 @@ parameters:
 	magento:
 		checkCollectionViaFactory: true
 		checkServiceContracts: true
+		checkResourceModelsUsedDirectly: true
 		magentoRoot: %currentWorkingDirectory%
 	bootstrapFiles:
 		- magento-autoloader.php
@@ -17,7 +18,8 @@ conditionalTags:
 		phpstan.rules.rule: %magento.checkCollectionViaFactory%
 	bitExpert\PHPStan\Magento\Rules\AbstractModelUseServiceContractRule:
 		phpstan.rules.rule: %magento.checkServiceContracts%
-
+	bitExpert\PHPStan\Magento\Rules\ResourceModelsShouldBeUsedDirectlyRule:
+		phpstan.rules.rule: %magento.checkResourceModelsUsedDirectly%
 services:
 	-
 		class: bitExpert\PHPStan\Magento\Type\ObjectManagerDynamicReturnTypeExtension
@@ -43,6 +45,8 @@ services:
 		class: bitExpert\PHPStan\Magento\Rules\AbstractModelRetrieveCollectionViaFactoryRule
 	-
 		class: bitExpert\PHPStan\Magento\Rules\AbstractModelUseServiceContractRule
+	-
+		class: bitExpert\PHPStan\Magento\Rules\ResourceModelsShouldBeUsedDirectlyRule
 	fileCacheStorage:
 		class: bitExpert\PHPStan\Magento\Autoload\Cache\FileCacheStorage
 		arguments:
diff --git a/src/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRule.php b/src/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRule.php
new file mode 100644
index 0000000..997db80
--- /dev/null
+++ b/src/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRule.php
@@ -0,0 +1,73 @@
+<?php
+
+/*
+ * This file is part of the phpstan-magento package.
+ *
+ * (c) bitExpert AG
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace bitExpert\PHPStan\Magento\Rules;
+
+use PhpParser\Node;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Rules\Rule;
+use PHPStan\ShouldNotHappenException;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\VerbosityLevel;
+
+/**
+ * Since 100.1.0 resource models should be used directly.
+ *
+ * @implements Rule<MethodCall>
+ */
+class ResourceModelsShouldBeUsedDirectlyRule implements Rule
+{
+    /**
+     * @phpstan-return class-string<MethodCall>
+     * @return string
+     */
+    public function getNodeType(): string
+    {
+        return MethodCall::class;
+    }
+
+    /**
+     * @param Node $node
+     * @param Scope $scope
+     * @return (string|\PHPStan\Rules\RuleError)[] errors
+     * @throws ShouldNotHappenException
+     */
+    public function processNode(Node $node, Scope $scope): array
+    {
+        if (!$node instanceof MethodCall) {
+            throw new ShouldNotHappenException();
+        }
+
+        if (!$node->name instanceof Node\Identifier) {
+            return [];
+        }
+
+        if (!in_array($node->name->name, ['getResource', '_getResource'], true)) {
+            return [];
+        }
+
+        $type = $scope->getType($node->var);
+        $isAbstractModelType = (new ObjectType('Magento\Framework\Model\AbstractModel'))->isSuperTypeOf($type);
+        if (!$isAbstractModelType->yes()) {
+            return [];
+        }
+
+        return [
+            sprintf(
+                '%s::%s() is deprecated. Use Resource Models directly',
+                $type->describe(VerbosityLevel::typeOnly()),
+                $node->name->name
+            )
+        ];
+    }
+}
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/Helper/resource_model.php b/tests/bitExpert/PHPStan/Magento/Rules/Helper/resource_model.php
new file mode 100644
index 0000000..a16c226
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Rules/Helper/resource_model.php
@@ -0,0 +1,4 @@
+<?php
+
+$model = new \bitExpert\PHPStan\Magento\Rules\Helper\SampleModel();
+$model->getResource();
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRuleUnitTest.php b/tests/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRuleUnitTest.php
new file mode 100644
index 0000000..0441e12
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRuleUnitTest.php
@@ -0,0 +1,97 @@
+<?php
+
+/*
+ * This file is part of the phpstan-magento package.
+ *
+ * (c) bitExpert AG
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace bitExpert\PHPStan\Magento\Rules;
+
+use bitExpert\PHPStan\Magento\Rules\Helper\SampleModel;
+use PhpParser\Node\Expr\MethodCall;
+use PhpParser\Node\Expr\Variable;
+use PHPStan\Analyser\Scope;
+use PHPStan\Rules\Rule;
+use PHPStan\ShouldNotHappenException;
+use PHPStan\Testing\RuleTestCase;
+
+/**
+ * @extends \PHPStan\Testing\RuleTestCase<ResourceModelsShouldBeUsedDirectlyRule>
+ */
+class ResourceModelsShouldBeUsedDirectlyRuleUnitTest extends RuleTestCase
+{
+    protected function getRule(): Rule
+    {
+        return new ResourceModelsShouldBeUsedDirectlyRule();
+    }
+
+    /**
+     * @test
+     */
+    public function checkCaughtExceptions(): void
+    {
+        $this->analyse([__DIR__ . '/Helper/resource_model.php'], [
+            [
+                SampleModel::class . '::getResource() is deprecated. Use Resource Models directly',
+                4,
+            ],
+        ]);
+    }
+
+    /**
+     * @test
+     */
+    public function getNodeTypeMethodReturnsMethodCall(): void
+    {
+        $rule = new ResourceModelsShouldBeUsedDirectlyRule();
+
+        self::assertSame(MethodCall::class, $rule->getNodeType());
+    }
+
+    /**
+     * @test
+     */
+    public function processNodeThrowsExceptionForNonMethodCallNodes(): void
+    {
+        $this->expectException(ShouldNotHappenException::class);
+
+        $node = new Variable('var');
+        $scope = $this->createMock(Scope::class);
+
+        $rule = new ResourceModelsShouldBeUsedDirectlyRule();
+        $rule->processNode($node, $scope);
+    }
+
+    /**
+     * @test
+     */
+    public function processNodeReturnsEarlyWhenNodeNameIsWrongType(): void
+    {
+        $node = new MethodCall(new Variable('var'), new Variable('wrong_node'));
+        $scope = $this->createMock(Scope::class);
+
+        $rule = new ResourceModelsShouldBeUsedDirectlyRule();
+        $return = $rule->processNode($node, $scope);
+
+        self::assertCount(0, $return);
+    }
+
+    /**
+     * @test
+     */
+    public function processNodeReturnsEarlyWhenNodeNameIsNotSaveOrLoadOrDelete(): void
+    {
+        $node = new MethodCall(new Variable('var'), 'wrong_node_name');
+        $scope = $this->createMock(Scope::class);
+
+        $rule = new ResourceModelsShouldBeUsedDirectlyRule();
+        $return = $rule->processNode($node, $scope);
+
+        self::assertCount(0, $return);
+    }
+}

From 89486782699438fe78f961aeed45cd5411193eff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Fri, 7 Apr 2023 14:40:11 +0200
Subject: [PATCH 04/40] Add rule "Disallow setTemplate() in Block classes"

---
 docs/features.md                              | 11 +++
 extension.neon                                |  6 ++
 phpstan.neon                                  |  3 +
 .../SetTemplateDisallowedForBlockRule.php     | 74 ++++++++++++++
 .../Magento/Rules/Helper/SampleBlock.php      | 24 +++++
 .../Rules/Helper/block_settemplate.php        |  4 +
 ...TemplateDisallowedForBlockRuleUnitTest.php | 99 +++++++++++++++++++
 7 files changed, 221 insertions(+)
 create mode 100644 src/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRule.php
 create mode 100644 tests/bitExpert/PHPStan/Magento/Rules/Helper/SampleBlock.php
 create mode 100644 tests/bitExpert/PHPStan/Magento/Rules/Helper/block_settemplate.php
 create mode 100644 tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php

diff --git a/docs/features.md b/docs/features.md
index ed736e1..32b5d15 100644
--- a/docs/features.md
+++ b/docs/features.md
@@ -83,3 +83,14 @@ parameters:
     magento:
         checkCollectionViaFactory: false
 ```
+
+### Do not use setTemplate in Block classes
+
+As the [ExtDN](https://github.com/extdn/extdn-phpcs/blob/master/Extdn/Sniffs/Blocks/SetTemplateInBlockSniff.md) folks have put it: Setters are deprecated in Block classes, because constructor arguments should be preferred instead. Any call to `$this->setTemplate()` after calling upon the parent constructor would undo the configuration via XML layout.
+
+To disable this rule add the following code to your `phpstan.neon` configuration file:
+```neon
+parameters:
+    extdn:
+        setTemplateDisallowedForBlockClasses: false
+```
diff --git a/extension.neon b/extension.neon
index bbd27a3..15a8b27 100644
--- a/extension.neon
+++ b/extension.neon
@@ -4,6 +4,8 @@ parameters:
 		checkServiceContracts: true
 		checkResourceModelsUsedDirectly: true
 		magentoRoot: %currentWorkingDirectory%
+	extdn:
+		setTemplateDisallowedForBlockClasses: true
 	bootstrapFiles:
 		- magento-autoloader.php
 parametersSchema:
@@ -20,6 +22,8 @@ conditionalTags:
 		phpstan.rules.rule: %magento.checkServiceContracts%
 	bitExpert\PHPStan\Magento\Rules\ResourceModelsShouldBeUsedDirectlyRule:
 		phpstan.rules.rule: %magento.checkResourceModelsUsedDirectly%
+	bitExpert\PHPStan\Magento\Rules\SetTemplateDisallowedForBlockRule:
+		phpstan.rules.rule: %extdn.setTemplateDisallowedForBlockClasses%
 services:
 	-
 		class: bitExpert\PHPStan\Magento\Type\ObjectManagerDynamicReturnTypeExtension
@@ -47,6 +51,8 @@ services:
 		class: bitExpert\PHPStan\Magento\Rules\AbstractModelUseServiceContractRule
 	-
 		class: bitExpert\PHPStan\Magento\Rules\ResourceModelsShouldBeUsedDirectlyRule
+	-
+		class: bitExpert\PHPStan\Magento\Rules\SetTemplateDisallowedForBlockRule
 	fileCacheStorage:
 		class: bitExpert\PHPStan\Magento\Autoload\Cache\FileCacheStorage
 		arguments:
diff --git a/phpstan.neon b/phpstan.neon
index 0f85674..bc46835 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -52,6 +52,9 @@ parameters:
         -
             message: '~bitExpert\\PHPStan\\Magento\\Rules\\Helper\\SampleModel::__construct\(\) does not call parent constructor~'
             path: tests/bitExpert/PHPStan/Magento/Rules/Helper/SampleModel.php
+        -
+            message: '~bitExpert\\PHPStan\\Magento\\Rules\\Helper\\SampleBlock::__construct\(\) does not call parent constructor~'
+            path: tests/bitExpert/PHPStan/Magento/Rules/Helper/SampleBlock.php
         -
             message: '~Call to static method PHPUnit\\Framework\\Assert::assertInstanceOf~'
             path: tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
diff --git a/src/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRule.php b/src/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRule.php
new file mode 100644
index 0000000..0e83a64
--- /dev/null
+++ b/src/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRule.php
@@ -0,0 +1,74 @@
+<?php
+
+/*
+ * This file is part of the phpstan-magento package.
+ *
+ * (c) bitExpert AG
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace bitExpert\PHPStan\Magento\Rules;
+
+use PhpParser\Node;
+use PhpParser\Node\Expr\MethodCall;
+use PHPStan\Analyser\Scope;
+use PHPStan\Rules\Rule;
+use PHPStan\ShouldNotHappenException;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\VerbosityLevel;
+
+/**
+ * Do not use setTemplate in Block classes, see
+ * https://github.com/extdn/extdn-phpcs/blob/master/Extdn/Sniffs/Blocks/SetTemplateInBlockSniff.md
+ *
+ * @implements Rule<MethodCall>
+ */
+class SetTemplateDisallowedForBlockRule implements Rule
+{
+    /**
+     * @phpstan-return class-string<MethodCall>
+     * @return string
+     */
+    public function getNodeType(): string
+    {
+        return MethodCall::class;
+    }
+
+    /**
+     * @param Node $node
+     * @param Scope $scope
+     * @return (string|\PHPStan\Rules\RuleError)[] errors
+     * @throws ShouldNotHappenException
+     */
+    public function processNode(Node $node, Scope $scope): array
+    {
+        if (!$node instanceof MethodCall) {
+            throw new ShouldNotHappenException();
+        }
+
+        if (!$node->name instanceof Node\Identifier) {
+            return [];
+        }
+
+        if ($node->name->name !== 'setTemplate') {
+            return [];
+        }
+
+        $type = $scope->getType($node->var);
+        $isAbstractModelType = (new ObjectType('Magento\Framework\View\Element\Template'))->isSuperTypeOf($type);
+        if (!$isAbstractModelType->yes()) {
+            return [];
+        }
+
+        return [
+            sprintf(
+                'Setter methods like %s::%s() are deprecated in Block classes, use constructor arguments instead',
+                $type->describe(VerbosityLevel::typeOnly()),
+                $node->name->name
+            )
+        ];
+    }
+}
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/Helper/SampleBlock.php b/tests/bitExpert/PHPStan/Magento/Rules/Helper/SampleBlock.php
new file mode 100644
index 0000000..ddfe61f
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Rules/Helper/SampleBlock.php
@@ -0,0 +1,24 @@
+<?php
+
+/*
+ * This file is part of the phpstan-magento package.
+ *
+ * (c) bitExpert AG
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace bitExpert\PHPStan\Magento\Rules\Helper;
+
+use \Magento\Framework\View\Element\Template;
+
+class SampleBlock extends Template
+{
+    public function __construct()
+    {
+        // We do not call the parent constructor here on purpose as we do not want do to deal with creating
+        // not needed dependencies just for the testcase!
+    }
+}
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/Helper/block_settemplate.php b/tests/bitExpert/PHPStan/Magento/Rules/Helper/block_settemplate.php
new file mode 100644
index 0000000..dfa3192
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Rules/Helper/block_settemplate.php
@@ -0,0 +1,4 @@
+<?php
+
+$block = new \bitExpert\PHPStan\Magento\Rules\Helper\SampleBlock();
+$block->setTemplate('template.phtml');
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php b/tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php
new file mode 100644
index 0000000..f0a88da
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php
@@ -0,0 +1,99 @@
+<?php
+
+/*
+ * This file is part of the phpstan-magento package.
+ *
+ * (c) bitExpert AG
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace bitExpert\PHPStan\Magento\Rules;
+
+use bitExpert\PHPStan\Magento\Rules\Helper\SampleBlock;
+use bitExpert\PHPStan\Magento\Rules\Helper\SampleModel;
+use PhpParser\Node\Expr\MethodCall;
+use PhpParser\Node\Expr\Variable;
+use PHPStan\Analyser\Scope;
+use PHPStan\Rules\Rule;
+use PHPStan\ShouldNotHappenException;
+use PHPStan\Testing\RuleTestCase;
+
+/**
+ * @extends \PHPStan\Testing\RuleTestCase<SetTemplateDisallowedForBlockRule>
+ */
+class SetTemplateDisallowedForBlockRuleUnitTest extends RuleTestCase
+{
+    protected function getRule(): Rule
+    {
+        return new SetTemplateDisallowedForBlockRule();
+    }
+
+    /**
+     * @test
+     */
+    public function checkCaughtExceptions(): void
+    {
+        $this->analyse([__DIR__ . '/Helper/block_settemplate.php'], [
+            [
+                'Setter methods like '.SampleBlock::class.'::setTemplate() are deprecated in Block classes, '.
+                'use constructor arguments instead',
+                4,
+            ],
+        ]);
+    }
+
+    /**
+     * @test
+     */
+    public function getNodeTypeMethodReturnsMethodCall(): void
+    {
+        $rule = new SetTemplateDisallowedForBlockRule();
+
+        self::assertSame(MethodCall::class, $rule->getNodeType());
+    }
+
+    /**
+     * @test
+     */
+    public function processNodeThrowsExceptionForNonMethodCallNodes(): void
+    {
+        $this->expectException(ShouldNotHappenException::class);
+
+        $node = new Variable('var');
+        $scope = $this->createMock(Scope::class);
+
+        $rule = new SetTemplateDisallowedForBlockRule();
+        $rule->processNode($node, $scope);
+    }
+
+    /**
+     * @test
+     */
+    public function processNodeReturnsEarlyWhenNodeNameIsWrongType(): void
+    {
+        $node = new MethodCall(new Variable('var'), new Variable('wrong_node'));
+        $scope = $this->createMock(Scope::class);
+
+        $rule = new SetTemplateDisallowedForBlockRule();
+        $return = $rule->processNode($node, $scope);
+
+        self::assertCount(0, $return);
+    }
+
+    /**
+     * @test
+     */
+    public function processNodeReturnsEarlyWhenNodeNameIsNotSaveOrLoadOrDelete(): void
+    {
+        $node = new MethodCall(new Variable('var'), 'wrong_node_name');
+        $scope = $this->createMock(Scope::class);
+
+        $rule = new SetTemplateDisallowedForBlockRule();
+        $return = $rule->processNode($node, $scope);
+
+        self::assertCount(0, $return);
+    }
+}

From 073b0d8a6db7acf81fd6900a1973882db437498f Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Fri, 7 Apr 2023 14:49:42 +0200
Subject: [PATCH 05/40] Update Readme.md file

---
 README.md | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/README.md b/README.md
index 2ff17e6..7eb3c25 100644
--- a/README.md
+++ b/README.md
@@ -73,7 +73,9 @@ This PHPStan extension works for both Magento module projects and Magento applic
 - Extension attributes
 - PHPStan rules
   - Service contracts
+  - Resource Models should be used directly
   - Collections should be used directly via factory
+  - Do not use setTemplate in Block classes
   
 For a detailed overview, check the feature documentation [here](docs/features.md).
 

From f3900b954fcba883fe4bfe0849b43806b6e87ccf Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Fri, 7 Apr 2023 15:02:21 +0200
Subject: [PATCH 06/40] Update Changelog file

---
 CHANGELOG.md | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1f94d6c..181b8df 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,26 @@
 
 All notable changes to this project will be documented in this file, in reverse chronological order by release.
 
+## 0.30.0
+
+### Added
+
+- [#300](https://github.com/bitExpert/phpstan-magento/pull/300) Add rule "Disallow setTemplate() in Block classes"
+- [#299](https://github.com/bitExpert/phpstan-magento/pull/299) Add rule "resource models should be used directly"
+- [#298](https://github.com/bitExpert/phpstan-magento/pull/298) Add Magento 2.4.6 to CI pipeline
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
 ## 0.29.0
 
 ### Added

From caa7fc003344b077ca2c286f9d1cff46d157e7ff Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Fri, 7 Apr 2023 15:27:31 +0200
Subject: [PATCH 07/40] Fix problem with parametersSchema

---
 extension.neon | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/extension.neon b/extension.neon
index 15a8b27..a43e09a 100644
--- a/extension.neon
+++ b/extension.neon
@@ -12,8 +12,12 @@ parametersSchema:
 	magento: structure([
 		checkCollectionViaFactory: bool()
 		checkServiceContracts: bool()
+		checkResourceModelsUsedDirectly: bool()
 		magentoRoot: string()
 	])
+	extdn: structure([
+		setTemplateDisallowedForBlockClasses: bool()
+	])
 
 conditionalTags:
 	bitExpert\PHPStan\Magento\Rules\AbstractModelRetrieveCollectionViaFactoryRule:

From 646994bb8f65f27f952145b436bd89d7d158e40b Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Fri, 7 Apr 2023 15:32:07 +0200
Subject: [PATCH 08/40] Update Changelog file

---
 CHANGELOG.md | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 181b8df..e30e33d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,24 @@
 
 All notable changes to this project will be documented in this file, in reverse chronological order by release.
 
+## 0.30.1
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- [#302](https://github.com/bitExpert/phpstan-magento/pull/302) Fix problem with parametersSchema
+
 ## 0.30.0
 
 ### Added

From 78458eace6cb1459ddd4858bd37d9df8d0507bdd Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 15 Apr 2023 12:33:36 +0200
Subject: [PATCH 09/40] Extend Neon validation logic for CI

---
 bin/ci_neon_lint | 44 +++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 43 insertions(+), 1 deletion(-)

diff --git a/bin/ci_neon_lint b/bin/ci_neon_lint
index b4e5135..9044313 100755
--- a/bin/ci_neon_lint
+++ b/bin/ci_neon_lint
@@ -13,6 +13,31 @@ declare(strict_types=1);
 
 require_once __DIR__ . '/../vendor/autoload.php';
 
+function convertToNetteSchemaElement($entity)
+{
+    $schema = [];
+
+    if ($entity instanceof \Nette\Neon\Entity) {
+        if (count($entity->attributes) === 0) {
+            return new \Nette\Schema\Elements\Type((string)$entity->value);
+        }
+
+        foreach($entity->attributes as $key => $value) {
+            if (is_array($value)) {
+                return convertToNetteSchemaElement($value);
+            } else {
+                $schema[$key] = new \Nette\Schema\Elements\Type($value);
+            }
+        }
+    } else if (is_array($entity)) {
+        foreach ($entity as $key => $value) {
+            $schema[$key] = convertToNetteSchemaElement($value);
+        }
+    }
+
+    return new \Nette\Schema\Elements\Structure($schema);
+}
+
 // this CLI script will lint all the .neon files in the repository
 
 $path = realpath(__DIR__ . '/../');
@@ -34,7 +59,24 @@ foreach ($it as $file) {
                 throw new \RuntimeException(sprintf('Class "%s" does not exist', $value));
             }
         });
-    } catch (Nette\Neon\Exception $e) {
+
+        if(isset($neon['parameters']) && isset($neon['parametersSchema'])) {
+            $schema = [];
+            foreach($neon['parametersSchema'] as $key => $item) {
+               $schema[$key] = convertToNetteSchemaElement($item);
+            }
+            $schema = new \Nette\Schema\Elements\Structure($schema);
+
+            // remove phpstam parameters to not trigger a failed schema validation
+            unset($neon['parameters']['bootstrapFiles']);
+
+            $processor = new \Nette\Schema\Processor();
+            $processor->process($schema, $neon['parameters']);
+        }
+    } catch (\Nette\Schema\ValidationException $e) {
+        $success = false;
+        echo sprintf("Schema validation failed: %s", $e->getMessage())."\n";
+    } catch (\Nette\Neon\Exception $e) {
         $success = false;
         $relPath = str_replace($path . DIRECTORY_SEPARATOR, '', $file->getRealPath());
         echo sprintf('Failed parsing file "%s"', $relPath)."\n";

From 561495f41229b9ad14de6143e8fe7742592795a7 Mon Sep 17 00:00:00 2001
From: Simon Sprankel <sprankhub@users.noreply.github.com>
Date: Mon, 17 Jul 2023 12:28:00 +0200
Subject: [PATCH 10/40] Fix typo

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index 7eb3c25..955abf1 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ composer.phar require --dev phpstan/extension-installer
 ```
 
 <details>
-  <summary>Composer Allow-PLugins configuration</summary>
+  <summary>Composer Allow-Plugins configuration</summary>
 
 If you're using Composer >= 2.2.0 you have to allow the execution of composer plugins ([see allow-plugins section](https://getcomposer.org/doc/06-config.md#allow-plugins)) as follows:
 

From 4266f15619483d96a3c23d9920edb6154a26c315 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 6 Apr 2024 08:22:09 +0200
Subject: [PATCH 11/40] Update README.md

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index 955abf1..e4c9e43 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ You can use this PHPStan extension for both Magento module projects and Magento
 [![Build Status](https://github.com/bitExpert/phpstan-magento/workflows/ci/badge.svg?branch=master)](https://github.com/bitExpert/phpstan-magento/actions)
 [![Coverage Status](https://coveralls.io/repos/github/bitExpert/phpstan-magento/badge.svg?branch=master)](https://coveralls.io/github/bitExpert/phpstan-magento?branch=master)
 [![installs on Packagist](https://img.shields.io/packagist/dt/bitExpert/phpstan-magento)](https://packagist.org/packages/bitExpert/phpstan-magento/)
+![Mastodon Follow](https://img.shields.io/mastodon/follow/109408681246972700?domain=https://rheinneckar.social)
 
 ## Requirements
 

From b93128e35f8843561cc9c3557619fb379072b327 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 6 Apr 2024 11:09:30 +0200
Subject: [PATCH 12/40] Update README.md

---
 README.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/README.md b/README.md
index e4c9e43..2814891 100644
--- a/README.md
+++ b/README.md
@@ -6,7 +6,7 @@ You can use this PHPStan extension for both Magento module projects and Magento
 [![Build Status](https://github.com/bitExpert/phpstan-magento/workflows/ci/badge.svg?branch=master)](https://github.com/bitExpert/phpstan-magento/actions)
 [![Coverage Status](https://coveralls.io/repos/github/bitExpert/phpstan-magento/badge.svg?branch=master)](https://coveralls.io/github/bitExpert/phpstan-magento?branch=master)
 [![installs on Packagist](https://img.shields.io/packagist/dt/bitExpert/phpstan-magento)](https://packagist.org/packages/bitExpert/phpstan-magento/)
-![Mastodon Follow](https://img.shields.io/mastodon/follow/109408681246972700?domain=https://rheinneckar.social)
+[![Mastodon Follow](https://img.shields.io/mastodon/follow/109408681246972700?domain=https://rheinneckar.social)](https://rheinneckar.social/@bitexpert)
 
 ## Requirements
 

From 3485f6177e397cd244484ec7f3325d0f27bfda9c Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Thu, 16 May 2024 04:33:28 +0000
Subject: [PATCH 13/40] Update phpstan/phpstan requirement from ~1.10.3 to
 ~1.11.1

Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.11.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.10.3...1.11.1)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 composer.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composer.json b/composer.json
index 67a7eac..cf3f972 100644
--- a/composer.json
+++ b/composer.json
@@ -23,7 +23,7 @@
     "php": "^7.2.0 || ^8.1.0",
     "ext-dom": "*",
     "laminas/laminas-code": "~3.3.0 || ~3.4.1 || ~3.5.1 || ^4.5 || ^4.10",
-    "phpstan/phpstan": "~1.10.3",
+    "phpstan/phpstan": "~1.11.1",
     "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0"
   },
   "conflict": {

From e9910327744d22c1dcf8318ff2a9982397ab864e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Tue, 9 Jul 2024 17:02:15 +0200
Subject: [PATCH 14/40] Update README.md file

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 2814891..81d3863 100644
--- a/README.md
+++ b/README.md
@@ -14,10 +14,10 @@ PHP: PHP 7.2 or higher
 
 Magento: Magento 2.3.0 or higher
 
-PHPStan: PHPStan 1.10
+PHPStan: PHPStan 1.11
 
 If you are using a Magento version that requires an older version of PHPStan (e.g. 0.12.77),  you need to manually upgrade it before 
-installing this extension. in your composer.json Change the PHPStan version to `~1.10` and run:
+installing this extension. in your composer.json Change the PHPStan version to `~1.11` and run:
 
 ```
 composer update phpstan/phpstan --with-all-dependencies

From 548663a767c471b23fd9acc08963789537c6abae Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Tue, 9 Jul 2024 17:06:57 +0200
Subject: [PATCH 15/40] Fix typo in PHPStan config

---
 phpstan.neon | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/phpstan.neon b/phpstan.neon
index bc46835..931cc8e 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -41,13 +41,13 @@ parameters:
             message: '~is not covered by backward compatibility promise.~'
             path: tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php
         -
-            message: '~Parameter #1 $argument of class ReflectionClass constructor expects class-string<MyUncachedExtension>|MyUncachedExtension, string given~'
+            message: '~Parameter #1 \$argument of class ReflectionClass constructor expects class-string<MyUncachedExtension>|MyUncachedExtension, string given~'
             path: tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php
         -
             message: '~is not covered by backward compatibility promise.~'
             path: tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php
         -
-            message: '~Parameter #1 $argument of class ReflectionClass constructor expects class-string<UncachedExtensionInterface>|UncachedExtensionInterface, string given~'
+            message: '~Parameter #1 \$argument of class ReflectionClass constructor expects class-string<UncachedExtensionInterface>|UncachedExtensionInterface, string given~'
             path: tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php
         -
             message: '~bitExpert\\PHPStan\\Magento\\Rules\\Helper\\SampleModel::__construct\(\) does not call parent constructor~'

From 4190f438dd8be2ce335949fef4ab9159f8d127a5 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Tue, 9 Jul 2024 17:12:18 +0200
Subject: [PATCH 16/40] Update CHANGELOG.md file

---
 CHANGELOG.md | 20 ++++++++++++++++++++
 1 file changed, 20 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e30e33d..92b4a1f 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,26 @@
 
 All notable changes to this project will be documented in this file, in reverse chronological order by release.
 
+## 0.31
+
+### Added
+
+- [#318](https://github.com/bitExpert/phpstan-magento/pull/318) Update phpstan/phpstan requirement to ~1.11.1
+- [#308](https://github.com/bitExpert/phpstan-magento/pull/308) Update nikic/php-parser requirement to ^5.0.1
+- [#304](https://github.com/bitExpert/phpstan-magento/pull/304) Extend Neon validation logic for CI
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- [#306](https://github.com/bitExpert/phpstan-magento/pull/306) Fix typo
+
 ## 0.30.1
 
 ### Added

From 897520019e01ad5fc24c5c1e6a4be606af76ed05 Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Wed, 28 Aug 2024 04:28:56 +0000
Subject: [PATCH 17/40] Update phpstan/phpstan requirement from ~1.11.1 to
 ~1.12.0

Updates the requirements on [phpstan/phpstan](https://github.com/phpstan/phpstan) to permit the latest version.
- [Release notes](https://github.com/phpstan/phpstan/releases)
- [Changelog](https://github.com/phpstan/phpstan/blob/1.12.x/CHANGELOG.md)
- [Commits](https://github.com/phpstan/phpstan/compare/1.11.1...1.12.0)

---
updated-dependencies:
- dependency-name: phpstan/phpstan
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
---
 composer.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composer.json b/composer.json
index cf3f972..d29ee49 100644
--- a/composer.json
+++ b/composer.json
@@ -23,7 +23,7 @@
     "php": "^7.2.0 || ^8.1.0",
     "ext-dom": "*",
     "laminas/laminas-code": "~3.3.0 || ~3.4.1 || ~3.5.1 || ^4.5 || ^4.10",
-    "phpstan/phpstan": "~1.11.1",
+    "phpstan/phpstan": "~1.12.0",
     "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0"
   },
   "conflict": {

From 2b23d81a29c91fa2132ac51fadc9ad2f1c80a725 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 31 Aug 2024 18:41:11 +0200
Subject: [PATCH 18/40] Update README.md file

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index 81d3863..e7ec08a 100644
--- a/README.md
+++ b/README.md
@@ -14,10 +14,10 @@ PHP: PHP 7.2 or higher
 
 Magento: Magento 2.3.0 or higher
 
-PHPStan: PHPStan 1.11
+PHPStan: PHPStan 1.12
 
 If you are using a Magento version that requires an older version of PHPStan (e.g. 0.12.77),  you need to manually upgrade it before 
-installing this extension. in your composer.json Change the PHPStan version to `~1.11` and run:
+installing this extension. in your composer.json Change the PHPStan version to `~1.12` and run:
 
 ```
 composer update phpstan/phpstan --with-all-dependencies

From 6b7e7cf76c9cbae36c46cffe406a30cbe3e10456 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 31 Aug 2024 18:41:21 +0200
Subject: [PATCH 19/40] Fix CI errors

---
 .../Autoload/ExtensionAutoloaderUnitTest.php  | 22 ++++++++++-------
 .../ExtensionInterfaceAutoloaderUnitTest.php  | 24 ++++++++++++-------
 2 files changed, 29 insertions(+), 17 deletions(-)

diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php
index cd1bca7..8c4a93b 100644
--- a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php
@@ -7,20 +7,25 @@
 use bitExpert\PHPStan\Magento\Autoload\DataProvider\ExtensionAttributeDataProvider;
 use org\bovigo\vfs\vfsStream;
 use PHPStan\Cache\Cache;
+use PHPStan\Cache\CacheStorage;
 use PHPUnit\Framework\TestCase;
 
 class ExtensionAutoloaderUnitTest extends TestCase
 {
     /**
-     * @var Cache|\PHPUnit\Framework\MockObject\MockObject
+     * @var Cache
      */
     private $cache;
     /**
-     * @var ClassLoaderProvider|\PHPUnit\Framework\MockObject\MockObject
+     * @var CacheStorage&\PHPUnit\Framework\MockObject\MockObject
+     */
+    private $cacheStorage;
+    /**
+     * @var ClassLoaderProvider&\PHPUnit\Framework\MockObject\MockObject
      */
     private $classLoader;
     /**
-     * @var ExtensionAttributeDataProvider|\PHPUnit\Framework\MockObject\MockObject
+     * @var ExtensionAttributeDataProvider&\PHPUnit\Framework\MockObject\MockObject
      */
     private $extAttrDataProvider;
     /**
@@ -30,7 +35,8 @@ class ExtensionAutoloaderUnitTest extends TestCase
 
     protected function setUp(): void
     {
-        $this->cache = $this->createMock(Cache::class);
+        $this->cacheStorage = $this->createMock(CacheStorage::class);
+        $this->cache = new Cache($this->cacheStorage);
         $this->classLoader = $this->createMock(ClassLoaderProvider::class);
         $this->extAttrDataProvider = $this->createMock(ExtensionAttributeDataProvider::class);
         $this->autoloader = new ExtensionAutoloader(
@@ -47,7 +53,7 @@ public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void
     {
         $this->classLoader->expects(self::never())
             ->method('findFile');
-        $this->cache->expects(self::never())
+        $this->cacheStorage->expects(self::never())
             ->method('load');
 
         $this->autoloader->autoload('SomeClass');
@@ -61,7 +67,7 @@ public function autoloaderPrefersLocalFile(): void
         $this->classLoader->expects(self::once())
             ->method('findFile')
             ->willReturn(__DIR__ . '/HelperExtension.php');
-        $this->cache->expects(self::never())
+        $this->cacheStorage->expects(self::never())
             ->method('load');
 
         $this->autoloader->autoload(HelperExtension::class);
@@ -77,11 +83,11 @@ public function autoloaderUsesCachedFileWhenFound(): void
         $this->classLoader->expects(self::once())
             ->method('findFile')
             ->willReturn(false);
-        $this->cache->expects(self::once())
+        $this->cacheStorage->expects(self::once())
             ->method('load')
             ->willReturn(__DIR__ . '/HelperExtension.php');
 
-        $this->cache->expects(self::never())
+        $this->cacheStorage->expects(self::never())
             ->method('save');
 
         $this->autoloader->autoload(HelperExtension::class);
diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php
index f62ba1a..2f0e750 100644
--- a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php
@@ -8,20 +8,25 @@
 use InvalidArgumentException;
 use org\bovigo\vfs\vfsStream;
 use PHPStan\Cache\Cache;
+use PHPStan\Cache\CacheStorage;
 use PHPUnit\Framework\TestCase;
 
 class ExtensionInterfaceAutoloaderUnitTest extends TestCase
 {
     /**
-     * @var Cache|\PHPUnit\Framework\MockObject\MockObject
+     * @var Cache
      */
     private $cache;
     /**
-     * @var ExtensionAttributeDataProvider|\PHPUnit\Framework\MockObject\MockObject
+     * @var CacheStorage&\PHPUnit\Framework\MockObject\MockObject
+     */
+    private $cacheStorage;
+    /**
+     * @var ExtensionAttributeDataProvider&\PHPUnit\Framework\MockObject\MockObject
      */
     private $extAttrDataProvider;
     /**
-     * @var ClassLoaderProvider|\PHPUnit\Framework\MockObject\MockObject
+     * @var ClassLoaderProvider&\PHPUnit\Framework\MockObject\MockObject
      */
     private $classLoader;
     /**
@@ -31,7 +36,8 @@ class ExtensionInterfaceAutoloaderUnitTest extends TestCase
 
     protected function setUp(): void
     {
-        $this->cache = $this->createMock(Cache::class);
+        $this->cacheStorage = $this->createMock(CacheStorage::class);
+        $this->cache = new Cache($this->cacheStorage);
         $this->classLoader = $this->createMock(ClassLoaderProvider::class);
         $this->extAttrDataProvider = $this->createMock(ExtensionAttributeDataProvider::class);
         $this->autoloader = new ExtensionInterfaceAutoloader(
@@ -48,7 +54,7 @@ public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void
     {
         $this->classLoader->expects(self::never())
             ->method('findFile');
-        $this->cache->expects(self::never())
+        $this->cacheStorage->expects(self::never())
             ->method('load');
 
         $this->autoloader->autoload('SomeClass');
@@ -62,7 +68,7 @@ public function autoloaderPrefersLocalFile(): void
         $this->classLoader->expects(self::once())
             ->method('findFile')
             ->willReturn(__DIR__ . '/HelperExtensionInterface.php');
-        $this->cache->expects(self::never())
+        $this->cacheStorage->expects(self::never())
             ->method('load');
 
         $this->autoloader->autoload(HelperExtensionInterface::class);
@@ -78,11 +84,11 @@ public function autoloaderUsesCachedFileWhenFound(): void
         $this->classLoader->expects(self::once())
             ->method('findFile')
             ->willReturn(false);
-        $this->cache->expects(self::once())
+        $this->cacheStorage->expects(self::once())
             ->method('load')
             ->willReturn(__DIR__ . '/HelperExtensionInterface.php');
 
-        $this->cache->expects(self::never())
+        $this->cacheStorage->expects(self::never())
             ->method('save');
 
         $this->autoloader->autoload(HelperExtensionInterface::class);
@@ -103,7 +109,7 @@ public function autoloadDoesNotGenerateInterfaceWhenNoAttributesExist(): void
         $this->classLoader->expects(self::once())
             ->method('findFile')
             ->willReturn(false);
-        $this->cache->expects(self::once())
+        $this->cacheStorage->expects(self::once())
             ->method('load')
             ->willReturn(null);
 

From 831f8fc63289432c5ba17abbc7b83c88eb1d4399 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 31 Aug 2024 18:56:53 +0200
Subject: [PATCH 20/40] Update CHANGELOG.md file

---
 CHANGELOG.md | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 92b4a1f..9da463a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,24 @@
 
 All notable changes to this project will be documented in this file, in reverse chronological order by release.
 
+## 0.32
+
+### Added
+
+- [#326](https://github.com/bitExpert/phpstan-magento/pull/326) Update phpstan/phpstan requirement to ~1.12.0
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
 ## 0.31
 
 ### Added

From e51578edff2f9bce2d258d119ed181e89d72c91d Mon Sep 17 00:00:00 2001
From: indykoning <15870933+indykoning@users.noreply.github.com>
Date: Fri, 1 Nov 2024 15:36:44 +0100
Subject: [PATCH 21/40] Add support for symfony/finder 7+

---
 composer.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composer.json b/composer.json
index d29ee49..19d1512 100644
--- a/composer.json
+++ b/composer.json
@@ -24,7 +24,7 @@
     "ext-dom": "*",
     "laminas/laminas-code": "~3.3.0 || ~3.4.1 || ~3.5.1 || ^4.5 || ^4.10",
     "phpstan/phpstan": "~1.12.0",
-    "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0"
+    "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"
   },
   "conflict": {
     "magento/framework": "<102.0.0"

From 949f032ddb15ecd57b19ad13f51666f079cd41e9 Mon Sep 17 00:00:00 2001
From: Pieter Hoste <hoste.pieter@gmail.com>
Date: Tue, 12 Nov 2024 21:46:15 +0100
Subject: [PATCH 22/40] Using RuleErrorBuilder to enrich reported errors

---
 ...tModelRetrieveCollectionViaFactoryRule.php | 25 ++++++++-----------
 .../AbstractModelUseServiceContractRule.php   | 25 ++++++++-----------
 ...nMockMethodNeedsCollectionSubclassRule.php | 24 ++++++++----------
 ...ResourceModelsShouldBeUsedDirectlyRule.php | 25 ++++++++-----------
 .../SetTemplateDisallowedForBlockRule.php     | 25 ++++++++-----------
 ...trieveCollectionViaFactoryRuleUnitTest.php | 15 -----------
 ...actModelUseServiceContractRuleUnitTest.php | 15 -----------
 ...hodNeedsCollectionSubclassRuleUnitTest.php | 14 -----------
 ...ModelsShouldBeUsedDirectlyRuleUnitTest.php | 15 -----------
 ...TemplateDisallowedForBlockRuleUnitTest.php | 15 -----------
 10 files changed, 50 insertions(+), 148 deletions(-)

diff --git a/src/bitExpert/PHPStan/Magento/Rules/AbstractModelRetrieveCollectionViaFactoryRule.php b/src/bitExpert/PHPStan/Magento/Rules/AbstractModelRetrieveCollectionViaFactoryRule.php
index fb81938..9a1c3c5 100644
--- a/src/bitExpert/PHPStan/Magento/Rules/AbstractModelRetrieveCollectionViaFactoryRule.php
+++ b/src/bitExpert/PHPStan/Magento/Rules/AbstractModelRetrieveCollectionViaFactoryRule.php
@@ -16,7 +16,8 @@
 use PhpParser\Node\Expr\MethodCall;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
+use PHPStan\Rules\RuleError;
+use PHPStan\Rules\RuleErrorBuilder;
 use PHPStan\Type\ObjectType;
 use PHPStan\Type\VerbosityLevel;
 
@@ -36,18 +37,8 @@ public function getNodeType(): string
         return MethodCall::class;
     }
 
-    /**
-     * @param Node $node
-     * @param Scope $scope
-     * @return (string|\PHPStan\Rules\RuleError)[] errors
-     * @throws ShouldNotHappenException
-     */
     public function processNode(Node $node, Scope $scope): array
     {
-        if (!$node instanceof MethodCall) {
-            throw new ShouldNotHappenException();
-        }
-
         if (!$node->name instanceof Node\Identifier) {
             return [];
         }
@@ -63,11 +54,15 @@ public function processNode(Node $node, Scope $scope): array
         }
 
         return [
-            sprintf(
-                'Collections should be used directly via factory, not via %s::%s() method',
-                $type->describe(VerbosityLevel::typeOnly()),
-                $node->name->name
+            RuleErrorBuilder::message(
+                sprintf(
+                    'Collections should be used directly via factory, not via %s::%s() method',
+                    $type->describe(VerbosityLevel::typeOnly()),
+                    $node->name->name
+                )
             )
+            ->identifier('bitExpertMagento.abstractModelRetrieveCollectionViaFactory')
+            ->build()
         ];
     }
 }
diff --git a/src/bitExpert/PHPStan/Magento/Rules/AbstractModelUseServiceContractRule.php b/src/bitExpert/PHPStan/Magento/Rules/AbstractModelUseServiceContractRule.php
index 17c47f9..0a0b620 100644
--- a/src/bitExpert/PHPStan/Magento/Rules/AbstractModelUseServiceContractRule.php
+++ b/src/bitExpert/PHPStan/Magento/Rules/AbstractModelUseServiceContractRule.php
@@ -16,7 +16,8 @@
 use PhpParser\Node\Expr\MethodCall;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
+use PHPStan\Rules\RuleError;
+use PHPStan\Rules\RuleErrorBuilder;
 use PHPStan\Type\ObjectType;
 use PHPStan\Type\VerbosityLevel;
 
@@ -36,18 +37,8 @@ public function getNodeType(): string
         return MethodCall::class;
     }
 
-    /**
-     * @param Node $node
-     * @param Scope $scope
-     * @return (string|\PHPStan\Rules\RuleError)[] errors
-     * @throws ShouldNotHappenException
-     */
     public function processNode(Node $node, Scope $scope): array
     {
-        if (!$node instanceof MethodCall) {
-            throw new ShouldNotHappenException();
-        }
-
         if (!$node->name instanceof Node\Identifier) {
             return [];
         }
@@ -63,11 +54,15 @@ public function processNode(Node $node, Scope $scope): array
         }
 
         return [
-            sprintf(
-                'Use service contracts to persist entities in favour of %s::%s() method',
-                $type->describe(VerbosityLevel::typeOnly()),
-                $node->name->name
+            RuleErrorBuilder::message(
+                sprintf(
+                    'Use service contracts to persist entities in favour of %s::%s() method',
+                    $type->describe(VerbosityLevel::typeOnly()),
+                    $node->name->name
+                )
             )
+            ->identifier('bitExpertMagento.abstractModelUseServiceContract')
+            ->build()
         ];
     }
 }
diff --git a/src/bitExpert/PHPStan/Magento/Rules/GetCollectionMockMethodNeedsCollectionSubclassRule.php b/src/bitExpert/PHPStan/Magento/Rules/GetCollectionMockMethodNeedsCollectionSubclassRule.php
index 82563a7..1135929 100644
--- a/src/bitExpert/PHPStan/Magento/Rules/GetCollectionMockMethodNeedsCollectionSubclassRule.php
+++ b/src/bitExpert/PHPStan/Magento/Rules/GetCollectionMockMethodNeedsCollectionSubclassRule.php
@@ -17,7 +17,8 @@
 use PhpParser\Node\Expr\MethodCall;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
+use PHPStan\Rules\RuleError;
+use PHPStan\Rules\RuleErrorBuilder;
 use PHPStan\Type\Constant\ConstantStringType;
 use PHPStan\Type\ErrorType;
 use PHPStan\Type\ObjectType;
@@ -38,18 +39,8 @@ public function getNodeType(): string
         return MethodCall::class;
     }
 
-    /**
-     * @param Node $node
-     * @param Scope $scope
-     * @return (string|\PHPStan\Rules\RuleError)[] errors
-     * @throws ShouldNotHappenException
-     */
     public function processNode(Node $node, Scope $scope): array
     {
-        if (!$node instanceof MethodCall) {
-            throw new ShouldNotHappenException();
-        }
-
         if (!$node->name instanceof Node\Identifier) {
             return [];
         }
@@ -78,11 +69,16 @@ public function processNode(Node $node, Scope $scope): array
         $args = $node->args;
         /** @var ConstantStringType $argType */
         $argType = $scope->getType($args[0]->value);
+
         return [
-            sprintf(
-                '%s does not extend \Magento\Framework\Data\Collection as required!',
-                $argType->getValue()
+            RuleErrorBuilder::message(
+                sprintf(
+                    '%s does not extend \Magento\Framework\Data\Collection as required!',
+                    $argType->getValue()
+                )
             )
+            ->identifier('bitExpertMagento.getCollectionMockMethodNeedsCollectionSubclass')
+            ->build()
         ];
     }
 }
diff --git a/src/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRule.php b/src/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRule.php
index 997db80..4252629 100644
--- a/src/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRule.php
+++ b/src/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRule.php
@@ -16,7 +16,8 @@
 use PhpParser\Node\Expr\MethodCall;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
+use PHPStan\Rules\RuleError;
+use PHPStan\Rules\RuleErrorBuilder;
 use PHPStan\Type\ObjectType;
 use PHPStan\Type\VerbosityLevel;
 
@@ -36,18 +37,8 @@ public function getNodeType(): string
         return MethodCall::class;
     }
 
-    /**
-     * @param Node $node
-     * @param Scope $scope
-     * @return (string|\PHPStan\Rules\RuleError)[] errors
-     * @throws ShouldNotHappenException
-     */
     public function processNode(Node $node, Scope $scope): array
     {
-        if (!$node instanceof MethodCall) {
-            throw new ShouldNotHappenException();
-        }
-
         if (!$node->name instanceof Node\Identifier) {
             return [];
         }
@@ -63,11 +54,15 @@ public function processNode(Node $node, Scope $scope): array
         }
 
         return [
-            sprintf(
-                '%s::%s() is deprecated. Use Resource Models directly',
-                $type->describe(VerbosityLevel::typeOnly()),
-                $node->name->name
+            RuleErrorBuilder::message(
+                sprintf(
+                    '%s::%s() is deprecated. Use Resource Models directly',
+                    $type->describe(VerbosityLevel::typeOnly()),
+                    $node->name->name
+                )
             )
+            ->identifier('bitExpertMagento.resourceModelsShouldBeUsedDirectly')
+            ->build()
         ];
     }
 }
diff --git a/src/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRule.php b/src/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRule.php
index 0e83a64..b986700 100644
--- a/src/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRule.php
+++ b/src/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRule.php
@@ -16,7 +16,8 @@
 use PhpParser\Node\Expr\MethodCall;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
+use PHPStan\Rules\RuleError;
+use PHPStan\Rules\RuleErrorBuilder;
 use PHPStan\Type\ObjectType;
 use PHPStan\Type\VerbosityLevel;
 
@@ -37,18 +38,8 @@ public function getNodeType(): string
         return MethodCall::class;
     }
 
-    /**
-     * @param Node $node
-     * @param Scope $scope
-     * @return (string|\PHPStan\Rules\RuleError)[] errors
-     * @throws ShouldNotHappenException
-     */
     public function processNode(Node $node, Scope $scope): array
     {
-        if (!$node instanceof MethodCall) {
-            throw new ShouldNotHappenException();
-        }
-
         if (!$node->name instanceof Node\Identifier) {
             return [];
         }
@@ -64,11 +55,15 @@ public function processNode(Node $node, Scope $scope): array
         }
 
         return [
-            sprintf(
-                'Setter methods like %s::%s() are deprecated in Block classes, use constructor arguments instead',
-                $type->describe(VerbosityLevel::typeOnly()),
-                $node->name->name
+            RuleErrorBuilder::message(
+                sprintf(
+                    'Setter methods like %s::%s() are deprecated in Block classes, use constructor arguments instead',
+                    $type->describe(VerbosityLevel::typeOnly()),
+                    $node->name->name
+                )
             )
+            ->identifier('bitExpertMagento.setTemplateDisallowedForBlock')
+            ->build()
         ];
     }
 }
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/AbstractModelRetrieveCollectionViaFactoryRuleUnitTest.php b/tests/bitExpert/PHPStan/Magento/Rules/AbstractModelRetrieveCollectionViaFactoryRuleUnitTest.php
index a15d6d4..933cfd3 100644
--- a/tests/bitExpert/PHPStan/Magento/Rules/AbstractModelRetrieveCollectionViaFactoryRuleUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Rules/AbstractModelRetrieveCollectionViaFactoryRuleUnitTest.php
@@ -17,7 +17,6 @@
 use PhpParser\Node\Expr\Variable;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
 use PHPStan\Testing\RuleTestCase;
 
 /**
@@ -54,20 +53,6 @@ public function getNodeTypeMethodReturnsMethodCall(): void
         self::assertSame(MethodCall::class, $rule->getNodeType());
     }
 
-    /**
-     * @test
-     */
-    public function processNodeThrowsExceptionForNonMethodCallNodes(): void
-    {
-        $this->expectException(ShouldNotHappenException::class);
-
-        $node = new Variable('var');
-        $scope = $this->createMock(Scope::class);
-
-        $rule = new AbstractModelRetrieveCollectionViaFactoryRule();
-        $rule->processNode($node, $scope);
-    }
-
     /**
      * @test
      */
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/AbstractModelUseServiceContractRuleUnitTest.php b/tests/bitExpert/PHPStan/Magento/Rules/AbstractModelUseServiceContractRuleUnitTest.php
index 20c802e..83155bb 100644
--- a/tests/bitExpert/PHPStan/Magento/Rules/AbstractModelUseServiceContractRuleUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Rules/AbstractModelUseServiceContractRuleUnitTest.php
@@ -17,7 +17,6 @@
 use PhpParser\Node\Expr\Variable;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
 use PHPStan\Testing\RuleTestCase;
 
 /**
@@ -61,20 +60,6 @@ public function getNodeTypeMethodReturnsMethodCall(): void
         self::assertSame(MethodCall::class, $rule->getNodeType());
     }
 
-    /**
-     * @test
-     */
-    public function processNodeThrowsExceptionForNonMethodCallNodes(): void
-    {
-        $this->expectException(ShouldNotHappenException::class);
-
-        $node = new Variable('var');
-        $scope = $this->createMock(Scope::class);
-
-        $rule = new AbstractModelUseServiceContractRule();
-        $rule->processNode($node, $scope);
-    }
-
     /**
      * @test
      */
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/GetCollectionMockMethodNeedsCollectionSubclassRuleUnitTest.php b/tests/bitExpert/PHPStan/Magento/Rules/GetCollectionMockMethodNeedsCollectionSubclassRuleUnitTest.php
index 7632376..3c0255d 100644
--- a/tests/bitExpert/PHPStan/Magento/Rules/GetCollectionMockMethodNeedsCollectionSubclassRuleUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Rules/GetCollectionMockMethodNeedsCollectionSubclassRuleUnitTest.php
@@ -18,7 +18,6 @@
 use PhpParser\Node\Expr\Variable;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
 use PHPStan\Testing\RuleTestCase;
 
 /**
@@ -63,19 +62,6 @@ public function getNodeTypeMethodReturnsMethodCall(): void
 
         self::assertSame(MethodCall::class, $rule->getNodeType());
     }
-    /**
-     * @test
-     */
-    public function processNodeThrowsExceptionForNonMethodCallNodes(): void
-    {
-        $this->expectException(ShouldNotHappenException::class);
-
-        $node = new Variable('var');
-        $scope = $this->createMock(Scope::class);
-
-        $rule = new GetCollectionMockMethodNeedsCollectionSubclassRule();
-        $rule->processNode($node, $scope);
-    }
 
     /**
      * @test
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRuleUnitTest.php b/tests/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRuleUnitTest.php
index 0441e12..ce67520 100644
--- a/tests/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRuleUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRuleUnitTest.php
@@ -17,7 +17,6 @@
 use PhpParser\Node\Expr\Variable;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
 use PHPStan\Testing\RuleTestCase;
 
 /**
@@ -53,20 +52,6 @@ public function getNodeTypeMethodReturnsMethodCall(): void
         self::assertSame(MethodCall::class, $rule->getNodeType());
     }
 
-    /**
-     * @test
-     */
-    public function processNodeThrowsExceptionForNonMethodCallNodes(): void
-    {
-        $this->expectException(ShouldNotHappenException::class);
-
-        $node = new Variable('var');
-        $scope = $this->createMock(Scope::class);
-
-        $rule = new ResourceModelsShouldBeUsedDirectlyRule();
-        $rule->processNode($node, $scope);
-    }
-
     /**
      * @test
      */
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php b/tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php
index f0a88da..274bf40 100644
--- a/tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php
@@ -18,7 +18,6 @@
 use PhpParser\Node\Expr\Variable;
 use PHPStan\Analyser\Scope;
 use PHPStan\Rules\Rule;
-use PHPStan\ShouldNotHappenException;
 use PHPStan\Testing\RuleTestCase;
 
 /**
@@ -55,20 +54,6 @@ public function getNodeTypeMethodReturnsMethodCall(): void
         self::assertSame(MethodCall::class, $rule->getNodeType());
     }
 
-    /**
-     * @test
-     */
-    public function processNodeThrowsExceptionForNonMethodCallNodes(): void
-    {
-        $this->expectException(ShouldNotHappenException::class);
-
-        $node = new Variable('var');
-        $scope = $this->createMock(Scope::class);
-
-        $rule = new SetTemplateDisallowedForBlockRule();
-        $rule->processNode($node, $scope);
-    }
-
     /**
      * @test
      */

From 8a39cd2392dc5fc72dfbc7ac2ab84c6a9b814b14 Mon Sep 17 00:00:00 2001
From: Pieter Hoste <hoste.pieter@gmail.com>
Date: Tue, 12 Nov 2024 21:47:20 +0100
Subject: [PATCH 23/40] Remove deprecated instanceof Type checks

---
 .../Type/ObjectManagerDynamicReturnTypeExtension.php   | 10 ++++++++--
 ...rameworkObjectManagerDynamicReturnTypeExtension.php | 10 ++++++++--
 2 files changed, 16 insertions(+), 4 deletions(-)

diff --git a/src/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtension.php b/src/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtension.php
index 96d4692..f27e641 100644
--- a/src/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtension.php
+++ b/src/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtension.php
@@ -64,9 +64,15 @@ public function getTypeFromMethodCall(
         /** @var \PhpParser\Node\Arg[] $args */
         $args = $methodCall->args;
         $argType = $scope->getType($args[0]->value);
-        if (!$argType instanceof ConstantStringType) {
+        if ($argType->getConstantStrings() === []) {
             return $mixedType;
         }
-        return TypeCombinator::addNull(new ObjectType($argType->getValue()));
+
+        $types = [];
+        foreach ($argType->getConstantStrings() as $constantString) {
+            $types[] = TypeCombinator::addNull(new ObjectType($constantString->getValue()));
+        }
+
+        return TypeCombinator::union(...$types);
     }
 }
diff --git a/src/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtension.php b/src/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtension.php
index 0a5d083..39eee49 100644
--- a/src/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtension.php
+++ b/src/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtension.php
@@ -92,10 +92,16 @@ private function getTypeForGetObjectMethodCall(
         /** @var \PhpParser\Node\Arg[] $args */
         $args = $methodCall->args;
         $argType = $scope->getType($args[0]->value);
-        if (!$argType instanceof ConstantStringType) {
+        if ($argType->getConstantStrings() === []) {
             return $mixedType;
         }
-        return TypeCombinator::addNull(new ObjectType($argType->getValue()));
+
+        $types = [];
+        foreach ($argType->getConstantStrings() as $constantString) {
+            $types[] = TypeCombinator::addNull(new ObjectType($constantString->getValue()));
+        }
+
+        return TypeCombinator::union(...$types);
     }
 
     /**

From 1db351968df391754ec19d8fa7af49aee228871a Mon Sep 17 00:00:00 2001
From: Pieter Hoste <hoste.pieter@gmail.com>
Date: Wed, 13 Nov 2024 22:10:43 +0100
Subject: [PATCH 24/40] Upgrade phpstan packages to 2.x

---
 composer.json | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/composer.json b/composer.json
index 19d1512..42e6df6 100644
--- a/composer.json
+++ b/composer.json
@@ -23,7 +23,7 @@
     "php": "^7.2.0 || ^8.1.0",
     "ext-dom": "*",
     "laminas/laminas-code": "~3.3.0 || ~3.4.1 || ~3.5.1 || ^4.5 || ^4.10",
-    "phpstan/phpstan": "~1.12.0",
+    "phpstan/phpstan": "^2.0",
     "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"
   },
   "conflict": {
@@ -39,8 +39,8 @@
     "nette/neon": "^3.3.3",
     "nikic/php-parser": "^4.13.2",
     "phpstan/extension-installer": "^1.1.0",
-    "phpstan/phpstan-phpunit": "^1.1.1",
-    "phpstan/phpstan-strict-rules": "^1.2.3",
+    "phpstan/phpstan-phpunit": "^2.0",
+    "phpstan/phpstan-strict-rules": "^2.0",
     "phpunit/phpunit": "^9.5.20",
     "squizlabs/php_codesniffer": "^3.6.2"
   },

From f21b5ce17bf8e9a51b776a0c0c987caca75998d1 Mon Sep 17 00:00:00 2001
From: Pieter Hoste <hoste.pieter@gmail.com>
Date: Wed, 13 Nov 2024 22:11:46 +0100
Subject: [PATCH 25/40] Mark certain tests to be skipped for now

---
 .../DataObjectMagicMethodReflectionExtensionUnitTest.php      | 2 ++
 .../SessionManagerMagicMethodReflectionExtensionUnitTest.php  | 2 ++
 .../Magento/Reflection/MagicMethodReflectionUnitTest.php      | 4 ++++
 3 files changed, 8 insertions(+)

diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php
index 46c270f..3b84827 100644
--- a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php
@@ -35,6 +35,8 @@ class DataObjectMagicMethodReflectionExtensionUnitTest extends TestCase
 
     protected function setUp(): void
     {
+        $this->markTestSkipped('TODO: solve issue with final class ClassReflection');
+
         $this->extension = new DataObjectMagicMethodReflectionExtension();
         $this->classReflection = $this->createMock(ClassReflection::class);
     }
diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php
index e78dcba..ce92176 100644
--- a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php
@@ -33,6 +33,8 @@ class SessionManagerMagicMethodReflectionExtensionUnitTest extends TestCase
 
     protected function setUp(): void
     {
+        $this->markTestSkipped('TODO: solve issue with final class ClassReflection');
+
         $this->extension = new SessionManagerMagicMethodReflectionExtension();
         $this->classReflection = $this->createMock(ClassReflection::class);
     }
diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
index 939c8a7..ca68f52 100644
--- a/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
@@ -22,6 +22,8 @@ class MagicMethodReflectionUnitTest extends TestCase
      */
     public function magicMethodReflectionCreation(): void
     {
+        $this->markTestSkipped('TODO: solve issue with final class ClassReflection');
+
         $classReflection = $this->createMock(ClassReflection::class);
         $methodName = 'myTestMethod';
         $variants = [];
@@ -53,6 +55,8 @@ public function magicMethodReflectionCreationSideeffects(
         string $methodName,
         \PHPStan\TrinaryLogic $expectedResult
     ): void {
+        $this->markTestSkipped('TODO: solve issue with final class ClassReflection');
+
         $classReflection = $this->createMock(ClassReflection::class);
         $variants = [];
 

From 131d11fcad0656802b5892b70b169173638c7138 Mon Sep 17 00:00:00 2001
From: Pieter Hoste <hoste.pieter@gmail.com>
Date: Wed, 13 Nov 2024 22:12:15 +0100
Subject: [PATCH 26/40] Fixes one unit test

---
 ...tFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
index bd579ff..0ba86c0 100644
--- a/tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
@@ -79,6 +79,7 @@ public function returnsErrorTypeForUnkownMethodCall(): void
         $scope = $this->createMock(Scope::class);
         $methodCall = $this->createMock(MethodCall::class);
         $methodCall->args = [];
+        $methodCall->name = new \PhpParser\Node\Identifier('somethingUnknown');
 
         $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
 

From 6c5ded9326a3067bf223431ec6943880511c9fde Mon Sep 17 00:00:00 2001
From: Pieter Hoste <hoste.pieter@gmail.com>
Date: Wed, 13 Nov 2024 22:14:48 +0100
Subject: [PATCH 27/40] Upgrading nikic/php-parser to fix coverage tests

---
 composer.json | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/composer.json b/composer.json
index 42e6df6..853b830 100644
--- a/composer.json
+++ b/composer.json
@@ -37,7 +37,7 @@
     "magento/framework": ">=102.0.0",
     "mikey179/vfsstream": "^1.6.10",
     "nette/neon": "^3.3.3",
-    "nikic/php-parser": "^4.13.2",
+    "nikic/php-parser": "^5.3",
     "phpstan/extension-installer": "^1.1.0",
     "phpstan/phpstan-phpunit": "^2.0",
     "phpstan/phpstan-strict-rules": "^2.0",

From 0aca5217bd3462b405a5aa19eb231870d301f4b7 Mon Sep 17 00:00:00 2001
From: Pieter Hoste <hoste.pieter@gmail.com>
Date: Wed, 13 Nov 2024 22:32:55 +0100
Subject: [PATCH 28/40] Fixes a few phpstan errors up to level 6

---
 .../PHPStan/Magento/Reflection/MagicMethodReflection.php   | 3 ---
 .../Type/ObjectManagerDynamicReturnTypeExtension.php       | 3 ---
 ...estFrameworkObjectManagerDynamicReturnTypeExtension.php | 3 ---
 .../PHPStan/Magento/Autoload/RegistrationUnitTest.php      | 7 +++----
 .../DataObjectMagicMethodReflectionExtensionUnitTest.php   | 2 +-
 ...essionManagerMagicMethodReflectionExtensionUnitTest.php | 2 +-
 .../Magento/Reflection/MagicMethodReflectionUnitTest.php   | 4 ++--
 7 files changed, 7 insertions(+), 17 deletions(-)

diff --git a/src/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflection.php b/src/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflection.php
index aea4596..6df016e 100644
--- a/src/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflection.php
+++ b/src/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflection.php
@@ -76,9 +76,6 @@ public function getPrototype(): ClassMemberReflection
         return $this;
     }
 
-    /**
-     * @return ParametersAcceptor[]
-     */
     public function getVariants(): array
     {
         return $this->variants;
diff --git a/src/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtension.php b/src/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtension.php
index f27e641..84e257f 100644
--- a/src/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtension.php
+++ b/src/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtension.php
@@ -24,9 +24,6 @@
 
 class ObjectManagerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
 {
-    /**
-     * @return string
-     */
     public function getClass(): string
     {
         return 'Magento\Framework\App\ObjectManager';
diff --git a/src/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtension.php b/src/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtension.php
index 39eee49..8827e41 100644
--- a/src/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtension.php
+++ b/src/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtension.php
@@ -28,9 +28,6 @@
 
 class TestFrameworkObjectManagerDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
 {
-    /**
-     * @return string
-     */
     public function getClass(): string
     {
         return 'Magento\Framework\TestFramework\Unit\Helper\ObjectManager';
diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php
index ca86cb4..c14eea9 100644
--- a/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php
@@ -21,20 +21,19 @@ class RegistrationUnitTest extends TestCase
 {
     /**
      * @test
-     * @dataProvider provideAutoloaders()
+     * @dataProvider provideAutoloaders
      */
     public function autoloadersCanRegisterAndUnregister(Autoloader $autoloader): void
     {
-        /** @var array<callable> $initialAutoloadFunctions */
         $initialAutoloadFunctions = spl_autoload_functions();
 
         $autoloader->register();
-        /** @var array<callable> $registerAutoloadFunctions */
+
         $registerAutoloadFunctions = spl_autoload_functions();
         static::assertCount(count($initialAutoloadFunctions) + 1, $registerAutoloadFunctions);
 
         $autoloader->unregister();
-        /** @var array<callable> $unregisterAutoloadFunctions */
+
         $unregisterAutoloadFunctions = spl_autoload_functions();
         static::assertCount(count($initialAutoloadFunctions), $unregisterAutoloadFunctions);
     }
diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php
index 3b84827..eeb4b1a 100644
--- a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php
@@ -35,7 +35,7 @@ class DataObjectMagicMethodReflectionExtensionUnitTest extends TestCase
 
     protected function setUp(): void
     {
-        $this->markTestSkipped('TODO: solve issue with final class ClassReflection');
+        self::markTestSkipped('TODO: solve issue with final class ClassReflection');
 
         $this->extension = new DataObjectMagicMethodReflectionExtension();
         $this->classReflection = $this->createMock(ClassReflection::class);
diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php
index ce92176..dbeefad 100644
--- a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php
@@ -33,7 +33,7 @@ class SessionManagerMagicMethodReflectionExtensionUnitTest extends TestCase
 
     protected function setUp(): void
     {
-        $this->markTestSkipped('TODO: solve issue with final class ClassReflection');
+        self::markTestSkipped('TODO: solve issue with final class ClassReflection');
 
         $this->extension = new SessionManagerMagicMethodReflectionExtension();
         $this->classReflection = $this->createMock(ClassReflection::class);
diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
index ca68f52..e092ea5 100644
--- a/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
@@ -22,7 +22,7 @@ class MagicMethodReflectionUnitTest extends TestCase
      */
     public function magicMethodReflectionCreation(): void
     {
-        $this->markTestSkipped('TODO: solve issue with final class ClassReflection');
+        self::markTestSkipped('TODO: solve issue with final class ClassReflection');
 
         $classReflection = $this->createMock(ClassReflection::class);
         $methodName = 'myTestMethod';
@@ -55,7 +55,7 @@ public function magicMethodReflectionCreationSideeffects(
         string $methodName,
         \PHPStan\TrinaryLogic $expectedResult
     ): void {
-        $this->markTestSkipped('TODO: solve issue with final class ClassReflection');
+        self::markTestSkipped('TODO: solve issue with final class ClassReflection');
 
         $classReflection = $this->createMock(ClassReflection::class);
         $variants = [];

From eb3e763c0b22bbfe55f40e917bd7c3d8ec92e3b2 Mon Sep 17 00:00:00 2001
From: Pieter Hoste <hoste.pieter@gmail.com>
Date: Wed, 13 Nov 2024 22:46:10 +0100
Subject: [PATCH 29/40] Fixes a few phpstan errors up to level 10

---
 .../Magento/Autoload/DataProvider/ClassLoaderProvider.php     | 3 +++
 src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php  | 2 +-
 src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php    | 4 +++-
 .../PHPStan/Magento/Reflection/MagicMethodReflection.php      | 4 ++--
 4 files changed, 9 insertions(+), 4 deletions(-)

diff --git a/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ClassLoaderProvider.php b/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ClassLoaderProvider.php
index 8142429..152464e 100644
--- a/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ClassLoaderProvider.php
+++ b/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ClassLoaderProvider.php
@@ -30,6 +30,7 @@ public function __construct(string $magentoRoot)
         $this->composer = new ClassLoader($magentoRoot . '/vendor');
         $autoloadFile = $magentoRoot . '/vendor/composer/autoload_namespaces.php';
         if (is_file($autoloadFile)) {
+            /** @var array<string, string> $map */
             $map = require $autoloadFile;
             foreach ($map as $namespace => $path) {
                 $this->composer->set($namespace, $path);
@@ -38,6 +39,7 @@ public function __construct(string $magentoRoot)
 
         $autoloadFile = $magentoRoot . '/vendor/composer/autoload_psr4.php';
         if (is_file($autoloadFile)) {
+            /** @var array<string, string> $map */
             $map = require $autoloadFile;
             foreach ($map as $namespace => $path) {
                 $this->composer->setPsr4($namespace, $path);
@@ -46,6 +48,7 @@ public function __construct(string $magentoRoot)
 
         $autoloadFile = $magentoRoot . '/vendor/composer/autoload_classmap.php';
         if (is_file($autoloadFile)) {
+            /** @var ?array<string, string> $classMap */
             $classMap = require $autoloadFile;
             if (is_array($classMap)) {
                 $this->composer->addClassMap($classMap);
diff --git a/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php
index 6eee7e4..c020208 100644
--- a/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php
+++ b/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php
@@ -69,7 +69,7 @@ protected function getFileContents(string $class): string
         $namespace = explode('\\', ltrim($class, '\\'));
         /** @var string $factoryClassname */
         $factoryClassname = array_pop($namespace);
-        $originalClassname = preg_replace('#Factory$#', '', $factoryClassname);
+        $originalClassname = preg_replace('#Factory$#', '', $factoryClassname) ?? $factoryClassname;
         $namespace = implode('\\', $namespace);
 
         $template = "<?php\n";
diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php
index 4cee192..61bf618 100644
--- a/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php
+++ b/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php
@@ -110,7 +110,9 @@ protected function getFileContents(string $class): string
                                 $defaultValue = ' = false';
                                 break;
                             default:
-                                $defaultValue = ' = ' . $parameter->getDefaultValue();
+                                if (is_string($parameter->getDefaultValue())) {
+                                    $defaultValue = ' = ' . $parameter->getDefaultValue();
+                                }
                                 break;
                         }
                     }
diff --git a/src/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflection.php b/src/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflection.php
index 6df016e..7bfee93 100644
--- a/src/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflection.php
+++ b/src/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflection.php
@@ -28,7 +28,7 @@ class MagicMethodReflection implements MethodReflection
      */
     private $declaringClass;
     /**
-     * @var ParametersAcceptor[]
+     * @var list<ParametersAcceptor>
      */
     private $variants;
 
@@ -37,7 +37,7 @@ class MagicMethodReflection implements MethodReflection
      *
      * @param string $name
      * @param ClassReflection $declaringClass
-     * @param ParametersAcceptor[] $variants
+     * @param list<ParametersAcceptor> $variants
      */
     public function __construct(string $name, ClassReflection $declaringClass, array $variants = [])
     {

From 47f4dd57b527e9bbb6334bf23a5bfcb30fb55c09 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 22 Feb 2025 14:10:13 +0100
Subject: [PATCH 30/40] Make skipped tests work again

---
 .../Reflection/Framework/DataObjectHelper.php | 17 ++++++++
 ...MagicMethodReflectionExtensionUnitTest.php | 26 ++++---------
 .../Session/SessionManagerHelper.php          | 21 ++++++++++
 ...MagicMethodReflectionExtensionUnitTest.php | 17 ++++----
 .../MagicMethodReflectionUnitTest.php         | 39 ++++++++++---------
 5 files changed, 72 insertions(+), 48 deletions(-)
 create mode 100644 tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectHelper.php
 create mode 100644 tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerHelper.php

diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectHelper.php b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectHelper.php
new file mode 100644
index 0000000..97e9a20
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectHelper.php
@@ -0,0 +1,17 @@
+<?php
+
+/*
+ * This file is part of the phpstan-magento package.
+ *
+ * (c) bitExpert AG
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace bitExpert\PHPStan\Magento\Reflection\Framework;
+
+class DataObjectHelper extends \Magento\Framework\DataObject
+{
+}
diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php
index eeb4b1a..c3d61f5 100644
--- a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/DataObjectMagicMethodReflectionExtensionUnitTest.php
@@ -13,15 +13,16 @@
 namespace bitExpert\PHPStan\Magento\Reflection\Framework;
 
 use PHPStan\Reflection\ClassReflection;
+use PHPStan\Reflection\ReflectionProvider;
 use PHPStan\ShouldNotHappenException;
+use PHPStan\Testing\PHPStanTestCase;
 use PHPStan\Type\BooleanType;
 use PHPStan\Type\MixedType;
 use PHPStan\Type\ObjectType;
 use PHPStan\Type\StringType;
 use PHPStan\Type\UnionType;
-use PHPUnit\Framework\TestCase;
 
-class DataObjectMagicMethodReflectionExtensionUnitTest extends TestCase
+class DataObjectMagicMethodReflectionExtensionUnitTest extends PHPStanTestCase
 {
     /**
      * @var DataObjectMagicMethodReflectionExtension
@@ -29,16 +30,17 @@ class DataObjectMagicMethodReflectionExtensionUnitTest extends TestCase
     private $extension;
 
     /**
-     * @var ClassReflection|\PHPUnit\Framework\MockObject\MockObject
+     * @var ClassReflection
      */
     private $classReflection;
 
     protected function setUp(): void
     {
-        self::markTestSkipped('TODO: solve issue with final class ClassReflection');
+        /** @var ReflectionProvider $reflectionProvider */
+        $reflectionProvider = $this->getContainer()->getService('reflectionProvider');
+        $this->classReflection = $reflectionProvider->getClass(DataObjectHelper::class);
 
         $this->extension = new DataObjectMagicMethodReflectionExtension();
-        $this->classReflection = $this->createMock(ClassReflection::class);
     }
 
     /**
@@ -172,13 +174,6 @@ public function throwsExceptionForUnknownMethodNames(): void
      */
     public function hasMethodDetectsDataObjectClass(string $method, bool $expectedResult): void
     {
-        $this->classReflection->expects(self::once())
-            ->method('getParentClassesNames')
-            ->willReturn([]);
-        $this->classReflection->expects(self::once())
-            ->method('getName')
-            ->willReturn('Magento\Framework\DataObject');
-
         self::assertSame($expectedResult, $this->extension->hasMethod($this->classReflection, $method));
     }
 
@@ -190,13 +185,6 @@ public function hasMethodDetectsDataObjectClass(string $method, bool $expectedRe
      */
     public function hasMethodDetectsDataObjectParentClass(string $method, bool $expectedResult): void
     {
-        $this->classReflection->expects(self::once())
-            ->method('getParentClassesNames')
-            ->willReturn(['Magento\Framework\DataObject']);
-        $this->classReflection->expects(self::once())
-            ->method('getName')
-            ->willReturn('Magento\Framework\Shell\Response');
-
         self::assertSame($expectedResult, $this->extension->hasMethod($this->classReflection, $method));
     }
 
diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerHelper.php b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerHelper.php
new file mode 100644
index 0000000..80c812b
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerHelper.php
@@ -0,0 +1,21 @@
+<?php
+
+/*
+ * This file is part of the phpstan-magento package.
+ *
+ * (c) bitExpert AG
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace bitExpert\PHPStan\Magento\Reflection\Framework\Session;
+
+/**
+ * Class helper needed by the SessionManagerMagicMethodReflectionExtensionUnitTest
+ * to test for SessionManager subclasses
+ */
+class SessionManagerHelper extends \Magento\Framework\Session\SessionManager
+{
+}
diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php
index dbeefad..80b6599 100644
--- a/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/Framework/Session/SessionManagerMagicMethodReflectionExtensionUnitTest.php
@@ -13,12 +13,13 @@
 namespace bitExpert\PHPStan\Magento\Reflection\Framework\Session;
 
 use PHPStan\Reflection\ClassReflection;
+use PHPStan\Reflection\ReflectionProvider;
+use PHPStan\Testing\PHPStanTestCase;
 use PHPStan\Type\BooleanType;
 use PHPStan\Type\MixedType;
 use PHPStan\Type\StringType;
-use PHPUnit\Framework\TestCase;
 
-class SessionManagerMagicMethodReflectionExtensionUnitTest extends TestCase
+class SessionManagerMagicMethodReflectionExtensionUnitTest extends PHPStanTestCase
 {
 
     /**
@@ -27,16 +28,16 @@ class SessionManagerMagicMethodReflectionExtensionUnitTest extends TestCase
     private $extension;
 
     /**
-     * @var ClassReflection|\PHPUnit\Framework\MockObject\MockObject
+     * @var ClassReflection
      */
     private $classReflection;
 
     protected function setUp(): void
     {
-        self::markTestSkipped('TODO: solve issue with final class ClassReflection');
-
+        /** @var ReflectionProvider $reflectionProvider */
+        $reflectionProvider = $this->getContainer()->getService('reflectionProvider');
+        $this->classReflection = $reflectionProvider->getClass(SessionManagerHelper::class);
         $this->extension = new SessionManagerMagicMethodReflectionExtension();
-        $this->classReflection = $this->createMock(ClassReflection::class);
     }
 
     /**
@@ -80,10 +81,6 @@ public function returnMagicMethodReflectionForGetMethod(): void
      */
     public function hasMethodDetectSessionManager(string $method, bool $expectedResult): void
     {
-        $this->classReflection->expects(self::once())
-            ->method('isSubclassOf')
-            ->willReturn(true);
-
         self::assertSame($expectedResult, $this->extension->hasMethod($this->classReflection, $method));
     }
 
diff --git a/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
index e092ea5..a75c296 100644
--- a/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
@@ -12,19 +12,20 @@
 
 namespace bitExpert\PHPStan\Magento\Reflection;
 
-use PHPStan\Reflection\ClassReflection;
-use PHPUnit\Framework\TestCase;
+use PHPStan\Reflection\ReflectionProvider;
+use PHPStan\Testing\PHPStanTestCase;
+use PHPStan\TrinaryLogic;
 
-class MagicMethodReflectionUnitTest extends TestCase
+class MagicMethodReflectionUnitTest extends PHPStanTestCase
 {
     /**
      * @test
      */
     public function magicMethodReflectionCreation(): void
     {
-        self::markTestSkipped('TODO: solve issue with final class ClassReflection');
-
-        $classReflection = $this->createMock(ClassReflection::class);
+        /** @var ReflectionProvider $reflectionProvider */
+        $reflectionProvider = $this->getContainer()->getService('reflectionProvider');
+        $classReflection = $reflectionProvider->getClass('Magento\Framework\App\RequestInterface');
         $methodName = 'myTestMethod';
         $variants = [];
 
@@ -38,10 +39,10 @@ public function magicMethodReflectionCreation(): void
         self::assertSame($reflection, $reflection->getPrototype());
         self::assertSame($variants, $reflection->getVariants());
         self::assertNull($reflection->getDocComment());
-        self::assertSame(\PHPStan\TrinaryLogic::createNo(), $reflection->isDeprecated());
+        self::assertSame(TrinaryLogic::createNo(), $reflection->isDeprecated());
         self::assertSame('', $reflection->getDeprecatedDescription());
-        self::assertSame(\PHPStan\TrinaryLogic::createNo(), $reflection->isFinal());
-        self::assertSame(\PHPStan\TrinaryLogic::createNo(), $reflection->isInternal());
+        self::assertSame(TrinaryLogic::createNo(), $reflection->isFinal());
+        self::assertSame(TrinaryLogic::createNo(), $reflection->isInternal());
         self::assertNull($reflection->getThrowType());
     }
 
@@ -49,15 +50,15 @@ public function magicMethodReflectionCreation(): void
      * @test
      * @dataProvider sideeffectsDataprovider
      * @param string $methodName
-     * @param \PHPStan\TrinaryLogic $expectedResult
+     * @param TrinaryLogic $expectedResult
      */
     public function magicMethodReflectionCreationSideeffects(
         string $methodName,
-        \PHPStan\TrinaryLogic $expectedResult
+        TrinaryLogic $expectedResult
     ): void {
-        self::markTestSkipped('TODO: solve issue with final class ClassReflection');
-
-        $classReflection = $this->createMock(ClassReflection::class);
+        /** @var ReflectionProvider $reflectionProvider */
+        $reflectionProvider = $this->getContainer()->getService('reflectionProvider');
+        $classReflection = $reflectionProvider->getClass('Magento\Framework\App\RequestInterface');
         $variants = [];
 
         $reflection = new MagicMethodReflection($methodName, $classReflection, $variants);
@@ -70,11 +71,11 @@ public function magicMethodReflectionCreationSideeffects(
     public function sideeffectsDataprovider(): array
     {
         return [
-            ['getTest', \PHPStan\TrinaryLogic::createNo()],
-            ['setTest', \PHPStan\TrinaryLogic::createYes()],
-            ['unsetTest', \PHPStan\TrinaryLogic::createYes()],
-            ['hasText', \PHPStan\TrinaryLogic::createNo()],
-            ['someOtherMethod', \PHPStan\TrinaryLogic::createNo()],
+            ['getTest', TrinaryLogic::createNo()],
+            ['setTest', TrinaryLogic::createYes()],
+            ['unsetTest', TrinaryLogic::createYes()],
+            ['hasText', TrinaryLogic::createNo()],
+            ['someOtherMethod', TrinaryLogic::createNo()],
         ];
     }
 }

From c5a1e99c381251acbbcefa163855de7dfd0a2eec Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 22 Feb 2025 14:22:14 +0100
Subject: [PATCH 31/40] Ignore remaining PHPStan errors

---
 phpstan.neon                                      | 12 ++++++++++++
 ...tManagerDynamicReturnTypeExtensionUnitTest.php | 15 ++++++++-------
 2 files changed, 20 insertions(+), 7 deletions(-)

diff --git a/phpstan.neon b/phpstan.neon
index 931cc8e..c94cc2b 100644
--- a/phpstan.neon
+++ b/phpstan.neon
@@ -28,6 +28,15 @@ parameters:
         -
             message: '~is not covered by backward compatibility promise.~'
             path: src/bitExpert/PHPStan/Magento/Reflection/AbstractMagicMethodReflectionExtension.php
+        -
+            message: '~PHPDoc tag @var assumes the expression with type~'
+            path: 'src/bitExpert/PHPStan/Magento/Rules/GetCollectionMockMethodNeedsCollectionSubclassRule.php'
+        -
+            message: '~PHPDoc tag @var assumes the expression with type~'
+            path: 'src/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtension.php'
+        -
+            message: '~Function is_subclass_of\(\) is a runtime reflection concept that might not work in PHPStan~'
+            path: 'src/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtension.php'
         -
             message: '~is not covered by backward compatibility promise.~'
             path: tests/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloaderUnitTest.php
@@ -58,3 +67,6 @@ parameters:
         -
             message: '~Call to static method PHPUnit\\Framework\\Assert::assertInstanceOf~'
             path: tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
+        -
+            message: '~PHPDoc tag @var assumes the expression with type~'
+            path: tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
diff --git a/tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
index 0ba86c0..0b2d001 100644
--- a/tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
@@ -15,6 +15,7 @@
 use Magento\Framework\View\Design\Theme\ThemeList;
 use PhpParser\Node\Arg;
 use PhpParser\Node\Expr\MethodCall;
+use PhpParser\Node\Identifier;
 use PhpParser\Node\Scalar\LNumber;
 use PhpParser\Node\Scalar\String_;
 use PHPStan\Analyser\Scope;
@@ -79,7 +80,7 @@ public function returnsErrorTypeForUnkownMethodCall(): void
         $scope = $this->createMock(Scope::class);
         $methodCall = $this->createMock(MethodCall::class);
         $methodCall->args = [];
-        $methodCall->name = new \PhpParser\Node\Identifier('somethingUnknown');
+        $methodCall->name = new Identifier('somethingUnknown');
 
         $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
 
@@ -95,7 +96,7 @@ public function returnsMixedTypeForGetObjectCallWithoutParameter(): void
         $scope = $this->createMock(Scope::class);
         $methodCall = $this->createMock(MethodCall::class);
         $methodCall->args = [];
-        $methodCall->name = new \PhpParser\Node\Identifier('getObject');
+        $methodCall->name = new Identifier('getObject');
 
         $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
 
@@ -111,7 +112,7 @@ public function returnsMixedTypeForGetObjectCallWithNonStringParameter(): void
         $scope = $this->createMock(Scope::class);
         $methodCall = $this->createMock(MethodCall::class);
         $methodCall->args = [new Arg(new LNumber(1))];
-        $methodCall->name = new \PhpParser\Node\Identifier('getObject');
+        $methodCall->name = new Identifier('getObject');
 
         $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
 
@@ -131,7 +132,7 @@ public function returnsUnionTypeForGetObjectCallWithStringParameter(): void
 
         $methodCall = $this->createMock(MethodCall::class);
         $methodCall->args = [new Arg(new String_('someArg'))];
-        $methodCall->name = new \PhpParser\Node\Identifier('getObject');
+        $methodCall->name = new Identifier('getObject');
 
         $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
 
@@ -147,7 +148,7 @@ public function returnsUnionTypeForGetConstructArgumentsCall(): void
         $scope = $this->createMock(Scope::class);
         $methodCall = $this->createMock(MethodCall::class);
         $methodCall->args = [];
-        $methodCall->name = new \PhpParser\Node\Identifier('getConstructArguments');
+        $methodCall->name = new Identifier('getConstructArguments');
 
         /** @var UnionType $resultType */
         $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
@@ -173,7 +174,7 @@ public function returnsErrorTypeForGetCollectionMockMethod(): void
 
         $methodCall = $this->createMock(MethodCall::class);
         $methodCall->args = [new Arg(new String_(self::class))];
-        $methodCall->name = new \PhpParser\Node\Identifier('getCollectionMock');
+        $methodCall->name = new Identifier('getCollectionMock');
 
         $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
 
@@ -194,7 +195,7 @@ public function returnsUnionTypeForGetCollectionMockMethod(): void
 
         $methodCall = $this->createMock(MethodCall::class);
         $methodCall->args = [new Arg(new String_(ThemeList::class))];
-        $methodCall->name = new \PhpParser\Node\Identifier('getCollectionMock');
+        $methodCall->name = new Identifier('getCollectionMock');
 
         /** @var IntersectionType $resultType */
         $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);

From 8c6aad55d40f8fed08dc01be829ec515e7a047e4 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 22 Feb 2025 14:42:49 +0100
Subject: [PATCH 32/40] Update Readme.md file

---
 README.md | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/README.md b/README.md
index e7ec08a..990cf4e 100644
--- a/README.md
+++ b/README.md
@@ -14,10 +14,10 @@ PHP: PHP 7.2 or higher
 
 Magento: Magento 2.3.0 or higher
 
-PHPStan: PHPStan 1.12
+PHPStan: PHPStan 2.0 or higher
 
 If you are using a Magento version that requires an older version of PHPStan (e.g. 0.12.77),  you need to manually upgrade it before 
-installing this extension. in your composer.json Change the PHPStan version to `~1.12` and run:
+installing this extension. in your composer.json Change the PHPStan version to `~2.0` and run:
 
 ```
 composer update phpstan/phpstan --with-all-dependencies

From bea603fbb64bbe37123833b481a7f3468d4d179e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 22 Feb 2025 14:48:59 +0100
Subject: [PATCH 33/40] Add string cast to make PHPStan happy

---
 src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php
index c020208..51630c2 100644
--- a/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php
+++ b/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php
@@ -69,7 +69,7 @@ protected function getFileContents(string $class): string
         $namespace = explode('\\', ltrim($class, '\\'));
         /** @var string $factoryClassname */
         $factoryClassname = array_pop($namespace);
-        $originalClassname = preg_replace('#Factory$#', '', $factoryClassname) ?? $factoryClassname;
+        $originalClassname = (string) preg_replace('#Factory$#', '', $factoryClassname);
         $namespace = implode('\\', $namespace);
 
         $template = "<?php\n";

From f62ee3c5e9077279d23592bf5f5a31c05633a6b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Wed, 26 Feb 2025 18:57:52 +0100
Subject: [PATCH 34/40] Update Changelog.md file

---
 CHANGELOG.md | 19 +++++++++++++++++++
 1 file changed, 19 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9da463a..c71cf3e 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,25 @@
 
 All notable changes to this project will be documented in this file, in reverse chronological order by release.
 
+## 0.40
+
+### Added
+
+- [#334](https://github.com/bitExpert/phpstan-magento/pull/334) PHPStan 2.0 compatibility
+- [#332](https://github.com/bitExpert/phpstan-magento/pull/332) Add support for symfony/finder 7+
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
 ## 0.32
 
 ### Added

From 07b46b30f4a8e03b259fcb6563e3d75deb219eca Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Wed, 26 Feb 2025 19:43:48 +0100
Subject: [PATCH 35/40] Fix CI pipeline issue

---
 .github/workflows/ci.yml | 48 +++++-----------------------------------
 1 file changed, 5 insertions(+), 43 deletions(-)

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 07d5709..49fea76 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,16 +11,10 @@ jobs:
     strategy:
       matrix:
         operating-system: ['ubuntu-latest']
-        php-versions: ['7.4']
-        magento: ['2.3.7-p2', '2.4.0', '2.4.1', '2.4.2', '2.4.2-p1', '2.4.2-p2', '2.4.3', '2.4.3-p1']
+        php-versions: ['8.1']
+        magento: ['2.4.4', '2.4.5']
         coveralls: [ false ]
         include:
-          - php-versions: '8.1'
-            magento: '2.4.4'
-            operating-system: 'ubuntu-latest'
-          - php-versions: '8.1'
-            magento: '2.4.5'
-            operating-system: 'ubuntu-latest'
           - php-versions: '8.1'
             magento: '2.4.6'
             operating-system: 'ubuntu-latest'
@@ -40,49 +34,17 @@ jobs:
       - name: Install Composer dependencies
         run: composer install
 
-      - name: Install Magento 2.3.7-p2
-        if: matrix.magento == '2.3.7-p2'
-        run: composer update --with-dependencies magento/framework:102.0.7-p2 laminas/laminas-code:3.4.1
-
-      - name: Install Magento 2.4.0
-        if: matrix.magento == '2.4.0'
-        run: composer update --with-dependencies magento/framework:103.0.0 laminas/laminas-code:3.4.1
-
-      - name: Install Magento 2.4.1
-        if: matrix.magento == '2.4.1'
-        run: composer update --with-dependencies magento/framework:103.0.1 laminas/laminas-code:3.4.1
-
-      - name: Install Magento 2.4.2
-        if: matrix.magento == '2.4.2'
-        run: composer update --with-dependencies magento/framework:103.0.2 laminas/laminas-code:3.4.1
-
-      - name: Install Magento 2.4.2-p1
-        if: matrix.magento == '2.4.2-p1'
-        run: composer update --with-dependencies magento/framework:103.0.2-p1 laminas/laminas-code:3.4.1
-
-      - name: Install Magento 2.4.2-p2
-        if: matrix.magento == '2.4.2-p2'
-        run: composer update --with-dependencies magento/framework:103.0.2-p1 laminas/laminas-code:3.4.1
-
-      - name: Install Magento 2.4.3
-        if: matrix.magento == '2.4.3'
-        run: composer update --with-dependencies magento/framework:103.0.3 laminas/laminas-code:3.5.1
-
-      - name: Install Magento 2.4.3-p1
-        if: matrix.magento == '2.4.3-p1'
-        run: composer update --with-dependencies magento/framework:103.0.3-p1 laminas/laminas-code:3.5.1
-
       - name: Install Magento 2.4.4
         if: matrix.magento == '2.4.4'
-        run: composer update --with-dependencies magento/framework:103.0.4 laminas/laminas-code:4.5.1 symfony/yaml symfony/console
+        run: composer update --with-dependencies magento/framework:103.0.4 laminas/laminas-code:4.5.1 symfony/yaml symfony/console madewithlove/license-checker
 
       - name: Install Magento 2.4.5
         if: matrix.magento == '2.4.5'
-        run: composer update --with-dependencies magento/framework:103.0.5 laminas/laminas-code:4.5.2 roave/security-advisories symfony/yaml symfony/console
+        run: composer update --with-dependencies magento/framework:103.0.5 laminas/laminas-code:4.5.2 roave/security-advisories symfony/yaml symfony/console madewithlove/license-checker
 
       - name: Install Magento 2.4.6
         if: matrix.magento == '2.4.6'
-        run: composer update --with-dependencies magento/framework:103.0.6 laminas/laminas-code:4.10.0 roave/security-advisories symfony/yaml symfony/console
+        run: composer update --with-dependencies magento/framework:103.0.6 laminas/laminas-code:4.10.0 roave/security-advisories symfony/yaml symfony/console madewithlove/license-checker
 
       - name: Composer license check
         run: composer check-license

From 1ff0a619d64309f2c28c5de94a962a0c755a8e27 Mon Sep 17 00:00:00 2001
From: Piotr Markiewicz <piotr.markiewicz@vaimo.com>
Date: Tue, 4 Mar 2025 14:56:25 +0100
Subject: [PATCH 36/40] Add extension for return type in Result Factory

---
 extension.neon                                |  4 +
 ...rollerResultFactoryReturnTypeExtension.php | 66 +++++++++++++
 ...sultFactoryReturnTypeExtensionUnitTest.php | 98 +++++++++++++++++++
 3 files changed, 168 insertions(+)
 create mode 100644 src/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtension.php
 create mode 100644 tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php

diff --git a/extension.neon b/extension.neon
index a43e09a..aa90e9d 100644
--- a/extension.neon
+++ b/extension.neon
@@ -37,6 +37,10 @@ services:
 		class: bitExpert\PHPStan\Magento\Type\TestFrameworkObjectManagerDynamicReturnTypeExtension
 		tags:
 			- phpstan.broker.dynamicMethodReturnTypeExtension
+	-
+	    class: bitExpert\PHPStan\Magento\Type\ControllerResultFactoryReturnTypeExtension
+	    tags:
+	        - phpstan.broker.dynamicMethodReturnTypeExtension
 	-
 		class: bitExpert\PHPStan\Magento\Reflection\Framework\Session\SessionManagerMagicMethodReflectionExtension
 		tags:
diff --git a/src/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtension.php b/src/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtension.php
new file mode 100644
index 0000000..6047375
--- /dev/null
+++ b/src/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtension.php
@@ -0,0 +1,66 @@
+<?php
+
+/*
+ * This file is part of the phpstan-magento package.
+ *
+ * (c) bitExpert AG
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace bitExpert\PHPStan\Magento\Type;
+
+use PhpParser\Node\Expr\ClassConstFetch;
+use PhpParser\Node\Expr\MethodCall;
+use PhpParser\Node\Identifier;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Type\DynamicMethodReturnTypeExtension;
+use PHPStan\Type\ObjectType;
+use PHPStan\Type\Type;
+
+/**
+ * \Magento\Framework\Controller\ResultFactory returns result type based on first parameter
+ */
+class ControllerResultFactoryReturnTypeExtension implements DynamicMethodReturnTypeExtension
+{
+    /** @see \Magento\Framework\Controller\ResultFactory */
+    private const TYPE_MAP = [
+        'TYPE_JSON' => \Magento\Framework\Controller\Result\Json::class,
+        'TYPE_RAW' => \Magento\Framework\Controller\Result\Raw::class,
+        'TYPE_REDIRECT' => \Magento\Framework\Controller\Result\Redirect::class,
+        'TYPE_FORWARD' => \Magento\Framework\Controller\Result\Forward::class,
+        'TYPE_LAYOUT' => \Magento\Framework\View\Result\Layout::class,
+        'TYPE_PAGE' => \Magento\Framework\View\Result\Page::class,
+    ];
+
+    public function getClass(): string
+    {
+        return \Magento\Framework\Controller\ResultFactory::class;
+    }
+
+    public function isMethodSupported(MethodReflection $methodReflection): bool
+    {
+        return $methodReflection->getName() === 'create';
+    }
+
+    public function getTypeFromMethodCall(
+        MethodReflection $methodReflection,
+        MethodCall $methodCall,
+        Scope $scope
+    ): ?ObjectType {
+        $class = null;
+        if (\count($methodCall->getArgs()) > 0) {
+            $arg = $methodCall->getArgs()[0];
+            $expr = $arg->value;
+
+            if ($expr instanceof ClassConstFetch && $expr->name instanceof Identifier) {
+                $class = self::TYPE_MAP[$expr->name->toString()] ?? null;
+            }
+        }
+
+        return $class !== null ? new ObjectType($class) : null;
+    }
+}
diff --git a/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php
new file mode 100644
index 0000000..2ba08c5
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php
@@ -0,0 +1,98 @@
+<?php
+
+/*
+ * This file is part of the phpstan-magento package.
+ *
+ * (c) bitExpert AG
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+declare(strict_types=1);
+
+namespace bitExpert\PHPStan\Magento\Type;
+
+use PhpParser\Node\Arg;
+use PhpParser\Node\Expr\ClassConstFetch;
+use PhpParser\Node\Expr\MethodCall;
+use PhpParser\Node\Identifier;
+use PHPStan\Analyser\Scope;
+use PHPStan\Reflection\MethodReflection;
+use PHPStan\Testing\PHPStanTestCase;
+use PHPStan\Type\ObjectType;
+
+class ControllerResultFactoryReturnTypeExtensionUnitTest extends PHPStanTestCase
+{
+    /**
+     * @var ControllerResultFactoryReturnTypeExtension
+     */
+    private $extension;
+
+    protected function setUp(): void
+    {
+        $this->extension = new ControllerResultFactoryReturnTypeExtension();
+    }
+
+    /**
+     * @return mixed[]
+     */
+    public function returnTypeDataProvider(): array
+    {
+        return [
+            ['TYPE_JSON', 'Magento\Framework\Controller\Result\Json'],
+            ['TYPE_PAGE', 'Magento\Framework\View\Result\Page'],
+        ];
+    }
+
+    /**
+     * @return mixed[]
+     */
+    public function isMethodSupportedDataProvider(): array
+    {
+        return [
+            ['create', true],
+            ['get', false]
+        ];
+    }
+
+    /**
+     * @test
+     * @dataProvider isMethodSupportedDataProvider
+     * @param string $method
+     * @param bool $expectedResult
+     */
+    public function checkIfMethodIsSupported(string $method, bool $expectedResult): void
+    {
+        $methodReflection = $this->createMock(MethodReflection::class);
+        $methodReflection->method('getName')->willReturn($method);
+
+        self::assertSame($expectedResult, $this->extension->isMethodSupported($methodReflection));
+    }
+
+    /**
+     * @test
+     * @dataProvider returnTypeDataProvider
+     * @param string $param
+     * @param string $expectedResult
+     */
+    public function returnValidResultType(string $param, string $expectedResult): void
+    {
+        $methodReflection = $this->createMock(MethodReflection::class);
+        $scope = $this->createMock(Scope::class);
+
+        $identifier = $this->createConfiguredMock(Identifier::class, ['toString' => $param]);
+
+        $expr = $this->createMock(ClassConstFetch::class);
+        $expr->name = $identifier;
+
+        $arg = $this->createMock(Arg::class);
+        $arg->value = $expr;
+
+        $methodCall = $this->createConfiguredMock(MethodCall::class, ['getArgs' => [$arg]]);
+
+        $resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
+
+        self::assertNotNull($resultType);
+        self::assertSame($expectedResult, $resultType->getClassName());
+    }
+}

From 37e204c1f35571a74076ea2fca7b19fe261a7ba2 Mon Sep 17 00:00:00 2001
From: Piotr Markiewicz <piotr.markiewicz@vaimo.com>
Date: Wed, 5 Mar 2025 12:36:28 +0100
Subject: [PATCH 37/40] Add explaination to docs

---
 docs/features.md                                               | 3 +++
 .../ControllerResultFactoryReturnTypeExtensionUnitTest.php     | 2 +-
 2 files changed, 4 insertions(+), 1 deletion(-)

diff --git a/docs/features.md b/docs/features.md
index 32b5d15..735ccb2 100644
--- a/docs/features.md
+++ b/docs/features.md
@@ -24,6 +24,9 @@ Additionally, a PHPStan rule checks that only `Magento\Framework\Data\Collection
 ### ObjectManager type hints
 A type extension is provided so that `Magento\Framework\App\ObjectManager` method calls do return the correct return type.
 
+### ResultFactory type hints
+Correct type is returned from `Magento\Framework\Controller\ResultFactory` based on passed parameter.
+
 ## Magic method calls
 For some classes like `Magento\Framework\DataObject` or `Magento\Framework\Session\SessionManager` PHPStan logic is provided
 to be able to let the magic method calls return proper types.
diff --git a/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php
index 2ba08c5..8587d98 100644
--- a/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php
@@ -40,7 +40,7 @@ public function returnTypeDataProvider(): array
     {
         return [
             ['TYPE_JSON', 'Magento\Framework\Controller\Result\Json'],
-            ['TYPE_PAGE', 'Magento\Framework\View\Result\Page'],
+            ['TYPE_PAGE', 'Magento\Framework\View\Result\Page']
         ];
     }
 

From 942283892cd6339578f55778cbd48a8858cd9172 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Thu, 6 Mar 2025 09:02:29 +0100
Subject: [PATCH 38/40] Update Changelog.md file

---
 CHANGELOG.md | 24 +++++++++++++++++++++---
 1 file changed, 21 insertions(+), 3 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index c71cf3e..2f85efd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,7 +2,25 @@
 
 All notable changes to this project will be documented in this file, in reverse chronological order by release.
 
-## 0.40
+## 0.41.0
+
+### Added
+
+- [#338](https://github.com/bitExpert/phpstan-magento/pull/338) Feature/type for controller result factory
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
+## 0.40.0
 
 ### Added
 
@@ -21,7 +39,7 @@ All notable changes to this project will be documented in this file, in reverse
 
 - Nothing.
 
-## 0.32
+## 0.32.0
 
 ### Added
 
@@ -39,7 +57,7 @@ All notable changes to this project will be documented in this file, in reverse
 
 - Nothing.
 
-## 0.31
+## 0.31.0
 
 ### Added
 

From 21f87d541c29b6cd774e69a599d1496271aafaa5 Mon Sep 17 00:00:00 2001
From: Stijn Bernards <stijn@bernards.nl>
Date: Fri, 9 May 2025 12:53:40 +0200
Subject: [PATCH 39/40] fix: error where scopeConfig proxy would get $scopeType
 = default

---
 src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php
index 61bf618..c4f0131 100644
--- a/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php
+++ b/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php
@@ -111,7 +111,7 @@ protected function getFileContents(string $class): string
                                 break;
                             default:
                                 if (is_string($parameter->getDefaultValue())) {
-                                    $defaultValue = ' = ' . $parameter->getDefaultValue();
+                                    $defaultValue = ' = \'' . $parameter->getDefaultValue() . '\'';
                                 }
                                 break;
                         }

From 30fe91cd383a81b222101e682f5192a6103f40a7 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= <S.Hochdoerfer@bitExpert.de>
Date: Sat, 17 May 2025 08:15:12 +0200
Subject: [PATCH 40/40] Update CHANGELOG.md file

---
 CHANGELOG.md | 18 ++++++++++++++++++
 1 file changed, 18 insertions(+)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2f85efd..f2c3858 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,24 @@
 
 All notable changes to this project will be documented in this file, in reverse chronological order by release.
 
+## 0.42.0
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- [#340](https://github.com/bitExpert/phpstan-magento/pull/340) fix: error where scopeConfig proxy would get $scopeType = default
+
 ## 0.41.0
 
 ### Added