diff --git a/composer.json b/composer.json index 77155e4c..2e4090fb 100644 --- a/composer.json +++ b/composer.json @@ -8,7 +8,7 @@ "php": "^7.4 || ^8.0", "composer-runtime-api": "^2.0", "composer/semver": "^3.2", - "phpstan/phpstan": "^2.1.2" + "phpstan/phpstan": "^2.3.0" }, "require-dev": { "ext-mysqli": "*", diff --git a/config/extensions.neon b/config/extensions.neon index a0117179..43737431 100644 --- a/config/extensions.neon +++ b/config/extensions.neon @@ -102,4 +102,4 @@ services: - class: staabm\PHPStanDba\Extensions\DbSchemaResultCacheMetaExtension tags: - - phpstan.resultCacheMetaExtension + - phpstan.resultCacheValueExtension diff --git a/src/Extensions/DbSchemaResultCacheMetaExtension.php b/src/Extensions/DbSchemaResultCacheMetaExtension.php index 91420b33..90b07b30 100644 --- a/src/Extensions/DbSchemaResultCacheMetaExtension.php +++ b/src/Extensions/DbSchemaResultCacheMetaExtension.php @@ -4,23 +4,32 @@ namespace staabm\PHPStanDba\Extensions; -use PHPStan\Analyser\ResultCache\ResultCacheMetaExtension; +use PHPStan\Analyser\DependencyTracker; +use PHPStan\Analyser\ResultCache\ResultCacheValueExtension; +use PHPStan\Analyser\Scope; use staabm\PHPStanDba\DbaException; use staabm\PHPStanDba\QueryReflection\QueryReflection; -// @phpstan-ignore class.implementsDeprecatedInterface -final class DbSchemaResultCacheMetaExtension implements ResultCacheMetaExtension +final class DbSchemaResultCacheMetaExtension implements ResultCacheValueExtension { - public function getKey(): string + private const SCHEMA_KEY = 'schema'; + + /** + * Marks the currently analysed file as dependent on the database schema. + */ + public static function trackSchemaDependency(Scope $scope): void { - return 'phpstan-dba-schema-hash'; + if ($scope instanceof DependencyTracker) { + $scope->trackValueDependency(self::class, self::SCHEMA_KEY); + } } - public function getHash(): string + public function getValue(string $key): string { try { $queryReflection = new QueryReflection(); $schemaHasher = $queryReflection->getSchemaHasher(); + return $schemaHasher->hashDb(); } catch (DbaException $e) { // don't break the whole process when reflector is not @@ -29,4 +38,14 @@ public function getHash(): string return 'reflector-not-initialized'; } } + + public function keyToResultCache(string $key): string + { + return $key; + } + + public function keyFromResultCache(string $storedKey): string + { + return $storedKey; + } } diff --git a/src/Extensions/DeployerRunMysqlQueryDynamicReturnTypeExtension.php b/src/Extensions/DeployerRunMysqlQueryDynamicReturnTypeExtension.php index 1834365c..6b81811a 100644 --- a/src/Extensions/DeployerRunMysqlQueryDynamicReturnTypeExtension.php +++ b/src/Extensions/DeployerRunMysqlQueryDynamicReturnTypeExtension.php @@ -37,6 +37,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return null; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $queryReflection = new QueryReflection(); $queryString = $queryReflection->resolveQueryString($args[0]->value, $scope); if (null === $queryString) { diff --git a/src/Extensions/DibiConnectionFetchDynamicReturnTypeExtension.php b/src/Extensions/DibiConnectionFetchDynamicReturnTypeExtension.php index 97f873a4..67aa5751 100644 --- a/src/Extensions/DibiConnectionFetchDynamicReturnTypeExtension.php +++ b/src/Extensions/DibiConnectionFetchDynamicReturnTypeExtension.php @@ -51,6 +51,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return null; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { return $this->inferType($methodReflection, $args[0]->value, $scope); } catch (UnresolvableQueryException $exception) { diff --git a/src/Extensions/DoctrineConnectionExecuteQueryDynamicReturnTypeExtension.php b/src/Extensions/DoctrineConnectionExecuteQueryDynamicReturnTypeExtension.php index bbc43815..12f9105c 100644 --- a/src/Extensions/DoctrineConnectionExecuteQueryDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineConnectionExecuteQueryDynamicReturnTypeExtension.php @@ -48,6 +48,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return null; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $params = null; if (\count($args) > 1) { $params = $args[1]->value; diff --git a/src/Extensions/DoctrineConnectionFetchDynamicReturnTypeExtension.php b/src/Extensions/DoctrineConnectionFetchDynamicReturnTypeExtension.php index 57d866ac..771a08f7 100644 --- a/src/Extensions/DoctrineConnectionFetchDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineConnectionFetchDynamicReturnTypeExtension.php @@ -61,6 +61,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return null; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $params = null; if (\count($args) > 1) { $params = $args[1]->value; diff --git a/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php b/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php index 2f6987a8..f865190c 100644 --- a/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineConnectionPrepareDynamicReturnTypeExtension.php @@ -48,6 +48,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return null; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { return $this->inferType($args[0]->value, $scope); } catch (UnresolvableQueryException $exception) { diff --git a/src/Extensions/DoctrineConnectionQueryDynamicReturnTypeExtension.php b/src/Extensions/DoctrineConnectionQueryDynamicReturnTypeExtension.php index 8ff4436c..c8be5b10 100644 --- a/src/Extensions/DoctrineConnectionQueryDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineConnectionQueryDynamicReturnTypeExtension.php @@ -48,6 +48,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return null; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { return $this->inferType($args[0]->value, $scope); } catch (UnresolvableQueryException $exception) { diff --git a/src/Extensions/DoctrineResultDynamicReturnTypeExtension.php b/src/Extensions/DoctrineResultDynamicReturnTypeExtension.php index fb6a6aa4..1570e9ea 100644 --- a/src/Extensions/DoctrineResultDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineResultDynamicReturnTypeExtension.php @@ -52,6 +52,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return null; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { return $this->inferType($methodReflection, $methodCall, $scope); } catch (UnresolvableQueryException $exception) { diff --git a/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php b/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php index 9642d453..7b02cfd6 100644 --- a/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php +++ b/src/Extensions/DoctrineStatementExecuteDynamicReturnTypeExtension.php @@ -44,6 +44,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return null; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { return $this->inferType($methodReflection, $methodCall, $args[0]->value, $scope); } catch (UnresolvableQueryException $exception) { diff --git a/src/Extensions/MysqliEscapeStringDynamicReturnTypeExtension.php b/src/Extensions/MysqliEscapeStringDynamicReturnTypeExtension.php index 422489b3..9055b240 100644 --- a/src/Extensions/MysqliEscapeStringDynamicReturnTypeExtension.php +++ b/src/Extensions/MysqliEscapeStringDynamicReturnTypeExtension.php @@ -64,6 +64,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $defaultReturn; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $argType = $scope->getType($args[1]->value); return $this->inferType($argType); @@ -86,6 +88,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return $defaultReturn; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $argType = $scope->getType($args[0]->value); return $this->inferType($argType); diff --git a/src/Extensions/MysqliQueryDynamicReturnTypeExtension.php b/src/Extensions/MysqliQueryDynamicReturnTypeExtension.php index 7bf71249..21b31531 100644 --- a/src/Extensions/MysqliQueryDynamicReturnTypeExtension.php +++ b/src/Extensions/MysqliQueryDynamicReturnTypeExtension.php @@ -65,6 +65,8 @@ public function getTypeFromFunctionCall(FunctionReflection $functionReflection, return $defaultReturn; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { $resultType = $this->inferResultType($args[1]->value, $scope); if (null !== $resultType) { @@ -98,6 +100,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return $defaultReturn; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { $resultType = $this->inferResultType($args[0]->value, $scope); if (null !== $resultType) { diff --git a/src/Extensions/PdoPrepareDynamicReturnTypeExtension.php b/src/Extensions/PdoPrepareDynamicReturnTypeExtension.php index e57e9164..75527f67 100644 --- a/src/Extensions/PdoPrepareDynamicReturnTypeExtension.php +++ b/src/Extensions/PdoPrepareDynamicReturnTypeExtension.php @@ -60,6 +60,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return $defaultReturn; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { $resultType = $this->inferType($args[0]->value, $scope); if (null !== $resultType) { diff --git a/src/Extensions/PdoQueryDynamicReturnTypeExtension.php b/src/Extensions/PdoQueryDynamicReturnTypeExtension.php index bb6a487a..a2a16741 100644 --- a/src/Extensions/PdoQueryDynamicReturnTypeExtension.php +++ b/src/Extensions/PdoQueryDynamicReturnTypeExtension.php @@ -62,6 +62,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return $defaultReturn; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { $resultType = $this->inferType($methodCall, $args[0]->value, $scope); if (null !== $resultType) { diff --git a/src/Extensions/PdoQuoteDynamicReturnTypeExtension.php b/src/Extensions/PdoQuoteDynamicReturnTypeExtension.php index 1a4cf50f..0e0f75db 100644 --- a/src/Extensions/PdoQuoteDynamicReturnTypeExtension.php +++ b/src/Extensions/PdoQuoteDynamicReturnTypeExtension.php @@ -58,6 +58,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method return $defaultReturn; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { $stringType = $this->inferType($methodCall, $scope); if (null !== $stringType) { diff --git a/src/Extensions/PdoStatementColumnCountDynamicReturnTypeExtension.php b/src/Extensions/PdoStatementColumnCountDynamicReturnTypeExtension.php index 6af01635..53df161b 100644 --- a/src/Extensions/PdoStatementColumnCountDynamicReturnTypeExtension.php +++ b/src/Extensions/PdoStatementColumnCountDynamicReturnTypeExtension.php @@ -31,6 +31,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method $statementType = $scope->getType($methodCall->var); if ($statementType instanceof PdoStatementObjectType) { + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $arrays = $statementType->newWithFetchType(QueryReflector::FETCH_TYPE_NUMERIC)->getRowType()->getConstantArrays(); if (count($arrays) === 1) { $rowType = $arrays[0]; diff --git a/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php b/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php index 310b275b..528cf550 100644 --- a/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php +++ b/src/Extensions/PdoStatementExecuteTypeSpecifyingExtension.php @@ -45,6 +45,8 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $nod // keep original param name because named-parameters $methodCall = $node; + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { $inferredType = $this->inferStatementType($methodReflection, $methodCall, $scope); } catch (UnresolvableQueryException $e) { diff --git a/src/Extensions/PdoStatementFetchDynamicReturnTypeExtension.php b/src/Extensions/PdoStatementFetchDynamicReturnTypeExtension.php index 64178009..d9fcd2c0 100644 --- a/src/Extensions/PdoStatementFetchDynamicReturnTypeExtension.php +++ b/src/Extensions/PdoStatementFetchDynamicReturnTypeExtension.php @@ -54,6 +54,8 @@ public function getTypeFromMethodCall(MethodReflection $methodReflection, Method $methodReflection->getVariants() )->getReturnType(); + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { $resultType = $this->inferType($methodReflection, $methodCall, $scope); if (null !== $resultType) { diff --git a/src/Extensions/PdoStatementFetchObjectDynamicReturnTypeExtension.php b/src/Extensions/PdoStatementFetchObjectDynamicReturnTypeExtension.php index c95fbdb8..63f8a874 100644 --- a/src/Extensions/PdoStatementFetchObjectDynamicReturnTypeExtension.php +++ b/src/Extensions/PdoStatementFetchObjectDynamicReturnTypeExtension.php @@ -37,6 +37,8 @@ public function isMethodSupported(MethodReflection $methodReflection): bool public function getTypeFromMethodCall(MethodReflection $methodReflection, MethodCall $methodCall, Scope $scope): ?Type { + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { return $this->inferType($methodReflection, $methodCall, $scope); } catch (UnresolvableQueryException $exception) { diff --git a/src/Extensions/PdoStatementSetFetchModeTypeSpecifyingExtension.php b/src/Extensions/PdoStatementSetFetchModeTypeSpecifyingExtension.php index 1b5de86c..50765611 100644 --- a/src/Extensions/PdoStatementSetFetchModeTypeSpecifyingExtension.php +++ b/src/Extensions/PdoStatementSetFetchModeTypeSpecifyingExtension.php @@ -42,6 +42,8 @@ public function specifyTypes(MethodReflection $methodReflection, MethodCall $nod $statementType = $scope->getType($methodCall->var); if ($statementType instanceof PdoStatementObjectType) { + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $reducedType = $this->reduceType($methodCall, $statementType, $scope); if (null !== $reducedType) { diff --git a/src/Rules/DoctrineKeyValueStyleRule.php b/src/Rules/DoctrineKeyValueStyleRule.php index a8e408c6..2a370f02 100644 --- a/src/Rules/DoctrineKeyValueStyleRule.php +++ b/src/Rules/DoctrineKeyValueStyleRule.php @@ -23,6 +23,7 @@ use PHPStan\Type\Type; use PHPStan\Type\TypeCombinator; use PHPStan\Type\VerbosityLevel; +use staabm\PHPStanDba\Extensions\DbSchemaResultCacheMetaExtension; use staabm\PHPStanDba\QueryReflection\QueryReflection; /** @@ -111,6 +112,8 @@ public function processNode(Node $callLike, Scope $scope): array return []; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + // Reorder arguments to account for named parameters $parametersAcceptor = ParametersAcceptorSelector::selectFromArgs( $scope, diff --git a/src/Rules/PdoStatementExecuteMethodRule.php b/src/Rules/PdoStatementExecuteMethodRule.php index ea564b8d..a991fee3 100644 --- a/src/Rules/PdoStatementExecuteMethodRule.php +++ b/src/Rules/PdoStatementExecuteMethodRule.php @@ -16,6 +16,7 @@ use PHPStan\Type\Constant\ConstantIntegerType; use PHPStan\Type\Constant\ConstantStringType; use PHPStan\Type\ObjectType; +use staabm\PHPStanDba\Extensions\DbSchemaResultCacheMetaExtension; use staabm\PHPStanDba\PdoReflection\PdoStatementReflection; use staabm\PHPStanDba\QueryReflection\PlaceholderValidation; use staabm\PHPStanDba\QueryReflection\QueryReflection; @@ -72,6 +73,8 @@ private function checkErrors(MethodReflection $methodReflection, MethodCall $met return []; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $args = $methodCall->getArgs(); if (\count($args) < 1) { $parameterKeys = []; diff --git a/src/Rules/QueryPlanAnalyzerRule.php b/src/Rules/QueryPlanAnalyzerRule.php index 43b6eba3..2668a61b 100644 --- a/src/Rules/QueryPlanAnalyzerRule.php +++ b/src/Rules/QueryPlanAnalyzerRule.php @@ -19,6 +19,7 @@ use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; use PHPStan\Type\ObjectType; +use staabm\PHPStanDba\Extensions\DbSchemaResultCacheMetaExtension; use staabm\PHPStanDba\QueryReflection\QueryReflection; use staabm\PHPStanDba\Tests\QueryPlanAnalyzerRuleTest; use staabm\PHPStanDba\UnresolvableQueryException; @@ -107,6 +108,8 @@ public function processNode(Node $callLike, Scope $scope): array return []; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + try { return $this->analyze($callLike, $scope); } catch (UnresolvableQueryException $exception) { diff --git a/src/Rules/SyntaxErrorInDibiPreparedStatementMethodRule.php b/src/Rules/SyntaxErrorInDibiPreparedStatementMethodRule.php index f94d2442..39216006 100644 --- a/src/Rules/SyntaxErrorInDibiPreparedStatementMethodRule.php +++ b/src/Rules/SyntaxErrorInDibiPreparedStatementMethodRule.php @@ -19,6 +19,7 @@ use PHPStan\Type\Constant\ConstantArrayType; use PHPStan\Type\ObjectType; use staabm\PHPStanDba\DibiReflection\DibiReflection; +use staabm\PHPStanDba\Extensions\DbSchemaResultCacheMetaExtension; use staabm\PHPStanDba\QueryReflection\DbaApi; use staabm\PHPStanDba\QueryReflection\QueryReflection; use staabm\PHPStanDba\QueryReflection\QueryReflector; @@ -110,6 +111,8 @@ private function checkErrors(CallLike $callLike, Scope $scope, MethodReflection return []; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $queryReflection = new QueryReflection(new DbaApi(DbaApi::API_DIBI)); $queryParameters = []; $errors = []; diff --git a/src/Rules/SyntaxErrorInPreparedStatementMethodRule.php b/src/Rules/SyntaxErrorInPreparedStatementMethodRule.php index 68774e6e..d0bdd74e 100644 --- a/src/Rules/SyntaxErrorInPreparedStatementMethodRule.php +++ b/src/Rules/SyntaxErrorInPreparedStatementMethodRule.php @@ -20,6 +20,7 @@ use PHPStan\ShouldNotHappenException; use PHPStan\Type\MixedType; use PHPStan\Type\ObjectType; +use staabm\PHPStanDba\Extensions\DbSchemaResultCacheMetaExtension; use staabm\PHPStanDba\QueryReflection\PlaceholderValidation; use staabm\PHPStanDba\QueryReflection\QueryReflection; use staabm\PHPStanDba\UnresolvableQueryException; @@ -120,6 +121,8 @@ private function checkErrors(CallLike $callLike, Scope $scope): array return []; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $queryExpr = $args[0]->value; $queryReflection = new QueryReflection(); diff --git a/src/Rules/SyntaxErrorInQueryFunctionRule.php b/src/Rules/SyntaxErrorInQueryFunctionRule.php index c20c56c1..9d6e141e 100644 --- a/src/Rules/SyntaxErrorInQueryFunctionRule.php +++ b/src/Rules/SyntaxErrorInQueryFunctionRule.php @@ -11,6 +11,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use staabm\PHPStanDba\Extensions\DbSchemaResultCacheMetaExtension; use staabm\PHPStanDba\QueryReflection\QueryReflection; use staabm\PHPStanDba\Tests\SyntaxErrorInQueryFunctionRuleTest; use staabm\PHPStanDba\UnresolvableQueryException; @@ -81,6 +82,8 @@ public function processNode(Node $node, Scope $scope): array return []; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $queryExpr = $args[$queryArgPosition]->value; $queryReflection = new QueryReflection(); diff --git a/src/Rules/SyntaxErrorInQueryMethodRule.php b/src/Rules/SyntaxErrorInQueryMethodRule.php index 457515f4..7e973565 100644 --- a/src/Rules/SyntaxErrorInQueryMethodRule.php +++ b/src/Rules/SyntaxErrorInQueryMethodRule.php @@ -15,6 +15,7 @@ use PHPStan\Rules\Rule; use PHPStan\Rules\RuleErrorBuilder; use PHPStan\ShouldNotHappenException; +use staabm\PHPStanDba\Extensions\DbSchemaResultCacheMetaExtension; use staabm\PHPStanDba\QueryReflection\QueryReflection; use staabm\PHPStanDba\UnresolvableQueryException; @@ -102,6 +103,8 @@ public function processNode(Node $callLike, Scope $scope): array return []; } + DbSchemaResultCacheMetaExtension::trackSchemaDependency($scope); + $queryExpr = $args[$queryArgPosition]->value; $queryReflection = new QueryReflection();