From 39e4b770c2099137ce94039afc453d7c898bd381 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 6 Mar 2023 20:43:02 +0100 Subject: [PATCH 01/12] Use ReflectionProvider instead of Broker --- ...rineSelectableClassReflectionExtension.php | 15 ++++++------- .../NewExprDynamicReturnTypeExtension.php | 22 ++++++++----------- 2 files changed, 16 insertions(+), 21 deletions(-) diff --git a/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php b/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php index 2e8bec07..88cec5d0 100644 --- a/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php +++ b/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php @@ -2,21 +2,20 @@ namespace PHPStan\Reflection\Doctrine; -use PHPStan\Broker\Broker; -use PHPStan\Reflection\BrokerAwareExtension; use PHPStan\Reflection\ClassReflection; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\MethodsClassReflectionExtension; +use PHPStan\Reflection\ReflectionProvider; -class DoctrineSelectableClassReflectionExtension implements MethodsClassReflectionExtension, BrokerAwareExtension +class DoctrineSelectableClassReflectionExtension implements MethodsClassReflectionExtension { - /** @var Broker */ - private $broker; + /** @var ReflectionProvider */ + private $reflectionProvider; - public function setBroker(Broker $broker): void + public function setBroker(ReflectionProvider $reflectionProvider): void { - $this->broker = $broker; + $this->reflectionProvider = $reflectionProvider; } public function hasMethod(ClassReflection $classReflection, string $methodName): bool @@ -27,7 +26,7 @@ public function hasMethod(ClassReflection $classReflection, string $methodName): public function getMethod(ClassReflection $classReflection, string $methodName): MethodReflection { - $selectableReflection = $this->broker->getClass('Doctrine\Common\Collections\Selectable'); + $selectableReflection = $this->reflectionProvider->getClass('Doctrine\Common\Collections\Selectable'); return $selectableReflection->getNativeMethod($methodName); } diff --git a/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php b/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php index 3b04a80b..1690c63c 100644 --- a/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/QueryBuilder/Expr/NewExprDynamicReturnTypeExtension.php @@ -5,9 +5,8 @@ use PhpParser\Node\Expr\StaticCall; use PhpParser\Node\Name; use PHPStan\Analyser\Scope; -use PHPStan\Broker\Broker; -use PHPStan\Reflection\BrokerAwareExtension; use PHPStan\Reflection\MethodReflection; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Rules\Doctrine\ORM\DynamicQueryBuilderArgumentException; use PHPStan\ShouldNotHappenException; use PHPStan\Type\Doctrine\ArgumentsProcessor; @@ -16,7 +15,7 @@ use PHPStan\Type\Type; use function class_exists; -class NewExprDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension, BrokerAwareExtension +class NewExprDynamicReturnTypeExtension implements DynamicStaticMethodReturnTypeExtension { /** @var ArgumentsProcessor */ @@ -25,21 +24,18 @@ class NewExprDynamicReturnTypeExtension implements DynamicStaticMethodReturnType /** @var string */ private $class; - /** @var Broker */ - private $broker; + /** @var ReflectionProvider */ + private $reflectionProvider; public function __construct( ArgumentsProcessor $argumentsProcessor, - string $class + string $class, + ReflectionProvider $reflectionProvider ) { $this->argumentsProcessor = $argumentsProcessor; $this->class = $class; - } - - public function setBroker(Broker $broker): void - { - $this->broker = $broker; + $this->reflectionProvider = $reflectionProvider; } public function getClass(): string @@ -59,7 +55,7 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, } $className = $scope->resolveName($methodCall->class); - if (!$this->broker->hasClass($className)) { + if (!$this->reflectionProvider->hasClass($className)) { return new ObjectType($className); } @@ -76,7 +72,7 @@ public function getTypeFromStaticMethodCall(MethodReflection $methodReflection, ) ); } catch (DynamicQueryBuilderArgumentException $e) { - return new ObjectType($this->broker->getClassName($className)); + return new ObjectType($this->reflectionProvider->getClassName($className)); } return new ExprType($className, $exprObject); From 271db64cf7da3fa33bf2fa26cc1de7d9ee4fe402 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Mon, 6 Mar 2023 20:34:39 +0100 Subject: [PATCH 02/12] Drop inline `@var` phpstan is able to infer this types without additional magic required --- .../Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php b/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php index 9c1d76bd..5cfbda93 100644 --- a/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php +++ b/src/Type/Doctrine/QueryBuilder/OtherMethodQueryBuilderParser.php @@ -99,10 +99,7 @@ private function findQueryBuilderTypesInCalledMethod(Scope $scope, MethodCall $m return []; } - /** @var NodeScopeResolver $nodeScopeResolver */ $nodeScopeResolver = $this->container->getByType(NodeScopeResolver::class); - - /** @var ScopeFactory $scopeFactory */ $scopeFactory = $this->container->getByType(ScopeFactory::class); $methodScope = $scopeFactory->create(ScopeContext::create($fileName)); From 8d660e92ca61bd5fdb0c1f666e7b2cfb8d3c9adc Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 10 Mar 2023 14:27:45 +0100 Subject: [PATCH 03/12] ClassMetadataInfo - idGenerator property can be null --- extension.neon | 1 + stubs/ORM/Id/AbstractIdGenerator.stub | 10 ++++++++++ stubs/ORM/Mapping/ClassMetadataInfo.stub | 6 ++++++ 3 files changed, 17 insertions(+) create mode 100644 stubs/ORM/Id/AbstractIdGenerator.stub diff --git a/extension.neon b/extension.neon index e7bdba93..4851a816 100644 --- a/extension.neon +++ b/extension.neon @@ -36,6 +36,7 @@ parameters: - stubs/Collections/ReadableCollection.stub - stubs/Collections/Selectable.stub - stubs/ORM/AbstractQuery.stub + - stubs/ORM/Id/AbstractIdGenerator.stub - stubs/ORM/Mapping/ClassMetadata.stub - stubs/ORM/Mapping/ClassMetadataInfo.stub - stubs/ORM/ORMException.stub diff --git a/stubs/ORM/Id/AbstractIdGenerator.stub b/stubs/ORM/Id/AbstractIdGenerator.stub new file mode 100644 index 00000000..a4b5fcd0 --- /dev/null +++ b/stubs/ORM/Id/AbstractIdGenerator.stub @@ -0,0 +1,10 @@ + Date: Fri, 10 Mar 2023 15:42:06 +0100 Subject: [PATCH 04/12] Fix DoctrineSelectableClassReflectionExtension --- ...octrineSelectableClassReflectionExtension.php | 2 +- ...ineSelectableClassReflectionExtensionTest.php | 16 +++++++--------- 2 files changed, 8 insertions(+), 10 deletions(-) diff --git a/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php b/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php index 88cec5d0..16970a60 100644 --- a/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php +++ b/src/Reflection/Doctrine/DoctrineSelectableClassReflectionExtension.php @@ -13,7 +13,7 @@ class DoctrineSelectableClassReflectionExtension implements MethodsClassReflecti /** @var ReflectionProvider */ private $reflectionProvider; - public function setBroker(ReflectionProvider $reflectionProvider): void + public function __construct(ReflectionProvider $reflectionProvider) { $this->reflectionProvider = $reflectionProvider; } diff --git a/tests/Reflection/Doctrine/DoctrineSelectableClassReflectionExtensionTest.php b/tests/Reflection/Doctrine/DoctrineSelectableClassReflectionExtensionTest.php index 5702a68d..1debe15e 100644 --- a/tests/Reflection/Doctrine/DoctrineSelectableClassReflectionExtensionTest.php +++ b/tests/Reflection/Doctrine/DoctrineSelectableClassReflectionExtensionTest.php @@ -3,24 +3,22 @@ namespace PHPStan\Reflection\Doctrine; use Doctrine\Common\Collections\Collection; -use PHPStan\Broker\Broker; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\Testing\PHPStanTestCase; final class DoctrineSelectableClassReflectionExtensionTest extends PHPStanTestCase { - /** @var Broker */ - private $broker; + /** @var ReflectionProvider */ + private $reflectionProvider; /** @var DoctrineSelectableClassReflectionExtension */ private $extension; protected function setUp(): void { - $this->broker = $this->createBroker(); - - $this->extension = new DoctrineSelectableClassReflectionExtension(); - $this->extension->setBroker($this->broker); + $this->reflectionProvider = $this->createReflectionProvider(); + $this->extension = new DoctrineSelectableClassReflectionExtension($this->reflectionProvider); } /** @@ -39,13 +37,13 @@ public function dataHasMethod(): array */ public function testHasMethod(string $className, string $method, bool $expectedResult): void { - $classReflection = $this->broker->getClass($className); + $classReflection = $this->reflectionProvider->getClass($className); self::assertSame($expectedResult, $this->extension->hasMethod($classReflection, $method)); } public function testGetMethod(): void { - $classReflection = $this->broker->getClass(Collection::class); + $classReflection = $this->reflectionProvider->getClass(Collection::class); $methodReflection = $this->extension->getMethod($classReflection, 'matching'); self::assertSame('matching', $methodReflection->getName()); } From 8c22f2499444897941f9162c25f8538217c16f15 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Wed, 15 Mar 2023 15:56:00 +0100 Subject: [PATCH 05/12] Bleeding edge - BetterReflectionProvider Use PHPStan's static reflection instead of runtime reflection. --- extension.neon | 1 + phpcs.xml | 1 + phpstan-baseline.neon | 10 +++ .../Mapping/BetterReflectionService.php | 79 +++++++++++++++++++ src/Doctrine/Mapping/ClassMetadataFactory.php | 30 +++++++ src/Type/Doctrine/ObjectMetadataResolver.php | 15 +++- tests/Rules/Doctrine/ORM/DqlRuleTest.php | 2 +- .../Doctrine/ORM/EntityColumnRuleTest.php | 2 +- .../ORM/EntityMappingExceptionRuleTest.php | 2 +- .../Doctrine/ORM/EntityNotFinalRuleTest.php | 2 +- .../Doctrine/ORM/EntityRelationRuleTest.php | 2 +- .../ORM/QueryBuilderDqlRuleSlowTest.php | 2 +- .../Doctrine/ORM/QueryBuilderDqlRuleTest.php | 2 +- .../ORM/RepositoryMethodCallRuleTest.php | 2 +- ...CallRuleWithoutObjectManagerLoaderTest.php | 2 +- ...ingGedmoByPhpDocPropertyAssignRuleTest.php | 2 +- .../MissingGedmoPropertyAssignRuleTest.php | 2 +- 17 files changed, 145 insertions(+), 13 deletions(-) create mode 100644 src/Doctrine/Mapping/BetterReflectionService.php diff --git a/extension.neon b/extension.neon index 4851a816..eb8f6163 100644 --- a/extension.neon +++ b/extension.neon @@ -96,6 +96,7 @@ services: class: PHPStan\Type\Doctrine\ObjectMetadataResolver arguments: objectManagerLoader: %doctrine.objectManagerLoader% + bleedingEdge: %featureToggles.bleedingEdge% - class: PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderGetDqlDynamicReturnTypeExtension arguments: diff --git a/phpcs.xml b/phpcs.xml index 40fe4382..6f3a85c0 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -33,6 +33,7 @@ + diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 53397101..27aa7f91 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,5 +1,15 @@ parameters: ignoreErrors: + - + message: "#^Method PHPStan\\\\Doctrine\\\\Mapping\\\\BetterReflectionService\\:\\:getParentClasses\\(\\) should return array\\ but returns array\\\\.$#" + count: 1 + path: src/Doctrine/Mapping/BetterReflectionService.php + + - + message: "#^PHPDoc tag @var with type ReflectionClass\\ is not subtype of type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionClass\\|PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" + count: 1 + path: src/Doctrine/Mapping/BetterReflectionService.php + - message: "#^Calling PHPStan\\\\Type\\\\ParserNodeTypeToPHPStanType\\:\\:resolve\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#" count: 1 diff --git a/src/Doctrine/Mapping/BetterReflectionService.php b/src/Doctrine/Mapping/BetterReflectionService.php new file mode 100644 index 00000000..b3c3471e --- /dev/null +++ b/src/Doctrine/Mapping/BetterReflectionService.php @@ -0,0 +1,79 @@ +reflectionProvider = $reflectionProvider; + } + + public function getParentClasses($class) + { + if (!$this->reflectionProvider->hasClass($class)) { + throw MappingException::nonExistingClass($class); + } + + $classReflection = $this->reflectionProvider->getClass($class); + + return $classReflection->getParentClassesNames(); + } + + public function getClassShortName($class) + { + return $this->getClass($class)->getShortName(); + } + + public function getClassNamespace($class) + { + return $this->getClass($class)->getNamespaceName(); + } + + /** + * @param class-string $class + * @return ReflectionClass + * + * @template T of object + */ + public function getClass($class) + { + if (!$this->reflectionProvider->hasClass($class)) { + throw MappingException::nonExistingClass($class); + } + + $classReflection = $this->reflectionProvider->getClass($class); + + /** @var ReflectionClass */ + return $classReflection->getNativeReflection(); + } + + public function getAccessibleProperty($class, $property) + { + $classReflection = $this->getClass($class); + $property = $classReflection->getProperty($property); + $property->setAccessible(true); + + return $property; + } + + public function hasPublicMethod($class, $method) + { + $classReflection = $this->getClass($class); + if (!$classReflection->hasMethod($method)) { + return false; + } + + return $classReflection->getMethod($method)->isPublic(); + } + +} diff --git a/src/Doctrine/Mapping/ClassMetadataFactory.php b/src/Doctrine/Mapping/ClassMetadataFactory.php index 3f3aa3ee..7bba82aa 100644 --- a/src/Doctrine/Mapping/ClassMetadataFactory.php +++ b/src/Doctrine/Mapping/ClassMetadataFactory.php @@ -9,6 +9,8 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\Mapping\Driver\AttributeDriver; +use Doctrine\Persistence\Mapping\ReflectionService; +use PHPStan\Reflection\ReflectionProvider; use ReflectionClass; use function class_exists; use function count; @@ -17,6 +19,21 @@ class ClassMetadataFactory extends \Doctrine\ORM\Mapping\ClassMetadataFactory { + /** @var ReflectionProvider */ + private $reflectionProvider; + + /** @var bool */ + private $bleedingEdge; + + /** @var BetterReflectionService|null */ + private $reflectionService = null; + + public function __construct(ReflectionProvider $reflectionProvider, bool $bleedingEdge) + { + $this->reflectionProvider = $reflectionProvider; + $this->bleedingEdge = $bleedingEdge; + } + protected function initialize(): void { $parentReflection = new ReflectionClass(parent::class); @@ -52,6 +69,19 @@ protected function initialize(): void $targetPlatformProperty->setValue($this, $platform); } + public function getReflectionService(): ReflectionService + { + if (!$this->bleedingEdge) { + return parent::getReflectionService(); + } + + if ($this->reflectionService === null) { + $this->reflectionService = new BetterReflectionService($this->reflectionProvider); + } + + return $this->reflectionService; + } + /** * @template T of object * @param class-string $className diff --git a/src/Type/Doctrine/ObjectMetadataResolver.php b/src/Type/Doctrine/ObjectMetadataResolver.php index 962b2897..2a15364b 100644 --- a/src/Type/Doctrine/ObjectMetadataResolver.php +++ b/src/Type/Doctrine/ObjectMetadataResolver.php @@ -7,6 +7,7 @@ use Doctrine\ORM\Mapping\MappingException; use Doctrine\Persistence\ObjectManager; use PHPStan\Doctrine\Mapping\ClassMetadataFactory; +use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; use ReflectionException; use function class_exists; @@ -17,9 +18,15 @@ final class ObjectMetadataResolver { + /** @var ReflectionProvider */ + private $reflectionProvider; + /** @var string|null */ private $objectManagerLoader; + /** @var bool */ + private $bleedingEdge; + /** @var ObjectManager|false|null */ private $objectManager; @@ -27,10 +34,14 @@ final class ObjectMetadataResolver private $metadataFactory; public function __construct( - ?string $objectManagerLoader + ReflectionProvider $reflectionProvider, + ?string $objectManagerLoader, + bool $bleedingEdge ) { + $this->reflectionProvider = $reflectionProvider; $this->objectManagerLoader = $objectManagerLoader; + $this->bleedingEdge = $bleedingEdge; } public function hasObjectManagerLoader(): bool @@ -97,7 +108,7 @@ private function getMetadataFactory(): ?ClassMetadataFactory return null; } - return $this->metadataFactory = new ClassMetadataFactory(); + return $this->metadataFactory = new ClassMetadataFactory($this->reflectionProvider, $this->bleedingEdge); } /** diff --git a/tests/Rules/Doctrine/ORM/DqlRuleTest.php b/tests/Rules/Doctrine/ORM/DqlRuleTest.php index df7579b2..f263afb4 100644 --- a/tests/Rules/Doctrine/ORM/DqlRuleTest.php +++ b/tests/Rules/Doctrine/ORM/DqlRuleTest.php @@ -14,7 +14,7 @@ class DqlRuleTest extends RuleTestCase protected function getRule(): Rule { - return new DqlRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')); + return new DqlRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)); } public function testRule(): void diff --git a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php index f68e3f2f..f5283df3 100644 --- a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php @@ -56,7 +56,7 @@ protected function getRule(): Rule } return new EntityColumnRule( - new ObjectMetadataResolver($this->objectManagerLoader), + new ObjectMetadataResolver($this->createReflectionProvider(), $this->objectManagerLoader, true), new DescriptorRegistry([ new ArrayType(), new BigIntType(), diff --git a/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php b/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php index dc125c6a..e11aa708 100644 --- a/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php @@ -16,7 +16,7 @@ class EntityMappingExceptionRuleTest extends RuleTestCase protected function getRule(): Rule { return new EntityMappingExceptionRule( - new ObjectMetadataResolver(__DIR__ . '/entity-manager.php') + new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true) ); } diff --git a/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php b/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php index d4ffb0fd..32978094 100644 --- a/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php @@ -19,7 +19,7 @@ class EntityNotFinalRuleTest extends RuleTestCase protected function getRule(): Rule { return new EntityNotFinalRule( - new ObjectMetadataResolver($this->objectManagerLoader) + new ObjectMetadataResolver($this->createReflectionProvider(), $this->objectManagerLoader, true) ); } diff --git a/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php b/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php index ec089d73..8c3bfb7e 100644 --- a/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php @@ -23,7 +23,7 @@ class EntityRelationRuleTest extends RuleTestCase protected function getRule(): Rule { return new EntityRelationRule( - new ObjectMetadataResolver($this->objectManagerLoader), + new ObjectMetadataResolver($this->createReflectionProvider(), $this->objectManagerLoader, true), $this->allowNullablePropertyForRequiredField, true ); diff --git a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php index 132a028f..16894967 100644 --- a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php +++ b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php @@ -14,7 +14,7 @@ class QueryBuilderDqlRuleSlowTest extends RuleTestCase protected function getRule(): Rule { - return new QueryBuilderDqlRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php'), true); + return new QueryBuilderDqlRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true), true); } public function testRule(): void diff --git a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php index 3285b491..24e99fac 100644 --- a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php +++ b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php @@ -14,7 +14,7 @@ class QueryBuilderDqlRuleTest extends RuleTestCase protected function getRule(): Rule { - return new QueryBuilderDqlRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php'), true); + return new QueryBuilderDqlRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true), true); } public function testRule(): void diff --git a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php index 8edcd878..6578be2f 100644 --- a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php +++ b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php @@ -14,7 +14,7 @@ class RepositoryMethodCallRuleTest extends RuleTestCase protected function getRule(): Rule { - return new RepositoryMethodCallRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')); + return new RepositoryMethodCallRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)); } /** diff --git a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php index b63b361c..71f140a0 100644 --- a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php +++ b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php @@ -14,7 +14,7 @@ class RepositoryMethodCallRuleWithoutObjectManagerLoaderTest extends RuleTestCas protected function getRule(): Rule { - return new RepositoryMethodCallRule(new ObjectMetadataResolver(null)); + return new RepositoryMethodCallRule(new ObjectMetadataResolver($this->createReflectionProvider(), null, true)); } /** diff --git a/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php b/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php index d4b8e0da..e28da2dd 100644 --- a/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php +++ b/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php @@ -23,7 +23,7 @@ protected function getRule(): Rule protected function getReadWritePropertiesExtensions(): array { return [ - new PropertiesExtension(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')), + new PropertiesExtension(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)), ]; } diff --git a/tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php b/tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php index f9dcda7d..12f151ad 100644 --- a/tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php +++ b/tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php @@ -24,7 +24,7 @@ protected function getRule(): Rule protected function getReadWritePropertiesExtensions(): array { return [ - new PropertiesExtension(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')), + new PropertiesExtension(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)), ]; } From 62bd362b432fe29e175168689510ddd927b698f8 Mon Sep 17 00:00:00 2001 From: Ondrej Mirtes Date: Fri, 17 Mar 2023 15:57:03 +0100 Subject: [PATCH 06/12] Revert "Bleeding edge - BetterReflectionProvider" This reverts commit 8c22f2499444897941f9162c25f8538217c16f15. --- extension.neon | 1 - phpcs.xml | 1 - phpstan-baseline.neon | 10 --- .../Mapping/BetterReflectionService.php | 79 ------------------- src/Doctrine/Mapping/ClassMetadataFactory.php | 30 ------- src/Type/Doctrine/ObjectMetadataResolver.php | 15 +--- tests/Rules/Doctrine/ORM/DqlRuleTest.php | 2 +- .../Doctrine/ORM/EntityColumnRuleTest.php | 2 +- .../ORM/EntityMappingExceptionRuleTest.php | 2 +- .../Doctrine/ORM/EntityNotFinalRuleTest.php | 2 +- .../Doctrine/ORM/EntityRelationRuleTest.php | 2 +- .../ORM/QueryBuilderDqlRuleSlowTest.php | 2 +- .../Doctrine/ORM/QueryBuilderDqlRuleTest.php | 2 +- .../ORM/RepositoryMethodCallRuleTest.php | 2 +- ...CallRuleWithoutObjectManagerLoaderTest.php | 2 +- ...ingGedmoByPhpDocPropertyAssignRuleTest.php | 2 +- .../MissingGedmoPropertyAssignRuleTest.php | 2 +- 17 files changed, 13 insertions(+), 145 deletions(-) delete mode 100644 src/Doctrine/Mapping/BetterReflectionService.php diff --git a/extension.neon b/extension.neon index eb8f6163..4851a816 100644 --- a/extension.neon +++ b/extension.neon @@ -96,7 +96,6 @@ services: class: PHPStan\Type\Doctrine\ObjectMetadataResolver arguments: objectManagerLoader: %doctrine.objectManagerLoader% - bleedingEdge: %featureToggles.bleedingEdge% - class: PHPStan\Type\Doctrine\QueryBuilder\QueryBuilderGetDqlDynamicReturnTypeExtension arguments: diff --git a/phpcs.xml b/phpcs.xml index 6f3a85c0..40fe4382 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -33,7 +33,6 @@ - diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 27aa7f91..53397101 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -1,15 +1,5 @@ parameters: ignoreErrors: - - - message: "#^Method PHPStan\\\\Doctrine\\\\Mapping\\\\BetterReflectionService\\:\\:getParentClasses\\(\\) should return array\\ but returns array\\\\.$#" - count: 1 - path: src/Doctrine/Mapping/BetterReflectionService.php - - - - message: "#^PHPDoc tag @var with type ReflectionClass\\ is not subtype of type PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionClass\\|PHPStan\\\\BetterReflection\\\\Reflection\\\\Adapter\\\\ReflectionEnum\\.$#" - count: 1 - path: src/Doctrine/Mapping/BetterReflectionService.php - - message: "#^Calling PHPStan\\\\Type\\\\ParserNodeTypeToPHPStanType\\:\\:resolve\\(\\) is not covered by backward compatibility promise\\. The method might change in a minor PHPStan version\\.$#" count: 1 diff --git a/src/Doctrine/Mapping/BetterReflectionService.php b/src/Doctrine/Mapping/BetterReflectionService.php deleted file mode 100644 index b3c3471e..00000000 --- a/src/Doctrine/Mapping/BetterReflectionService.php +++ /dev/null @@ -1,79 +0,0 @@ -reflectionProvider = $reflectionProvider; - } - - public function getParentClasses($class) - { - if (!$this->reflectionProvider->hasClass($class)) { - throw MappingException::nonExistingClass($class); - } - - $classReflection = $this->reflectionProvider->getClass($class); - - return $classReflection->getParentClassesNames(); - } - - public function getClassShortName($class) - { - return $this->getClass($class)->getShortName(); - } - - public function getClassNamespace($class) - { - return $this->getClass($class)->getNamespaceName(); - } - - /** - * @param class-string $class - * @return ReflectionClass - * - * @template T of object - */ - public function getClass($class) - { - if (!$this->reflectionProvider->hasClass($class)) { - throw MappingException::nonExistingClass($class); - } - - $classReflection = $this->reflectionProvider->getClass($class); - - /** @var ReflectionClass */ - return $classReflection->getNativeReflection(); - } - - public function getAccessibleProperty($class, $property) - { - $classReflection = $this->getClass($class); - $property = $classReflection->getProperty($property); - $property->setAccessible(true); - - return $property; - } - - public function hasPublicMethod($class, $method) - { - $classReflection = $this->getClass($class); - if (!$classReflection->hasMethod($method)) { - return false; - } - - return $classReflection->getMethod($method)->isPublic(); - } - -} diff --git a/src/Doctrine/Mapping/ClassMetadataFactory.php b/src/Doctrine/Mapping/ClassMetadataFactory.php index 7bba82aa..3f3aa3ee 100644 --- a/src/Doctrine/Mapping/ClassMetadataFactory.php +++ b/src/Doctrine/Mapping/ClassMetadataFactory.php @@ -9,8 +9,6 @@ use Doctrine\ORM\Mapping\ClassMetadata; use Doctrine\ORM\Mapping\Driver\AnnotationDriver; use Doctrine\ORM\Mapping\Driver\AttributeDriver; -use Doctrine\Persistence\Mapping\ReflectionService; -use PHPStan\Reflection\ReflectionProvider; use ReflectionClass; use function class_exists; use function count; @@ -19,21 +17,6 @@ class ClassMetadataFactory extends \Doctrine\ORM\Mapping\ClassMetadataFactory { - /** @var ReflectionProvider */ - private $reflectionProvider; - - /** @var bool */ - private $bleedingEdge; - - /** @var BetterReflectionService|null */ - private $reflectionService = null; - - public function __construct(ReflectionProvider $reflectionProvider, bool $bleedingEdge) - { - $this->reflectionProvider = $reflectionProvider; - $this->bleedingEdge = $bleedingEdge; - } - protected function initialize(): void { $parentReflection = new ReflectionClass(parent::class); @@ -69,19 +52,6 @@ protected function initialize(): void $targetPlatformProperty->setValue($this, $platform); } - public function getReflectionService(): ReflectionService - { - if (!$this->bleedingEdge) { - return parent::getReflectionService(); - } - - if ($this->reflectionService === null) { - $this->reflectionService = new BetterReflectionService($this->reflectionProvider); - } - - return $this->reflectionService; - } - /** * @template T of object * @param class-string $className diff --git a/src/Type/Doctrine/ObjectMetadataResolver.php b/src/Type/Doctrine/ObjectMetadataResolver.php index 2a15364b..962b2897 100644 --- a/src/Type/Doctrine/ObjectMetadataResolver.php +++ b/src/Type/Doctrine/ObjectMetadataResolver.php @@ -7,7 +7,6 @@ use Doctrine\ORM\Mapping\MappingException; use Doctrine\Persistence\ObjectManager; use PHPStan\Doctrine\Mapping\ClassMetadataFactory; -use PHPStan\Reflection\ReflectionProvider; use PHPStan\ShouldNotHappenException; use ReflectionException; use function class_exists; @@ -18,15 +17,9 @@ final class ObjectMetadataResolver { - /** @var ReflectionProvider */ - private $reflectionProvider; - /** @var string|null */ private $objectManagerLoader; - /** @var bool */ - private $bleedingEdge; - /** @var ObjectManager|false|null */ private $objectManager; @@ -34,14 +27,10 @@ final class ObjectMetadataResolver private $metadataFactory; public function __construct( - ReflectionProvider $reflectionProvider, - ?string $objectManagerLoader, - bool $bleedingEdge + ?string $objectManagerLoader ) { - $this->reflectionProvider = $reflectionProvider; $this->objectManagerLoader = $objectManagerLoader; - $this->bleedingEdge = $bleedingEdge; } public function hasObjectManagerLoader(): bool @@ -108,7 +97,7 @@ private function getMetadataFactory(): ?ClassMetadataFactory return null; } - return $this->metadataFactory = new ClassMetadataFactory($this->reflectionProvider, $this->bleedingEdge); + return $this->metadataFactory = new ClassMetadataFactory(); } /** diff --git a/tests/Rules/Doctrine/ORM/DqlRuleTest.php b/tests/Rules/Doctrine/ORM/DqlRuleTest.php index f263afb4..df7579b2 100644 --- a/tests/Rules/Doctrine/ORM/DqlRuleTest.php +++ b/tests/Rules/Doctrine/ORM/DqlRuleTest.php @@ -14,7 +14,7 @@ class DqlRuleTest extends RuleTestCase protected function getRule(): Rule { - return new DqlRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)); + return new DqlRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')); } public function testRule(): void diff --git a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php index f5283df3..f68e3f2f 100644 --- a/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityColumnRuleTest.php @@ -56,7 +56,7 @@ protected function getRule(): Rule } return new EntityColumnRule( - new ObjectMetadataResolver($this->createReflectionProvider(), $this->objectManagerLoader, true), + new ObjectMetadataResolver($this->objectManagerLoader), new DescriptorRegistry([ new ArrayType(), new BigIntType(), diff --git a/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php b/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php index e11aa708..dc125c6a 100644 --- a/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityMappingExceptionRuleTest.php @@ -16,7 +16,7 @@ class EntityMappingExceptionRuleTest extends RuleTestCase protected function getRule(): Rule { return new EntityMappingExceptionRule( - new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true) + new ObjectMetadataResolver(__DIR__ . '/entity-manager.php') ); } diff --git a/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php b/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php index 32978094..d4ffb0fd 100644 --- a/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityNotFinalRuleTest.php @@ -19,7 +19,7 @@ class EntityNotFinalRuleTest extends RuleTestCase protected function getRule(): Rule { return new EntityNotFinalRule( - new ObjectMetadataResolver($this->createReflectionProvider(), $this->objectManagerLoader, true) + new ObjectMetadataResolver($this->objectManagerLoader) ); } diff --git a/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php b/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php index 8c3bfb7e..ec089d73 100644 --- a/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php +++ b/tests/Rules/Doctrine/ORM/EntityRelationRuleTest.php @@ -23,7 +23,7 @@ class EntityRelationRuleTest extends RuleTestCase protected function getRule(): Rule { return new EntityRelationRule( - new ObjectMetadataResolver($this->createReflectionProvider(), $this->objectManagerLoader, true), + new ObjectMetadataResolver($this->objectManagerLoader), $this->allowNullablePropertyForRequiredField, true ); diff --git a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php index 16894967..132a028f 100644 --- a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php +++ b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleSlowTest.php @@ -14,7 +14,7 @@ class QueryBuilderDqlRuleSlowTest extends RuleTestCase protected function getRule(): Rule { - return new QueryBuilderDqlRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true), true); + return new QueryBuilderDqlRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php'), true); } public function testRule(): void diff --git a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php index 24e99fac..3285b491 100644 --- a/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php +++ b/tests/Rules/Doctrine/ORM/QueryBuilderDqlRuleTest.php @@ -14,7 +14,7 @@ class QueryBuilderDqlRuleTest extends RuleTestCase protected function getRule(): Rule { - return new QueryBuilderDqlRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true), true); + return new QueryBuilderDqlRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php'), true); } public function testRule(): void diff --git a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php index 6578be2f..8edcd878 100644 --- a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php +++ b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleTest.php @@ -14,7 +14,7 @@ class RepositoryMethodCallRuleTest extends RuleTestCase protected function getRule(): Rule { - return new RepositoryMethodCallRule(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)); + return new RepositoryMethodCallRule(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')); } /** diff --git a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php index 71f140a0..b63b361c 100644 --- a/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php +++ b/tests/Rules/Doctrine/ORM/RepositoryMethodCallRuleWithoutObjectManagerLoaderTest.php @@ -14,7 +14,7 @@ class RepositoryMethodCallRuleWithoutObjectManagerLoaderTest extends RuleTestCas protected function getRule(): Rule { - return new RepositoryMethodCallRule(new ObjectMetadataResolver($this->createReflectionProvider(), null, true)); + return new RepositoryMethodCallRule(new ObjectMetadataResolver(null)); } /** diff --git a/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php b/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php index e28da2dd..d4b8e0da 100644 --- a/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php +++ b/tests/Rules/Properties/MissingGedmoByPhpDocPropertyAssignRuleTest.php @@ -23,7 +23,7 @@ protected function getRule(): Rule protected function getReadWritePropertiesExtensions(): array { return [ - new PropertiesExtension(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)), + new PropertiesExtension(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')), ]; } diff --git a/tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php b/tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php index 12f151ad..f9dcda7d 100644 --- a/tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php +++ b/tests/Rules/Properties/MissingGedmoPropertyAssignRuleTest.php @@ -24,7 +24,7 @@ protected function getRule(): Rule protected function getReadWritePropertiesExtensions(): array { return [ - new PropertiesExtension(new ObjectMetadataResolver($this->createReflectionProvider(), __DIR__ . '/entity-manager.php', true)), + new PropertiesExtension(new ObjectMetadataResolver(__DIR__ . '/entity-manager.php')), ]; } From fbf55c3209184a239ef699708372dd16f7600d3a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 20 Mar 2023 01:03:43 +0000 Subject: [PATCH 07/12] Update metcalfc/changelog-generator action to v4.1.0 --- .github/workflows/release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bac4a006..92b72547 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,7 +18,7 @@ jobs: - name: Generate changelog id: changelog - uses: metcalfc/changelog-generator@v4.0.1 + uses: metcalfc/changelog-generator@v4.1.0 with: myToken: ${{ secrets.PHPSTAN_BOT_TOKEN }} From 41c5663433e81f4f1fc18bdd40991d3aac7b23e3 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Wed, 22 Mar 2023 09:19:52 +0100 Subject: [PATCH 08/12] Update PHPCS --- build-cs/composer.json | 8 +-- build-cs/composer.lock | 117 ++++++++++++++++++++++------------------- 2 files changed, 67 insertions(+), 58 deletions(-) diff --git a/build-cs/composer.json b/build-cs/composer.json index cd1d3b01..16a240bc 100644 --- a/build-cs/composer.json +++ b/build-cs/composer.json @@ -1,9 +1,9 @@ { "require-dev": { - "consistence-community/coding-standard": "^3.11", - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.0", - "slevomat/coding-standard": "^7.0", - "squizlabs/php_codesniffer": "^3.6.2" + "consistence-community/coding-standard": "^3.11.0", + "dealerdirect/phpcodesniffer-composer-installer": "^1.0.0", + "slevomat/coding-standard": "^8.8.0", + "squizlabs/php_codesniffer": "^3.5.3" }, "config": { "allow-plugins": { diff --git a/build-cs/composer.lock b/build-cs/composer.lock index bc202eab..c25a151a 100644 --- a/build-cs/composer.lock +++ b/build-cs/composer.lock @@ -4,35 +4,35 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "82a2e1142c0fe8456bd29fcf5266a239", + "content-hash": "e69c1916405a7e3c8001c1b609a0ee61", "packages": [], "packages-dev": [ { "name": "consistence-community/coding-standard", - "version": "3.11.1", + "version": "3.11.2", "source": { "type": "git", "url": "https://github.com/consistence-community/coding-standard.git", - "reference": "4632fead8c9ee8f50044fcbce9f66c797b34c0df" + "reference": "adb4be482e76990552bf624309d2acc8754ba1bd" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consistence-community/coding-standard/zipball/4632fead8c9ee8f50044fcbce9f66c797b34c0df", - "reference": "4632fead8c9ee8f50044fcbce9f66c797b34c0df", + "url": "https://api.github.com/repos/consistence-community/coding-standard/zipball/adb4be482e76990552bf624309d2acc8754ba1bd", + "reference": "adb4be482e76990552bf624309d2acc8754ba1bd", "shasum": "" }, "require": { - "php": ">=7.4", - "slevomat/coding-standard": "~7.0", - "squizlabs/php_codesniffer": "~3.6.0" + "php": "~8.0", + "slevomat/coding-standard": "~8.0", + "squizlabs/php_codesniffer": "~3.7.0" }, "replace": { "consistence/coding-standard": "3.10.*" }, "require-dev": { - "phing/phing": "2.16.4", - "php-parallel-lint/php-parallel-lint": "1.3.0", - "phpunit/phpunit": "9.5.4" + "phing/phing": "2.17.0", + "php-parallel-lint/php-parallel-lint": "1.3.1", + "phpunit/phpunit": "9.5.10" }, "type": "library", "autoload": { @@ -70,41 +70,44 @@ ], "support": { "issues": "https://github.com/consistence-community/coding-standard/issues", - "source": "https://github.com/consistence-community/coding-standard/tree/3.11.1" + "source": "https://github.com/consistence-community/coding-standard/tree/3.11.2" }, - "time": "2021-05-03T18:13:22+00:00" + "time": "2022-06-21T08:36:36+00:00" }, { "name": "dealerdirect/phpcodesniffer-composer-installer", - "version": "v0.7.2", + "version": "v1.0.0", "source": { "type": "git", - "url": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer.git", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db" + "url": "https://github.com/PHPCSStandards/composer-installer.git", + "reference": "4be43904336affa5c2f70744a348312336afd0da" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/Dealerdirect/phpcodesniffer-composer-installer/zipball/1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", - "reference": "1c968e542d8843d7cd71de3c5c9c3ff3ad71a1db", + "url": "https://api.github.com/repos/PHPCSStandards/composer-installer/zipball/4be43904336affa5c2f70744a348312336afd0da", + "reference": "4be43904336affa5c2f70744a348312336afd0da", "shasum": "" }, "require": { "composer-plugin-api": "^1.0 || ^2.0", - "php": ">=5.3", + "php": ">=5.4", "squizlabs/php_codesniffer": "^2.0 || ^3.1.0 || ^4.0" }, "require-dev": { "composer/composer": "*", + "ext-json": "*", + "ext-zip": "*", "php-parallel-lint/php-parallel-lint": "^1.3.1", - "phpcompatibility/php-compatibility": "^9.0" + "phpcompatibility/php-compatibility": "^9.0", + "yoast/phpunit-polyfills": "^1.0" }, "type": "composer-plugin", "extra": { - "class": "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" + "class": "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\Plugin" }, "autoload": { "psr-4": { - "Dealerdirect\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" + "PHPCSStandards\\Composer\\Plugin\\Installers\\PHPCodeSniffer\\": "src/" } }, "notification-url": "https://packagist.org/downloads/", @@ -120,7 +123,7 @@ }, { "name": "Contributors", - "homepage": "https://github.com/Dealerdirect/phpcodesniffer-composer-installer/graphs/contributors" + "homepage": "https://github.com/PHPCSStandards/composer-installer/graphs/contributors" } ], "description": "PHP_CodeSniffer Standards Composer Installer Plugin", @@ -144,23 +147,23 @@ "tests" ], "support": { - "issues": "https://github.com/dealerdirect/phpcodesniffer-composer-installer/issues", - "source": "https://github.com/dealerdirect/phpcodesniffer-composer-installer" + "issues": "https://github.com/PHPCSStandards/composer-installer/issues", + "source": "https://github.com/PHPCSStandards/composer-installer" }, - "time": "2022-02-04T12:51:07+00:00" + "time": "2023-01-05T11:28:13+00:00" }, { "name": "phpstan/phpdoc-parser", - "version": "1.5.1", + "version": "1.15.3", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "981cc368a216c988e862a75e526b6076987d1b50" + "reference": "61800f71a5526081d1b5633766aa88341f1ade76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/981cc368a216c988e862a75e526b6076987d1b50", - "reference": "981cc368a216c988e862a75e526b6076987d1b50", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/61800f71a5526081d1b5633766aa88341f1ade76", + "reference": "61800f71a5526081d1b5633766aa88341f1ade76", "shasum": "" }, "require": { @@ -170,6 +173,7 @@ "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", "phpstan/phpstan": "^1.5", + "phpstan/phpstan-phpunit": "^1.1", "phpstan/phpstan-strict-rules": "^1.0", "phpunit/phpunit": "^9.5", "symfony/process": "^5.2" @@ -189,43 +193,43 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.5.1" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.15.3" }, - "time": "2022-05-05T11:32:40+00:00" + "time": "2022-12-20T20:56:55+00:00" }, { "name": "slevomat/coding-standard", - "version": "7.2.1", + "version": "8.8.0", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "aff06ae7a84e4534bf6f821dc982a93a5d477c90" + "reference": "59e25146a4ef0a7b194c5bc55b32dd414345db89" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/aff06ae7a84e4534bf6f821dc982a93a5d477c90", - "reference": "aff06ae7a84e4534bf6f821dc982a93a5d477c90", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/59e25146a4ef0a7b194c5bc55b32dd414345db89", + "reference": "59e25146a4ef0a7b194c5bc55b32dd414345db89", "shasum": "" }, "require": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7", + "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": "^1.5.1", - "squizlabs/php_codesniffer": "^3.6.2" + "phpstan/phpdoc-parser": ">=1.15.2 <1.16.0", + "squizlabs/php_codesniffer": "^3.7.1" }, "require-dev": { - "phing/phing": "2.17.3", + "phing/phing": "2.17.4", "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.4.10|1.7.1", - "phpstan/phpstan-deprecation-rules": "1.0.0", - "phpstan/phpstan-phpunit": "1.0.0|1.1.1", - "phpstan/phpstan-strict-rules": "1.2.3", - "phpunit/phpunit": "7.5.20|8.5.21|9.5.20" + "phpstan/phpstan": "1.4.10|1.9.6", + "phpstan/phpstan-deprecation-rules": "1.1.1", + "phpstan/phpstan-phpunit": "1.0.0|1.3.3", + "phpstan/phpstan-strict-rules": "1.4.4", + "phpunit/phpunit": "7.5.20|8.5.21|9.5.27" }, "type": "phpcodesniffer-standard", "extra": { "branch-alias": { - "dev-master": "7.x-dev" + "dev-master": "8.x-dev" } }, "autoload": { @@ -238,9 +242,13 @@ "MIT" ], "description": "Slevomat Coding Standard for PHP_CodeSniffer complements Consistence Coding Standard by providing sniffs with additional checks.", + "keywords": [ + "dev", + "phpcs" + ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/7.2.1" + "source": "https://github.com/slevomat/coding-standard/tree/8.8.0" }, "funding": [ { @@ -252,20 +260,20 @@ "type": "tidelift" } ], - "time": "2022-05-25T10:58:12+00:00" + "time": "2023-01-09T10:46:13+00:00" }, { "name": "squizlabs/php_codesniffer", - "version": "3.6.2", + "version": "3.7.2", "source": { "type": "git", "url": "https://github.com/squizlabs/PHP_CodeSniffer.git", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a" + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/5e4e71592f69da17871dba6e80dd51bce74a351a", - "reference": "5e4e71592f69da17871dba6e80dd51bce74a351a", + "url": "https://api.github.com/repos/squizlabs/PHP_CodeSniffer/zipball/ed8e00df0a83aa96acf703f8c2979ff33341f879", + "reference": "ed8e00df0a83aa96acf703f8c2979ff33341f879", "shasum": "" }, "require": { @@ -301,14 +309,15 @@ "homepage": "https://github.com/squizlabs/PHP_CodeSniffer", "keywords": [ "phpcs", - "standards" + "standards", + "static analysis" ], "support": { "issues": "https://github.com/squizlabs/PHP_CodeSniffer/issues", "source": "https://github.com/squizlabs/PHP_CodeSniffer", "wiki": "https://github.com/squizlabs/PHP_CodeSniffer/wiki" }, - "time": "2021-12-12T21:44:58+00:00" + "time": "2023-02-22T23:07:41+00:00" } ], "aliases": [], From 13c649f24686d13d04cde91b15e614607906b693 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 27 Mar 2023 01:27:38 +0000 Subject: [PATCH 09/12] Update dependency slevomat/coding-standard to v8.9.0 --- build-cs/composer.lock | 38 +++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/build-cs/composer.lock b/build-cs/composer.lock index c25a151a..b52da6f8 100644 --- a/build-cs/composer.lock +++ b/build-cs/composer.lock @@ -154,16 +154,16 @@ }, { "name": "phpstan/phpdoc-parser", - "version": "1.15.3", + "version": "1.16.1", "source": { "type": "git", "url": "https://github.com/phpstan/phpdoc-parser.git", - "reference": "61800f71a5526081d1b5633766aa88341f1ade76" + "reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/61800f71a5526081d1b5633766aa88341f1ade76", - "reference": "61800f71a5526081d1b5633766aa88341f1ade76", + "url": "https://api.github.com/repos/phpstan/phpdoc-parser/zipball/e27e92d939e2e3636f0a1f0afaba59692c0bf571", + "reference": "e27e92d939e2e3636f0a1f0afaba59692c0bf571", "shasum": "" }, "require": { @@ -193,38 +193,38 @@ "description": "PHPDoc parser with support for nullable, intersection and generic types", "support": { "issues": "https://github.com/phpstan/phpdoc-parser/issues", - "source": "https://github.com/phpstan/phpdoc-parser/tree/1.15.3" + "source": "https://github.com/phpstan/phpdoc-parser/tree/1.16.1" }, - "time": "2022-12-20T20:56:55+00:00" + "time": "2023-02-07T18:11:17+00:00" }, { "name": "slevomat/coding-standard", - "version": "8.8.0", + "version": "8.9.0", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "59e25146a4ef0a7b194c5bc55b32dd414345db89" + "reference": "8f11e0f5ff984d6862bb9d83aa513dc05a1773ef" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/59e25146a4ef0a7b194c5bc55b32dd414345db89", - "reference": "59e25146a4ef0a7b194c5bc55b32dd414345db89", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/8f11e0f5ff984d6862bb9d83aa513dc05a1773ef", + "reference": "8f11e0f5ff984d6862bb9d83aa513dc05a1773ef", "shasum": "" }, "require": { "dealerdirect/phpcodesniffer-composer-installer": "^0.6.2 || ^0.7 || ^1.0", "php": "^7.2 || ^8.0", - "phpstan/phpdoc-parser": ">=1.15.2 <1.16.0", + "phpstan/phpdoc-parser": ">=1.16.0 <1.17.0", "squizlabs/php_codesniffer": "^3.7.1" }, "require-dev": { "phing/phing": "2.17.4", "php-parallel-lint/php-parallel-lint": "1.3.2", - "phpstan/phpstan": "1.4.10|1.9.6", - "phpstan/phpstan-deprecation-rules": "1.1.1", - "phpstan/phpstan-phpunit": "1.0.0|1.3.3", - "phpstan/phpstan-strict-rules": "1.4.4", - "phpunit/phpunit": "7.5.20|8.5.21|9.5.27" + "phpstan/phpstan": "1.4.10|1.10.8", + "phpstan/phpstan-deprecation-rules": "1.1.3", + "phpstan/phpstan-phpunit": "1.0.0|1.3.10", + "phpstan/phpstan-strict-rules": "1.5.0", + "phpunit/phpunit": "7.5.20|8.5.21|9.6.5" }, "type": "phpcodesniffer-standard", "extra": { @@ -234,7 +234,7 @@ }, "autoload": { "psr-4": { - "SlevomatCodingStandard\\": "SlevomatCodingStandard" + "SlevomatCodingStandard\\": "SlevomatCodingStandard/" } }, "notification-url": "https://packagist.org/downloads/", @@ -248,7 +248,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.8.0" + "source": "https://github.com/slevomat/coding-standard/tree/8.9.0" }, "funding": [ { @@ -260,7 +260,7 @@ "type": "tidelift" } ], - "time": "2023-01-09T10:46:13+00:00" + "time": "2023-03-25T15:52:37+00:00" }, { "name": "squizlabs/php_codesniffer", From 11b14c4e250e371e88c095ee26e6390580313905 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 3 Apr 2023 00:20:01 +0000 Subject: [PATCH 10/12] Update build-cs --- build-cs/composer.lock | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/build-cs/composer.lock b/build-cs/composer.lock index b52da6f8..d8730d8a 100644 --- a/build-cs/composer.lock +++ b/build-cs/composer.lock @@ -9,16 +9,16 @@ "packages-dev": [ { "name": "consistence-community/coding-standard", - "version": "3.11.2", + "version": "3.11.3", "source": { "type": "git", "url": "https://github.com/consistence-community/coding-standard.git", - "reference": "adb4be482e76990552bf624309d2acc8754ba1bd" + "reference": "f38e06327d5bf80ff5ff523a2c05e623b5e8d8b1" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/consistence-community/coding-standard/zipball/adb4be482e76990552bf624309d2acc8754ba1bd", - "reference": "adb4be482e76990552bf624309d2acc8754ba1bd", + "url": "https://api.github.com/repos/consistence-community/coding-standard/zipball/f38e06327d5bf80ff5ff523a2c05e623b5e8d8b1", + "reference": "f38e06327d5bf80ff5ff523a2c05e623b5e8d8b1", "shasum": "" }, "require": { @@ -70,9 +70,9 @@ ], "support": { "issues": "https://github.com/consistence-community/coding-standard/issues", - "source": "https://github.com/consistence-community/coding-standard/tree/3.11.2" + "source": "https://github.com/consistence-community/coding-standard/tree/3.11.3" }, - "time": "2022-06-21T08:36:36+00:00" + "time": "2023-03-27T14:55:41+00:00" }, { "name": "dealerdirect/phpcodesniffer-composer-installer", @@ -199,16 +199,16 @@ }, { "name": "slevomat/coding-standard", - "version": "8.9.0", + "version": "8.9.1", "source": { "type": "git", "url": "https://github.com/slevomat/coding-standard.git", - "reference": "8f11e0f5ff984d6862bb9d83aa513dc05a1773ef" + "reference": "3d4fe0c803ae15829ef72d90d3d4eee3dd9f79b2" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/8f11e0f5ff984d6862bb9d83aa513dc05a1773ef", - "reference": "8f11e0f5ff984d6862bb9d83aa513dc05a1773ef", + "url": "https://api.github.com/repos/slevomat/coding-standard/zipball/3d4fe0c803ae15829ef72d90d3d4eee3dd9f79b2", + "reference": "3d4fe0c803ae15829ef72d90d3d4eee3dd9f79b2", "shasum": "" }, "require": { @@ -248,7 +248,7 @@ ], "support": { "issues": "https://github.com/slevomat/coding-standard/issues", - "source": "https://github.com/slevomat/coding-standard/tree/8.9.0" + "source": "https://github.com/slevomat/coding-standard/tree/8.9.1" }, "funding": [ { @@ -260,7 +260,7 @@ "type": "tidelift" } ], - "time": "2023-03-25T15:52:37+00:00" + "time": "2023-03-27T11:00:16+00:00" }, { "name": "squizlabs/php_codesniffer", From c546058a67c652c68425b824df58c67da6cb8f4b Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Mon, 26 Dec 2022 21:06:02 +0100 Subject: [PATCH 11/12] Improve QueryResultDynamicReturnTypeExtension --- .../QueryResultDynamicReturnTypeExtension.php | 168 +++++++- .../Doctrine/data/QueryResult/queryResult.php | 400 +++++++++++++++++- 2 files changed, 537 insertions(+), 31 deletions(-) diff --git a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php index 20e23831..a46f96be 100644 --- a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php @@ -10,14 +10,21 @@ use PHPStan\ShouldNotHappenException; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\ArrayType; +use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; +use PHPStan\Type\Doctrine\ObjectMetadataResolver; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\IntegerType; use PHPStan\Type\IterableType; +use PHPStan\Type\MixedType; use PHPStan\Type\NullType; +use PHPStan\Type\ObjectWithoutClassType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use PHPStan\Type\TypeTraverser; +use PHPStan\Type\TypeWithClassName; use PHPStan\Type\VoidType; +use function count; final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { @@ -32,6 +39,23 @@ final class QueryResultDynamicReturnTypeExtension implements DynamicMethodReturn 'getSingleResult' => 0, ]; + private const METHOD_HYDRATION_MODE = [ + 'getArrayResult' => AbstractQuery::HYDRATE_ARRAY, + 'getScalarResult' => AbstractQuery::HYDRATE_SCALAR, + 'getSingleColumnResult' => AbstractQuery::HYDRATE_SCALAR_COLUMN, + 'getSingleScalarResult' => AbstractQuery::HYDRATE_SINGLE_SCALAR, + ]; + + /** @var ObjectMetadataResolver */ + private $objectMetadataResolver; + + public function __construct( + ObjectMetadataResolver $objectMetadataResolver + ) + { + $this->objectMetadataResolver = $objectMetadataResolver; + } + public function getClass(): string { return AbstractQuery::class; @@ -39,7 +63,8 @@ public function getClass(): string public function isMethodSupported(MethodReflection $methodReflection): bool { - return isset(self::METHOD_HYDRATION_MODE_ARG[$methodReflection->getName()]); + return isset(self::METHOD_HYDRATION_MODE_ARG[$methodReflection->getName()]) + || isset(self::METHOD_HYDRATION_MODE[$methodReflection->getName()]); } public function getTypeFromMethodCall( @@ -50,21 +75,23 @@ public function getTypeFromMethodCall( { $methodName = $methodReflection->getName(); - if (!isset(self::METHOD_HYDRATION_MODE_ARG[$methodName])) { - throw new ShouldNotHappenException(); - } - - $argIndex = self::METHOD_HYDRATION_MODE_ARG[$methodName]; - $args = $methodCall->getArgs(); + if (isset(self::METHOD_HYDRATION_MODE[$methodName])) { + $hydrationMode = new ConstantIntegerType(self::METHOD_HYDRATION_MODE[$methodName]); + } elseif (isset(self::METHOD_HYDRATION_MODE_ARG[$methodName])) { + $argIndex = self::METHOD_HYDRATION_MODE_ARG[$methodName]; + $args = $methodCall->getArgs(); - if (isset($args[$argIndex])) { - $hydrationMode = $scope->getType($args[$argIndex]->value); + if (isset($args[$argIndex])) { + $hydrationMode = $scope->getType($args[$argIndex]->value); + } else { + $parametersAcceptor = ParametersAcceptorSelector::selectSingle( + $methodReflection->getVariants() + ); + $parameter = $parametersAcceptor->getParameters()[$argIndex]; + $hydrationMode = $parameter->getDefaultValue() ?? new NullType(); + } } else { - $parametersAcceptor = ParametersAcceptorSelector::selectSingle( - $methodReflection->getVariants() - ); - $parameter = $parametersAcceptor->getParameters()[$argIndex]; - $hydrationMode = $parameter->getDefaultValue() ?? new NullType(); + throw new ShouldNotHappenException(); } $queryType = $scope->getType($methodCall->var); @@ -98,12 +125,34 @@ private function getMethodReturnTypeForHydrationMode( return $this->originalReturnType($methodReflection); } - if (!$this->isObjectHydrationMode($hydrationMode)) { - // We support only HYDRATE_OBJECT. For other hydration modes, we - // return the declared return type of the method. + if (!$hydrationMode instanceof ConstantIntegerType) { return $this->originalReturnType($methodReflection); } + $singleResult = false; + switch ($hydrationMode->getValue()) { + case AbstractQuery::HYDRATE_OBJECT: + break; + case AbstractQuery::HYDRATE_ARRAY: + $queryResultType = $this->getArrayHydratedReturnType($queryResultType); + break; + case AbstractQuery::HYDRATE_SCALAR: + $queryResultType = $this->getScalarHydratedReturnType($queryResultType); + break; + case AbstractQuery::HYDRATE_SINGLE_SCALAR: + $singleResult = true; + $queryResultType = $this->getSingleScalarHydratedReturnType($queryResultType); + break; + case AbstractQuery::HYDRATE_SIMPLEOBJECT: + $queryResultType = $this->getSimpleObjectHydratedReturnType($queryResultType); + break; + case AbstractQuery::HYDRATE_SCALAR_COLUMN: + $queryResultType = $this->getScalarColumnHydratedReturnType($queryResultType); + break; + default: + return $this->originalReturnType($methodReflection); + } + switch ($methodReflection->getName()) { case 'getSingleResult': return $queryResultType; @@ -115,6 +164,10 @@ private function getMethodReturnTypeForHydrationMode( $queryResultType ); default: + if ($singleResult) { + return $queryResultType; + } + if ($queryKeyType->isNull()->yes()) { return AccessoryArrayListType::intersectWith(new ArrayType( new IntegerType(), @@ -128,13 +181,86 @@ private function getMethodReturnTypeForHydrationMode( } } - private function isObjectHydrationMode(Type $type): bool + private function getArrayHydratedReturnType(Type $queryResultType): Type + { + $objectManager = $this->objectMetadataResolver->getObjectManager(); + + return TypeTraverser::map( + $queryResultType, + static function (Type $type, callable $traverse) use ($objectManager): Type { + $isObject = (new ObjectWithoutClassType())->isSuperTypeOf($type); + if ($isObject->no()) { + return $traverse($type); + } + if ( + $isObject->maybe() + || !$type instanceof TypeWithClassName + || $objectManager === null + ) { + return new MixedType(); + } + + return $objectManager->getMetadataFactory()->hasMetadataFor($type->getClassName()) + ? new ArrayType(new MixedType(), new MixedType()) + : $traverse($type); + } + ); + } + + private function getScalarHydratedReturnType(Type $queryResultType): Type + { + if (!$queryResultType instanceof ArrayType) { + return new ArrayType(new MixedType(), new MixedType()); + } + + $itemType = $queryResultType->getItemType(); + $hasNoObject = (new ObjectWithoutClassType())->isSuperTypeOf($itemType)->no(); + $hasNoArray = $itemType->isArray()->no(); + + if ($hasNoArray && $hasNoObject) { + return $queryResultType; + } + + return new ArrayType(new MixedType(), new MixedType()); + } + + private function getSimpleObjectHydratedReturnType(Type $queryResultType): Type { - if (!$type instanceof ConstantIntegerType) { - return false; + if ((new ObjectWithoutClassType())->isSuperTypeOf($queryResultType)->yes()) { + return $queryResultType; + } + + return new MixedType(); + } + + private function getSingleScalarHydratedReturnType(Type $queryResultType): Type + { + $queryResultType = $this->getScalarHydratedReturnType($queryResultType); + if (!$queryResultType instanceof ConstantArrayType) { + return new MixedType(); + } + + $values = $queryResultType->getValueTypes(); + if (count($values) !== 1) { + return new MixedType(); + } + + return $queryResultType->getFirstIterableValueType(); + } + + private function getScalarColumnHydratedReturnType(Type $queryResultType): Type + { + $queryResultType = $this->getScalarHydratedReturnType($queryResultType); + if (!$queryResultType instanceof ConstantArrayType) { + return new MixedType(); + } + + $values = $queryResultType->getValueTypes(); + if (count($values) !== 1) { + return new MixedType(); } - return $type->getValue() === AbstractQuery::HYDRATE_OBJECT; + return $queryResultType->getFirstIterableValueType(); } private function originalReturnType(MethodReflection $methodReflection): Type diff --git a/tests/Type/Doctrine/data/QueryResult/queryResult.php b/tests/Type/Doctrine/data/QueryResult/queryResult.php index 18a1faa9..61ed83d1 100644 --- a/tests/Type/Doctrine/data/QueryResult/queryResult.php +++ b/tests/Type/Doctrine/data/QueryResult/queryResult.php @@ -143,11 +143,11 @@ public function testReturnTypeOfQueryMethodsWithExplicitObjectHydrationMode(Enti } /** - * Test that we properly infer the return type of Query methods with explicit hydration mode that is not HYDRATE_OBJECT + * Test that we properly infer the return type of Query methods with explicit hydration mode of HYDRATE_ARRAY * - * We are never able to infer the return type here + * We can infer the return type by changing every object by an array */ - public function testReturnTypeOfQueryMethodsWithExplicitNonObjectHydrationMode(EntityManagerInterface $em): void + public function testReturnTypeOfQueryMethodsWithExplicitArrayHydrationMode(EntityManagerInterface $em): void { $query = $em->createQuery(' SELECT m @@ -155,35 +155,415 @@ public function testReturnTypeOfQueryMethodsWithExplicitNonObjectHydrationMode(E '); assertType( - 'mixed', + 'list', $query->getResult(AbstractQuery::HYDRATE_ARRAY) ); assertType( - 'iterable', + 'list', + $query->getArrayResult() + ); + assertType( + 'iterable', $query->toIterable([], AbstractQuery::HYDRATE_ARRAY) ); assertType( - 'mixed', + 'list', $query->execute(null, AbstractQuery::HYDRATE_ARRAY) ); assertType( - 'mixed', + 'list', $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_ARRAY) ); assertType( - 'mixed', + 'list', $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_ARRAY) ); assertType( - 'mixed', + 'array', $query->getSingleResult(AbstractQuery::HYDRATE_ARRAY) ); assertType( - 'mixed', + 'array|null', + $query->getOneOrNullResult(AbstractQuery::HYDRATE_ARRAY) + ); + + $query = $em->createQuery(' + SELECT m.intColumn, m.stringNullColumn, m.datetimeColumn + FROM QueryResult\Entities\Many m + '); + + assertType( + 'list', + $query->getResult(AbstractQuery::HYDRATE_ARRAY) + ); + assertType( + 'list', + $query->getArrayResult() + ); + assertType( + 'list', + $query->execute(null, AbstractQuery::HYDRATE_ARRAY) + ); + assertType( + 'list', + $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_ARRAY) + ); + assertType( + 'list', + $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_ARRAY) + ); + assertType( + 'array{intColumn: int, stringNullColumn: string|null, datetimeColumn: DateTime}', + $query->getSingleResult(AbstractQuery::HYDRATE_ARRAY) + ); + assertType( + 'array{intColumn: int, stringNullColumn: string|null, datetimeColumn: DateTime}|null', $query->getOneOrNullResult(AbstractQuery::HYDRATE_ARRAY) ); } + /** + * Test that we properly infer the return type of Query methods with explicit hydration mode of HYDRATE_SCALAR + */ + public function testReturnTypeOfQueryMethodsWithExplicitScalarHydrationMode(EntityManagerInterface $em): void + { + $query = $em->createQuery(' + SELECT m + FROM QueryResult\Entities\Many m + '); + + assertType( + 'list', + $query->getResult(AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'list', + $query->getScalarResult() + ); + assertType( + 'iterable', + $query->toIterable([], AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'list', + $query->execute(null, AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'list', + $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'list', + $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'array', + $query->getSingleResult(AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'array|null', + $query->getOneOrNullResult(AbstractQuery::HYDRATE_SCALAR) + ); + + $query = $em->createQuery(' + SELECT m.intColumn, m.stringNullColumn + FROM QueryResult\Entities\Many m + '); + + assertType( + 'list', + $query->getResult(AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'list', + $query->getScalarResult() + ); + assertType( + 'list', + $query->execute(null, AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'list', + $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'list', + $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'array{intColumn: int, stringNullColumn: string|null}', + $query->getSingleResult(AbstractQuery::HYDRATE_SCALAR) + ); + assertType( + 'array{intColumn: int, stringNullColumn: string|null}|null', + $query->getOneOrNullResult(AbstractQuery::HYDRATE_SCALAR) + ); + } + + /** + * Test that we properly infer the return type of Query methods with explicit hydration mode of HYDRATE_SCALAR + */ + public function testReturnTypeOfQueryMethodsWithExplicitSingleScalarHydrationMode(EntityManagerInterface $em): void + { + $query = $em->createQuery(' + SELECT m + FROM QueryResult\Entities\Many m + '); + + assertType( + 'mixed', + $query->getResult(AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'mixed', + $query->getSingleScalarResult() + ); + assertType( + 'iterable', + $query->toIterable([], AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'mixed', + $query->execute(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'mixed', + $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'mixed', + $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'mixed', + $query->getSingleResult(AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'mixed', + $query->getOneOrNullResult(AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + + $query = $em->createQuery(' + SELECT m.intColumn + FROM QueryResult\Entities\Many m + '); + + assertType( + 'int', + $query->getResult(AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'int', + $query->getSingleScalarResult() + ); + assertType( + 'int', + $query->execute(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'int', + $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'int', + $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'int', + $query->getSingleResult(AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'int|null', + $query->getOneOrNullResult(AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + + $query = $em->createQuery(' + SELECT COUNT(m.id) + FROM QueryResult\Entities\Many m + '); + + assertType( + 'int<0, max>|numeric-string', + $query->getResult(AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'int<0, max>|numeric-string', + $query->getSingleScalarResult() + ); + assertType( + 'int<0, max>|numeric-string', + $query->execute(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'int<0, max>|numeric-string', + $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'int<0, max>|numeric-string', + $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'int<0, max>|numeric-string', + $query->getSingleResult(AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + assertType( + 'int<0, max>|numeric-string|null', + $query->getOneOrNullResult(AbstractQuery::HYDRATE_SINGLE_SCALAR) + ); + } + + /** + * Test that we properly infer the return type of Query methods with explicit hydration mode of HYDRATE_SIMPLEOBJECT + * + * We are never able to infer the return type here + */ + public function testReturnTypeOfQueryMethodsWithExplicitSimpleObjectHydrationMode(EntityManagerInterface $em): void + { + $query = $em->createQuery(' + SELECT m + FROM QueryResult\Entities\Many m + '); + + assertType( + 'list', + $query->getResult(AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'iterable', + $query->toIterable([], AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'list', + $query->execute(null, AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'list', + $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'list', + $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'QueryResult\Entities\Many', + $query->getSingleResult(AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'QueryResult\Entities\Many|null', + $query->getOneOrNullResult(AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + + $query = $em->createQuery(' + SELECT m.intColumn, m.stringNullColumn + FROM QueryResult\Entities\Many m + '); + + assertType( + 'list', + $query->getResult(AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'list', + $query->execute(null, AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'list', + $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'list', + $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'mixed', + $query->getSingleResult(AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + assertType( + 'mixed', + $query->getOneOrNullResult(AbstractQuery::HYDRATE_SIMPLEOBJECT) + ); + } + + /** + * Test that we properly infer the return type of Query methods with explicit hydration mode of HYDRATE_SCALAR_COLUMN + * + * We are never able to infer the return type here + */ + public function testReturnTypeOfQueryMethodsWithExplicitScalarColumnHydrationMode(EntityManagerInterface $em): void + { + $query = $em->createQuery(' + SELECT m + FROM QueryResult\Entities\Many m + '); + + assertType( + 'list', + $query->getResult(AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'list', + $query->getSingleColumnResult() + ); + assertType( + 'iterable', + $query->toIterable([], AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'list', + $query->execute(null, AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'list', + $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'list', + $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'mixed', + $query->getSingleResult(AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'mixed', + $query->getOneOrNullResult(AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + + $query = $em->createQuery(' + SELECT m.intColumn + FROM QueryResult\Entities\Many m + '); + + assertType( + 'list', + $query->getResult(AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'list', + $query->getSingleColumnResult() + ); + assertType( + 'list', + $query->execute(null, AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'list', + $query->executeIgnoreQueryCache(null, AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'list', + $query->executeUsingQueryCache(null, AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'int', + $query->getSingleResult(AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + assertType( + 'int|null', + $query->getOneOrNullResult(AbstractQuery::HYDRATE_SCALAR_COLUMN) + ); + } + /** * Test that we properly infer the return type of Query methods with explicit hydration mode that is not a constant value * From cdb0ba75bf043b3c1f51917e4e7db31a4e674989 Mon Sep 17 00:00:00 2001 From: Vincent Langlet Date: Sat, 25 Feb 2023 16:22:01 +0100 Subject: [PATCH 12/12] Solve phpstan deprecation --- .../QueryResultDynamicReturnTypeExtension.php | 48 ++++++++++++------- 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php index a46f96be..e7357b35 100644 --- a/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php +++ b/src/Type/Doctrine/Query/QueryResultDynamicReturnTypeExtension.php @@ -10,7 +10,6 @@ use PHPStan\ShouldNotHappenException; use PHPStan\Type\Accessory\AccessoryArrayListType; use PHPStan\Type\ArrayType; -use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Doctrine\ObjectMetadataResolver; use PHPStan\Type\DynamicMethodReturnTypeExtension; @@ -209,19 +208,22 @@ static function (Type $type, callable $traverse) use ($objectManager): Type { private function getScalarHydratedReturnType(Type $queryResultType): Type { - if (!$queryResultType instanceof ArrayType) { + if (!$queryResultType->isArray()->yes()) { return new ArrayType(new MixedType(), new MixedType()); } - $itemType = $queryResultType->getItemType(); - $hasNoObject = (new ObjectWithoutClassType())->isSuperTypeOf($itemType)->no(); - $hasNoArray = $itemType->isArray()->no(); + foreach ($queryResultType->getArrays() as $arrayType) { + $itemType = $arrayType->getItemType(); - if ($hasNoArray && $hasNoObject) { - return $queryResultType; + if ( + !(new ObjectWithoutClassType())->isSuperTypeOf($itemType)->no() + || !$itemType->isArray()->no() + ) { + return new ArrayType(new MixedType(), new MixedType()); + } } - return new ArrayType(new MixedType(), new MixedType()); + return $queryResultType; } private function getSimpleObjectHydratedReturnType(Type $queryResultType): Type @@ -236,31 +238,41 @@ private function getSimpleObjectHydratedReturnType(Type $queryResultType): Type private function getSingleScalarHydratedReturnType(Type $queryResultType): Type { $queryResultType = $this->getScalarHydratedReturnType($queryResultType); - if (!$queryResultType instanceof ConstantArrayType) { + if (!$queryResultType->isConstantArray()->yes()) { return new MixedType(); } - $values = $queryResultType->getValueTypes(); - if (count($values) !== 1) { - return new MixedType(); + $types = []; + foreach ($queryResultType->getConstantArrays() as $constantArrayType) { + $values = $constantArrayType->getValueTypes(); + if (count($values) !== 1) { + return new MixedType(); + } + + $types[] = $constantArrayType->getFirstIterableValueType(); } - return $queryResultType->getFirstIterableValueType(); + return TypeCombinator::union(...$types); } private function getScalarColumnHydratedReturnType(Type $queryResultType): Type { $queryResultType = $this->getScalarHydratedReturnType($queryResultType); - if (!$queryResultType instanceof ConstantArrayType) { + if (!$queryResultType->isConstantArray()->yes()) { return new MixedType(); } - $values = $queryResultType->getValueTypes(); - if (count($values) !== 1) { - return new MixedType(); + $types = []; + foreach ($queryResultType->getConstantArrays() as $constantArrayType) { + $values = $constantArrayType->getValueTypes(); + if (count($values) !== 1) { + return new MixedType(); + } + + $types[] = $constantArrayType->getFirstIterableValueType(); } - return $queryResultType->getFirstIterableValueType(); + return TypeCombinator::union(...$types); } private function originalReturnType(MethodReflection $methodReflection): Type