diff --git a/extension.neon b/extension.neon index 7be2f17c..ce08b076 100644 --- a/extension.neon +++ b/extension.neon @@ -398,3 +398,8 @@ services: class: PHPStan\PhpDoc\Doctrine\QueryTypeNodeResolverExtension tags: - phpstan.phpDoc.typeNodeResolverExtension + + - + class: PHPStan\Type\Doctrine\EntityManagerInterfaceThrowTypeExtension + tags: + - phpstan.dynamicMethodThrowTypeExtension diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 53397101..f5f73b8a 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -40,6 +40,11 @@ parameters: count: 1 path: tests/Rules/Exceptions/CatchWithUnthrownExceptionRuleTest.php + - + message: "#^Accessing PHPStan\\\\Rules\\\\Exceptions\\\\TooWideMethodThrowTypeRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" + count: 1 + path: tests/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php + - message: "#^Accessing PHPStan\\\\Rules\\\\DeadCode\\\\UnusedPrivatePropertyRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" count: 1 @@ -59,4 +64,3 @@ parameters: message: "#^Accessing PHPStan\\\\Rules\\\\Properties\\\\MissingReadOnlyPropertyAssignRule\\:\\:class is not covered by backward compatibility promise\\. The class might change in a minor PHPStan version\\.$#" count: 1 path: tests/Rules/Properties/MissingReadOnlyPropertyAssignRuleTest.php - diff --git a/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php b/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php new file mode 100644 index 00000000..0070af9f --- /dev/null +++ b/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php @@ -0,0 +1,49 @@ + [ + ORMException::class, + UniqueConstraintViolationException::class, + ], + ]; + + public function isMethodSupported(MethodReflection $methodReflection): bool + { + return $methodReflection->getDeclaringClass()->getName() === ObjectManager::class + && isset(self::SUPPORTED_METHOD[$methodReflection->getName()]); + } + + public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type + { + $type = $scope->getType($methodCall->var); + + if ((new ObjectType(EntityManagerInterface::class))->isSuperTypeOf($type)->yes()) { + return TypeCombinator::union( + ...array_map(static function ($class): Type { + return new ObjectType($class); + }, self::SUPPORTED_METHOD[$methodReflection->getName()]) + ); + } + + return $methodReflection->getThrowType(); + } + +} diff --git a/stubs/EntityManagerInterface.stub b/stubs/EntityManagerInterface.stub index 8ab8db8a..87e8fbcb 100644 --- a/stubs/EntityManagerInterface.stub +++ b/stubs/EntityManagerInterface.stub @@ -60,14 +60,6 @@ interface EntityManagerInterface extends ObjectManager */ public function copy($entity, $deep = false); - /** - * @return void - * - * @throws ORMException - * @throws \Doctrine\DBAL\Exception\UniqueConstraintViolationException - */ - public function flush(); - /** * @template T of object * @phpstan-param class-string $className diff --git a/tests/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php b/tests/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php new file mode 100644 index 00000000..e3d91158 --- /dev/null +++ b/tests/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php @@ -0,0 +1,31 @@ + + */ +class TooWideMethodThrowTypeRuleTest extends RuleTestCase +{ + + protected function getRule(): Rule + { + return self::getContainer()->getByType(TooWideMethodThrowTypeRule::class); + } + + public function testRule(): void + { + $this->analyse([__DIR__ . '/data/entity-manager-interface.php'], []); + } + + public static function getAdditionalConfigFiles(): array + { + return [ + __DIR__ . '/../../../extension.neon', + ]; + } + +} diff --git a/tests/Rules/Exceptions/data/entity-manager-interface.php b/tests/Rules/Exceptions/data/entity-manager-interface.php new file mode 100644 index 00000000..46e67680 --- /dev/null +++ b/tests/Rules/Exceptions/data/entity-manager-interface.php @@ -0,0 +1,21 @@ +flush(); + } + +}