diff --git a/.phpstan-dba-mysqli.cache b/.phpstan-dba-mysqli.cache index a889d7168..158295a2d 100644 --- a/.phpstan-dba-mysqli.cache +++ b/.phpstan-dba-mysqli.cache @@ -1,6 +1,6 @@ 'v11-phpstan1_9_3-update', - 'schemaHash' => NULL, + 'schemaHash' => '436d43fc96b9dd0cfad4bbc2837886e4', 'records' => array ( 'SELECT @@ -571,7 +571,6 @@ WHERE table_name = \'1970-01-01\'' => 'cachedDescriptions' => array ( 4 => 'string|null', - 3 => 'string|null', ), 'types' => array ( diff --git a/.phpstan-dba-pdo-mysql.cache b/.phpstan-dba-pdo-mysql.cache index a889d7168..07d843bd6 100644 --- a/.phpstan-dba-pdo-mysql.cache +++ b/.phpstan-dba-pdo-mysql.cache @@ -571,7 +571,6 @@ WHERE table_name = \'1970-01-01\'' => 'cachedDescriptions' => array ( 4 => 'string|null', - 3 => 'string|null', ), 'types' => array ( diff --git a/composer.json b/composer.json index 54cad3822..eeb5c1927 100644 --- a/composer.json +++ b/composer.json @@ -15,10 +15,12 @@ "doctrine/dbal": "^3.2", "friendsofphp/php-cs-fixer": "3.4.0", "php-parallel-lint/php-parallel-lint": "^1.3", + "phpstan/extension-installer": "^1.2", "phpstan/phpstan-php-parser": "^1.1", "phpstan/phpstan-phpunit": "^1.0", "phpstan/phpstan-strict-rules": "^1.1", "phpunit/phpunit": "^9", + "tomasvotruba/unused-public": "^0.0.26", "vlucas/phpdotenv": "^5.4" }, "conflicts": { @@ -86,7 +88,8 @@ "config": { "sort-packages": true, "allow-plugins": { - "composer/package-versions-deprecated": true + "composer/package-versions-deprecated": true, + "phpstan/extension-installer": true } }, "extra": { diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon new file mode 100644 index 000000000..56d8883f1 --- /dev/null +++ b/phpstan-baseline.neon @@ -0,0 +1,51 @@ +parameters: + ignoreErrors: + - + message: '#^Instanceof between mysqli_result\\> and mysqli_result will always evaluate to true\.$#' + count: 1 + path: src/DbSchema/SchemaHasherMysql.php + + - + message: "#^Instanceof between mysqli_result\\\\|string\\|null\\>\\> and mysqli_result will always evaluate to true\\.$#" + count: 1 + path: src/DbSchema/SchemaHasherMysql.php + + - + message: "#^Strict comparison using \\!\\=\\= between null and PHPStan\\\\Type\\\\Type will always evaluate to true\\.$#" + count: 1 + path: src/Extensions/PdoStatementFetchDynamicReturnTypeExtension.php + + - + message: "#^Strict comparison using \\=\\=\\= between 'dibi' and 'dibi' will always evaluate to true\\.$#" + count: 1 + path: src/QueryReflection/DbaApi.php + + - + message: "#^Return type \\(array\\\\}\\>\\|PDOException\\|null\\) of method staabm\\\\PHPStanDba\\\\QueryReflection\\\\PdoPgSqlQueryReflector\\:\\:simulateQuery\\(\\) should be covariant with return type \\(array\\, precision\\: int\\<0, max\\>, pdo_type\\: 0\\|1\\|2\\|3\\|4\\|5\\|6\\|536870912\\|1073741824\\|2147483648\\}\\>\\|PDOException\\|null\\) of method staabm\\\\PHPStanDba\\\\QueryReflection\\\\BasePdoQueryReflector\\:\\:simulateQuery\\(\\)$#" + count: 1 + path: src/QueryReflection/PdoPgSqlQueryReflector.php + + - + message: "#^Casting to \\*NEVER\\* something that's already \\*NEVER\\*\\.$#" + count: 1 + path: src/QueryReflection/QueryReflection.php + + - + message: "#^Instanceof between PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType and PHPStan\\\\Type\\\\Constant\\\\ConstantIntegerType will always evaluate to true\\.$#" + count: 1 + path: src/QueryReflection/QueryReflection.php + + - + message: "#^Strict comparison using \\=\\=\\= between null and null will always evaluate to true\\.$#" + count: 1 + path: src/QueryReflection/QueryReflection.php + + - + message: "#^Call to function is_string\\(\\) with string will always evaluate to true\\.$#" + count: 2 + path: src/TypeMapping/MysqliTypeMapper.php + + - + message: "#^Only booleans are allowed in an if condition, int given\\.$#" + count: 1 + path: src/TypeMapping/MysqliTypeMapper.php diff --git a/phpstan.neon.dist b/phpstan.neon.dist index a7972aa2c..d44cf1904 100644 --- a/phpstan.neon.dist +++ b/phpstan.neon.dist @@ -1,6 +1,7 @@ includes: - config/stubFiles.neon - config/extensions.neon + - phpstan-baseline.neon parameters: level: max @@ -8,6 +9,11 @@ parameters: paths: - src/ + unused_public: + methods: true + properties: true + constants: true + bootstrapFiles: - bootstrap.php diff --git a/src/Analyzer/QueryPlanAnalyzer.php b/src/Analyzer/QueryPlanAnalyzer.php index ee57b749e..5b740321b 100644 --- a/src/Analyzer/QueryPlanAnalyzer.php +++ b/src/Analyzer/QueryPlanAnalyzer.php @@ -4,6 +4,9 @@ namespace staabm\PHPStanDba\Analyzer; +/** + * @api + */ final class QueryPlanAnalyzer { /** diff --git a/src/Analyzer/QueryPlanAnalyzerMysql.php b/src/Analyzer/QueryPlanAnalyzerMysql.php index ac6a02456..8b313a3ff 100644 --- a/src/Analyzer/QueryPlanAnalyzerMysql.php +++ b/src/Analyzer/QueryPlanAnalyzerMysql.php @@ -12,14 +12,20 @@ final class QueryPlanAnalyzerMysql { /** + * @api + * * @deprecated use QueryPlanAnalyzer::DEFAULT_UNINDEXED_READS_THRESHOLD instead */ public const DEFAULT_UNINDEXED_READS_THRESHOLD = QueryPlanAnalyzer::DEFAULT_UNINDEXED_READS_THRESHOLD; /** + * @api + * * @deprecated use QueryPlanAnalyzer::TABLES_WITHOUT_DATA instead */ public const TABLES_WITHOUT_DATA = QueryPlanAnalyzer::TABLES_WITHOUT_DATA; /** + * @api + * * @deprecated use QueryPlanAnalyzer::DEFAULT_SMALL_TABLE_THRESHOLD instead */ public const DEFAULT_SMALL_TABLE_THRESHOLD = QueryPlanAnalyzer::DEFAULT_SMALL_TABLE_THRESHOLD; diff --git a/src/Ast/ExpressionFinder.php b/src/Ast/ExpressionFinder.php index 6c71d7c8e..f6930e84a 100644 --- a/src/Ast/ExpressionFinder.php +++ b/src/Ast/ExpressionFinder.php @@ -27,6 +27,8 @@ public function __construct() } /** + * @api + * * @param Variable|MethodCall $expr * * @deprecated use findAssignmentExpression() instead diff --git a/src/Ast/PreviousConnectingVisitor.php b/src/Ast/PreviousConnectingVisitor.php index c3d1df7e6..93fe934cf 100644 --- a/src/Ast/PreviousConnectingVisitor.php +++ b/src/Ast/PreviousConnectingVisitor.php @@ -14,7 +14,7 @@ final class PreviousConnectingVisitor extends NodeVisitorAbstract public const ATTRIBUTE_PREVIOUS = 'dba-previous'; /** - * @var Node[] + * @var list */ private $stack = []; @@ -33,7 +33,7 @@ public function beforeTraverse(array $nodes) public function enterNode(Node $node) { - if (!empty($this->stack)) { + if ([] !== $this->stack) { $node->setAttribute(self::ATTRIBUTE_PARENT, $this->stack[\count($this->stack) - 1]); } diff --git a/src/Error.php b/src/Error.php index f966888c0..4e4e8cbf4 100644 --- a/src/Error.php +++ b/src/Error.php @@ -7,6 +7,8 @@ use staabm\PHPStanDba\QueryReflection\QuerySimulation; /** + * @api + * * @phpstan-type ErrorCodes value-of|value-of */ final class Error diff --git a/src/PdoReflection/PdoStatementReflection.php b/src/PdoReflection/PdoStatementReflection.php index 152b1c361..cb815bd30 100644 --- a/src/PdoReflection/PdoStatementReflection.php +++ b/src/PdoReflection/PdoStatementReflection.php @@ -87,7 +87,7 @@ public function createGenericStatement(iterable $queryStrings, int $reflectionFe foreach ($queryStrings as $queryString) { $bothType = $queryReflection->getResultType($queryString, QueryReflector::FETCH_TYPE_BOTH); - if ($bothType) { + if (null !== $bothType) { $genericObjects[] = new PdoStatementObjectType($bothType, $reflectionFetchType); } } diff --git a/src/PhpDoc/PhpDocUtil.php b/src/PhpDoc/PhpDocUtil.php index 373ca848a..07809f0f3 100644 --- a/src/PhpDoc/PhpDocUtil.php +++ b/src/PhpDoc/PhpDocUtil.php @@ -30,7 +30,7 @@ public static function matchTaintEscape($callLike, Scope $scope): ?string // atm no resolved phpdoc for methods // see https://github.com/phpstan/phpstan/discussions/7657 $phpDocString = $methodReflection->getDocComment(); - if (null !== $phpDocString && preg_match('/@psalm-taint-escape\s+(\S+).*$/m', $phpDocString, $matches)) { + if (null !== $phpDocString && 1 === preg_match('/@psalm-taint-escape\s+(\S+).*$/m', $phpDocString, $matches)) { return $matches[1]; } } @@ -91,7 +91,7 @@ private static function matchStringAnnotation(string $annotation, $callLike, Sco // atm no resolved phpdoc for methods // see https://github.com/phpstan/phpstan/discussions/7657 $phpDocString = $methodReflection->getDocComment(); - if (null !== $phpDocString && preg_match('/'.$annotation.'\s+(\S+).*$/m', $phpDocString, $matches)) { + if (null !== $phpDocString && 1 === preg_match('/'.$annotation.'\s+(\S+).*$/m', $phpDocString, $matches)) { $placeholder = $matches[1]; if (\in_array($placeholder[0], ['"', "'"], true)) { diff --git a/src/QueryReflection/ChainedReflector.php b/src/QueryReflection/ChainedReflector.php index b61abed3c..9a660f000 100644 --- a/src/QueryReflection/ChainedReflector.php +++ b/src/QueryReflection/ChainedReflector.php @@ -30,7 +30,7 @@ public function validateQueryString(string $queryString): ?Error $reflectorError = $reflector->validateQueryString($queryString); // on "not found" error, we try the next reflector. - if ($reflectorError) { + if (null !== $reflectorError) { if (!\in_array($reflectorError->getCode(), [MysqliQueryReflector::MYSQL_UNKNOWN_TABLE], true)) { return $reflectorError; } @@ -54,7 +54,7 @@ public function getResultType(string $queryString, int $fetchType): ?Type foreach ($this->reflectors as $reflector) { $reflectorResult = $reflector->getResultType($queryString, $fetchType); - if ($reflectorResult) { + if (null !== $reflectorResult) { return $reflectorResult; } } diff --git a/src/QueryReflection/MysqliQueryReflector.php b/src/QueryReflection/MysqliQueryReflector.php index 1e54d452e..3c931c9d4 100644 --- a/src/QueryReflection/MysqliQueryReflector.php +++ b/src/QueryReflection/MysqliQueryReflector.php @@ -20,10 +20,11 @@ final class MysqliQueryReflector implements QueryReflector, RecordingReflector private const MYSQL_SYNTAX_ERROR_CODE = 1064; private const MYSQL_UNKNOWN_COLUMN_IN_FIELDLIST = 1054; public const MYSQL_UNKNOWN_TABLE = 1146; - public const MYSQL_INCORRECT_TABLE = 1103; - - public const MYSQL_HOST_NOT_FOUND = 2002; + private const MYSQL_INCORRECT_TABLE = 1103; + /** + * @api + */ public const NAME = 'mysqli'; private const MYSQL_ERROR_CODES = [ diff --git a/src/QueryReflection/Parameter.php b/src/QueryReflection/Parameter.php index 965380e43..20817b410 100644 --- a/src/QueryReflection/Parameter.php +++ b/src/QueryReflection/Parameter.php @@ -6,6 +6,9 @@ use PHPStan\Type\Type; +/** + * @api + */ final class Parameter { /** diff --git a/src/QueryReflection/PdoMysqlQueryReflector.php b/src/QueryReflection/PdoMysqlQueryReflector.php index 12547a4f6..ae6127b65 100644 --- a/src/QueryReflection/PdoMysqlQueryReflector.php +++ b/src/QueryReflection/PdoMysqlQueryReflector.php @@ -19,6 +19,9 @@ */ class PdoMysqlQueryReflector extends BasePdoQueryReflector { + /** + * @api + */ public const NAME = 'pdo-mysql'; public function __construct(PDO $pdo) diff --git a/src/QueryReflection/PdoPgSqlQueryReflector.php b/src/QueryReflection/PdoPgSqlQueryReflector.php index 5e00069a5..f513135e2 100644 --- a/src/QueryReflection/PdoPgSqlQueryReflector.php +++ b/src/QueryReflection/PdoPgSqlQueryReflector.php @@ -17,6 +17,9 @@ */ final class PdoPgSqlQueryReflector extends BasePdoQueryReflector { + /** + * @api + */ public const NAME = 'pdo-pgsql'; public function __construct(PDO $pdo) diff --git a/src/QueryReflection/QueryReflection.php b/src/QueryReflection/QueryReflection.php index dbb4a3457..74daab7c3 100644 --- a/src/QueryReflection/QueryReflection.php +++ b/src/QueryReflection/QueryReflection.php @@ -48,6 +48,9 @@ public function __construct(?DbaApi $dbaApi = null) self::reflector()->setupDbaApi($dbaApi); } + /** + * @api + */ public static function setupReflector(QueryReflector $reflector, RuntimeConfiguration $runtimeConfiguration): void { self::$reflector = $reflector; @@ -56,7 +59,7 @@ public static function setupReflector(QueryReflector $reflector, RuntimeConfigur public function validateQueryString(string $queryString): ?Error { - if ('SELECT' !== $this->getQueryType($queryString)) { + if ('SELECT' !== self::getQueryType($queryString)) { return null; } @@ -73,7 +76,7 @@ public function validateQueryString(string $queryString): ?Error */ public function getResultType(string $queryString, int $fetchType): ?Type { - if ('SELECT' !== $this->getQueryType($queryString)) { + if ('SELECT' !== self::getQueryType($queryString)) { return null; } @@ -111,6 +114,8 @@ public function resolvePreparedQueryStrings(Expr $queryExpr, Type $parameterType } /** + * @api + * * @deprecated use resolvePreparedQueryStrings() instead * * @throws UnresolvableQueryException @@ -257,7 +262,7 @@ public static function getQueryType(string $query): ?string { $query = ltrim($query); - if (preg_match('/^\s*\(?\s*(SELECT|SHOW|UPDATE|INSERT|DELETE|REPLACE|CREATE|CALL|OPTIMIZE)/i', $query, $matches)) { + if (1 === preg_match('/^\s*\(?\s*(SELECT|SHOW|UPDATE|INSERT|DELETE|REPLACE|CREATE|CALL|OPTIMIZE)/i', $query, $matches)) { return strtoupper($matches[1]); } diff --git a/src/QueryReflection/QuerySimulation.php b/src/QueryReflection/QuerySimulation.php index 52cf2bd49..1a7b55e4b 100644 --- a/src/QueryReflection/QuerySimulation.php +++ b/src/QueryReflection/QuerySimulation.php @@ -27,7 +27,7 @@ */ final class QuerySimulation { - public const DATE_FORMAT = 'Y-m-d'; + private const DATE_FORMAT = 'Y-m-d'; /** * @throws UnresolvableQueryException diff --git a/src/QueryReflection/ReflectionCache.php b/src/QueryReflection/ReflectionCache.php index e7f4e9270..d78262ab6 100644 --- a/src/QueryReflection/ReflectionCache.php +++ b/src/QueryReflection/ReflectionCache.php @@ -13,7 +13,7 @@ final class ReflectionCache { - public const SCHEMA_VERSION = 'v11-phpstan1_9_3-update'; + private const SCHEMA_VERSION = 'v11-phpstan1_9_3-update'; /** * @var string @@ -67,6 +67,13 @@ private function __construct(string $cacheFile) } } + /** + * @api + * + * @param non-empty-string $cacheFile + * + * @return static + */ public static function create(string $cacheFile): self { return new self($cacheFile); @@ -74,6 +81,8 @@ public static function create(string $cacheFile): self /** * @deprecated use create() instead + * + * @api */ public static function load(string $cacheFile): self { diff --git a/src/QueryReflection/RuntimeConfiguration.php b/src/QueryReflection/RuntimeConfiguration.php index 0e8d44271..baa93314d 100644 --- a/src/QueryReflection/RuntimeConfiguration.php +++ b/src/QueryReflection/RuntimeConfiguration.php @@ -7,6 +7,9 @@ use PHPStan\Php\PhpVersion; use staabm\PHPStanDba\Analyzer\QueryPlanAnalyzer; +/** + * @api + */ final class RuntimeConfiguration { /** diff --git a/src/TypeMapping/MysqliTypeMapper.php b/src/TypeMapping/MysqliTypeMapper.php index 61078fc53..575823b6e 100644 --- a/src/TypeMapping/MysqliTypeMapper.php +++ b/src/TypeMapping/MysqliTypeMapper.php @@ -29,12 +29,12 @@ public function __construct(?DbaApi $dbaApi) // skip bool constants like MYSQLI_IS_MARIADB continue; } - if (preg_match('/^MYSQLI_TYPE_(.*)/', $c, $m)) { + if (1 === preg_match('/^MYSQLI_TYPE_(.*)/', $c, $m)) { if (!\is_string($m[1])) { throw new ShouldNotHappenException(); } $this->nativeTypes[$n] = $m[1]; - } elseif (preg_match('/MYSQLI_(.*)_FLAG$/', $c, $m)) { + } elseif (1 === preg_match('/MYSQLI_(.*)_FLAG$/', $c, $m)) { if (!\is_string($m[1])) { throw new ShouldNotHappenException(); } diff --git a/tests/default/QuerySimulationTest.php b/tests/default/QuerySimulationTest.php index b38d1c570..f0cfa52b0 100644 --- a/tests/default/QuerySimulationTest.php +++ b/tests/default/QuerySimulationTest.php @@ -18,7 +18,7 @@ public function testIntersectionTypeInt() $builder->setOffsetValueType(new IntegerType(), new IntegerType()); $simulatedValue = QuerySimulation::simulateParamValueType($builder->getArray(), false); - $this->assertNotNull($simulatedValue); + self::assertNotNull($simulatedValue); } public function testIntersectionTypeString() @@ -28,7 +28,7 @@ public function testIntersectionTypeString() $builder->setOffsetValueType(new StringType(), new IntegerType()); $simulatedValue = QuerySimulation::simulateParamValueType($builder->getArray(), false); - $this->assertNotNull($simulatedValue); + self::assertNotNull($simulatedValue); } public function testIntersectionTypeMix() @@ -39,7 +39,7 @@ public function testIntersectionTypeMix() $builder->setOffsetValueType(new IntegerType(), new FloatType()); $simulatedValue = QuerySimulation::simulateParamValueType($builder->getArray(), false); - $this->assertNotNull($simulatedValue); + self::assertNotNull($simulatedValue); } /** diff --git a/tests/rules/QueryPlanAnalyzerRuleTest.php b/tests/rules/QueryPlanAnalyzerRuleTest.php index 435778955..597b086a0 100644 --- a/tests/rules/QueryPlanAnalyzerRuleTest.php +++ b/tests/rules/QueryPlanAnalyzerRuleTest.php @@ -49,11 +49,11 @@ public static function getAdditionalConfigFiles(): array public function testNotUsingIndex(): void { if ('pdo-pgsql' === getenv('DBA_REFLECTOR')) { - $this->markTestSkipped('query plan analyzer is not yet implemented for pgsql'); + self::markTestSkipped('query plan analyzer is not yet implemented for pgsql'); } if ('recording' !== getenv('DBA_MODE')) { - $this->markTestSkipped('query plan analyzer requires a active database connection'); + self::markTestSkipped('query plan analyzer requires a active database connection'); } $this->numberOfAllowedUnindexedReads = true; @@ -94,11 +94,11 @@ public function testNotUsingIndex(): void public function testNotUsingIndexInDebugMode(): void { if ('pdo-pgsql' === getenv('DBA_REFLECTOR')) { - $this->markTestSkipped('query plan analyzer is not yet implemented for pgsql'); + self::markTestSkipped('query plan analyzer is not yet implemented for pgsql'); } if ('recording' !== getenv('DBA_MODE')) { - $this->markTestSkipped('query plan analyzer requires a active database connection'); + self::markTestSkipped('query plan analyzer requires a active database connection'); } $this->debugMode = true;