Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4,479 changes: 4,400 additions & 79 deletions .phpstan-dba.cache

Large diffs are not rendered by default.

6 changes: 4 additions & 2 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@
"phpstan analyse -c phpstan.neon.dist",
"phpstan analyse -c tests/default/config/phpstan.neon.dist",
"phpstan analyse -c tests/stringify/config/phpstan.neon.dist",
"phpstan analyse -c tests/defaultFetchAssoc/config/phpstan.neon.dist"
"phpstan analyse -c tests/defaultFetchAssoc/config/phpstan.neon.dist",
"phpstan analyse -c tests/defaultFetchNumeric/config/phpstan.neon.dist"
],
"phpunit": [
"phpunit -c tests/default/config/phpunit.xml",
"phpunit -c tests/stringify/config/phpunit.xml",
"phpunit -c tests/defaultFetchAssoc/config/phpunit.xml"
"phpunit -c tests/defaultFetchAssoc/config/phpunit.xml",
"phpunit -c tests/defaultFetchNumeric/config/phpunit.xml"
]
},
"config": {
Expand Down
7 changes: 4 additions & 3 deletions config/PdoStatement.stub
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<?php

/**
* @template TValue
* @implements Traversable<int, TValue>
* @implements IteratorAggregate<int, TValue>
* @template TRowTypeInFetchMode
* @template TRowTypeBoth
* @implements Traversable<int, TRowTypeInFetchMode>
* @implements IteratorAggregate<int, TRowTypeInFetchMode>
* @link https://php.net/manual/en/class.pdostatement.php
*/
class PDOStatement implements Traversable, IteratorAggregate
Expand Down
10 changes: 3 additions & 7 deletions src/Extensions/PdoPrepareDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace staabm\PHPStanDba\Extensions;

use PDO;
use PDOStatement;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
Expand All @@ -14,10 +13,10 @@
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use staabm\PHPStanDba\PdoReflection\PdoStatementReflection;
use staabm\PHPStanDba\QueryReflection\QueryReflection;

final class PdoPrepareDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
Expand Down Expand Up @@ -76,11 +75,8 @@ private function inferType(Expr $queryExpr, Scope $scope): ?Type
}

$reflectionFetchType = QueryReflection::getRuntimeConfiguration()->getDefaultFetchMode();
$resultType = $queryReflection->getResultType($queryString, $reflectionFetchType);
if ($resultType) {
return new GenericObjectType(PDOStatement::class, [$resultType]);
}
$pdoStatementReflection = new PdoStatementReflection();

return null;
return $pdoStatementReflection->createGenericStatement($queryString, $reflectionFetchType);
}
}
9 changes: 2 additions & 7 deletions src/Extensions/PdoQueryDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
namespace staabm\PHPStanDba\Extensions;

use PDO;
use PDOStatement;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Analyser\Scope;
Expand All @@ -14,7 +13,6 @@
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
Expand Down Expand Up @@ -93,11 +91,8 @@ private function inferType(MethodCall $methodCall, Expr $queryExpr, Scope $scope
return null;
}

$resultType = $queryReflection->getResultType($queryString, $reflectionFetchType);
if ($resultType) {
return new GenericObjectType(PDOStatement::class, [$resultType]);
}
$pdoStatementReflection = new PdoStatementReflection();

return null;
return $pdoStatementReflection->createGenericStatement($queryString, $reflectionFetchType);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\Type;
use staabm\PHPStanDba\PdoReflection\PdoStatementReflection;
use staabm\PHPStanDba\QueryReflection\QueryReflector;

