diff --git a/Dockerfile b/Dockerfile index 1a0ec95..33f83da 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.3 +FROM php:8.2 ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ diff --git a/composer.json b/composer.json index 9cc7476..94e8247 100644 --- a/composer.json +++ b/composer.json @@ -19,23 +19,23 @@ ], "require": { "php": "~8.2.0 || ~8.3.0", - "phpstan/phpstan": "^1.12.4" + "phpstan/phpstan": "^2.0.1" }, "require-dev": { "laminas/laminas-cache": "^3.12.2", "laminas/laminas-cache-storage-adapter-memory": "^2.3.0", - "laminas/laminas-filter": "^2.37.0", - "laminas/laminas-form": "^3.20.1", + "laminas/laminas-filter": "^2.39.0", + "laminas/laminas-form": "^3.21.0", "laminas/laminas-hydrator": "^4.15.0", - "laminas/laminas-i18n": "^2.28.1", + "laminas/laminas-i18n": "^2.29.0", "laminas/laminas-inputfilter": "^2.30.1", "laminas/laminas-mail": "^2.25.1", "laminas/laminas-mvc": "^3.7.0", - "laminas/laminas-paginator": "^2.18.1", + "laminas/laminas-paginator": "^2.19.0", "laminas/laminas-validator": "^2.64.1", - "phpstan/phpstan-deprecation-rules": "^1.2.1", - "phpstan/phpstan-phpunit": "^1.4.0", - "phpunit/phpunit": "^9.6.21", + "phpstan/phpstan-deprecation-rules": "^2", + "phpstan/phpstan-phpunit": "^2", + "phpunit/phpunit": "^11.4.3", "slam/php-cs-fixer-extensions": "^3.11.1" }, "conflict": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 6eb6b60..ec89e43 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,6 +1,31 @@ parameters: ignoreErrors: - - message: "#^Call to static method PHPUnit\\\\Framework\\\\Assert\\:\\:assertInstanceOf\\(\\) with 'LaminasPhpStan\\\\\\\\TestAsset\\\\\\\\BarService' and Laminas\\\\Stdlib\\\\DispatchableInterface will always evaluate to false\\.$#" + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + identifier: phpstanApi.runtimeReflection + count: 1 + path: src/Rules/Laminas/ServiceManagerGetMethodCallRule.php + + - + message: '#^Parameter \#1 \$config of method Laminas\\ServiceManager\\ServiceManager\:\:configure\(\) expects array\{abstract_factories\?\: array\\|Laminas\\ServiceManager\\Factory\\AbstractFactoryInterface\>, aliases\?\: array\, delegators\?\: mixed, factories\?\: mixed, initializers\?\: mixed, invokables\?\: array\, lazy_services\?\: array\{class_map\?\: array\, proxies_namespace\?\: non\-empty\-string, proxies_target_dir\?\: non\-empty\-string, write_proxy_files\?\: bool\}, services\?\: array\\|object\>, \.\.\.\}, mixed given\.$#' + identifier: argument.type + count: 1 + path: src/ServiceManagerLoader.php + + - + message: '#^Parameter \#1 \$config of method Laminas\\ServiceManager\\ServiceManager\:\:configure\(\) expects array\{abstract_factories\?\: array\\|Laminas\\ServiceManager\\Factory\\AbstractFactoryInterface\>, aliases\?\: array\, delegators\?\: mixed, factories\?\: mixed, initializers\?\: mixed, invokables\?\: array\, lazy_services\?\: array\{class_map\?\: array\, proxies_namespace\?\: non\-empty\-string, proxies_target_dir\?\: non\-empty\-string, write_proxy_files\?\: bool\}, services\?\: array\\|object\>, \.\.\.\}, non\-empty\-array given\.$#' + identifier: argument.type + count: 1 + path: src/ServiceManagerLoader.php + + - + message: '#^Creating new ReflectionClass is a runtime reflection concept that might not work in PHPStan because it uses fully static reflection engine\. Use objects retrieved from ReflectionProvider instead\.$#' + identifier: phpstanApi.runtimeReflection + count: 2 + path: src/Type/Laminas/AbstractServiceLocatorGetDynamicReturnTypeExtension.php + + - + message: '#^Call to static method PHPUnit\\Framework\\Assert\:\:assertInstanceOf\(\) with ''LaminasPhpStan\\\\TestAsset\\\\BarService'' and Laminas\\Stdlib\\DispatchableInterface will always evaluate to false\.$#' + identifier: staticMethod.impossibleType count: 1 path: tests/Type/Laminas/ServiceManagerLoaderTest.php diff --git a/phpunit.xml b/phpunit.xml index 17a5ddf..c78350d 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,15 +1,10 @@ - - ./src - @@ -17,4 +12,9 @@ ./tests + + + ./src + + diff --git a/src/Rules/Laminas/ServiceManagerGetMethodCallRule.php b/src/Rules/Laminas/ServiceManagerGetMethodCallRule.php index 0227977..e450702 100644 --- a/src/Rules/Laminas/ServiceManagerGetMethodCallRule.php +++ b/src/Rules/Laminas/ServiceManagerGetMethodCallRule.php @@ -10,11 +10,11 @@ use LaminasPhpStan\ServiceManagerLoader; use LaminasPhpStan\Type\Laminas\ObjectServiceManagerType; use PhpParser\Node; -use PhpParser\Node\Arg; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Rule; +use PHPStan\Rules\RuleErrorBuilder; use PHPStan\Type\ObjectType; use PHPStan\Type\Type; use Psr\Container\ContainerInterface as PsrContainerInterface; @@ -35,11 +35,7 @@ public function getNodeType(): string return MethodCall::class; } - /** - * @param MethodCall $node - * - * @return string[] - */ + /** @param MethodCall $node */ public function processNode(Node $node, Scope $scope): array { $args = $node->getArgs(); @@ -47,10 +43,7 @@ public function processNode(Node $node, Scope $scope): array return []; } - $firstArg = $args[0]; - if (! $firstArg instanceof Arg) { - return []; - } + $firstArg = $args[0]; $argType = $scope->getType($firstArg->value); $constantStrings = $argType->getConstantStrings(); if (1 !== \count($constantStrings)) { @@ -92,14 +85,14 @@ public function processNode(Node $node, Scope $scope): array } } - return [\sprintf( + return [RuleErrorBuilder::message(\sprintf( 'The service "%s" was not configured in %s%s.', $serviceName, $calledOnType instanceof ObjectServiceManagerType ? $calledOnType->getServiceName() : $calledOnType->getClassName(), $classDoesNotExistNote - )]; + ))->identifier('servicemanager.servicenotconfigured')->build()]; } /** @phpstan-assert-if-true ObjectType $type */ diff --git a/src/ServiceManagerLoader.php b/src/ServiceManagerLoader.php index 0a42872..fcce7c2 100644 --- a/src/ServiceManagerLoader.php +++ b/src/ServiceManagerLoader.php @@ -67,12 +67,10 @@ public function getServiceLocator(string $serviceManagerName): ServiceLocatorInt } if (\class_exists(ServiceListenerFactory::class)) { $refProp = new ReflectionProperty(ServiceListenerFactory::class, 'defaultServiceConfig'); - $refProp->setAccessible(true); - $config = $refProp->getValue(new ServiceListenerFactory()); + $config = $refProp->getValue(new ServiceListenerFactory()); \assert(\is_array($config)); \assert(\is_array($config['factories'])); unset($config['factories']['config']); - $refProp->setAccessible(false); $serviceManager->configure($config); } foreach ($this->knownModules as $module) { diff --git a/src/Type/Laminas/AbstractServiceLocatorGetDynamicReturnTypeExtension.php b/src/Type/Laminas/AbstractServiceLocatorGetDynamicReturnTypeExtension.php index d078e98..cf0efb0 100644 --- a/src/Type/Laminas/AbstractServiceLocatorGetDynamicReturnTypeExtension.php +++ b/src/Type/Laminas/AbstractServiceLocatorGetDynamicReturnTypeExtension.php @@ -6,12 +6,10 @@ use Laminas\ServiceManager\AbstractPluginManager; use LaminasPhpStan\ServiceManagerLoader; -use PhpParser\Node\Arg; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ReflectionProvider; -use PHPStan\ShouldNotHappenException; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\MixedType; use PHPStan\Type\NeverType; @@ -48,15 +46,7 @@ final public function getTypeFromMethodCall( $serviceManager = $this->serviceManagerLoader->getServiceLocator($calledOnType->getObjectClassNames()[0]); - $firstArg = $args[0]; - if (! $firstArg instanceof Arg) { - throw new ShouldNotHappenException(\sprintf( - 'Argument passed to %s::%s should be a string, %s given', - $methodReflection->getDeclaringClass()->getName(), - $methodReflection->getName(), - $firstArg->getType() - )); - } + $firstArg = $args[0]; $argType = $scope->getType($firstArg->value); $constantStringTypes = $argType->getConstantStrings(); if (1 !== \count($constantStringTypes)) { diff --git a/src/Type/Laminas/PluginMethodDynamicReturnTypeExtension/AbstractPluginMethodDynamicReturnTypeExtension.php b/src/Type/Laminas/PluginMethodDynamicReturnTypeExtension/AbstractPluginMethodDynamicReturnTypeExtension.php index 806fa27..1397db4 100644 --- a/src/Type/Laminas/PluginMethodDynamicReturnTypeExtension/AbstractPluginMethodDynamicReturnTypeExtension.php +++ b/src/Type/Laminas/PluginMethodDynamicReturnTypeExtension/AbstractPluginMethodDynamicReturnTypeExtension.php @@ -5,7 +5,6 @@ namespace LaminasPhpStan\Type\Laminas\PluginMethodDynamicReturnTypeExtension; use LaminasPhpStan\ServiceManagerLoader; -use PhpParser\Node\Arg; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; @@ -36,17 +35,9 @@ final public function getTypeFromMethodCall( Scope $scope ): Type { $firstArg = $methodCall->getArgs()[0]; - if (! $firstArg instanceof Arg) { - throw new ShouldNotHappenException(\sprintf( - 'Argument passed to %s::%s should be a string, %s given', - $methodReflection->getDeclaringClass()->getName(), - $methodReflection->getName(), - $firstArg->getType() - )); - } - $argType = $scope->getType($firstArg->value); - $strings = $argType->getConstantStrings(); - $plugin = 1 === \count($strings) ? $strings[0]->getValue() : null; + $argType = $scope->getType($firstArg->value); + $strings = $argType->getConstantStrings(); + $plugin = 1 === \count($strings) ? $strings[0]->getValue() : null; if (null !== $plugin) { $pluginManager = $this->serviceManagerLoader->getServiceLocator($this->getPluginManagerName()); diff --git a/tests/LaminasIntegration/IntegrationTest.php b/tests/LaminasIntegration/IntegrationTest.php index b3daaac..fc2d8b7 100644 --- a/tests/LaminasIntegration/IntegrationTest.php +++ b/tests/LaminasIntegration/IntegrationTest.php @@ -11,7 +11,7 @@ */ final class IntegrationTest extends LevelsTestCase { - public function dataTopics(): array + public static function dataTopics(): array { return [ ['serviceManagerDynamicReturn'], @@ -36,7 +36,7 @@ public function getPhpStanExecutablePath(): string return __DIR__ . '/../../vendor/bin/phpstan'; } - public function getPhpStanConfigPath(): ?string + public function getPhpStanConfigPath(): string { return __DIR__ . '/phpstan.neon'; } diff --git a/tests/LaminasIntegration/data/controllerPluginMethod-10.json b/tests/LaminasIntegration/data/controllerPluginMethod-10.json new file mode 100644 index 0000000..42810ea --- /dev/null +++ b/tests/LaminasIntegration/data/controllerPluginMethod-10.json @@ -0,0 +1,7 @@ +[ + { + "message": "Cannot call method isFoo() on mixed.", + "line": 16, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/LaminasIntegration/data/serviceManagerDynamicReturn-4.json b/tests/LaminasIntegration/data/serviceManagerDynamicReturn-4.json new file mode 100644 index 0000000..ffeca68 --- /dev/null +++ b/tests/LaminasIntegration/data/serviceManagerDynamicReturn-4.json @@ -0,0 +1,12 @@ +[ + { + "message": "Expression \"(static function (\\LaminasPhpStan\\TestAsset\\FooService $fooService): void {…\" on a separate line does not do anything.", + "line": 52, + "ignorable": true + }, + { + "message": "Expression \"(static function (\\LaminasPhpStan\\TestAsset\\FooInterface $fooService): void {…\" on a separate line does not do anything.", + "line": 55, + "ignorable": true + } +] \ No newline at end of file diff --git a/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-2.json b/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-2.json index 97f9a32..0953b86 100644 --- a/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-2.json +++ b/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-2.json @@ -5,7 +5,7 @@ "ignorable": true }, { - "message": "Property class@anonymous/stdlibAbstractOptionsProperties.php:27::$myxyz is not writable.", + "message": "Property Laminas\\Stdlib\\AbstractOptions@anonymous/stdlibAbstractOptionsProperties.php:27::$myxyz is not writable.", "line": 37, "ignorable": true }, diff --git a/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-4.json b/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-4.json index 8d68f89..b62603f 100644 --- a/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-4.json +++ b/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-4.json @@ -1,6 +1,6 @@ [ { - "message": "Property class@anonymous/stdlibAbstractOptionsProperties.php:27::$myxyz is never read, only written.", + "message": "Property Laminas\\Stdlib\\AbstractOptions@anonymous/stdlibAbstractOptionsProperties.php:27::$myxyz is never read, only written.", "line": 28, "ignorable": true } diff --git a/tests/Type/Laminas/PluginMethodReflectionTest.php b/tests/Type/Laminas/PluginMethodReflectionTest.php index 33b530f..d8d94e1 100644 --- a/tests/Type/Laminas/PluginMethodReflectionTest.php +++ b/tests/Type/Laminas/PluginMethodReflectionTest.php @@ -5,21 +5,20 @@ namespace LaminasPhpStan\Tests\Type\Laminas; use LaminasPhpStan\Type\Laminas\PluginMethodReflection; -use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionVariant; +use PHPStan\Testing\PHPStanTestCase; use PHPStan\TrinaryLogic; use PHPStan\Type\ObjectType; -use PHPUnit\Framework\TestCase; use stdClass; /** * @covers \LaminasPhpStan\Type\Laminas\PluginMethodReflection */ -final class PluginMethodReflectionTest extends TestCase +final class PluginMethodReflectionTest extends PHPStanTestCase { public function testTrivialUsage(): void { - $declaringClass = $this->createMock(ClassReflection::class); + $declaringClass = $this->createReflectionProvider()->getClass(stdClass::class); $methodName = 'redirect'; $returnType = new ObjectType(stdClass::class);