From 7d1b96c2ee90c183f47bf4b6a4e1ea489ad702a0 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 23 Jan 2022 13:22:28 +0100 Subject: [PATCH 1/2] doctrine-dbal: added `Connection->prepare()` dynamic return type extension --- .phpstan-dba.cache | 321 +++++------------- .phpunit-phpstan-dba.cache | 12 + config/extensions.neon | 5 + ...ctionPrepareDynamicReturnTypeExtension.php | 80 +++++ tests/data/doctrine-dbal.php | 14 + 5 files changed, 198 insertions(+), 234 deletions(-) create mode 100644 src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php diff --git a/.phpstan-dba.cache b/.phpstan-dba.cache index 7487986fe..afde0f41d 100644 --- a/.phpstan-dba.cache +++ b/.phpstan-dba.cache @@ -2850,126 +2850,6 @@ array ( ), )), - 2 => - PHPStan\Type\Constant\ConstantArrayType::__set_state(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, - )), - ), - )), - 'itemType' => - PHPStan\Type\UnionType::__set_state(array( - 'types' => - array ( - 0 => - PHPStan\Type\IntegerRangeType::__set_state(array( - 'min' => 0, - 'max' => 4294967295, - )), - 1 => - PHPStan\Type\StringType::__set_state(array( - )), - ), - )), - 'allArrays' => NULL, - 'keyTypes' => - array ( - 0 => - PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( - 'value' => 0, - )), - 1 => - PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( - 'value' => 1, - )), - ), - 'valueTypes' => - array ( - 0 => - PHPStan\Type\StringType::__set_state(array( - )), - 1 => - PHPStan\Type\IntegerRangeType::__set_state(array( - 'min' => 0, - 'max' => 4294967295, - )), - ), - 'nextAutoIndex' => 2, - 'optionalKeys' => - array ( - ), - )), - 1 => - PHPStan\Type\Constant\ConstantArrayType::__set_state(array( - 'keyType' => - PHPStan\Type\UnionType::__set_state(array( - 'types' => - array ( - 0 => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'adaid', - 'isClassString' => false, - )), - 1 => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'email', - 'isClassString' => false, - )), - ), - )), - 'itemType' => - PHPStan\Type\UnionType::__set_state(array( - 'types' => - array ( - 0 => - PHPStan\Type\IntegerRangeType::__set_state(array( - 'min' => 0, - 'max' => 4294967295, - )), - 1 => - PHPStan\Type\StringType::__set_state(array( - )), - ), - )), - 'allArrays' => NULL, - 'keyTypes' => - array ( - 0 => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'email', - 'isClassString' => false, - )), - 1 => - PHPStan\Type\Constant\ConstantStringType::__set_state(array( - 'value' => 'adaid', - 'isClassString' => false, - )), - ), - 'valueTypes' => - array ( - 0 => - PHPStan\Type\StringType::__set_state(array( - )), - 1 => - PHPStan\Type\IntegerRangeType::__set_state(array( - 'min' => 0, - 'max' => 4294967295, - )), - ), - 'nextAutoIndex' => 0, - 'optionalKeys' => - array ( - ), - )), ), ), 'SELECT email, adaid FROM ada ' => @@ -3423,18 +3303,26 @@ Simulated query: SELECT email, adaid FROM ada . WHERE email=\'my_other_table\' L 'error' => NULL, 'result' => array ( - 1 => + 3 => PHPStan\Type\Constant\ConstantArrayType::__set_state(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\ConstantStringType::__set_state(array( 'value' => 'adaid', 'isClassString' => false, )), - 1 => + 3 => PHPStan\Type\Constant\ConstantStringType::__set_state(array( 'value' => 'email', 'isClassString' => false, @@ -3464,10 +3352,18 @@ Simulated query: SELECT email, adaid FROM ada . WHERE email=\'my_other_table\' L '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, + )), ), 'valueTypes' => array ( @@ -3475,36 +3371,36 @@ Simulated query: SELECT email, adaid FROM ada . WHERE email=\'my_other_table\' L 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, )), ), - 'nextAutoIndex' => 0, + 'nextAutoIndex' => 2, 'optionalKeys' => array ( ), )), - 3 => + 1 => PHPStan\Type\Constant\ConstantArrayType::__set_state(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\ConstantStringType::__set_state(array( 'value' => 'adaid', 'isClassString' => false, )), - 3 => + 1 => PHPStan\Type\Constant\ConstantStringType::__set_state(array( 'value' => 'email', 'isClassString' => false, @@ -3534,18 +3430,10 @@ Simulated query: SELECT email, adaid FROM ada . WHERE email=\'my_other_table\' L '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, - )), ), 'valueTypes' => array ( @@ -3553,20 +3441,12 @@ Simulated query: SELECT email, adaid FROM ada . WHERE email=\'my_other_table\' L 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, )), ), - 'nextAutoIndex' => 2, + 'nextAutoIndex' => 0, 'optionalKeys' => array ( ), @@ -4524,28 +4404,51 @@ Simulated query: SELECT email, adaid FROM ada . WHERE email=\'my_other_table\' L array ( ), )), - 1 => + ), + ), + 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada ' => + array ( + 'error' => NULL, + 'result' => + array ( + 3 => PHPStan\Type\Constant\ConstantArrayType::__set_state(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, )), - 1 => + 5 => PHPStan\Type\Constant\ConstantStringType::__set_state(array( 'value' => 'email', 'isClassString' => false, )), - 2 => + 6 => PHPStan\Type\Constant\ConstantStringType::__set_state(array( 'value' => 'freigabe1u1', 'isClassString' => false, )), - 3 => + 7 => PHPStan\Type\Constant\ConstantStringType::__set_state(array( 'value' => 'gesperrt', 'isClassString' => false, @@ -4575,20 +4478,36 @@ Simulated query: SELECT email, adaid FROM ada . WHERE email=\'my_other_table\' L 'isClassString' => false, )), 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 2 => PHPStan\Type\Constant\ConstantStringType::__set_state(array( 'value' => 'adaid', 'isClassString' => false, )), - 2 => + 3 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 1, + )), + 4 => PHPStan\Type\Constant\ConstantStringType::__set_state(array( 'value' => 'gesperrt', 'isClassString' => false, )), - 3 => + 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 ( @@ -4596,100 +4515,34 @@ Simulated query: SELECT email, adaid FROM ada . WHERE email=\'my_other_table\' L PHPStan\Type\StringType::__set_state(array( )), 1 => + PHPStan\Type\StringType::__set_state(array( + )), + 2 => PHPStan\Type\IntegerRangeType::__set_state(array( 'min' => 0, 'max' => 4294967295, )), - 2 => + 3 => PHPStan\Type\IntegerRangeType::__set_state(array( - 'min' => -128, - 'max' => 127, + 'min' => 0, + 'max' => 4294967295, )), - 3 => + 4 => PHPStan\Type\IntegerRangeType::__set_state(array( 'min' => -128, 'max' => 127, )), - ), - 'nextAutoIndex' => 0, - 'optionalKeys' => - array ( - ), - )), - 2 => - PHPStan\Type\Constant\ConstantArrayType::__set_state(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, - )), - ), - )), - '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( - )), - ), - )), - 'allArrays' => NULL, - 'keyTypes' => - 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, - )), - ), - 'valueTypes' => - array ( - 0 => - PHPStan\Type\StringType::__set_state(array( - )), - 1 => + 5 => PHPStan\Type\IntegerRangeType::__set_state(array( - 'min' => 0, - 'max' => 4294967295, + 'min' => -128, + 'max' => 127, )), - 2 => + 6 => PHPStan\Type\IntegerRangeType::__set_state(array( 'min' => -128, 'max' => 127, )), - 3 => + 7 => PHPStan\Type\IntegerRangeType::__set_state(array( 'min' => -128, 'max' => 127, diff --git a/.phpunit-phpstan-dba.cache b/.phpunit-phpstan-dba.cache index ffbf2dc70..27bcfef9d 100644 --- a/.phpunit-phpstan-dba.cache +++ b/.phpunit-phpstan-dba.cache @@ -4036,6 +4036,18 @@ Simulated query: SELECT email, adaid, gesperrt, freigabe1u1 FROM ada . WHERE ema )), ), ), + 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE adaid = ?' => + array ( + 'error' => + staabm\PHPStanDba\Error::__set_state(array( + 'message' => 'You have an error in your SQL syntax; check the manual that corresponds to your MySQL/MariaDB server version for the right syntax to use near \'? LIMIT 0\' at line 1', + 'code' => 1064, + )), + 'result' => + array ( + 3 => NULL, + ), + ), 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE adaid=1' => array ( 'error' => NULL, diff --git a/config/extensions.neon b/config/extensions.neon index db316c489..6b9e5013a 100644 --- a/config/extensions.neon +++ b/config/extensions.neon @@ -9,6 +9,11 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: staabm\PHPStanDba\Extensions\DoctrineConnectionPrepareDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - class: staabm\PHPStanDba\Extensions\DoctrineResultDynamicReturnTypeExtension tags: diff --git a/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php b/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php new file mode 100644 index 000000000..4aea7302f --- /dev/null +++ b/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php @@ -0,0 +1,80 @@ +getName()), ['prepare'], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $args = $methodCall->getArgs(); + $defaultReturn = ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); + + if (\count($args) < 1) { + return $defaultReturn; + } + + if ($scope->getType($args[0]->value) instanceof MixedType) { + return $defaultReturn; + } + + // make sure we don't report wrong types in doctrine 2.x + if (!InstalledVersions::satisfies(new VersionParser(), 'doctrine/dbal', '3.*')) { + return $defaultReturn; + } + + $resultType = $this->inferType($args[0]->value, $scope); + if (null !== $resultType) { + return $resultType; + } + + return $defaultReturn; + } + + private function inferType(Expr $queryExpr, Scope $scope): ?Type + { + $queryReflection = new QueryReflection(); + $queryString = $queryReflection->resolveQueryString($queryExpr, $scope); + if (null === $queryString) { + return null; + } + + $resultType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH); + if ($resultType) { + return new GenericObjectType(Statement::class, [$resultType]); + } + + return null; + } +} diff --git a/tests/data/doctrine-dbal.php b/tests/data/doctrine-dbal.php index eebd14dfb..9e9ca890b 100644 --- a/tests/data/doctrine-dbal.php +++ b/tests/data/doctrine-dbal.php @@ -37,4 +37,18 @@ public function executeQuery(Connection $conn, array $types, QueryCacheProfile $ $stmt = $conn->executeCacheQuery('SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE adaid = ?', [1], $types, $qcp); assertType('Doctrine\DBAL\Result, 1: int<0, 4294967295>, gesperrt: int<-128, 127>, 2: int<-128, 127>, freigabe1u1: int<-128, 127>, 3: int<-128, 127>}>', $stmt); } + + public function executeStatement(Connection $conn, int $adaid) + { + $stmt = $conn->prepare('SELECT email, adaid, gesperrt, freigabe1u1 FROM ada'); + assertType('Doctrine\DBAL\Statement, 1: int<0, 4294967295>, gesperrt: int<-128, 127>, 2: int<-128, 127>, freigabe1u1: int<-128, 127>, 3: int<-128, 127>}>', $stmt); + + $stmt = $conn->prepare('SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE adaid = ?'); + $result = $stmt->execute([$adaid]); + assertType('Doctrine\DBAL\Result, 1: int<0, 4294967295>, gesperrt: int<-128, 127>, 2: int<-128, 127>, freigabe1u1: int<-128, 127>, 3: int<-128, 127>}>', $result); + + $stmt = $conn->prepare('SELECT email, adaid, gesperrt, freigabe1u1 FROM ada WHERE adaid = ?'); + $result = $stmt->executeQuery([$adaid]); + assertType('Doctrine\DBAL\Result, 1: int<0, 4294967295>, gesperrt: int<-128, 127>, 2: int<-128, 127>, freigabe1u1: int<-128, 127>, 3: int<-128, 127>}>', $result); + } } From 1df1b37409dc0ef5fd739392d9410bb5564e8066 Mon Sep 17 00:00:00 2001 From: Markus Staab Date: Sun, 23 Jan 2022 13:32:52 +0100 Subject: [PATCH 2/2] impl --- .phpstan-dba.cache | 296 ++++++++++++++++++ config/extensions.neon | 5 + ...ementExecuteDynamicReturnTypeExtension.php | 84 +++++ src/PdoReflection/PdoStatementReflection.php | 6 - 4 files changed, 385 insertions(+), 6 deletions(-) create mode 100644 src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php diff --git a/.phpstan-dba.cache b/.phpstan-dba.cache index afde0f41d..b7db4e39d 100644 --- a/.phpstan-dba.cache +++ b/.phpstan-dba.cache @@ -2850,6 +2850,126 @@ array ( ), )), + 2 => + PHPStan\Type\Constant\ConstantArrayType::__set_state(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, + )), + ), + )), + 'itemType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + 1 => + PHPStan\Type\StringType::__set_state(array( + )), + ), + )), + 'allArrays' => NULL, + 'keyTypes' => + array ( + 0 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 0, + )), + 1 => + PHPStan\Type\Constant\ConstantIntegerType::__set_state(array( + 'value' => 1, + )), + ), + 'valueTypes' => + array ( + 0 => + PHPStan\Type\StringType::__set_state(array( + )), + 1 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + ), + 'nextAutoIndex' => 2, + 'optionalKeys' => + array ( + ), + )), + 1 => + PHPStan\Type\Constant\ConstantArrayType::__set_state(array( + 'keyType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + 1 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + ), + )), + 'itemType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + 1 => + PHPStan\Type\StringType::__set_state(array( + )), + ), + )), + 'allArrays' => NULL, + 'keyTypes' => + array ( + 0 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + 1 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + ), + 'valueTypes' => + array ( + 0 => + PHPStan\Type\StringType::__set_state(array( + )), + 1 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + ), + 'nextAutoIndex' => 0, + 'optionalKeys' => + array ( + ), + )), ), ), 'SELECT email, adaid FROM ada ' => @@ -4404,6 +4524,182 @@ Simulated query: SELECT email, adaid FROM ada . WHERE email=\'my_other_table\' L array ( ), )), + 1 => + PHPStan\Type\Constant\ConstantArrayType::__set_state(array( + 'keyType' => + PHPStan\Type\UnionType::__set_state(array( + 'types' => + array ( + 0 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + 1 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + 2 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'freigabe1u1', + 'isClassString' => false, + )), + 3 => + 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( + )), + ), + )), + 'allArrays' => NULL, + 'keyTypes' => + array ( + 0 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'email', + 'isClassString' => false, + )), + 1 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'adaid', + 'isClassString' => false, + )), + 2 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'gesperrt', + 'isClassString' => false, + )), + 3 => + PHPStan\Type\Constant\ConstantStringType::__set_state(array( + 'value' => 'freigabe1u1', + 'isClassString' => false, + )), + ), + 'valueTypes' => + array ( + 0 => + PHPStan\Type\StringType::__set_state(array( + )), + 1 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + 2 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + 3 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + ), + 'nextAutoIndex' => 0, + 'optionalKeys' => + array ( + ), + )), + 2 => + PHPStan\Type\Constant\ConstantArrayType::__set_state(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, + )), + ), + )), + '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( + )), + ), + )), + 'allArrays' => NULL, + 'keyTypes' => + 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, + )), + ), + 'valueTypes' => + array ( + 0 => + PHPStan\Type\StringType::__set_state(array( + )), + 1 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => 0, + 'max' => 4294967295, + )), + 2 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + 3 => + PHPStan\Type\IntegerRangeType::__set_state(array( + 'min' => -128, + 'max' => 127, + )), + ), + 'nextAutoIndex' => 4, + 'optionalKeys' => + array ( + ), + )), ), ), 'SELECT email, adaid, gesperrt, freigabe1u1 FROM ada ' => diff --git a/config/extensions.neon b/config/extensions.neon index 6b9e5013a..6ad15990f 100644 --- a/config/extensions.neon +++ b/config/extensions.neon @@ -19,6 +19,11 @@ services: tags: - phpstan.broker.dynamicMethodReturnTypeExtension + - + class: staabm\PHPStanDba\Extensions\DoctrineStatementExecuteDynamicReturnTypeExtension + tags: + - phpstan.broker.dynamicMethodReturnTypeExtension + - class: staabm\PHPStanDba\Extensions\PdoQueryDynamicReturnTypeExtension tags: diff --git a/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php b/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php new file mode 100644 index 000000000..63a661cab --- /dev/null +++ b/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php @@ -0,0 +1,84 @@ +getName()), ['executequery', 'execute'], true); + } + + public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type + { + $args = $methodCall->getArgs(); + $defaultReturn = ParametersAcceptorSelector::selectFromArgs( + $scope, + $methodCall->getArgs(), + $methodReflection->getVariants(), + )->getReturnType(); + + if (\count($args) < 1) { + return $defaultReturn; + } + + // make sure we don't report wrong types in doctrine 2.x + if (!InstalledVersions::satisfies(new VersionParser(), 'doctrine/dbal', '3.*')) { + return $defaultReturn; + } + + $resultType = $this->inferType($methodReflection, $methodCall, $args[0]->value, $scope); + if (null !== $resultType) { + return $resultType; + } + + return $defaultReturn; + } + + private function inferType(MethodReflection $methodReflection, MethodCall $methodCall, Expr $paramsExpr, Scope $scope): ?Type + { + $parameterTypes = $scope->getType($paramsExpr); + + $stmtReflection = new PdoStatementReflection(); + $queryExpr = $stmtReflection->findPrepareQueryStringExpression($methodReflection, $methodCall); + if (null === $queryExpr) { + return null; + } + + $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]); + } + + return null; + } +} diff --git a/src/PdoReflection/PdoStatementReflection.php b/src/PdoReflection/PdoStatementReflection.php index 80dfa6f9f..34d1ef545 100644 --- a/src/PdoReflection/PdoStatementReflection.php +++ b/src/PdoReflection/PdoStatementReflection.php @@ -4,21 +4,15 @@ namespace staabm\PHPStanDba\PdoReflection; -use PDOStatement; use PhpParser\Node\Expr; use PhpParser\Node\Expr\MethodCall; use PHPStan\Reflection\MethodReflection; -use PHPStan\ShouldNotHappenException; use staabm\PHPStanDba\QueryReflection\ExpressionFinder; final class PdoStatementReflection { public function findPrepareQueryStringExpression(MethodReflection $methodReflection, MethodCall $methodCall): ?Expr { - if ('execute' !== $methodReflection->getName() || PdoStatement::class !== $methodReflection->getDeclaringClass()->getName()) { - throw new ShouldNotHappenException(); - } - $exprFinder = new ExpressionFinder(); $queryExpr = $exprFinder->findQueryStringExpression($methodCall);