From 8319f11cefbeab38ad29adc19042b4a999579bed Mon Sep 17 00:00:00 2001 From: Mark Shust Date: Wed, 18 May 2022 16:50:49 -0400 Subject: [PATCH 01/73] Add link to related blog post --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 0243268..24fee16 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,8 @@ You can add `bitexpert/phpstan-magento` as a dev dependency, as follows: composer.phar require --dev bitexpert/phpstan-magento ``` +> 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/). + ### 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. From 02df0bd720217ed6490a5002f7d7c6cd0dc32232 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Thu, 26 May 2022 09:37:11 +0200 Subject: [PATCH 02/73] Do not yet install PHPStan 1.7 --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 3d2b6a7..2a1632c 100644 --- a/composer.json +++ b/composer.json @@ -22,7 +22,7 @@ "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", + "phpstan/phpstan": "^1.6.2 <1.7.0", "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, "conflict": { From b3a58db0c826f22611f0081a55b2e53b2346bc27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Thu, 26 May 2022 09:45:44 +0200 Subject: [PATCH 03/73] Update Changelog file --- CHANGELOG.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1a52fba..75baf93 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.22.0 + +### Added +- [#243](https://github.com/bitExpert/phpstan-magento/pull/243) 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 From 4537a6cc44b1b4fcc7ddb8ab2190093ce7b62a50 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Mon, 23 May 2022 18:51:25 +0200 Subject: [PATCH 04/73] Upgrade to PHPStan 1.7.2 --- README.md | 4 ++-- composer.json | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 24fee16..c4d8a51 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ PHP: PHP 7.2 or higher Magento: Magento 2.3.0 or higher -PHPStan: PHPStan 1.6 +PHPStan: PHPStan 1.7 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 `~1.7` and run: ``` composer update phpstan/phpstan --with-all-dependencies diff --git a/composer.json b/composer.json index 2a1632c..1aaaec7 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", @@ -22,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.0", - "phpstan/phpstan": "^1.6.2 <1.7.0", + "phpstan/phpstan": "~1.7.2", "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, "conflict": { From 91032ba3777bf22df981c959d70f0fdbc391e515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Fri, 27 May 2022 11:01:09 +0200 Subject: [PATCH 05/73] Update Changelog file --- CHANGELOG.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 75baf93..074713a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,10 +2,27 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 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 -- [#243](https://github.com/bitExpert/phpstan-magento/pull/243) Do not yet require PHPStan 1.7 +- [#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 From d3dbb8dbc43b8a7a38c49e18ffe161f469e2a0c3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Jul 2022 04:16:41 +0000 Subject: [PATCH 06/73] Update phpstan/phpstan requirement from ~1.7.2 to ~1.8.2 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.8.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.7.2...1.8.2) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1aaaec7..a866a6b 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.0", - "phpstan/phpstan": "~1.7.2", + "phpstan/phpstan": "~1.8.2", "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, "conflict": { From c975ea994997444a24a7e1014a847cb8131ca38d Mon Sep 17 00:00:00 2001 From: Matthew Wells Date: Mon, 1 Aug 2022 10:35:08 -0400 Subject: [PATCH 07/73] Fix factory generation for "FactoryThing" classes --- .../Magento/Autoload/FactoryAutoloader.php | 2 +- .../Autoload/FactoryAutoloaderUnitTest.php | 25 +++++++++++++++++++ .../Magento/Autoload/FactoryThingFactory.php | 20 +++++++++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 tests/bitExpert/PHPStan/Magento/Autoload/FactoryThingFactory.php diff --git a/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php index 3aef82f..e6e7426 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php @@ -57,7 +57,7 @@ protected function getFileContents(string $class): string $namespace = explode('\\', ltrim($class, '\\')); /** @var string $factoryClassname */ $factoryClassname = array_pop($namespace); - $originalClassname = str_replace('Factory', '', $factoryClassname); + $originalClassname = preg_replace('#Factory$#', '', $factoryClassname); $namespace = implode('\\', $namespace); $template = "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 @@ + Date: Sat, 6 Aug 2022 09:51:53 +0200 Subject: [PATCH 08/73] Allow arrays in extension attributes --- .../DataProvider/ExtensionAttributeDataProvider.php | 2 +- .../PHPStan/Magento/Autoload/ExtensionAutoloader.php | 10 +++++++++- .../Magento/Autoload/ExtensionInterfaceAutoloader.php | 10 +++++++++- 3 files changed, 19 insertions(+), 3 deletions(-) diff --git a/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ExtensionAttributeDataProvider.php b/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ExtensionAttributeDataProvider.php index 2c53e7d..4bd425e 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ExtensionAttributeDataProvider.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ExtensionAttributeDataProvider.php @@ -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..31d385f 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php @@ -18,6 +18,7 @@ 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; class ExtensionAutoloader implements Autoloader @@ -88,6 +89,13 @@ public function getFileContents(string $className): string * @see \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::_getClassMethods */ + // treat array types properly in the generated code. Similar to Magento core MyInterface[] type gets + // converted to just an array + $paramType = $type; + if (strpos($type, '[]') !== false) { + $paramType = '?array'; + } + $generator->addMethodFromGenerator( MethodGenerator::fromArray([ 'name' => 'get' . ucfirst($propertyName), @@ -101,7 +109,7 @@ public function getFileContents(string $className): string $generator->addMethodFromGenerator( MethodGenerator::fromArray([ 'name' => 'set' . ucfirst($propertyName), - 'parameters' => [$propertyName], + 'parameters' => [new ParameterGenerator($propertyName, $paramType)], '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..915037e 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 @@ -104,6 +105,13 @@ public function getFileContents(string $interfaceName): string * @see \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::_getClassMethods */ + // treat array types properly in the generated code. Similar to Magento core MyInterface[] type gets + // converted to just an array + $paramType = $type; + if (strpos($type, '[]') !== false) { + $paramType = '?array'; + } + $generator->addMethodFromGenerator( MethodGenerator::fromArray([ 'name' => 'get' . ucfirst($propertyName), @@ -117,7 +125,7 @@ public function getFileContents(string $interfaceName): string $generator->addMethodFromGenerator( MethodGenerator::fromArray([ 'name' => 'set' . ucfirst($propertyName), - 'parameters' => [$propertyName], + 'parameters' => [new ParameterGenerator($propertyName, $paramType)], 'docblock' => DocBlockGenerator::fromArray([ 'tags' => [ new ParamTag($propertyName, [$type]), From 540d0af62addd154d327ae11c70af69a27aac593 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 6 Aug 2022 10:00:16 +0200 Subject: [PATCH 09/73] Update Changelog file --- CHANGELOG.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 074713a..eb0523f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,9 +2,29 @@ All notable changes to this project will be documented in this file, in reverse chronological order by release. +## 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 @@ -22,6 +42,7 @@ All notable changes to this project will be documented in this file, in reverse ## 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 From 94dba31e75bf7173188f2a37f70c84e45bfe7f34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 13 Aug 2022 09:27:02 +0200 Subject: [PATCH 10/73] Autoloaders prefer local classes --- extension.neon | 18 +++++----- .../DataProvider/ClassLoaderProvider.php | 19 +++++++--- .../Magento/Autoload/FactoryAutoloader.php | 24 +++++++++---- .../Magento/Autoload/ProxyAutoloader.php | 24 +++++++++---- .../Autoload/FactoryAutoloaderUnitTest.php | 36 ++++++++++++++++++- .../Autoload/ProxyAutoloaderUnitTest.php | 33 ++++++++++++++++- .../Magento/Autoload/RegistrationUnitTest.php | 5 +-- 7 files changed, 131 insertions(+), 28 deletions(-) diff --git a/extension.neon b/extension.neon index 7a9eef4..7976673 100644 --- a/extension.neon +++ b/extension.neon @@ -52,6 +52,14 @@ 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: @@ -64,22 +72,16 @@ services: 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: diff --git a/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ClassLoaderProvider.php b/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ClassLoaderProvider.php index 013760f..8142429 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ClassLoaderProvider.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ClassLoaderProvider.php @@ -27,8 +27,8 @@ class ClassLoaderProvider */ public function __construct(string $magentoRoot) { - $this->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)) { $map = require $autoloadFile; foreach ($map as $namespace => $path) { @@ -36,7 +36,7 @@ public function __construct(string $magentoRoot) } } - $autoloadFile = $magentoRoot.'/vendor/composer/autoload_psr4.php'; + $autoloadFile = $magentoRoot . '/vendor/composer/autoload_psr4.php'; if (is_file($autoloadFile)) { $map = require $autoloadFile; foreach ($map as $namespace => $path) { @@ -44,7 +44,7 @@ public function __construct(string $magentoRoot) } } - $autoloadFile = $magentoRoot.'/vendor/composer/autoload_classmap.php'; + $autoloadFile = $magentoRoot . '/vendor/composer/autoload_classmap.php'; if (is_file($autoloadFile)) { $classMap = require $autoloadFile; if (is_array($classMap)) { @@ -63,4 +63,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/FactoryAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloader.php index e6e7426..6eee7e4 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); } /** diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php index 4f44a35..4cee192 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloader.php @@ -12,6 +12,7 @@ namespace bitExpert\PHPStan\Magento\Autoload; +use bitExpert\PHPStan\Magento\Autoload\DataProvider\ClassLoaderProvider; use PHPStan\Cache\Cache; class ProxyAutoloader implements Autoloader @@ -20,15 +21,21 @@ class ProxyAutoloader implements Autoloader * @var Cache */ private $cache; + /** + * @var ClassLoaderProvider + */ + private $classLoaderProvider; /** * ProxyAutoloader 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); } /** diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloaderUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/FactoryAutoloaderUnitTest.php index c5372ba..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'); @@ -79,6 +110,9 @@ public function autoloaderGeneratesCacheFileWhenNotFoundInCache(): void */ 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'); diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloaderUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloaderUnitTest.php index 76cfc70..e2815f8 100644 --- a/tests/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloaderUnitTest.php +++ b/tests/bitExpert/PHPStan/Magento/Autoload/ProxyAutoloaderUnitTest.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 ProxyAutoloaderUnitTest extends TestCase * @var ProxyAutoloader */ private $autoloader; + /** + * @var ClassLoaderProvider|\PHPUnit\Framework\MockObject\MockObject + */ + private $classLoader; public function setUp(): void { $this->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..7e3485b 100644 --- a/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php +++ b/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php @@ -45,11 +45,12 @@ 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 ProxyAutoloader($cache, $classLoader)], [new TestFrameworkAutoloader()], [new ExtensionInterfaceAutoloader( $cache, From 886a52d5b6ca4b95b37a50eb42539b85e1ea3b12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 6 Aug 2022 10:30:16 +0200 Subject: [PATCH 11/73] Use Magento root for TestFrameworkAutoloader --- extension.neon | 2 ++ .../Magento/Autoload/TestFrameworkAutoloader.php | 14 +++++++++++++- .../Magento/Autoload/RegistrationUnitTest.php | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) diff --git a/extension.neon b/extension.neon index 7976673..482414c 100644 --- a/extension.neon +++ b/extension.neon @@ -66,6 +66,8 @@ services: - phpstan.magento.autoloader testFrameworkAutoloader: class: bitExpert\PHPStan\Magento\Autoload\TestFrameworkAutoloader + arguments: + magentoRoot: %magento.magentoRoot% tags: - phpstan.magento.autoloader factoryAutoloader: diff --git a/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php index b1499bc..185d412 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php @@ -18,10 +18,22 @@ */ class TestFrameworkAutoloader implements Autoloader { + private string $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/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php index 7e3485b..1b83da3 100644 --- a/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php +++ b/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php @@ -51,7 +51,7 @@ public function provideAutoloaders(): array [new FactoryAutoloader($cache, $classLoader)], [new MockAutoloader()], [new ProxyAutoloader($cache, $classLoader)], - [new TestFrameworkAutoloader()], + [new TestFrameworkAutoloader(__DIR__)], [new ExtensionInterfaceAutoloader( $cache, new ExtensionAttributeDataProvider(__DIR__), From 45db3f54feb9573a63bc6452a0447a42346d6a63 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 13 Aug 2022 09:53:39 +0200 Subject: [PATCH 12/73] Add Magento 2.4.5 to CI pipeline --- .github/workflows/ci.yml | 10 +++++++++- composer.json | 2 +- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 64e0eeb..e75020f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,10 @@ jobs: - php-versions: '8.1' magento: '2.4.4' operating-system: 'ubuntu-latest' + - php-versions: '8.1' + magento: '2.4.5' + operating-system: 'ubuntu-latest' + coveralls: true steps: - name: Checkout repo uses: actions/checkout@v2 @@ -67,7 +71,11 @@ 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 roave/security-advisories + run: composer update --with-dependencies magento/framework:103.0.4 laminas/laminas-code:4.5.1 + + - 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 - name: Composer license check run: composer check-license diff --git a/composer.json b/composer.json index a866a6b..9b6855e 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.0", + "laminas/laminas-code": "~3.3.0 || ~3.4.1 || ~3.5.1 || ~4.5.0 || ~4.5.2", "phpstan/phpstan": "~1.8.2", "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, From 6c7c197161e7e996f37c49f2f84e090ec1fd3cc3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 13 Aug 2022 09:59:31 +0200 Subject: [PATCH 13/73] Whitelist LGPL license (ezyang/htmlpurifier) --- .allowed-licenses | 1 + 1 file changed, 1 insertion(+) 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 From ed74d2528b419e07d978a52321a4370b42985b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 13 Aug 2022 10:42:09 +0200 Subject: [PATCH 14/73] Update Changelog file --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb0523f..87386cb 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.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 From f21db689c8be893996ed8c3ec3bef11d2b72d881 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 13 Aug 2022 10:58:08 +0200 Subject: [PATCH 15/73] Update Composer dependencies --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 9b6855e..94c5eb6 100644 --- a/composer.json +++ b/composer.json @@ -30,18 +30,18 @@ "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", "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-strict-rules": "^1.2.3", + "phpunit/phpunit": "^9.5.20", "squizlabs/php_codesniffer": "^3.6.2" }, "autoload": { From 7605bef95d1291eb8644e418ea5007a3e366188e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Mon, 15 Aug 2022 08:35:14 +0200 Subject: [PATCH 16/73] Change PHPStan version in README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index c4d8a51..37b4252 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ PHP: PHP 7.2 or higher Magento: Magento 2.3.0 or higher -PHPStan: PHPStan 1.7 +PHPStan: PHPStan 1.8 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.7` and run: +installing this extension. in your composer.json Change the PHPStan version to `~1.8` and run: ``` composer update phpstan/phpstan --with-all-dependencies From d50e54272e484b1331dbf32d9eafa732151d7558 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Fri, 2 Sep 2022 19:49:32 +0200 Subject: [PATCH 17/73] Make code compatible with PHP 7.2 --- .../PHPStan/Magento/Autoload/TestFrameworkAutoloader.php | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php index 185d412..1153a43 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/TestFrameworkAutoloader.php @@ -18,7 +18,10 @@ */ class TestFrameworkAutoloader implements Autoloader { - private string $magentoRoot; + /** + * @var string + */ + private $magentoRoot; /** * TestFrameworkAutoloader constructor. From 1c3cf10576c850f8f49deeab817bd429044c2223 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sun, 25 Sep 2022 20:04:14 +0200 Subject: [PATCH 18/73] Don't use ?array type hint in ext attributes --- src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php | 2 +- .../PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php index 31d385f..1b361c0 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php @@ -93,7 +93,7 @@ public function getFileContents(string $className): string // converted to just an array $paramType = $type; if (strpos($type, '[]') !== false) { - $paramType = '?array'; + $paramType = null; } $generator->addMethodFromGenerator( diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php index 915037e..0ac2acc 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php @@ -109,7 +109,7 @@ public function getFileContents(string $interfaceName): string // converted to just an array $paramType = $type; if (strpos($type, '[]') !== false) { - $paramType = '?array'; + $paramType = null; } $generator->addMethodFromGenerator( From 9a5e2ef4201a05ef07a0068b3784f9256d3d1e3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Mon, 3 Oct 2022 09:34:21 +0200 Subject: [PATCH 19/73] Remove param type hints from generated classes --- .../PHPStan/Magento/Autoload/ExtensionAutoloader.php | 9 +-------- .../Magento/Autoload/ExtensionInterfaceAutoloader.php | 9 +-------- 2 files changed, 2 insertions(+), 16 deletions(-) diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php index 1b361c0..e3286bc 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php @@ -89,13 +89,6 @@ public function getFileContents(string $className): string * @see \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::_getClassMethods */ - // treat array types properly in the generated code. Similar to Magento core MyInterface[] type gets - // converted to just an array - $paramType = $type; - if (strpos($type, '[]') !== false) { - $paramType = null; - } - $generator->addMethodFromGenerator( MethodGenerator::fromArray([ 'name' => 'get' . ucfirst($propertyName), @@ -109,7 +102,7 @@ public function getFileContents(string $className): string $generator->addMethodFromGenerator( MethodGenerator::fromArray([ 'name' => 'set' . ucfirst($propertyName), - 'parameters' => [new ParameterGenerator($propertyName, $paramType)], + 'parameters' => [$propertyName], '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 0ac2acc..cd0cd13 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php @@ -105,13 +105,6 @@ public function getFileContents(string $interfaceName): string * @see \Magento\Framework\Api\Code\Generator\ExtensionAttributesGenerator::_getClassMethods */ - // treat array types properly in the generated code. Similar to Magento core MyInterface[] type gets - // converted to just an array - $paramType = $type; - if (strpos($type, '[]') !== false) { - $paramType = null; - } - $generator->addMethodFromGenerator( MethodGenerator::fromArray([ 'name' => 'get' . ucfirst($propertyName), @@ -125,7 +118,7 @@ public function getFileContents(string $interfaceName): string $generator->addMethodFromGenerator( MethodGenerator::fromArray([ 'name' => 'set' . ucfirst($propertyName), - 'parameters' => [new ParameterGenerator($propertyName, $paramType)], + 'parameters' => [$propertyName], 'docblock' => DocBlockGenerator::fromArray([ 'tags' => [ new ParamTag($propertyName, [$type]), From 1b763332de22dd9f16831cf728d68d6d5cee14a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Tue, 4 Oct 2022 19:03:30 +0200 Subject: [PATCH 20/73] Restructure the docs --- README.md | 51 +++++++++++++++++++-------------------------------- 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/README.md b/README.md index 37b4252..f6ff348 100644 --- a/README.md +++ b/README.md @@ -22,29 +22,17 @@ installing this extension. in your composer.json Change the PHPStan version to ` 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 -``` - -> 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/). - -### 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 @@ -52,25 +40,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 +
+ +## Installation -If you don't want to use `phpstan/extension-installer`, include extension.neon in your project's PHPStan config: +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 From bb1fec15bf8b72d7b5412d1169e59223956a21cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Tue, 4 Oct 2022 19:07:07 +0200 Subject: [PATCH 21/73] Update Changelog file --- CHANGELOG.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87386cb..b81ff78 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.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 From b7f8346ea5e32aa56319888c94dc163a4744d958 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Tue, 1 Nov 2022 10:25:38 +0100 Subject: [PATCH 22/73] Check existing ext interface for types --- .../Magento/Autoload/ExtensionAutoloader.php | 43 ++++++++++++++++++- .../Autoload/ExtensionInterfaceAutoloader.php | 21 ++++----- .../ExtensionInterfaceAutoloaderUnitTest.php | 33 +++++++++++++- 3 files changed, 85 insertions(+), 12 deletions(-) diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php index e3286bc..377a74a 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php @@ -20,6 +20,7 @@ use Laminas\Code\Generator\MethodGenerator; use Laminas\Code\Generator\ParameterGenerator; use PHPStan\Cache\Cache; +use ReflectionClass; class ExtensionAutoloader implements Autoloader { @@ -67,11 +68,14 @@ public function autoload(string $class): void /** * 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'; @@ -89,9 +93,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']), @@ -99,10 +112,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 cd0cd13..462a364 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php @@ -54,23 +54,24 @@ public function __construct( $this->classLoaderProvider = $classLoaderProvider; } - 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); } /** diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php index 19283e1..80824e2 100644 --- a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php +++ b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php @@ -5,6 +5,7 @@ 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 PHPUnit\Framework\TestCase; @@ -45,17 +46,38 @@ protected function setUp(): void */ public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void { + $this->classyDataProvider->expects(self::never()) + ->method('findFile'); $this->cache->expects(self::never()) ->method('load'); $this->autoloader->autoload('SomeClass'); } + /** + * @test + */ + public function autoloaderPrefersLocalFile(): void + { + $this->classyDataProvider->expects(self::once()) + ->method('findFile') + ->willReturn(__DIR__ . '/HelperExtensionInterface.php'); + $this->cache->expects(self::never()) + ->method('load'); + + $this->autoloader->autoload(HelperExtensionInterface::class); + + self::assertTrue(interface_exists(HelperExtensionInterface::class, false)); + } + /** * @test */ public function autoloaderUsesCachedFileWhenFound(): void { + $this->classyDataProvider->expects(self::once()) + ->method('findFile') + ->willReturn(false); $this->cache->expects(self::once()) ->method('load') ->willReturn(__DIR__ . '/HelperExtensionInterface.php'); @@ -73,8 +95,13 @@ public function autoloaderUsesCachedFileWhenFound(): void */ public function autoloadDoesNotGenerateInterfaceWhenNoAttributesExist(): void { + $this->expectException(InvalidArgumentException::class); + $interfaceName = 'NonExistentExtensionInterface'; + $this->classyDataProvider->expects(self::once()) + ->method('findFile') + ->willReturn(false); $this->cache->expects(self::once()) ->method('load') ->willReturn(null); @@ -84,7 +111,6 @@ public function autoloadDoesNotGenerateInterfaceWhenNoAttributesExist(): void ->willReturn(false); $this->autoloader->autoload($interfaceName); - static::assertFalse(interface_exists($interfaceName)); } /** @@ -98,6 +124,10 @@ public function autoloadGeneratesInterfaceWhenNotCached(): void $cache = new Cache(new FileCacheStorage($root->url() . '/tmp/cache/PHPStan')); $autoloader = new ExtensionInterfaceAutoloader($cache, $this->extAttrDataProvider, $this->classyDataProvider); + $this->classyDataProvider->expects(self::once()) + ->method('findFile') + ->willReturn(false); + $this->classyDataProvider->expects(self::once()) ->method('exists') ->willReturn(true); @@ -108,6 +138,7 @@ public function autoloadGeneratesInterfaceWhenNotCached(): void $autoloader->autoload($interfaceName); static::assertTrue(interface_exists($interfaceName)); + $interfaceReflection = new \ReflectionClass($interfaceName); try { $getAttrReflection = $interfaceReflection->getMethod('getAttr'); From 289d94ace4487509ad8a8bc9db59ea8c24074250 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 11 Nov 2022 04:15:00 +0000 Subject: [PATCH 23/73] Update phpstan/phpstan requirement from ~1.8.2 to ~1.9.2 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.9.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.8.2...1.9.2) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 94c5eb6..453f375 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.0 || ~4.5.2", - "phpstan/phpstan": "~1.8.2", + "phpstan/phpstan": "~1.9.2", "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, "conflict": { From dce85731e94bf134cbff50c6fc98d9443e23da3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 12 Nov 2022 20:40:08 +0100 Subject: [PATCH 24/73] Update Changelog file --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b81ff78..3446bc8 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.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 From b3f37645be6f6239db4113645f6297c5bf260b10 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 12 Nov 2022 20:47:27 +0100 Subject: [PATCH 25/73] Mention PHPStan 1.9 dependency in Readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index f6ff348..07e96aa 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ PHP: PHP 7.2 or higher Magento: Magento 2.3.0 or higher -PHPStan: PHPStan 1.8 +PHPStan: PHPStan 1.9 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.8` and run: +installing this extension. in your composer.json Change the PHPStan version to `~1.9` and run: ``` composer update phpstan/phpstan --with-all-dependencies From d50c840ea0b29029b596cf3b55311ee616bf22d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 12 Nov 2022 20:53:20 +0100 Subject: [PATCH 26/73] Update Changelog file --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3446bc8..c69c8e9 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.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 From ec58d06843a255d4c002868b3fcd80ca062f9083 Mon Sep 17 00:00:00 2001 From: Pieter Hoste Date: Wed, 18 Jan 2023 20:43:09 +0100 Subject: [PATCH 27/73] Adds PHP 8.2 / Magento 2.4.6 support Allow higher versions of laminas/laminas-code to be installed which brings in PHP 8.2 / Magento 2.4.6 support. Also replaced PHP 8.2 deprecated syntax. --- composer.json | 2 +- .../Autoload/DataProvider/ExtensionAttributeDataProvider.php | 2 +- .../PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php | 4 +++- .../Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php | 1 + 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/composer.json b/composer.json index 453f375..104a2d0 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.0 || ~4.5.2", + "laminas/laminas-code": "~3.3.0 || ~3.4.1 || ~3.5.1 || ^4.5", "phpstan/phpstan": "~1.9.2", "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, diff --git a/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ExtensionAttributeDataProvider.php b/src/bitExpert/PHPStan/Magento/Autoload/DataProvider/ExtensionAttributeDataProvider.php index 4bd425e..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 ); diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php index 462a364..6f6227d 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php @@ -90,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/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php index 80824e2..f1cbc9d 100644 --- a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php +++ b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php @@ -96,6 +96,7 @@ 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'; From bfe87d05f19090283a25f94a58f679153e9791f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 21 Jan 2023 08:57:55 +0100 Subject: [PATCH 28/73] Ignore ext autoloader for local classes --- extension.neon | 3 +- .../Magento/Autoload/ExtensionAutoloader.php | 23 +++++++---- .../Autoload/ExtensionInterfaceAutoloader.php | 14 +++---- .../Autoload/ExtensionAutoloaderUnitTest.php | 41 ++++++++++++++++--- .../ExtensionInterfaceAutoloaderUnitTest.php | 24 +++++------ .../Magento/Autoload/RegistrationUnitTest.php | 9 +++- 6 files changed, 80 insertions(+), 34 deletions(-) diff --git a/extension.neon b/extension.neon index 482414c..ed89b52 100644 --- a/extension.neon +++ b/extension.neon @@ -88,14 +88,15 @@ services: 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/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php index 377a74a..b97845f 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloader.php @@ -12,6 +12,7 @@ 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; @@ -28,6 +29,10 @@ class ExtensionAutoloader implements Autoloader * @var Cache */ private $cache; + /** + * @var ClassLoaderProvider + */ + private $classLoaderProvider; /** * @var ExtensionAttributeDataProvider */ @@ -37,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; } @@ -53,17 +61,18 @@ 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); } /** diff --git a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php index 6f6227d..e619323 100644 --- a/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php +++ b/src/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloader.php @@ -28,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. @@ -46,12 +46,12 @@ 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 $interfaceName): void diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php index f585616..cd1bca7 100644 --- a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php +++ b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionAutoloaderUnitTest.php @@ -3,6 +3,7 @@ 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; @@ -14,6 +15,10 @@ class ExtensionAutoloaderUnitTest extends TestCase * @var Cache|\PHPUnit\Framework\MockObject\MockObject */ private $cache; + /** + * @var ClassLoaderProvider|\PHPUnit\Framework\MockObject\MockObject + */ + private $classLoader; /** * @var ExtensionAttributeDataProvider|\PHPUnit\Framework\MockObject\MockObject */ @@ -26,9 +31,11 @@ class ExtensionAutoloaderUnitTest extends TestCase protected function setUp(): void { $this->cache = $this->createMock(Cache::class); + $this->classLoader = $this->createMock(ClassLoaderProvider::class); $this->extAttrDataProvider = $this->createMock(ExtensionAttributeDataProvider::class); $this->autoloader = new ExtensionAutoloader( $this->cache, + $this->classLoader, $this->extAttrDataProvider ); } @@ -38,17 +45,38 @@ protected function setUp(): void */ public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void { + $this->classLoader->expects(self::never()) + ->method('findFile'); $this->cache->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->cache->expects(self::never()) + ->method('load'); + + $this->autoloader->autoload(HelperExtension::class); + + self::assertTrue(class_exists(HelperExtension::class, false)); + } + /** * @test */ public function autoloaderUsesCachedFileWhenFound(): void { + $this->classLoader->expects(self::once()) + ->method('findFile') + ->willReturn(false); $this->cache->expects(self::once()) ->method('load') ->willReturn(__DIR__ . '/HelperExtension.php'); @@ -66,6 +94,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 +108,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 f1cbc9d..f62ba1a 100644 --- a/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php +++ b/tests/bitExpert/PHPStan/Magento/Autoload/ExtensionInterfaceAutoloaderUnitTest.php @@ -23,7 +23,7 @@ class ExtensionInterfaceAutoloaderUnitTest extends TestCase /** * @var ClassLoaderProvider|\PHPUnit\Framework\MockObject\MockObject */ - private $classyDataProvider; + private $classLoader; /** * @var ExtensionInterfaceAutoloader */ @@ -32,12 +32,12 @@ class ExtensionInterfaceAutoloaderUnitTest extends TestCase protected function setUp(): void { $this->cache = $this->createMock(Cache::class); + $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 ); } @@ -46,7 +46,7 @@ protected function setUp(): void */ public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void { - $this->classyDataProvider->expects(self::never()) + $this->classLoader->expects(self::never()) ->method('findFile'); $this->cache->expects(self::never()) ->method('load'); @@ -59,7 +59,7 @@ public function autoloaderIgnoresClassesWithoutExtensionInterfacePostfix(): void */ public function autoloaderPrefersLocalFile(): void { - $this->classyDataProvider->expects(self::once()) + $this->classLoader->expects(self::once()) ->method('findFile') ->willReturn(__DIR__ . '/HelperExtensionInterface.php'); $this->cache->expects(self::never()) @@ -75,7 +75,7 @@ public function autoloaderPrefersLocalFile(): void */ public function autoloaderUsesCachedFileWhenFound(): void { - $this->classyDataProvider->expects(self::once()) + $this->classLoader->expects(self::once()) ->method('findFile') ->willReturn(false); $this->cache->expects(self::once()) @@ -100,14 +100,14 @@ public function autoloadDoesNotGenerateInterfaceWhenNoAttributesExist(): void $interfaceName = 'NonExistentExtensionInterface'; - $this->classyDataProvider->expects(self::once()) + $this->classLoader->expects(self::once()) ->method('findFile') ->willReturn(false); $this->cache->expects(self::once()) ->method('load') ->willReturn(null); - $this->classyDataProvider->expects(self::once()) + $this->classLoader->expects(self::once()) ->method('exists') ->willReturn(false); @@ -123,13 +123,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->classyDataProvider->expects(self::once()) + $this->classLoader->expects(self::once()) ->method('exists') ->willReturn(true); diff --git a/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php b/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php index 1b83da3..ca86cb4 100644 --- a/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php +++ b/tests/bitExpert/PHPStan/Magento/Autoload/RegistrationUnitTest.php @@ -52,10 +52,15 @@ public function provideAutoloaders(): array [new MockAutoloader()], [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__) )] ]; } From 27559f34cf21e08cbe07f974a774d570d6cd454c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sun, 22 Jan 2023 08:50:24 +0100 Subject: [PATCH 29/73] Update Changelog file --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c69c8e9..a4100d3 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.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 From 8cc57efaae8c0f73398de2f59f1e06503d2f39aa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 27 Feb 2023 05:01:17 +0000 Subject: [PATCH 30/73] Update phpstan/phpstan requirement from ~1.9.2 to ~1.10.3 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.10.x/CHANGELOG.md) - [Commits](https://github.com/phpstan/phpstan/compare/1.9.2...1.10.3) --- updated-dependencies: - dependency-name: phpstan/phpstan dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- composer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 104a2d0..f3b336e 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", - "phpstan/phpstan": "~1.9.2", + "phpstan/phpstan": "~1.10.3", "symfony/finder": "^3.0 || ^4.0 || ^5.0 || ^6.0" }, "conflict": { From d28394a762d1aff96c68b2c260f2a1d821067d3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Tue, 28 Feb 2023 08:33:50 +0100 Subject: [PATCH 31/73] Update PHPStan version in README.md file --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 07e96aa..2ff17e6 100644 --- a/README.md +++ b/README.md @@ -13,10 +13,10 @@ PHP: PHP 7.2 or higher Magento: Magento 2.3.0 or higher -PHPStan: PHPStan 1.9 +PHPStan: PHPStan 1.10 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.9` and run: +installing this extension. in your composer.json Change the PHPStan version to `~1.10` and run: ``` composer update phpstan/phpstan --with-all-dependencies From 61a22ddc30e6149957315dceb3a13396f76e1612 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Tue, 28 Feb 2023 08:41:16 +0100 Subject: [PATCH 32/73] Update Changelog file --- CHANGELOG.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4100d3..1f94d6c 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.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 From 6050536ab6b44bcae0cab521605fdfdefab0db87 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Fri, 7 Apr 2023 09:12:09 +0200 Subject: [PATCH 33/73] 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?= Date: Fri, 7 Apr 2023 09:29:50 +0200 Subject: [PATCH 34/73] 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?= Date: Fri, 7 Apr 2023 11:11:18 +0200 Subject: [PATCH 35/73] 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 @@ + + */ +class ResourceModelsShouldBeUsedDirectlyRule implements Rule +{ + /** + * @phpstan-return class-string + * @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 @@ +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 @@ + + */ +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?= Date: Fri, 7 Apr 2023 14:40:11 +0200 Subject: [PATCH 36/73] 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 @@ + + */ +class SetTemplateDisallowedForBlockRule implements Rule +{ + /** + * @phpstan-return class-string + * @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 @@ +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 @@ + + */ +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?= Date: Fri, 7 Apr 2023 14:49:42 +0200 Subject: [PATCH 37/73] 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?= Date: Fri, 7 Apr 2023 15:02:21 +0200 Subject: [PATCH 38/73] 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?= Date: Fri, 7 Apr 2023 15:27:31 +0200 Subject: [PATCH 39/73] 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?= Date: Fri, 7 Apr 2023 15:32:07 +0200 Subject: [PATCH 40/73] 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?= Date: Sat, 15 Apr 2023 12:33:36 +0200 Subject: [PATCH 41/73] 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 Date: Mon, 17 Jul 2023 12:28:00 +0200 Subject: [PATCH 42/73] 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 ```
- Composer Allow-PLugins configuration + 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: From 4266f15619483d96a3c23d9920edb6154a26c315 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 6 Apr 2024 08:22:09 +0200 Subject: [PATCH 43/73] 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?= Date: Sat, 6 Apr 2024 11:09:30 +0200 Subject: [PATCH 44/73] 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 45/73] 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] --- 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?= Date: Tue, 9 Jul 2024 17:02:15 +0200 Subject: [PATCH 46/73] 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?= Date: Tue, 9 Jul 2024 17:06:57 +0200 Subject: [PATCH 47/73] 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, 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~' From 4190f438dd8be2ce335949fef4ab9159f8d127a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Tue, 9 Jul 2024 17:12:18 +0200 Subject: [PATCH 48/73] 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 49/73] 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] --- 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?= Date: Sat, 31 Aug 2024 18:41:11 +0200 Subject: [PATCH 50/73] 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?= Date: Sat, 31 Aug 2024 18:41:21 +0200 Subject: [PATCH 51/73] 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?= Date: Sat, 31 Aug 2024 18:56:53 +0200 Subject: [PATCH 52/73] 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 53/73] 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 Date: Tue, 12 Nov 2024 21:46:15 +0100 Subject: [PATCH 54/73] 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 Date: Tue, 12 Nov 2024 21:47:20 +0100 Subject: [PATCH 55/73] 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 Date: Wed, 13 Nov 2024 22:10:43 +0100 Subject: [PATCH 56/73] 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 Date: Wed, 13 Nov 2024 22:11:46 +0100 Subject: [PATCH 57/73] 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 Date: Wed, 13 Nov 2024 22:12:15 +0100 Subject: [PATCH 58/73] 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 Date: Wed, 13 Nov 2024 22:14:48 +0100 Subject: [PATCH 59/73] 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 Date: Wed, 13 Nov 2024 22:32:55 +0100 Subject: [PATCH 60/73] 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 $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); } 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 Date: Wed, 13 Nov 2024 22:46:10 +0100 Subject: [PATCH 61/73] 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 $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 $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 $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 = "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 */ 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 = []) { From 47f4dd57b527e9bbb6334bf23a5bfcb30fb55c09 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Stephan=20Hochd=C3=B6rfer?= Date: Sat, 22 Feb 2025 14:10:13 +0100 Subject: [PATCH 62/73] 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 @@ +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 @@ +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?= Date: Sat, 22 Feb 2025 14:22:14 +0100 Subject: [PATCH 63/73] 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?= Date: Sat, 22 Feb 2025 14:42:49 +0100 Subject: [PATCH 64/73] 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?= Date: Sat, 22 Feb 2025 14:48:59 +0100 Subject: [PATCH 65/73] 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 = " Date: Wed, 26 Feb 2025 18:57:52 +0100 Subject: [PATCH 66/73] 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?= Date: Wed, 26 Feb 2025 19:43:48 +0100 Subject: [PATCH 67/73] 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 Date: Tue, 4 Mar 2025 14:56:25 +0100 Subject: [PATCH 68/73] 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 @@ + \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 @@ +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 Date: Wed, 5 Mar 2025 12:36:28 +0100 Subject: [PATCH 69/73] 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?= Date: Thu, 6 Mar 2025 09:02:29 +0100 Subject: [PATCH 70/73] 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 Date: Fri, 9 May 2025 12:53:40 +0200 Subject: [PATCH 71/73] 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?= Date: Sat, 17 May 2025 08:15:12 +0200 Subject: [PATCH 72/73] 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 From 570cca8e7b8b35aa88d60f8d1400b9387c6b8bfb Mon Sep 17 00:00:00 2001 From: Navarr Date: Fri, 11 Jul 2025 17:05:27 -0400 Subject: [PATCH 73/73] Add Stubs for Magento's loosey-string behavior In many cases, Magento typedefs a `string` but truly accepts anything that can be cast to a string. This can be annoying for things like Escaper and Session Messages, which many are inclined to pass Phrase objects into. Additionally, Escaper has return types of `string|array`, passing an array if the input is an array. However, Magento's documentation blocks don't specify that's when it passes an array - so phpstan thinks that the result of Escaper can be either at any time. In that way, you cannot echo out the result of an `escapeHtml` in a PHTML file on max level without getting some errors. --- src/Magento/Framework/Escaper.php | 69 ++++++ .../Framework/Message/ManagerInterface.php | 212 ++++++++++++++++++ .../Framework/Message/MessageInterface.php | 118 ++++++++++ 3 files changed, 399 insertions(+) create mode 100644 src/Magento/Framework/Escaper.php create mode 100644 src/Magento/Framework/Message/ManagerInterface.php create mode 100644 src/Magento/Framework/Message/MessageInterface.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 @@ +