From 6fbd5f27f59c16857f9fde0862e5f2900eba3b34 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 13 Nov 2023 11:30:43 +0100 Subject: [PATCH 1/3] Use dynamic throw type extension --- extension.neon | 5 ++ ...tityManagerInterfaceThrowTypeExtension.php | 47 +++++++++++++++++++ stubs/EntityManagerInterface.stub | 8 ---- 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php 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/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php b/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php new file mode 100644 index 00000000..e7306cc4 --- /dev/null +++ b/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php @@ -0,0 +1,47 @@ + [ + 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 From 6be44222d2cd443708b3337236408f6e58fec204 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 13 Nov 2023 11:34:05 +0100 Subject: [PATCH 2/3] Fix cs --- .../Doctrine/EntityManagerInterfaceThrowTypeExtension.php | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php b/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php index e7306cc4..0070af9f 100644 --- a/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php +++ b/src/Type/Doctrine/EntityManagerInterfaceThrowTypeExtension.php @@ -1,4 +1,4 @@ - [ ORMException::class, - UniqueConstraintViolationException::class + UniqueConstraintViolationException::class, ], ]; @@ -44,4 +45,5 @@ public function getThrowTypeFromMethodCall(MethodReflection $methodReflection, M return $methodReflection->getThrowType(); } + } From 365f7221173438e5b1560b3761bd4e1b8cfda830 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 13 Nov 2023 15:44:03 +0100 Subject: [PATCH 3/3] Add test --- phpstan-baseline.neon | 6 +++- .../TooWideMethodThrowTypeRuleTest.php | 31 +++++++++++++++++++ .../data/entity-manager-interface.php | 21 +++++++++++++ 3 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 tests/Rules/Exceptions/TooWideMethodThrowTypeRuleTest.php create mode 100644 tests/Rules/Exceptions/data/entity-manager-interface.php 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/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(); + } + +}