final class PdoStatementColumnCountDynamicReturnTypeExtension implements DynamicMethodReturnTypeExtension
{
Expand All @@ -29,21 +31,15 @@ public function isMethodSupported(MethodReflection $methodReflection): bool

public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): Type
{
$pdoStatementReflection = new PdoStatementReflection();
$defaultReturn = ParametersAcceptorSelector::selectSingle($methodReflection->getVariants())->getReturnType();

$statementType = $scope->getType($methodCall->var);

if ($statementType instanceof GenericObjectType) {
$genericTypes = $statementType->getTypes();

if (1 !== \count($genericTypes)) {
return $defaultReturn;
}

$resultType = $genericTypes[0];

if ($resultType instanceof ConstantArrayType) {
return new ConstantIntegerType(\count($resultType->getKeyTypes()));
$rowType = $pdoStatementReflection->getRowType($statementType, QueryReflector::FETCH_TYPE_NUMERIC);
if ($rowType instanceof ConstantArrayType) {
return new ConstantIntegerType(\count($rowType->getKeyTypes()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
use PHPStan\Analyser\TypeSpecifierAwareExtension;
use PHPStan\Analyser\TypeSpecifierContext;
use PHPStan\Reflection\MethodReflection;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\MethodTypeSpecifyingExtension;
use PHPStan\Type\Type;
use staabm\PHPStanDba\PdoReflection\PdoStatementReflection;
Expand Down Expand Up @@ -73,12 +72,7 @@ private function inferStatementType(MethodReflection $methodReflection, MethodCa
}

$reflectionFetchType = QueryReflection::getRuntimeConfiguration()->getDefaultFetchMode();
$resultType = $queryReflection->getResultType($queryString, $reflectionFetchType);

if ($resultType) {
return new GenericObjectType(PDOStatement::class, [$resultType]);
}

return null;
return $stmtReflection->createGenericStatement($queryString, $reflectionFetchType);
}
}
14 changes: 9 additions & 5 deletions src/Extensions/PdoStatementFetchDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
use PHPStan\Type\ArrayType;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicMethodReturnTypeExtension;
use PHPStan\Type\Generic\GenericObjectType;
use PHPStan\Type\IntegerType;
use PHPStan\Type\MixedType;
use PHPStan\Type\Type;
Expand Down Expand Up @@ -65,6 +66,9 @@ private function inferType(MethodReflection $methodReflection, MethodCall $metho
$pdoStatementReflection = new PdoStatementReflection();

$statementType = $scope->getType($methodCall->var);
if (!$statementType instanceof GenericObjectType) {
return null;
}

$fetchType = QueryReflection::getRuntimeConfiguration()->getDefaultFetchMode();
if (\count($args) > 0) {
Expand All @@ -76,19 +80,19 @@ private function inferType(MethodReflection $methodReflection, MethodCall $metho
}
}

$resultType = $pdoStatementReflection->getStatementResultType($statementType, $fetchType);
if (null === $resultType) {
$rowType = $pdoStatementReflection->getRowType($statementType, $fetchType);
if (null === $rowType) {
return null;
}

if ('fetchAll' === $methodReflection->getName()) {
return new ArrayType(new IntegerType(), $resultType);
return new ArrayType(new IntegerType(), $rowType);
}

if (QueryReflection::getRuntimeConfiguration()->throwsPdoExceptions($this->phpVersion)) {
return $resultType;
return $rowType;
}

return new UnionType([$resultType, new ConstantBooleanType(false)]);
return new UnionType([$rowType, new ConstantBooleanType(false)]);
}
}
64 changes: 37 additions & 27 deletions src/PdoReflection/PdoStatementReflection.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace staabm\PHPStanDba\PdoReflection;

use PDO;
use PDOStatement;
use PhpParser\Node\Expr;
use PhpParser\Node\Expr\MethodCall;
use PHPStan\Type\Constant\ConstantArrayType;
Expand Down Expand Up @@ -35,6 +36,8 @@ public function findPrepareQueryStringExpression(MethodCall $methodCall): ?Expr
}

/**
* Turns a PDO::FETCH_* parameter-type into a QueryReflector::FETCH_TYPE* constant.
*
* @return QueryReflector::FETCH_TYPE*|null
*/
public function getFetchType(Type $fetchModeType): ?int
Expand All @@ -55,46 +58,52 @@ public function getFetchType(Type $fetchModeType): ?int
}

/**
* @param QueryReflector::FETCH_TYPE* $fetchType
* @param QueryReflector::FETCH_TYPE* $reflectionFetchType
*/
public function getStatementResultType(Type $statementType, int $fetchType): ?Type
public function createGenericStatement(string $queryString, int $reflectionFetchType): ?GenericObjectType
{
if (!$statementType instanceof GenericObjectType) {
return null;
}
$queryReflection = new QueryReflection();
$bothType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH);

$genericTypes = $statementType->getTypes();
if (1 !== \count($genericTypes)) {
return null;
if ($bothType) {
$rowTypeInFetchMode = $this->reduceStatementResultType($bothType, $reflectionFetchType);

return new GenericObjectType(PDOStatement::class, [$rowTypeInFetchMode, $bothType]);
}

$resultType = $genericTypes[0];
return null;
}

// turn ASSOC typed statement into a NUMERIC one
$defaultFetchType = QueryReflection::getRuntimeConfiguration()->getDefaultFetchMode();
if (QueryReflector::FETCH_TYPE_ASSOC === $defaultFetchType && QueryReflector::FETCH_TYPE_NUMERIC === $fetchType &&
$resultType instanceof ConstantArrayType && \count($resultType->getValueTypes()) > 0) {
$builder = ConstantArrayTypeBuilder::createEmpty();
/**
* @param QueryReflector::FETCH_TYPE* $fetchType
*/
public function getRowType(GenericObjectType $statementType, int $fetchType): ?Type
{
$genericTypes = $statementType->getTypes();

$valueTypes = $resultType->getValueTypes();
if (2 !== \count($genericTypes)) {
return null;
}

$i = 0;
foreach ($valueTypes as $valueType) {
$builder->setOffsetValueType(new ConstantIntegerType($i), $valueType);
++$i;
}
$bothType = $genericTypes[1];

return $builder->getArray();
}
return $this->reduceStatementResultType($bothType, $fetchType);
}

/**
* @param QueryReflector::FETCH_TYPE* $fetchType
*/
private function reduceStatementResultType(Type $bothType, int $fetchType): Type
{
// turn a BOTH typed statement into either NUMERIC or ASSOC
if (QueryReflector::FETCH_TYPE_BOTH === $defaultFetchType &&
if (
(QueryReflector::FETCH_TYPE_NUMERIC === $fetchType || QueryReflector::FETCH_TYPE_ASSOC === $fetchType) &&
$resultType instanceof ConstantArrayType && \count($resultType->getValueTypes()) > 0) {
$bothType instanceof ConstantArrayType && \count($bothType->getValueTypes()) > 0
) {
$builder = ConstantArrayTypeBuilder::createEmpty();

$keyTypes = $resultType->getKeyTypes();
$valueTypes = $resultType->getValueTypes();
$keyTypes = $bothType->getKeyTypes();
$valueTypes = $bothType->getValueTypes();

foreach ($keyTypes as $i => $keyType) {
if (QueryReflector::FETCH_TYPE_NUMERIC === $fetchType && $keyType instanceof ConstantIntegerType) {
Expand All @@ -107,6 +116,7 @@ public function getStatementResultType(Type $statementType, int $fetchType): ?Ty
return $builder->getArray();
}

return $resultType;
// not yet supported fetch type - or $fetchType == BOTH
return $bothType;
}
}
2 changes: 1 addition & 1 deletion src/QueryReflection/RuntimeConfiguration.php
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ public function errorMode(string $mode): self
* Defines the PDO default fetch mode.
* This might be necessary in case you are using `\PDO::ATTR_DEFAULT_FETCH_MODE`.
*
* @param QueryReflector::FETCH_TYPE_BOTH|QueryReflector::FETCH_TYPE_ASSOC $mode
* @param QueryReflector::FETCH_TYPE_* $mode
*/
public function defaultFetchMode(int $mode): self
{
Expand Down
Loading