diff --git a/src/Extensions/PdoQueryDynamicReturnTypeExtension.php b/src/Extensions/PdoQueryDynamicReturnTypeExtension.php index 1d53defc7..a0e8987ed 100644 --- a/src/Extensions/PdoQueryDynamicReturnTypeExtension.php +++ b/src/Extensions/PdoQueryDynamicReturnTypeExtension.php @@ -18,6 +18,7 @@ use PHPStan\Type\TypeCombinator; use staabm\PHPStanDba\PdoReflection\PdoStatementReflection; use staabm\PHPStanDba\QueryReflection\QueryReflection; +use staabm\PHPStanDba\QueryReflection\QueryReflector; final class PdoQueryDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { @@ -83,6 +84,10 @@ private function inferType(MethodCall $methodCall, Expr $queryExpr, Scope $scope if (null === $reflectionFetchType) { return null; } + // not yet implemented on query() + if (QueryReflector::FETCH_TYPE_COLUMN === $reflectionFetchType) { + return null; + } } $queryReflection = new QueryReflection(); diff --git a/src/Extensions/PdoStatementFetchDynamicReturnTypeExtension.php b/src/Extensions/PdoStatementFetchDynamicReturnTypeExtension.php index 0d1a27c11..430a49d61 100644 --- a/src/Extensions/PdoStatementFetchDynamicReturnTypeExtension.php +++ b/src/Extensions/PdoStatementFetchDynamicReturnTypeExtension.php @@ -12,6 +12,7 @@ use PHPStan\Reflection\ParametersAcceptorSelector; use PHPStan\Type\ArrayType; use PHPStan\Type\Constant\ConstantBooleanType; +use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\DynamicMethodReturnTypeExtension; use PHPStan\Type\IntegerType; use PHPStan\Type\MixedType; @@ -19,6 +20,7 @@ use PHPStan\Type\TypeCombinator; use staabm\PHPStanDba\PdoReflection\PdoStatementReflection; use staabm\PHPStanDba\QueryReflection\QueryReflection; +use staabm\PHPStanDba\QueryReflection\QueryReflector; final class PdoStatementFetchDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension { @@ -76,7 +78,23 @@ private function inferType(MethodReflection $methodReflection, MethodCall $metho } } - $rowType = $pdoStatementReflection->getRowType($statementType, $fetchType); + if ('fetchAll' === $methodReflection->getName() && QueryReflector::FETCH_TYPE_COLUMN === $fetchType) { + $columnIndex = 0; + + if (\count($args) > 1) { + $columnIndexType = $scope->getType($args[1]->value); + if ($columnIndexType instanceof ConstantIntegerType) { + $columnIndex = $columnIndexType->getValue(); + } else { + return null; + } + } + + $rowType = $pdoStatementReflection->getColumnRowType($statementType, $columnIndex); + } else { + $rowType = $pdoStatementReflection->getRowType($statementType, $fetchType); + } + if (null === $rowType) { return null; } diff --git a/src/PdoReflection/PdoStatementReflection.php b/src/PdoReflection/PdoStatementReflection.php index 6d8ff465c..672c362ff 100644 --- a/src/PdoReflection/PdoStatementReflection.php +++ b/src/PdoReflection/PdoStatementReflection.php @@ -64,6 +64,8 @@ public function getFetchType(Type $fetchModeType): ?int return QueryReflector::FETCH_TYPE_NUMERIC; } elseif (PDO::FETCH_BOTH === $fetchModeType->getValue()) { return QueryReflector::FETCH_TYPE_BOTH; + } elseif (PDO::FETCH_COLUMN === $fetchModeType->getValue()) { + return QueryReflector::FETCH_TYPE_COLUMN; } return null; @@ -132,6 +134,20 @@ public function getRowType(Type $statementType, int $fetchType): ?Type return null; } + public function getColumnRowType(Type $statementType, int $columnIndex): ?Type + { + $statementType = $this->getRowType($statementType, QueryReflector::FETCH_TYPE_NUMERIC); + + if ($statementType instanceof ConstantArrayType) { + $valueTypes = $statementType->getValueTypes(); + if (\array_key_exists($columnIndex, $valueTypes)) { + return $valueTypes[$columnIndex]; + } + } + + return null; + } + /** * @param QueryReflector::FETCH_TYPE* $fetchType */ diff --git a/src/QueryReflection/QueryReflector.php b/src/QueryReflection/QueryReflector.php index 3afdd090a..5fccb06e8 100644 --- a/src/QueryReflection/QueryReflector.php +++ b/src/QueryReflection/QueryReflector.php @@ -14,6 +14,8 @@ interface QueryReflector public const FETCH_TYPE_BOTH = 5; public const FETCH_TYPE_KEY_VALUE = 6; + public const FETCH_TYPE_COLUMN = 50; + public function validateQueryString(string $queryString): ?Error; /** diff --git a/tests/default/data/pdo-stmt-fetch.php b/tests/default/data/pdo-stmt-fetch.php index 64652858f..ba0935cbf 100644 --- a/tests/default/data/pdo-stmt-fetch.php +++ b/tests/default/data/pdo-stmt-fetch.php @@ -28,6 +28,15 @@ public function fetchAll(PDO $pdo) $all = $stmt->fetchAll(PDO::FETCH_ASSOC); assertType('array}>', $all); + $all = $stmt->fetchAll(PDO::FETCH_COLUMN); + assertType('array', $all); + + $all = $stmt->fetchAll(PDO::FETCH_COLUMN, 0); + assertType('array', $all); + + $all = $stmt->fetchAll(PDO::FETCH_COLUMN, 1); + assertType('array>', $all); + // not yet supported fetch types $all = $stmt->fetchAll(PDO::FETCH_OBJ); assertType('array', $all); // XXX since php8 this cannot return false 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' =>