diff --git a/README.md b/README.md index 4fe76195..c22d4c98 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,24 @@ parameters: container_xml_path: %rootDir%/../../../var/cache/dev/srcDevDebugProjectContainer.xml ``` -## Limitations - You have to provide a path to `srcDevDebugProjectContainer.xml` or similar xml file describing your container. + +## Constant hassers + +Sometimes, when you are dealing with optional dependencies, the `::has()` methods can cause problems. For example, the following construct would complain that the condition is always either on or off, depending on whether you have the dependency for `service` installed: + +```php +if ($this->has('service')) { + // ... +} +``` + +In that case, you can disable the `::has()` method return type resolving like this: + +``` +parameters: + symfony: + constant_hassers: false +``` + +Be aware that it may hide genuine errors in your application. diff --git a/extension.neon b/extension.neon index 628b0b5f..e09d9779 100644 --- a/extension.neon +++ b/extension.neon @@ -1,3 +1,7 @@ +parameters: + symfony: + constant_hassers: true + rules: - PHPStan\Rules\Symfony\ContainerInterfacePrivateServiceRule - PHPStan\Rules\Symfony\ContainerInterfaceUnknownServiceRule @@ -12,13 +16,13 @@ services: # ControllerTrait::get()/has() return type - - class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface) + class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Component\DependencyInjection\ContainerInterface, %symfony.constant_hassers%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller) + class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\Controller, %symfony.constant_hassers%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] - - class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController) + class: PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension(Symfony\Bundle\FrameworkBundle\Controller\AbstractController, %symfony.constant_hassers%) tags: [phpstan.broker.dynamicMethodReturnTypeExtension] # ControllerTrait::has() type specification diff --git a/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php b/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php index 49aa5b88..48e8aacf 100644 --- a/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php +++ b/src/Type/Symfony/ServiceDynamicReturnTypeExtension.php @@ -20,12 +20,16 @@ final class ServiceDynamicReturnTypeExtension implements DynamicMethodReturnType /** @var string */ private $className; + /** @var bool */ + private $constantHassers; + /** @var \PHPStan\Symfony\ServiceMap */ private $symfonyServiceMap; - public function __construct(string $className, ServiceMap $symfonyServiceMap) + public function __construct(string $className, bool $constantHassers, ServiceMap $symfonyServiceMap) { $this->className = $className; + $this->constantHassers = $constantHassers; $this->symfonyServiceMap = $symfonyServiceMap; } @@ -79,7 +83,7 @@ private function getHasTypeFromMethodCall( ): Type { $returnType = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType(); - if (!isset($methodCall->args[0])) { + if (!isset($methodCall->args[0]) || !$this->constantHassers) { return $returnType; } diff --git a/tests/Symfony/ExampleContainer.php b/tests/Symfony/ExampleContainer.php index 866c1ba6..785004a1 100644 --- a/tests/Symfony/ExampleContainer.php +++ b/tests/Symfony/ExampleContainer.php @@ -10,12 +10,12 @@ class Container_14d7103555 extends Nette\DI\Container 'PhpParser\PrettyPrinter\Standard' => [1 => ['3_PhpParser_PrettyPrinter_Standard']], 'PHPStan\Symfony\ServiceMap' => [1 => ['4']], 'PHPStan\Symfony\ServiceMapFactory' => [1 => ['symfony.serviceMapFactory']], - 'PHPStan\Type\DynamicMethodReturnTypeExtension' => [1 => ['6', '7', '9', '11']], + 'PHPStan\Type\DynamicMethodReturnTypeExtension' => [1 => ['6', '10', '11', '12']], 'PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension' => [1 => ['6']], - 'PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension' => [1 => ['7', '9', '11']], - 'PHPStan\Type\MethodTypeSpecifyingExtension' => [1 => ['8', '10', '12']], - 'PHPStan\Analyser\TypeSpecifierAwareExtension' => [1 => ['8', '10', '12']], - 'PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension' => [1 => ['8', '10', '12']], + 'PHPStan\Type\MethodTypeSpecifyingExtension' => [1 => ['7', '8', '9']], + 'PHPStan\Analyser\TypeSpecifierAwareExtension' => [1 => ['7', '8', '9']], + 'PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension' => [1 => ['7', '8', '9']], + 'PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension' => [1 => ['10', '11', '12']], 'Nette\DI\Container' => [1 => ['container']], ], 'services' => [ @@ -26,17 +26,17 @@ class Container_14d7103555 extends Nette\DI\Container 'symfony.serviceMapFactory' => 'PHPStan\Symfony\ServiceMapFactory', 4 => 'PHPStan\Symfony\ServiceMap', 6 => 'PHPStan\Type\Symfony\RequestDynamicReturnTypeExtension', - 'PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension', 'PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension', - 'PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension', 'PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension', - 'PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension', 'PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension', + 'PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension', + 'PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension', + 'PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension', ], 'tags' => [ 'phpstan.rules.rule' => ['rules.0' => true, 'rules.1' => true], - 'phpstan.broker.dynamicMethodReturnTypeExtension' => [6 => true, true, 9 => true, 11 => true], - 'phpstan.typeSpecifier.methodTypeSpecifyingExtension' => [8 => true, 10 => true, 12 => true], + 'phpstan.broker.dynamicMethodReturnTypeExtension' => [6 => true, 10 => true, true, true], + 'phpstan.typeSpecifier.methodTypeSpecifyingExtension' => [7 => true, true, true], ], 'aliases' => [], ]; @@ -45,7 +45,7 @@ class Container_14d7103555 extends Nette\DI\Container public function __construct(array $params = []) { $this->parameters = $params; - $this->parameters += ['symfony' => ['container_xml_path' => '']]; + $this->parameters += ['symfony' => ['container_xml_path' => '', 'constant_hassers' => true]]; } @@ -97,9 +97,12 @@ public function createService__6(): PHPStan\Type\Symfony\RequestDynamicReturnTyp } - public function createService__7(): PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension + public function createService__7(): PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension { - $service = new PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension('Symfony\Bundle\FrameworkBundle\Controller\AbstractController', $this->getService('4')); + $service = new PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension( + 'Symfony\Bundle\FrameworkBundle\Controller\AbstractController', + $this->getService('3_PhpParser_PrettyPrinter_Standard') + ); return $service; } @@ -107,43 +110,40 @@ public function createService__7(): PHPStan\Type\Symfony\ServiceDynamicReturnTyp public function createService__8(): PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension { $service = new PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension( - 'Symfony\Bundle\FrameworkBundle\Controller\AbstractController', + 'Symfony\Bundle\FrameworkBundle\Controller\Controller', $this->getService('3_PhpParser_PrettyPrinter_Standard') ); return $service; } - public function createService__9(): PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension + public function createService__9(): PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension { - $service = new PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension('Symfony\Bundle\FrameworkBundle\Controller\Controller', $this->getService('4')); + $service = new PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension( + 'Symfony\Component\DependencyInjection\ContainerInterface', + $this->getService('3_PhpParser_PrettyPrinter_Standard') + ); return $service; } - public function createService__10(): PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension + public function createService__10(): PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension { - $service = new PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension( - 'Symfony\Bundle\FrameworkBundle\Controller\Controller', - $this->getService('3_PhpParser_PrettyPrinter_Standard') - ); + $service = new PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension('Symfony\Bundle\FrameworkBundle\Controller\AbstractController', true, $this->getService('4')); return $service; } public function createService__11(): PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension { - $service = new PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension('Symfony\Component\DependencyInjection\ContainerInterface', $this->getService('4')); + $service = new PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension('Symfony\Bundle\FrameworkBundle\Controller\Controller', true, $this->getService('4')); return $service; } - public function createService__12(): PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension + public function createService__12(): PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension { - $service = new PHPStan\Type\Symfony\ServiceTypeSpecifyingExtension( - 'Symfony\Component\DependencyInjection\ContainerInterface', - $this->getService('3_PhpParser_PrettyPrinter_Standard') - ); + $service = new PHPStan\Type\Symfony\ServiceDynamicReturnTypeExtension('Symfony\Component\DependencyInjection\ContainerInterface', true, $this->getService('4')); return $service; } diff --git a/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php b/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php index 710169a6..a1ef5100 100644 --- a/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php +++ b/tests/Type/Symfony/ServiceDynamicReturnTypeExtensionTest.php @@ -18,7 +18,7 @@ public function testServices(string $expression, string $type): void __DIR__ . '/ExampleController.php', $expression, $type, - new ServiceDynamicReturnTypeExtension(Controller::class, (new XmlServiceMapFactory(__DIR__ . '/container.xml'))->create()) + new ServiceDynamicReturnTypeExtension(Controller::class, true, (new XmlServiceMapFactory(__DIR__ . '/container.xml'))->create()) ); } @@ -34,4 +34,23 @@ public function servicesProvider(): Iterator yield ['$has4', 'bool']; } + /** + * @dataProvider constantHassersOffProvider + */ + public function testConstantHassersOff(string $expression, string $type): void + { + $this->processFile( + __DIR__ . '/ExampleController.php', + $expression, + $type, + new ServiceDynamicReturnTypeExtension(Controller::class, false, (new XmlServiceMapFactory(__DIR__ . '/container.xml'))->create()) + ); + } + + public function constantHassersOffProvider(): Iterator + { + yield ['$has1', 'bool']; + yield ['$has2', 'bool']; + } + }