diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7167109..1b09468 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -45,6 +45,7 @@ jobs: php-version: - "8.2" - "8.3" + - "8.4" dependencies: - "highest" diff --git a/Dockerfile b/Dockerfile index 33f83da..24126fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,8 +1,8 @@ -FROM php:8.2 +FROM php:8.3 ADD --chmod=0755 https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/ -RUN install-php-extensions @composer intl pcov +RUN install-php-extensions @composer intl xdebug pcov ARG USER_ID ARG GROUP_ID diff --git a/composer.json b/composer.json index 94e8247..bb1ad2c 100644 --- a/composer.json +++ b/composer.json @@ -18,28 +18,24 @@ } ], "require": { - "php": "~8.2.0 || ~8.3.0", - "phpstan/phpstan": "^2.0.1" + "php": "~8.2.0 || ~8.3.0 || ~8.4.0", + "phpstan/phpstan": "^2.0.2" }, "require-dev": { - "laminas/laminas-cache": "^3.12.2", - "laminas/laminas-cache-storage-adapter-memory": "^2.3.0", "laminas/laminas-filter": "^2.39.0", "laminas/laminas-form": "^3.21.0", - "laminas/laminas-hydrator": "^4.15.0", + "laminas/laminas-hydrator": "^4.16.0", "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-inputfilter": "^2.31.0", + "laminas/laminas-mvc": "^3.8.0", "laminas/laminas-paginator": "^2.19.0", "laminas/laminas-validator": "^2.64.1", "phpstan/phpstan-deprecation-rules": "^2", - "phpstan/phpstan-phpunit": "^2", + "phpstan/phpstan-phpunit": "^2.0.1", "phpunit/phpunit": "^11.4.3", "slam/php-cs-fixer-extensions": "^3.11.1" }, "conflict": { - "laminas/laminas-cache": "<3.12", "laminas/laminas-filter": "<2.37", "laminas/laminas-form": "<3.20", "laminas/laminas-hydrator": "<4.15", diff --git a/extension.neon b/extension.neon index cc98056..4c07a58 100644 --- a/extension.neon +++ b/extension.neon @@ -1,12 +1,10 @@ parameters: - laminasframework: - serviceManagerLoader: null universalObjectCratesClasses: - Laminas\Stdlib\ArrayObject parametersSchema: laminasframework: structure([ - serviceManagerLoader: schema(string(), nullable()) + serviceManagerLoader: schema(string()) ]) rules: diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index ec89e43..8ca25e9 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -6,18 +6,6 @@ parameters: 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 diff --git a/phpstan.neon b/phpstan.neon index 37ec4d6..6972962 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -11,6 +11,5 @@ parameters: excludePaths: analyseAndScan: - tests/Rules/Laminas/ServiceManagerGetMethodCallRule/ - - tests/Rules/Laminas/PluginManagerGetMethodCallRule/ - tests/TestAsset/ - tests/LaminasIntegration/data/ diff --git a/src/ServiceManagerLoader.php b/src/ServiceManagerLoader.php index fcce7c2..68269aa 100644 --- a/src/ServiceManagerLoader.php +++ b/src/ServiceManagerLoader.php @@ -5,47 +5,24 @@ namespace LaminasPhpStan; use Interop\Container\ContainerInterface as InteropContainerInterface; -use Laminas\Cache\ConfigProvider; -use Laminas\Mvc\Service\ServiceListenerFactory; -use Laminas\Mvc\Service\ServiceManagerConfig; use Laminas\ServiceManager\ServiceLocatorInterface; use Laminas\ServiceManager\ServiceManager; use PHPStan\ShouldNotHappenException; use Psr\Container\ContainerInterface as PsrContainerInterface; -use ReflectionProperty; -final class ServiceManagerLoader +final readonly class ServiceManagerLoader { - private ?UnmappedAliasServiceLocatorProxy $serviceLocator = null; - - /** @var string[] */ - private array $knownModules = [ - ConfigProvider::class, - \Laminas\Filter\ConfigProvider::class, - \Laminas\Form\ConfigProvider::class, - \Laminas\Hydrator\ConfigProvider::class, - \Laminas\I18n\ConfigProvider::class, - \Laminas\InputFilter\ConfigProvider::class, - \Laminas\Mail\ConfigProvider::class, - \Laminas\Paginator\ConfigProvider::class, - \Laminas\Router\ConfigProvider::class, - \Laminas\Validator\ConfigProvider::class, - ]; - - /** @var array */ - private array $serviceManagerNames = [ + private const serviceManagerNames = [ ServiceManager::class => true, ServiceLocatorInterface::class => true, InteropContainerInterface::class => true, PsrContainerInterface::class => true, ]; - public function __construct(?string $serviceManagerLoader) - { - if (null === $serviceManagerLoader) { - return; - } + private UnmappedAliasServiceLocatorProxy $serviceLocator; + public function __construct(string $serviceManagerLoader) + { if (! \file_exists($serviceManagerLoader) || ! \is_readable($serviceManagerLoader)) { throw new ShouldNotHappenException('Service manager could not be loaded'); } @@ -60,32 +37,8 @@ public function __construct(?string $serviceManagerLoader) public function getServiceLocator(string $serviceManagerName): ServiceLocatorInterface { - if (null === $this->serviceLocator) { - $serviceManager = new ServiceManager(['services' => ['config' => []]]); - if (\class_exists(ServiceManagerConfig::class)) { - (new ServiceManagerConfig())->configureServiceManager($serviceManager); - } - if (\class_exists(ServiceListenerFactory::class)) { - $refProp = new ReflectionProperty(ServiceListenerFactory::class, 'defaultServiceConfig'); - $config = $refProp->getValue(new ServiceListenerFactory()); - \assert(\is_array($config)); - \assert(\is_array($config['factories'])); - unset($config['factories']['config']); - $serviceManager->configure($config); - } - foreach ($this->knownModules as $module) { - if (\class_exists($module)) { - $module = new $module(); - \assert(\method_exists($module, 'getDependencyConfig')); - $serviceManager->configure($module->getDependencyConfig()); - } - } - - $this->serviceLocator = new UnmappedAliasServiceLocatorProxy($serviceManager); - } - $serviceLocator = $this->serviceLocator; - if (! isset($this->serviceManagerNames[$serviceManagerName])) { + if (! isset(self::serviceManagerNames[$serviceManagerName])) { $serviceLocator = $serviceLocator->get($serviceManagerName); \assert($serviceLocator instanceof ServiceLocatorInterface); } diff --git a/src/Type/Laminas/PluginMethodReflection.php b/src/Type/Laminas/PluginMethodReflection.php index 675dde1..3f529d9 100644 --- a/src/Type/Laminas/PluginMethodReflection.php +++ b/src/Type/Laminas/PluginMethodReflection.php @@ -8,6 +8,7 @@ use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\FunctionVariant; use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ParametersAcceptor; use PHPStan\TrinaryLogic; use PHPStan\Type\Generic\TemplateTypeMap; use PHPStan\Type\ObjectType; @@ -96,7 +97,7 @@ public function hasSideEffects(): TrinaryLogic return TrinaryLogic::createNo(); } - /** @return \PHPStan\Reflection\ParametersAcceptor[] */ + /** @return ParametersAcceptor[] */ public function getVariants(): array { return [ diff --git a/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-2.json b/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-2.json index 0953b86..5d23daf 100644 --- a/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-2.json +++ b/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-2.json @@ -1,6 +1,6 @@ [ { - "message": "Access to an undefined property Laminas\\Mail\\Transport\\Envelope::$foobar.", + "message": "Access to an undefined property Laminas\\ModuleManager\\Listener\\ListenerOptions::$foobar.", "line": 18, "ignorable": true }, diff --git a/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-3.json b/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-3.json index 72bdb83..f45c1a1 100644 --- a/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-3.json +++ b/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties-3.json @@ -1,6 +1,6 @@ [ { - "message": "Property Laminas\\Mail\\Transport\\Envelope::$from (string|null) does not accept stdClass.", + "message": "Property Laminas\\ModuleManager\\Listener\\ListenerOptions::$configCacheKey (string) does not accept stdClass.", "line": 19, "ignorable": true } diff --git a/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties.php b/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties.php index e2a78ce..c774197 100644 --- a/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties.php +++ b/tests/LaminasIntegration/data/stdlibAbstractOptionsProperties.php @@ -4,7 +4,7 @@ namespace LaminasPhpStan\Tests\LaminasIntegration\data; -use Laminas\Mail\Transport\Envelope; +use Laminas\ModuleManager\Listener\ListenerOptions; use Laminas\Stdlib\AbstractOptions; use stdClass; @@ -12,14 +12,14 @@ final class stdlibAbstractOptionsProperties { public function mainLibrary(): void { - $envelope = new Envelope(); + $envelope = new ListenerOptions(); // Bad - $envelope->foobar = 1; - $envelope->from = new stdClass(); + $envelope->foobar = 1; + $envelope->configCacheKey = new stdClass(); // Good - $envelope->from = 'test@example.com'; + $envelope->configCacheKey = 'my_key'; } public function custom(): void diff --git a/tests/Rules/Laminas/PluginManagerGetMethodCallRule/Foo.php b/tests/Rules/Laminas/PluginManagerGetMethodCallRule/Foo.php deleted file mode 100644 index 2be50ce..0000000 --- a/tests/Rules/Laminas/PluginManagerGetMethodCallRule/Foo.php +++ /dev/null @@ -1,25 +0,0 @@ -serviceManager = $serviceManager; - } - - public function foo(): void - { - $controllerPluginManager = $this->serviceManager->get('ControllerPluginManager'); - - $controllerPluginManager->get('non_existent_service'); - $controllerPluginManager->get('redirect'); - } -} diff --git a/tests/Rules/Laminas/PluginManagerGetMethodCallRuleTest.php b/tests/Rules/Laminas/PluginManagerGetMethodCallRuleTest.php deleted file mode 100644 index 07dfe03..0000000 --- a/tests/Rules/Laminas/PluginManagerGetMethodCallRuleTest.php +++ /dev/null @@ -1,49 +0,0 @@ - - */ -final class PluginManagerGetMethodCallRuleTest extends RuleTestCase -{ - private ServiceManagerLoader $serviceManagerLoader; - - protected function setUp(): void - { - $this->serviceManagerLoader = new ServiceManagerLoader(null); - } - - /** @return Rule<\PhpParser\Node\Expr\MethodCall> */ - protected function getRule(): Rule - { - return new ServiceManagerGetMethodCallRule($this->createReflectionProvider(), $this->serviceManagerLoader); - } - - public function testRule(): void - { - $this->analyse([__DIR__ . '/PluginManagerGetMethodCallRule/Foo.php'], [ - [ - 'The service "non_existent_service" was not configured in ControllerPluginManager nor the class "non_existent_service" exists.', - 22, - ], - ]); - } - - /** @return string[] */ - public static function getAdditionalConfigFiles(): array - { - return [ - __DIR__ . '/../../../extension.neon', - ]; - } -} diff --git a/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/InteropContainerFoo.php b/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/InteropContainerFoo.php index 780dca7..f12ef6c 100644 --- a/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/InteropContainerFoo.php +++ b/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/InteropContainerFoo.php @@ -5,7 +5,6 @@ namespace LaminasPhpStan\Tests\Rules\Laminas\ServiceManagerGetMethodCallRule; use Interop\Container\ContainerInterface; -use Laminas\Form\FormElementManager; use Laminas\Mvc\Controller\ControllerManager; use stdClass; @@ -34,7 +33,6 @@ public function foo(): void $stdClass->get('non_existent_service'); $this->container->get(ControllerManager::class); - $this->container->get(FormElementManager::class); } public function get(string $foo): void {} diff --git a/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/PsrContainerFoo.php b/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/PsrContainerFoo.php index b3158a9..4340f23 100644 --- a/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/PsrContainerFoo.php +++ b/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/PsrContainerFoo.php @@ -4,7 +4,6 @@ namespace LaminasPhpStan\Tests\Rules\Laminas\ServiceManagerGetMethodCallRule; -use Laminas\Form\FormElementManager; use Laminas\Mvc\Controller\ControllerManager; use Psr\Container\ContainerInterface; use stdClass; @@ -34,7 +33,6 @@ public function foo(): void $stdClass->get('non_existent_service'); $this->container->get(ControllerManager::class); - $this->container->get(FormElementManager::class); } public function get(string $foo): void {} diff --git a/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/ServiceManagerFoo.php b/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/ServiceManagerFoo.php index fef6d14..3d5acb1 100644 --- a/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/ServiceManagerFoo.php +++ b/tests/Rules/Laminas/ServiceManagerGetMethodCallRule/ServiceManagerFoo.php @@ -4,7 +4,6 @@ namespace LaminasPhpStan\Tests\Rules\Laminas\ServiceManagerGetMethodCallRule; -use Laminas\Form\FormElementManager; use Laminas\Mvc\Controller\ControllerManager; use Laminas\ServiceManager\ServiceManager; use stdClass; @@ -34,7 +33,6 @@ public function foo(): void $stdClass->get('non_existent_service'); $this->serviceManager->get(ControllerManager::class); - $this->serviceManager->get(FormElementManager::class); } public function get(string $foo): void {} diff --git a/tests/Rules/Laminas/ServiceManagerGetMethodCallRuleTest.php b/tests/Rules/Laminas/ServiceManagerGetMethodCallRuleTest.php index 2646cf7..cd78ddb 100644 --- a/tests/Rules/Laminas/ServiceManagerGetMethodCallRuleTest.php +++ b/tests/Rules/Laminas/ServiceManagerGetMethodCallRuleTest.php @@ -8,6 +8,7 @@ use Laminas\ServiceManager\ServiceManager; use LaminasPhpStan\Rules\Laminas\ServiceManagerGetMethodCallRule; use LaminasPhpStan\ServiceManagerLoader; +use PhpParser\Node\Expr\MethodCall; use PHPStan\Rules\Rule; use PHPStan\Testing\RuleTestCase; use Psr\Container\ContainerInterface as PsrContainerInterface; @@ -24,7 +25,7 @@ final class ServiceManagerGetMethodCallRuleTest extends RuleTestCase protected function setUp(): void { - $this->serviceManagerLoader = new ServiceManagerLoader(null); + $this->serviceManagerLoader = new ServiceManagerLoader(\dirname(__DIR__, 2) . '/LaminasIntegration/servicemanagerloader.php'); } /** @return string[][] */ @@ -37,7 +38,7 @@ public static function provideRuleCases(): iterable ]; } - /** @return Rule<\PhpParser\Node\Expr\MethodCall> */ + /** @return Rule */ protected function getRule(): Rule { return new ServiceManagerGetMethodCallRule($this->createReflectionProvider(), $this->serviceManagerLoader); @@ -49,7 +50,7 @@ public function testRule(string $filename, string $containerClassname): void $this->analyse([__DIR__ . '/ServiceManagerGetMethodCallRule/' . $filename], [ [ 'The service "non_existent_service" was not configured in ' . $containerClassname . '.', - 23, + 22, ], ]); } diff --git a/tests/Type/Laminas/ServiceManagerLoaderTest.php b/tests/Type/Laminas/ServiceManagerLoaderTest.php index 40097b6..fb70528 100644 --- a/tests/Type/Laminas/ServiceManagerLoaderTest.php +++ b/tests/Type/Laminas/ServiceManagerLoaderTest.php @@ -4,26 +4,8 @@ namespace LaminasPhpStan\Tests\Type\Laminas; -use Laminas\Cache\Storage\AdapterPluginManager as CacheStorageAdapterPluginManager; -use Laminas\Cache\Storage\PluginManager as CacheStoragePluginManager; -use Laminas\Config\ReaderPluginManager as ConfigReaderPluginManager; -use Laminas\Config\WriterPluginManager as ConfigWriterPluginManager; -use Laminas\EventManager\EventManagerInterface; -use Laminas\Filter\FilterPluginManager; -use Laminas\Form\FormElementManager; -use Laminas\Hydrator\HydratorPluginManager; -use Laminas\I18n\Translator\LoaderPluginManager as I18nLoaderPluginManager; -use Laminas\InputFilter\InputFilterPluginManager; -use Laminas\Mail\Protocol\SmtpPluginManager; -use Laminas\Mvc\Controller\ControllerManager; use Laminas\Mvc\Controller\PluginManager as ControllerPluginManager; -use Laminas\Paginator\AdapterPluginManager as PaginatorAdapterPluginManager; -use Laminas\Paginator\ScrollingStylePluginManager; -use Laminas\Router\RoutePluginManager; use Laminas\ServiceManager\ServiceManager; -use Laminas\Validator\ValidatorPluginManager; -use Laminas\View\Helper\Navigation\PluginManager as NavigationPluginManager; -use Laminas\View\HelperPluginManager; use LaminasPhpStan\ServiceManagerLoader; use LaminasPhpStan\TestAsset\BarService; use LaminasPhpStan\TestAsset\FooService; @@ -35,51 +17,6 @@ */ final class ServiceManagerLoaderTest extends TestCase { - public function testWithNullFileUseADefaultInstanceWithPluginManagerConfigured(): void - { - $serviceManagerLoader = new ServiceManagerLoader(null); - - $serviceManager = $serviceManagerLoader->getServiceLocator(ServiceManager::class); - - // @see \Laminas\Mvc\Service\ServiceManagerConfig - self::assertTrue($serviceManager->has(EventManagerInterface::class)); - self::assertTrue($serviceManager->has('ControllerPluginManager')); - - /** @var ControllerPluginManager $controllerPluginManager */ - $controllerPluginManager = $serviceManager->get('ControllerPluginManager'); - - self::assertTrue($controllerPluginManager->has('redirect')); - } - - public function testGetSubserviceDependingOnCallOnTypeGiven(): void - { - $serviceManagerLoader = new ServiceManagerLoader(null); - $knownPluginManagers = [ - CacheStorageAdapterPluginManager::class, - CacheStoragePluginManager::class, - // ConfigReaderPluginManager::class, - // ConfigWriterPluginManager::class, - ControllerManager::class, - ControllerPluginManager::class, - FilterPluginManager::class, - FormElementManager::class, - HelperPluginManager::class, - HydratorPluginManager::class, - I18nLoaderPluginManager::class, - InputFilterPluginManager::class, - // NavigationPluginManager::class, - PaginatorAdapterPluginManager::class, - RoutePluginManager::class, - ScrollingStylePluginManager::class, - SmtpPluginManager::class, - ValidatorPluginManager::class, - ]; - - foreach ($knownPluginManagers as $pluginManagerClassName) { - self::assertInstanceOf($pluginManagerClassName, $serviceManagerLoader->getServiceLocator($pluginManagerClassName)); - } - } - public function testLoaderMustBeAValidFile(): void { $this->expectException(ShouldNotHappenException::class); @@ -96,9 +33,7 @@ public function testLoaderMustReturnAServiceManagerInstance(): void public function testLoaderReturnsTheProvidedServiceManager(): void { - $file = \dirname(__DIR__, 2) . '/LaminasIntegration/servicemanagerloader.php'; - $serviceManagerFromFile = require $file; - $serviceManagerLoader = new ServiceManagerLoader($file); + $serviceManagerLoader = new ServiceManagerLoader(\dirname(__DIR__, 2) . '/LaminasIntegration/servicemanagerloader.php'); $serviceManager = $serviceManagerLoader->getServiceLocator(ServiceManager::class);