diff --git a/.phpstan-dba-mysqli.cache b/.phpstan-dba-mysqli.cache index d6fb5f19b..2e18e8a6d 100644 --- a/.phpstan-dba-mysqli.cache +++ b/.phpstan-dba-mysqli.cache @@ -2591,6 +2591,112 @@ )), ), ), + 'SELECT count(*) FROM typemix WHERE c_date = \'1970-01-01\'' => + array ( + 'result' => + array ( + 5 => + PHPStan\Type\Constant\ConstantArrayType::__set_state(array( + 'allArrays' => NULL, + 'keyTypes' => + array ( + 0 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'count(*)', + 'isClassString' => false, + )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + ), + 'valueTypes' => + array ( + 0 => + PHPStan\Type\IntegerType::__set_state(array( + )), + 1 => + PHPStan\Type\IntegerType::__set_state(array( + )), + ), + 'nextAutoIndex' => 1, + 'optionalKeys' => + array ( + ), + 'keyType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 1 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'count(*)', + 'isClassString' => false, + )), + ), + )), + 'itemType' => + PHPStan\Type\IntegerType::__set_state(array( + )), + )), + ), + ), + 'SELECT count(*) FROM typemix WHERE c_datetime = \'1970-01-01\'' => + array ( + 'result' => + array ( + 5 => + PHPStan\Type\Constant\ConstantArrayType::__set_state(array( + 'allArrays' => NULL, + 'keyTypes' => + array ( + 0 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'count(*)', + 'isClassString' => false, + )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + ), + 'valueTypes' => + array ( + 0 => + PHPStan\Type\IntegerType::__set_state(array( + )), + 1 => + PHPStan\Type\IntegerType::__set_state(array( + )), + ), + 'nextAutoIndex' => 1, + 'optionalKeys' => + array ( + ), + 'keyType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 1 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'count(*)', + 'isClassString' => false, + )), + ), + )), + 'itemType' => + PHPStan\Type\IntegerType::__set_state(array( + )), + )), + ), + ), 'SELECT eladaid FROM ak' => array ( 'result' => @@ -3655,6 +3761,154 @@ )), ), ), + 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE adaid = \'1\'' => + array ( + 'result' => + array ( + 5 => + PHPStan\Type\Constant\ConstantArrayType::__set_state(array( + 'allArrays' => NULL, + 'keyTypes' => + array ( + 0 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 2 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + 3 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 1, + )), + 4 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'gesperrt', + 'isClassString' => false, + )), + 5 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 2, + )), + 6 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'freigabe1u1', + 'isClassString' => false, + )), + 7 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 3, + )), + ), + 'valueTypes' => + array ( + 0 => + PHPStan\Type\StringType::__set_state(array( + )), + 1 => + PHPStan\Type\StringType::__set_state(array( + )), + 2 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + 3 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + 4 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + 5 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + 6 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + 7 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + ), + 'nextAutoIndex' => 4, + 'optionalKeys' => + array ( + ), + 'keyType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 1, + )), + 2 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 2, + )), + 3 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 3, + )), + 4 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + 5 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + 6 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'freigabe1u1', + 'isClassString' => false, + )), + 7 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'gesperrt', + 'isClassString' => false, + )), + ), + )), + 'itemType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 4294967295, + )), + 1 => + PHPStan\Type\StringType::__set_state(array( + )), + ), + )), + )), + ), + ), 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE adaid = 1' => array ( 'result' => @@ -3803,6 +4057,13 @@ )), ), ), + 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE adaid = ?' => + array ( + 'result' => + array ( + 5 => NULL, + ), + ), 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE adaid IN(1,3)' => array ( 'result' => @@ -4099,6 +4360,154 @@ )), ), ), + 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE email = \'1970-01-01\'' => + array ( + 'result' => + array ( + 5 => + PHPStan\Type\Constant\ConstantArrayType::__set_state(array( + 'allArrays' => NULL, + 'keyTypes' => + array ( + 0 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 2 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + 3 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 1, + )), + 4 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'gesperrt', + 'isClassString' => false, + )), + 5 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 2, + )), + 6 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'freigabe1u1', + 'isClassString' => false, + )), + 7 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 3, + )), + ), + 'valueTypes' => + array ( + 0 => + PHPStan\Type\StringType::__set_state(array( + )), + 1 => + PHPStan\Type\StringType::__set_state(array( + )), + 2 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + 3 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + 4 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + 5 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + 6 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + 7 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + ), + 'nextAutoIndex' => 4, + 'optionalKeys' => + array ( + ), + 'keyType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 1, + )), + 2 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 2, + )), + 3 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 3, + )), + 4 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + 5 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + 6 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'freigabe1u1', + 'isClassString' => false, + )), + 7 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'gesperrt', + 'isClassString' => false, + )), + ), + )), + 'itemType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 4294967295, + )), + 1 => + PHPStan\Type\StringType::__set_state(array( + )), + ), + )), + )), + ), + ), 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE email = \'test@example.org\'' => array ( 'result' => diff --git a/src/DoctrineReflection/DoctrineReflection.php b/src/DoctrineReflection/DoctrineReflection.php index f5c55144e..f8074c94c 100644 --- a/src/DoctrineReflection/DoctrineReflection.php +++ b/src/DoctrineReflection/DoctrineReflection.php @@ -4,6 +4,8 @@ namespace staabm\PHPStanDba\DoctrineReflection; +use Doctrine\DBAL\Result; +use Doctrine\DBAL\Statement; use PHPStan\Reflection\MethodReflection; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantArrayType; @@ -16,12 +18,13 @@ use PHPStan\Type\IntegerType; use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; +use staabm\PHPStanDba\QueryReflection\QueryReflection; use staabm\PHPStanDba\QueryReflection\QueryReflector; use Traversable; final class DoctrineReflection { - public function fetchResultType(MethodReflection $methodReflection, Type $resultRowType): ?Type + public function reduceResultType(MethodReflection $methodReflection, Type $resultRowType): ?Type { $usedMethod = strtolower($methodReflection->getName()); @@ -107,4 +110,95 @@ public function fetchResultType(MethodReflection $methodReflection, Type $result return null; } + + /** + * @param iterable $queryStrings + * @param QueryReflector::FETCH_TYPE* $reflectionFetchType + */ + public function createGenericStatement(iterable $queryStrings, int $reflectionFetchType): ?Type + { + $genericObjects = []; + + foreach ($queryStrings as $queryString) { + $queryReflection = new QueryReflection(); + + $resultType = $queryReflection->getResultType($queryString, $reflectionFetchType); + if (null === $resultType) { + return null; + } + + $genericObjects[] = new GenericObjectType(Statement::class, [$resultType]); + } + + if (\count($genericObjects) > 1) { + return TypeCombinator::union(...$genericObjects); + } + if (1 === \count($genericObjects)) { + return $genericObjects[0]; + } + + return null; + } + + /** + * @param iterable $queryStrings + * @param QueryReflector::FETCH_TYPE* $reflectionFetchType + */ + public function createGenericResult(iterable $queryStrings, int $reflectionFetchType): ?Type + { + $genericObjects = []; + + foreach ($queryStrings as $queryString) { + $queryReflection = new QueryReflection(); + + $resultType = $queryReflection->getResultType($queryString, $reflectionFetchType); + if (null === $resultType) { + return null; + } + + $genericObjects[] = new GenericObjectType(Result::class, [$resultType]); + } + + if (\count($genericObjects) > 1) { + return TypeCombinator::union(...$genericObjects); + } + if (1 === \count($genericObjects)) { + return $genericObjects[0]; + } + + return null; + } + + /** + * @param iterable $queryStrings + */ + public function createFetchType(iterable $queryStrings, MethodReflection $methodReflection): ?Type + { + $queryReflection = new QueryReflection(); + + $fetchTypes = []; + foreach ($queryStrings as $queryString) { + $resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH); + + if (null === $resultType) { + return null; + } + + $fetchResultType = $this->reduceResultType($methodReflection, $resultType); + if (null === $fetchResultType) { + return null; + } + + $fetchTypes[] = $fetchResultType; + } + + if (\count($fetchTypes) > 1) { + return TypeCombinator::union(...$fetchTypes); + } + if (1 === \count($fetchTypes)) { + return $fetchTypes[0]; + } + + return null; + } } diff --git a/src/Extensions/DoctrineConnectionExecuteQueryDynamicReturnTypeExtension.php b/src/Extensions/DoctrineConnectionExecuteQueryDynamicReturnTypeExtension.php index 0a20ff0fb..27d15ff45 100644 --- a/src/Extensions/DoctrineConnectionExecuteQueryDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineConnectionExecuteQueryDynamicReturnTypeExtension.php @@ -7,16 +7,15 @@ use Composer\InstalledVersions; use Composer\Semver\VersionParser; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Result; use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; +use staabm\PHPStanDba\DoctrineReflection\DoctrineReflection; use staabm\PHPStanDba\QueryReflection\QueryReflection; use staabm\PHPStanDba\QueryReflection\QueryReflector; @@ -71,25 +70,16 @@ private function inferType(Expr $queryExpr, ?Expr $paramsExpr, Scope $scope): ?T { if (null === $paramsExpr) { $queryReflection = new QueryReflection(); - $queryString = $queryReflection->resolveQueryString($queryExpr, $scope); - if (null === $queryString) { - return null; - } + $queryStrings = $queryReflection->resolveQueryStrings($queryExpr, $scope); } else { $parameterTypes = $scope->getType($paramsExpr); $queryReflection = new QueryReflection(); - $queryString = $queryReflection->resolvePreparedQueryString($queryExpr, $parameterTypes, $scope); - if (null === $queryString) { - return null; - } + $queryStrings = $queryReflection->resolvePreparedQueryStrings($queryExpr, $parameterTypes, $scope); } - $resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH); - if ($resultType) { - return new GenericObjectType(Result::class, [$resultType]); - } + $doctrineReflection = new DoctrineReflection(); - return null; + return $doctrineReflection->createGenericResult($queryStrings, QueryReflector::FETCH_TYPE_BOTH); } } diff --git a/src/Extensions/DoctrineConnectionFetchDynamicReturnTypeExtension.php b/src/Extensions/DoctrineConnectionFetchDynamicReturnTypeExtension.php index 0db7386e8..5e758fb3c 100644 --- a/src/Extensions/DoctrineConnectionFetchDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineConnectionFetchDynamicReturnTypeExtension.php @@ -17,7 +17,6 @@ use PHPStan\Type\Type; use staabm\PHPStanDba\DoctrineReflection\DoctrineReflection; use staabm\PHPStanDba\QueryReflection\QueryReflection; -use staabm\PHPStanDba\QueryReflection\QueryReflector; final class DoctrineConnectionFetchDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { @@ -82,33 +81,16 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method private function inferType(MethodReflection $methodReflection, Expr $queryExpr, ?Expr $paramsExpr, Scope $scope): ?Type { + $queryReflection = new QueryReflection(); + $doctrineReflection = new DoctrineReflection(); + if (null === $paramsExpr) { - $queryReflection = new QueryReflection(); - $queryString = $queryReflection->resolveQueryString($queryExpr, $scope); - if (null === $queryString) { - return null; - } + $queryString = $queryReflection->resolveQueryStrings($queryExpr, $scope); } else { $parameterTypes = $scope->getType($paramsExpr); - - $queryReflection = new QueryReflection(); - $queryString = $queryReflection->resolvePreparedQueryString($queryExpr, $parameterTypes, $scope); - if (null === $queryString) { - return null; - } - } - - $resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH); - - if ($resultType) { - $doctrineReflection = new DoctrineReflection(); - $fetchResultType = $doctrineReflection->fetchResultType($methodReflection, $resultType); - - if (null !== $fetchResultType) { - return $fetchResultType; - } + $queryString = $queryReflection->resolvePreparedQueryStrings($queryExpr, $parameterTypes, $scope); } - return null; + return $doctrineReflection->createFetchType($queryString, $methodReflection); } } diff --git a/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php b/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php index 4aea7302f..86f278640 100644 --- a/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php @@ -7,16 +7,15 @@ use Composer\InstalledVersions; use Composer\Semver\VersionParser; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Statement; use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; +use staabm\PHPStanDba\DoctrineReflection\DoctrineReflection; use staabm\PHPStanDba\QueryReflection\QueryReflection; use staabm\PHPStanDba\QueryReflection\QueryReflector; @@ -65,16 +64,10 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method private function inferType(Expr $queryExpr, Scope $scope): ?Type { $queryReflection = new QueryReflection(); - $queryString = $queryReflection->resolveQueryString($queryExpr, $scope); - if (null === $queryString) { - return null; - } + $queryStrings = $queryReflection->resolveQueryStrings($queryExpr, $scope); - $resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH); - if ($resultType) { - return new GenericObjectType(Statement::class, [$resultType]); - } + $doctrineReflection = new DoctrineReflection(); - return null; + return $doctrineReflection->createGenericStatement($queryStrings, QueryReflector::FETCH_TYPE_BOTH); } } diff --git a/src/Extensions/DoctrineConnectionQueryDynamicReturnTypeExtension.php b/src/Extensions/DoctrineConnectionQueryDynamicReturnTypeExtension.php index 2a68dff56..70ae54cb3 100644 --- a/src/Extensions/DoctrineConnectionQueryDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineConnectionQueryDynamicReturnTypeExtension.php @@ -7,16 +7,15 @@ use Composer\InstalledVersions; use Composer\Semver\VersionParser; use Doctrine\DBAL\Connection; -use Doctrine\DBAL\Result; use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PHPStan\Analyser\Scope; use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\MixedType; use PHPStan\Type\Type; +use staabm\PHPStanDba\DoctrineReflection\DoctrineReflection; use staabm\PHPStanDba\QueryReflection\QueryReflection; use staabm\PHPStanDba\QueryReflection\QueryReflector; @@ -65,16 +64,10 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method private function inferType(Expr $queryExpr, Scope $scope): ?Type { $queryReflection = new QueryReflection(); - $queryString = $queryReflection->resolveQueryString($queryExpr, $scope); - if (null === $queryString) { - return null; - } + $queryStrings = $queryReflection->resolveQueryStrings($queryExpr, $scope); - $resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH); - if ($resultType) { - return new GenericObjectType(Result::class, [$resultType]); - } + $doctrineReflection = new DoctrineReflection(); - return null; + return $doctrineReflection->createGenericResult($queryStrings, QueryReflector::FETCH_TYPE_BOTH); } } diff --git a/src/Extensions/DoctrineResultDynamicReturnTypeExtension.php b/src/Extensions/DoctrineResultDynamicReturnTypeExtension.php index 549ac5f6f..4561f1c83 100644 --- a/src/Extensions/DoctrineResultDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineResultDynamicReturnTypeExtension.php @@ -82,7 +82,7 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method } $doctrineReflection = new DoctrineReflection(); - $fetchResultType = $doctrineReflection->fetchResultType($methodReflection, $resultRowType); + $fetchResultType = $doctrineReflection->reduceResultType($methodReflection, $resultRowType); if (null !== $fetchResultType) { return $fetchResultType; } diff --git a/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php b/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php index cbad59598..614c3d21f 100644 --- a/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php @@ -6,7 +6,6 @@ use Composer\InstalledVersions; use Composer\Semver\VersionParser; -use Doctrine\DBAL\Result; use Doctrine\DBAL\Statement; use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; @@ -14,8 +13,8 @@ use PHPStan\Reflection\MethodReflection; use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\DynamicMethodReturnTypeExtension; -use PHPStan\Type\Generic\GenericObjectType; use PHPStan\Type\Type; +use staabm\PHPStanDba\DoctrineReflection\DoctrineReflection; use staabm\PHPStanDba\PdoReflection\PdoStatementReflection; use staabm\PHPStanDba\QueryReflection\QueryReflection; use staabm\PHPStanDba\QueryReflection\QueryReflector; @@ -60,6 +59,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method private function inferType(MethodReflection $methodReflection, MethodCall $methodCall, Expr $paramsExpr, Scope $scope): ?Type { + $doctrineReflection = new DoctrineReflection(); + $parameterTypes = $scope->getType($paramsExpr); $stmtReflection = new PdoStatementReflection(); @@ -69,16 +70,8 @@ private function inferType(MethodReflection $methodReflection, MethodCall $metho } $queryReflection = new QueryReflection(); - $queryString = $queryReflection->resolvePreparedQueryString($queryExpr, $parameterTypes, $scope); - if (null === $queryString) { - return null; - } - - $resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH); - if ($resultType) { - return new GenericObjectType(Result::class, [$resultType]); - } + $queryStrings = $queryReflection->resolvePreparedQueryStrings($queryExpr, $parameterTypes, $scope); - return null; + return $doctrineReflection->createGenericResult($queryStrings, QueryReflector::FETCH_TYPE_BOTH); } } diff --git a/tests/default/config/.phpstan-dba-mysqli.cache b/tests/default/config/.phpstan-dba-mysqli.cache index 9f9b1ce8f..8c671ee01 100644 --- a/tests/default/config/.phpstan-dba-mysqli.cache +++ b/tests/default/config/.phpstan-dba-mysqli.cache @@ -1512,7 +1512,7 @@ array ( 'result' => array ( - 3 => + 5 => PHPStan\Type\Constant\ConstantArrayType::__set_state(array( 'allArrays' => NULL, 'keyTypes' => @@ -1522,6 +1522,10 @@ 'value' => 'adaid', 'isClassString' => false, )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), ), 'valueTypes' => array ( @@ -1530,15 +1534,30 @@ 'min' => 0, 'max' => 4294967295, )), + 1 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), ), - 'nextAutoIndex' => 0, + 'nextAutoIndex' => 1, 'optionalKeys' => array ( ), 'keyType' => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'adaid', - 'isClassString' => false, + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 1 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + ), )), 'itemType' => PHPStan\Type\IntegerRangeType::__set_state(array( @@ -1546,7 +1565,7 @@ 'max' => 4294967295, )), )), - 5 => + 3 => PHPStan\Type\Constant\ConstantArrayType::__set_state(array( 'allArrays' => NULL, 'keyTypes' => @@ -1556,10 +1575,6 @@ 'value' => 'adaid', 'isClassString' => false, )), - 1 => - PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( - 'value' => 0, - )), ), 'valueTypes' => array ( @@ -1568,30 +1583,15 @@ 'min' => 0, 'max' => 4294967295, )), - 1 => - PHPStan\Type\IntegerRangeType::__set_state(array( - 'min' => 0, - 'max' => 4294967295, - )), ), - 'nextAutoIndex' => 1, + 'nextAutoIndex' => 0, 'optionalKeys' => array ( ), 'keyType' => - PHPStan\Type\UnionType::__set_state(array( - 'types' => - array ( - 0 => - PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( - 'value' => 0, - )), - 1 => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'adaid', - 'isClassString' => false, - )), - ), + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, )), 'itemType' => PHPStan\Type\IntegerRangeType::__set_state(array( @@ -2585,7 +2585,7 @@ array ( 'result' => array ( - 3 => + 5 => PHPStan\Type\Constant\ConstantArrayType::__set_state(array( 'allArrays' => NULL, 'keyTypes' => @@ -2595,27 +2595,44 @@ 'value' => 'email', 'isClassString' => false, )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), ), 'valueTypes' => array ( 0 => PHPStan\Type\StringType::__set_state(array( )), + 1 => + PHPStan\Type\StringType::__set_state(array( + )), ), - 'nextAutoIndex' => 0, + 'nextAutoIndex' => 1, 'optionalKeys' => array ( ), 'keyType' => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'email', - 'isClassString' => false, + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 1 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + ), )), 'itemType' => PHPStan\Type\StringType::__set_state(array( )), )), - 5 => + 3 => PHPStan\Type\Constant\ConstantArrayType::__set_state(array( 'allArrays' => NULL, 'keyTypes' => @@ -2625,38 +2642,21 @@ 'value' => 'email', 'isClassString' => false, )), - 1 => - PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( - 'value' => 0, - )), ), 'valueTypes' => array ( 0 => PHPStan\Type\StringType::__set_state(array( )), - 1 => - PHPStan\Type\StringType::__set_state(array( - )), ), - 'nextAutoIndex' => 1, + 'nextAutoIndex' => 0, 'optionalKeys' => array ( ), 'keyType' => - PHPStan\Type\UnionType::__set_state(array( - 'types' => - array ( - 0 => - PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( - 'value' => 0, - )), - 1 => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'email', - 'isClassString' => false, - )), - ), + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, )), 'itemType' => PHPStan\Type\StringType::__set_state(array( diff --git a/tests/default/config/.phpstan-dba-pdo.cache b/tests/default/config/.phpstan-dba-pdo.cache index 1a0fedadf..3b507a686 100644 --- a/tests/default/config/.phpstan-dba-pdo.cache +++ b/tests/default/config/.phpstan-dba-pdo.cache @@ -1512,7 +1512,7 @@ array ( 'result' => array ( - 3 => + 5 => PHPStan\Type\Constant\ConstantArrayType::__set_state(array( 'allArrays' => NULL, 'keyTypes' => @@ -1522,6 +1522,10 @@ 'value' => 'adaid', 'isClassString' => false, )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), ), 'valueTypes' => array ( @@ -1530,15 +1534,30 @@ 'min' => 0, 'max' => 4294967295, )), + 1 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), ), - 'nextAutoIndex' => 0, + 'nextAutoIndex' => 1, 'optionalKeys' => array ( ), 'keyType' => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'adaid', - 'isClassString' => false, + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 1 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + ), )), 'itemType' => PHPStan\Type\IntegerRangeType::__set_state(array( @@ -1546,7 +1565,7 @@ 'max' => 4294967295, )), )), - 5 => + 3 => PHPStan\Type\Constant\ConstantArrayType::__set_state(array( 'allArrays' => NULL, 'keyTypes' => @@ -1556,10 +1575,6 @@ 'value' => 'adaid', 'isClassString' => false, )), - 1 => - PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( - 'value' => 0, - )), ), 'valueTypes' => array ( @@ -1568,30 +1583,15 @@ 'min' => 0, 'max' => 4294967295, )), - 1 => - PHPStan\Type\IntegerRangeType::__set_state(array( - 'min' => 0, - 'max' => 4294967295, - )), ), - 'nextAutoIndex' => 1, + 'nextAutoIndex' => 0, 'optionalKeys' => array ( ), 'keyType' => - PHPStan\Type\UnionType::__set_state(array( - 'types' => - array ( - 0 => - PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( - 'value' => 0, - )), - 1 => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'adaid', - 'isClassString' => false, - )), - ), + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, )), 'itemType' => PHPStan\Type\IntegerRangeType::__set_state(array( @@ -2585,7 +2585,7 @@ array ( 'result' => array ( - 3 => + 5 => PHPStan\Type\Constant\ConstantArrayType::__set_state(array( 'allArrays' => NULL, 'keyTypes' => @@ -2595,27 +2595,44 @@ 'value' => 'email', 'isClassString' => false, )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), ), 'valueTypes' => array ( 0 => PHPStan\Type\StringType::__set_state(array( )), + 1 => + PHPStan\Type\StringType::__set_state(array( + )), ), - 'nextAutoIndex' => 0, + 'nextAutoIndex' => 1, 'optionalKeys' => array ( ), 'keyType' => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'email', - 'isClassString' => false, + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 1 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + ), )), 'itemType' => PHPStan\Type\StringType::__set_state(array( )), )), - 5 => + 3 => PHPStan\Type\Constant\ConstantArrayType::__set_state(array( 'allArrays' => NULL, 'keyTypes' => @@ -2625,38 +2642,21 @@ 'value' => 'email', 'isClassString' => false, )), - 1 => - PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( - 'value' => 0, - )), ), 'valueTypes' => array ( 0 => PHPStan\Type\StringType::__set_state(array( )), - 1 => - PHPStan\Type\StringType::__set_state(array( - )), ), - 'nextAutoIndex' => 1, + 'nextAutoIndex' => 0, 'optionalKeys' => array ( ), 'keyType' => - PHPStan\Type\UnionType::__set_state(array( - 'types' => - array ( - 0 => - PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( - 'value' => 0, - )), - 1 => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'email', - 'isClassString' => false, - )), - ), + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, )), 'itemType' => PHPStan\Type\StringType::__set_state(array( diff --git a/tests/default/data/doctrine-dbal-union-result.php b/tests/default/data/doctrine-dbal-union-result.php new file mode 100644 index 000000000..b4d98e5d8 --- /dev/null +++ b/tests/default/data/doctrine-dbal-union-result.php @@ -0,0 +1,39 @@ +prepare($query); + assertType('Doctrine\DBAL\Statement, 0: int<0, 4294967295>}>|Doctrine\DBAL\Statement', $stmt); + + $result = $stmt->executeQuery([]); + assertType('Doctrine\DBAL\Result, 0: int<0, 4294967295>}>|Doctrine\DBAL\Result', $result); + + $result = $stmt->execute([]); + assertType('Doctrine\DBAL\Result, 0: int<0, 4294967295>}>|Doctrine\DBAL\Result', $result); + + // XXX todo + // $fetch = $result->fetchOne(); + // assertType('Doctrine\DBAL\Result, 0: int<0, 4294967295>}>', $fetch); + } + } + + public function doBar(Connection $conn) + { + $queries = ['SELECT adaid FROM ada', 'SELECT email FROM ada']; + + foreach ($queries as $query) { + $result = $conn->query($query); + assertType('Doctrine\DBAL\Result, 0: int<0, 4294967295>}>|Doctrine\DBAL\Result', $result); + } + } +} diff --git a/tests/rules/config/.phpstan-dba-mysqli.cache b/tests/rules/config/.phpstan-dba-mysqli.cache index 4fd72dad7..e8565ec04 100644 --- a/tests/rules/config/.phpstan-dba-mysqli.cache +++ b/tests/rules/config/.phpstan-dba-mysqli.cache @@ -972,7 +972,7 @@ array ( 'result' => array ( - 5 => NULL, + 3 => NULL, ), ), 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE gesperrt=1' => diff --git a/tests/rules/config/.phpstan-dba-pdo.cache b/tests/rules/config/.phpstan-dba-pdo.cache index e8565ec04..4fd72dad7 100644 --- a/tests/rules/config/.phpstan-dba-pdo.cache +++ b/tests/rules/config/.phpstan-dba-pdo.cache @@ -972,7 +972,7 @@ array ( 'result' => array ( - 3 => NULL, + 5 => NULL, ), ), 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE gesperrt=1' =>