diff --git a/.allowed-licenses b/.allowed-licenses
index 87c87c4..b33d653 100644
--- a/.allowed-licenses
+++ b/.allowed-licenses
@@ -3,3 +3,4 @@
- BSD-2-Clause
- MIT
- OSL-3.0
+- LGPL-2.1-or-later
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 64e0eeb..49fea76 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -11,13 +11,14 @@ 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'
+ magento: '2.4.6'
operating-system: 'ubuntu-latest'
+ coveralls: true
steps:
- name: Checkout repo
uses: actions/checkout@v2
@@ -33,41 +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 roave/security-advisories
+ 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 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 madewithlove/license-checker
- name: Composer license check
run: composer check-license
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1a52fba..f2c3858 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,306 @@
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
+
+- [#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
+
+- [#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.0
+
+### 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.0
+
+### 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
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- [#302](https://github.com/bitExpert/phpstan-magento/pull/302) Fix problem with parametersSchema
+
+## 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
+
+- [#292](https://github.com/bitExpert/phpstan-magento/pull/292) Update phpstan/phpstan requirement to ~1.10.3
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
+## 0.28.0
+
+### Added
+
+- [#283](https://github.com/bitExpert/phpstan-magento/pull/283) Adds PHP 8.2 / Magento 2.4.6 support
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- [#284](https://github.com/bitExpert/phpstan-magento/pull/284) Ignore ext autoloader for local classes
+
+## 0.27.0
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- [#279](https://github.com/bitExpert/phpstan-magento/pull/279) Update phpstan/phpstan requirement to ~1.9.2
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
+## 0.26.0
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- [#276](https://github.com/bitExpert/phpstan-magento/pull/276) Check existing extension interface for types
+
+## 0.25.0
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- [#275](https://github.com/bitExpert/phpstan-magento/pull/275) Restructure the docs
+- [#274](https://github.com/bitExpert/phpstan-magento/pull/274) Don't use ?array type hint in ext attributes
+- [#273](https://github.com/bitExpert/phpstan-magento/pull/273) Make code compatible with PHP 7.2
+
+## 0.24.0
+
+### Added
+
+- [#269](https://github.com/bitExpert/phpstan-magento/pull/269) Add Magento 2.4.5 to CI pipeline
+- [#267](https://github.com/bitExpert/phpstan-magento/pull/267) Use Magento root for TestFrameworkAutoloader
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- [#268](https://github.com/bitExpert/phpstan-magento/pull/268) Autoloaders prefer local classes
+- [#261](https://github.com/bitExpert/phpstan-magento/pull/261) Update phpstan/phpstan requirement from ~1.7.2 to ~1.8.2
+
+## 0.23.1
+
+### Added
+
+- Nothing.
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- [#266](https://github.com/bitExpert/phpstan-magento/pull/266) Allow arrays in extension attributes
+- [#264](https://github.com/bitExpert/phpstan-magento/pull/264) Fix factory generation for "FactoryThing" classes
+
+## 0.23.0
+
+### Added
+
+- [#243](https://github.com/bitExpert/phpstan-magento/pull/243) Upgrade to PHPStan 1.7
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
+## 0.22.0
+
+### Added
+
+- [#246](https://github.com/bitExpert/phpstan-magento/pull/246) Do not yet require PHPStan 1.7
+- [#242](https://github.com/bitExpert/phpstan-magento/pull/242) Add link to related blog post
+- [#239](https://github.com/bitExpert/phpstan-magento/pull/239) Update madewithlove/license-checker requirement from to ^1.2
+
+### Deprecated
+
+- Nothing.
+
+### Removed
+
+- Nothing.
+
+### Fixed
+
+- Nothing.
+
## 0.21.0
### Added
diff --git a/README.md b/README.md
index 0243268..990cf4e 100644
--- a/README.md
+++ b/README.md
@@ -6,6 +6,7 @@ You can use this PHPStan extension for both Magento module projects and Magento
[](https://github.com/bitExpert/phpstan-magento/actions)
[](https://coveralls.io/github/bitExpert/phpstan-magento?branch=master)
[](https://packagist.org/packages/bitExpert/phpstan-magento/)
+[](https://rheinneckar.social/@bitexpert)
## Requirements
@@ -13,36 +14,26 @@ PHP: PHP 7.2 or higher
Magento: Magento 2.3.0 or higher
-PHPStan: PHPStan 1.6
+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.6` 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
```
-## Installation
-
-The preferred way of installing `bitexpert/phpstan-magento` is through Composer.
-You can add `bitexpert/phpstan-magento` as a dev dependency, as follows:
-
-```
-composer.phar require --dev bitexpert/phpstan-magento
-```
-
-### PHPStan configuration
-
-If you have not already a PHPStan configuration file `phpstan.neon` in your project, create a new empty file next to your `composer.json` file.
-
-This PHPStan extension needs to be registered with PHPStan so that PHPStan can load it properly.
-The easiest way to do this is to install the `phpstan/extension-installer` package as follows:
+This PHPStan extension needs to be registered with PHPStan so that the extension gets loaded properly. The easiest way to do this is
+to install the `phpstan/extension-installer` package as follows:
```
composer.phar require --dev phpstan/extension-installer
```
-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:
+
+ Composer Allow-Plugins configuration
+
+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:
```
- Installing phpstan/extension-installer (1.1.0): Extracting archive
@@ -50,25 +41,24 @@ phpstan/extension-installer contains a Composer plugin which is currently not in
Do you trust "phpstan/extension-installer" to execute code and wish to enable it now? (writes "allow-plugins" to composer.json) [y,n,d,?] y
```
-
- Manual installation
+
-If you don't want to use `phpstan/extension-installer`, include extension.neon in your project's PHPStan config:
+## Installation
+
+The preferred way of installing `bitexpert/phpstan-magento` is through Composer.
+You can add `bitexpert/phpstan-magento` as a dev dependency, as follows:
-```neon
-includes:
- - vendor/bitexpert/phpstan-magento/extension.neon
```
-
+composer.phar require --dev bitexpert/phpstan-magento
+```
-If you are using phpstan-magento 0.15.0 and earlier, you need to register the custom autoloader that comes with this extension by adding `vendor/bitexpert/phpstan-magento/autoload.php`
-as a bootstrap file. If you are using phpstan-magento > 0.15.0 this step is done automatically by the extension itself.
+> Want a full walk-through of the installation & configuration process? Read the blog post at M.academy about [Static Analysis in Magento with PHPStan](https://m.academy/blog/static-analysis-magento-phpstan/).
-```neon
-parameters:
- bootstrapFiles:
- - vendor/bitexpert/phpstan-magento/autoload.php
-```
+### PHPStan configuration
+
+If you have not already a PHPStan configuration file `phpstan.neon` in your project, create a new empty file next to your `composer.json` file.
+
+See [here](https://phpstan.org/config-reference) what options PHPStan allows you to configure.
## Feature overview
@@ -84,7 +74,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).
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";
diff --git a/composer.json b/composer.json
index 3d2b6a7..853b830 100644
--- a/composer.json
+++ b/composer.json
@@ -7,7 +7,8 @@
"sort-packages": true,
"allow-plugins": {
"phpstan/extension-installer": true,
- "captainhook/plugin-composer": true
+ "captainhook/plugin-composer": true,
+ "magento/composer-dependency-version-audit-plugin": false
}
},
"license": "MIT",
@@ -21,26 +22,26 @@
"require": {
"php": "^7.2.0 || ^8.1.0",
"ext-dom": "*",
- "laminas/laminas-code": "~3.3.0 || ~3.4.1 || ~3.5.1 || ~4.5.0",
- "phpstan/phpstan": "^1.6.2",
- "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0"
+ "laminas/laminas-code": "~3.3.0 || ~3.4.1 || ~3.5.1 || ^4.5 || ^4.10",
+ "phpstan/phpstan": "^2.0",
+ "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0 || ^7.0"
},
"conflict": {
"magento/framework": "<102.0.0"
},
"require-dev": {
- "captainhook/captainhook": "^5.10.8",
+ "captainhook/captainhook": "^5.10.9",
"captainhook/plugin-composer": "^5.3.3",
- "league/commonmark": "^2.2.3",
- "madewithlove/license-checker": "^0.10.0 || ^1.2",
+ "league/commonmark": "^2.3.1",
+ "madewithlove/license-checker": "^0.10.0 || ^1.4",
"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": "^1.1.1",
- "phpstan/phpstan-strict-rules": "^1.2.0",
- "phpunit/phpunit": "^9.5.19",
+ "phpstan/phpstan-phpunit": "^2.0",
+ "phpstan/phpstan-strict-rules": "^2.0",
+ "phpunit/phpunit": "^9.5.20",
"squizlabs/php_codesniffer": "^3.6.2"
},
"autoload": {
diff --git a/docs/features.md b/docs/features.md
index 2da9045..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.
@@ -61,6 +64,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
@@ -72,3 +86,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 7a9eef4..aa90e9d 100644
--- a/extension.neon
+++ b/extension.neon
@@ -2,22 +2,32 @@ parameters:
magento:
checkCollectionViaFactory: true
checkServiceContracts: true
+ checkResourceModelsUsedDirectly: true
magentoRoot: %currentWorkingDirectory%
+ extdn:
+ setTemplateDisallowedForBlockClasses: true
bootstrapFiles:
- magento-autoloader.php
parametersSchema:
magento: structure([
checkCollectionViaFactory: bool()
checkServiceContracts: bool()
+ checkResourceModelsUsedDirectly: bool()
magentoRoot: string()
])
+ extdn: structure([
+ setTemplateDisallowedForBlockClasses: bool()
+ ])
conditionalTags:
bitExpert\PHPStan\Magento\Rules\AbstractModelRetrieveCollectionViaFactoryRule:
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%
+ bitExpert\PHPStan\Magento\Rules\SetTemplateDisallowedForBlockRule:
+ phpstan.rules.rule: %extdn.setTemplateDisallowedForBlockClasses%
services:
-
class: bitExpert\PHPStan\Magento\Type\ObjectManagerDynamicReturnTypeExtension
@@ -27,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:
@@ -43,6 +57,10 @@ services:
class: bitExpert\PHPStan\Magento\Rules\AbstractModelRetrieveCollectionViaFactoryRule
-
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:
@@ -52,46 +70,51 @@ services:
class: PHPStan\Cache\Cache
arguments:
storage: @fileCacheStorage
+ extensionAttributeDataProvider:
+ class: bitExpert\PHPStan\Magento\Autoload\DataProvider\ExtensionAttributeDataProvider
+ arguments:
+ magentoRoot: %magento.magentoRoot%
+ classLoaderProvider:
+ class: bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider
+ arguments:
+ magentoRoot: %magento.magentoRoot%
mockAutoloader:
class: bitExpert\PHPStan\Magento\Autoload\MockAutoloader
tags:
- phpstan.magento.autoloader
testFrameworkAutoloader:
class: bitExpert\PHPStan\Magento\Autoload\TestFrameworkAutoloader
+ arguments:
+ magentoRoot: %magento.magentoRoot%
tags:
- phpstan.magento.autoloader
factoryAutoloader:
class: bitExpert\PHPStan\Magento\Autoload\FactoryAutoloader
arguments:
cache: @autoloaderCache
+ classLoaderProvider: @classLoaderProvider
tags:
- phpstan.magento.autoloader
proxyAutoloader:
class: bitExpert\PHPStan\Magento\Autoload\ProxyAutoloader
arguments:
cache: @autoloaderCache
+ classLoaderProvider: @classLoaderProvider
tags:
- phpstan.magento.autoloader
- extensionAttributeDataProvider:
- class: bitExpert\PHPStan\Magento\Autoload\DataProvider\ExtensionAttributeDataProvider
- arguments:
- magentoRoot: %magento.magentoRoot%
- classLoaderProvider:
- class: bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider
- arguments:
- magentoRoot: %magento.magentoRoot%
extensionInterfaceAutoloader:
class: bitExpert\PHPStan\Magento\Autoload\ExtensionInterfaceAutoloader
arguments:
cache: @autoloaderCache
- attributeDataProvider: @extensionAttributeDataProvider
classLoaderProvider: @classLoaderProvider
+ attributeDataProvider: @extensionAttributeDataProvider
tags:
- phpstan.magento.autoloader
extensionAutoloader:
class: bitExpert\PHPStan\Magento\Autoload\ExtensionAutoloader
arguments:
cache: @autoloaderCache
+ classLoaderProvider: @classLoaderProvider
attributeDataProvider: @extensionAttributeDataProvider
tags:
- phpstan.magento.autoloader
diff --git a/phpstan.neon b/phpstan.neon
index 0f85674..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
@@ -41,17 +50,23 @@ 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, string given~'
+ message: '~Parameter #1 \$argument of class ReflectionClass constructor expects class-string|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, string given~'
+ message: '~Parameter #1 \$argument of class ReflectionClass constructor expects class-string|UncachedExtensionInterface, string given~'
path: tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php
-
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
+ -
+ message: '~PHPDoc tag @var assumes the expression with type~'
+ path: tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
diff --git a/src/Magento/Framework/Escaper.php b/src/Magento/Framework/Escaper.php
new file mode 100644
index 0000000..bdd7673
--- /dev/null
+++ b/src/Magento/Framework/Escaper.php
@@ -0,0 +1,69 @@
+ $data
+ * @param string[]|null $allowedTags
+ * @return ($data is array ? string[] : string)
+ */
+ public function escapeHtml($data, array|null $allowedTags = null) {}
+
+ /**
+ * @param CastableToString $string
+ * @param bool $escapeSingleQuote
+ * @return string
+ */
+ public function escapeHtmlAttr($string, bool $escapeSingleQuote = true) {}
+
+ /**
+ * @param CastableToString $string
+ * @return string
+ */
+ public function escapeUrl($string) {}
+
+ /**
+ * @param CastableToString $string
+ * @return string
+ */
+ public function encodeUrlParam($string) {}
+
+ /**
+ * @param CastableToString $string
+ * @return string
+ */
+ public function escapeJs($string) {}
+
+ /**
+ * @param CastableToString $string
+ * @return string
+ */
+ public function escapeCss($string) {}
+
+ /**
+ * @param CastableToString[]|CastableToString $data
+ * @param string $quote
+ * @return ($data is array ? string[] : string)
+ */
+ public function escapeJsQuote($data, string $quote = '\'') {}
+
+ /**
+ * @param CastableToString $data
+ * @return string
+ */
+ public function escapeXssInUrl($data) {}
+
+ /**
+ * @param string $data
+ * @param bool $addSlashes
+ * @return string
+ */
+ public function escapeQuote(string $data, bool $addSlashes = false) {}
+}
\ No newline at end of file
diff --git a/src/Magento/Framework/Message/ManagerInterface.php b/src/Magento/Framework/Message/ManagerInterface.php
new file mode 100644
index 0000000..7621b14
--- /dev/null
+++ b/src/Magento/Framework/Message/ManagerInterface.php
@@ -0,0 +1,212 @@
+composer = new ClassLoader($magentoRoot.'/vendor');
- $autoloadFile = $magentoRoot.'/vendor/composer/autoload_namespaces.php';
+ $this->composer = new ClassLoader($magentoRoot . '/vendor');
+ $autoloadFile = $magentoRoot . '/vendor/composer/autoload_namespaces.php';
if (is_file($autoloadFile)) {
+ /** @var array $map */
$map = require $autoloadFile;
foreach ($map as $namespace => $path) {
$this->composer->set($namespace, $path);
}
}
- $autoloadFile = $magentoRoot.'/vendor/composer/autoload_psr4.php';
+ $autoloadFile = $magentoRoot . '/vendor/composer/autoload_psr4.php';
if (is_file($autoloadFile)) {
+ /** @var array $map */
$map = require $autoloadFile;
foreach ($map as $namespace => $path) {
$this->composer->setPsr4($namespace, $path);
}
}
- $autoloadFile = $magentoRoot.'/vendor/composer/autoload_classmap.php';
+ $autoloadFile = $magentoRoot . '/vendor/composer/autoload_classmap.php';
if (is_file($autoloadFile)) {
+ /** @var ?array $classMap */
$classMap = require $autoloadFile;
if (is_array($classMap)) {
$this->composer->addClassMap($classMap);
@@ -63,4 +66,15 @@ public function exists(string $classyConstructName): bool
{
return $this->composer->findFile($classyConstructName) !== false;
}
+
+ /**
+ * Finds the path to the file where the class is defined.
+ *
+ * @param string $class The name of the class
+ * @return string|false The path if found, false otherwise
+ */
+ public function findFile($class)
+ {
+ return $this->composer->findFile($class);
+ }
}
diff --git a/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ExtensionAttributeDataProvider.php b/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ExtensionAttributeDataProvider.php
index 2c53e7d..8f0b2ed 100644
--- a/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ExtensionAttributeDataProvider.php
+++ b/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ExtensionAttributeDataProvider.php
@@ -50,7 +50,7 @@ public function getAttributesForInterface(string $sourceInterface): array
foreach ($this->getExtensionAttributesXmlDocs() as $doc) {
$xpath = new DOMXPath($doc);
$attrs = $xpath->query(
- "//extension_attributes[@for=\"${sourceInterface}\"]/attribute",
+ sprintf('//extension_attributes[@for="%s"]/attribute', $sourceInterface),
$doc->documentElement
);
@@ -113,7 +113,7 @@ protected function getAttrType(DOMElement $attr): string
$cleanType = str_replace('[]', '', $type);
$primitiveTypes = ['float', 'int', 'string', 'bool', 'boolean'];
- return in_array(strtolower($cleanType), $primitiveTypes, true) ? $cleanType : '\\'.$cleanType;
+ return in_array(strtolower($cleanType), $primitiveTypes, true) ? $type : '\\'.$type;
}
/**
diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php
index 950b259..b97845f 100644
--- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php
+++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php
@@ -12,13 +12,16 @@
namespace bitExpert\PHPStan\Magento\Autoload;
+use bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider;
use bitExpert\PHPStan\Magento\Autoload\DataProvider\ExtensionAttributeDataProvider;
use Laminas\Code\Generator\ClassGenerator;
use Laminas\Code\Generator\DocBlock\Tag\ParamTag;
use Laminas\Code\Generator\DocBlock\Tag\ReturnTag;
use Laminas\Code\Generator\DocBlockGenerator;
use Laminas\Code\Generator\MethodGenerator;
+use Laminas\Code\Generator\ParameterGenerator;
use PHPStan\Cache\Cache;
+use ReflectionClass;
class ExtensionAutoloader implements Autoloader
{
@@ -26,6 +29,10 @@ class ExtensionAutoloader implements Autoloader
* @var Cache
*/
private $cache;
+ /**
+ * @var ClassLoaderProvider
+ */
+ private $classLoaderProvider;
/**
* @var ExtensionAttributeDataProvider
*/
@@ -35,13 +42,16 @@ class ExtensionAutoloader implements Autoloader
* ExtensionAutoloader constructor.
*
* @param Cache $cache
+ * @param ClassLoaderProvider $classLoaderProvider
* @param ExtensionAttributeDataProvider $attributeDataProvider
*/
public function __construct(
Cache $cache,
+ ClassLoaderProvider $classLoaderProvider,
ExtensionAttributeDataProvider $attributeDataProvider
) {
$this->cache = $cache;
+ $this->classLoaderProvider = $classLoaderProvider;
$this->attributeDataProvider = $attributeDataProvider;
}
@@ -51,26 +61,30 @@ public function autoload(string $class): void
return;
}
- $cachedFilename = $this->cache->load($class, '');
- if ($cachedFilename === null) {
- try {
+ // fix for PHPStan 1.7.5 and later: Classes generated by autoloaders are supposed to "win" against
+ // local classes in your project. We need to check first if classes exists locally before generating them!
+ $pathToLocalClass = $this->classLoaderProvider->findFile($class);
+ if ($pathToLocalClass === false) {
+ $pathToLocalClass = $this->cache->load($class, '');
+ if ($pathToLocalClass === null) {
$this->cache->save($class, '', $this->getFileContents($class));
- $cachedFilename = $this->cache->load($class, '');
- } catch (\Exception $e) {
- return;
+ $pathToLocalClass = $this->cache->load($class, '');
}
}
- require_once($cachedFilename);
+ require_once($pathToLocalClass);
}
/**
* Given an extension attributes interface name, generate that interface (if possible)
+ *
+ * @throws \ReflectionException
*/
public function getFileContents(string $className): string
{
/** @var class-string $sourceInterface */
$sourceInterface = rtrim(substr($className, 0, -1 * strlen('Extension')), '\\') . 'ExtensionInterface';
+ $sourceInterfaceReflection = new ReflectionClass($sourceInterface);
/** @var class-string $attrInterface */
$attrInterface = rtrim(substr($sourceInterface, 0, -1 * strlen('ExtensionInterface')), '\\') . 'Interface';
@@ -88,9 +102,18 @@ public function getFileContents(string $className): string
* @see \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::_getClassMethods
*/
+ // check return type of method in interface and reuse it in the generated class
+ $returnType = null;
+ try {
+ $reflectionMethod = $sourceInterfaceReflection->getMethod('get' . ucfirst($propertyName));
+ $returnType = $reflectionMethod->getReturnType();
+ } catch (\Exception $e) {
+ }
+
$generator->addMethodFromGenerator(
MethodGenerator::fromArray([
'name' => 'get' . ucfirst($propertyName),
+ 'returntype' => $returnType,
'docblock' => DocBlockGenerator::fromArray([
'tags' => [
new ReturnTag([$type, 'null']),
@@ -98,10 +121,38 @@ public function getFileContents(string $className): string
]),
])
);
+
+ // check param type of method in interface and reuse it in the generated class
+ $paramType = null;
+ try {
+ $reflectionMethod = $sourceInterfaceReflection->getMethod('set' . ucfirst($propertyName));
+ $reflectionParams = $reflectionMethod->getParameters();
+ if (isset($reflectionParams[0])) {
+ $paramType = $reflectionParams[0]->getType();
+ if (($paramType !== null) && $reflectionParams[0]->isOptional()) {
+ $paramType = '?'.$paramType;
+ }
+ }
+
+ if ($paramType !== null) {
+ $paramType = (string) $paramType;
+ }
+ } catch (\Exception $e) {
+ }
+
+ // check return type of method in interface and reuse it in the generated class
+ $returnType = null;
+ try {
+ $reflectionMethod = $sourceInterfaceReflection->getMethod('set' . ucfirst($propertyName));
+ $returnType = $reflectionMethod->getReturnType();
+ } catch (\Exception $e) {
+ }
+
$generator->addMethodFromGenerator(
MethodGenerator::fromArray([
'name' => 'set' . ucfirst($propertyName),
- 'parameters' => [$propertyName],
+ 'parameters' => [new ParameterGenerator($propertyName, $paramType)],
+ 'returntype' => $returnType,
'docblock' => DocBlockGenerator::fromArray([
'tags' => [
new ParamTag($propertyName, [$type]),
diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php
index cda4614..e619323 100644
--- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php
+++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php
@@ -19,6 +19,7 @@
use Laminas\Code\Generator\DocBlockGenerator;
use Laminas\Code\Generator\InterfaceGenerator;
use Laminas\Code\Generator\MethodGenerator;
+use Laminas\Code\Generator\ParameterGenerator;
use PHPStan\Cache\Cache;
class ExtensionInterfaceAutoloader implements Autoloader
@@ -27,14 +28,14 @@ class ExtensionInterfaceAutoloader implements Autoloader
* @var Cache
*/
private $cache;
- /**
- * @var ExtensionAttributeDataProvider
- */
- private $attributeDataProvider;
/**
* @var ClassLoaderProvider
*/
private $classLoaderProvider;
+ /**
+ * @var ExtensionAttributeDataProvider
+ */
+ private $attributeDataProvider;
/**
* ExtensionInterfaceAutoloader constructor.
@@ -45,31 +46,32 @@ class ExtensionInterfaceAutoloader implements Autoloader
*/
public function __construct(
Cache $cache,
- ExtensionAttributeDataProvider $attributeDataProvider,
- ClassLoaderProvider $classLoaderProvider
+ ClassLoaderProvider $classLoaderProvider,
+ ExtensionAttributeDataProvider $attributeDataProvider
) {
$this->cache = $cache;
- $this->attributeDataProvider = $attributeDataProvider;
$this->classLoaderProvider = $classLoaderProvider;
+ $this->attributeDataProvider = $attributeDataProvider;
}
- public function autoload(string $class): void
+ public function autoload(string $interfaceName): void
{
- if (preg_match('#ExtensionInterface$#', $class) !== 1) {
+ if (preg_match('#ExtensionInterface$#', $interfaceName) !== 1) {
return;
}
- $cachedFilename = $this->cache->load($class, '');
- if ($cachedFilename === null) {
- try {
- $this->cache->save($class, '', $this->getFileContents($class));
- $cachedFilename = $this->cache->load($class, '');
- } catch (\Exception $e) {
- return;
+ // fix for PHPStan 1.7.5 and later: Classes generated by autoloaders are supposed to "win" against
+ // local classes in your project. We need to check first if classes exists locally before generating them!
+ $pathToLocalInterface = $this->classLoaderProvider->findFile($interfaceName);
+ if ($pathToLocalInterface === false) {
+ $pathToLocalInterface = $this->cache->load($interfaceName, '');
+ if ($pathToLocalInterface === null) {
+ $this->cache->save($interfaceName, '', $this->getFileContents($interfaceName));
+ $pathToLocalInterface = $this->cache->load($interfaceName, '');
}
}
- require_once($cachedFilename);
+ require_once($pathToLocalInterface);
}
/**
@@ -88,7 +90,9 @@ public function getFileContents(string $interfaceName): string
// Magento only creates extension attribute interfaces for existing interfaces; retain that logic
if (!$this->classLoaderProvider->exists($sourceInterface)) {
- throw new \InvalidArgumentException("${sourceInterface} does not exist and has no extension interface");
+ throw new \InvalidArgumentException(
+ sprintf('%s does not exist and has no extension interface', $sourceInterface)
+ );
}
$generator = new InterfaceGenerator();
diff --git a/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php
index 3aef82f..51630c2 100644
--- a/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php
+++ b/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php
@@ -12,6 +12,7 @@
namespace bitExpert\PHPStan\Magento\Autoload;
+use bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider;
use PHPStan\Cache\Cache;
class FactoryAutoloader implements Autoloader
@@ -20,15 +21,21 @@ class FactoryAutoloader implements Autoloader
* @var Cache
*/
private $cache;
+ /**
+ * @var ClassLoaderProvider
+ */
+ private $classLoaderProvider;
/**
* FactoryAutoloader constructor.
*
* @param Cache $cache
+ * @param ClassLoaderProvider $classLoaderProvider
*/
- public function __construct(Cache $cache)
+ public function __construct(Cache $cache, ClassLoaderProvider $classLoaderProvider)
{
$this->cache = $cache;
+ $this->classLoaderProvider = $classLoaderProvider;
}
public function autoload(string $class): void
@@ -37,13 +44,18 @@ public function autoload(string $class): void
return;
}
- $cacheFilename = $this->cache->load($class, '');
- if ($cacheFilename === null) {
- $this->cache->save($class, '', $this->getFileContents($class));
- $cacheFilename = $this->cache->load($class, '');
+ // fix for PHPStan 1.7.5 and later: Classes generated by autoloaders are supposed to "win" against
+ // local classes in your project. We need to check first if classes exists locally before generating them!
+ $pathToLocalClass = $this->classLoaderProvider->findFile($class);
+ if ($pathToLocalClass === false) {
+ $pathToLocalClass = $this->cache->load($class, '');
+ if ($pathToLocalClass === null) {
+ $this->cache->save($class, '', $this->getFileContents($class));
+ $pathToLocalClass = $this->cache->load($class, '');
+ }
}
- require_once($cacheFilename);
+ require_once($pathToLocalClass);
}
/**
@@ -57,7 +69,7 @@ protected function getFileContents(string $class): string
$namespace = explode('\\', ltrim($class, '\\'));
/** @var string $factoryClassname */
$factoryClassname = array_pop($namespace);
- $originalClassname = str_replace('Factory', '', $factoryClassname);
+ $originalClassname = (string) preg_replace('#Factory$#', '', $factoryClassname);
$namespace = implode('\\', $namespace);
$template = "cache = $cache;
+ $this->classLoaderProvider = $classLoaderProvider;
}
public function autoload(string $class): void
@@ -37,13 +44,18 @@ public function autoload(string $class): void
return;
}
- $cacheFilename = $this->cache->load($class, '');
- if ($cacheFilename === null) {
- $this->cache->save($class, '', $this->getFileContents($class));
- $cacheFilename = $this->cache->load($class, '');
+ // fix for PHPStan 1.7.5 and later: Classes generated by autoloaders are supposed to "win" against
+ // local classes in your project. We need to check first if classes exists locally before generating them!
+ $pathToLocalClass = $this->classLoaderProvider->findFile($class);
+ if ($pathToLocalClass === false) {
+ $pathToLocalClass = $this->cache->load($class, '');
+ if ($pathToLocalClass === null) {
+ $this->cache->save($class, '', $this->getFileContents($class));
+ $pathToLocalClass = $this->cache->load($class, '');
+ }
}
- require_once($cacheFilename);
+ require_once($pathToLocalClass);
}
/**
@@ -98,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/Autoload/TestFrameworkAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php
index b1499bc..1153a43 100644
--- a/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php
+++ b/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php
@@ -18,10 +18,25 @@
*/
class TestFrameworkAutoloader implements Autoloader
{
+ /**
+ * @var string
+ */
+ private $magentoRoot;
+
+ /**
+ * TestFrameworkAutoloader constructor.
+ *
+ * @param string $magentoRoot
+ */
+ public function __construct(string $magentoRoot)
+ {
+ $this->magentoRoot = $magentoRoot;
+ }
+
public function autoload(string $class): void
{
$class = str_replace('\\', '/', $class);
- $testsBaseDir = __DIR__ . '/../../../../../../../../dev/tests/static';
+ $testsBaseDir = $this->magentoRoot.'/dev/tests/static';
$directories = [
// try to find Magento\TestFramework classes...
diff --git a/src/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflection.php b/src/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflection.php
index aea4596..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
*/
private $variants;
@@ -37,7 +37,7 @@ class MagicMethodReflection implements MethodReflection
*
* @param string $name
* @param ClassReflection $declaringClass
- * @param ParametersAcceptor[] $variants
+ * @param list $variants
*/
public function __construct(string $name, ClassReflection $declaringClass, array $variants = [])
{
@@ -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/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
new file mode 100644
index 0000000..4252629
--- /dev/null
+++ b/src/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRule.php
@@ -0,0 +1,68 @@
+
+ */
+class ResourceModelsShouldBeUsedDirectlyRule implements Rule
+{
+ /**
+ * @phpstan-return class-string
+ * @return string
+ */
+ public function getNodeType(): string
+ {
+ return MethodCall::class;
+ }
+
+ public function processNode(Node $node, Scope $scope): array
+ {
+ 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 [
+ 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
new file mode 100644
index 0000000..b986700
--- /dev/null
+++ b/src/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRule.php
@@ -0,0 +1,69 @@
+
+ */
+class SetTemplateDisallowedForBlockRule implements Rule
+{
+ /**
+ * @phpstan-return class-string
+ * @return string
+ */
+ public function getNodeType(): string
+ {
+ return MethodCall::class;
+ }
+
+ public function processNode(Node $node, Scope $scope): array
+ {
+ 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 [
+ 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/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 @@
+ \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/src/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtension.php b/src/bitExpert/PHPStan/Magento/Type/ObjectManagerDynamicReturnTypeExtension.php
index 96d4692..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';
@@ -64,9 +61,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..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';
@@ -92,10 +89,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);
}
/**
diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php
index f585616..8c4a93b 100644
--- a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php
@@ -3,19 +3,29 @@
namespace bitExpert\PHPStan\Magento\Autoload;
use bitExpert\PHPStan\Magento\Autoload\Cache\FileCacheStorage;
+use bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider;
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 ExtensionAttributeDataProvider|\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
*/
private $extAttrDataProvider;
/**
@@ -25,10 +35,13 @@ 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(
$this->cache,
+ $this->classLoader,
$this->extAttrDataProvider
);
}
@@ -38,22 +51,43 @@ protected function setUp(): void
*/
public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void
{
- $this->cache->expects(self::never())
+ $this->classLoader->expects(self::never())
+ ->method('findFile');
+ $this->cacheStorage->expects(self::never())
->method('load');
$this->autoloader->autoload('SomeClass');
}
+ /**
+ * @test
+ */
+ public function autoloaderPrefersLocalFile(): void
+ {
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(__DIR__ . '/HelperExtension.php');
+ $this->cacheStorage->expects(self::never())
+ ->method('load');
+
+ $this->autoloader->autoload(HelperExtension::class);
+
+ self::assertTrue(class_exists(HelperExtension::class, false));
+ }
+
/**
* @test
*/
public function autoloaderUsesCachedFileWhenFound(): void
{
- $this->cache->expects(self::once())
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(false);
+ $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);
@@ -66,6 +100,13 @@ public function autoloaderUsesCachedFileWhenFound(): void
*/
public function autoloadGeneratesInterfaceWhenNotCached(): void
{
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(false);
+ $this->extAttrDataProvider->expects(self::once())
+ ->method('getAttributesForInterface')
+ ->willReturn(['attr' => 'string']);
+
$className = 'MyUncachedExtension';
// since the generated class implements an interface, we need to make it available here, otherwise
// the autoloader will fail with an exception that the interface can't be found!
@@ -73,11 +114,7 @@ class_alias(HelperExtensionInterface::class, 'MyUncachedExtensionInterface');
$root = vfsStream::setup('test');
$cache = new Cache(new FileCacheStorage($root->url() . '/tmp/cache/PHPStan'));
- $autoloader = new ExtensionAutoloader($cache, $this->extAttrDataProvider);
-
- $this->extAttrDataProvider->expects(self::once())
- ->method('getAttributesForInterface')
- ->willReturn(['attr' => 'string']);
+ $autoloader = new ExtensionAutoloader($cache, $this->classLoader, $this->extAttrDataProvider);
$autoloader->autoload($className);
static::assertTrue(class_exists($className));
diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php
index 19283e1..2f0e750 100644
--- a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php
@@ -5,24 +5,30 @@
use bitExpert\PHPStan\Magento\Autoload\Cache\FileCacheStorage;
use bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider;
use bitExpert\PHPStan\Magento\Autoload\DataProvider\ExtensionAttributeDataProvider;
+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 $classyDataProvider;
+ private $classLoader;
/**
* @var ExtensionInterfaceAutoloader
*/
@@ -30,13 +36,14 @@ 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->classyDataProvider = $this->createMock(ClassLoaderProvider::class);
$this->autoloader = new ExtensionInterfaceAutoloader(
$this->cache,
- $this->extAttrDataProvider,
- $this->classyDataProvider
+ $this->classLoader,
+ $this->extAttrDataProvider
);
}
@@ -45,22 +52,43 @@ protected function setUp(): void
*/
public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void
{
- $this->cache->expects(self::never())
+ $this->classLoader->expects(self::never())
+ ->method('findFile');
+ $this->cacheStorage->expects(self::never())
->method('load');
$this->autoloader->autoload('SomeClass');
}
+ /**
+ * @test
+ */
+ public function autoloaderPrefersLocalFile(): void
+ {
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(__DIR__ . '/HelperExtensionInterface.php');
+ $this->cacheStorage->expects(self::never())
+ ->method('load');
+
+ $this->autoloader->autoload(HelperExtensionInterface::class);
+
+ self::assertTrue(interface_exists(HelperExtensionInterface::class, false));
+ }
+
/**
* @test
*/
public function autoloaderUsesCachedFileWhenFound(): void
{
- $this->cache->expects(self::once())
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(false);
+ $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);
@@ -73,18 +101,23 @@ public function autoloaderUsesCachedFileWhenFound(): void
*/
public function autoloadDoesNotGenerateInterfaceWhenNoAttributesExist(): void
{
+ $this->expectException(InvalidArgumentException::class);
+ $this->expectExceptionMessage('NonExistentInterface does not exist and has no extension interface');
+
$interfaceName = 'NonExistentExtensionInterface';
- $this->cache->expects(self::once())
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(false);
+ $this->cacheStorage->expects(self::once())
->method('load')
->willReturn(null);
- $this->classyDataProvider->expects(self::once())
+ $this->classLoader->expects(self::once())
->method('exists')
->willReturn(false);
$this->autoloader->autoload($interfaceName);
- static::assertFalse(interface_exists($interfaceName));
}
/**
@@ -96,9 +129,13 @@ public function autoloadGeneratesInterfaceWhenNotCached(): void
$root = vfsStream::setup('test');
$cache = new Cache(new FileCacheStorage($root->url() . '/tmp/cache/PHPStan'));
- $autoloader = new ExtensionInterfaceAutoloader($cache, $this->extAttrDataProvider, $this->classyDataProvider);
+ $autoloader = new ExtensionInterfaceAutoloader($cache, $this->classLoader, $this->extAttrDataProvider);
- $this->classyDataProvider->expects(self::once())
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(false);
+
+ $this->classLoader->expects(self::once())
->method('exists')
->willReturn(true);
@@ -108,6 +145,7 @@ public function autoloadGeneratesInterfaceWhenNotCached(): void
$autoloader->autoload($interfaceName);
static::assertTrue(interface_exists($interfaceName));
+
$interfaceReflection = new \ReflectionClass($interfaceName);
try {
$getAttrReflection = $interfaceReflection->getMethod('getAttr');
diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloaderUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloaderUnitTest.php
index 2e2e056..677bce6 100644
--- a/tests/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloaderUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloaderUnitTest.php
@@ -12,6 +12,7 @@
namespace bitExpert\PHPStan\Magento\Autoload;
+use bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider;
use PHPStan\Cache\Cache;
use PHPStan\Cache\CacheStorage;
use PHPUnit\Framework\TestCase;
@@ -26,11 +27,17 @@ class FactoryAutoloaderUnitTest extends TestCase
* @var FactoryAutoloader
*/
private $autoloader;
+ /**
+ * @var ClassLoaderProvider|\PHPUnit\Framework\MockObject\MockObject
+ */
+ private $classLoader;
public function setUp(): void
{
$this->storage = $this->createMock(CacheStorage::class);
- $this->autoloader = new FactoryAutoloader(new Cache($this->storage));
+ $this->classLoader = $this->createMock(ClassLoaderProvider::class);
+
+ $this->autoloader = new FactoryAutoloader(new Cache($this->storage), $this->classLoader);
}
/**
@@ -38,17 +45,38 @@ public function setUp(): void
*/
public function autoloaderIgnoresClassesWithoutFactoryPostfix(): void
{
+ $this->classLoader->expects(self::never())
+ ->method('findFile');
$this->storage->expects(self::never())
->method('load');
$this->autoloader->autoload('SomeClass');
}
+ /**
+ * @test
+ */
+ public function autoloaderPrefersLocalFile(): void
+ {
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(__DIR__ . '/HelperFactory.php');
+ $this->storage->expects(self::never())
+ ->method('load');
+
+ $this->autoloader->autoload(HelperFactory::class);
+
+ self::assertTrue(class_exists(HelperFactory::class, false));
+ }
+
/**
* @test
*/
public function autoloaderUsesCachedFileWhenFound(): void
{
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(false);
$this->storage->expects(self::once())
->method('load')
->willReturn(__DIR__ . '/HelperFactory.php');
@@ -63,6 +91,9 @@ public function autoloaderUsesCachedFileWhenFound(): void
*/
public function autoloaderGeneratesCacheFileWhenNotFoundInCache(): void
{
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(false);
$this->storage->expects(self::atMost(2))
->method('load')
->willReturnOnConsecutiveCalls(null, __DIR__ . '/HelperFactory.php');
@@ -73,4 +104,32 @@ public function autoloaderGeneratesCacheFileWhenNotFoundInCache(): void
self::assertTrue(class_exists(HelperFactory::class, false));
}
+
+ /**
+ * @test
+ */
+ public function autoloaderGeneratesFactoryForCorrectClassname(): void
+ {
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(false);
+ $this->storage->expects(self::atMost(2))
+ ->method('load')
+ ->willReturnOnConsecutiveCalls(null, __DIR__ . '/FactoryThingFactory.php');
+ $this->storage->expects(self::once())
+ ->method('save')
+ ->with(
+ 'bitExpert\PHPStan\Magento\Autoload\FactoryThingFactory',
+ static::isType('string'),
+ static::stringContains(<<autoloader->autoload(FactoryThingFactory::class);
+ }
}
diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/FactoryThingFactory.php b/tests/bitExpert/PHPStan/Magento/Autoload/FactoryThingFactory.php
new file mode 100644
index 0000000..c90f6b3
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Autoload/FactoryThingFactory.php
@@ -0,0 +1,20 @@
+storage = $this->createMock(CacheStorage::class);
- $this->autoloader = new ProxyAutoloader(new Cache($this->storage));
+ $this->classLoader = $this->createMock(ClassLoaderProvider::class);
+
+ $this->autoloader = new ProxyAutoloader(new Cache($this->storage), $this->classLoader);
}
/**
@@ -38,17 +45,38 @@ public function setUp(): void
*/
public function autoloaderIgnoresClassesWithoutProxyPostfix(): void
{
+ $this->classLoader->expects(self::never())
+ ->method('findFile');
$this->storage->expects(self::never())
->method('load');
$this->autoloader->autoload('SomeClass');
}
+ /**
+ * @test
+ */
+ public function autoloaderPrefersLocalFile(): void
+ {
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(__DIR__ . '/HelperProxy.php');
+ $this->storage->expects(self::never())
+ ->method('load');
+
+ $this->autoloader->autoload('\bitExpert\PHPStan\Magento\Autoload\Helper\Proxy');
+
+ self::assertTrue(class_exists(HelperProxy::class, false));
+ }
+
/**
* @test
*/
public function autoloaderUsesCachedFileWhenFound(): void
{
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(false);
$this->storage->expects(self::once())
->method('load')
->willReturn(__DIR__ . '/HelperProxy.php');
@@ -67,6 +95,9 @@ public function autoloaderGeneratesCacheFileWhenNotFoundInCache(): void
// to avoid having another stub class file, we define an class alias here
class_alias('\bitExpert\PHPStan\Magento\Autoload\HelperProxy', '\bitExpert\PHPStan\Magento\Autoload\Helper');
+ $this->classLoader->expects(self::once())
+ ->method('findFile')
+ ->willReturn(false);
$this->storage->expects(self::atMost(2))
->method('load')
->willReturnOnConsecutiveCalls(null, __DIR__ . '/HelperProxy.php');
diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php
index 4fe82a3..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 $initialAutoloadFunctions */
$initialAutoloadFunctions = spl_autoload_functions();
$autoloader->register();
- /** @var array $registerAutoloadFunctions */
+
$registerAutoloadFunctions = spl_autoload_functions();
static::assertCount(count($initialAutoloadFunctions) + 1, $registerAutoloadFunctions);
$autoloader->unregister();
- /** @var array $unregisterAutoloadFunctions */
+
$unregisterAutoloadFunctions = spl_autoload_functions();
static::assertCount(count($initialAutoloadFunctions), $unregisterAutoloadFunctions);
}
@@ -45,16 +44,22 @@ public function autoloadersCanRegisterAndUnregister(Autoloader $autoloader): voi
public function provideAutoloaders(): array
{
$cache = new Cache($this->getMockBuilder(\PHPStan\Cache\CacheStorage::class)->getMock());
+ $classLoader = $this->createMock(ClassLoaderProvider::class);
return [
- [new FactoryAutoloader($cache)],
+ [new FactoryAutoloader($cache, $classLoader)],
[new MockAutoloader()],
- [new ProxyAutoloader($cache)],
- [new TestFrameworkAutoloader()],
+ [new ProxyAutoloader($cache, $classLoader)],
+ [new TestFrameworkAutoloader(__DIR__)],
+ [new ExtensionAutoloader(
+ $cache,
+ new ClassLoaderProvider(__DIR__),
+ new ExtensionAttributeDataProvider(__DIR__)
+ )],
[new ExtensionInterfaceAutoloader(
$cache,
- new ExtensionAttributeDataProvider(__DIR__),
- new ClassLoaderProvider(__DIR__)
+ new ClassLoaderProvider(__DIR__),
+ new ExtensionAttributeDataProvider(__DIR__)
)]
];
}
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 @@
+getContainer()->getService('reflectionProvider');
+ $this->classReflection = $reflectionProvider->getClass(DataObjectHelper::class);
+
$this->extension = new DataObjectMagicMethodReflectionExtension();
- $this->classReflection = $this->createMock(ClassReflection::class);
}
/**
@@ -170,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));
}
@@ -188,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 @@
+getContainer()->getService('reflectionProvider');
+ $this->classReflection = $reflectionProvider->getClass(SessionManagerHelper::class);
$this->extension = new SessionManagerMagicMethodReflectionExtension();
- $this->classReflection = $this->createMock(ClassReflection::class);
}
/**
@@ -78,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 939c8a7..a75c296 100644
--- a/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
+++ b/tests/bitExpert/PHPStan/Magento/Reflection/MagicMethodReflectionUnitTest.php
@@ -12,17 +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
{
- $classReflection = $this->createMock(ClassReflection::class);
+ /** @var ReflectionProvider $reflectionProvider */
+ $reflectionProvider = $this->getContainer()->getService('reflectionProvider');
+ $classReflection = $reflectionProvider->getClass('Magento\Framework\App\RequestInterface');
$methodName = 'myTestMethod';
$variants = [];
@@ -36,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());
}
@@ -47,13 +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 {
- $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);
@@ -66,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()],
];
}
}
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/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 @@
+setTemplate('template.phtml');
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 @@
+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..ce67520
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Rules/ResourceModelsShouldBeUsedDirectlyRuleUnitTest.php
@@ -0,0 +1,82 @@
+
+ */
+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 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);
+ }
+}
diff --git a/tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php b/tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php
new file mode 100644
index 0000000..274bf40
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Rules/SetTemplateDisallowedForBlockRuleUnitTest.php
@@ -0,0 +1,84 @@
+
+ */
+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 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);
+ }
+}
diff --git a/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php
new file mode 100644
index 0000000..8587d98
--- /dev/null
+++ b/tests/bitExpert/PHPStan/Magento/Type/ControllerResultFactoryReturnTypeExtensionUnitTest.php
@@ -0,0 +1,98 @@
+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());
+ }
+}
diff --git a/tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php b/tests/bitExpert/PHPStan/Magento/Type/TestFrameworkObjectManagerDynamicReturnTypeExtensionUnitTest.php
index bd579ff..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,6 +80,7 @@ public function returnsErrorTypeForUnkownMethodCall(): void
$scope = $this->createMock(Scope::class);
$methodCall = $this->createMock(MethodCall::class);
$methodCall->args = [];
+ $methodCall->name = new Identifier('somethingUnknown');
$resultType = $this->extension->getTypeFromMethodCall($methodReflection, $methodCall, $scope);
@@ -94,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);
@@ -110,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);
@@ -130,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);
@@ -146,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);
@@ -172,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);
@@ -193,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);